railiance-platform/docs/placement-policy.md
codex e7e4e33bb8 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>
2026-08-17 21:55:11 +02:00

12 KiB
Raw Blame History

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: P0P4 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.