Finish RPF-WP-0018; RPF-WP-0019 repository-complete
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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>
This commit is contained in:
codex 2026-08-18 13:35:04 +02:00
parent e52ae26001
commit dc4245361d
20 changed files with 650 additions and 73 deletions

View file

@ -12,7 +12,8 @@ review_interval: 6m
## Context
`net-kingdom/canon/standards/tenancy-posture_v0.1.md` §8.2 proposes that
An earlier draft of
`net-kingdom/canon/standards/tenancy-posture_v0.1.md` §8.2 proposed 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
@ -49,15 +50,18 @@ 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.
`adaptive-pricing` declined the standing co-signature on 2026-08-17. The
replacement is binding and machine-reconcilable: it publishes typed tier
minimums; this policy validates placement against them. Ownership stays here.
## 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.
- §19.8 is answered for `platform-pg` by `rapp-postgres` ADR-0004: four
consumers, with `platform-pg-2` named as the still-absent overflow target.
Rule P-4.1 still requires `apps-pg` to declare its own ceiling before its
third consumer.
- 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,
@ -65,8 +69,8 @@ avoid — an operations-shaped repo holding a commercial decision alone.
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.
- `adaptive-pricing`'s decline is recorded as single policy ownership plus a
mandatory typed constraint join, not as an absent signature.
## Alternatives considered

View file

@ -97,18 +97,30 @@ applied; coulomb-social connectivity smoke from labeled consumer ns OK.
## Backup And Roadmap
`apps-pg` starts as a conservative single-instance, 10Gi cluster to match
the current node capacity and existing CNPG footprint. Adding a replica,
PgBouncer/CNPG `Pooler`, resize policy, and CNPG-native backup coverage
are follow-up platform work items.
`apps-pg` remains a conservative single-instance, 10Gi cluster. The desired
state now includes continuous WAL archival, a daily 02:15 UTC base backup and
30-day retention under the distinct `apps-pg/` object-store prefix. The
credential lane is shared with `platform-pg`; the backup data path is not.
Until backup coverage is explicitly added, consumer onboarding should
record whether app data is disposable, externally reproducible, or
requires an immediate backup follow-up before production use.
The declared ceiling is three consumers. Each gets at most 20 connections;
40 of the explicit 100-connection aggregate remains for CNPG and operator
headroom. Memory (1Gi limit), not the clean connection refusal, is treated as
the binding safety constraint. A fourth consumer goes to the named
`apps-pg-2` overflow substrate, which must be provisioned before onboarding.
Reviewed, unapplied source for that cell is
`helm/apps-pg-2-{cluster,backup,networkpolicies}.yaml`; it uses a distinct
bootstrap Secret and backup prefix. `make apps-pg-verify-capacity` rejects a
fourth role on either cell, missing connection limits, duplicate roles, missing
resource envelopes, or a reused backup path.
`statement_timeout` and `idle_in_transaction_session_timeout` are 15 seconds
per consumer role. CNPG 1.28 cannot express those role settings, so
`helm/apps-pg-consumer-controls.sql` is the idempotent controlled-operator
step; `connectionLimit` remains declaratively reconciled by CNPG.
Resource evidence for `resource:railiance:apps-pg` (capacity, recovery,
labor, allocation drivers) is published under
`docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md`. The
2026-08-14 observation: no ScheduledBackup, railiance01 `apps-pg` is
not an Option A target, RPO is unbounded, RTO is undefined. That is
unresolved local-storage risk, not verified recovery.
2026-08-14 observation remains historically correct. The 2026-08-18 desired
state is not called verified recovery until the first backup succeeds and a
scratch restore artifact is recorded.

View file

@ -5,7 +5,7 @@ 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.
the framework reaching `accepted`.
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
@ -13,8 +13,9 @@ 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:**
Draft-8 §8.2 assigns the placement rule to `railiance-platform`, substrate
numbers to the package repo, workload requirements to the consumer and tier
minimums to `adaptive-pricing`. **Accepted, with the scope stated:**
> **This repo owns the rule. The package repo owns the number.**
@ -32,10 +33,11 @@ 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.
`adaptive-pricing` declined a standing co-signature on 2026-08-17 and supplied
the stronger replacement adopted by draft-8: typed tier minima are joined to
consumer and provider declarations at tier definition and on change. This repo
therefore owns this policy singly; it must validate, not copy, the commercial
constraint artifact.
## 2. The default, and what overrides it
@ -73,7 +75,7 @@ triggers. It is not the same role as the operator of the cluster.
| 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 |
| `tenant-engine` | SQLite PVC current; `platform-pg` desired | off-ladder current; P1 desired | `tenant-engine` | this repo | source conformance; live cutover absent |
| `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 |
@ -85,7 +87,7 @@ triggers. It is not the same role as the operator of the cluster.
§13.1 claims a level only with its artifact present. The P1P4 artifact is
*"provisioning declaration plus the platform's isolation probes"*.
`platform-pg` has them — `rapp-postgres` runs 15 adversarial probes against the
`platform-pg` has them — `rapp-postgres` runs 19 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`
@ -145,8 +147,8 @@ one". "Provision a second cluster" is an answer; "revisit at the time" is not.
| 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 |
| `platform-pg` | 4 declarations (estimated; ADR-0004) | `platform-pg-2`, named but absent | 3/4 including the isolation probe; measure before the next workload |
| `apps-pg` | 3 consumers; 20 connections each, 1Gi memory binds first | `apps-pg-2` | 2/3; overflow must exist before a 4th |
| `forgejo-db` | n/a — single consumer | n/a | — |
Both live shared clusters are one consumer away from the trigger. This repo

View file

@ -55,29 +55,28 @@ 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:
**Quotas and limits** — declared in `helm/apps-pg-cluster.yaml` and
`helm/apps-pg-consumer-controls.sql`, 2026-08-18. These values are published
before the corresponding live rollout:
| 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. |
| `max_connections` | 100 | Explicit aggregate cap. Forty connections remain outside the declared three-consumer ceiling for operator/CNPG overhead. |
| Per-consumer connections | 20 | CNPG `managed.roles[].connectionLimit`, for both current roles. |
| `statement_timeout` | 15s | Per role; idempotent operator SQL because CNPG 1.28 has no managed-role settings field. |
| `idle_in_transaction_session_timeout` | 15s | Per role, same enforcement path. |
| CPU / memory | 100m / 256Mi requests; 1 CPU / 1Gi limits | Burstable QoS; memory is expected to bind before connections if workload size grows. |
| 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. |
| Backup | 30d; daily 02:15 UTC + continuous WAL | S3 prefix `s3://railiance-platform-pg-backup/apps-pg/`; rollout is not evidence of recoverability until first backup and restore proof exist. |
| Effective erasure horizon | 30d, or the max across co-residents | Shared-cluster rule from placement policy §7. |
| Declared ceiling / overflow | 3 consumers / `apps-pg-2` | Memory is the binding safety constraint; provision the named overflow substrate before admitting a fourth. |
**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.
The manifests close the configuration gap, not the evidence gap. Until the
cluster change is applied and `LastBackupSucceeded=True` plus a scratch restore
are captured, recovery remains operationally unproven. Likewise the P1 noisy-
neighbour claim waits for the controlled probe window; source configuration is
not substituted for either artifact.
## `rapp-postgres.v1`
@ -99,7 +98,7 @@ Consumer is a `consumers[].declaration`. Boundary unit is
| 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. |
| Declared ceiling / overflow target | **4 declarations / `platform-pg-2`** | The isolation probe counts. `rapp-postgres` rejects a fifth declaration on one cell; ADR-0004 owns the number. |
Service classes currently co-resident on `platform-pg`: `latency-critical`
(`tenant-engine`) and `batch` (`audit-core`). Nothing prioritises between them

View file

@ -2,9 +2,13 @@
Work record: `RPF-WP-0018` T01
Declared: 2026-08-17
Framework: `net-kingdom/canon/standards/tenancy-posture_v0.1.md` (draft-5, proposed)
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."*
@ -55,8 +59,8 @@ surface to the `rapp-*` repos while custody and policy stayed here.
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 }
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:
@ -70,7 +74,7 @@ tenancy:
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."
- "P is n/a rather than a number. See §P-axis note."
```
### `forgejo-db` — dedicated cluster for the forge
@ -79,8 +83,8 @@ tenancy:
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 }
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:
@ -97,8 +101,8 @@ tenancy:
tenancy:
service: openbao
role: credential-provider
current: { I: 0, A: 0, E: 0, P: 0, R: 0 }
target: { I: 0, A: 0, E: 0, P: 0, R: 1 }
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"
@ -145,7 +149,7 @@ great deal, it is central, and it is mechanically evidenced —
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.
**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
@ -164,7 +168,7 @@ 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-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
@ -173,14 +177,18 @@ 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.
placement, and a later reader would use it to argue about isolation. `n/a`
plus the provider statement is the accurate canonical form.
## Finding routed to `net-kingdom`
## Finding adopted by `net-kingdom`
**The five ladders describe a consumer of storage. They do not describe a
**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 —
@ -208,15 +216,16 @@ 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
**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 five consumer ladders
produces it.
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