ADR-0024: Deployment is a factory — sites pull governed packages, config not forks
ADR-0024: Deployment is a factory — sites pull governed packages, config not forks
Status
Accepted
Date
2026-07-03
Context
The deployment concern (NETW-20) asks for cloud infrastructure, environments, CI/CD, observability, scaling, and to “reuse the smart-market-platform base where it fits.” Canon already fixes the deployment ports: ports & adapters §2 names P20 · Compute Orchestrator (Kubernetes / Nomad) and P21 · Desired-State Deployment (Argo CD → Apollo-style hub/spoke → signed bundles), and §3 resolves them across the Lean / Scale / Air-gapped reference profiles. ADR-0007 picks Lean as the honest first build; ADR-0014 makes observability real. What canon does not yet decide is the fleet shape: how many heterogeneous deployments — customer-specific, air-gapped, multi-tenant cloud — are provisioned, versioned, and reconciled from one place. NETW-20 is that decision. This ADR closes NETW-20.
The anti-spec is the legacy product: SmartInventory carries ~19 per-customer code forks (ADR-0008) — deployment by fork. The factory exists to make that shape structurally impossible.
Reality check. Deployment is greenfield: nothing is deployed to any cloud. CI
(.github/workflows/ci.yml) is test-only; local docker-compose.yml (the Lean stack) is the only
running environment. This ADR fixes the deployment architecture and topology, not running infra.
Vocabulary.
- Factory — the software control plane (central, hub/spoke) that builds and publishes signed, versioned Packages and holds each Site’s desired state. The Vinxi analog of Palantir Apollo; the concrete form of ports §2 P21.
- Package — a signed, content-addressed unit of code/artifacts for one release, identical across every Site on that release. Config and data are never inside it.
- Site — an install: one deployment/environment running the kernel. Carries a deployment mode and a ports profile. A Site is the spoke — it pulls its desired state and reconciles.
- Deployment mode — the topology axis (who shares an install): multi-tenant cloud · dedicated single-tenant · air-gapped on-prem. Orthogonal to the ports profile (the adapter axis), except air-gapped ⇒ air-gapped profile.
- Sovereign — a residency/retention overlay (in-country storage, local model artifacts, long immutable retention) on a dedicated or air-gapped Site; not a mode.
- Cell — a Universe-specific data shard (the physical placement unit; ADR-0020, data-plane); a Universe may span multiple Cells. Cell placement drives residency and scale sharding.
Decision
-
Deployment is a factory, not a per-customer build. One control plane (the Factory) publishes signed Packages and holds each Site’s desired state; Sites pull and reconcile, never the reverse. This promotes ports §2 P21’s “Apollo-style hub/spoke — a Hub computes desired state, spokes reconcile signed bundles” from a candidate adapter to the deployment architecture. Deployment-by-fork is rejected outright.
-
Three deployment modes, orthogonal to the ports profile. The factory targets multi-tenant cloud, dedicated single-tenant, and air-gapped on-prem. Mode is the topology axis (“who shares an install”); the ports §3 profile (Lean/Scale/Air-gapped adapter set) is a separate axis chosen per Site. The two compose freely, with one hard coupling: air-gapped mode ⇒ air-gapped profile. Sovereign is a policy overlay on dedicated/air-gapped, not a fourth mode.
-
Package = code-only, identical everywhere; per-customer difference is config, not code. A Package is signed, content-addressed code/artifacts for a release, byte-identical across every Site. Everything that differs per customer — branding, workflows, reality — is config + Reality Packages (data) composed at the Site (ADR-0020: “customer = Tenant + Universe(s) + a Reality Package, not a code branch”). Genuine custom code ships only as a separate signed extension package, never a fork of the core. One code lineage, N overlays — the structural kill of the legacy ~19-fork problem (ADR-0008). The ports profile is itself a config manifest selecting which compiled adapters are active; all adapters are present in the artifact.
-
The Site is the deployment unit; the Cell is data placement inside it. A Site is one install (the spoke, carrying mode + profile). Within a Site, a Universe’s data lives in one or more Cells (Universe-specific shards); a Universe may span Cells. The Site boundary is physical/deployment isolation; the Universe boundary (ADR-0020) is logical isolation inside a Site. Tenant→Site and Universe→Cell placement is where residency/sovereignty is decided: an air-gapped or dedicated Tenant runs its own Site and Cells, and its data never leaves them.
-
Desired state is Factory-held; Sites pull. The Factory holds each Site’s desired state — channel + pinned Package version + config/extension set. Sites reconcile to it (connected Sites pull continuously, GitOps/Argo-style; air-gapped Sites receive desired state inside an operator-imported signed bundle). Built now (Lean, ADR-0007): release channels, per-site version pinning (a certified dedicated/air-gapped Site pins and lags while cloud rolls forward), and rollback = re-point to the prior content-addressed version (trivial because Packages are immutable). Promotion is manual for now.
-
Fleet-safety mechanisms are designed here but deferred to Scale, on evidence. Ring-based staged rollout (canary → early → broad), health-gated auto-promotion (reading the ADR-0014 observability signals as the Site-health input), and the compatibility-constraint engine (kernel × extension × migration) are specified but not built until a real fleet demands them — the same measured-graduation discipline ADR-0007 applies to the Scale profile.
-
CI/CD is a signed supply chain into the Factory. CI builds a signed, content-addressed Package (OCI image) → publishes it to the registry that is the Factory’s artifact source → Sites pull and reconcile. Migrations (ADR-0004: SQLx, explicit, forward-only, schema-per-plane) ride inside the Package and apply on reconcile, never out-of-band. Today’s CI stays test-only; the build/sign/publish step lands when the first Site is stood up.
-
No speculative cloud. No cloud Site is stood up yet; local
docker-composeremains the only running environment until a real cloud customer exists. Our own dev / staging / prod are just Sites the Factory manages — we dogfood the factory rather than treating our environments as a special case. The first cloud provider is deliberately unpinned — GCP-leaning (the Google Cloud Premier Partner relationship + the Kubernetes/S3 direction) — and stays swappable behind P20/P21.
Consequences
- NETW-20 is satisfied: infra architecture + deployment topology (Factory / Site / Cell), environments (dev/staging/prod as managed Sites), CI/CD (the signed-package supply chain), observability (ADR-0014 as the Site-health signal), and scaling (the ports §3 profile columns + Cell sharding).
- “Reuse SMP base” is resolved: superseded for the kernel by ADR-0008 (SMP is a connector / import source). The Stage-1 product track (NETW-28/33) reuses SMP separately and is out of scope here.
- Deployment-by-fork becomes impossible by construction: the only per-customer artifacts are config, Reality-Package data, and signed extension packages. The legacy fork sprawl cannot recur.
- Cell is owed a dedicated data-plane doc (the dangling Cells-doc TODO); this ADR fixes its deployment role (placement/residency), not its internal shard mechanics.
- Revisit triggers: the first air-gapped customer (BSNL-class) forces the signed-bundle import path to be built; the first multi-Site fleet forces rings + the constraint engine; the first cloud customer pins the provider and lights up the CI publish step.
Alternatives Considered
- Deployment-by-fork (the legacy SMP shape). Rejected — it is the ~19-fork problem ADR-0008 exists to kill; per-customer code lineages are unmaintainable and un-auditable.
- Per-site baked packages (config compiled into the artifact). Rejected — simpler single-artifact delivery, but the Package lineage forks per customer: the legacy failure mode in modern dress.
- Pure per-site GitOps with no central desired state. Rejected — decentralized and simple, but loses fleet-wide orchestration and constraints and is awkward for air-gapped bundle delivery; a heterogeneous fleet needs the Factory to hold desired state.
- Push-based deployment (the control plane pushes into sites). Rejected — pull is the air-gap-honest, least-privilege model: Sites reach out or import, and the Factory needs no inbound path into a customer’s network.
- Standing up cloud now (pick GCP/Railway and build). Rejected as premature — ADR-0007 honesty: no speculative infra before a customer. The model is defined so a Site can be stood up in days when one arrives.