railiance-platform/docs/tenancy-posture.md
codex dc4245361d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Finish RPF-WP-0018; RPF-WP-0019 repository-complete
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>
2026-08-18 13:35:04 +02:00

12 KiB
Raw Blame History

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

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

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

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 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.