RPF-WP-0018 closed: all seven tasks done. The provider-declaration finding was adopted upstream and its canonical form is the provider: block in tenancy.yaml; adaptive-pricing declined the standing co-signature and supplied typed tier minima instead, recorded in ADR-0002. Three corrections against our own output are recorded in the documents rather than edited away. RPF-WP-0019 T03 done (ceiling of three, memory binding, apps-pg-2 named as overflow, enforced by make apps-pg-verify-capacity). T01/T02 are repository-complete: backup target, retention, per-consumer connection limits, role timeouts and Burstable resources are declared in source and published in s3-consumer-interfaces 1.1.0 before rollout. They stay in progress because no live application, backup success or restore proof exists, and declared configuration is not a section 13 artifact. T04 waits on that window. apps-pg R reason corrected to say the target is declared-not-applied rather than absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
248 lines
12 KiB
Markdown
248 lines
12 KiB
Markdown
# 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-8, proposed)
|
||
Contract version of this document: `1.0.0`
|
||
|
||
Machine-readable source: `tenancy.yaml`. This document preserves the review
|
||
reasoning; where a snippet and the root declaration differ, `tenancy.yaml` is
|
||
authoritative.
|
||
|
||
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.
|
||
|
||
**Every level below carries what §13 requires of it.** §13.1: a level is
|
||
claimed only with its evidence artifact present. §13.1a: at or below the
|
||
"no control" rung, a declaration requires a **stated reason** instead, because
|
||
there is nothing to overclaim at the bottom of a ladder. Floor claims here
|
||
carry `reason:`; claims above the floor carry `evidence:`. A claim with
|
||
neither would be the overclaim §6 prohibits.
|
||
|
||
Where `target` equals `current`, that is a **settled position, not a stalled
|
||
trajectory** — §6 permits a low level to be permanent by design, on the
|
||
`flex-auth` precedent (`I1` forever, because verifying its own inputs would
|
||
make it the identity provider its scope refuses to be). Those lines carry
|
||
`permanent: true` so §12's guard does not nag them.
|
||
|
||
## 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: "n/a", R: 0, V: 0 }
|
||
target: { I: 0, A: 0, E: 0, P: "n/a", R: 2, V: 1 }
|
||
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 n/a 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: "n/a", R: 0, V: 0 }
|
||
target: { I: 0, A: 0, E: 0, P: "n/a", R: 2, V: 1 }
|
||
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: 0, E: 0, P: 0, R: 0, V: 0 }
|
||
target: { I: 0, A: 0, E: 0, P: 0, R: 1, V: 1 }
|
||
permanent: [I, A, E]
|
||
service_class: latency-critical
|
||
reviewed: "2026-08-17"
|
||
reason:
|
||
I: "No tenant concept. OpenBao authenticates workloads via Kubernetes
|
||
auth; it never receives, parses or validates a NetKingdom tenant
|
||
identifier. Permanent by design — a secrets engine that resolved
|
||
tenant identity would be duplicating tenant-engine."
|
||
A: "No tenant context is carried, so there is nothing to bind. Permanent
|
||
by design, on the §6 flex-auth precedent."
|
||
E: "No tenant-keyed data. OpenBao holds workload secrets, not tenant
|
||
records."
|
||
P: "One instance, every consumer shares it. Single-node rail; §17
|
||
scaling demands apply."
|
||
R: "No retention position on audit device output or on KV version
|
||
history."
|
||
gap:
|
||
R: "R1 (platform default) is reachable cheaply and is not yet declared.
|
||
The only line here with a real trajectory."
|
||
```
|
||
|
||
**Correction, 2026-08-17.** This vector first declared `A: 2`. That was
|
||
wrong twice over and both errors are worth recording rather than quietly
|
||
editing.
|
||
|
||
*It was internally incoherent.* `E: 0` was justified on the ground that
|
||
OpenBao has no tenant dimension. §4.2's `A2` reads "a single local
|
||
authorization boundary; **tenant context** bound once, centrally" — the same
|
||
dimension. A declaration cannot invoke the absence of tenant context to claim
|
||
the floor on one axis and ignore it to claim a rung on the next.
|
||
|
||
*It was unevidenced at the moment of claiming.* §13.1 requires the artifact to
|
||
be present when the level is claimed, and none was cited. §13.1a would not
|
||
have rescued it: the table defines an artifact from `A2` upward, so `A2` is
|
||
precisely the first rung where the exemption stops applying.
|
||
|
||
The irony is the point. This repo routed a finding about unevidenced claims
|
||
in the same week it made one.
|
||
|
||
**What the corrected zeros conceal, and why the provider note below exists.**
|
||
`A: 0` now reads as though OpenBao performs no authorization. It performs a
|
||
great deal, it is central, and it is mechanically evidenced —
|
||
`scripts/openbao-verify-token-grants.py` mints a scoped child token, asserts
|
||
it *can* sign with `ssh/sign/agt-role`, and asserts it *cannot* read policy
|
||
metadata, then revokes by accessor. That is exactly the shape §13 asks for at
|
||
`A2`: choke point identified, unbound request refused. It is pointed at the
|
||
**consumer** boundary, and none of the tenancy axes has anywhere to put it.
|
||
|
||
That evidence is therefore cited under the provider statement rather than
|
||
against a consumer axis. Consumer separation in OpenBao is structural — a
|
||
workload token's policy cannot address another workload's path at all, which
|
||
is `E4`-shaped machinery — and the framework's honest application scores it
|
||
`0`.
|
||
|
||
`A3` is not a target: a PDP in the credential path would put `flex-auth` on
|
||
OpenBao's availability path and OpenBao on `flex-auth`'s.
|
||
|
||
### `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 `n/a`
|
||
|
||
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. `n/a`
|
||
plus the provider statement is the accurate canonical form.
|
||
|
||
## Finding adopted by `net-kingdom`
|
||
|
||
**The six ladders describe a consumer of storage. They do not describe a
|
||
provider of it.**
|
||
|
||
**Resolved in draft-7 and formalised in draft-8.** Decision 5.5 adopted the
|
||
provider declaration; the draft-8 schema standardises `available`, `maximum`,
|
||
conditions and evidence per axis. The canonical form is now in `tenancy.yaml`.
|
||
|
||
**Narrowed on re-reading, 2026-08-17.** An earlier version of this finding
|
||
claimed the framework had no way to say "this zero is structural, not weak".
|
||
That was wrong: §6 says exactly that, and §13.1a supplies the mechanism —
|
||
`target` equal to `current` with a stated reason is a settled position, on the
|
||
`flex-auth` `I1`-forever precedent. This declaration now uses it. Half of the
|
||
finding is withdrawn.
|
||
|
||
What survives is the other half, and it is not expressible: a provider cannot
|
||
state **what level it makes reachable for the workloads it holds**, nor record
|
||
a control that is real, mechanically evidenced, and simply not on any of the
|
||
five consumer axes.
|
||
|
||
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 at draft-5, 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 consumer ladders produces
|
||
it. Draft-8 adopted and standardised that amendment; the root declaration is
|
||
now authoritative.
|
||
|
||
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 P0–P4 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**.
|