Portfolio registry as three microservices on Cosmos-shaped Postgres
Status: Accepted · Date: 2026-07-02 (design v3), live in production 2026-07-03 · Area: Data
Context
Aurora needed a first-class registry of the portfolio’s Customer → Tenant → Solution hierarchy. Earlier design iterations placed it inside the webapp: v2 proposed an in-webapp module over a git-backed YAML registry repo. But the registry was becoming runtime infrastructure for more than Aurora — the tenant-scoped Orbit reader and the deploy write-back path both hit it — and a registry queried per-request needs real read/write latency, indexed filtering, and row-level concurrency, not Contents-API round-trips and sha-based commits.
Decision
- Three standalone Hono microservices —
customer-service,tenant-service,solution-service— each a first-class platform component, sharing plumbing viaimplementation/shared/registry-kit. - Storage is Postgres shaped like Cosmos DB: one schema per service, one
recordstable (idtext PK,datajsonb,versionint, timestamps), B-tree expression indexes per filterable JSON field plus ajsonb_path_opsGIN index. Documents are zod-validated at the service boundary and use camelCase keys (hyphenated style stays with human-edited YAML). The shape keeps a later lift to actual Cosmos mechanical. - Optimistic concurrency via
If-Match: <version>(409 on stale), and every mutation appends to anaudit.eventstable — keeping the git era’s auditability deliberately, not accidentally. - Invariants are cross-service, synchronous, and fail closed: unknown
customer → 400, cross-customer attach → 409, delete-with-children → 409,
peer unreachable → mutation 503 (reads never need a peer). No distributed
transactions; a reconciliation sweep is the drift backstop. Solution
ownership is a single
tenantpointer on the solution record, so single-ownership holds by construction. - aurora-webapp is the BFF in front of the services (which run with
internal ingress on Azure and hold no GitHub credentials): it joins
registry records to
solution.yamlby customer-repo name, exposes/api/customers|tenants|solutions, and serves the tenant-scoped reader’s contract. - Azure Postgres is Entra-only — no password exists anywhere. Services
authenticate with a
DefaultAzureCredentialtoken as the pg password, refreshed per connection; the UAMI and the signed-in user are the Entra admins.
Consequences
- The registry scales, deploys, and versions independently of Aurora, and GitHub App permissions stayed read-only — v2’s write-elevation blast-radius concern disappeared with the registry repo itself.
- Schema-flexible documents mean field changes without column migrations; services run their own advisory-locked SQL migrations at boot.
- Live-run costs were paid and recorded:
AZURE_CLIENT_IDmust be set orDefaultAzureCredentialignores the user-assigned identity; in-environment service URLs must use the app-name form (http://svc-customer) because the internal FQDN form does not resolve; and severalazCLI renames had to be chased in the provisioner. - The 2026-07-03 production migration registered 7 customers, 7 tenants, and 7 solutions with a final sweep of 0 unregistered / 0 orphaned.
Evidence
implementation/customer-service/,implementation/tenant-service/,implementation/solution-service/,implementation/shared/registry-kit/implementation/aurora-webapp/codebase/src/lib/registry/— the BFF client, join, and write-backinfrastructure/azure/provision-postgres.sh,provision-services.sh,app-service.yaml.tmpldesigns/tenant-model.md(v3, incl. the v2 → v3 rationale table),memory/portfolio-registry.md