Finish RAILIANCE-WP-0021: family schemas, validator, and ADR-0007

Add rail and reef schemas, derive reef bound_rapps from rapp.bound_reefs,
and ship a standalone family-declaration validator with an inventory-fed
coverage check. Point the bootstrap contract at the schemas, record the
dimension and cardinality decisions in ADR-0007, correct the first-wave
candidates document, and release the shape to downstream repos.
This commit is contained in:
codex 2026-08-13 15:29:07 +02:00
parent d3b3512a60
commit 3cc0dc31d6
20 changed files with 1509 additions and 187 deletions

View file

@ -7,8 +7,8 @@ authority where the two disagree.
| Family | Declaration path | Schema |
|---|---|---|
| `rapp-*` | `declarations/rapp.yaml` | `rapp.schema.json` |
| `rail-*` | `declarations/rail.yaml` | `rail.schema.json` (RAILIANCE-WP-0021-T04) |
| `reef-*` | `declarations/reef.yaml` | `reef.schema.json` (RAILIANCE-WP-0021-T04) |
| `rail-*` | `declarations/rail.yaml` | `rail.schema.json` |
| `reef-*` | `declarations/reef.yaml` | `reef.schema.json` |
Written under `RAILIANCE-WP-0021` from the drift survey routed by
`railiance-platform` under its `RAILIANCE-WP-0015` (State Hub messages
@ -34,7 +34,7 @@ Rapp bounded context is therefore its own dimension, derived neither from
Forgejo organizations nor from State Hub domains. A repo lives in exactly one
Forgejo org, so `org:repo` is one-to-many, and a many-to-many grouping cannot
be derived from a one-to-many one. Domains fail in both directions. See
`docs/adr/ADR-0007` (T07) and decision `d07ee5f9`.
`docs/adr/ADR-0007-rapp-declaration-contract.md` and decision `d07ee5f9`.
## Normative choices, and what they cost
@ -89,12 +89,97 @@ layer repo that owns dozens of unrelated workloads. That is a modelling call
about where accountability sits, not a lint, and it belongs to the architecture
owner. Until it is made, treat the permissive rule as provisional.
## Validating
## Validator-only constraints
The validator lands under `tools/` in `RAILIANCE-WP-0021-T05`. Until then these
schemas are normative but unenforced — which is precisely the condition that
produced the drift, so T05 should not lag T02/T03 by long.
JSON Schema cannot express these; `tools/validate-family-declarations.py` does:
Two constraints are validator-only, because JSON Schema cannot express them:
`primary_rail` must appear in `supported_rails`, and reef `bound_rapps` must be
derivable from the rapp declarations rather than hand-listed.
- `primary_rail` must appear in `supported_rails` (rapp) or `hosted_rails` (reef)
- reef `bound_rapps`, if present, must match the projection of `rapp.bound_reefs`
- a rapp `ownership_repo` must not be the rapp repo itself
- declared member repos, named rails, and named reefs must resolve on disk
- a deployable name may belong to at most one rapp
## Rail schema (T04)
`rail.schema.json` codifies the two live rails. It does not tighten the family
beyond current practice.
Required on every rail: `kind`, `rail_id`, `repo`, `ownership_repo`,
`contract_version`, `composition_kind`, `execution_architecture`,
`readiness_state`, `required_substrate_capabilities`, `supported_rollout_modes`.
`composition_kind: derived` additionally requires `base_rail`,
`base_rail_contract`, `inherited_semantics`, `overridden_semantics`, and
`compatibility_constraints`. A `base` rail must not carry those fields.
`required_substrate_capabilities` is the live name. The bootstrap contract's
older `substrate_prerequisites` label is retired; do not reintroduce it.
Rail `readiness_state` uses the composition-contract vocabulary (`declared`,
`installed`, `verified`, `production-approved`, `deprecated`). That is
deliberately not the rapp vocabulary and not the reef `lifecycle_state`.
Validated 2026-08-13 against both live rails with `jsonschema` 4.10.3: both
conform. That is the intended contrast with the rapp family.
## Reef schema (T04)
`reef.schema.json` codifies the live `reef-railiance` declaration, with one
normative change: `bound_rapps` is no longer a hand-maintained required field.
Required on every reef: `kind`, `reef_id`, `repo`, `ownership_repo`,
`substrate_kind`, `lifecycle_state`, `criticality`, `primary_rail`,
`hosted_rails`, `current_members`.
`bound_rapps` is optional. Its source of truth is `rapp.bound_reefs`. The
validator inverts that field. If a reef still lists `bound_rapps`, the list
must match the projection exactly.
**Migration for `reef-railiance`.** The current list is `[rapp-qonto]`. That
is already stale: `rapp-openbao` and `rapp-postgres` are live on Railiance01.
Until those three declarations name `reef-railiance` in `bound_reefs`, the
derived projection is empty and a hand-list of anything fails. The owning
repo should delete `bound_rapps` now, or replace it with the derived set once
the rapps name the reef. Either is a one-line change; leaving the stale list
is the failure mode this check exists to catch.
`rapp.bound_reefs` was added as a required field in the same task. An empty
list is legal and means the package is declared but not yet placed.
Validated 2026-08-13 against the live reef: it conforms as a document. The
`bound_rapps` value does not survive the derivation check, which is the
intended result.
## Runtime dependencies name capabilities, not resources
`runtime_dependencies` is a list of capability names (`s3-backup-target`,
`openbao-database-secrets-engine`). The schema does not, and must not, say
who provisions the thing that satisfies a name.
The postgres backup object store is the worked example. `rapp-postgres`
declares `s3-backup-target` as a consumed capability. The bucket is a
`resource-control` resource (`RESOURCE-WP-0002`). The credential is
`railiance-platform` OpenBao custody (`Secret platform-pg-backup-s3`).
Purchase itself is the escalated human decision `9c21c0e0`. Until
`resource-control` hands an endpoint to `railiance-platform`,
`make postgres-backup-deploy` stays fail-closed on purpose. That is not a
hole in `rapp-postgres` ownership and not a hole in this schema.
## Running the validator
```bash
tools/validate-family-declarations.py
tools/validate-family-declarations.py --root ..
tools/validate-family-declarations.py --inventory path/to/live-deployables.json
tools/validate-family-declarations.py --self-test
```
`--inventory` is the T06 coverage check. This repo does not query a cluster.
The file is produced by an implementation repo and must be a JSON mapping
with a `deployables` list of `{name, namespace?, kind?}` (or bare name
strings). Live names that no rapp claims are reported as wave-2 worklist
items, not suppressed.
The validator is `tools/validate-family-declarations.py` (RAILIANCE-WP-0021-T05).
It is runnable standalone now. Calling it from `fix-consistency` waits on
`the-custodian` admitting the family prefixes; that sequencing is not ours.