Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
# The S3 Platform-Service Rapp Pattern
|
|
|
|
|
|
|
|
|
|
Date: 2026-08-11
|
|
|
|
|
Owner: `railiance-platform` (S3)
|
|
|
|
|
Work record: `RAILIANCE-WP-0015-T01`
|
|
|
|
|
|
|
|
|
|
## Purpose
|
|
|
|
|
|
|
|
|
|
This is the reference shape for packaging a **platform service** as a `rapp-*`
|
|
|
|
|
managed workload package. It generalizes the two boundaries S3 has already
|
|
|
|
|
drawn — `docs/rapp-openbao-boundary.md` and `docs/rapp-postgres-boundary.md` —
|
|
|
|
|
so the third and fourth platform rapps do not each re-derive the split from
|
|
|
|
|
scratch.
|
|
|
|
|
|
|
|
|
|
It is deliberately *not* a general rapp standard. The four-axis repo family
|
|
|
|
|
model belongs to `railiance-master/docs/repository-axes.md`, and the
|
|
|
|
|
`rapp.yaml` schema and canon promotion are in flight there and in
|
|
|
|
|
`the-custodian`. This document covers only the half S3 owns: what a
|
|
|
|
|
platform-service rapp looks like, and where the line falls between the package
|
|
|
|
|
and the platform.
|
|
|
|
|
|
|
|
|
|
## The rule
|
|
|
|
|
|
|
|
|
|
> A rapp owns how its workload is **packaged, deployed, verified, rolled back
|
|
|
|
|
> and recovered**. S3 retains everything that is **true across workloads**:
|
|
|
|
|
> policy, credential custody, lane approval, and the shared substrate the
|
|
|
|
|
> package assumes.
|
|
|
|
|
|
|
|
|
|
Both existing platform rapps already state this in their own words. From
|
|
|
|
|
`rapp-openbao-boundary.md`: the package may own how OpenBao is packaged,
|
|
|
|
|
deployed, verified and skinned, and must not become the home for platform-admin
|
|
|
|
|
policy, workload KV lane policy, delegated metadata authority, or credential
|
|
|
|
|
grants. From `rapp-postgres-boundary.md`: the package owns manifests, consumer
|
|
|
|
|
declarations, provisioning surface, isolation tests and recovery, while S3
|
|
|
|
|
retains the operator, storage policy, backup target and the credential-broker
|
|
|
|
|
grant catalog.
|
|
|
|
|
|
|
|
|
|
## The ownership test
|
|
|
|
|
|
|
|
|
|
When an asset's home is unclear, ask in this order. The first answer that
|
|
|
|
|
applies decides it.
|
|
|
|
|
|
|
|
|
|
1. **Would this asset survive replacing the workload with a different product?**
|
|
|
|
|
If yes, it is S3's. Credential lane approval survives swapping OpenBao for
|
|
|
|
|
another secrets engine; an OpenBao Helm values file does not.
|
|
|
|
|
2. **Does any other workload depend on this asset?** If yes, it is S3's. A
|
|
|
|
|
`workload-kv-read-*` policy is consumed by the workload it names but governed
|
|
|
|
|
by a lane model shared by all of them.
|
|
|
|
|
3. **Does it encode who may approve, not how to apply?** Approval authority is
|
|
|
|
|
S3's; application mechanics are the package's. This is the line that keeps a
|
|
|
|
|
rapp from becoming a shadow S3 repo — the failure mode named explicitly in
|
|
|
|
|
`railiance-master/docs/rapp-first-wave-candidates.md`.
|
|
|
|
|
4. **Otherwise it is the package's.** Charts, values, overlays, deploy and
|
|
|
|
|
verify scripts, workload-specific smoke and recovery procedure, and the
|
|
|
|
|
Makefile targets that drive them.
|
|
|
|
|
|
|
|
|
|
### Applied
|
|
|
|
|
|
|
|
|
|
| Asset class | Home | Test |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| Helm values, chart pins, overlays, ingress/middleware manifests | rapp | 4 |
|
|
|
|
|
| Deploy / dry-run / status / verify / rollback commands | rapp | 4 |
|
|
|
|
|
| Workload-specific smoke and recovery procedure | rapp | 4 |
|
|
|
|
|
| Consumer declarations and provisioning surface | rapp | 4 |
|
|
|
|
|
| Operator and read-only policy surface | S3 | 1 |
|
|
|
|
|
| Credential lane approval, CCRs, grant catalog | S3 | 3 |
|
|
|
|
|
| Cross-workload secret delivery (ESO, KV lanes) | S3 | 2 |
|
|
|
|
|
| Backup target procurement and its credentials | S3 | 1 |
|
|
|
|
|
| Storage class, monitoring substrate, cluster access | S3 | 1 |
|
|
|
|
|
|
|
|
|
|
## Reference `rapp.yaml` for a platform service
|
|
|
|
|
|
2026-08-14 00:39:06 +02:00
|
|
|
The normative shape is `railiance-master/schemas/rapp.schema.json` (ADR-0007).
|
|
|
|
|
A platform-service rapp fills that schema as follows. `RAILIANCE-WP-0015-T02`
|
|
|
|
|
converged `rapp-openbao` and `rapp-postgres` onto this shape.
|
Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
kind: managed-workload-package
|
|
|
|
|
repo_family: rapp
|
|
|
|
|
rapp_id: rapp-<workload>
|
|
|
|
|
repo: rapp-<workload>
|
|
|
|
|
ownership_repo: railiance-platform # the S3 home retaining governance
|
2026-08-14 00:39:06 +02:00
|
|
|
contract_version: 1.0.0
|
|
|
|
|
readiness_state: verified
|
|
|
|
|
data_classification: <internal|restricted>
|
|
|
|
|
criticality: <high|critical>
|
Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
workload_identity:
|
|
|
|
|
name: <workload> # the workload, never the repo name
|
|
|
|
|
package_type: helm-managed-platform-service
|
|
|
|
|
chart: <repo/chart>
|
|
|
|
|
chart_version: <pinned>
|
|
|
|
|
app_version: <pinned>
|
|
|
|
|
primary_rail: rail-kubernetes
|
|
|
|
|
supported_rails: [rail-kubernetes]
|
2026-08-14 00:39:06 +02:00
|
|
|
bound_reefs: [reef-railiance] # empty only if the package is not placed
|
|
|
|
|
runtime_dependencies: [...] # capabilities, not the resource that satisfies them
|
|
|
|
|
composition:
|
|
|
|
|
purpose: <what this coherent workload does>
|
|
|
|
|
member_repos:
|
|
|
|
|
- repo: rapp-<workload> # single-repo rapp: one member, itself
|
|
|
|
|
role: <what it contributes>
|
|
|
|
|
deployables: [<running unit names>]
|
|
|
|
|
upstream_components:
|
|
|
|
|
- name: <chart|image>
|
|
|
|
|
source: <repo/chart or registry ref>
|
|
|
|
|
version: <exact pin>
|
|
|
|
|
rollout_contract:
|
|
|
|
|
default_mode: <helm-upgrade-install|kubectl-server-side-apply>
|
|
|
|
|
commands: [...] # renamed from supported_commands
|
|
|
|
|
smoke_contract:
|
|
|
|
|
required: [...] # outcomes the commands establish
|
|
|
|
|
commands: [...]
|
|
|
|
|
rollback_contract:
|
|
|
|
|
order: [...] # most-preferred strategy first
|
|
|
|
|
commands: [...]
|
Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
source_documents: [...]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Two conventions worth stating because they have already drifted:
|
|
|
|
|
|
|
|
|
|
- `workload_identity.name` is the **workload**, not the repo. `openbao`, not
|
|
|
|
|
`rapp-openbao`.
|
|
|
|
|
- `ownership_repo` is the repo that retains governance after extraction — for
|
|
|
|
|
platform services that is `railiance-platform`, and it is not the same thing
|
|
|
|
|
as the repo the package was carved out of.
|
|
|
|
|
|
|
|
|
|
## Grouped rapps
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2026-08-11 11:34:51 +02:00
|
|
|
### 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
|
2026-08-14 00:39:06 +02:00
|
|
|
`railiance-master/schemas/rapp.schema.json`; the form is:
|
2026-08-11 11:34:51 +02:00
|
|
|
|
|
|
|
|
```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>
|
|
|
|
|
```
|
|
|
|
|
|
Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
For a grouped rapp:
|
|
|
|
|
|
|
|
|
|
- members **must** be declared explicitly; an undeclared bundle is not a rapp,
|
|
|
|
|
it is a drawer
|
|
|
|
|
- grouping is legitimate **only** where members share rollout and rollback fate.
|
|
|
|
|
If one member can be rolled back without the others, it is a separate rapp
|
|
|
|
|
- the group's smoke contract must cover the group, not just its largest member
|
|
|
|
|
|
|
|
|
|
`rapp-postgres` is already a grouped package in this sense — it owns the CNPG
|
|
|
|
|
cluster manifests plus per-consumer declarations, and declares its consumers
|
|
|
|
|
explicitly in `consumers:`. That is the shape to copy.
|
|
|
|
|
|
|
|
|
|
## Credential lanes
|
|
|
|
|
|
|
|
|
|
A platform-service rapp never owns credential custody. It declares what it
|
2026-08-14 00:47:28 +02:00
|
|
|
needs; S3 vends it through the existing broker. The bind is
|
|
|
|
|
`docs/rapp-credential-lane-binding.md`: standing secrets go through a CCR
|
|
|
|
|
and `secret_references`; short-lived leases go through the grant catalog
|
|
|
|
|
and `rapp_id`. Follow `docs/credential-broker.md` and
|
|
|
|
|
`docs/credential-change-approval.md` for gate, delivery, and revocation —
|
|
|
|
|
this pattern does not replace them.
|
Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
|
|
|
|
|
The existing rule holds without exception: the package never commits
|
|
|
|
|
credentials, and a workload receives a short-lived lease through the platform
|
|
|
|
|
broker rather than a package-managed secret.
|
|
|
|
|
|
2026-08-14 00:39:06 +02:00
|
|
|
## Live deployable inventory
|
|
|
|
|
|
|
|
|
|
`railiance-master` coverage check (`tools/validate-family-declarations.py
|
|
|
|
|
--inventory`) must not query the cluster. S3 emits the interchange file:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
docs/evidence/reef-railiance-deployables.json
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Refresh with `scripts/capture-reef-deployables.py -o docs/evidence/reef-railiance-deployables.json`.
|
|
|
|
|
The file lists non-substrate Deployments, StatefulSets, CNPG Clusters, and
|
|
|
|
|
Knative Services on `reef-railiance`. Declared `composition.member_repos.deployables`
|
|
|
|
|
names must match these live names.
|
|
|
|
|
|
Publish S3 platform-service rapp pattern; route family proposals
T01: docs/rapp-platform-service-pattern.md generalizes the ownership split
already drawn in the rapp-openbao and rapp-postgres boundary docs into a
reusable four-question test, a reference rapp.yaml for platform services, the
grouped-rapp member rule, and the credential-lane position. It deliberately
does not restate the four-axis model, which railiance-master owns.
T03/T04/T05: proposals routed to the repos that own the model rather than
authored here - reef-railiance (bound_rapps lists 1 of 3 live rapps, and should
be derived rather than hand-listed), railiance-master (rapp.schema.json plus a
family declaration validator, grouped-rapp members field, wave-2 candidate
refresh), the-custodian (canon promotion of the four-axis model, which also
closes the open C-31 multi-segment prefix failures).
T02 is held until the schema settles so the platform rapps and the schema do
not converge on different answers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:11:57 +02:00
|
|
|
## When a platform service earns a rapp
|
|
|
|
|
|
|
|
|
|
Not every S3 service needs one. A platform service is ready for extraction when
|
|
|
|
|
it has a stable workload identity, a package surface already visible in Git, an
|
|
|
|
|
explicit dependency and secret story, and described deploy/verify/recover
|
|
|
|
|
behavior — the criteria from `rapp-first-wave-candidates.md`. A service that
|
|
|
|
|
fails these is not blocked from being operated; it simply stays owned by S3
|
|
|
|
|
until its packaging identity stops moving.
|