Flow

Architecture

How services store state, publish events, authorize requests, and call each other.

Every service owns one Postgres: Each service owns one Postgres database as the source of truth for its bounded context; services never read each other's databases. database. A Command: An HTTP POST that changes state, recorded with an Idempotency-Key so retries do not double-apply; not CQRS or a command bus. reads current state, validates, writes the change, and appends an event row in one transaction. Queries are SELECT handlers against the same database. Services call each other over HTTP/JSON.

Events use a Transactional outbox: Each domain inserts events into its own <domain>_events table in the same transaction as the state change; a publisher drains unpublished rows to Azure Service Bus.: each domain's <domain>_events table is durable. Each domain has its own fanout topic. The broker is Azure Service Bus in all environments (0016, 0041). The emulator runs locally. We never read events to determine our own state.

Authentication and authorization

Authentication is the login ceremony (OIDC with the IdP via authn). It produces a signed JWT.

Authorization happens at the service when it is called:

  1. Validate Authorization: Bearer <jwt> — signature against authn JWKS, trust chain, verified sub becomes the actor FID: The id of a principal. For a person it is their Kratos identity id, a lowercase UUID; for a service it is its OAuth2 client_id. It arrives as the verified sub claim of a Hydra access token and becomes the actor on the request..
  2. Evaluate Cedar: The authorization engine; each service embeds a Cedar policy and evaluates every mutating request against the actor's principal record. against that FID and the local principals copy.

A JWT is an authorization credential, not authentication. X-Dev-Actor is a temporary local stand-in with no trust chain until JWT middleware ships.

See docs/decisions/0013-jwt-validated-in-each-service.md.

Commands and idempotency

Callers send an Idempotency-Key header. command.Execute claims the key, runs domain logic inside one transaction, and stores the response payload for replay. No remote calls inside a transaction.

Pagination

List endpoints use bracket query parameters (pagination[page], pagination[pageSize]) and return { data, meta: { pagination } } inside the template envelope's payload. Create and get put the record in payload.data as an object. See docs/decisions/0014-pagination.md.

Identifiers

JobType
Event orderingbigserial (internal cursor)
Entity identityUUIDv7 from uuid.NewV7() in Go
Principalstext FID

Service layout

services/<name>/
  cmd/           binaries (api, workers)
  migrations/    goose SQL; 0001 is shared platform tables
  queries/       sqlc input
  db/            sqlc output (generated)
  policies/      Cedar policy (embedded)
  events/        public event payloads
  messageBus/    generated topic names from topology.yaml
  internal/      domain packages, handlers, authz, command

What we explicitly do not do

  • Event sourcing or CQRS
  • A runtime schema registry
  • NATS, gRPC, or a service mesh (for now)
  • Postgres extensions for UUID generation

Read the full binding document: docs/architecture.md.

Tracing

Shared HTTP middleware and configured internal clients carry W3C trace context. The template, accounting, billing, and leases APIs support opt-in OpenTelemetry export. Outbox headers preserve context for the shared Service Bus publisher and consumer. See HTTP and messaging tracing for process coverage and local verification.

On this page