# Adding a Service


Each microservice in this repo is self-contained: own `package.json`, own
DB schema, own `createMicroservice` boot. This is the checklist every new
service follows — nothing more, nothing less.

## 1. Package

```json
{
  "name": "primebrick-<code>",
  "private": true,
  "capabilities": ["<domain_capability>"],
  "scripts": {
    "dev": "bun --hot src/index.ts",
    "build": "tsc",
    "db:migrate": "bun scripts/database-patch-apply.ts",
    "generate:docs": "bun scripts/build-docs-pack.ts"
  },
  "dependencies": {
    "@primebrick/dal-pg": "workspace:*",
    "@primebrick/sdk": "workspace:*"
  }
}
```

- `capabilities[]` is what `ServiceRegistrar` publishes — the BE registry
  and the AI docs-pack pull both read it.
- `version:auto`/`prebuild` call `../scripts/version-sync.mjs` (root
  script) to keep versions aligned across services.

## 2. Boot — `src/index.ts`

One `createMicroservice(options)` call. Required options:

`serviceName`, `envSchema` (closed-set: exactly `DATABASE_URL` +
`DATABASE_SCHEMA` — `assertDbEnv()` parses `DATABASE_SCHEMA` into the
`string[]` domain; `[0]` is the service's owned schema and is sent to the
BE as the `service.register` schema claim), `initDal`/`dalClose`,
`configRepositoryAdapter`, `authConfigPort`, `healthCheckPort`,
`routeHandler` (factory-produced only).

Domain wiring goes in `onReady(ctx)` — NATS subscribers, config holder
init, cron jobs. See SDK's *microservice-anatomy* for the full 12-stage
boot sequence.

## 3. Database — `db-meta/`

```
db-meta/
  patches/           # ordered, sha256-registered migrations (dal-pg runMigrations, at boot)
  MANIFEST.json      # Ed25519-signed patch_name → content_sha256 map
  keys.json          # trusted signing keys (dev/release)
```

Seed at minimum: `http_port`, `service_base_url`,
`service_client_shield_secret` (type `secret` + `_header` companion),
`nats_url`, plus the service's own config keys — all via a numbered patch.

## 4. Routes + Spec — `src/service-routes.ts`

Declare the service's whole HTTP surface ONCE via `makeServiceRoutes` —
it derives BOTH the request routers and `/api/v1/openapi.json` from the
same declaration, so the spec can never drift from the registered routes:

```ts
export const myServiceRouteHandler = makeServiceRoutes({
  info: { title: "... Catalog", version: detectPackageIdentity().version ?? "0.0.0" },
  serverUrl: "http://localhost:30XX",
  tags: [...],
  entities: [ /* EntityRouteDecl — CRUD router + spec ops derived from
    `permissions`/`meta`/`duplicate`; `spec:{tag,label,listResponse}` is
    doc cosmetics only, never a second op list */ ],
  rpc: [ MY_ROUTES /* RpcRoute[] — per-route `openapi` metadata feeds
    the spec; `{ routes, deps }` form for router deps (e.g. apiKeyPort) */ ],
});
```

Pass the result straight to `createMicroservice({ routeHandler })` — it
is factory-branded. The handler carries `.spec` (the built OpenAPI doc):
`scripts/build-docs-pack.ts` embeds `myServiceRouteHandler.spec`. A route
that exists but isn't documented (or vice versa) is impossible by
construction — never hand-compose `buildOpenApiSpec`/`entityCrudSpec`/
`makeOpenApiHandler` separately.

## 6. Docs pack — `scripts/build-docs-pack.ts`

Copy the standard script verbatim, changing `repo`, `docsDir` (omit for
dev-only services) and `openapi`. Build it in the deploy chain —
`ServiceRegistrar` auto-advertises `docs_pack` when `dist/docs-pack/
manifest.json` exists.

## 7. Registration — automatic

On boot the service publishes `service.register`; the BE registry learns
name, base_url, capabilities, endpoints — and the `/ws/<code>/*` proxy
path becomes reachable. Nav/sidebar entries for its screens are declared
by the service itself in the `endpoints.nav` block of the
`service.register` payload (`route_prefixes` + `nav[]` + full `routes`
census — validated at enrollment; the BE's static `module-nav-meta`
registry is reserved for BE-owned modules like `settings`).

**Root level**: the service declares `partOf` in its registration options
(`service_registry.part_of`). Its single FE route namespace is derived,
never free-chosen: `/{part_of}/{code}` — e.g. `partOf: "system"` + code
`job` → all nav hrefs and census routes under `/system/job/**`. When the
service IS its own root (`partOf === code`, e.g. crm) the namespace
collapses to `/{code}` → `/crm/**`. A mismatch is rejected at register
time.

**Display name**: declare `labelKey` (→ `service_registry.label_key`) —
the i18n key for the module's localized name, resolved against the
service's OWN `*.translations` schema (seed it there, like `nav[].label_key`
entries). Every UI surface (app launcher, breadcrumbs, module menus)
renders it via `resolveModuleLabel` with the raw `name` as fallback.

## 8. Docs

`<svc>/docs/dev-guide/index.mdx` — always. `<svc>/docs/user-guide/` only
if the feature is end-user visible. Update `docs/dev-guide/overview.mdx`'s
service catalog.
