diff --git a/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md b/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md new file mode 100644 index 0000000..405c9f1 --- /dev/null +++ b/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md @@ -0,0 +1,345 @@ +--- +id: RAILIANCE-WP-0021 +type: workplan +title: "Rapp Declaration Schema and Family Declaration Validator" +domain: financials +repo: railiance-master +status: ready +owner: codex +topic_slug: railiance +planning_priority: high +planning_order: 21 +created: "2026-08-11" +updated: "2026-08-11" +related_repos: + - railiance-master + - railiance-platform + - rapp-openbao + - rapp-postgres + - rapp-qonto + - rail-kubernetes + - rail-knative + - reef-railiance + - the-custodian +--- + +# RAILIANCE-WP-0021 - Rapp Declaration Schema and Family Declaration Validator + +## Goal + +Give the `rapp-*` family a machine-checkable declaration contract, and stand up +a validator over all three family declaration files (`rail.yaml`, `rapp.yaml`, +`reef.yaml`) so that architectural drift fails loudly instead of accumulating +quietly. + +The deliverable is not more prose. `docs/repo-family-bootstrap-contract.md` +already names the required `rapp.yaml` fields; naming them in prose is exactly +what failed. The deliverable is a schema plus a check that runs. + +## Why This Exists + +`RAILIANCE-WP-0017` through `RAILIANCE-WP-0019` established the four-axis model +and materialized the first concrete family repos. The model held up. Its +enforcement did not. + +`railiance-platform` surveyed all six family repos and the live Railiance01 +cluster on 2026-08-11 under its `RAILIANCE-WP-0015` and routed the findings +here, on the grounds that `railiance-master` owns the model and an +implementation repo should not author it. State Hub messages +`04c776c4-52fa-494a-9761-942123f28a6e` (proposal) and +`f88f938d-1ebc-4ba0-91ee-9395e44a7cb3` (amendment) carry the detail. + +Reported drift across the three live rapps: + +- rollout, smoke, and rollback contracts are mutually unreadable — + `rapp-openbao` and `rapp-postgres` use `supported_commands` lists, + `rapp-qonto` uses `default_mode` / `production_gate`, a `required:` smoke + list and an `order:` rollback list +- `contract_version`, `readiness_state`, `data_classification` and + `criticality` appear only in `rapp-qonto`, though both rails carry + `contract_version` / `readiness_state` consistently +- `consumer_contract` / `consumers` exist only in `rapp-postgres` +- `workload_identity.name` is the workload name in two rapps and the repo name + in the third +- `ownership_repo` points at a layer repo twice and at an app repo once +- `reef-railiance` `bound_rapps` lists `rapp-qonto` only, while `rapp-openbao` + and `rapp-postgres` are both live on Railiance01 — a hand-maintained registry + with nothing checking it +- roughly 17 live non-system workloads in-cluster against 3 rapps + +The `rail-*` family received a tighter contract in wave 1 and has stayed clean. +That contrast is the argument for schema rather than for more documentation. + +## Current Starting Point + +- no `schemas/` directory exists in this repo +- `docs/repo-family-bootstrap-contract.md` lists eight required `rapp.yaml` + fields with no shapes, no types, and no cardinality rules +- `docs/rapp-first-wave-candidates.md` still reads as though the planned first + wave (`rapp-openbao`, `rapp-vergabe-teilnahme`, `rapp-forgejo`) happened; the + actual population is `rapp-openbao`, `rapp-postgres`, `rapp-qonto`, the + latter two created ad hoc from immediate need +- `rapp-vergabe-teilnahme` was never built, so the user-facing wrapper pattern + it existed to prove remains unproven while the platform-service pattern has + been proven twice +- canon does not yet admit the family prefixes, so `fix-consistency` cannot + call a family validator until `the-custodian` lands its side +- `railiance-platform` is holding `RAILIANCE-WP-0015-T02` (bringing + `rapp-openbao` and `rapp-postgres` onto one shape) until this schema exists + +## Target Outcome + +When this workplan is complete: + +1. `schemas/rapp.schema.json` defines every required and optional `rapp.yaml` + field with one normative shape per contract. +2. A `composition:` block expresses what a grouped rapp actually is: a stated + purpose, first-party member repos, and pinned third-party components. +3. The two cardinalities are written down and enforced separately — repos are + many-to-many with rapps, deployables are one-to-one. +4. A validator runs over `rail.yaml`, `rapp.yaml`, and `reef.yaml` and fails on + drift. +5. Reef `bound_rapps` is derived from rapp declarations rather than + hand-listed. +6. `docs/rapp-first-wave-candidates.md` records what was actually built versus + planned, and carries the grouped wave-2 inventory. +7. Downstream repos have a single shape to converge on, and know it. + +## Boundaries + +This workplan owns the schema, the validator, the cardinality rules, and the +framework documents that describe them. + +It does not migrate any rapp repo onto the new shape — that stays with each +owning repo, coordinated through `railiance-platform` `RAILIANCE-WP-0015-T02`. + +It does not touch canon. Admitting the family prefixes into +`repo-classification-standard_v1.0` is `the-custodian`'s call, routed there +separately as `cc64764a`. + +It does not address the Forgejo organization split. All repos currently sit +under a single `coulomb/` org and a split into stewardship orgs is a +URL-breaking change across every remote, CI config and ArgoCD source. It is an +independent dimension from rapp grouping and needs its own work record. + +## Tasks + +## T01 - Accept the routed proposal and record the ownership decision + +```task +id: RAILIANCE-WP-0021-T01 +status: done +priority: high +``` + +Done 2026-08-11. Both messages marked read; reply `a9a8109f` sent to +`railiance-platform` accepting ownership and confirming this workplan as the +vehicle. No decision record was created here: `d07ee5f9` already carries the +dimension decision from the `railiance-platform` side, and duplicating it would +create two records of one decision. The rationale lands instead in `ADR-0007` +under T07. One question was returned to `railiance-platform` — whether they can +emit the live deployable inventory that T06 needs, so this repo does not +acquire a cluster dependency. + +Read and acknowledge State Hub messages `04c776c4` and `f88f938d`, mark them +read, and reply to `railiance-platform` confirming that `railiance-master` +accepts ownership of the schema and that this workplan is the vehicle. + +Record the framework decision that rapp bounded context is a grouping dimension +in its own right, derived neither from Forgejo organizations nor from State Hub +domains, with the cardinality argument as rationale: a repo lives in exactly +one Forgejo org, so `org:repo` is one-to-many, while a repo may legitimately +contribute to more than one rapp, so `rapp:repo` is many-to-many — and a +many-to-many grouping cannot be derived from a one-to-many one. Domains fail in +both directions. The three dimensions also change at different speeds. + +Reference OAS P1 in `canon/standards/orthogonal-architecture_v1.0.md`: separate +independent perspectives into orthogonal dimensions. + +## T02 - Author schemas/rapp.schema.json + +```task +id: RAILIANCE-WP-0021-T02 +status: todo +priority: high +``` + +Create `schemas/` and write `rapp.schema.json` covering: + +- the eight fields already required by the bootstrap contract: `rapp_id`, + `workload_identity`, `ownership_repo`, `supported_rails`, + `runtime_dependencies`, `rollout_contract`, `smoke_contract`, + `rollback_contract` +- `contract_version` and `readiness_state`, matching the shape the rail family + already carries +- `data_classification` and `criticality`, promoted from `rapp-qonto`-only to + required +- `consumer_contract`, promoted from `rapp-postgres`-only to optional but + schema-defined + +Settle the two definitional questions the survey exposed. `workload_identity.name` +is the workload, never the repo. `ownership_repo` names the repo that owns the +workload's lifecycle; state explicitly whether a layer repo is a legal value, +since current declarations disagree. + +Pick one normative shape for `rollout_contract`, `smoke_contract` and +`rollback_contract` rather than admitting all three observed variants. Note in +the schema which existing declaration the chosen shape most resembles, so +migration cost is visible. + +## T03 - Define the composition block and its cardinality rules + +```task +id: RAILIANCE-WP-0021-T03 +status: todo +priority: high +``` + +Supersede the flat `members:` list from the original proposal with the +composition block from the amendment: + +```yaml +composition: + purpose: + member_repos: # first-party, each sharing rollout and rollback fate + - repo: + role: + deployables: [] + upstream_components: # third-party, pinned + - name: + source: + version: +``` + +Encode both cardinalities. A repo may appear in the `member_repos` of more than +one rapp. A deployable must belong to exactly one rapp — each running +deployable has exactly one rapp owning its rollout. Keeping these distinct is +what makes the coverage check in T06 well-defined. + +Carry the operator decision of 2026-08-11 into the schema: rapp granularity is +grouped-by-bounded-context, one rapp per cohesive group that deploys, versions +and rolls back together, not one per deployable. Grouping is legitimate only +where members share rollout and rollback fate; make that testable rather than +advisory where possible. + +## T04 - Schematize rail.yaml and reef.yaml, and derive bound_rapps + +```task +id: RAILIANCE-WP-0021-T04 +status: todo +priority: medium +``` + +Write `schemas/rail.schema.json` and `schemas/reef.schema.json` from the +minimum field sets in `docs/repo-family-bootstrap-contract.md`. The rail family +has stayed clean, so this is largely codifying what already works; do not +tighten it beyond current practice without cause. + +Change `reef.yaml` `bound_rapps` from a hand-maintained list to a derived +projection of the rapp declarations that name the reef, and record the +migration path for `reef-railiance`, whose current list is already known stale. + +## T05 - Build the family declaration validator + +```task +id: RAILIANCE-WP-0021-T05 +status: todo +priority: high +``` + +Add a validator under `tools/` that walks the family repos, locates each +`declarations/*.yaml`, and validates it against the matching schema. + +Checks in priority order, per the routed proposal: + +1. every declaration conforms to its schema +2. every declared member repo and upstream pin resolves +3. reef `bound_rapps` is derivable from rapp declarations rather than + hand-listed +4. deployable coverage — deferred to T06, which needs cluster state + +Exit non-zero on any failure and name the offending file and field. Make it +runnable standalone now, and callable from `fix-consistency` later; do not +block on canon admitting the prefixes, since that is `the-custodian`'s +sequencing, not ours. + +## T06 - Add the deployable coverage check + +```task +id: RAILIANCE-WP-0021-T06 +status: todo +priority: medium +``` + +Extend the validator with the standing control-loop question: does every live +deployable belong to exactly one rapp? + +This is the check that turns the schema from a one-shot lint into an ongoing +control loop. Per the amendment it catches all three drift findings at once — +stale reef bindings, unpackaged live workloads, and the roughly +17-workloads-versus-3-rapps gap. + +Decide how live cluster state reaches the check without `railiance-master` +acquiring a cluster dependency it should not have. Consuming an inventory +produced by an implementation repo is preferable to querying Railiance01 from +here. + +Expect the first run to report a large uncovered set. That output is the wave-2 +worklist, not a failure to suppress. + +## T07 - Update the bootstrap contract to point at the schemas + +```task +id: RAILIANCE-WP-0021-T07 +status: todo +priority: medium +``` + +Revise `docs/repo-family-bootstrap-contract.md` so the minimum-field lists cite +the schema files as normative rather than restating fields in prose. Prose that +duplicates a schema will drift from it. + +Record an ADR (next free number, `ADR-0007`) for the framework decisions this +workplan settles: bounded rapp context as an independent dimension, the two +cardinalities, and grouped-by-bounded-context granularity. + +## T08 - Refresh rapp-first-wave-candidates.md + +```task +id: RAILIANCE-WP-0021-T08 +status: todo +priority: medium +``` + +Correct the document to record what was actually built against what was +planned: `rapp-openbao` as planned, then `rapp-postgres` and `rapp-qonto` ad +hoc from need, and `rapp-vergabe-teilnahme` never built. + +State the consequence plainly — the platform-service pattern is proven twice +and the user-facing wrapper pattern is unproven — and carry the operator +decision to build `rapp-vergabe-teilnahme` to prove the user-facing shape +before mass extraction. + +Add the grouped wave-2 inventory in the new composition shape. Indicative +candidates: `rapp-netkingdom-identity` (sso, mfa, user-engine, tenant-engine, +flex-auth), `rapp-forgejo` (forgejo, runner, registry), `rapp-coulomb-social`, +`rapp-helix-forge`. + +## T09 - Release the schema to downstream repos + +```task +id: RAILIANCE-WP-0021-T09 +status: wait +priority: high +``` + +Waiting on T02, T03 and T05. + +Notify `railiance-platform` that the shape is settled and +`RAILIANCE-WP-0015-T02` is unblocked, and notify `rapp-openbao`, +`rapp-postgres`, `rapp-qonto` and `reef-railiance` of the migration they each +own. Include the normative shape chosen for the rollout, smoke and rollback +contracts and what each repo must change. + +Migration itself belongs to the owning repos. This task ends when they have +what they need, not when they are done.