diff --git a/SCOPE.md b/SCOPE.md index dd8237e..31a605c 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -89,11 +89,6 @@ policy, and lane governance. declared, not deployed. OpenBao package boundary and PAT cutover are closed. Platform rapp declarations conform (`RAILIANCE-WP-0015`). Versioned consumer interfaces: `docs/s3-consumer-interfaces.md`. - Tenancy posture and database placement policy are declared - (`docs/tenancy-posture.md`, `docs/placement-policy.md`, `RPF-WP-0018`). - Two open items they surfaced: `apps-pg` has no backup configured, and - both shared clusters owe a declared consumer ceiling before their third - consumer. Decisions are now recorded as ADRs in `docs/adr/` (ADR-0003). - Known drift: State Hub attributes ~11 capabilities to this repo, including S1/S2/S4/S5 concerns it does not own. The four `capability` blocks in this file are authoritative; the hub carries stale pre-split attributions diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 1dfa2b6..c637b60 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -20,7 +20,6 @@ | 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 | | 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 +80,3 @@ | 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 | diff --git a/docs/adr/ADR-0001-s3-platform-service-boundary.md b/docs/adr/ADR-0001-s3-platform-service-boundary.md deleted file mode 100644 index 53f4460..0000000 --- a/docs/adr/ADR-0001-s3-platform-service-boundary.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "ADR-0001 — S3 owns platform services, not the substrate beneath them" -status: accepted -revision: "1.0" -owner: railiance-platform -date: "2026-08-17" -last_reviewed: "2026-08-17" -review_interval: 12m ---- - -# ADR-0001 — S3 owns platform services, not the substrate beneath them - -## Context - -`railiance-platform` is S3 on the OAS Stack: the shared services several -applications depend on — PostgreSQL, secrets, cache, object storage. The layers -around it are S1 `railiance-infra` (OS and host concerns), S2 -`railiance-cluster` (Kubernetes runtime, ingress), S4 `railiance-enablement` -(tooling and CI), S5 `railiance-apps` (workloads). - -This boundary has been stated in `SCOPE.md` and in ADR-003 of `railiance-infra` -since the five-repo split, and it has been tested twice. `RAIL-PL-WP-0001` -existed to extract platform services *out* of S2 subcharts. On 2026-08-17 -`POLICY-NEXUS-WP-0001` assigned this repo "the substrate — DNS, TLS, ingress, -hosting" for `policy.coulomb.social`, which would move the boundary back the -other way. - -The pressure is predictable and will recur: S3 is the layer that looks like it -owns infrastructure, because it owns things that feel infrastructural. Recording -the rule as an ADR rather than as a line in `SCOPE.md` gives future requests -something to be answered against. - -## Decision - -**S3 owns shared platform *services* and the interfaces consumers bind to. It -does not own DNS, TLS, Kubernetes ingress, host configuration, or CI.** A -request that would place any of those here is redirected to S1, S2 or S4 rather -than absorbed, even when this repo is the closest available owner and even when -absorbing it would be faster. - -Where S3 has a genuine part in such a request — an object-storage bucket, a -credential lane, a database — it supplies that part and co-signs the rest. It -does not become the owner of the whole by being the first repo asked. - -## Consequences - -- `POLICY-NEXUS-WP-0001` T04 needs splitting: ingress and TLS to S2, DNS to S1 - or the reef boundary, storage and credentials here. Routed as `RPF-WP-0018` - finding F1. -- A request this repo declines does not thereby become nobody's. Declining - carries an obligation to name the owner, which is why F1 names two. -- The rule cuts against this repo's convenience in the common case. That is - what makes it a rule rather than a preference. -- `rapp-openbao` and `rapp-postgres` are the same principle applied inward: - the deployable package left, custody and policy stayed. See - `docs/rapp-openbao-boundary.md` and `docs/rapp-postgres-boundary.md`. - -## Alternatives considered - -**Accept the substrate assignment as written.** Fastest, and the requester had -already resolved it with the operator. Rejected: it re-imports the coupling -`RAIL-PL-WP-0001` spent a workplan removing, and a boundary that yields to -whoever asks most recently is not a boundary. - -**Own ingress for S3-adjacent services only.** A narrower version, and it fails -on the first argument about what counts as adjacent. The line has to be drawn -where it can be checked. diff --git a/docs/adr/ADR-0002-placement-policy-ownership.md b/docs/adr/ADR-0002-placement-policy-ownership.md deleted file mode 100644 index fe11c39..0000000 --- a/docs/adr/ADR-0002-placement-policy-ownership.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: "ADR-0002 — S3 owns the placement rule; the package repo owns the number" -status: proposed -revision: "1.0" -owner: railiance-platform -date: "2026-08-17" -last_reviewed: "2026-08-17" -review_interval: 6m ---- - -# ADR-0002 — S3 owns the placement rule; the package repo owns the number - -## Context - -`net-kingdom/canon/standards/tenancy-posture_v0.1.md` §8.2 proposes that -database placement policy — dedicated versus shared, and when that changes — -be owned by `railiance-platform`, co-signed by `adaptive-pricing`. -`tenant-engine` raised the same gap independently on 2026-08-16: both patterns -are live on railiance01, neither is written down, and each new service copies -whichever neighbour it looked at. - -The complication is that this repo no longer holds the specs. `RAILIANCE-WP-0012` -and `RAILIANCE-WP-0015` moved the deployable surface to the `rapp-*` repos. -`platform-pg`'s `instances`, `max_connections`, memory limit and retention are -`rapp-postgres`'s cluster CR. Tenancy Posture §19.8 nonetheless asks *this repo* -for `platform-pg`'s declared maximum size — a question one hop from where its -answer lives. - -Accepting ownership without stating this would produce either an answer we -cannot substantiate or a quiet non-answer. - -## Decision - -**Accept placement ownership, split along rule and number.** - -*This repo owns the rule:* the default placement level, what overrides it, who -is accountable for each workload's placement, that a shared cluster must publish -a ceiling and an overflow target before its third consumer, that retention has a -floor and a ceiling, and that a consumer's erasure horizon on shared substrate -is disclosed rather than inherited silently. - -*The package repo owns the number:* what the ceiling actually is, which resource -binds first, what the retention values are, and the validator that enforces them -where the CR lives. - -**Placement ownership per workload sits with the consuming repo; this repo -co-signs.** The consumer knows its latency requirement, erasure horizon and -compliance position; S3 knows the substrate. A placement change needs both. -Putting the decision solely here would recreate the problem §8.2 exists to -avoid — an operations-shaped repo holding a commercial decision alone. - -`adaptive-pricing` co-signature is requested, not assumed. - -## Consequences - -- `docs/placement-policy.md` is the operative document; this ADR is the - decision behind it. -- §19.8 is answered by obligation rather than by number: Rule P-4.1 requires - `platform-pg` and `apps-pg` to declare a ceiling before their third consumer. - Both are at two consumers. -- This repo owes `apps-pg`'s ceiling under its own rule. A policy whose author - is exempt is not a policy. -- §8.1 requires triggers to be *monitored*. This repo has no monitoring plane, - so the connection-and-memory-ceiling trigger is recorded `unmonitored` - pending `railiance-telemetry`. Declaring a ceiling is a partial substitute — - a written threshold is checkable by a human at provisioning, which beats a - limit discovered by an OOM kill. -- If `adaptive-pricing` declines, this ADR is revised to record single - ownership explicitly rather than leaving an unsigned co-signature line. - -## Alternatives considered - -**Decline ownership; route it to `rapp-postgres`.** They hold the specs and the -operational knowledge. Rejected: placement is a cross-cluster question and -`rapp-postgres` owns one package. A policy owned by one substrate's operator -cannot govern movement between substrates. - -**Accept whole, including the numbers.** Rejected: it would either re-import -the deployable surface this repo deliberately gave up, or produce numbers -restated here that drift from the CR — a second source of truth for exactly the -values a consumer must be able to trust. diff --git a/docs/adr/ADR-0003-decisions-live-in-the-repo.md b/docs/adr/ADR-0003-decisions-live-in-the-repo.md deleted file mode 100644 index 187d528..0000000 --- a/docs/adr/ADR-0003-decisions-live-in-the-repo.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "ADR-0003 — Decisions that bind others live in docs/adr, not only in the State Hub" -status: accepted -revision: "1.0" -owner: railiance-platform -date: "2026-08-17" -last_reviewed: "2026-08-17" -review_interval: 12m ---- - -# ADR-0003 — Decisions that bind others live in `docs/adr`, not only in the State Hub - -## Context - -This repo recorded decisions with the State Hub's `record_decision()` and wrote -governing content as prose in `docs/` — 24 files on 2026-08-17, none carrying a -status, owner, revision or review date. It held no ADRs at all. - -Two things made that a defect rather than a style. - -**The hub is a read model.** The estate's standing rule is that local files are -the source of truth and the hub reflects them. A decision that exists only as a -hub record inverts that for the one class of content where it matters most. - -**`policy-nexus` publishes canon and ADRs only**, globbing `docs/adr/*.md` from -each repo. A repo with no ADRs is invisible to the estate's publication surface -no matter how much governing content it holds. This repo owns S3 boundary -rules, the credential-lane model and the consumption-mode gate — all binding on -other repos, all unpublishable. - -The gap was structural, not an oversight: nothing in the session protocol ever -told anyone to write an ADR here. - -## Decision - -**A decision that binds anyone outside this repo is recorded as an ADR in -`docs/adr/`, with the frontmatter in `docs/adr/README.md`, before or alongside -any hub record.** The hub record points at the ADR; it does not replace it. - -Runbooks and procedures stay as prose in `docs/` and stay unpublished. The test -is not importance — several runbooks are critical — but whether the document -*decides* something for someone else. - -## Consequences - -- `docs/adr/` exists, with a convention, a template and this repo's first three - ADRs. -- Existing prose is not converted wholesale. Content is promoted when it is - next touched, or when a consumer needs to cite it. -- `last_reviewed` and `review_interval` are mandatory here, which is stricter - than the estate norm — of 69 ADRs across 19 repos on 2026-08-17, 21 had YAML - frontmatter and 2 had any notion of a review date. `POLICY-NEXUS-WP-0001` T05 - cannot compute staleness without those fields and must not invent them, since - inventing them would make the publication a second source of truth, which is - that workplan's own top risk. Routed as `RPF-WP-0018` finding F5. -- Session protocol guidance should follow, so the next agent writes an ADR - without being told. Not done in this ADR. - -## Alternatives considered - -**Keep decisions in the hub and have `policy-nexus` read it.** Rejected on -both sides: it would make a read model authoritative, and it would give the -publication surface a source that no repo can diff or review. - -**Add frontmatter to all 24 existing `docs/` files.** Rejected. Most are -runbooks that should not be published, and stamping them with a status would -assert a decision that was never made. diff --git a/docs/adr/README.md b/docs/adr/README.md deleted file mode 100644 index 591425e..0000000 --- a/docs/adr/README.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: "ADR convention for railiance-platform" -status: accepted -revision: "1.0" -owner: railiance-platform -date: "2026-08-17" -last_reviewed: "2026-08-17" -review_interval: 12m ---- - -# ADRs in this repo - -Work record: `RPF-WP-0018` T05. - -Until 2026-08-17 this repo held **no ADRs**. Its decisions lived either as -prose in `docs/` without status or ownership, or in the State Hub via -`record_decision()`. Both are wrong homes, for the same reason: the hub is a -read model and prose is not addressable as a decision. `policy-nexus` -publishes canon and ADRs only, so a repo with no ADRs publishes nothing — -regardless of how much governing content it holds. See `ADR-0003`. - -## What gets an ADR here - -A decision gets an ADR when it **binds someone other than the person who made -it** — another repo, a future maintainer, or a consumer of an S3 interface. - -| Content | Home | Published | -| --- | --- | --- | -| A decision binding others | `docs/adr/` | yes | -| A procedure someone follows | `docs/*.md` runbook | no | -| Live interface values | `docs/s3-consumer-interfaces.md` | no | -| Work sequencing | `workplans/` | no | - -Runbooks stay prose deliberately. A site that publishes everything publishes -nothing in particular, and `POLICY-NEXUS-WP-0001` T03 already draws that line. - -## Frontmatter is mandatory - -Every ADR carries the YAML block below. The fields are not decoration: they -are exactly what `POLICY-NEXUS-WP-0001` T02 and T05 consume to render a page -and to mark it stale. - -```yaml ---- -title: "ADR-NNNN — short imperative statement of the decision" -status: proposed | accepted | superseded | withdrawn -revision: "1.0" -owner: railiance-platform -date: "YYYY-MM-DD" # when decided -last_reviewed: "YYYY-MM-DD" -review_interval: 6m | 12m # after which the page shows as stale -supersedes: ADR-NNNN # optional -superseded_by: ADR-NNNN # optional ---- -``` - -`last_reviewed` and `review_interval` are the two fields the rest of the estate -mostly lacks — of 69 ADRs across 19 repos on 2026-08-17, 2 carried any notion -of a review date. A publication surface cannot compute staleness without them -and must not invent them, so they are required here. - -Numbering is `ADR-NNNN`, four digits, never reused. A superseded ADR keeps its -number and its file; it gains `superseded_by` and its status changes. Nothing -is deleted — the published URL must keep resolving. - -## Sections - -Context · Decision · Consequences · Alternatives considered. Keep the decision -statement to one paragraph a reader can quote. diff --git a/docs/placement-policy.md b/docs/placement-policy.md deleted file mode 100644 index b68b28e..0000000 --- a/docs/placement-policy.md +++ /dev/null @@ -1,224 +0,0 @@ -# S3 database placement policy - -Work record: `RPF-WP-0018` T02, T03, T06 -Effective: 2026-08-17 -Framework: `net-kingdom/canon/standards/tenancy-posture_v0.1.md` §4.4, §8 -Contract version of this document: `1.0.0` -Status: **provisional** — binding on this repo now; formally conditional on -the framework reaching `accepted` and on `adaptive-pricing` co-signature. - -Answers `tenant-engine`'s 2026-08-16 request ("a default and the conditions -that override it, so migrations stop being decided by whichever neighbour was -consulted") and Tenancy Posture §19.2. - -## 1. Ownership, and its limit - -§8.2 proposes placement ownership to `railiance-platform` co-signed by -`adaptive-pricing`. **Accepted, with the scope stated:** - -> **This repo owns the rule. The package repo owns the number.** - -We decide *that* a cluster must publish a ceiling, what happens when it is -reached, what the default placement is, and who is accountable for each -workload's placement. We do not decide what `platform-pg`'s `max_connections` -is — that is `rapp-postgres`'s cluster CR, and `RAILIANCE-WP-0012` / -`RAILIANCE-WP-0015` moved that surface there deliberately. - -This matters for §19.8, which asks *this repo* for `platform-pg`'s declared -maximum size. That question is misrouted by one hop. `rapp-postgres`'s own -analysis makes the case better than we could: memory probably binds before -connections do, at roughly 10MB per backend against a 1Gi limit, and that is -an observation only the operator of the package can make. Our obligation under -this policy is §4 below — that a ceiling and an overflow target **exist and -are published**. Theirs is what they are. - -`adaptive-pricing` co-signature is **requested, not assumed**. The reasoning in -§8.2 is sound: tenancy model selection is commercial as much as technical. If -they decline, this document records single ownership and says so plainly rather -than leaving a co-signature line nobody signed. - -## 2. The default, and what overrides it - -The ladder is `net-kingdom` canon and is not restated here. Adopted by -reference: P0–P4 per Tenancy Posture §4.4. - -**Default: P1 — database per consumer, shared cluster** — for platform -services. P3 for client-facing business apps, per -`business-app-service-contract` §1.2. Both already ratified as Decision 4.4.1; -this document adopts rather than re-decides them. - -**P0 is not a sanctioned target.** It is the state repos arrive in. A consumer -sharing a database with another consumer is a defect with a migration owed, -not a placement. - -**The distinction `tenant-engine` asked us to preserve, preserved:** sharing a -*cluster* is a capacity decision and is cheap to undo — create target, dump, -restore, swap the `secretKeyRef`, restart. Sharing a *database* entangles -schemas and makes relocation a schema merge. This policy says "shared" only -ever in the first sense. Any consumer on an S3-custody cluster owns a whole -database. - -**Portability is an expectation, not a courtesy.** A consumer connects by -injected URL only; no cluster, namespace, host or database name in source. -`tenant-engine` adopted this unprompted and it is hereby the rule, because a -placement policy that cannot be executed is a preference. - -## 3. Placement owner per workload - -§19.2 asks for a recorded owner per workload so that "should this be dedicated" -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 | - -**The consuming repo is the placement owner; this repo co-signs.** The -consumer knows its latency requirement, its erasure horizon and its compliance -position; we know the substrate. Neither can decide alone, and putting the -decision solely here would recreate the "ops repo holds a commercial decision" -problem §8.2 exists to avoid. A placement change needs both signatures. - -New consumers record a placement owner at provisioning. A provisioning request -without one is incomplete. - -## 4. Ceiling and overflow — the §19.8 obligation, generalised - -**Rule P-4.1.** Every shared cluster under S3 policy MUST publish a **declared -maximum consumer count** and a **named overflow target**, both before its -third consumer is provisioned. - -**Rule P-4.2.** The ceiling is stated as the *binding* constraint with its -number, not as a capacity boast. Where more than one resource could bind -first — connections versus memory is the live case — the declaration names -which is expected to bind and why. A ceiling derived from the resource that -fails *cleanly* while another fails *catastrophically* is a mis-stated ceiling: -connection exhaustion errors and is recoverable, memory pressure OOM-kills and -degrades every co-resident at once. - -**Rule P-4.3.** The overflow target is a named substrate, not "we will build -one". "Provision a second cluster" is an answer; "revisit at the time" is not. - -**Status against these rules today:** - -| Cluster | Ceiling declared | Overflow target | Standing | -| --- | --- | --- | --- | -| `platform-pg` | not yet — `rapp-postgres` owes it | not yet | **2 consumers of ~6 practical; owed before the 3rd** | -| `apps-pg` | not yet — this repo owes it | not yet | 2 consumers; owed before the 3rd | -| `forgejo-db` | n/a — single consumer | n/a | — | - -Both live shared clusters are one consumer away from the trigger. This repo -owes `apps-pg`'s ceiling under its own rule, which is the correct asymmetry: a -policy whose author is exempt is not a policy. - -## 5. Triggers, and who is watching them - -§8.1: triggers MUST be **monitored**, not merely recorded. *"A trigger in a -YAML comment nobody re-reads is documentation, not control."* - -This repo has no monitoring plane. `SCOPE.md` records that S3 emits to -`railiance-telemetry` "once the evidence plane exists — seeded 2026-08-11, not -yet implemented". Rather than claim coverage we do not have — the overclaim -§6 prohibits — each trigger below carries either a named watcher or the -literal marker `unmonitored`. - -| # | Trigger (§8) | Signal that would fire it | Watcher | Cadence | -| --- | --- | --- | --- | --- | -| 1 | Noisy neighbour on a latency-critical path | Query latency regression on the consumer's own path | **consumer-observed** — the consumer notices, we do not | on incident | -| 2 | Compliance or residency requirement | Consumer declares it | placement owner | at declaration | -| 3 | Plan tier requiring a higher minimum | `adaptive-pricing` tier definition names a P floor | `adaptive-pricing` | on tier change | -| 4 | Erasure horizon no longer fits | Consumer's declared horizon < cluster retention floor | placement owner + this repo | at provisioning, then `unmonitored` | -| 5 | Connection or memory ceiling reached | Backend count vs declared ceiling; memory vs limit | **`unmonitored` — pending `railiance-telemetry`** | — | - -**Trigger 5 is the one that matters and it is the one nobody is watching.** -That is the honest position. It is also why Rule P-4.1 forces the ceiling to be -declared *before the third consumer*: a threshold that is written down is at -least checkable by a human at provisioning time, which is strictly better than -a limit discovered by an OOM kill. Declaring the ceiling is a partial substitute -for monitoring it, and it is the part we can do this week. - -**Closing trigger 5 belongs to `railiance-telemetry`, not to this workplan.** -Recorded here as a dependency so it is visible rather than assumed. - -## 6. Service class co-residency — the §8.3.3 report - -§8.3.2 declares service class as a category, never a priority. §8.3.3 requires -the platform to report which classes are co-resident, because "an unenforceable -risk that nobody can see is strictly worse than one that is stated". - -| Cluster | Co-resident classes | Mismatch | -| --- | --- | --- | -| `platform-pg` | `latency-critical` (`tenant-engine`) + `batch` (`audit-core`) | **yes — reported** | -| `apps-pg` | `interactive` + `interactive` | no | -| `forgejo-db` | single consumer | n/a | - -**`platform-pg` mixes `latency-critical` with `batch` today.** Under §8.3.1 -co-residents are equal — community PostgreSQL has no resource governor, so -nothing prioritises `tenant-engine`'s synchronous authorization queries over -`audit-core`'s batch work. `tenant-engine` chose a 5s statement timeout -precisely because it sits on `flex-auth`'s synchronous path. - -This is **reported, not remediated**. It may well still be the right call — -§8.3.2 says a recognised mismatch "may still be the right call, but it should -be a decision, not an accident of who was provisioned when". It is now a -decision. The escalation, if the mismatch bites, is P2 for `tenant-engine`, and -the known alternative — gateway-level prioritisation in a connection proxy — is -infrastructure this estate does not run and is not adopted here. - -## 7. Retention floor and ceiling — the §19.9 answer - -**Answered: yes to both, as a rule; the numbers belong to the package repo.** - -**Rule R-7.1 — floor.** A consumer's requested `backupRetentionDays` below the -cluster's declared floor is a **validation error at provisioning**, not a quiet -disappointment. A consumer that asks for 1 day and silently gets 30 has been -told nothing true; a consumer that gets an error learns the actual constraint -at the only moment it can act on it. - -**Rule R-7.2 — ceiling.** A maximum exists so no consumer can exhaust the -backup volume. Exceeding it is likewise an error, and the remedy named in the -error is P2 — a dedicated cluster whose retention is the consumer's own. - -**Rule R-7.3 — the floor is a placement trigger, and this is the important -one.** Per `rapp-postgres` ADR-0002, physical backup is instance-wide, so on a -shared cluster **a consumer's erasure horizon is the maximum declared across -all co-residents**. A consumer needing a horizon shorter than that floor -**cannot have it at P1** and must move to P2 — for a reason that has nothing to -do with performance or capacity, which is exactly why it must be recorded -rather than assumed. - -This makes someone else's retention choice everyone's, which is what -`tenant-engine` warned about on 2026-08-16 and was right to. Rule R-7.4 -follows. - -**Rule R-7.4 — inherited retention is disclosed, not inherited silently.** A -consumer joining a shared cluster is told its effective erasure horizon at -provisioning, and told again when a co-resident's change moves it. This is -§10.2 applied to retention and is implemented in -`docs/s3-consumer-interfaces.md` 1.1.0. - -**Enforcement is the package repo's.** The validator lives where the CR lives. -This document states the rule; `rapp-postgres` and this repo's own `apps-pg` -onboarding enforce it. - -## 8. What this policy deliberately does not do - -- **It moves no workload.** Declaring where things are is separate from - changing where they are. Nothing above is a migration instruction. -- **It adds no QoS axis.** §19.12 resolved that: we could not enforce one. -- **It does not decide identity-provider placement** (realm-per-tenant versus - Organizations, §19.7). That is `key-cape`'s, on a different substrate. -- **It claims no monitoring.** See §5. - -## Review interval - -Six months, or on: a new shared-cluster consumer, a co-residency class change, -`railiance-telemetry` going live, or the framework reaching `accepted`. Next -review due **2027-02-17**. diff --git a/docs/s3-consumer-interfaces.md b/docs/s3-consumer-interfaces.md index ce2470a..bb9a596 100644 --- a/docs/s3-consumer-interfaces.md +++ b/docs/s3-consumer-interfaces.md @@ -1,8 +1,8 @@ # S3 consumer service interfaces -Work record: `RAILIANCE-WP-0016` item 17; `RPF-WP-0018` T04 -Date: 2026-08-15, revised 2026-08-17 -Contract version of this index: `1.1.0` +Work record: `RAILIANCE-WP-0016` item 17 +Date: 2026-08-15 +Contract version of this index: `1.0.0` Consumers bind to these contracts, not to Helm chart names or pod topology. Changing an engine behind a versioned interface does not bump the interface @@ -27,26 +27,6 @@ unless the consumer-visible fields change. `postgresql-ha-pgpool.platform.svc.cluster.local` is **not** an interface. That stack is retired. -## Quota disclosure - -Added in `1.1.0` — additive, so no consumer contract breaks. - -Tenancy Posture §10.2: *quotas are disclosed, not discovered.* Every consumer -is told at provisioning what the platform will refuse to do for it, and told -again when that changes. **A consumer learning its statement timeout by hitting -it in production is a disclosure failure, not a consumer bug.** - -The obligation this places on S3 is a standing one: a change to any value in -the tables below is announced to bound consumers before it takes effect. The -values themselves are read from the cluster CR and are the package owner's to -set — this index publishes them, it does not choose them. - -Retention disclosure carries the additional §7 rule from -`docs/placement-policy.md`: on a shared cluster the **effective erasure -horizon is the maximum declared across co-residents**, not the value the -consumer requested. That number is disclosed, and it moves when a co-resident -changes. - ## `apps-pg.v1` Consumer supplies: app name, namespace, database name, role name. Platform @@ -55,30 +35,6 @@ provisions the role and CNPG `Database`, labels the namespace RW endpoint: `apps-pg-rw.databases.svc.cluster.local:5432`. Bootstrap role `apps_admin` is not a consumer credential. -**Quotas and limits** — read from `helm/apps-pg-cluster.yaml`, 2026-08-17: - -| Property | Value | Note | -| --- | --- | --- | -| `max_connections` | **PostgreSQL default (100)** | Not tuned in the CR. Not partitioned per consumer — there is no per-consumer connection allowance, so one consumer can exhaust the cluster. | -| `statement_timeout` | **none** | Not set. A runaway query runs until it finishes. | -| `idle_in_transaction_session_timeout` | **none** | Not set. | -| CPU / memory limits | **none declared** | The pod is BestEffort QoS and is the first thing evicted under node pressure. | -| Instances | 1 | No HA. Restart recovery only, per the single-node rail. | -| Storage | 10Gi | | -| Backup | **none configured** | No `barmanObjectStore`, no `retentionPolicy`. | -| Effective erasure horizon | **undefined** | Follows from the line above. | - -**Read the backup row before provisioning against this cluster.** `apps-pg` -has no backup at all — not a short retention, none. Recovery from data loss is -not available on this interface today. This is disclosed rather than fixed -because §10.2 requires disclosure now and the fix needs a backup target; it is -recorded as the R-axis gap in `docs/tenancy-posture.md` and is the highest -value item that declaration surfaced. - -The unpartitioned connection pool is the same shape of risk `rapp-postgres` -reported for `platform-pg`: the bound is aggregate, so every consumer can be -politely inside its own expectations while the cluster still fails. - ## `rapp-postgres.v1` Consumer is a `consumers[].declaration`. Boundary unit is @@ -86,26 +42,6 @@ Consumer is a `consumers[].declaration`. Boundary unit is `openbao-dynamic-database-credential` via grant `rapp-postgres/-`. Tenant identifiers are opaque. -**Quotas and limits** — authority is `rapp-postgres`'s -`helm/platform-pg-cluster.yaml`; reproduced here for disclosure, 2026-08-17: - -| Property | Value | Note | -| --- | --- | --- | -| `max_connections` | 100 | Aggregate, not per consumer. ~14 per consumer (12 runtime + 2 migration) puts the practical ceiling near 6. | -| Memory limit | 1Gi | Expected to bind before connections do — ~10MB per backend. Memory pressure OOM-kills; connection exhaustion errors cleanly. | -| CPU | 100m request / 1 limit | | -| Instances | 1 | No HA. | -| Storage | 20Gi | | -| Backup retention | 30d | Instance-wide, per `rapp-postgres` ADR-0002. | -| Effective erasure horizon | **30d, or the max across co-residents** | A consumer needing shorter cannot have it at P1. See `docs/placement-policy.md` §7. | -| Statement timeout | consumer-set | `tenant-engine` sets 5s for its own path; not a platform default. | -| Declared ceiling / overflow target | **not yet declared** | Owed by `rapp-postgres` before the third consumer, per `docs/placement-policy.md` Rule P-4.1. | - -Service classes currently co-resident on `platform-pg`: `latency-critical` -(`tenant-engine`) and `batch` (`audit-core`). Nothing prioritises between them -— community PostgreSQL has no resource governor. Reported per Tenancy Posture -§8.3.3. - ## `ccr-workload-kv.v1` and `credential-grant.v1` Unchanged semantics from `docs/credential-change-approval.md` and diff --git a/docs/tenancy-posture.md b/docs/tenancy-posture.md deleted file mode 100644 index 29fad61..0000000 --- a/docs/tenancy-posture.md +++ /dev/null @@ -1,177 +0,0 @@ -# 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. - -## 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: 2, E: 0, P: 0, R: 0 } - target: { I: 0, A: 2, E: 0, P: 0, R: 1 } - service_class: latency-critical - reviewed: "2026-08-17" - 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." -``` - -### `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.** - -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**. diff --git a/workplans/RPF-WP-0018-policy-surface-alignment.md b/workplans/RPF-WP-0018-policy-surface-alignment.md index eba8a12..ed8deca 100644 --- a/workplans/RPF-WP-0018-policy-surface-alignment.md +++ b/workplans/RPF-WP-0018-policy-surface-alignment.md @@ -4,7 +4,7 @@ type: workplan title: "Align S3 with the estate policy surface (Tenancy Posture + policy-nexus)" domain: financials repo: railiance-platform -status: active +status: proposed owner: codex topic_slug: railiance created: "2026-08-17" @@ -15,7 +15,6 @@ related: - RAPP-POSTGRES-WP-0002 origin: routed origin_ref: "net-kingdom/canon/standards/tenancy-posture_v0.1.md §19.2, §20" -state_hub_workstream_id: "d40827cb-bb48-4cdd-9b41-8dfae116d705" --- # RPF-WP-0018 — policy surface alignment @@ -66,9 +65,9 @@ monitoring we do not have is the exact overclaim §6 prohibits, so T03 records each trigger with a named monitor or an explicit `unmonitored` marker, and the marker is the honest answer until telemetry lands. -**Does not fit, and the policy should change.** Five findings, T07. +**Does not fit, and the policy should change.** Three findings, T07. -## Findings against the policy (routed 2026-08-17, T07) +## Findings against the policy (recommendations, not yet routed) **F1 — `POLICY-NEXUS-WP-0001` assigns S3 a concern S3 does not own.** It states "`railiance-platform` owns the substrate — DNS, TLS, ingress, @@ -132,38 +131,6 @@ has custody of — plus a statement of which it owns versus operates. T01 does that and routes the finding back to `net-kingdom` as the framework's first real correction from review rather than from research. -Refined while writing T01: the sharper form of F4 is that **the five -ladders describe a consumer of storage, not a provider of it.** Every S3 -service scores at or near zero on I, A and E for the same structural -reason — a storage or credential platform has no tenant dimension of its -own. The `openbao` E line is the demonstration: `E: 0` is literally correct -and actively misleading, because the mechanism in place is -credential-scoped structural separation, the machinery E4 describes, -pointed at a consumer boundary rather than a tenant one. Proposed remedy is -a **provider declaration** beside the consumer vector, stating per axis the -maximum level the platform makes reachable and what the consumer must do to -reach it. Offered as an addition, not a renumbering. - -**F5 — `policy-nexus` T02/T05 cannot be built against the corpus as it -stands.** Measured across the workstation on 2026-08-17: **69 ADRs in 19 -repos** — closely matching that workplan's "roughly 68 across 18" — of -which **21 carry YAML frontmatter**, **41 carry any status field**, and -**2 carry any notion of a review date**. T02 specifies a front-matter-driven -renderer taking title, status, revision and review date from the source; T05 -requires every page to show status, revision and last-reviewed, with a -staleness marker once a review interval is exceeded. Against this corpus -that renderer has nothing to read for 48 of 69 documents, and the currency -half of the repo's purpose has data for 2. - -*Recommend:* T03 of that workplan should treat frontmatter as an **ingestion -precondition**, not a rendering input — a document without the required -fields is reported as non-conformant and left unpublished, rather than -rendered with blanks or with dates the site inferred. Inferring them is the -failure mode that workplan already names as its top risk: the publication -becomes a second source of truth. The corollary is that policy-nexus needs -a conformance report aimed at source repos before it needs a site, and this -repo's `docs/adr/README.md` is one repo's answer to it. - ## Boundaries - No workload moves placement level in this workplan. Declaring where @@ -179,9 +146,8 @@ repo's `docs/adr/README.md` is one repo's answer to it. ```task id: RPF-WP-0018-T01 -status: done +status: todo priority: high -state_hub_task_id: "ff392a23-e72a-4442-843f-929f218404ae" ``` **Publish the S3 posture vector set.** Write `docs/tenancy-posture.md`: one vector per service under S3 custody (`openbao`, `apps-pg`, and the @@ -192,9 +158,8 @@ Route F4 to `net-kingdom` with the vector set as the evidence. ```task id: RPF-WP-0018-T02 -status: done +status: todo priority: high -state_hub_task_id: "962fc494-bb9b-4f67-8e5b-2ed88a670945" ``` **Accept placement ownership, scoped.** Write `docs/placement-policy.md`: adopt the P0–P4 ladder by reference (do not restate it — the canon copy is @@ -206,9 +171,8 @@ either way. ```task id: RPF-WP-0018-T03 -status: done +status: todo priority: medium -state_hub_task_id: "f6f30233-3f6e-44a0-a644-7268f9d88f3c" ``` **Make triggers monitorable or honestly unmonitored.** For each of the five §8 triggers, record in `docs/placement-policy.md` what signal would fire it, @@ -220,9 +184,8 @@ report which service classes are co-resident. Today that is ```task id: RPF-WP-0018-T04 -status: done +status: todo priority: medium -state_hub_task_id: "8a3ce90b-8af2-4dcd-9a35-77025d2123f2" ``` **Disclose quotas to consumers (§10.2).** Extend `docs/s3-consumer-interfaces.md` to `1.1.0` — additive under its own @@ -233,9 +196,8 @@ these is announced to bound consumers, not discovered by them. ```task id: RPF-WP-0018-T05 -status: done +status: todo priority: medium -state_hub_task_id: "a6da631d-fa4e-4c2b-acc3-d3cf53cfb57f" ``` **Create the ADR surface (F2).** Create `docs/adr/` with an ADR template carrying the frontmatter `policy-nexus` T02/T05 consume: title, status, @@ -248,9 +210,8 @@ only home for a decision. ```task id: RPF-WP-0018-T06 -status: done +status: todo priority: low -state_hub_task_id: "99f2a4d8-b20c-4c43-8c94-0bc469bfcc46" ``` **Answer §19.9 — retention floor and ceiling.** Decide whether `backupRetentionDays` gets a platform minimum (so a consumer asking for one @@ -262,9 +223,8 @@ maximum declared across co-residents, so a shorter horizon is a P2 trigger. ```task id: RPF-WP-0018-T07 -status: done +status: todo priority: high -state_hub_task_id: "b021fda3-23bd-4843-8d1b-983b6ec582b5" ``` **Route the findings.** Reply to `rapp-postgres` and `tenant-engine` with T01/T02 outcomes and F3/F4. Reply to `policy-nexus` with F1 (substrate