Rapp context is its own grouping dimension
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Operator design review considered binding bounded rapp context to Forgejo orgs
or State Hub domains and rejected both. OAS P1 governs - independent
perspectives must stay in separate dimensions - and cardinality forces it: a
repo has exactly one Forgejo org (a path segment in the clone URL) so org:repo
is 1:many, while rapp:repo is many:many, and a many:many grouping cannot be
derived from a 1:many one.

Records the dimension table, the composition block (first-party member repos
plus pinned upstream components and a stated purpose), and the precision that
makes enforcement well-defined: repos are many:many with rapps but deployables
are 1:1, so the validator can ask whether every live deployable belongs to
exactly one rapp. That is the coverage check that would have caught all three
of this survey's drift findings at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-11 11:34:51 +02:00
parent a1f948564f
commit 2597fa46da
2 changed files with 52 additions and 4 deletions

View file

@ -114,6 +114,54 @@ Operator decision of 2026-08-11: rapp granularity is **grouped by bounded
context** — one rapp per cohesive group of services that deploys, versions and
rolls back together, rather than one rapp per deployable.
### Rapp context is its own dimension
A bounded rapp context is **not** derived from a Forgejo organization, nor from
a State Hub domain. It is a grouping in its own right. This follows OAS P1
(`canon/standards/orthogonal-architecture_v1.0.md`) — independent perspectives
MUST stay in separate dimensions — and it is forced by cardinality:
| Grouping | Answers | Cardinality to repos |
| --- | --- | --- |
| Forgejo org | who may push; what is discoverable together | 1:many (a repo has one org — it is a path segment in the clone URL) |
| State Hub domain | which strand of work, for attention and priority | 1:many |
| Rapp context | what deploys, versions and rolls back together | **many:many** |
A repo can legitimately contribute to more than one rapp — a shared library, or
a service that is both a member of one bundle and a dependency of another. A
many:many grouping cannot be derived from a 1:many one. Domains fail in both
directions: several rapps sit within one domain, and a cross-cutting rapp such
as an identity bundle serves every domain rather than sitting inside one.
The three also change at different speeds — org membership is expensive to
change, domains change occasionally, rapp contexts change often as workloads
consolidate. Deriving the volatile grouping from the expensive one guarantees
churn in the wrong place.
**One precision matters for enforcement:** repos are many:many with rapps, but
*deployables* are **1:1** — each running deployable has exactly one rapp owning
its rollout. That distinction is what makes the coverage check well-defined:
*does every live deployable belong to exactly one rapp?*
### Declaring the composition
A rapp declares what it combines: first-party repos and pinned third-party
components, plus the purpose the combination serves. The normative shape is
`railiance-master`'s to settle; the proposed form is:
```yaml
composition:
purpose: <what this coherent workload does>
member_repos:
- repo: <slug>
role: <what it contributes>
deployables: [<names>]
upstream_components:
- name: <chart|image|operator>
source: <repo/chart or registry ref>
version: <pin>
```
For a grouped rapp:
- members **must** be declared explicitly; an undeclared bundle is not a rapp,