Commit graph

6 commits

Author SHA1 Message Date
codex
1674ea550d ADR-008: relocate the multi-tenancy framework to NetKingdom canon
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Multi-tenancy is part of the IT-security framework NetKingdom provides, so it
belongs beside the IAM Profile and the tenant-engine boundary contract rather
than in the work-factory canon. Operator decision.

Relocation surfaced two things a review would have caught embarrassingly late.

NetKingdom's accepted platform-identity-security-architecture has used the word
plane since July for a trust and deployment layer - bootstrap, platform
control, tenant. This framework was using the same word for an independent
dimension of concern. Two senses of one word in one canon is precisely the
concept-ownership collision the estate is careful about, and the newcomer
yields: they are now axes. The rename is also just better, since a posture
vector is a point in five-dimensional space.

That same document also disproves the framework's opening line. It has
described the trust model, the tenant model and a capability progression since
2026-07-23, so the claim that the estate had never written down what it was
building was wrong. The accurate and narrower claim is that nothing said how
far a given service had got, or could hold several answers at once.

Stub left behind so the ADR-008 identifier resolves. The renderer moved to
policy-nexus, which owns publication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 15:40:59 +02:00
codex
1c65257352 tools: generate the artifact page from canon markdown
The page and the ADR had already diverged once. They are now one source: the
markdown is authoritative and the page is generated, never hand-edited.

The renderer recognises conventions the document already uses rather than
requiring extra markup, so the source stays a readable document. A table whose
first column is **X0**/**X1** becomes a level ladder; a table headed Threat
becomes the threat matrix; a table with a Kind column gets mechanical and
adversarial chips; a table headed "E \ P" becomes the two-axis grid; ## N.
headings build the section rail.

Stdlib only, per the structure-not-tooling stance. A publishing step that needs
its own toolchain is a publishing step that stops being run - the same
reasoning tenant-engine used for its pin check.

One real consequence beyond deduplication: the E x P matrix had existed only on
the page, so the canonical document did not contain its own central diagram.
It is now a table in the markdown, which is where it should always have been.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 15:10:22 +02:00
codex
c4f22a5e55 ADR-008 draft-4: apply the eight amendments the gap research forced
R4 now has a mechanism. It splits into horizon-elapsed (wait out a window
somebody else's retention set - available to all, proves little) and
key-destroyed (encrypt per entity, destroy the key, immediate). A service must
name which route it uses.

The regulatory position is stated rather than implied: DPAs have accepted key
destruction as erasure where physical deletion is disproportionate, under
conditions, and the EDPB has not formally endorsed it. Section 11.4 now says a
service may make that claim but not in language implying a regulator blessed
it. Overclaiming here would be worse than anywhere else in the document.

Third coupling recorded: shredding a single tenant's data needs the application
to encrypt under a per-tenant key before writing, so the top of the retention
ladder is an enforcement-plane capability. Reaching R4 is not a retention
project.

The noisy-neighbour artifact was replaced. It had required proof that a
saturating consumer does not breach another's allowance, which shared
infrastructure cannot provide - an artifact that can only fail, or passes by
being run gently, is an overclaim in the costume of evidence. It now measures:
baseline, saturation run, proof the controls bind, recorded degradation.
Generalised as decision 13.4.

The connection ceiling is reframed. Seven consumers each politely inside a
14-connection allowance still exhaust the instance, so the number is the
aggregate noisy-neighbour bound, and our per-consumer governance guards the
other shape entirely.

Also: quota transparency as a disclosure obligation (10.2) - a consumer
learning its statement timeout by hitting it in production is our failure, not
theirs; and two new open questions - crypto-shredding an audit trail destroys
what audit-core exists to hold, and we have no QoS vocabulary despite a
latency-critical consumer sharing an instance with a batch one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 14:49:00 +02:00
codex
d0b57b1a50 ADR-008 draft-3: corrections, plane R, and a name
Renamed to Tenancy Posture. Four Planes stopped being true and a name that
encodes a count breaks every time the framework grows; the posture vector is
the durable centre.

Corrections from the research digests:

- E3 no longer claims the application cannot route around it. Any session can
  re-SET the GUC, so E3 is decisive against a forgotten predicate and useless
  against SQL injection or a compromised process. Replaced the claim with a
  threat-model table. Draft-2 was overclaiming in exactly the way section 6
  prohibits, which is the correction I most wanted made before review.
- E3 layers on E2 rather than replacing it; claiming E3 now requires the E2
  artifact too, because E3 alone fails open under injection.
- The GUC is set transaction-locally, not at pool checkout, and the scaling
  section's pooling claim was backwards: SET LOCAL is what makes E3 safe under
  a pooler; statement-level pooling is what leaks.
- Placement is a default plus per-tenant exceptions. One P level per service
  could not express the isolation tiers section 11 already sells.

Added plane R for retention and erasure, implementing the rapp-postgres
ADR-0002 interface: N-day retention defaulting to 30, a published erasure
horizon, and dataset sweeps whose policy the consumer owns. R exists because
deleting a row does not remove it from yesterday's backup, and the estate had
never written that down.

Also: A4 for a standard PDP interface now that AuthZEN is Final; evidence split
into mechanical and adversarial, since a green CI run is not E2 evidence; the
tenant boundary named as OWASP API1; and section 14 recording the
structure-not-tooling stance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 14:33:50 +02:00
codex
f8c3c1d8fd ADR-008 draft-2: reframe from fixed model to graduated framework
Draft-1 proposed one model of multi-tenancy with fixed characteristics.
Rejected: the estate needs a framework that can hold several situations at
once, including repos that do not separate tenants rigorously today and must
be developed toward doing so.

What changed:

- Every plane now carries an ordered ladder (I0-I3, A0-A3, E0-E4, P0-P4),
  not just placement. A service is a posture vector, not a verdict.
- D3 reversed. Draft-1 forbade RLS as a control that "looks like a database
  guarantee without being one". The observation was right, the conclusion
  wrong: RLS is E3, materially stronger than E2, and the actual error was
  calling E3 by E4's name. Platform enforcement is now the direction of
  travel and an obligation on the platform, not only on consumers.
- New governing rule: conformance is accuracy, not altitude. Declaring E0 is
  conformant; concealing it is not. Overclaiming is the only violation.
- Fixed a flaw of draft-1's own making: R0 "shared tables, tenant column" was
  an enforcement state mislabelled as placement. Removed; P ladder renumbered.
- Added methodology (analyze/establish/improve/guard) and per-level evidence
  artifacts. Guarding checks a service against its own declaration, which is
  what makes the verification problem tractable at all.
- D7 softened per operator: tiers carry minimum levels internally, marketing
  language stays free, the constraint is on evidence not vocabulary. One hard
  line kept: "another tenant cannot reach your data" requires E4.

Ratification now also tests the framework — each reviewing repo publishes its
posture vector, and if a repo cannot express itself in these ladders the
ladders are wrong, not the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 12:20:49 +02:00
codex
d7d1a490f9 ADR-008: multi-tenancy model — four planes, one isolation ladder (proposed)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The estate has built multi-tenancy across five documents that each cover a
slice and do not compose. This records the whole model and names what is
missing.

Core framing: multi-tenancy here is four orthogonal planes — identity,
authorization, data isolation, placement — not one property. Identity and
authorization are ratified and solid. Data isolation is stated but
unverified. Placement is owned by nobody and is precedent-by-accident.

Three findings that motivated the draft:

- R0 -> R1 -> R2 does not improve tenant isolation at all. Those rungs buy
  consumer isolation and capacity. Only R3 makes the tenant boundary
  structural. A plan tier selling R2 as isolation would be false.
- No fleet mechanism verifies that any consumer actually enforces the tenant
  boundary it is obliged to enforce. A missing WHERE tenant_id would be a
  cross-tenant breach that no probe fails and no log shows as an error.
  Highest-severity gap; needs an appointed owner.
- platform-pg holds roughly six consumers (100 max_connections, 14 each) and
  memory likely binds before connections do. Two are provisioned. The runway
  is shorter than the ladder implies.

Also reconciles two already-ratified and contradictory defaults:
instance-per-client for business apps, pooled for platform services, with no
rule for telling a new service which it is.

Proposed, not ratified. Carries a ratification path so it does not join the
shared-platform-relational-storage draft in limbo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 11:26:07 +02:00