RPF-WP-0018 T01-T06: publish S3 posture, placement policy, quotas, ADR surface

T01 docs/tenancy-posture.md - vector set per service rather than one repo
vector, with the provider-versus-consumer finding routed to net-kingdom.
T02/T03/T06 docs/placement-policy.md - accepts placement ownership scoped
to rule-here/number-there, records a placement owner per workload, reports
the latency-critical + batch co-residency on platform-pg, marks the
connection-ceiling trigger unmonitored pending railiance-telemetry, and
answers the retention floor/ceiling question.
T04 s3-consumer-interfaces 1.1.0 - quota disclosure per SS10.2. Surfaces
that apps-pg has no backup, no resource limits and no tuned parameters.
T05 docs/adr/ created with a mandatory-frontmatter convention and the
first three ADRs. This repo previously held none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-17 21:55:11 +02:00
parent b83194741d
commit e7e4e33bb8
9 changed files with 809 additions and 12 deletions

177
docs/tenancy-posture.md Normal file
View file

@ -0,0 +1,177 @@
# S3 tenancy posture declaration
Work record: `RPF-WP-0018` T01
Declared: 2026-08-17
Framework: `net-kingdom/canon/standards/tenancy-posture_v0.1.md` (draft-5, proposed)
Contract version of this document: `1.0.0`
This is `railiance-platform`'s response to the Tenancy Posture ratification
test (§20.2): *"Each publishes its own posture vector as part of review. The
framework is validated by whether it can describe them accurately."*
It declares under §6 — **conformance is accuracy, not altitude**. Several
levels below are `0`. None of them is an admission of weakness, and §6 is
explicit that a declared low level is conformant while an unevidenced high
level is not.
## Why this is a vector *set* and not a vector
§5 asks a **service** for one level per axis. `railiance-platform` is an OAS
**layer** holding custody of several services with materially different
postures. A single vector for the repo would be an average of things that are
not alike, which is the inaccuracy §6 prohibits.
So: one vector per service, plus an explicit statement of what this repo owns
versus what it merely holds policy for. The distinction matters because
`RAILIANCE-WP-0012` and `RAILIANCE-WP-0015` deliberately moved the deployable
surface to the `rapp-*` repos while custody and policy stayed here.
| Service | This repo's relationship | Deployable owned by |
| --- | --- | --- |
| `apps-pg` | **owns** — cluster CR is `helm/apps-pg-cluster.yaml` | this repo |
| `forgejo-db` | **owns** — cluster CR is `helm/forgejo-db-cluster.yaml` | this repo |
| `openbao` | **custody and policy**; package extracted | `rapp-openbao` |
| `platform-pg` | **policy only**; consumer contract and grants here | `rapp-postgres` |
| Valkey, MinIO | declared capability, no live instance | — |
## The vectors
### `apps-pg` — shared cluster for S5 application databases
```yaml
tenancy:
service: apps-pg
role: storage-provider
current: { I: 0, A: 0, E: 0, P: null, R: 0 }
target: { I: 0, A: 0, E: 0, P: null, R: 2 }
service_class: interactive
reviewed: "2026-08-17"
gap:
R: "No backup is configured on this cluster at all — no barmanObjectStore,
no retentionPolicy, no deletion path. R0 is the accurate reading and
it is worse than it looks: R0 here means no recovery, not merely no
erasure policy. Closing this is the highest-value item this
declaration surfaced. Target R2 requires a backup target first."
notes:
- "I/A/E are 0 by construction, not by neglect. apps-pg holds no tenant
concept, carries no tenant claim, and applies no tenant predicate. The
tenant boundary for data on this cluster sits entirely in the consuming
S5 application. See the provider note below."
- "P is null rather than a number. See §P-axis note."
```
### `forgejo-db` — dedicated cluster for the forge
```yaml
tenancy:
service: forgejo-db
role: storage-provider
current: { I: 0, A: 0, E: 0, P: null, R: 0 }
target: { I: 0, A: 0, E: 0, P: null, R: 2 }
service_class: interactive
reviewed: "2026-08-17"
gap:
R: "Backup exists as a documented procedure (docs/forgejo-backup.md), not
as a declared retention with a published erasure horizon. R2 needs the
horizon published to the consumer, which is the point of the level."
notes:
- "Single-consumer cluster. Provides P2 to forgejo; is not itself at P2."
```
### `openbao` — platform secrets service
```yaml
tenancy:
service: openbao
role: credential-provider
current: { I: 0, A: 2, E: 0, P: 0, R: 0 }
target: { I: 0, A: 2, E: 0, P: 0, R: 1 }
service_class: latency-critical
reviewed: "2026-08-17"
gap:
R: "No retention position on audit device output or on KV version history.
R1 (platform default) is reachable cheaply and is not yet declared."
notes:
- "A2 is claimed on the consumer boundary, not a tenant boundary: policy
per workload path, bound once, centrally, in OpenBao's own policy
engine. It is NOT delegated to flex-auth, so A3 is not claimed and is
not currently a target — a PDP in the credential path would put
flex-auth on OpenBao's availability path and OpenBao on flex-auth's."
- "E is 0 on the TENANT axis and that number is misleading without this
sentence: consumer separation in OpenBao is structural — a workload
token's policy cannot address another workload's path at all, which is
E4-shaped machinery. It scores 0 because the axis measures the tenant
boundary and OpenBao has no tenant dimension. See the provider note."
- "P0 is accurate and deliberate: one instance, every consumer shares it.
Single-node rail; §17 scaling demands apply."
```
### `platform-pg` — policy relationship only
Declared by `rapp-postgres`. This repo does not restate it. What this repo
declares about it is in `docs/placement-policy.md`: the rule, the placement
owner per consumer, and the triggers. The numbers — `instances: 1`,
`max_connections: 100`, `1Gi`, `retentionPolicy: 30d` — are `rapp-postgres`'s
cluster CR and its declaration to make.
## The P-axis note — why two entries are `null`
The P ladder describes **which substrate holds a tenant's data**, from the
point of view of the workload whose data it is. `apps-pg` and `forgejo-db` are
the substrate. Asking a cluster what its own placement level is has no
well-formed answer: `apps-pg` *provides* P1 to each of its consumers, and
`forgejo-db` *provides* P2 to forgejo, but neither *is* at a level.
Declaring `P: 1` for `apps-pg` would read as though the cluster had a
placement, and a later reader would use it to argue about isolation. `null`
plus the provider statement is the accurate form. This is routed as a finding.
## Finding routed to `net-kingdom`
**The five ladders describe a consumer of storage. They do not describe a
provider of it.**
Every service above scores at or near zero on I, A and E, and in each case for
the same structural reason rather than for a weakness: a storage or credential
platform has no tenant dimension of its own. It is exactly as strong or weak as
its consumers make it. The framework anticipates this in §3.3 and in the
"newly absorbed repo" example, but the vector shape gives a provider no way to
say the useful thing, which is not *what level am I at* but **what level do I
make reachable for the workloads I hold**.
The concrete case is the `openbao` E line. `E: 0` is literally correct and
actively misleading — the mechanism in place is credential-scoped structural
separation, the same machinery E4 describes, pointed at a consumer boundary
rather than a tenant one. A reader scanning a table of E values would rank
OpenBao below a service doing per-query filtering in application code, which
inverts the actual security position.
**Proposed amendment, offered as the first correction from review rather than
from research (§Status observes that every correction so far came from research
or relocation):** add a **provider declaration** beside the posture vector — a
platform service states, per axis, the maximum level it makes reachable and
what the consumer must do to reach it. For `apps-pg` that is: E4 unreachable
(shared credential per consumer, no per-tenant credential), E3 reachable once
the GUC contract exists, R2 blocked on a backup target. That is the sentence a
consumer actually needs, and no arrangement of the five consumer ladders
produces it.
This is offered as an addition, not a replacement. The consumer vector is
right for consumers, and this repo is not asking for the ladders to be
renumbered.
## What this repo accepts
- The P0P4 ladder as written, by reference. It describes our estate correctly.
- §8.1's monitoring obligation, with the deferrals stated in
`docs/placement-policy.md` rather than silently.
- §9.1 — no static long-lived database credentials above E0. Already how the
credential broker works; no change needed.
- §10.2 — quota disclosure. Landed in `docs/s3-consumer-interfaces.md` 1.1.0.
- §6 — accuracy over altitude, which is why this document declares four zeros.
## Review interval
Six months, or on any of: a service moving placement level, a backup target
becoming available, or the framework reaching `accepted`. Next review due
**2027-02-17**.