RPF-WP-0018 T01-T06: publish S3 posture, placement policy, quotas, ADR surface
T01 docs/tenancy-posture.md - vector set per service rather than one repo vector, with the provider-versus-consumer finding routed to net-kingdom. T02/T03/T06 docs/placement-policy.md - accepts placement ownership scoped to rule-here/number-there, records a placement owner per workload, reports the latency-critical + batch co-residency on platform-pg, marks the connection-ceiling trigger unmonitored pending railiance-telemetry, and answers the retention floor/ceiling question. T04 s3-consumer-interfaces 1.1.0 - quota disclosure per SS10.2. Surfaces that apps-pg has no backup, no resource limits and no tuned parameters. T05 docs/adr/ created with a mandatory-frontmatter convention and the first three ADRs. This repo previously held none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b83194741d
commit
e7e4e33bb8
9 changed files with 809 additions and 12 deletions
224
docs/placement-policy.md
Normal file
224
docs/placement-policy.md
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# 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**.
|
||||
Loading…
Add table
Add a link
Reference in a new issue