railiance-master/docs/adr/ADR-0007-rapp-declaration-contract.md

86 lines
3.8 KiB
Markdown

---
id: RMASTER-ADR-0007
title: "Rapp Declaration Contract"
status: accepted
revision: "accepted-1"
owner: railiance-master
last_reviewed: "2026-08-13"
review_interval: 6m
---
# ADR-0007: Rapp Declaration Contract
Date: 2026-08-13
Status: Accepted
## Context
`RMASTER-WP-0017` through `RMASTER-WP-0019` established the four-axis
model and materialized the first family repos. The model held up. Its
enforcement did not.
A 2026-08-11 survey by `railiance-platform` found that the three live
`rapp.yaml` files were mutually unreadable: rollout, smoke, and rollback
contracts used different shapes; metadata that both rails carry consistently
appeared in only one rapp; `reef-railiance` `bound_rapps` listed `rapp-qonto`
only, while `rapp-openbao` and `rapp-postgres` were already live on the same
reef. `docs/repo-family-bootstrap-contract.md` named fields in prose and
could not catch any of this.
The same survey treated rapp grouping as something that might be derived from
Forgejo organizations or from State Hub domains. Neither works. A repo lives
in exactly one Forgejo org, so `org:repo` is one-to-many. A repo may
legitimately contribute to more than one rapp, so `rapp:repo` is
many-to-many. 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.
Canon OAS P1 requires independent perspectives to be separate orthogonal
dimensions. Decision `d07ee5f9` already recorded this from the
`railiance-platform` side; this ADR is the framework record.
## Decision
1. **Bounded rapp context is its own dimension.** It is derived neither from
Forgejo organizations nor from State Hub domains. Grouping is a first-class
declaration, not a projection of some other axis.
2. **Two cardinalities stay distinct.** Repos to rapps is many-to-many: a
repo may appear in the `composition.member_repos` of more than one rapp.
Deployables to rapps is one-to-one: every running deployable has exactly
one rapp that owns its rollout. The coverage question — *does every live
deployable belong to exactly one rapp?* — is well-formed only if these
stay distinct.
3. **Granularity is grouped-by-bounded-context.** One rapp per cohesive group
that deploys, versions, and rolls back together, not one rapp per
deployable. Grouping is legitimate only where members share rollout and
rollback fate. A single-repo rapp is the one-member case of the same
`composition` block, not a second shape.
4. **The schema is normative.** `schemas/rapp.schema.json`,
`schemas/rail.schema.json`, and `schemas/reef.schema.json` define the
shapes. Framework prose cites those files. It does not restate their
fields. Reef `bound_rapps` is a derived projection of `rapp.bound_reefs`,
not a hand-maintained registry.
The detailed shapes, including the single normative form of the rollout,
smoke, and rollback contracts, live in the schema files and
`schemas/README.md`.
## Consequences
- Drift across family declarations fails in
`tools/validate-family-declarations.py` instead of accumulating in prose.
- `railiance-platform` `RAILIANCE-WP-0015-T02` can converge `rapp-openbao`
and `rapp-postgres` onto one shape. Migration belongs to the owning repos;
this ADR does not move any declaration.
- `reef-railiance` must stop treating `bound_rapps: [rapp-qonto]` as source
of truth. The list is already stale.
- Three further `rapp-*` repos (`rapp-secrets-engine`, `rapp-tenant-engine`,
`rapp-user-engine`) carry the family prefix and no declaration. They are
visible to the validator as undeclared and must be declared, renamed, or
retired by their owners.
- Calling the validator from `fix-consistency` still waits on
`the-custodian` admitting the family prefixes into the classification
standard. That sequencing is not this repo's.