2026-08-17 21:55:11 +02:00
|
|
|
|
# S3 tenancy posture declaration
|
|
|
|
|
|
|
|
|
|
|
|
Work record: `RPF-WP-0018` T01
|
|
|
|
|
|
Declared: 2026-08-17
|
2026-08-18 13:35:04 +02:00
|
|
|
|
Framework: `net-kingdom/canon/standards/tenancy-posture_v0.1.md` (draft-8, proposed)
|
2026-08-17 21:55:11 +02:00
|
|
|
|
Contract version of this document: `1.0.0`
|
|
|
|
|
|
|
2026-08-18 13:35:04 +02:00
|
|
|
|
Machine-readable source: `tenancy.yaml`. This document preserves the review
|
|
|
|
|
|
reasoning; where a snippet and the root declaration differ, `tenancy.yaml` is
|
|
|
|
|
|
authoritative.
|
|
|
|
|
|
|
2026-08-17 21:55:11 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-17 22:56:03 +02:00
|
|
|
|
**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.
|
|
|
|
|
|
|
2026-08-17 21:55:11 +02:00
|
|
|
|
## 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
|
2026-08-18 13:35:04 +02:00
|
|
|
|
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 }
|
2026-08-17 21:55:11 +02:00
|
|
|
|
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."
|
2026-08-18 13:35:04 +02:00
|
|
|
|
- "P is n/a rather than a number. See §P-axis note."
|
2026-08-17 21:55:11 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### `forgejo-db` — dedicated cluster for the forge
|
|
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
|
tenancy:
|
|
|
|
|
|
service: forgejo-db
|
|
|
|
|
|
role: storage-provider
|
2026-08-18 13:35:04 +02:00
|
|
|
|
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 }
|
2026-08-17 21:55:11 +02:00
|
|
|
|
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
|
2026-08-18 13:35:04 +02:00
|
|
|
|
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 }
|
2026-08-17 22:56:03 +02:00
|
|
|
|
permanent: [I, A, E]
|
2026-08-17 21:55:11 +02:00
|
|
|
|
service_class: latency-critical
|
|
|
|
|
|
reviewed: "2026-08-17"
|
2026-08-17 22:56:03 +02:00
|
|
|
|
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."
|
2026-08-17 21:55:11 +02:00
|
|
|
|
gap:
|
2026-08-17 22:56:03 +02:00
|
|
|
|
R: "R1 (platform default) is reachable cheaply and is not yet declared.
|
|
|
|
|
|
The only line here with a real trajectory."
|
2026-08-17 21:55:11 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-17 22:56:03 +02:00
|
|
|
|
**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
|
2026-08-18 13:35:04 +02:00
|
|
|
|
**consumer** boundary, and none of the tenancy axes has anywhere to put it.
|
2026-08-17 22:56:03 +02:00
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-17 21:55:11 +02:00
|
|
|
|
### `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.
|
|
|
|
|
|
|
2026-08-18 13:35:04 +02:00
|
|
|
|
## The P-axis note — why two entries are `n/a`
|
2026-08-17 21:55:11 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-08-18 13:35:04 +02:00
|
|
|
|
placement, and a later reader would use it to argue about isolation. `n/a`
|
|
|
|
|
|
plus the provider statement is the accurate canonical form.
|
2026-08-17 21:55:11 +02:00
|
|
|
|
|
2026-08-18 13:35:04 +02:00
|
|
|
|
## Finding adopted by `net-kingdom`
|
2026-08-17 21:55:11 +02:00
|
|
|
|
|
2026-08-18 13:35:04 +02:00
|
|
|
|
**The six ladders describe a consumer of storage. They do not describe a
|
2026-08-17 21:55:11 +02:00
|
|
|
|
provider of it.**
|
|
|
|
|
|
|
2026-08-18 13:35:04 +02:00
|
|
|
|
**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`.
|
|
|
|
|
|
|
2026-08-17 22:56:03 +02:00
|
|
|
|
**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.
|
|
|
|
|
|
|
2026-08-17 21:55:11 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-18 13:35:04 +02:00
|
|
|
|
**Proposed amendment at draft-5, offered as the first correction from review rather than
|
2026-08-17 21:55:11 +02:00
|
|
|
|
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
|
2026-08-18 13:35:04 +02:00
|
|
|
|
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.
|
2026-08-17 21:55:11 +02:00
|
|
|
|
|
|
|
|
|
|
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**.
|