NestFleetDocs

Architecture

NestFleet is a monorepo with a clean separation between the HTTP API, the background worker system, and the Next.js console. All state lives in a single PostgreSQL database. There are no external message queues — job scheduling is handled by pg-boss, which runs inside PostgreSQL.

High-level overview

ComponentTechnologyResponsibility
APIHono (TypeScript)HTTP REST API. Handles all inbound requests: auth, case ingestion, channel webhooks, CRUD.
ConsoleNext.js 15 (App Router)Operator UI. Server components + client islands. Communicates with the API over HTTP.
WorkerNode.js processBackground job executor. Connects to pg-boss and processes AI pipeline jobs.
DatabasePostgreSQL 16 + pgvectorPrimary datastore and job queue (via pg-boss). Vector search for KB matching.
Reverse proxyCaddyTLS termination, HTTP→HTTPS redirect, routing between API and console.

Layered architecture

The API and worker both follow a strict three-layer architecture to separate concerns and make individual pieces independently testable:

LayerWhat it doesExample files
Route (controller)Parses and validates the HTTP request using Zod, calls the service, returns the HTTP response. No business logic.src/api/routes/cases.ts
ServiceContains all business logic. Orchestrates calls to one or more repositories, dispatches pg-boss jobs, enforces rules.src/api/services/case.service.ts
RepositoryExecutes SQL queries against the database. Returns typed domain objects. Never called directly from routes.src/infra/repositories/case.repo.ts

This structure is enforced by convention — ESLint rules prevent direct database access from route files. Services are unit-tested with mocked repositories. Integration tests exercise the full stack (route → service → real DB).

Worker system and pg-boss

NestFleet uses pg-boss to manage background jobs. pg-boss stores job queues as PostgreSQL tables, eliminating the need for a separate Redis or RabbitMQ service. Jobs survive process restarts, have built-in retry logic, and support at-least-once delivery semantics.

Workers are registered at startup in src/workers/index.ts. Each worker is a function that receives a job payload and returns a result. Workers are registered with a queue name that corresponds to the job type:

boss.work("triage", async (job) => {
  await triageWorker(job.data)
})

boss.work("auto_reply", async (job) => {
  await autoReplyWorker(job.data)
})

Jobs are dispatched from services using the pg-boss client:

await boss.send("triage", { caseId: newCase.id })

AI pipeline

The AI pipeline runs as a chain of pg-boss jobs. Each step is independent and can be retried on failure without re-running earlier steps:

JobLLM tierWhat it does
triageFast (LLM_MODEL_FAST)Classifies the case: type, severity, confidence. Produces the reasoning trace. Dispatches known_issue_match if confidence is high enough.
known_issue_matchFast (embedding)Runs a vector similarity search against the knowledge base. Attaches matching articles to the case. Dispatches auto_reply if a close match is found.
auto_replyStandard (LLM_MODEL)Generates a reply draft using the matched articles as RAG context. Routes to approval queue or sends immediately based on product settings.
change_prepComplex (LLM_MODEL_COMPLEX)Analyses the case and produces a structured PR draft with affected surfaces and risk assessment. Used for novel bugs.
embed_articleEmbedding modelEmbeds a new or updated KB article and upserts its vector in pgvector. Triggered when an article is created or updated.

Auth: JWT + RBAC

Authentication is JWT-based. The login endpoint issues a signed access token (short-lived) and a refresh token (long-lived, stored in an httpOnly cookie). All other API routes require a valid Bearer token in the Authorization header.

Authorization is enforced by a requireAuth middleware in src/auth/. It:

  1. Verifies the JWT signature using JWT_SECRET
  2. Loads the user and their roles from the database
  3. Checks the required role(s) for the route
  4. Attaches the authenticated user to the Hono context for downstream handlers

Every API route calls requireAuth() — there is an ESLint rule (no-unprotected-route) that flags routes missing auth middleware during CI.

Encryption

Sensitive values — LLM API keys, webhook secrets, SMTP passwords — are encrypted at rest using AES-256-GCM before being stored in the database. Encryption is performed bysrc/infra/crypto.ts using Node.js's built-incrypto module with a random IV per value. The ENCRYPTION_KEY env var (64 hex chars = 32 bytes) is the key material. Without it, secrets are stored in plaintext with a startup warning.

Key directories

src/
├── api/
│   ├── routes/          # Hono route handlers (thin controllers)
│   ├── services/        # Business logic
│   └── middleware/      # Auth, logging, error handling
├── workers/
│   ├── index.ts         # Worker registration and pg-boss setup
│   ├── triage/          # Triage agent logic
│   ├── auto-reply/      # Auto-reply generation
│   ├── change-prep/     # Change request preparation
│   └── embed/           # Embedding jobs
├── infra/
│   ├── db/
│   │   ├── migrations/  # SQL migration files (up only, sequential)
│   │   └── client.ts    # postgres.js connection pool
│   ├── repositories/    # SQL query functions
│   └── crypto.ts        # AES-256-GCM encryption helpers
├── auth/
│   ├── jwt.ts           # Token sign / verify
│   └── middleware.ts    # requireAuth(), requireRole()
└── shared/
    ├── config.ts        # Zod-validated env var schema
    └── logger.ts        # Structured JSON logger (pino)

Database migrations

Migrations are plain SQL files in src/infra/db/migrations/, named sequentially: 001_initial_schema.sql,002_add_cases.sql, etc. The API applies all pending migrations at startup using a simple migration runner — no ORM, no migration framework dependency. Migrations are idempotent (applied only once, tracked in a_migrations table).