diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index bd91541..12fdaab 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -13,7 +13,6 @@ | workplan | RAILIANCE-WP-0018 | finished | — | workplans/RAILIANCE-WP-0018-first-wave-repo-family-materialization.md | | workplan | RAILIANCE-WP-0019 | finished | — | workplans/RAILIANCE-WP-0019-knative-qonto-runtime-on-reef-railiance.md | | workplan | RAILIANCE-WP-0020 | backlog | — | workplans/RAILIANCE-WP-0020-openbao-migration-to-reef-railiance.md | -| workplan | RAILIANCE-WP-0021 | ready | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | | task | ADHOC-2026-07-30-T01 | done | — | workplans/ADHOC-2026-07-30.md | | task | RAILIANCE-WP-0017-T01 | done | — | workplans/RAILIANCE-WP-0017-rail-rapp-reef-repo-separation.md | | task | RAILIANCE-WP-0017-T02 | done | — | workplans/RAILIANCE-WP-0017-rail-rapp-reef-repo-separation.md | @@ -46,12 +45,3 @@ | task | RAILIANCE-WP-0020-T06 | done | — | workplans/RAILIANCE-WP-0020-openbao-migration-to-reef-railiance.md | | task | RAILIANCE-WP-0020-T07 | done | — | workplans/RAILIANCE-WP-0020-openbao-migration-to-reef-railiance.md | | task | RAILIANCE-WP-0020-T08 | todo | — | workplans/RAILIANCE-WP-0020-openbao-migration-to-reef-railiance.md | -| task | RAILIANCE-WP-0021-T01 | done | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T02 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T03 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T04 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T05 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T06 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T07 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T08 | todo | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | -| task | RAILIANCE-WP-0021-T09 | wait | — | workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md | diff --git a/schemas/README.md b/schemas/README.md deleted file mode 100644 index 9551ee7..0000000 --- a/schemas/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# Railiance family declaration schemas - -Normative schemas for the repo-family declaration files. `docs/repo-family-bootstrap-contract.md` -names the minimum fields; these schemas define their shapes and are the -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) | - -Written under `RAILIANCE-WP-0021` from the drift survey routed by -`railiance-platform` under its `RAILIANCE-WP-0015` (State Hub messages -`04c776c4`, `f88f938d`). - -## The two cardinalities - -These are the load-bearing rules. Everything the validator can usefully check -follows from keeping them distinct. - -- **Repos to rapps is many-to-many.** A repo may contribute to more than one - rapp — a shared library, or a service that is a member of one bundle and a - dependency of another. -- **Deployables to rapps is one-to-one.** Every running deployable has exactly - one rapp that owns its rollout. - -The second is what makes the coverage question well-formed: *does every live -deployable belong to exactly one rapp?* That single check catches stale reef -bindings, unpackaged live workloads, and the workload-count gap at once. It is -what turns this schema from a one-shot lint into a standing control loop. - -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`. - -## Normative choices, and what they cost - -The three live rapp declarations expressed their rollout, smoke and rollback -contracts three mutually unreadable ways. The schema picks one shape. Where it -picks, it says so and it says who pays. - -**`default_mode` is kept** — all three declarations already carried it. No cost. - -**`supported_commands` becomes `commands`** in all three contracts. Mechanical -rename for `rapp-openbao` and `rapp-postgres`. - -**`smoke_contract.required` is required, and `commands` is optional.** Taken -from `rapp-qonto`. This is the one migration with real cost: the platform rapps -declare only commands, so their owners must name the outcomes those commands -establish. That cost is the point. A command list records what was run; a -required list records what must be true. Only the second can be checked by -anything other than the person who ran it. - -**`rollback_contract.order` is required.** Taken from `rapp-qonto`. Recoverable -from the command sequences the platform rapps already document, so the cost is -low. `direct_kubernetes_fallback` generalizes to `fallback`. - -**`workload_identity.name` is the workload, never the repo**, and the schema -forbids the `rapp-` prefix on it outright. `rapp-qonto` currently sets it to -`rapp-qonto`; the correct value is `qonto`. - -**`contract_version`, `readiness_state`, `data_classification` and -`criticality` are required.** All four exist only in `rapp-qonto` today. The -first two are promoted because the rail family has carried them consistently in -both rails and has not drifted — that contrast is the whole argument for -schema. The second two are promoted because a package whose classification and -criticality are unstated cannot be admitted to a reef on evidence. - -**`consumer_contract` is schema-defined but optional.** Only `rapp-postgres` -provisions isolated units to other services. Requiring it everywhere would be -false precision. - -## Open question: what may `ownership_repo` name? - -Currently `rapp-openbao` and `rapp-postgres` name `railiance-platform`, a layer -repo, and `rapp-qonto` names `qonto-assistant`, an application repo. - -The schema admits **both**, requiring only that there be exactly one and that -it not be the rapp repo itself. This is deliberately the permissive reading, -taken so the schema does not silently invalidate two of three live declarations -on a question the survey cannot settle. - -The tighter alternative is that `ownership_repo` must name the repo whose team -is accountable for the workload in production, which would likely disqualify a -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 - -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. - -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. diff --git a/schemas/rapp.schema.json b/schemas/rapp.schema.json deleted file mode 100644 index 811278e..0000000 --- a/schemas/rapp.schema.json +++ /dev/null @@ -1,350 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://railiance.dev/schemas/rapp.schema.json", - "title": "Railiance rapp declaration", - "description": "Normative shape for declarations/rapp.yaml in every rapp-* repo. Authored by railiance-master under RAILIANCE-WP-0021 from the drift survey routed by railiance-platform (RAILIANCE-WP-0015). This schema is normative; docs/repo-family-bootstrap-contract.md cites it rather than restating it.", - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "repo_family", - "rapp_id", - "repo", - "ownership_repo", - "contract_version", - "readiness_state", - "workload_identity", - "data_classification", - "criticality", - "primary_rail", - "supported_rails", - "runtime_dependencies", - "composition", - "rollout_contract", - "smoke_contract", - "rollback_contract" - ], - "properties": { - "kind": { - "const": "managed-workload-package", - "description": "Fixed discriminator. All three live declarations already agree on this value." - }, - "repo_family": { - "const": "rapp" - }, - "rapp_id": { - "$ref": "#/$defs/rappSlug", - "description": "Canonical identifier for the rapp. Must equal the repo slug for a single-repo rapp; for a grouped rapp it names the bounded context and need not match any member repo." - }, - "repo": { - "$ref": "#/$defs/repoSlug", - "description": "The rapp-* repo holding this declaration." - }, - "ownership_repo": { - "$ref": "#/$defs/repoSlug", - "description": "The single repo accountable for this workload's lifecycle. Both layer repos (railiance-platform) and application repos (qonto-assistant) are legal values; what the schema requires is exactly one, and that it not be the rapp repo itself. See schemas/README.md for the open question on tightening this." - }, - "contract_version": { - "$ref": "#/$defs/semver", - "description": "Version of this declaration contract. Promoted from rapp-qonto-only to required, matching the rail family, which has carried it consistently in both rails and has not drifted." - }, - "readiness_state": { - "enum": ["draft", "declared", "deployed", "verified", "retired"], - "description": "Lifecycle state of the package. Promoted from rapp-qonto-only to required, matching rail.yaml." - }, - "workload_identity": { - "$ref": "#/$defs/workloadIdentity" - }, - "data_classification": { - "enum": ["public", "internal", "confidential", "restricted"], - "description": "Highest classification of data the workload handles. Promoted from rapp-qonto-only to required: a package whose classification is unstated cannot be admitted to a reef on evidence." - }, - "criticality": { - "enum": ["low", "medium", "high", "critical"], - "description": "Operational criticality. Promoted from rapp-qonto-only to required. reef.yaml already carries the same vocabulary." - }, - "primary_rail": { - "$ref": "#/$defs/railSlug", - "description": "The rail this package is deployed on by default. Must appear in supported_rails." - }, - "supported_rails": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "$ref": "#/$defs/railSlug" } - }, - "runtime_dependencies": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "minLength": 1 }, - "description": "Capabilities the workload requires at runtime. Free-form strings today; a controlled vocabulary shared with rail.required_substrate_capabilities is deferred, not rejected." - }, - "composition": { "$ref": "#/$defs/composition" }, - "secret_references": { - "type": "array", - "uniqueItems": true, - "items": { "type": "string", "minLength": 1 }, - "description": "OpenBao paths the workload reads. Paths only — never values. Optional, since not every package holds secrets." - }, - "consumer_contract": { "$ref": "#/$defs/consumerContract" }, - "consumers": { - "type": "array", - "items": { "$ref": "#/$defs/consumer" }, - "description": "Present only where consumer_contract is present." - }, - "rollout_contract": { "$ref": "#/$defs/rolloutContract" }, - "smoke_contract": { "$ref": "#/$defs/smokeContract" }, - "rollback_contract": { "$ref": "#/$defs/rollbackContract" }, - "source_documents": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#/$defs/sourceDocument" }, - "description": "Documents this declaration was derived from. All three live rapps already carry this consistently." - } - }, - "allOf": [ - { - "description": "primary_rail must be one of supported_rails. Expressed as a validator check rather than in schema, since JSON Schema cannot compare a value against a sibling array; the validator enforces it.", - "type": "object" - }, - { - "description": "consumers requires consumer_contract.", - "if": { "required": ["consumers"] }, - "then": { "required": ["consumer_contract"] } - } - ], - "$defs": { - "repoSlug": { - "type": "string", - "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", - "minLength": 2 - }, - "rappSlug": { - "type": "string", - "pattern": "^rapp-[a-z0-9]+(-[a-z0-9]+)*$" - }, - "railSlug": { - "type": "string", - "pattern": "^rail-[a-z0-9]+(-[a-z0-9]+)*$" - }, - "semver": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$" - }, - "notes": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "description": "Human-readable caveats. Never load-bearing: nothing may depend on parsing a note." - }, - - "workloadIdentity": { - "type": "object", - "additionalProperties": false, - "required": ["name", "package_type"], - "description": "Identity of the WORKLOAD, never of the repo. rapp-qonto currently sets name to the repo slug (rapp-qonto); that is the drift this definition closes. The correct value there is qonto.", - "properties": { - "name": { - "type": "string", - "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", - "not": { "pattern": "^rapp-" }, - "description": "The workload's own name. Must not carry the rapp- prefix: that prefix names a repo, and this field does not name a repo." - }, - "package_type": { - "enum": [ - "helm-managed-platform-service", - "manifest-managed-platform-service", - "knative-managed-service", - "grouped-composition" - ], - "description": "grouped-composition is for rapps whose members carry their own package types." - }, - "principal": { "type": "string" }, - "service_account": { "type": "string" }, - "tenant": { - "type": "string", - "description": "Opaque NetKingdom tenant identifier (tenant::, net-kingdom ADR-0013). A rapp never mints, parses, or authorizes on this." - }, - "chart": { "type": "string" }, - "chart_version": { "type": "string" }, - "app_version": { "type": "string" }, - "notes": { "$ref": "#/$defs/notes" }, - "note": { "type": "string" } - } - }, - - "composition": { - "type": "object", - "additionalProperties": false, - "required": ["purpose", "member_repos"], - "description": "What this rapp is composed of. Supersedes the flat members: list from proposal 04c776c4 per amendment f88f938d. A rapp is a composition of first-party repos AND pinned third-party components, serving a stated purpose.", - "properties": { - "purpose": { - "type": "string", - "minLength": 12, - "description": "What this coherent workload does. One sentence. This is the bounded-context statement; if it cannot be written without 'and also', the grouping is probably wrong." - }, - "member_repos": { - "type": "array", - "minItems": 1, - "description": "First-party repos, each sharing rollout and rollback fate with the others. A single-repo rapp declares exactly one member. Grouping is legitimate ONLY where members share rollout and rollback fate; members that can be rolled back independently belong in separate rapps.", - "items": { - "type": "object", - "additionalProperties": false, - "required": ["repo", "role", "deployables"], - "properties": { - "repo": { "$ref": "#/$defs/repoSlug" }, - "role": { - "type": "string", - "minLength": 3, - "description": "What this repo contributes to the composition." - }, - "deployables": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "minLength": 1 }, - "description": "Names of the running units this repo contributes. CARDINALITY: a repo may appear in the member_repos of MORE THAN ONE rapp (many-to-many), but each deployable belongs to EXACTLY ONE rapp (one-to-one) — exactly one rapp owns its rollout. The validator's coverage check depends on this asymmetry; see schemas/README.md." - } - } - } - }, - "upstream_components": { - "type": "array", - "description": "Third-party components, pinned. An unpinned upstream is not a composition member, it is a liability.", - "items": { - "type": "object", - "additionalProperties": false, - "required": ["name", "source", "version"], - "properties": { - "name": { "type": "string", "minLength": 1 }, - "source": { - "type": "string", - "minLength": 1, - "description": "Chart repo/chart or registry reference." - }, - "version": { - "type": "string", - "minLength": 1, - "description": "An exact pin. Ranges and floating tags such as latest are not pins." - } - } - } - } - } - }, - - "rolloutContract": { - "type": "object", - "additionalProperties": false, - "required": ["default_mode"], - "description": "NORMATIVE SHAPE. Resembles rapp-openbao and rapp-postgres for the imperative fields (commands) and rapp-qonto for the gate. default_mode was already common to all three live declarations and is kept.", - "properties": { - "default_mode": { - "type": "string", - "minLength": 3, - "description": "How this package is rolled out by default, e.g. helm-upgrade-install, kubectl-server-side-apply, revision-canary." - }, - "commands": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "description": "Renamed from supported_commands. The imperative execution path where one exists; absent for packages rolled out declaratively." - }, - "production_gate": { - "type": "string", - "description": "Named gate that must be satisfied before production rollout, e.g. reef-production-approved." - }, - "notes": { "$ref": "#/$defs/notes" } - } - }, - - "smokeContract": { - "type": "object", - "additionalProperties": false, - "required": ["required"], - "description": "NORMATIVE SHAPE, taken from rapp-qonto. required: names the OUTCOMES that must hold; commands: is how they are exercised. The platform rapps currently declare only commands, so migrating them means naming their checks — that is real migration cost and it is the point: a command list says what was run, not what must be true.", - "properties": { - "required": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { "type": "string", "minLength": 1 }, - "description": "Named checks that must pass, e.g. authenticated-activation-from-zero, wrong-tenant-denied." - }, - "commands": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "description": "Renamed from supported_commands." - }, - "notes": { "$ref": "#/$defs/notes" } - } - }, - - "rollbackContract": { - "type": "object", - "additionalProperties": false, - "required": ["order"], - "description": "NORMATIVE SHAPE, taken from rapp-qonto. order: is the ordered preference of rollback strategies, most-preferred first. The platform rapps currently declare only commands; their order is recoverable from the command sequence they already document.", - "properties": { - "order": { - "type": "array", - "minItems": 1, - "items": { "type": "string", "minLength": 1 }, - "description": "Ordered rollback strategies, most preferred first, e.g. previous-verified-knative-revision, temporary-minimum-scale, unavailable." - }, - "commands": { - "type": "array", - "items": { "type": "string", "minLength": 1 }, - "description": "Renamed from supported_commands." - }, - "fallback": { - "type": "string", - "description": "Generalized from rapp-qonto's direct_kubernetes_fallback. Names the exceptional path and its expiry condition." - }, - "notes": { "$ref": "#/$defs/notes" } - } - }, - - "consumerContract": { - "type": "object", - "additionalProperties": false, - "required": ["boundary_unit", "credential_lane", "credential_type"], - "description": "Present only on packages that provision isolated units to other services. Schema-defined but optional: today only rapp-postgres carries one, and requiring it everywhere would be false precision.", - "properties": { - "boundary_unit": { "type": "string", "minLength": 1 }, - "credential_lane": { "type": "string", "minLength": 1 }, - "credential_type": { "type": "string", "minLength": 1 }, - "tenant_keying_required": { "type": "boolean" }, - "notes": { "$ref": "#/$defs/notes" } - } - }, - - "consumer": { - "type": "object", - "additionalProperties": false, - "required": ["name", "declaration"], - "properties": { - "name": { "type": "string", "minLength": 1 }, - "declaration": { - "type": "string", - "minLength": 1, - "description": "Repo-relative path to the consumer declaration." - }, - "cost_attribution_key": { - "type": "string", - "description": "Ties consumption to the cost evidence contract (fin-hub FIN-WP-0004)." - } - } - }, - - "sourceDocument": { - "type": "object", - "additionalProperties": false, - "required": ["repo", "path"], - "properties": { - "repo": { "$ref": "#/$defs/repoSlug" }, - "path": { "type": "string", "minLength": 1 } - } - } - } -} diff --git a/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md b/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md index 9c0ff3d..405c9f1 100644 --- a/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md +++ b/workplans/RAILIANCE-WP-0021-rapp-declaration-schema.md @@ -21,7 +21,6 @@ related_repos: - rail-knative - reef-railiance - the-custodian -state_hub_workstream_id: "37667c23-1fa9-430e-8d80-d67d98f90e38" --- # RAILIANCE-WP-0021 - Rapp Declaration Schema and Family Declaration Validator @@ -131,9 +130,17 @@ independent dimension from rapp grouping and needs its own work record. id: RAILIANCE-WP-0021-T01 status: done priority: high -state_hub_task_id: "af45d82d-c1e3-47b3-81ae-2974bc65cc5d" ``` +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. @@ -149,22 +156,12 @@ 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. -**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. - ## T02 - Author schemas/rapp.schema.json ```task id: RAILIANCE-WP-0021-T02 -status: done +status: todo priority: high -state_hub_task_id: "87ae4551-1fac-4613-b258-28c8b22bbb82" ``` Create `schemas/` and write `rapp.schema.json` covering: @@ -190,64 +187,12 @@ Pick one normative shape for `rollout_contract`, `smoke_contract` and the schema which existing declaration the chosen shape most resembles, so migration cost is visible. -**Done 2026-08-11.** `schemas/rapp.schema.json` (draft 2020-12) and -`schemas/README.md` written from the three live declarations rather than from -the survey summary, which turned out to understate the drift — see the extra -finding below. - -Normative choices, with who pays: - -- `default_mode` kept; all three declarations already carried it. No cost. -- `supported_commands` → `commands` in all three contracts. Mechanical. -- `smoke_contract.required` is required, `commands` optional — taken from - `rapp-qonto`. This is the one migration with real cost: the platform rapps - declare only commands and their owners must now name the outcomes those - commands establish. A command list records what was run; a required list - records what must be true, and only the second is checkable by anyone other - than the person who ran it. -- `rollback_contract.order` required, also from `rapp-qonto`, recoverable from - the command sequences already documented. `direct_kubernetes_fallback` - generalizes to `fallback`. -- `workload_identity.name` is the workload, never the repo; the schema forbids - the `rapp-` prefix outright. -- `contract_version`, `readiness_state`, `data_classification`, `criticality` - all promoted to required. -- `consumer_contract` schema-defined but optional. - -`ownership_repo` is left permissive — exactly one repo, not the rapp repo -itself, layer and application repos both legal — so the schema does not -silently invalidate two of three live declarations on a question the survey -cannot settle. The tighter alternative and its consequence are written up in -`schemas/README.md` for the architecture owner. Treat the permissive rule as -provisional. - -Validated against all three live declarations with `jsonschema` 4.10.3. The -schema is well-formed and reproduces precisely the drift the survey reported, -which is the intended result — it fails on real drift and not on anything else: - -| Declaration | Errors | Character | -|---|---|---| -| `rapp-openbao` | 10 | four missing metadata fields, no composition, three contracts on the old shape | -| `rapp-postgres` | 10 | identical set — the two platform rapps drifted together | -| `rapp-qonto` | 4 | composition, `direct_kubernetes_fallback`, missing `package_type`, and `workload_identity.name` = `rapp-qonto` | - -**Extra finding, not in the routed survey.** There are six `rapp-*` repos, not -three: `rapp-secrets-engine`, `rapp-tenant-engine` and `rapp-user-engine` exist -and carry no `declarations/` directory at all. The survey counted only declared -rapps, so the real gap is wider than the roughly 17-workloads-versus-3-rapps -figure suggests — three further repos claim the family prefix while declaring -nothing. Raised to `railiance-platform`; whether these are stubs, abandoned, or -undeclared live workloads should be settled before T06 draws its coverage -baseline, since undeclared repos are invisible to a coverage check that reads -declarations. - ## T03 - Define the composition block and its cardinality rules ```task id: RAILIANCE-WP-0021-T03 -status: done +status: todo priority: high -state_hub_task_id: "8b130ff8-0601-40bb-8539-d6f5847c2f41" ``` Supersede the flat `members:` list from the original proposal with the @@ -277,35 +222,12 @@ 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. -**Done 2026-08-11.** `composition` is required on every rapp, in -`schemas/rapp.schema.json` under `$defs/composition`. A single-repo rapp -declares exactly one member, so there is no second shape for the ungrouped -case — the composition block is the only way to state membership. - -`purpose` carries a `minLength` and is documented as the bounded-context -statement: if it cannot be written without "and also", the grouping is probably -wrong. That is the closest this schema gets to testing shared rollout fate; -the substantive check is structural, in that `deployables` is required on every -member, which is what T06 consumes. - -Both cardinalities are encoded where they bite. `member_repos[].repo` is -unconstrained across rapps, so a repo may appear in several — many-to-many. -`member_repos[].deployables` is documented as one-to-one and enforced by the -validator's coverage check rather than by the schema, since no single-file -schema can see across declarations. `upstream_components` requires an exact -`version` pin, with the schema stating that ranges and floating tags are not -pins. - -`workload_identity.package_type` gains a `grouped-composition` value for rapps -whose members carry their own package types. - ## T04 - Schematize rail.yaml and reef.yaml, and derive bound_rapps ```task id: RAILIANCE-WP-0021-T04 status: todo priority: medium -state_hub_task_id: "50ffbff9-f8f0-4085-975a-6c5123d9c2f0" ``` Write `schemas/rail.schema.json` and `schemas/reef.schema.json` from the @@ -323,7 +245,6 @@ migration path for `reef-railiance`, whose current list is already known stale. id: RAILIANCE-WP-0021-T05 status: todo priority: high -state_hub_task_id: "0a212d33-2169-4472-80d0-69ec4edba5cb" ``` Add a validator under `tools/` that walks the family repos, locates each @@ -348,7 +269,6 @@ sequencing, not ours. id: RAILIANCE-WP-0021-T06 status: todo priority: medium -state_hub_task_id: "519c2838-c4af-46e9-81fa-612fb09e91e3" ``` Extend the validator with the standing control-loop question: does every live @@ -373,7 +293,6 @@ worklist, not a failure to suppress. id: RAILIANCE-WP-0021-T07 status: todo priority: medium -state_hub_task_id: "aa71bc2f-7d9e-4351-8130-8b6e23b8ee36" ``` Revise `docs/repo-family-bootstrap-contract.md` so the minimum-field lists cite @@ -390,7 +309,6 @@ cardinalities, and grouped-by-bounded-context granularity. id: RAILIANCE-WP-0021-T08 status: todo priority: medium -state_hub_task_id: "28e4ef82-62ea-41d2-a059-05c047292240" ``` Correct the document to record what was actually built against what was @@ -413,7 +331,6 @@ flex-auth), `rapp-forgejo` (forgejo, runner, registry), `rapp-coulomb-social`, id: RAILIANCE-WP-0021-T09 status: wait priority: high -state_hub_task_id: "b8dc2452-8ec0-4301-98b7-4ec96411f428" ``` Waiting on T02, T03 and T05.