"description":"Normative shape for declarations/rapp.yaml in every rapp-* repo. Authored by railiance-master under RMASTER-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.",
"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."
"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."
"description":"Reefs this package is bound to. Added by RMASTER-WP-0021-T04 as the source of reef.yaml bound_rapps. Required so the derivation is always defined: an empty list means the package is declared but not yet placed. The validator inverts this field to produce each reef's bound_rapps."
"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."
"description":"ADR-0006 admission state of the reef binding this public listener sits on. Not rapp readiness_state. Required by the validator when posture is public."
"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.",
"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."
"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.",