railiance-master/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md
codex f3c44c7a80 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>
2026-08-11 20:40:40 +02:00

13 KiB

id type title domain repo status owner topic_slug planning_priority planning_order created updated related_repos
RAILIANCE-WP-0021 workplan Rapp Declaration Schema and Family Declaration Validator financials railiance-master ready codex railiance high 21 2026-08-11 2026-08-11
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

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

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

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:

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

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

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

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

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

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

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.