From cc4e659a9e92fbf0a4506eaa2c86791e8164cbca Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 17 Aug 2026 22:56:03 +0200 Subject: [PATCH 1/2] Correct the S3 posture declaration against Tenancy Posture SS13 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 --- WORK-RECORDS.md | 16 +++---- docs/tenancy-posture.md | 96 +++++++++++++++++++++++++++++++++-------- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 1dfa2b6..b553aac 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -20,7 +20,7 @@ | workplan | RAILIANCE-WP-0016 | finished | — | workplans/RAILIANCE-WP-0016-apps-pg-resource-evidence.md | | workplan | RAILIANCE-WP-0016 | finished | — | workplans/RAILIANCE-WP-0016-architecture-cleanup-backlog.md | | workplan | RAILIANCE-WP-0017 | finished | — | workplans/RAILIANCE-WP-0017-consumption-mode-enforcement.md | -| workplan | RPF-WP-0018 | proposed | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| workplan | RPF-WP-0018 | active | — | workplans/RPF-WP-0018-policy-surface-alignment.md | | task | RAILIANCE-WP-0005-T01 | done | — | workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md | | task | RAILIANCE-WP-0005-T02 | done | — | workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md | | task | RAILIANCE-WP-0005-T03 | done | — | workplans/RAILIANCE-WP-0005-credential-request-and-lease-broker.md | @@ -81,10 +81,10 @@ | task | RAILIANCE-WP-0016-T04 | done | — | workplans/RAILIANCE-WP-0016-architecture-cleanup-backlog.md | | task | RAILIANCE-WP-0016-T05 | done | — | workplans/RAILIANCE-WP-0016-architecture-cleanup-backlog.md | | task | RAILIANCE-WP-0017-T01 | done | — | workplans/RAILIANCE-WP-0017-consumption-mode-enforcement.md | -| task | RPF-WP-0018-T01 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | -| task | RPF-WP-0018-T02 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | -| task | RPF-WP-0018-T03 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | -| task | RPF-WP-0018-T04 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | -| task | RPF-WP-0018-T05 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | -| task | RPF-WP-0018-T06 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | -| task | RPF-WP-0018-T07 | todo | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T01 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T02 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T03 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T04 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T05 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T06 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | +| task | RPF-WP-0018-T07 | done | — | workplans/RPF-WP-0018-policy-surface-alignment.md | diff --git a/docs/tenancy-posture.md b/docs/tenancy-posture.md index 29fad61..df1db9e 100644 --- a/docs/tenancy-posture.md +++ b/docs/tenancy-posture.md @@ -14,6 +14,19 @@ 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 @@ -84,28 +97,65 @@ tenancy: 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 } + 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: "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." + 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 @@ -131,6 +181,18 @@ plus the provider statement is the accurate form. This is routed as a finding. **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 From b15215d1911d6e2ff3f06d4b8f9ede666c640334 Mon Sep 17 00:00:00 2001 From: codex Date: Mon, 17 Aug 2026 23:01:57 +0200 Subject: [PATCH 2/2] RPF-WP-0018: mark unevidenced P1 claims; seed RPF-WP-0019 for apps-pg Placement policy 3.1 records that the P1 levels for vergabe and coulomb_social are provisioning declarations without the SS13 isolation probes - apps-pg has none. 3.2 records that apps-pg cannot produce the noisy-neighbour artifact at all until per-consumer controls exist, per SS13.4: with nothing to bind, a probe could only demonstrate degradation. RPF-WP-0019 seeded for the three live defects the disclosure work found: no backup, no per-consumer controls, no isolation probes. Kept separate from RPF-WP-0018 so declaring is not confused with fixing. Co-Authored-By: Claude Opus 5 --- docs/placement-policy.md | 53 +++++-- ...019-apps-pg-recoverability-and-controls.md | 130 ++++++++++++++++++ 2 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 workplans/RPF-WP-0019-apps-pg-recoverability-and-controls.md diff --git a/docs/placement-policy.md b/docs/placement-policy.md index b68b28e..a4e0208 100644 --- a/docs/placement-policy.md +++ b/docs/placement-policy.md @@ -70,15 +70,50 @@ has an answer that is not archaeology. The **placement owner** decides placement changes for that workload and is accountable for watching its triggers. It is not the same role as the operator of the cluster. -| Workload | Substrate | Provided level | Placement owner | Co-signer | -| --- | --- | --- | --- | --- | -| `audit-core` | `platform-pg` | P1 | `audit-core` | this repo | -| `tenant-engine` | `platform-pg` | P1 | `tenant-engine` | this repo | -| `user-engine` | `user-engine-pg` | P2 | `net-kingdom` | this repo | -| `target-revenue` | `target-revenue-pg` | P2 | `target-revenue` | this repo | -| `forgejo` | `forgejo-db` | P2 | this repo | — | -| `vergabe` | `apps-pg` | P1 | `vergabe-teilnahme` | this repo | -| `coulomb_social` | `apps-pg` | P1 | `coulomb-social` | this repo | +| Workload | Substrate | Provided level | Placement owner | Co-signer | §13 evidence | +| --- | --- | --- | --- | --- | --- | +| `audit-core` | `platform-pg` | P1 | `audit-core` | this repo | probes exist | +| `tenant-engine` | `platform-pg` | P1 | `tenant-engine` | this repo | probes exist | +| `user-engine` | `user-engine-pg` | P2 | `net-kingdom` | this repo | not assessed here | +| `target-revenue` | `target-revenue-pg` | P2 | `target-revenue` | this repo | not assessed here | +| `forgejo` | `forgejo-db` | P2 | this repo | — | single consumer | +| `vergabe` | `apps-pg` | P1 | `vergabe-teilnahme` | this repo | **absent** | +| `coulomb_social` | `apps-pg` | P1 | `coulomb-social` | this repo | **absent** | + +### 3.1 Two of these P1 levels are asserted without evidence + +§13.1 claims a level only with its artifact present. The P1–P4 artifact is +*"provisioning declaration plus the platform's isolation probes"*. + +`platform-pg` has them — `rapp-postgres` runs 15 adversarial probes against the +consumer boundary. **`apps-pg` has none.** `scripts/capture-apps-pg-evidence.py` +captures *capacity* evidence for `resource-control`; it makes no isolation +assertion. So the P1 levels recorded above for `vergabe` and `coulomb_social` +are, as of 2026-08-17, **provisioning declarations without probes** — stated +here rather than quietly carried, because a table of levels that mixes +evidenced and unevidenced entries without saying which is which is the failure +§13.1 exists to prevent. + +Recorded as owed by this repo. Until the probes exist, those two rows should be +read as *intended* placement, not *verified* placement. + +### 3.2 `apps-pg` cannot produce the noisy-neighbour artifact at all + +Worth separating from the gap above, because it is not a matter of writing a +missing test. §13's P1–P2 noisy-neighbour artifact requires a per-consumer +usage baseline, a saturation run, and **evidence that the governance controls +bind — that the greedy consumer is held at its limits.** + +`apps-pg` has no per-consumer governance controls. Its connection pool is not +partitioned, there is no per-consumer connection allowance, no statement +timeout and no resource limit on the pod. There is nothing that could be shown +to bind, so a run against it could only demonstrate that a saturating consumer +degrades its co-residents — which §13.4 explicitly rejects as an artifact that +"can only fail, or that passes by being run gently enough". + +The honest sequence is therefore: **controls first, artifact second.** Writing +the probe before the limits exist would produce an overclaim wearing the +costume of evidence. Tracked as `RPF-WP-0019`. **The consuming repo is the placement owner; this repo co-signs.** The consumer knows its latency requirement, its erasure horizon and its compliance diff --git a/workplans/RPF-WP-0019-apps-pg-recoverability-and-controls.md b/workplans/RPF-WP-0019-apps-pg-recoverability-and-controls.md new file mode 100644 index 0000000..9e1fa14 --- /dev/null +++ b/workplans/RPF-WP-0019-apps-pg-recoverability-and-controls.md @@ -0,0 +1,130 @@ +--- +id: RPF-WP-0019 +type: workplan +title: "apps-pg: backup, per-consumer controls, and the isolation probes they make possible" +domain: financials +repo: railiance-platform +status: proposed +owner: codex +topic_slug: railiance +created: "2026-08-17" +updated: "2026-08-17" +related: + - RPF-WP-0018 +origin: residual +origin_ref: RPF-WP-0018 +--- + +# RPF-WP-0019 — apps-pg recoverability and per-consumer controls + +## Goal + +Close the three defects `RPF-WP-0018` surfaced in `apps-pg` by writing its +quota disclosure. Documentation found them; only this workplan fixes them. + +## Why this is separate from RPF-WP-0018 + +That workplan declared posture and policy. This one changes a live cluster. +Keeping them apart matters: a declaration workplan that quietly starts editing +production is how "we wrote it down" becomes indistinguishable from "we fixed +it". The declaration is published as-is, with the defects visible, and this is +the record of closing them. + +## The three defects + +**D1 — `apps-pg` has no backup.** No `barmanObjectStore`, no `retentionPolicy`, +nothing. This is not a short retention window; it is no recovery path at all, +on a cluster holding two S5 application databases. Its R level is `R0` and +`R0` here means unrecoverable, not merely un-erasable. + +**D2 — no per-consumer controls.** The connection pool is unpartitioned, so +one consumer can exhaust the cluster while staying politely inside its own +expectations. No `statement_timeout`, no `idle_in_transaction_session_timeout`, +no CPU or memory limits — the pod is BestEffort QoS and is the first thing +evicted under node pressure. + +**D3 — no isolation probes**, so the `P1` levels recorded for `vergabe` and +`coulomb_social` in `docs/placement-policy.md` §3.1 are provisioning +declarations without the §13 artifact. + +## Sequencing, and why it is not the obvious one + +**D1 first.** It is the only one whose failure is unrecoverable. A cluster +with no backup is one bad afternoon from data loss that no amount of isolation +evidence compensates for. + +**D2 before D3, necessarily.** Tenancy Posture §13.4: an artifact must assert +something achievable. The noisy-neighbour artifact requires showing the +governance controls *bind*. With no controls there is nothing to bind, so a +probe written now could only demonstrate degradation — an artifact that "can +only fail, or that passes by being run gently enough". Writing the probe first +would produce an overclaim wearing the costume of evidence. + +D1 also depends on a backup target, which is `resource-control`'s bucket and +the `platform-pg-backup-s3` credential lane — the same handoff +`make postgres-backup-deploy` waits on. Check whether that is now live before +assuming this is blocked. + +## Tasks + +```task +id: RPF-WP-0019-T01 +status: todo +priority: high +``` +**Establish a backup target for `apps-pg`.** Confirm the state of the +`resource-control` bucket and the `platform-pg-backup-s3` OpenBao Secret; if +live, configure `barmanObjectStore` and a `retentionPolicy` on the cluster. If +not live, record the dependency and say so — do not leave the absence +undocumented a second time. + +```task +id: RPF-WP-0019-T02 +status: todo +priority: high +``` +**Declare and enforce per-consumer controls.** Per-consumer connection +allowance, `statement_timeout`, `idle_in_transaction_session_timeout`, and +pod resource requests/limits to lift `apps-pg` off BestEffort QoS. Publish +every value in `docs/s3-consumer-interfaces.md` before it takes effect — +§10.2 is a disclosure rule, and applying a timeout consumers learn about by +hitting it would breach the rule while implementing it. + +```task +id: RPF-WP-0019-T03 +status: todo +priority: medium +``` +**Declare the ceiling and overflow target.** Owed under this repo's own +Rule P-4.1 before `apps-pg`'s third consumer; it is at two. Name the binding +resource per Rule P-4.2 — memory or connections — and a named overflow +substrate per P-4.3. + +```task +id: RPF-WP-0019-T04 +status: todo +priority: medium +``` +**Isolation probes, after T02.** Consumer-boundary probes on the +`rapp-postgres` model, then the §13 noisy-neighbour artifact: per-consumer +baseline, saturation run, evidence the controls bind, degradation measured and +judged against each consumer's declared service class. Update +`docs/placement-policy.md` §3.1 and `docs/tenancy-posture.md` when the P1 +claims become evidenced. + +## Boundaries + +- `apps-pg` only. `platform-pg`'s equivalents are `rapp-postgres`'s. +- No consumer is migrated. This changes the cluster, not who is on it. +- Values are published before they are enforced, never after. + +## Risks + +**Applying limits to a live cluster breaks a consumer that was relying on +their absence.** Most likely with `statement_timeout`. Mitigation is the +disclosure-first ordering in T02, which gives consumers a window to object. + +**T01 stays blocked on a handoff outside this repo and the cluster keeps no +backup meanwhile.** Mitigation is that T01 requires the dependency be recorded +explicitly rather than left as a silent absence — which is exactly how D1 +survived this long.