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>
This commit is contained in:
parent
e52ae26001
commit
dc4245361d
20 changed files with 650 additions and 73 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 P1–P4 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue