feat: publish reviewed architecture and ADR batch
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 19s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
This commit is contained in:
tegwick 2026-08-31 21:34:23 +02:00
parent 023badb512
commit 93608c1f17
120 changed files with 17791 additions and 727 deletions

View file

@ -1,7 +1,7 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="ccc2618daee997bb4bd4249613d7c4c7344845cf">
<meta name="policy-source-digest" content="99f802d91a0b3a65f0dac58230d8904f7c61cf3f81eff072fbbc59b634612a8a">
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
<meta name="policy-source-digest" content="27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876">
<title>NetKingdom Tenancy Posture v0.1</title>
<style>
:root{
@ -191,9 +191,10 @@ a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-off
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-tenancy-posture</span> <span class="stat">proposed · draft-8</span> <span>net-kingdom</span> <span>reviewed 2026-08-17</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Tenancy Posture v0.1</h1><p class="sub">A framework for describing, holding and improving multi-tenancy — including where we are not there yet.</p><p class="sub">Source: <code>net-kingdom · canon/standards/tenancy-posture_v0.1.md · ccc2618daee997bb4bd4249613d7c4c7344845cf</code></p><p class="sub">Review due: 2027-02-17</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#s0"><span class="n">0</span>Terminology: axes, not planes</a></li><li><a href="#s1"><span class="n">1</span>Context</a></li><li><a href="#s2"><span class="n">2</span>What this document is</a></li><li><a href="#s3"><span class="n">3</span>Six orthogonal axes</a></li><li><a href="#s4"><span class="n">4</span>Graduated levels</a></li><li><a href="#s5"><span class="n">5</span>The posture vector</a></li><li><a href="#s6"><span class="n">6</span>Conformance is accuracy, not altitude</a></li><li><a href="#s7"><span class="n">7</span>Portability across placement levels</a></li><li><a href="#s8"><span class="n">8</span>Placement triggers</a></li><li><a href="#s9"><span class="n">9</span>Credentials as a tenancy control</a></li><li><a href="#s10"><span class="n">10</span>Blast radius must be published</a></li><li><a href="#s11"><span class="n">11</span>Commercial expression</a></li><li><a href="#s12"><span class="n">12</span>Methodology — analyze, establish, improve, guard</a></li><li><a href="#s13"><span class="n">13</span>Evidence per level</a></li><li><a href="#s14"><span class="n">14</span>Adoption stance — structure, not tooling</a></li><li><a href="#s15"><span class="n">15</span>Alternatives considered</a></li><li><a href="#s16"><span class="n">16</span>Held against outside practice</a></li><li><a href="#s17"><span class="n">17</span>Scaling demands</a></li><li><a href="#s18"><span class="n">18</span>Consequences</a></li><li><a href="#s19"><span class="n">19</span>Review resolutions and residual questions</a></li><li><a href="#s20"><span class="n">20</span>Ratification path</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p><strong>Proposed, draft-8; ratification-ready.</strong> Relocated from <code>the-custodian/canon/architecture</code> on 2026-08-17: multi-tenancy is part of the IT-security framework NetKingdom provides, so this framework belongs in NetKingdom canon beside the IAM Profile and the tenant-engine boundary contract, not in the work-factory canon.</p>
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-tenancy-posture</span> <span class="stat">proposed · draft-14</span> <span>net-kingdom</span> <span>reviewed 2026-08-23</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Tenancy Posture v0.1</h1><p class="sub">A framework for describing, holding and improving multi-tenancy — including where we are not there yet.</p><p class="sub">Source: <code>net-kingdom · canon/standards/tenancy-posture_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2027-02-23</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#s0"><span class="n">0</span>Terminology: axes, not planes</a></li><li><a href="#s1"><span class="n">1</span>Context</a></li><li><a href="#s2"><span class="n">2</span>What this document is</a></li><li><a href="#s3"><span class="n">3</span>Six orthogonal axes</a></li><li><a href="#s4"><span class="n">4</span>Graduated levels</a></li><li><a href="#s5"><span class="n">5</span>The posture vector</a></li><li><a href="#s6"><span class="n">6</span>Conformance is accuracy, not altitude</a></li><li><a href="#s7"><span class="n">7</span>Portability across placement levels</a></li><li><a href="#s8"><span class="n">8</span>Placement triggers</a></li><li><a href="#s9"><span class="n">9</span>Credentials as a tenancy control</a></li><li><a href="#s10"><span class="n">10</span>Blast radius must be published</a></li><li><a href="#s11"><span class="n">11</span>Commercial expression</a></li><li><a href="#s12"><span class="n">12</span>Methodology — analyze, establish, improve, guard</a></li><li><a href="#s13"><span class="n">13</span>Evidence per level</a></li><li><a href="#s14"><span class="n">14</span>Adoption stance — structure, not tooling</a></li><li><a href="#s15"><span class="n">15</span>Alternatives considered</a></li><li><a href="#s16"><span class="n">16</span>Held against outside practice</a></li><li><a href="#s17"><span class="n">17</span>Scaling demands</a></li><li><a href="#s18"><span class="n">18</span>Consequences</a></li><li><a href="#s19"><span class="n">19</span>Review resolutions and residual questions</a></li><li><a href="#s20"><span class="n">20</span>Ratification path</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p><strong>Proposed, draft-13; ratification-ready.</strong> Relocated from <code>the-custodian/canon/architecture</code> on 2026-08-17: multi-tenancy is part of the IT-security framework NetKingdom provides, so this framework belongs in NetKingdom canon beside the IAM Profile and the tenant-engine boundary contract, not in the work-factory canon.</p>
<ul><li><strong>draft-1</strong> proposed a single model with fixed characteristics. Rejected: it could not describe a repo that is not there yet.</li><li><strong>draft-2</strong> reframed to graduated levels per axis. Externally corroborated (§16), but four of its statements were wrong and one thing it needed was missing.</li><li><strong>draft-3</strong> applied those corrections, added the retention axis, and recorded an adoption stance.</li><li><strong>draft-4</strong> closed the two gaps draft-3 left open: <code>R4</code> had no mechanism beyond waiting, and the noisy-neighbour evidence artifact asserted something shared infrastructure cannot provide.</li><li><strong>draft-5</strong> relocated to NetKingdom and renamed the dimensions from <em>planes</em> to <em>axes</em>, because the word was already taken (§0).</li><li><strong>draft-6</strong> applied <code>tenant-engine</code>'s review: five changes, including an axis that did not fit its data shape.</li><li><strong>draft-7</strong> applies <code>audit-core</code>, <code>railiance-platform</code> and <code>flex-auth</code>. Eleven further changes, two of them corrections to statements this document made as fact about other repos. <strong>Every posture I guessed was too generous, on every repo that has now self-reported.</strong></li><li><strong>draft-8</strong> applies <code>adaptive-pricing</code>'s review, the last of the six, and the consistency review across all declarations. It adds the missing availability axis, a canonical declaration schema, explicit authority for tier assurance, retention/placement coupling, downgrade propagation, and honest sanctioned customer language. It also corrects the distinction between an implemented control and an evidenced current level.</li></ul>
<ul><li><strong>draft-9</strong> answers <code>zone-engine</code>'s <code>ZONE-WP-0001-T01</code>. It rules that enforcement stance is <strong>not</strong> a seventh axis (Decision 5.6) while reserving <code>zones:</code> in <code>tenancy.yaml</code> so the estate keeps one declaration surface, and it records the reef/<code>P</code>/<code>V</code> reconciliation as an open defect of this document rather than of the repo that noticed it (Decision 8.4).</li><li><strong>draft-10</strong> answers <code>zone-engine</code>'s <code>ZONE-WP-0001-T03</code>. It keeps the workload as the sole security-zone policy subject, makes operational and control-plane execution units part of that term, and requires unresolved workload identity or membership to remain <code>unknown</code> without zone inference (Decision 5.6.1). It also closes the textual reef/<code>P</code> boundary while leaving the substrate-provider declaration and mechanical <code>V</code> join as implementation work (Decision 8.4.2).</li><li><strong>draft-11</strong> makes that ruling declarable. A <code>zones:</code> block now requires an authoritative <code>workload_identity</code> beside it, including for non-<code>rapp</code> operational workloads; multi-service files carry both per service. Missing identity remains absence rather than a guessed join (Decision 5.6.2).</li><li><strong>draft-12</strong> reconciles that declaration with RMGR-ADR-004 and the published <code>security-zones_v0.1</code> proposal. Managed deployables use their authoritative rapp declaration and the exact Repo Manager reference tuple; independently governed operational execution units that are not managed deployables may declare locally. Native actions, actors, lanes, patterns, and resources are explicitly <code>not-applicable</code>, while omitted or unresolved workload references remain <code>unknown</code> (Decision 5.6.2).</li><li><strong>draft-13</strong> advances the <code>audit-core</code> worked example from E1 to E2 after bounded adversarial run <code>WH-ENG-20260822-AUDIT-E2-03</code> supplied the artifact required by §13.2. The claim remains explicitly bounded and freshness-dated: the attempted cross-tenant attacks did not work; this is not a universal isolation proof.</li><li><strong>draft-14</strong> makes evidence freshness and remediation ownership declarable. Adversarial evidence may now carry its observation and expiry timestamps, bounded scope, responsible repository, and replacement action. A separate proposal-only evaluator treats absent authoritative owner or freshness as <code>unknown</code>; it does not infer either or mutate the declared posture.</li></ul>
<p><strong>Reviewed by all six. The score:</strong> six repos found three live defects in their own code by reading the ladders — <code>tenant-engine</code>'s unfiltered event accessor, <code>audit-core</code>'s unfiltered read path, <code>flex-auth</code>'s unauthenticated <code>/v1/check</code> — and <code>railiance-platform</code> found <code>apps-pg</code> running with no backup configured at all while writing its §10.2 disclosure. The framework changed to fit the repos; no repo was told to fabricate a posture.</p>
<p>Informed by five external research digests plus their index in <code>the-custodian/research/2026-08-17-adr008-*</code>, which carry full citations for the external claims made here.</p>
</section>
@ -328,14 +329,56 @@ evidence:
<p>The sharp case is OpenBao at <code>E0</code>. Literally correct, and actively misleading: the mechanism in place is credential-scoped structural separation — <code>E4</code> machinery — pointed at a <em>consumer</em> boundary rather than a tenant one. A reader scanning a column of E values would rank it below a service doing per-query filtering in application code, inverting the real security position.</p>
<p>So a platform service additionally declares, per axis, <strong>the level available now, the maximum it can make reachable, and what a consumer must do to reach it</strong>. For <code>apps-pg</code>: E4 unreachable (shared credential per consumer, no per-tenant credential), E3 conditional on the GUC contract, R2 blocked on a backup target, V1 at most on the single-node rail. That is the sentence a consumer actually needs, and no arrangement of the consumer ladders produces it.</p>
<p>A provider's own <code>P</code> is <code>n/a</code>, not a number. <code>apps-pg</code> <em>provides</em> <code>P1</code>; it is not <em>at</em> <code>P1</code>, and writing <code>P: 1</code> there would later read as an isolation claim.</p>
<p><span class="dec">Decision 5.6 — enforcement stance is not a seventh axis, and <code>zones:</code> is reserved in this file.</span> <code>zone-engine</code> asked whether <em>enforcement stance</em> — whether a given control is enforced, advisory or exempt in a given band of the estate — should fold in here rather than become a second standard. It should not, for a reason that is structural rather than territorial.</p>
<p><strong>Every one of the six ladders is monotone: higher is stronger, and higher is what a service wants.</strong> That assumption is load-bearing throughout. §12's <em>improve</em> step moves a service up. §12's <em>guard</em> checks that none is below what it declared. §6 has to make a special allowance for a level that is <em>permanently</em> low by design, and §12's guard is told not to nag it — the allowance exists because low-is-normal is the exception here.</p>
<p>Enforcement stance is not monotone. The correct stance for a bootstrap lane is deliberately and permanently <em>below</em> the top rung, and the top rung is sometimes the wrong answer outright: <code>ops-warden</code>'s <code>ADR-0006</code> is exactly the finding that a fail-closed authorization gate on the SSH lane the tunnels depend on is not a stronger position, it is an outage. A ladder whose top is sometimes wrong is an enumeration, not a ladder, and putting one inside this vector would break <code>current</code>/<code>target</code>/<code>gap</code>, the guard, and §6 for the six that are.</p>
<p>§8.3 already refused an axis for a weaker reason than this one — that a QoS level would be an unenforced claim. Enforcement stance <em>is</em> enforced. It fails the other half of the same test.</p>
<p>There is a second, sharper reason. §6's conformance rule is <em>accuracy, not altitude</em>, and it works because this framework is <strong>descriptive</strong>: it never blocks anything by itself. Prescription enters only through §11 and Decision 8.2, where a <strong>requirer</strong> — never the declaring repo — sets a minimum level and the two are machine-reconciled. Enforcement stance is prescriptive by nature. Fold it in as an axis and an accurately declared <code>exempt</code> becomes conformant <em>and</em> exempt: a conformance rule that hands out the exemption it exists to audit. The declarer must not be the party that sets the stance.</p>
<p>So the split is the one <code>flex-auth</code> already argued to <code>zone-engine</code>: <strong>membership is data and is declared; stance is a rule and belongs to the control's owner.</strong> Membership is posture-shaped and behaves like a level. Stance behaves like a tier minimum under Decision 8.2 — asserted elsewhere, joined by machine.</p>
<p><strong>What canon rules, and it is binding on the sibling standard:</strong></p>
<ul><li>A security-zone standard is a <strong>separate document</strong> in this family, drafted by <code>zone-engine</code> and published in NetKingdom canon beside this one and the <code>*-engine</code> boundary contracts. It carries over §6 verbatim and §13's evidence discipline.</li><li><strong>Zone membership is declared in <code>tenancy.yaml</code></strong>, under a reserved top-level <code>zones:</code> key, sibling to <code>tenancy:</code> and <code>provider:</code> — <em>not</em> inside <code>tenancy.current</code>. Decision 5.4 makes this file the repo's single posture declaration surface, and a second root file would recreate the divergence §5.4 was written to end. One file, one review cadence, one validator; two standards, because the two have different owners and different conformance semantics.</li><li><code>organization_posture</code> (<code>ops-warden</code> WP-0029) does <strong>not</strong> belong in this file at all, under either key. It is a fleet-wide, time-varying scalar describing the estate, not a property of the declaring service, and a per-repo copy of a global would go stale in as many places as there are repos. It is an <em>input</em> to stance selection and should be read by the zone model, not absorbed into a declaration.</li></ul>
<p><span class="dec">Decision 5.6.1 — the workload remains the sole zone policy subject, and absence resolves to <code>unknown</code>.</span> Here <strong>workload</strong> means an independently governed execution unit that performs application, automation, or operational control-plane work and can be attributed at enforcement time to both a responsible party and an authoritative workload identity. The independently governed executing unit behind operator-driven or automated SSH access, tunnel operation, credential brokering, policy compilation or enforcement, or maintenance is a workload when such a unit exists. The observed access, tunnel, grant, lane, pattern, or action is not itself a workload. A human or agent identity remains caller context; it does not replace the workload whose execution is being governed.</p>
<p>A repository, credential lane, grant template, pattern, or software package is not an alternative <strong>zone</strong> policy subject. A broker runtime is a workload; the grants and lanes it handles retain native resource identity and may explicitly be <code>not-applicable</code> to workload resolution. Every managed running deployable, including operational and tooling runtimes, has one authoritative <code>rapp-*/declarations/rapp.yaml</code> under RMGR-ADR-004. A pre-rapp managed runtime is migration debt and resolves <code>unknown</code>. An independently governed operational execution unit that is not a managed deployable may declare directly in its responsible repo's <code>tenancy.yaml</code>. The declaration surface broadens to cover real workloads; the subject model does not broaden merely to totalize an incomplete registry.</p>
<p>When a workload-applicable subject lacks an authoritative workload identity, when its reference is absent or ambiguous, or when that identity has no authoritative zone membership, the resolved membership MUST be <code>unknown</code>. It MUST NOT be inferred from a repository owner, credential path, lane type, actor class, environment, criticality, reef, organization posture, or a default zone. A control owner MAY define an explicit, reviewable fail-safe treatment for <code>unknown</code> — including denial, escalation, or a build-stage rule — but that treatment is stance, not membership. <code>unknown</code> never silently inherits a permissive zone or exception.</p>
<p><span class="dec">Decision 5.6.2 — zone membership requires an authoritative workload binding in the same declaration entry.</span> A bare service name is a label, not identity evidence. Any single-service declaration carrying <code>zones:</code> MUST also carry <code>workload_identity</code>; in a layer repo using <code>services:</code>, both fields live on the same service entry. Top-level <code>zones:</code> is not valid for a multi-service file, because it would make membership ambiguous.</p>
<p>The binding states:</p>
<ul><li><code>name</code> — the stable workload id, exactly equal to the declaration's <code>service</code>;</li><li><code>kind</code> — application, platform service, automation, operational control plane, or maintenance job;</li><li><code>responsible_repo</code> — the repository accountable for the identity and zone declaration;</li><li>one or more <code>identity_bindings</code>, each naming the identity scheme, authoritative issuer or registry, exact subject, IAM Profile principal type, and optional environment and evidence; and</li><li>for a managed deployable, a <code>declaration_ref</code> to its authoritative rapp declaration. Consuming catalogs reference it with the exact Repo Manager v1 tuple <code>(rapp_id, workload_identity.name)</code> and optional <code>deployable</code>.</li></ul>
<p>An identity binding uses <code>principal_type: service</code> or <code>agent</code>. A human identity may still be required as actor or delegation context, but cannot be the sole workload identity. A managed application, operational runtime, or tooling runtime references its rapp declaration. Only an independently governed operational execution unit that is not a managed deployable declares directly in its responsible repo's <code>tenancy.yaml</code>; native actions and resources do not acquire a fictional workload or rapp merely to enter policy.</p>
<p>The stable workload id is the join key. Credential lanes, grants, controls, and compiled policy resources reference that id explicitly; compilers MUST NOT recover it by parsing a credential path or repository name. Multiple runtime principals may bind to one workload when environments or mechanisms differ, but the bindings must be unique and remain owner-reviewed. If no authoritative binding matches the runtime principal, Decision 5.6.1 returns <code>unknown</code>.</p>
<pre>service: ops-bridge-tunnel
role: operational-access-path
workload_identity:
name: ops-bridge-tunnel
kind: operational-control-plane
responsible_repo: ops-bridge
identity_bindings:
- scheme: ssh-certificate
authority: ops-warden
subject: agt-ops-bridge
principal_type: agent
environment: prod
zones:
standard: security-zones_v0.1
membership: z2-continuity
responsible_party: ops-bridge
justification: foundational tunnel path must retain availability under PDP loss
context:
maturity: M2
criticality: high
data_classification: confidential
evidence:
- ref: docs/evidence/ops-bridge-tunnel-zone.md
supports: [M2, continuity-dependency, recovery]
reviewed: &quot;2026-08-22&quot;
review_due: &quot;2026-11-22&quot;</pre>
<p>Worked examples after applying the evidence rule and minimum-across-paths rule consistently:</p>
<div class="scroll"><table><thead><tr><th>Service</th><th>Current</th><th>Notes</th></tr></thead><tbody><tr><td><code>tenant-engine</code></td><td><code>I1 A0 E1 P n/a R0 V0</code></td><td>Acting identity is caller-supplied; unauthorised read paths set the A minimum; E2-shaped child-table controls are not evidenced; SQLite is outside P; no erasure or availability evidence. This corrects draft-7, which quoted A2/E2 despite its own minimum/evidence rules.</td></tr><tr><td><code>audit-core</code></td><td><code>I1 A2 E1 P1 R2 V0</code></td><td>E2 is implemented on both paths but awaits the adversarial artifact, so current remains E1. Its 30-day retention and erasure horizon are now declared and published.</td></tr><tr><td><code>flex-auth</code></td><td><code>I1 A0 E1 P n/a R n/a V0</code></td><td>Enables A3 for consumers. <code>/v1/check</code> authenticates no caller; E2 is implemented but not evidenced.</td></tr><tr><td><code>platform-pg</code> (provider)</td><td><code>I0 A0 E0 P n/a R2 V1</code></td><td>Provides P1; backup/restore and single-node recovery are evidenced. Provides no tenant boundary by itself.</td></tr><tr><td><code>apps-pg</code> (provider)</td><td><code>I0 A0 E0 P n/a R0 V0</code></td><td>Zeros are structural, except R0/V0 are live gaps: no backup and no recovery evidence.</td></tr><tr><td><code>adaptive-pricing</code> observatory</td><td><code>I0 A0 E0 P n/a R n/a V0</code></td><td>Local, unauthenticated, single-user analysis surface; not a production service.</td></tr><tr><td>A newly absorbed repo</td><td><code>I1 A1 E1 P0 R0 V0</code></td><td>Conformant <strong>if declared</strong>, with a recorded path.</td></tr></tbody></table></div>
<div class="scroll"><table><thead><tr><th>Service</th><th>Current</th><th>Notes</th></tr></thead><tbody><tr><td><code>tenant-engine</code></td><td><code>I1 A0 E1 P n/a R0 V0</code></td><td>Acting identity is caller-supplied; unauthorised read paths set the A minimum; E2-shaped child-table controls are not evidenced; SQLite is outside P; no erasure or availability evidence. This corrects draft-7, which quoted A2/E2 despite its own minimum/evidence rules.</td></tr><tr><td><code>audit-core</code></td><td><code>I1 A2 E2 P1 R2 V0</code></td><td>Bounded adversarial run <code>WH-ENG-20260822-AUDIT-E2-03</code> passed all three calibrated cross-tenant probes over ten operations, so E2 is evidenced as of 2026-08-22. This establishes only that the attempted attacks did not work. The 24-hour facility baseline requires review or replacement by <code>2026-08-23T22:10:25Z</code>. Its 30-day retention and erasure horizon remain declared and published.</td></tr><tr><td><code>flex-auth</code></td><td><code>I1 A0 E1 P n/a R n/a V0</code></td><td>Enables A3 for consumers. <code>/v1/check</code> authenticates no caller; E2 is implemented but not evidenced.</td></tr><tr><td><code>platform-pg</code> (provider)</td><td><code>I0 A0 E0 P n/a R2 V1</code></td><td>Provides P1; backup/restore and single-node recovery are evidenced. Provides no tenant boundary by itself.</td></tr><tr><td><code>apps-pg</code> (provider)</td><td><code>I0 A0 E0 P n/a R0 V0</code></td><td>Zeros are structural, except R0/V0 are live gaps: no backup and no recovery evidence.</td></tr><tr><td><code>adaptive-pricing</code> observatory</td><td><code>I0 A0 E0 P n/a R n/a V0</code></td><td>Local, unauthenticated, single-user analysis surface; not a production service.</td></tr><tr><td>A newly absorbed repo</td><td><code>I1 A1 E1 P0 R0 V0</code></td><td>Conformant <strong>if declared</strong>, with a recorded path.</td></tr></tbody></table></div>
<p><span class="dec">Decision 5.1:</span> the posture vector is declared in the repo, not in the hub, consistent with local-files-are-source-of-truth.</p>
<p><span class="dec">Decision 5.2 — declare per path, quote the minimum.</span> A service whose mutations are authorized and whose reads are not is at the reads' level. The quoted number is the minimum across paths; the per-path detail is declared beside it.</p>
<p>Draft-6 required only the minimum, on <code>tenant-engine</code>'s review. <code>audit-core</code> then showed why that is insufficient on its own: a bare minimum destroys signal, because <code>E3</code>-write/<code>E1</code>-read declares identically to <code>E1</code>/<code>E1</code>. Bare per-path invites "our write path is E3", which is the sentence §6 exists to stop. Both, related explicitly, is the rule.</p>
<p>Two services found this shape in themselves within a day of each other — <code>tenant-engine</code> (writes authorized, three read routes not) and <code>audit-core</code> (write path tenant-filtered, read path not filtered at all). Most services enforce harder on write than read, so this is the common case, not the corner.</p>
<p><span class="dec">Decision 5.3 — <code>n/a</code> is a level, and it is conformant.</span> <code>P0</code> presupposes a shared database and <code>R0</code> presupposes retained data. A service holding nothing at rest — <code>flex-auth</code> runs with its registry and policy baked read-only into the image and no decision log persisted — is neither. A datastore outside a ladder's substrate vocabulary, such as <code>tenant-engine</code>'s current SQLite PVC, also uses <code>n/a</code> rather than inventing a level. Without an admissible <code>n/a</code>, a missing rung <strong>forces</strong> the fabrication §6 prohibits, which is precisely what draft-1 was rejected for. <code>n/a</code> is declared with a stated reason.</p>
<p><span class="dec">Decision 5.4 — the vector lives at <code>tenancy.yaml</code> in the repo root.</span> Draft-6 said "in the repo" and not where or in what shape, which left §12's guard needing per-repo archaeology. <code>flex-auth</code> adopted <code>tenancy.yaml</code> speculatively; adopted here as the convention. A repo representing one service uses the single-service form above. A layer repo uses the schema's <code>services</code> list in the same root file — one vector per service, never an average. The normative schema is <code>canon/schemas/tenancy-posture_v0.1.schema.json</code>; prose documents may explain a declaration but do not replace it. The schema carries <code>current</code>, <code>implemented</code>, <code>target</code>, <code>reviewed</code>, <code>review_due</code>, <code>gap</code>, <code>placement_exceptions</code>, <code>service_class</code> (§8.3), per-path detail (§5.2), and provider reachability (§5.5). From the <code>net-kingdom</code> repo, owners validate one or more declarations with <code>uv run tools/tenancy-posture/validate.py &lt;path&gt;...</code>; the validator applies the JSON Schema and the evidence, date, implemented/current and provider-range rules that JSON Schema alone cannot express.</p>
<p><span class="dec">Decision 5.4 — the vector lives at <code>tenancy.yaml</code> in the repo root.</span> Draft-6 said "in the repo" and not where or in what shape, which left §12's guard needing per-repo archaeology. <code>flex-auth</code> adopted <code>tenancy.yaml</code> speculatively; adopted here as the convention. A repo representing one service uses the single-service form above. A layer repo uses the schema's <code>services</code> list in the same root file — one vector per service, never an average. The normative schema is <code>canon/schemas/tenancy-posture_v0.1.schema.json</code>; prose documents may explain a declaration but do not replace it. The schema carries <code>current</code>, <code>implemented</code>, <code>target</code>, <code>reviewed</code>, <code>review_due</code>, <code>gap</code>, <code>placement_exceptions</code>, <code>service_class</code> (§8.3), per-path detail (§5.2), and provider reachability (§5.5), plus the workload identity prerequisite for zone membership (§5.6.2). It also permits <code>responsible_repo</code> for authoritative posture routing and <code>evidence_freshness</code> for machine-readable evidence observation, expiry, scope, owner, and remediation metadata. Their absence remains valid declaration syntax; feedback resolution must report <code>unknown</code>, never infer them from a directory, service name, or previous owner. From the <code>net-kingdom</code> repo, owners validate one or more declarations with <code>uv run tools/tenancy-posture/validate.py &lt;path&gt;...</code>; the validator applies the JSON Schema and the evidence, date, implemented/current and provider-range rules that JSON Schema alone cannot express.</p>
</section>
<section id="s6"><h2><span class="sn">06</span>Conformance is accuracy, not altitude</h2>
<div class="rule-quote"><p><strong>A service is conformant when its declared posture is accurate, its target is recorded, and it does not claim a level it cannot evidence. It is non-conformant when it overclaims — at any altitude.</strong></p></div>
@ -360,6 +403,13 @@ evidence:
<p><span class="dec">Decision 8.3.2 — service class is declared anyway</span>, as a category rather than a level: <code>latency-critical</code>, <code>interactive</code>, or <code>batch</code>. It buys three things, none of which is priority:</p>
<ul><li><strong>A placement input.</strong> Mixing <code>latency-critical</code> with <code>batch</code> on one instance is a recognised mismatch. It may still be the right call — it is right today — but it should be a decision, not an accident of who was provisioned when.</li><li><strong>A trigger.</strong> A <code>latency-critical</code> consumer acquiring a <code>batch</code> co-resident is a recorded placement trigger under §8, on the same footing as noisy neighbour.</li><li><strong>An acceptance criterion for evidence.</strong> The noisy-neighbour artifact in §13 asks whether measured degradation is <em>acceptable</em>; without a declared class that word has no referent. Degradation tolerable for <code>batch</code> may be an outage for <code>latency-critical</code>.</li></ul>
<p><span class="dec">Decision 8.3.3 — class mixture must be visible.</span> The platform reports which classes are co-resident. An unenforceable risk that nobody can see is strictly worse than one that is stated.</p>
<h3>8.4 Substrate location is not evidence — and reefs are not reconciled with <code>P</code> or <code>V</code></h3>
<p><span class="dec">Decision 8.4.1 — location is not evidence of any property of the workload on it.</span> Three repos have now written this rule locally in three vocabularies: <code>railiance-master</code>'s <em>topology is not readiness</em>, <code>zone-engine</code>'s <em>placement is not posture</em>, and §3.1 here, which requires every use of "isolation" to name its axis. They are one rule. Stated once: <strong>the substrate a workload sits on is never, by itself, evidence for a level on any ladder in this framework.</strong> Binding to a reef, being on a dedicated instance, or naming a rail proves placement and nothing else. Other repos should cite this rather than restate it.</p>
<p><span class="dec">Decision 8.4.2 — the reef taxonomy and this framework are not reconciled, and that is this document's defect.</span> <code>zone-engine</code> asked whether canon should reconcile reefs with the posture axes, on the assumption that this was a scope question for the zone model. It is not: the unreconciled pair is not zone ↔ reef, it is <strong>reef ↔ <code>P</code> and <code>V</code></strong>, and it belongs to canon.</p>
<p><code>repo-manager</code> owns substrate placement (<code>reef-railiance</code>, <code>reef-storage</code>) with an explicit residual-risk acceptance attached to a binding. §7 and §8 of this document presuppose that placement is fully described by the <code>P</code> ladder. It is not. <code>P</code> grades <strong>tenant data isolation within a datastore</strong>; a reef is a named <strong>compute substrate carrying an accepted residual risk</strong>. The <code>P</code> ladder has no rung meaning "single node, shared control plane, risk accepted", and inventing one would be the fabrication §6 prohibits.</p>
<p>A reef binding therefore satisfies none of the <code>P</code> couplings in Decision 3.2 by itself. It records the compute substrate and its accepted residual risk; it does not establish a database-per-consumer or database-per-tenant boundary, a per-tenant credential or encryption boundary, or an erasure horizon. Those remain properties of the workload and data provider declarations. The binding does participate in <code>V</code>, because availability composes across the complete critical path and the substrate can impose a ceiling.</p>
<p>The live consequence is on <code>V</code>, not <code>P</code>. §4.6 already warns that "a dedicated cluster can still be a single instance on a single node", and Decision 4.6.1 makes <code>V</code> the minimum across the synchronous path. <code>reef-railiance</code> is single-node with a shared control plane, which <strong>caps <code>V</code> for every workload bound to it</strong> regardless of that workload's own replica count — which Decision 4.6.1 already says is not evidence. Nothing today joins the reef's facts to a consumer's <code>V</code> declaration, so a rapp can declare <code>V2</code> accurately by its own reading and be wrong by this document's own composition rule.</p>
<p>This is <code>railiance-platform</code>'s provider-declaration finding (Decision 5.5) generalised one layer down. A reef is a <strong>provider</strong> and has nowhere to state what it makes reachable. Tracked as <code>NK-WP-0027</code>; the fix is canon's, and the zone model is not blocked on it.</p>
<p>The known escalation short of P2 is gateway-level prioritisation — ordering submissions in a connection proxy by the requesting tenant's current consumption. It is real, it is where the industry puts this when it must, and it is new infrastructure we do not run. Recorded as the option, not adopted.</p>
</section>
<section id="s9"><h2><span class="sn">09</span>Credentials as a tenancy control</h2>
@ -387,6 +437,8 @@ evidence:
<p>At <code>I0/I1</code>, <code>A0/A1</code>, <code>E0</code>, <code>P0</code>, <code>R0/R1</code>, <code>V0</code>, or <code>n/a</code>, a declaration requires a <strong>stated reason</strong>, not an artifact. Evidence is what stops you overclaiming, and there is nothing to overclaim at those floors.</p>
<p><span class="dec">Decision 13.4 — an artifact must assert something achievable.</span> Draft-3's noisy-neighbour evidence required proof that a saturating consumer "does not breach" another's allowance. Shared infrastructure cannot provide that; the risk is inherent and cannot be wholly removed. An artifact that can only fail, or that passes by being run gently enough, is an overclaim wearing the costume of evidence. Where a property cannot be guaranteed, the artifact measures and records it instead.</p>
<p><span class="dec">Decision 13.2 — evidence is of two kinds, and conflating them is an overclaim.</span> <em>Mechanical</em> evidence is a structural assertion a machine can make and belongs in CI. <em>Adversarial</em> evidence is semantic, requires setting up separate tenant contexts and comparing responses, and carries a review date rather than a green build. Cross-tenant findings are the category external testing practice identifies as needing human review. <strong>A passing CI run is not E2 evidence.</strong></p>
<p><span class="dec">Decision 13.5 — freshness and ownership are explicit inputs.</span> A declaration may attach <code>evidence_freshness.&lt;level&gt;</code> to an evidence key. An adversarial entry requires <code>observed_at</code>, <code>valid_until</code>, <code>responsible_repo</code>, <code>scope</code>, and <code>remediation</code>; a mechanical entry may omit <code>valid_until</code> when the artifact is continuously re-established by the referenced revision or CI control. The timestamps use RFC 3339 and the responsible repository is the authority for replacement evidence.</p>
<p>The feedback evaluator does not parse prose for dates, infer ownership from a file path, or silently extend a validity window. A current adversarial claim without freshness metadata resolves to <strong>freshness <code>unknown</code></strong>. An expired artifact resolves to <strong>freshness <code>expired</code></strong>. Neither automatically rewrites the declared level: the evaluator emits a deterministic owner-routed remediation proposal so review remains observable and controlled. The proposal contract is <code>posture-feedback_v0.1</code>; it performs no State Hub write or policy mutation.</p>
<div class="scroll"><table><thead><tr><th>Level</th><th>Evidence</th><th>Kind</th></tr></thead><tbody><tr><td><strong>I2</strong></td><td>Identifiers validated against the vocabulary; rejection test for a malformed id; binding shown to come from a verified token</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>I3</strong></td><td>Live re-query demonstrated on an <code>aal2</code>-class path; cached-claim path shown unused there</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>A2</strong></td><td>Choke point identified; test that an unbound request is refused</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>A3</strong></td><td>Live decision with a denial observed at the endpoint, not only at the decision surface</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>A4</strong></td><td>Decision served over the standard interface; a second PDP substituted without PEP change, <strong>with the decision differences between the two recorded</strong> — substitution proves interface portability, not decision equivalence</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>E1</strong></td><td>Every tenant-owned table carries the tenant key</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>E2</strong></td><td>Choke point identified; identity bound to tenant A demonstrably cannot read tenant B</td><td><span class="kind adv">Adversarial, with a review date</span></td></tr><tr><td><strong>E3</strong></td><td><code>FORCE ROW LEVEL SECURITY</code> on every tenant table; no <code>BYPASSRLS</code> on leased roles; probe that a session without the GUC reads nothing; probe that a wrong GUC reads nothing; <code>EXPLAIN</code> comparison</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>E4</strong></td><td>Per-tenant credential demonstrated unable to connect to another tenant's substrate</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>P1–P4</strong></td><td>Provisioning declaration plus the platform's isolation probes</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>Shared P1–P2 capacity assurance</strong></td><td>A recorded baseline of per-consumer resource usage; a run in which one consumer saturates its declared allowance; evidence that the governance controls <strong>bind</strong> (the greedy consumer is held at its limits) and that the degradation co-residents experience is <strong>measured, recorded and judged acceptable against each one's declared service class</strong> (§8.3); the aggregate headroom at time of measurement</td><td><span class="kind adv">Adversarial, load-generated, with a review date</span></td></tr><tr><td><strong>R2</strong></td><td>Declared retention rendered; erasure horizon published and reported in the operator surface</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>R3</strong></td><td>Sweep evidence records: timestamp, dataset, identifiers removed, authorising policy reference</td><td><span class="kind ">Mechanical</span></td></tr><tr><td><strong>R4</strong></td><td>Erasure demonstrated across live data, backups and derived copies within the horizon</td><td><span class="kind adv">Adversarial</span></td></tr><tr><td><strong>V1</strong></td><td>Critical dependencies enumerated; restart/recreate recovery exercised; interruption and measured recovery time recorded</td><td><span class="kind ">Mechanical exercise</span></td></tr><tr><td><strong>V2</strong></td><td>One instance terminated while traffic continues or recovers automatically; measured RTO/RPO and remaining shared failure domains recorded</td><td><span class="kind adv">Adversarial, failure-injected</span></td></tr><tr><td><strong>V3</strong></td><td>Declared failure domain removed in an exercise; complete critical path and degraded modes observed against RTO/RPO</td><td><span class="kind adv">Adversarial, failure-injected</span></td></tr><tr><td><strong>V4</strong></td><td>Region made unavailable in an exercise; traffic and state recover in the alternate region against RTO/RPO</td><td><span class="kind adv">Adversarial, failure-injected</span></td></tr></tbody></table></div>
<p>The P1–P4 artifact proves the declared placement topology. The shared-capacity artifact is additional: it is required before a P1/P2 service can claim that a noisy-neighbour control binds, that the trigger is actively guarded, or that a customer performance assurance survives co-residency. It is not required merely to report the true topology as P1 or P2. No such capacity artifact exists in the estate today, so §11 requires P2 or an enforceable governor for a performance-differentiated tier.</p>
<p><span class="dec">Decision 13.3:</span> the tenant-boundary E2/E3 and noisy-neighbour artifacts do not exist anywhere in the estate today. <code>rapp-postgres</code> runs 19 adversarial probes, all against the <em>consumer</em> boundary, none against the tenant boundary inside a consumer. Externally, what this framework calls a tenant boundary failure is <strong>Broken Object Level Authorization</strong> — OWASP API1, top of the API Security Top 10 since that list launched, and the most commonly exploited API vulnerability in published assessments. We have no coverage for the highest-ranked risk in our class of system. §19.3 records the owner.</p>
@ -437,8 +489,8 @@ per consumer: 14 connections (12 runtime + 2 migration)</pre>
<p>The residual tension is recorded rather than resolved: NetKingdom owns this framework <em>and</em> the facility that tests conformance to it, so those findings are NetKingdom assessing NetKingdom. The mitigation is that findings leave for <code>risk-nexus</code>, under <code>the-custodian</code>, rather than being closed in place. Proportionate, not perfect. Revisit if conformance findings start getting quietly closed.</p>
<p>Two consequences land back here. <strong>Cadence is now a security parameter, not a schedule</strong> — for any control whose guarantee is detection rather than prevention, the interval between probe runs <em>is</em> the exposure window, and <code>rapp-postgres</code> ADR-0003 leaves that number to the facility. And <strong>a passing suite is not proof of isolation</strong>; it is proof that the attacks attempted did not work. §13's evidence artifacts should be read with that distinction, because a green run recorded as "E2 verified" would be exactly the overclaim §6 prohibits.</p>
<ol><li><strong>Business app vs platform service — open.</strong> Custodian canon: a classification rule. Candidate: reuse <code>repo-classification-standard_v1.0</code>.</li><li><strong>Tier → minimum level mapping — policy resolved, implementation open.</strong> <code>adaptive-pricing</code> owns typed minima and wording; <code>tenant-engine</code> owns plan assignment by id. Current tiers make no assurance claims.</li><li><strong>The E3 mechanism — resolved.</strong> <code>rapp-postgres</code> ADR-0003 publishes the GUC contract with the <code>FORCE</code>/<code>BYPASSRLS</code>/<code>SECURITY INVOKER</code>/<code>EXPLAIN</code> requirements.</li><li><strong>Identity-provider placement — open.</strong> Owner of <code>key-cape</code>: realm-per-tenant or Organizations? Realm-per-tenant's ~5–20 tenant ceiling is below our target.</li><li><strong>Cell sizing — resolved for <code>platform-pg</code>.</strong> <code>rapp-postgres</code> ADR-0004 sets four consumers and names absent overflow target <code>platform-pg-2</code>; measurement and provisioning remain live gaps.</li><li><strong>Retention floor and ceiling — resolved as policy.</strong> Both exist; requests outside them fail validation and the package repo owns the numbers.</li><li><strong>Engine neutrality — open.</strong> The P ladder rests on a PostgreSQL property. State it engine-specifically and say so, or abstract it and risk a non-Postgres implementation that silently differs?</li><li><strong>Erasure versus audit — framework resolved.</strong> <code>audit-core</code>: crypto-shredding a tenant's audit records destroys the evidence the service exists to hold, and ADR-0001 §2 deliberately built the role model so history could not be rewritten. The usual resolution separates the <em>fact</em> of an event, retained, from its <em>personal payload</em>, encrypted per subject and shreddable. Raised because a naive "R4 everywhere" target would instruct the audit service to destroy its own evidence. <code>audit-core</code> targets R2 and is explicitly not a fleet R4 target. The legal basis for retaining audit facts remains a risk/legal question outside this framework.</li><li><strong>Quality of service</strong> — <strong>resolved 2026-08-17.</strong> Co-residents are equal; a declared <em>service class</em> informs placement but never grants priority. See §8.3. The question asked whether to add a QoS dimension; the answer is no, and the reason is that we could not enforce one.</li></ol>
<p><strong>Routed elsewhere, deliberately.</strong> The tenant identifier <code>tenant:&lt;grouping&gt;:&lt;name&gt;</code> embeds headcount bands (<code>small</code>, <code>medium</code>, <code>large</code>) that change as a tenant grows, contradicting the consensus that identifiers should not encode mutable attributes. That is a critique of ADR-0013, not of this framework, and belongs to <code>tenant-engine</code> and NetKingdom canon. Folding it in here would overreach.</p>
<p><strong>Tenant grouping ambiguity — resolved outside this framework.</strong> ADR-0013 revision 2 makes the identifier segment immutable onboarding-time history and the <code>tenant-engine</code> record authoritative for current grouping. Consumers do not parse current policy or spend-ceiling inputs from <code>tenant:&lt;grouping&gt;:&lt;name&gt;</code>.</p>
</section>
<section id="s20"><h2><span class="sn">20</span>Ratification path</h2>
<ol><li>Reviewed by <code>tenant-engine</code>, <code>flex-auth</code>, <code>audit-core</code>, <code>rapp-postgres</code>, <code>railiance-platform</code> and <code>adaptive-pricing</code> against §19. <strong>Complete in draft-8.</strong></li><li>Each publishes its own posture vector (§5) as part of review. <strong>The framework is validated by whether it can describe them accurately</strong> — if a repo cannot express itself in these six ladders, the ladders are wrong and this document changes, not the repo. <strong>Complete in draft-8; all six root declarations validate against the canonical schema.</strong></li><li>On acceptance, <strong>supersedes</strong> the routing of <code>rapp-postgres/docs/canon-drafts/shared-platform-relational-storage_v0.1-draft.md</code>, whose §§3–8 are absorbed here. That draft is withdrawn rather than left pending.</li><li>On acceptance, <code>rapp-postgres</code> ADR-0001 through ADR-0004 move to <code>accepted</code> and are annotated as the PostgreSQL implementation of the E, P, R and shared-capacity rules.</li></ol>
</section><footer><span>netkingdom-tenancy-posture · draft-8 · proposed</span><span>net-kingdom · canon/standards/tenancy-posture_v0.1.md · ccc2618daee997bb4bd4249613d7c4c7344845cf</span></footer></main></div></div></html>
</section><footer><span>netkingdom-tenancy-posture · draft-14 · proposed</span><span>net-kingdom · canon/standards/tenancy-posture_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>