Self-audit after reading SS6, SS11, SS12 and SS13 in full rather than the sections cited in review. openbao A:2 -> A:0. The claim was incoherent (it invoked the absence of tenant context to justify E:0 and ignored it to claim A2, which SS4.2 defines in terms of tenant context) and unevidenced at the moment of claiming, which SS13.1 forbids and SS13.1a does not excuse above the floor. The real authorization evidence, openbao-verify-token-grants.py, is consumer-boundary and is now cited under the provider statement. Floor claims carry reason: per SS13.1a; permanent-by-design lines are marked so SS12 guard does not read them as stalled. The provider-versus-consumer finding is narrowed: SS6 plus the flex-auth I1-forever precedent already express a structurally permanent low level, so that half is withdrawn. What survives is that a provider cannot state the level it makes reachable for its consumers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
239 lines
11 KiB
Markdown
239 lines
11 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-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.
|
||
|
||
**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: 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: 0, E: 0, P: 0, R: 0 }
|
||
target: { I: 0, A: 0, E: 0, P: 0, R: 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 five 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 `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.**
|
||
|
||
**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, 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 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**.
|