Plugin slots & the process tree

Beyond the database, an application plugs into runtime substrate — an event bus, a cache, an observability pipeline. The manifest wires each through a plugin slot: name the plugin and its connection, and the generator emits the client wiring and, where needed, the extra processes to run. You declare the slot; the framework builds the plumbing.

project.vishwakarma
events kafka { bootstrap_servers "${KAFKA_BOOTSTRAP_SERVERS}" }

cache valkey { url "${VALKEY_URL}" }

observe otel { service_name "chowk" endpoint "${OTEL_EXPORTER_OTLP_ENDPOINT}" }

events — the asynchronous spine

The events slot wires the event bus that carries domain events from emitter to subscriber. This is the slot that makes the transactional outbox real: it emits a relay process that drains the outbox table into the broker, and the app's subscriptions compile into a worker process that consumes from the broker and dispatches them.

Without this slot, an event has nowhere to flow — the framework falls back to an in-memory consumer and every asynchronous flow silently no-ops. Declaring events kafka { … } is what turns emits and subscription from declarations into a running pipeline. bootstrap_servers is env-expanded, so the broker address is deploy-specific.

cache — opt-in read acceleration

The cache valkey { … } slot wires a Valkey/Redis-compatible cache. The slot alone only emits the client and connection; an entity opts into it per-read with a cache spec (cache_read / cache_populate / cache_invalidate). So the cache is available everywhere but used only where an aggregate asks for it — no implicit caching to reason around.

observe — instrumentation for free

The observe otel { … } slot wires OpenTelemetry tracing and metrics. Every request, command, query, and subscription is instrumented automatically; spans and metrics export over OTLP to the configured endpoint. Point it at a collector in a deploy, or leave the endpoint env unset for a no-op exporter in local dev. You write no instrumentation code — it comes from the slot.

security — the HTTP security posture

The manifest also declares the project-wide HTTP security surface — CSRF protection and CORS:

project.vishwakarma
security {
  csrf {
    bootstrap "issue_on_reject"
    client { auto_retry true }
  }
  cors {
    allowed_origins ["http://localhost:5173"]
  }
}

This lowers to the security stanza of the emitted config: double-submit CSRF (with a bootstrap that mints a token on the rejecting response so a fresh client can recover), and a CORS allow-list for your SPA origins. Declaring it makes the surface explicit and teachable rather than relying on defaults.

The process tree

Put the slots together and a running deployment is more than one process. The events slot in particular fans the app out into three:

flowchart LR
  C[client] -->|HTTP| M[monolith<br/>·the API·]
  M -->|writes| DB[(database)]
  M -->|writes event| OB[[outbox table]]
  OB -.drained by.-> RL[relay<br/>·singleton·]
  RL -->|publishes| BR{{event bus}}
  BR -->|consumed by| WK[worker<br/>·runs subscriptions·]
  WK -->|dispatches| M
ProcessEmitted fromRole
monoliththe app itselfserves the HTTP API — every route
relaythe events slotdrains the transactional outbox into the broker (one leader-elected instance)
workerthe subscriptionsconsumes broker events and dispatches subscription handlers

An end-to-end asynchronous flow needs all three up. If the postmaster/log shows no event effect after an action, the usual cause is the relay or worker not running — the write committed, but nothing drained the outbox.

Client realms — the generated SDKs

Finally, the manifest declares client realms — one per client SDK the generator emits:

project.vishwakarma
realm web {
  kind     client
  language typescript
  root     "gen/clients/web"
  auth     { transmission cookie }
  // … slots: types, validators, tanstack queries, api-client, … …
}

Each realm emits a full typed client stack — wire models, Zod validators, TanStack query/mutation hooks, an API-fetch client with auth and CSRF baked in — rooted at root. Chowk declares a web realm (cookie auth, for the SPA) and a cli realm (header/bearer auth, for the command line). You never hand-write that stack; you build a thin product shell on top of it. The realm is the seam between the generated backend and the hand-built frontends — the last piece of infrastructure the manifest wires.