Compare commits
2 commits
17b2c8028b
...
28cbba1025
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28cbba1025 | ||
|
|
8fc92bd7d2 |
4 changed files with 554 additions and 11 deletions
|
|
@ -13,6 +13,7 @@
|
|||
| 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 |
|
||||
|
|
@ -45,3 +46,12 @@
|
|||
| 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 |
|
||||
|
|
|
|||
100
schemas/README.md
Normal file
100
schemas/README.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# 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.
|
||||
350
schemas/rapp.schema.json
Normal file
350
schemas/rapp.schema.json
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
{
|
||||
"$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:<grouping>:<name>, 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ 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
|
||||
|
|
@ -130,17 +131,9 @@ 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.
|
||||
|
|
@ -156,12 +149,22 @@ 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: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "87ae4551-1fac-4613-b258-28c8b22bbb82"
|
||||
```
|
||||
|
||||
Create `schemas/` and write `rapp.schema.json` covering:
|
||||
|
|
@ -187,12 +190,64 @@ 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: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8b130ff8-0601-40bb-8539-d6f5847c2f41"
|
||||
```
|
||||
|
||||
Supersede the flat `members:` list from the original proposal with the
|
||||
|
|
@ -222,12 +277,35 @@ 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
|
||||
|
|
@ -245,6 +323,7 @@ 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
|
||||
|
|
@ -269,6 +348,7 @@ 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
|
||||
|
|
@ -293,6 +373,7 @@ 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
|
||||
|
|
@ -309,6 +390,7 @@ 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
|
||||
|
|
@ -331,6 +413,7 @@ 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue