ADR-0015: NetKingdom Railiance packaging and relational platform
Settles two questions raised by bringing NetKingdom under Railiance governance: 1. Separate rapp-* repos per engine (rapp-tenant-engine, rapp-user-engine), following repository-axes.md's one-workload rule. The decisive property is independent rollback -- a single rapp would need one rollback contract across independently versioned services. secrets-engine is not packaged as a rapp: it has no deployed workload. 2. CloudNative PG via rapp-postgres is the default relational platform for production. Per-workload SQLite-on-a-PVC is dev/test only, and rail-kubernetes wave-1 does not support the persistent-storage contract it depends on. tenant-engine migrates; its TenantStore Protocol makes this a backend swap behind an existing seam. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4511a43f7a
commit
1bb7bce99b
1 changed files with 166 additions and 0 deletions
|
|
@ -0,0 +1,166 @@
|
|||
# ADR-0015 - NetKingdom Railiance Workload Packaging and Relational Platform
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-08-11
|
||||
**Deciders:** Bernd Worsch, Claude
|
||||
|
||||
## Context
|
||||
|
||||
NetKingdom's runtime services are deployed today outside the Railiance
|
||||
reef/rail/`rapp` model. `tenant-engine` and `user-engine` run on the coulomb
|
||||
substrate with digest-pinned images
|
||||
(`forgejo.coulomb.social/coulomb/{tenant,user}-engine@sha256:…`), but their
|
||||
Kubernetes manifests live in this repo at `sso-mfa/k8s/<service>/runtime.yaml`
|
||||
— a canon repo holding runtime YAML — applied imperatively, with
|
||||
`verify-t0*.sh` scripts as verification. Neither service declares
|
||||
`railiance/app.toml`, appears in `reef-railiance/bindings/rapps.yaml`, or is
|
||||
reconciled by a GitOps controller.
|
||||
|
||||
The decision to bring NetKingdom under Railiance governance forces two
|
||||
questions this ADR settles.
|
||||
|
||||
**Packaging granularity.** `railiance-master/docs/repository-axes.md` maps
|
||||
`rapp-*` to "one Railiance-managed workload" and states that a `rapp` "does
|
||||
not replace the responsibility repo that owns the broader domain. It is
|
||||
explicitly about managed wrapping, not ownership." Precedent exists in two
|
||||
shapes: `rapp-qonto` (`ownership_repo: qonto-assistant`, one workload, thin
|
||||
package repo pointing back at the domain repo) and `rapp-postgres`
|
||||
(`ownership_repo: railiance-platform`, one workload with a `consumers:` list
|
||||
and a `consumer_contract`). The open question was whether NetKingdom's engines
|
||||
are one workload or several.
|
||||
|
||||
**Relational storage.** The engines have diverged. `user-engine` already runs
|
||||
CloudNative PG (`kind: Cluster` in its own namespace) with NetworkPolicies and
|
||||
a `migrations/` surface. `tenant-engine` runs SQLite on a 1 Gi RWO PVC, chosen
|
||||
in TEN-WP-0004 for expedience. `railiance-platform` (S3) declares CloudNative
|
||||
PG the canonical database operator, and `rapp-postgres` packages it with a
|
||||
`database-per-consumer` boundary unit and OpenBao dynamic credentials.
|
||||
Separately, `rail-kubernetes`'s wave-1 contract explicitly states the rail must
|
||||
**not** assume "a generic persistent-storage contract" — so a per-workload PVC
|
||||
is unsupported by the rail regardless of which database is chosen.
|
||||
|
||||
TEN-WP-0005 was drafted against PostgreSQL and implemented against SQLite,
|
||||
because T05 must roll out against the runtime that exists. That workplan's
|
||||
`TenantStore` Protocol was kept as an explicit seam for this decision.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Separate `rapp-*` repos per engine
|
||||
|
||||
Each deployed NetKingdom engine gets its own managed workload package repo:
|
||||
|
||||
```text
|
||||
rapp-tenant-engine ownership_repo: tenant-engine
|
||||
rapp-user-engine ownership_repo: user-engine
|
||||
```
|
||||
|
||||
Following `rapp-qonto`'s shape: the `rapp` owns Railiance packaging, rail
|
||||
compatibility, workload-specific smoke/health checks, rollout and rollback
|
||||
expectations, secret references, and dependency declarations. The engine repo
|
||||
retains domain ownership; `net-kingdom` retains canon.
|
||||
|
||||
This follows the one-workload rule rather than working around it. The engines
|
||||
are already independent on every axis a `rapp` declaration must state:
|
||||
separate namespaces, separate image digests, separate release cadence, and
|
||||
(until decision 2 lands) different storage. A single `rapp-netkingdom` would
|
||||
have to declare one `workload_identity`, one `rollout_contract`, and one
|
||||
`rollback_contract` across services that genuinely differ.
|
||||
|
||||
The decisive property is **independent rollback**: rolling back a bad
|
||||
`tenant-engine` revision must not force a `user-engine` revision change. A
|
||||
single rapp would make that either impossible or fictional.
|
||||
|
||||
Rollout ordering between the engines is a declared constraint between rapps,
|
||||
not a reason to merge them: `tenant-engine` before `user-engine`, following the
|
||||
dependency direction.
|
||||
|
||||
### 2. `secrets-engine` is not packaged as a `rapp`
|
||||
|
||||
`secrets-engine` has no deployed workload — no Containerfile, no Makefile, no
|
||||
Kubernetes manifests. It is a catalog/policy/workflow layer over OpenBao, whose
|
||||
packaging already belongs to `rapp-openbao` with custody and lane policy in
|
||||
`railiance-platform`.
|
||||
|
||||
A `rapp` wraps a workload; `secrets-engine` has none to wrap. Revisit only if
|
||||
it becomes a runtime service, at which point this decision reopens for that
|
||||
repo alone and not for the packaging model.
|
||||
|
||||
### 3. CloudNative PG is the default relational platform for production
|
||||
|
||||
Production NetKingdom services requiring relational storage use CloudNative PG
|
||||
via `rapp-postgres`, consuming the `database-per-consumer` boundary unit and
|
||||
the `openbao-dynamic-database-credential` lane, with tenant-keying per
|
||||
`business-app-service-contract_v0.1` section 1.3.
|
||||
|
||||
Per-workload SQLite-on-a-PVC is not a production pattern. It remains acceptable
|
||||
for local development and tests.
|
||||
|
||||
`tenant-engine` migrates from SQLite to cnpg. Its `TenantStore` Protocol makes
|
||||
this a backend swap behind an existing seam rather than a rewrite; the
|
||||
lifecycle semantics proven in TEN-WP-0005 (atomic compare-and-swap, durable
|
||||
idempotency receipts, forward-only migration) are the conformance bar the
|
||||
PostgreSQL backend must meet, and its store-conformance suite is already
|
||||
parametrised across backends to enforce exactly that.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Two new `rapp-*` repos to create, each requiring a `declarations/rapp.yaml`,
|
||||
a binding in `reef-railiance/bindings/rapps.yaml`, and its own readiness
|
||||
evidence progression (`declared` → `installed` → `verified` →
|
||||
`production-approved`).
|
||||
- Per-binding evidence multiplies, but per-reef evidence does not — the
|
||||
`reef-production-readiness-contract` puts substrate, ingress, storage,
|
||||
network, and backup evidence on the reef, once. What multiplies is the
|
||||
critical-workload gate (threat model, negative authorization tests, rollback
|
||||
rules, residual-risk owner), which identity-plane services warrant
|
||||
individually. A shared NetKingdom threat model may be referenced by both
|
||||
bindings rather than duplicated.
|
||||
- Runtime manifests move out of `net-kingdom/sso-mfa/k8s/` into the respective
|
||||
`rapp` repos. This repo stops holding runtime YAML and returns to canon,
|
||||
standards, and ADRs.
|
||||
- `tenant-engine` requires a data migration from SQLite to cnpg, including
|
||||
migration of existing tenants, grants, plan assignments, and idempotency
|
||||
receipts. TEN-WP-0005-T05's rollout plan is affected: it currently targets
|
||||
the SQLite runtime.
|
||||
- NetKingdom services become subject to the `railiance/app.toml` staged
|
||||
promotion contract (Stage 1 local → Stage 2 canary → Stage 3 production),
|
||||
including declared rollback commands and health endpoints. Digest-pinned
|
||||
images already satisfy `digest_policy = "required"`.
|
||||
- The reef must accept or mitigate its single-server and shared-control-plane
|
||||
risk for each NetKingdom binding, as it must for `rapp-qonto`. The reef name
|
||||
and a `high` criticality label are not evidence.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**One `rapp-netkingdom` for all associated repos.** Rejected. It would reduce
|
||||
binding-evidence count and match the current coupled deployment (both engines'
|
||||
manifests share one tree; `flex-auth` runs per-consumer instances
|
||||
`flex-auth-tenant-engine` and `flex-auth-user-engine`). But it contradicts the
|
||||
one-workload rule in `repository-axes.md`, and would force a single rollback
|
||||
contract across independently versioned services. A rollback contract that
|
||||
depends on which service failed is not a contract. This alternative would be
|
||||
correct only if the engines were always promoted and rolled back as one atomic
|
||||
cutover — which their separate digests and release cadences contradict.
|
||||
|
||||
**Keep SQLite for `tenant-engine`, standardise later.** Rejected. It leaves the
|
||||
two engines operationally dissimilar, which is the opposite of the intent, and
|
||||
`rail-kubernetes` does not support the per-workload persistent-storage contract
|
||||
the PVC depends on. Deferring also grows the migration: every tenant, grant,
|
||||
and receipt written between now and the cutover is data to move.
|
||||
|
||||
**A `rapp` per NetKingdom concern with a `consumers:` list, mirroring
|
||||
`rapp-postgres`.** Rejected as a category error. `rapp-postgres`'s consumers
|
||||
are consumers *of one workload* (PostgreSQL); NetKingdom's engines are distinct
|
||||
workloads, not consumers of a shared one.
|
||||
|
||||
## Follow-Up
|
||||
|
||||
- Create `rapp-tenant-engine` and `rapp-user-engine`; move runtime manifests
|
||||
out of `net-kingdom/sso-mfa/k8s/`.
|
||||
- Add both bindings to `reef-railiance/bindings/rapps.yaml` at `declared`.
|
||||
- Add `railiance/app.toml` to `tenant-engine` and `user-engine`.
|
||||
- Open a `tenant-engine` workplan for the cnpg backend and data migration;
|
||||
reconcile with TEN-WP-0005-T05, whose rollout currently targets SQLite.
|
||||
- Confirm whether `secrets-engine` is intended to remain a non-deployed control
|
||||
layer. This ADR assumes it is.
|
||||
- Record the NetKingdom-wide threat model that both bindings will reference.
|
||||
Loading…
Add table
Add a link
Reference in a new issue