Open RAILIANCE-WP-0021 for the rapp declaration schema

railiance-platform surveyed the four-axis model against the live cluster and
all six family repos and routed the findings here (messages 04c776c4 and
f88f938d): rapp.yaml has no schema and has drifted three ways, reef bound_rapps
is a stale hand-maintained list, and ~17 live workloads sit against 3 rapps.

Takes ownership of the schema, the composition block with its two cardinalities
(repos many:many with rapps, deployables 1:1), and a validator over all three
family declaration files. Migration stays with the owning repos.

T01 done: both messages acknowledged, ownership confirmed to railiance-platform.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-11 20:40:40 +02:00
parent a89019eae1
commit f3c44c7a80

View file

@ -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: <what this coherent workload does>
member_repos: # first-party, each sharing rollout and rollback fate
- repo: <slug>
role: <what it contributes>
deployables: [<names>]
upstream_components: # third-party, pinned
- name: <chart|image|operator>
source: <repo/chart or registry ref>
version: <pin>
```
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.