railiance-master/docs/adr/ADR-0007-rapp-declaration-contract.md
codex 51aa55dcb2 docs(RMASTER-WP-0025): define universal rapp coverage
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b90-83bf-75c2-81c8-aa705414e4d4
2026-08-23 01:38:29 +02:00

5.4 KiB

id title status revision owner last_reviewed review_interval
RMASTER-ADR-0007 Rapp Declaration Contract accepted accepted-2 railiance-master 2026-08-23 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.

  5. Coverage includes every managed running deployable. Application, operational, and tooling runtimes participate in the same exactly-one-rapp invariant when they are installed, scheduled, or otherwise operated as a managed deployable. This includes a managed one-shot Job; it does not turn a human command or approval act into a workload. Human access, credential patterns, broker actions, one-off operational acts, and infrastructure resources that are not workloads retain their native actor, lane, activity, or resource identity.

    A running deployable that predates rapp extraction is migration debt. Until an authoritative declaration claims it, workload-based controls report it as unknown; they do not infer a rapp from its repository, namespace, path, labels, or apparent owner. A subject explicitly established as not being a workload is not-applicable. unknown and not-applicable are different outcomes and omission must not collapse them.

    Railiance Master remains the sole owner of the normative rapp vocabulary and schemas. Consumer catalogs may store explicit references and integration owners may resolve them, but neither creates a parallel declaration surface or copies rapp metadata as another source of truth.

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.
  • Operational and tooling deployables are not exempt from family coverage. Existing pre-rapp runtimes may continue during migration, but their workload identity remains visibly unknown to controls until declared.
  • Runtime inventory is still required to prove universal coverage. Repository discovery alone cannot establish that every running unit has exactly one authoritative rapp.
  • 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.