feat: publish reviewed architecture and ADR batch
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 19s
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:
parent
023badb512
commit
93608c1f17
120 changed files with 17791 additions and 727 deletions
|
|
@ -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="6287be08e35ddefc8e93d3b127cd8a1be27dc311b014becdbcfb78c4164faa0a">
|
||||
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
|
||||
<meta name="policy-source-digest" content="6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d">
|
||||
<title>NetKingdom IAM Profile v0.3</title>
|
||||
<style>
|
||||
:root{
|
||||
|
|
@ -191,7 +191,7 @@ 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-iam-profile-v0.3</span> <span class="stat">accepted</span> <span>net-kingdom</span> <span>reviewed 2026-07-23</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom IAM Profile v0.3</h1><p class="sub">Source: <code>net-kingdom · canon/standards/iam-profile_v0.3.md · ccc2618daee997bb4bd4249613d7c4c7344845cf</code></p><p class="sub">Review due: 2027-01-23</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#purpose"><span class="n">·</span>Purpose</a></li><li><a href="#ownership"><span class="n">·</span>Ownership</a></li><li><a href="#design-principles"><span class="n">·</span>Design Principles</a></li><li><a href="#discovery-contract"><span class="n">·</span>Discovery Contract</a></li><li><a href="#required-flows"><span class="n">·</span>Required Flows</a></li><li><a href="#core-claims"><span class="n">·</span>Core Claims</a></li><li><a href="#tenant-claim"><span class="n">·</span>Tenant Claim</a></li><li><a href="#tenant-roles"><span class="n">·</span>Tenant Roles</a></li><li><a href="#assurance-evidence"><span class="n">·</span>Assurance Evidence</a></li><li><a href="#identity-to-authorization-contract"><span class="n">·</span>Identity To Authorization Contract</a></li><li><a href="#token-lifecycle"><span class="n">·</span>Token Lifecycle</a></li><li><a href="#local-development-profile"><span class="n">·</span>Local Development Profile</a></li><li><a href="#emergency-and-break-glass-access"><span class="n">·</span>Emergency And Break-Glass Access</a></li><li><a href="#conformance"><span class="n">·</span>Conformance</a></li><li><a href="#validation-checklist"><span class="n">·</span>Validation Checklist</a></li></ol></nav><main><div class="rule-quote"><p>Minor version. Per ADR-0011's versioning rule, this adds an optional claim and clarifies non-normative guidance — no required claim, validation rule, or previously-issued token is invalidated. Existing v0.2 implementations remain conformant; <code>tenant_roles</code> and the revised Tenant Claim guidance are additive.</p></div>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-iam-profile-v0.3</span> <span class="stat">accepted · accepted-1</span> <span>net-kingdom</span> <span>reviewed 2026-08-22</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom IAM Profile v0.3</h1><p class="sub">Source: <code>net-kingdom · canon/standards/iam-profile_v0.3.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2027-02-22</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#purpose"><span class="n">·</span>Purpose</a></li><li><a href="#ownership"><span class="n">·</span>Ownership</a></li><li><a href="#design-principles"><span class="n">·</span>Design Principles</a></li><li><a href="#discovery-contract"><span class="n">·</span>Discovery Contract</a></li><li><a href="#required-flows"><span class="n">·</span>Required Flows</a></li><li><a href="#core-claims"><span class="n">·</span>Core Claims</a></li><li><a href="#tenant-claim"><span class="n">·</span>Tenant Claim</a></li><li><a href="#tenant-roles"><span class="n">·</span>Tenant Roles</a></li><li><a href="#assurance-evidence"><span class="n">·</span>Assurance Evidence</a></li><li><a href="#identity-to-authorization-contract"><span class="n">·</span>Identity To Authorization Contract</a></li><li><a href="#token-lifecycle"><span class="n">·</span>Token Lifecycle</a></li><li><a href="#local-development-profile"><span class="n">·</span>Local Development Profile</a></li><li><a href="#emergency-and-break-glass-access"><span class="n">·</span>Emergency And Break-Glass Access</a></li><li><a href="#conformance"><span class="n">·</span>Conformance</a></li><li><a href="#validation-checklist"><span class="n">·</span>Validation Checklist</a></li></ol></nav><main><div class="rule-quote"><p>Minor version. Per ADR-0011's versioning rule, this adds an optional claim and clarifies non-normative guidance — no required claim, validation rule, or previously-issued token is invalidated. Existing v0.2 implementations remain conformant; <code>tenant_roles</code> and the revised Tenant Claim guidance are additive.</p></div>
|
||||
<section id="purpose"><h2>Purpose</h2>
|
||||
<p>The NetKingdom IAM Profile is the provider-neutral OIDC contract that identity implementations issue and applications consume.</p>
|
||||
<p>It defines:</p>
|
||||
|
|
@ -248,7 +248,7 @@ a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-off
|
|||
</section>
|
||||
<section id="tenant-claim"><h2>Tenant Claim</h2>
|
||||
<p><code>tenant</code> is required for every token accepted by profile consumers.</p>
|
||||
<p>Tenant identifiers follow <code>tenant:<grouping>:<name></code>, where <code><grouping></code> is one of the taxonomy ratified by ADR-0013:</p>
|
||||
<p>Tenant identifiers follow <code>tenant:<grouping>:<name></code>, where <code><grouping></code> is one of the taxonomy ratified by ADR-0013 at identifier creation:</p>
|
||||
<pre>trial - test/trial/showcase tenants only
|
||||
friendly - known, easily reached, tolerant of experimentation/instability
|
||||
single - one-person business entities (freelance consultants)
|
||||
|
|
@ -263,7 +263,7 @@ association - a legal association of people
|
|||
agentic - financially enabled AI entities</pre>
|
||||
<p><code>tenant:platform</code> and <code>tenant:coulomb</code> remain <strong>reserved, ungrouped</strong> identifiers outside this taxonomy: <code>tenant:platform</code> is the platform control-plane tenant, not a business entity being onboarded; <code>tenant:coulomb</code> is the first internal/reference tenant established by ADR-0006, predating this taxonomy. Tenant administration for <code>tenant:coulomb</code> or any grouped tenant must never imply platform-root authority.</p>
|
||||
<p>Subjects may have access to multiple tenants, but a token used for a request MUST identify the tenant context for that request. If a client needs to switch tenant context, it obtains a new token or uses an approved token-exchange flow that records the target tenant.</p>
|
||||
<p>The grouping segment is <strong>onboarding-risk / entity-shape</strong> classification only. It does not gate which capability roles (below) a tenant may hold — see Tenant Roles.</p>
|
||||
<p>The grouping segment is an immutable record of the tenant's <strong>onboarding-time</strong> onboarding-risk / entity-shape classification. It remains vocabulary-valid but becomes historical if the tenant's classification later changes. The authoritative current grouping is the <code>grouping</code> field held by <code>tenant-engine</code>; consumers MUST NOT split <code>tenant</code> and treat its middle segment as current policy input. Changing current grouping never renames the tenant. Neither the historical segment nor current grouping gates which capability roles (below) a tenant may hold — see Tenant Roles.</p>
|
||||
</section>
|
||||
<section id="tenant-roles"><h2>Tenant Roles</h2>
|
||||
<p>Tenant <strong>capability roles</strong> are a separate fact from the grouping above and from the subject-level <code>roles</code> claim: <code>PLTF</code>, <code>IAM</code>, <code>VEN</code>, <code>CUS</code> (ratified by ADR-0014), non-exclusive — a tenant may hold several simultaneously, and holding one does not require or restrict any grouping.</p>
|
||||
|
|
@ -316,4 +316,4 @@ agentic - financially enabled AI entities</pre>
|
|||
<section id="validation-checklist"><h2>Validation Checklist</h2>
|
||||
<p>A service or implementation is profile-ready when:</p>
|
||||
<ul><li>it reads OIDC discovery rather than hardcoding endpoints;</li><li>it validates issuer, audience, expiry, <code>nbf</code>, algorithm, and signature;</li><li>it refreshes JWKS on unknown <code>kid</code>;</li><li>it supports Authorization Code + PKCE for human login;</li><li>it supports service-account or workload identity tokens;</li><li>it emits <code>tenant</code>, <code>principal_type</code>, <code>groups</code>, <code>roles</code>, <code>scope</code>/<code>scp</code>, and <code>assurance</code>;</li><li>it uses the ADR-0013 grouping vocabulary for new tenant identifiers;</li><li>if it consumes <code>tenant_roles</code>, it treats the claim as a cache and re-validates live against <code>tenant-engine</code> before any <code>aal2</code>-class decision;</li><li>it maps provider-native claims into the canonical core claims;</li><li>it rejects local-development issuers in production;</li><li>it logs emergency access with a durable audit trail;</li><li>flex-auth receives identity facts from the profile, not from provider-specific sessions.</li></ul>
|
||||
</section><footer><span>netkingdom-iam-profile-v0.3 · · accepted</span><span>net-kingdom · canon/standards/iam-profile_v0.3.md · ccc2618daee997bb4bd4249613d7c4c7344845cf</span></footer></main></div></div></html>
|
||||
</section><footer><span>netkingdom-iam-profile-v0.3 · accepted-1 · accepted</span><span>net-kingdom · canon/standards/iam-profile_v0.3.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>
|
||||
|
|
|
|||
319
build/standards/iam-profile/v0.3/revisions/accepted-1/index.html
Normal file
319
build/standards/iam-profile/v0.3/revisions/accepted-1/index.html
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
|
||||
<meta name="policy-source-digest" content="6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d">
|
||||
<title>NetKingdom IAM Profile v0.3</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-iam-profile-v0.3</span> <span class="stat">accepted · accepted-1</span> <span>net-kingdom</span> <span>reviewed 2026-08-22</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom IAM Profile v0.3</h1><p class="sub">Source: <code>net-kingdom · canon/standards/iam-profile_v0.3.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2027-02-22</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#purpose"><span class="n">·</span>Purpose</a></li><li><a href="#ownership"><span class="n">·</span>Ownership</a></li><li><a href="#design-principles"><span class="n">·</span>Design Principles</a></li><li><a href="#discovery-contract"><span class="n">·</span>Discovery Contract</a></li><li><a href="#required-flows"><span class="n">·</span>Required Flows</a></li><li><a href="#core-claims"><span class="n">·</span>Core Claims</a></li><li><a href="#tenant-claim"><span class="n">·</span>Tenant Claim</a></li><li><a href="#tenant-roles"><span class="n">·</span>Tenant Roles</a></li><li><a href="#assurance-evidence"><span class="n">·</span>Assurance Evidence</a></li><li><a href="#identity-to-authorization-contract"><span class="n">·</span>Identity To Authorization Contract</a></li><li><a href="#token-lifecycle"><span class="n">·</span>Token Lifecycle</a></li><li><a href="#local-development-profile"><span class="n">·</span>Local Development Profile</a></li><li><a href="#emergency-and-break-glass-access"><span class="n">·</span>Emergency And Break-Glass Access</a></li><li><a href="#conformance"><span class="n">·</span>Conformance</a></li><li><a href="#validation-checklist"><span class="n">·</span>Validation Checklist</a></li></ol></nav><main><div class="rule-quote"><p>Minor version. Per ADR-0011's versioning rule, this adds an optional claim and clarifies non-normative guidance — no required claim, validation rule, or previously-issued token is invalidated. Existing v0.2 implementations remain conformant; <code>tenant_roles</code> and the revised Tenant Claim guidance are additive.</p></div>
|
||||
<section id="purpose"><h2>Purpose</h2>
|
||||
<p>The NetKingdom IAM Profile is the provider-neutral OIDC contract that identity implementations issue and applications consume.</p>
|
||||
<p>It defines:</p>
|
||||
<ul><li>OIDC discovery and endpoint requirements;</li><li>Authorization Code + PKCE for human login;</li><li>service-account and workload identity token requirements;</li><li>human, service, and agent principal representation;</li><li>tenant, tenant-grouping, and platform-boundary claims;</li><li>tenant capability roles and their carrying mechanism;</li><li>explicit assurance evidence;</li><li>the identity-to-authorization claim contract consumed by flex-auth;</li><li>local-development and emergency-access behavior;</li><li>executable conformance expectations.</li></ul>
|
||||
<p>Applications target this profile, not a concrete identity provider. key-cape is the lightweight implementation. Keycloak is the expanded-mode implementation. Both are interchangeable at the application and authorization boundary when they conform to this document.</p>
|
||||
</section>
|
||||
<section id="ownership"><h2>Ownership</h2>
|
||||
<p>NetKingdom owns the core/platform profile. See ADR-0011.</p>
|
||||
<p>Downstream systems may define extension scopes, roles, resource names, and tenant policy vocabularies. Those extensions are not part of the core profile unless a future version explicitly adopts them. Extension vocabularies must map back to the core claims in this document before flex-auth or applications consume them.</p>
|
||||
</section>
|
||||
<section id="design-principles"><h2>Design Principles</h2>
|
||||
<ul><li>Consumers trust signed OIDC tokens, not provider-specific sessions.</li><li>Identity providers assert identity and authentication evidence; they do not make final resource authorization decisions.</li><li>The same profile works in lightweight key-cape mode and expanded Keycloak mode.</li><li>Tenancy is explicit. <code>tenant:platform</code> is distinct from tenant planes such as <code>tenant:coulomb</code> and from later tenants grouped per ADR-0013.</li><li>A tenant's onboarding <strong>grouping</strong> (ADR-0013) and its <strong>capability roles</strong> (ADR-0014) are independent axes. Neither is encoded in the other; a tenant's roles may change without renaming its identifier.</li><li>Human, service, and agent principals are distinguishable.</li><li>Assurance evidence is explicit enough for flex-auth policy.</li><li>Local-development issuers are useful but never accepted by production.</li><li>Emergency access is auditable, time-bounded, and reviewable.</li></ul>
|
||||
</section>
|
||||
<section id="discovery-contract"><h2>Discovery Contract</h2>
|
||||
<p>Every IAM Profile implementation MUST expose OIDC discovery at:</p>
|
||||
<pre>GET <issuer>/.well-known/openid-configuration</pre>
|
||||
<p>The discovery response MUST include:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Field</th><th>Requirement</th></tr></thead><tbody><tr><td><code>issuer</code></td><td>Exact issuer identifier used in tokens</td></tr><tr><td><code>authorization_endpoint</code></td><td>Required for human Authorization Code + PKCE</td></tr><tr><td><code>token_endpoint</code></td><td>Required for token exchange and service accounts</td></tr><tr><td><code>jwks_uri</code></td><td>Required for signature validation</td></tr><tr><td><code>userinfo_endpoint</code></td><td>Required when userinfo is supported by the flow</td></tr><tr><td><code>scopes_supported</code></td><td>MUST include <code>openid</code>; SHOULD include <code>profile</code> and <code>email</code></td></tr><tr><td><code>response_types_supported</code></td><td>MUST include <code>code</code></td></tr><tr><td><code>grant_types_supported</code></td><td>MUST include <code>authorization_code</code>; MUST include <code>client_credentials</code> or a documented workload-token exchange for service identities</td></tr><tr><td><code>id_token_signing_alg_values_supported</code></td><td>MUST include the implementation signing algorithm; RS256 is required for v0.2+ conformance</td></tr><tr><td><code>code_challenge_methods_supported</code></td><td>MUST include <code>S256</code></td></tr></tbody></table></div>
|
||||
<p>The response SHOULD include <code>end_session_endpoint</code> where logout is supported and <code>claims_supported</code> listing the core claims below.</p>
|
||||
<p>Consumers MUST discover endpoints and key material from the issuer metadata instead of hardcoding provider-specific paths.</p>
|
||||
</section>
|
||||
<section id="required-flows"><h2>Required Flows</h2>
|
||||
<h3>Human Interactive Flow</h3>
|
||||
<p>Human users authenticate with Authorization Code + PKCE.</p>
|
||||
<p>Required properties:</p>
|
||||
<ul><li>PKCE with <code>S256</code> is mandatory for browser and CLI clients.</li><li>Implicit flow is not part of the profile.</li><li>MFA or equivalent strong assurance is mandatory for privileged, destructive, platform-root, and emergency access in production.</li><li>Access tokens are short-lived.</li><li>Refresh tokens are allowed only for trusted clients with explicit rotation and revocation.</li></ul>
|
||||
<h3>Service Account Flow</h3>
|
||||
<p>Service-to-service traffic uses client credentials or a deployment's documented workload identity token-exchange equivalent.</p>
|
||||
<p>Required properties:</p>
|
||||
<ul><li>Service subjects are stable and named for service plus environment.</li><li>Secrets or workload credentials are delivered through the credential-management standard, not plaintext configuration.</li><li>Tokens include an audience that identifies the target service.</li><li>Tokens carry <code>principal_type: service</code>.</li><li>Service accounts receive only required scopes and roles.</li><li>Credentials are rotated and never shared between environments.</li></ul>
|
||||
<h3>Agent Principal Flow</h3>
|
||||
<p>Agents are automation principals that may act autonomously or under delegated authority.</p>
|
||||
<p>Required properties:</p>
|
||||
<ul><li>Tokens carry <code>principal_type: agent</code>.</li><li>Tokens include an <code>agent</code> object with <code>id</code> and <code>mode</code>.</li><li><code>agent.mode</code> is <code>autonomous</code> or <code>delegated</code>.</li><li>Delegated agents MUST identify the delegating actor using <code>actor_sub</code> or an equivalent <code>act.sub</code> claim.</li><li>Agent tokens MUST carry the tenant they operate within.</li><li>Agent tokens MUST include assurance evidence for both the agent credential and any delegated human authority when policy needs it.</li></ul>
|
||||
</section>
|
||||
<section id="core-claims"><h2>Core Claims</h2>
|
||||
<p>Access tokens accepted by production consumers MUST provide the following claims after provider mapping or normalization:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Claim</th><th>Type</th><th>Meaning</th></tr></thead><tbody><tr><td><code>iss</code></td><td>string</td><td>OIDC issuer URL or issuer identifier</td></tr><tr><td><code>sub</code></td><td>string</td><td>Stable subject identifier unique within <code>iss</code></td></tr><tr><td><code>aud</code></td><td>string or array</td><td>Intended audience; MUST include the receiving service</td></tr><tr><td><code>exp</code></td><td>number</td><td>Expiry timestamp</td></tr><tr><td><code>iat</code></td><td>number</td><td>Issued-at timestamp</td></tr><tr><td><code>nbf</code></td><td>number</td><td>Not-before timestamp, recommended for production tokens</td></tr><tr><td><code>jti</code></td><td>string</td><td>Token identifier, recommended for audit and replay controls</td></tr><tr><td><code>tenant</code></td><td>string</td><td>Tenant identifier such as <code>tenant:platform</code> or <code>tenant:friendly:binky</code></td></tr><tr><td><code>principal_type</code></td><td>string</td><td><code>human</code>, <code>service</code>, or <code>agent</code></td></tr><tr><td><code>groups</code></td><td>array</td><td>Group memberships, possibly empty</td></tr><tr><td><code>roles</code></td><td>array</td><td>Coarse <strong>identity</strong> roles for the subject, possibly empty</td></tr><tr><td><code>scope</code> or <code>scp</code></td><td>string or array</td><td>Granted OAuth scopes</td></tr><tr><td><code>assurance</code></td><td>object</td><td>Authentication and credential assurance evidence</td></tr></tbody></table></div>
|
||||
<p>Recommended human claims:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Claim</th><th>Meaning</th></tr></thead><tbody><tr><td><code>preferred_username</code></td><td>Human-readable username</td></tr><tr><td><code>email</code></td><td>Contact identity</td></tr><tr><td><code>name</code></td><td>Display name</td></tr></tbody></table></div>
|
||||
<p>Recommended service claims:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Claim</th><th>Meaning</th></tr></thead><tbody><tr><td><code>azp</code> or <code>client_id</code></td><td>Authorized client/service identifier</td></tr><tr><td><code>service</code></td><td>Object naming the service and environment</td></tr></tbody></table></div>
|
||||
<p>Recommended agent claims:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Claim</th><th>Meaning</th></tr></thead><tbody><tr><td><code>agent.id</code></td><td>Stable agent identifier</td></tr><tr><td><code>agent.mode</code></td><td><code>autonomous</code> or <code>delegated</code></td></tr><tr><td><code>actor_sub</code> or <code>act.sub</code></td><td>Delegating subject for delegated agents</td></tr></tbody></table></div>
|
||||
<p>Optional claims (new in v0.3):</p>
|
||||
<div class="scroll"><table><thead><tr><th>Claim</th><th>Type</th><th>Meaning</th></tr></thead><tbody><tr><td><code>tenant_roles</code></td><td>array</td><td>Cached tenant <strong>capability</strong> roles (<code>PLTF</code>/<code>IAM</code>/<code>VEN</code>/<code>CUS</code>), possibly empty. See "Tenant Roles" below — this is a point-in-time cache, not the authoritative source.</td></tr></tbody></table></div>
|
||||
<h3>Role Claim</h3>
|
||||
<p>The canonical <strong>subject</strong>-level role claim is <code>roles</code>, an array of strings. This is distinct from <code>tenant_roles</code> (below) — a subject's own coarse identity roles are not the same fact as which capability roles the subject's tenant holds.</p>
|
||||
<p>Expanded-mode Keycloak deployments may also expose provider-native roles such as <code>realm_access.roles</code>, but conforming tokens consumed by flex-auth or applications MUST either emit <code>roles</code> directly or pass through a normalizing adapter that produces <code>roles</code>.</p>
|
||||
<h3>Scope Vocabulary</h3>
|
||||
<p>The core profile defines only OAuth/OIDC base scopes:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Scope</th><th>Meaning</th></tr></thead><tbody><tr><td><code>openid</code></td><td>Required for OIDC login</td></tr><tr><td><code>profile</code></td><td>Basic profile claims</td></tr><tr><td><code>email</code></td><td>Email claim where appropriate</td></tr><tr><td><code>offline_access</code></td><td>Refresh-token capable access where explicitly allowed</td></tr></tbody></table></div>
|
||||
<p>Hub-, application-, and resource-specific scopes such as <code>hub:*</code>, <code>ops:*</code>, <code>fin:*</code>, or storage actions are downstream extensions. They are valid only when the consuming system defines them and maps them to flex-auth resource/action semantics.</p>
|
||||
</section>
|
||||
<section id="tenant-claim"><h2>Tenant Claim</h2>
|
||||
<p><code>tenant</code> is required for every token accepted by profile consumers.</p>
|
||||
<p>Tenant identifiers follow <code>tenant:<grouping>:<name></code>, where <code><grouping></code> is one of the taxonomy ratified by ADR-0013 at identifier creation:</p>
|
||||
<pre>trial - test/trial/showcase tenants only
|
||||
friendly - known, easily reached, tolerant of experimentation/instability
|
||||
single - one-person business entities (freelance consultants)
|
||||
small - up to 10 employees at time of onboarding (attoo)
|
||||
medium - up to 100 employees (attoo)
|
||||
large - up to 1000 employees (attoo)
|
||||
enterprise - 1001+ employees (attoo)
|
||||
consumer - private individuals
|
||||
family - a legal family
|
||||
community - a non-legal group of people
|
||||
association - a legal association of people
|
||||
agentic - financially enabled AI entities</pre>
|
||||
<p><code>tenant:platform</code> and <code>tenant:coulomb</code> remain <strong>reserved, ungrouped</strong> identifiers outside this taxonomy: <code>tenant:platform</code> is the platform control-plane tenant, not a business entity being onboarded; <code>tenant:coulomb</code> is the first internal/reference tenant established by ADR-0006, predating this taxonomy. Tenant administration for <code>tenant:coulomb</code> or any grouped tenant must never imply platform-root authority.</p>
|
||||
<p>Subjects may have access to multiple tenants, but a token used for a request MUST identify the tenant context for that request. If a client needs to switch tenant context, it obtains a new token or uses an approved token-exchange flow that records the target tenant.</p>
|
||||
<p>The grouping segment is an immutable record of the tenant's <strong>onboarding-time</strong> onboarding-risk / entity-shape classification. It remains vocabulary-valid but becomes historical if the tenant's classification later changes. The authoritative current grouping is the <code>grouping</code> field held by <code>tenant-engine</code>; consumers MUST NOT split <code>tenant</code> and treat its middle segment as current policy input. Changing current grouping never renames the tenant. Neither the historical segment nor current grouping gates which capability roles (below) a tenant may hold — see Tenant Roles.</p>
|
||||
</section>
|
||||
<section id="tenant-roles"><h2>Tenant Roles</h2>
|
||||
<p>Tenant <strong>capability roles</strong> are a separate fact from the grouping above and from the subject-level <code>roles</code> claim: <code>PLTF</code>, <code>IAM</code>, <code>VEN</code>, <code>CUS</code> (ratified by ADR-0014), non-exclusive — a tenant may hold several simultaneously, and holding one does not require or restrict any grouping.</p>
|
||||
<p><strong>Source of truth:</strong> <code>tenant-engine</code> (<code>canon/standards/tenant-engine-boundary-contract_v0.1.md</code>), not this profile and not any token. <code>tenant-engine</code> records role grants/revocations, their link (if any) to a plan/subscription, and emits domain events on change.</p>
|
||||
<p><strong>Carrying mechanism — hybrid, not claim-only:</strong></p>
|
||||
<ul><li><code>key-cape</code> (or Keycloak) MAY stamp a cached <code>tenant_roles</code> claim onto an issued token at issuance time, sourced from <code>tenant-engine</code>.</li><li>Consumers MAY trust the cached claim for ordinary, non-privileged decisions.</li><li>Consumers <strong>MUST NOT</strong> trust the cached claim for privileged, destructive, platform-root, secret, credential-vending, or otherwise <code>assurance.level >= aal2</code>-class decisions. Those decisions <strong>MUST</strong> query <code>tenant-engine</code> live for current role state before authorizing the action.</li><li>This bounds staleness for ordinary actions to the issuing token's lifetime (5-30 minutes for service/agent tokens, see Token Lifecycle) while guaranteeing freshness exactly where a stale grant (e.g. <code>VEN</code> surviving a plan cancellation) would matter most.</li></ul>
|
||||
<p><code>trial</code>-grouped tenants may hold any capability role without restriction — the grouping exists to showcase, test, and explore every role. Safety for <code>trial</code> tenants is enforced through <code>tenant-engine</code>-owned resource guardrails (spend limits, entity/action counts — reserved, not yet specified), not through role gating.</p>
|
||||
</section>
|
||||
<section id="assurance-evidence"><h2>Assurance Evidence</h2>
|
||||
<p>The canonical assurance claim is <code>assurance</code>.</p>
|
||||
<p>It is an object with these fields:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Field</th><th>Type</th><th>Meaning</th></tr></thead><tbody><tr><td><code>level</code></td><td>string</td><td><code>aal0</code>, <code>aal1</code>, <code>aal2</code>, <code>aal3</code>, or <code>break_glass</code></td></tr><tr><td><code>methods</code></td><td>array</td><td>Authentication methods, e.g. <code>pwd</code>, <code>otp</code>, <code>webauthn</code>, <code>client_secret</code>, <code>workload_identity</code>, <code>upstream_mfa</code></td></tr><tr><td><code>mfa</code></td><td>boolean</td><td>Whether the authentication included multiple factors or equivalent upstream evidence</td></tr><tr><td><code>source</code></td><td>string</td><td>Provider of the evidence, e.g. <code>key-cape</code>, <code>keycloak</code>, <code>privacyidea</code>, <code>entra</code>, <code>local-identity</code></td></tr><tr><td><code>at</code></td><td>number</td><td>Authentication time, recommended</td></tr></tbody></table></div>
|
||||
<p>Level meanings:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Level</th><th>Meaning</th></tr></thead><tbody><tr><td><code>aal0</code></td><td>Local/dev or unauthenticated bootstrap evidence; never production privileged</td></tr><tr><td><code>aal1</code></td><td>Single-factor or service credential evidence</td></tr><tr><td><code>aal2</code></td><td>MFA or equivalent strong upstream assurance</td></tr><tr><td><code>aal3</code></td><td>Phishing-resistant or hardware-backed assurance</td></tr><tr><td><code>break_glass</code></td><td>Time-bounded emergency access with post-event review</td></tr></tbody></table></div>
|
||||
<p>Privileged, destructive, platform-root, secret, credential-vending, and emergency flows require <code>aal2</code> or stronger unless a policy explicitly permits a narrower service or workload identity path. Emergency access MUST use <code>break_glass</code> and short token lifetimes. This is the same threshold class that gates when <code>tenant_roles</code> MUST be re-validated live against <code>tenant-engine</code> rather than trusted from the token — see Tenant Roles.</p>
|
||||
<p>Provider-native claims such as <code>acr</code> and <code>amr</code> may be present, but consumers use <code>assurance</code> as the normalized profile claim.</p>
|
||||
</section>
|
||||
<section id="identity-to-authorization-contract"><h2>Identity To Authorization Contract</h2>
|
||||
<p>flex-auth consumes IAM Profile tokens as normative identity input. flex-auth MUST NOT re-derive identity, tenant, group, role, or assurance facts from provider-specific session state.</p>
|
||||
<p>The profile guarantees these inputs for authorization decision envelopes:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Decision input</th><th>Source claim</th></tr></thead><tbody><tr><td>Subject</td><td><code>sub</code></td></tr><tr><td>Issuer</td><td><code>iss</code></td></tr><tr><td>Audience</td><td><code>aud</code></td></tr><tr><td>Tenant</td><td><code>tenant</code></td></tr><tr><td>Principal type</td><td><code>principal_type</code></td></tr><tr><td>Groups</td><td><code>groups</code></td></tr><tr><td>Subject roles</td><td><code>roles</code></td></tr><tr><td>Tenant capability roles</td><td><code>tenant_roles</code> (cached); <code>tenant-engine</code> live lookup required for high-stakes decisions</td></tr><tr><td>Scopes</td><td><code>scope</code> or <code>scp</code></td></tr><tr><td>Assurance</td><td><code>assurance</code></td></tr><tr><td>Authorized client</td><td><code>azp</code> or <code>client_id</code>, where present</td></tr><tr><td>Agent/delegation context</td><td><code>agent</code>, <code>actor_sub</code>, or <code>act</code>, where present</td></tr><tr><td>Token lifetime/audit ids</td><td><code>iat</code>, <code>nbf</code>, <code>exp</code>, <code>jti</code>, where present</td></tr></tbody></table></div>
|
||||
<p>Authorization decisions are made by flex-auth and its delegated PDP adapters. Identity providers may assert roles or scopes, but those claims are inputs to policy, not final permission to act on a resource.</p>
|
||||
</section>
|
||||
<section id="token-lifecycle"><h2>Token Lifecycle</h2>
|
||||
<p>Recommended production defaults:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Token</th><th>Lifetime</th><th>Notes</th></tr></thead><tbody><tr><td>Human access token</td><td>5-15 minutes</td><td>Short-lived bearer token</td></tr><tr><td>Refresh token</td><td>8-12 hours</td><td>Rotated and revoked on logout or suspicion</td></tr><tr><td>Service token</td><td>5-30 minutes</td><td>Reissued by client credentials or workload identity</td></tr><tr><td>Agent token</td><td>5-30 minutes</td><td>Shorter when delegated or platform-scoped</td></tr><tr><td>Emergency token</td><td>5-15 minutes</td><td>Requires incident/review record</td></tr></tbody></table></div>
|
||||
<p>Consumers MUST reject expired tokens and tokens with invalid issuer, audience, signature, <code>nbf</code>, or algorithm. Clock skew tolerance SHOULD be small, normally no more than 60 seconds.</p>
|
||||
<p>JWKS material may be cached, but consumers MUST tolerate key rotation by refreshing JWKS when a token uses an unknown <code>kid</code>.</p>
|
||||
</section>
|
||||
<section id="local-development-profile"><h2>Local Development Profile</h2>
|
||||
<p>A local file-backed provider may be used for development, tests, and bootstrap contexts where the full platform is unavailable.</p>
|
||||
<p>It MUST:</p>
|
||||
<ul><li>expose OIDC discovery;</li><li>issue signed JWTs;</li><li>support deterministic test users and service accounts;</li><li>use local-only issuer URLs or a clearly local issuer identifier;</li><li>mark tokens as local/development through issuer, audience, or assurance evidence;</li><li>be rejected by production consumers.</li></ul>
|
||||
<p>Production consumers MUST reject:</p>
|
||||
<ul><li>issuer <code>local-identity</code>;</li><li><code>http://</code> issuers;</li><li>loopback issuers such as <code>localhost</code> or <code>127.0.0.1</code>;</li><li>tokens with <code>assurance.level: aal0</code>;</li><li>tokens where the environment marks the issuer as local/dev.</li></ul>
|
||||
</section>
|
||||
<section id="emergency-and-break-glass-access"><h2>Emergency And Break-Glass Access</h2>
|
||||
<p>Emergency access is allowed only as a break-glass path.</p>
|
||||
<p>Requirements:</p>
|
||||
<ul><li>Emergency identities are disabled by default.</li><li>Activation requires an incident, decision, or human-recorded review reference.</li><li>Tokens are short-lived and carry the <code>emergency</code> role.</li><li>Tokens carry <code>assurance.level: break_glass</code>.</li><li>Every emergency action emits an audit/progress/incident event.</li><li>Emergency access is reviewed after use and then disabled again.</li></ul>
|
||||
<p>Emergency access MUST NOT bypass audit logging or flex-auth policy.</p>
|
||||
</section>
|
||||
<section id="conformance"><h2>Conformance</h2>
|
||||
<p>An implementation conforms to IAM Profile v0.3 when it passes the executable conformance suite in:</p>
|
||||
<pre>tools/iam-profile-conformance/</pre>
|
||||
<p>The suite validates:</p>
|
||||
<ul><li>discovery document completeness;</li><li>PKCE <code>S256</code> advertisement and rejection of authorization requests that omit a code challenge;</li><li>JWKS structure and key ids;</li><li>token issuer, audience, expiry, <code>nbf</code>, <code>iat</code>, and RS256 signature;</li><li>tenant, principal type, groups, roles, scopes, and assurance claim shape;</li><li><code>tenant_roles</code> claim shape when present (array of ratified role strings);</li><li>agent and delegated-agent claim shape;</li><li>local-development issuer rejection in production mode.</li></ul>
|
||||
<p>Conformance must be runnable against both key-cape lightweight issuers and Keycloak expanded-mode issuers. Implementations may add provider adapters, but the token consumed by applications and flex-auth must match the core claim contract above. <code>tenant_roles</code> conformance does not require an implementation to emit the claim (it is optional); when emitted, it must match the ratified vocabulary.</p>
|
||||
</section>
|
||||
<section id="validation-checklist"><h2>Validation Checklist</h2>
|
||||
<p>A service or implementation is profile-ready when:</p>
|
||||
<ul><li>it reads OIDC discovery rather than hardcoding endpoints;</li><li>it validates issuer, audience, expiry, <code>nbf</code>, algorithm, and signature;</li><li>it refreshes JWKS on unknown <code>kid</code>;</li><li>it supports Authorization Code + PKCE for human login;</li><li>it supports service-account or workload identity tokens;</li><li>it emits <code>tenant</code>, <code>principal_type</code>, <code>groups</code>, <code>roles</code>, <code>scope</code>/<code>scp</code>, and <code>assurance</code>;</li><li>it uses the ADR-0013 grouping vocabulary for new tenant identifiers;</li><li>if it consumes <code>tenant_roles</code>, it treats the claim as a cache and re-validates live against <code>tenant-engine</code> before any <code>aal2</code>-class decision;</li><li>it maps provider-native claims into the canonical core claims;</li><li>it rejects local-development issuers in production;</li><li>it logs emergency access with a durable audit trail;</li><li>flex-auth receives identity facts from the profile, not from provider-specific sessions.</li></ul>
|
||||
</section><footer><span>netkingdom-iam-profile-v0.3 · accepted-1 · accepted</span><span>net-kingdom · canon/standards/iam-profile_v0.3.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>
|
||||
224
build/standards/posture-feedback/v0.1/index.html
Normal file
224
build/standards/posture-feedback/v0.1/index.html
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
|
||||
<meta name="policy-source-digest" content="dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db">
|
||||
<title>NetKingdom Posture Feedback v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-posture-feedback-v0.1</span> <span class="stat">proposed</span> <span>net-kingdom</span> <span>reviewed 2026-08-23</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Posture Feedback v0.1</h1><p class="sub">Source: <code>net-kingdom · canon/standards/posture-feedback_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2026-11-23</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Deterministic time</a></li><li><a href="#s3"><span class="n">3</span>Owner resolution</a></li><li><a href="#s4"><span class="n">4</span>Finding classes</a></li><li><a href="#s5"><span class="n">5</span>Proposal and safety boundary</a></li><li><a href="#s6"><span class="n">6</span>Exit behavior</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>This contract is the first bounded C6 feedback mechanism. It turns explicit posture review dates, evidence freshness, implemented-but-unevidenced controls, and declared gaps into deterministic remediation <strong>proposals</strong>.</p>
|
||||
<p>It does not modify a posture level, policy, declaration, workplan, State Hub, or runtime. Human or separately governed automation decides whether a proposal becomes work.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Deterministic time</h2>
|
||||
<p>Evaluation requires an explicit RFC 3339 <code>as_of</code> timestamp. Wall-clock time is never read implicitly. A date-only <code>review_due</code> remains current through that calendar date in UTC and becomes overdue on the following UTC date.</p>
|
||||
<p>A non-negative horizon in days identifies items due soon. Changing the horizon changes the report digest and is therefore visible.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Owner resolution</h2>
|
||||
<p>The evaluator routes only from authoritative declaration fields:</p>
|
||||
<ul><li>posture review, gaps, and implemented controls: <code>responsible_repo</code>;</li><li>evidence replacement: <code>evidence_freshness.<level>.responsible_repo</code>;</li><li>security-zone review: <code>zones.responsible_party</code>.</li></ul>
|
||||
<p>If the relevant field is absent, owner resolution is <code>unknown</code>. The evaluator must not infer ownership from the service name, repository path, Git remote, previous work, or another policy subject.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Finding classes</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Finding</th><th>Trigger</th><th>Severity</th></tr></thead><tbody><tr><td><code>posture-review-overdue</code></td><td><code>as_of</code> is after <code>tenancy.review_due</code></td><td>high</td></tr><tr><td><code>posture-review-due-soon</code></td><td>review is within the horizon</td><td>medium</td></tr><tr><td><code>zone-review-overdue</code></td><td><code>as_of</code> is after <code>zones.review_due</code></td><td>high</td></tr><tr><td><code>zone-review-due-soon</code></td><td>zone review is within the horizon</td><td>medium</td></tr><tr><td><code>evidence-freshness-unknown</code></td><td>a current adversarial level has no complete freshness entry</td><td>high</td></tr><tr><td><code>evidence-expired</code></td><td><code>as_of</code> is after <code>valid_until</code></td><td>high</td></tr><tr><td><code>evidence-due-soon</code></td><td>evidence expires within the horizon</td><td>medium</td></tr><tr><td><code>implemented-not-evidenced</code></td><td>an <code>implemented</code> level is above <code>current</code></td><td>medium</td></tr><tr><td><code>declared-gap</code></td><td>a non-empty <code>tenancy.gap</code> entry exists</td><td>low</td></tr></tbody></table></div>
|
||||
<p>The review horizon does not generate a due-soon finding for an item already overdue or expired. Exact equality with a timestamp is still valid; expiry is strictly <code>as_of > valid_until</code>.</p>
|
||||
<p>Current adversarial levels are <code>E2</code>, <code>R4</code>, and <code>V2</code>–<code>V4</code>. This vocabulary comes from Tenancy Posture §13. Mechanical evidence is evaluated for expiry only when its declaration explicitly supplies <code>valid_until</code>.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>Proposal and safety boundary</h2>
|
||||
<p>Every finding receives a stable id derived from its source declaration, service, finding class, control, and due value. It contains the authoritative owner or <code>unknown</code>, current evidence state, reason, and recommended action. For declarations under the workspace containing this repository, the source is normalized to <code><repo>/<path></code> so absolute checkout locations do not perturb the identity. This source normalization identifies an input only; it is never an ownership inference.</p>
|
||||
<p>Every report declares:</p>
|
||||
<pre>automation:
|
||||
mode: proposal-only
|
||||
external_write_permitted: false
|
||||
policy_mutation_permitted: false
|
||||
declaration_mutation_permitted: false</pre>
|
||||
<p>Expired or unknown evidence does not silently inherit freshness and does not silently downgrade a level. It makes the uncertainty visible for governed review. Consumers that use the report as an admission gate may fail closed on high findings, but that is a separate owner decision.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>Exit behavior</h2>
|
||||
<p>The CLI emits a report conforming to <code>posture-feedback-report_v0.1.schema.json</code>. <code>--fail-on high</code> exits non-zero when at least one high-severity finding exists; <code>medium</code> includes medium and high; <code>low</code> includes every finding; <code>none</code> reports without a finding-based failure. Invalid declarations always exit non-zero.</p>
|
||||
</section><footer><span>netkingdom-posture-feedback-v0.1 · · proposed</span><span>net-kingdom · canon/standards/posture-feedback_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>
|
||||
224
build/standards/posture-feedback/v0.1/revisions/0.1/index.html
Normal file
224
build/standards/posture-feedback/v0.1/revisions/0.1/index.html
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="ce198fc2905687ea90a2892346b6860281ac87f8">
|
||||
<meta name="policy-source-digest" content="dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db">
|
||||
<title>NetKingdom Posture Feedback v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-posture-feedback-v0.1</span> <span class="stat">proposed</span> <span>net-kingdom</span> <span>reviewed 2026-08-23</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Posture Feedback v0.1</h1><p class="sub">Source: <code>net-kingdom · canon/standards/posture-feedback_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</code></p><p class="sub">Review due: 2026-11-23</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Deterministic time</a></li><li><a href="#s3"><span class="n">3</span>Owner resolution</a></li><li><a href="#s4"><span class="n">4</span>Finding classes</a></li><li><a href="#s5"><span class="n">5</span>Proposal and safety boundary</a></li><li><a href="#s6"><span class="n">6</span>Exit behavior</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>This contract is the first bounded C6 feedback mechanism. It turns explicit posture review dates, evidence freshness, implemented-but-unevidenced controls, and declared gaps into deterministic remediation <strong>proposals</strong>.</p>
|
||||
<p>It does not modify a posture level, policy, declaration, workplan, State Hub, or runtime. Human or separately governed automation decides whether a proposal becomes work.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Deterministic time</h2>
|
||||
<p>Evaluation requires an explicit RFC 3339 <code>as_of</code> timestamp. Wall-clock time is never read implicitly. A date-only <code>review_due</code> remains current through that calendar date in UTC and becomes overdue on the following UTC date.</p>
|
||||
<p>A non-negative horizon in days identifies items due soon. Changing the horizon changes the report digest and is therefore visible.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Owner resolution</h2>
|
||||
<p>The evaluator routes only from authoritative declaration fields:</p>
|
||||
<ul><li>posture review, gaps, and implemented controls: <code>responsible_repo</code>;</li><li>evidence replacement: <code>evidence_freshness.<level>.responsible_repo</code>;</li><li>security-zone review: <code>zones.responsible_party</code>.</li></ul>
|
||||
<p>If the relevant field is absent, owner resolution is <code>unknown</code>. The evaluator must not infer ownership from the service name, repository path, Git remote, previous work, or another policy subject.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Finding classes</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Finding</th><th>Trigger</th><th>Severity</th></tr></thead><tbody><tr><td><code>posture-review-overdue</code></td><td><code>as_of</code> is after <code>tenancy.review_due</code></td><td>high</td></tr><tr><td><code>posture-review-due-soon</code></td><td>review is within the horizon</td><td>medium</td></tr><tr><td><code>zone-review-overdue</code></td><td><code>as_of</code> is after <code>zones.review_due</code></td><td>high</td></tr><tr><td><code>zone-review-due-soon</code></td><td>zone review is within the horizon</td><td>medium</td></tr><tr><td><code>evidence-freshness-unknown</code></td><td>a current adversarial level has no complete freshness entry</td><td>high</td></tr><tr><td><code>evidence-expired</code></td><td><code>as_of</code> is after <code>valid_until</code></td><td>high</td></tr><tr><td><code>evidence-due-soon</code></td><td>evidence expires within the horizon</td><td>medium</td></tr><tr><td><code>implemented-not-evidenced</code></td><td>an <code>implemented</code> level is above <code>current</code></td><td>medium</td></tr><tr><td><code>declared-gap</code></td><td>a non-empty <code>tenancy.gap</code> entry exists</td><td>low</td></tr></tbody></table></div>
|
||||
<p>The review horizon does not generate a due-soon finding for an item already overdue or expired. Exact equality with a timestamp is still valid; expiry is strictly <code>as_of > valid_until</code>.</p>
|
||||
<p>Current adversarial levels are <code>E2</code>, <code>R4</code>, and <code>V2</code>–<code>V4</code>. This vocabulary comes from Tenancy Posture §13. Mechanical evidence is evaluated for expiry only when its declaration explicitly supplies <code>valid_until</code>.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>Proposal and safety boundary</h2>
|
||||
<p>Every finding receives a stable id derived from its source declaration, service, finding class, control, and due value. It contains the authoritative owner or <code>unknown</code>, current evidence state, reason, and recommended action. For declarations under the workspace containing this repository, the source is normalized to <code><repo>/<path></code> so absolute checkout locations do not perturb the identity. This source normalization identifies an input only; it is never an ownership inference.</p>
|
||||
<p>Every report declares:</p>
|
||||
<pre>automation:
|
||||
mode: proposal-only
|
||||
external_write_permitted: false
|
||||
policy_mutation_permitted: false
|
||||
declaration_mutation_permitted: false</pre>
|
||||
<p>Expired or unknown evidence does not silently inherit freshness and does not silently downgrade a level. It makes the uncertainty visible for governed review. Consumers that use the report as an admission gate may fail closed on high findings, but that is a separate owner decision.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>Exit behavior</h2>
|
||||
<p>The CLI emits a report conforming to <code>posture-feedback-report_v0.1.schema.json</code>. <code>--fail-on high</code> exits non-zero when at least one high-severity finding exists; <code>medium</code> includes medium and high; <code>low</code> includes every finding; <code>none</code> reports without a finding-based failure. Invalid declarations always exit non-zero.</p>
|
||||
</section><footer><span>netkingdom-posture-feedback-v0.1 · · proposed</span><span>net-kingdom · canon/standards/posture-feedback_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</span></footer></main></div></div></html>
|
||||
461
build/standards/security-layer-model/v0.7/index.html
Normal file
461
build/standards/security-layer-model/v0.7/index.html
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
|
||||
<meta name="policy-source-digest" content="8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b">
|
||||
<title>NetKingdom Security Layer Model v0.7</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-layer-model-v0.7</span> <span class="stat">accepted</span> <span>gate-house</span> <span>reviewed 2026-08-28</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Layer Model v0.7</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-layer-model_v0.7.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2026-11-28</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority and conformance</a></li><li><a href="#s3"><span class="n">3</span>The layers</a></li><li><a href="#s4"><span class="n">4</span>Layer catalog</a></li><li><a href="#s5"><span class="n">5</span>The binding rule</a></li><li><a href="#s6"><span class="n">6</span>One decision point</a></li><li><a href="#s7"><span class="n">7</span>Relationship to the Active Secrets Management Canon</a></li><li><a href="#s8"><span class="n">8</span>Vocabulary demarcations</a></li><li><a href="#s9"><span class="n">9</span>Capability assignment</a></li><li><a href="#s10"><span class="n">10</span>Changing layer</a></li><li><a href="#s11"><span class="n">11</span>Conformance</a></li><li><a href="#s12"><span class="n">12</span>The conformance loop</a></li><li><a href="#s13"><span class="n">13</span>Open gaps</a></li><li><a href="#s14"><span class="n">14</span>Adoption</a></li><li><a href="#s15"><span class="n">15</span>Change log</a></li><li><a href="#s16"><span class="n">16</span>Open questions</a></li><li><a href="#s17"><span class="n">17</span>Taxonomy artifacts</a></li><li><a href="#s18"><span class="n">18</span>Composition with the sibling standards</a></li><li><a href="#s20"><span class="n">20</span>The Railiance interaction boundary</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>This standard states how NetKingdom's IT-security estate is layered, and what each layer may and may not do. It answers one question:</p>
|
||||
<div class="rule-quote"><p><strong>Given a repository, which layer is it in, and what does that permit it to own?</strong></p></div>
|
||||
<p>The layers are distinguished by <strong>determinism</strong> and by <strong>the kind of artifact the layer produces</strong>, not by technical tier, deployment topology, or team.</p>
|
||||
<p>It is not an org chart, not a network model, not a deployment topology, and not a dependency graph. It does not assign work, and it does not replace any repository's boundary contract; it constrains what such a contract may claim.</p>
|
||||
<p><strong>What changed in v0.7.</strong> v0.6 announced a rule it never wrote: §1 and §15 said the standard separates human and agent principals inside Staff, and §3.4 was byte-identical to v0.5. <code>kings-guard</code> found it and put it correctly — <em>a rule stated about a standard in its own change log is not a rule</em>, which is §11's own principle turned on the standard. §3.4 is now written.</p>
|
||||
<p>The rest are collisions between rules written for the clean case: §6.4's first obligation forbade what its third obligation blesses, and its second forbade the session-bound allow §9.7.1 permits. §9.4's atomicity was described as closing a threat it does not close. §19 graded the document it lived in. And §20 records the interaction boundary with Railiance operations, on definitions from <code>railiance-master</code> rather than inference. §15 records the change list.</p>
|
||||
<p><strong>What changed in v0.6.</strong> An independent assessment against industry practice (<code>net-kingdom/history/2026-08-29-layering-standard-assessment.md</code>) found the model sound as a layering constitution and incomplete as a <em>self-healing</em> one: cognition, authority, and execution are specified, but the two verbs that close a healing loop — observe in production and actuate through a deterministic surface — are pending, and one is unstaffed. It also found the Engine layer untyped, so that "we need an engine for X" drifts toward "X now decides", and the enforcement point unnamed.</p>
|
||||
<p>v0.6 types the engines (§3.3), names the enforcement point (§6.4), replaces the containment assignment with an actuation surface held at zero (§9.2), separates human and agent principals inside Staff (§3.4), puts time into the model (§9.7), requires the Taxonomy artifacts that make §6.2 compileable rather than reviewable (§17), and composes the sibling standards it had only cited (§18). Section numbers below §14 are unchanged: the estate cites them.</p>
|
||||
<p><strong>What changed in v0.5.</strong> All four reviewing repositories returned findings on v0.4, and one contested a rule. §9.3 was wrong: it collapsed <em>engine reachable but degraded</em> with <em>engine not reachable at all</em>, and the second case has no evaluator in the path to express anything. §9.1 collapsed <em>no route exists</em> with <em>route exists under a declared gap</em>, which would have forced a false "pending" onto a production capability. §11 claimed mechanical checkability for a rule that cannot be checked in prose. §13 filed two opposite conformance states in one table and recorded proposed owners as owners. §9.6 needed the load-bearing distinction it implied but never drew. §15 records the change list.</p>
|
||||
<p><strong>What changed in v0.4.</strong> <code>audit-core</code> assented to the approval evidence half and corrected the rationale twice. v0.3 rested §9.4 on that repository's INTENT principle 6, which is an aspiration; the shipped bound in its <code>docs/integrity.md</code> is weaker and conditional. More consequentially, no append-only archive can prove <strong>omission at source</strong> — a suppressed revocation leaves the chain intact — which is now stated as an estate-wide doctrine constraint (§9.6) rather than left implicit. <code>audit-core</code> was also referenced as an owner in v0.3 without appearing in the §4 catalog at all; it is catalogued here, as an Engine, on its own declaration. §15 records the change list.</p>
|
||||
<p><strong>What changed in v0.3.</strong> Two engines were seeded to own concepts v0.2 recorded as unowned: <code>approval-engine</code> takes the approval object that §13 left homeless, and <code>maturity-engine</code> takes graded progression — closing a §9.1 defect in gate-house's own catalog claim, which asserted conformance review with no engine to act through. §15 records the change list. v0.3 is <strong>proposed</strong>: the two new engines are seeded by owner direction and have no other side to assent yet, and the evidence half of the approval split needs <code>audit-core</code>'s assent.</p>
|
||||
<p><strong>What changed in v0.2.</strong> v0.1 was assented to by all three repositories whose boundaries moved, and each returned a finding. v0.1 had one lane for a Staff repository that legitimately touches Tooling — read-only diagnostics — which is narrower than the estate as it actually stands, and a rule with no lane for a real sanctioned case is satisfied by relabelling rather than by closing the gap. v0.1 also catalogued a capability (§4, containment) that §5 forbade discharging, and applied its reconstructability test to engines but not to the doctrine gate-house feeds them. §15 records the full change list.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Authority and conformance</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Fact or rule</th><th>Authority</th></tr></thead><tbody><tr><td>The layers, their definitions, and the rules between them</td><td>This standard, owned by gate-house</td></tr><tr><td>Which layer a given repository is in</td><td>This standard, §4 catalog</td></tr><tr><td>What a repository owns within its layer</td><td>That repository's <code>INTENT.md</code> and boundary contract</td></tr><tr><td>Whether a specific request is permitted</td><td><code>access-engine</code> — never this standard</td></tr><tr><td>Whether a Tooling contact is sanctioned</td><td>The declaring repository, under the shapes in §5, reviewable by gate-house</td></tr><tr><td>Security doctrine and invariants</td><td>gate-house</td></tr><tr><td>Publication</td><td>net-kingdom canon</td></tr><tr><td>Whether an invariant is <em>watched in practice</em></td><td>the observing repository's own report — never this standard, and never §12's diagram</td></tr></tbody></table></div>
|
||||
<p>A repository conforms when its <code>INTENT.md</code> declares its layer, its claims fall within that layer's permissions (§3), and its Tooling contacts take one of the sanctioned shapes in §5 or are declared as gaps under §5.3.</p>
|
||||
<p><strong>No estate argument may cite observation that has not happened.</strong> §12 lists <code>kings-guard</code> against the loop's fourth step, and that repository has reported that it has never observed a real event. Until it reports otherwise, no assessment, review, or decision in this estate may treat an invariant as being watched in practice on the strength of the diagram. Lifted here from §12 so it cannot be lost in a summary.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>The layers</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Layer</th><th>Character</th><th>Produces</th><th>Deterministic</th></tr></thead><tbody><tr><td><strong>Taxonomy</strong></td><td>cross-cutting language</td><td>terms, semantic contracts, standards</td><td>n/a — describes</td></tr><tr><td><strong>Tooling</strong></td><td>infrastructure and state</td><td>data structures, persistence</td><td>yes</td></tr><tr><td><strong>Engines</strong></td><td>interfaces for a modeled concept</td><td>APIs, contracts</td><td>yes</td></tr><tr><td><strong>Staff</strong></td><td>management, operations, change, controlling</td><td>specifications, decisions, workplans, tasks</td><td><strong>no</strong></td></tr></tbody></table></div>
|
||||
<h3>3.1 Taxonomy</h3>
|
||||
<p>Cross-cutting language. Taxonomy repositories define terms and semantic contracts so the other layers interoperate without integration by interpretation. They own no runtime position and no state any layer depends on.</p>
|
||||
<p><code>info-tech-canon</code> holds ecosystem-wide semantic contracts. NetKingdom-specific security architecture — including this standard — is net-kingdom canon's.</p>
|
||||
<h3>3.2 Tooling</h3>
|
||||
<p>Deterministic infrastructure: data structures, persistence, and the consistent, performant, scalable keeping of state. Much of it is third-party.</p>
|
||||
<h3>3.3 Engines</h3>
|
||||
<p>Deterministic APIs for a modeled concept — a user, a tenant, a zone, a secret, an access rule. An engine's defining property is that <strong>the same authoritative input state yields the same result</strong>. Engines are where the estate's deterministic guarantees live, and therefore where every enforcement boundary MUST sit.</p>
|
||||
<p>A repository whose core function is inference or judgment fails this test by construction and is Staff, however much of its work happens at runtime.</p>
|
||||
<p><strong>Engines are typed.</strong> "Engine" is one layer but four roles, and collapsing them hides different failure modes. Every §4 Engine row carries a role:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Role</th><th>Meaning</th><th>Outage means</th></tr></thead><tbody><tr><td><strong>PDP</strong></td><td>renders the authorization decision — <code>access-engine</code>, and only it (§6)</td><td>consumer residue (§9.3)</td></tr><tr><td><strong>PIP</strong></td><td>supplies facts a decision consumes as claims — user, tenant, zone, approval, maturity</td><td>input degradation, engine's own fallback (§9.3)</td></tr><tr><td><strong>Evidence</strong></td><td>records what happened and proves integrity of what it holds — <code>audit-core</code></td><td>MUST NOT block the operation being recorded — a default, not a property; see below</td></tr><tr><td><strong>Lifecycle</strong></td><td>a deterministic API over Tooling it owns — <code>secrets-engine</code></td><td>the owning engine's failure semantics</td></tr></tbody></table></div>
|
||||
<p>The roles are why <em>"we need an engine for X"</em> does not mean <em>"X now decides"</em>. A new engine is a PIP unless this standard is amended to say otherwise, and §6 means it can never be a second PDP.</p>
|
||||
<p>The Evidence row's outage rule is an estate <strong>trade</strong>, not a property of evidence engines. Choosing availability there means accepting that a compromised source can suppress a record and that detection is the answer (§9.6). The opposite shape — <em>do not proceed unless an independent custodian already holds the record</em> — is the only one that puts evidence outside the actor's blast radius <strong>before</strong> the act. The estate has not needed it, so it is not ruled out by a table cell: an operation whose control genuinely requires independent recording before effect is a declared exception, raised when needed. Raised by <code>audit-core</code> against its own row.</p>
|
||||
<p>The industry vocabulary is deliberately mirrored here — PDP, PIP, PEP as in NIST ZTA and XACML — because it is how the estate talks to the outside and how a PEP is stopped from quietly becoming a PDP. The determinism cut in §3 stays primary where the two disagree.</p>
|
||||
<h3>3.4 Staff</h3>
|
||||
<p>Interactive and non-deterministic. Staff is the management layer: operations, change, innovation, and controlling. It works through agentic capability — assistants and autonomous agents — and its artifacts are specifications, decisions, workplans, and tasks.</p>
|
||||
<p>Staff repositories MUST NOT hold state that another layer depends on at runtime, and MUST NOT render or cache any decision an Engine is responsible for.</p>
|
||||
<p>Acting at runtime does not make a repository an Engine. Being agentic makes it Staff, and §5 governs how it acts.</p>
|
||||
<p><strong>Two principals, one layer.</strong> Humans and agents are both non-deterministic and both Staff, so they share the layer's permissions. They do not share blast radius. Four rules bind the agent principal specifically:</p>
|
||||
<ol><li><strong>No standing credential.</strong> An agent holds no long-lived credential of its own. Authority is issued per task, time-bounded under §9.7, and attributable to the principal on whose behalf it acts.</li><li><strong>Tool use is a conduit or an Engine API.</strong> An agent acts through §5.2 — the owner's tool under the caller's identity, presenting no authority of its own — or through an engine. There is no third route. Tool availability is not permission: a callable tool means the operation exists, not that this actor may invoke it.</li><li><strong>Agent memory is not a state plane.</strong> Agent memory, tool-call traces, and prompt caches are the agent's own. They MUST NOT become state another layer depends on at runtime unless catalogued as Tooling in §4, which subjects them to §5 like anything else, and to §5's sunset.</li><li><strong>Every agent action is reconstructable as the caller's action</strong>, bounded by §9.6 — the archive shows the actions it received, not that it received all of them.</li></ol>
|
||||
<p>Session semantics — session loops, tool policy, harness routing, model selection — are <strong>not</strong> governed here. They belong to <code>glas-harness</code> and its <code>rein-*</code> backends (§20). This standard governs what an agent may be authorized to do; <code>glas-harness</code> governs how an agent session is conducted. Rule 2 is the seam between them, and neither side may treat its own half as sufficient.</p>
|
||||
<p>v0.6 claimed these rules in its change log and did not write them. Found by <code>kings-guard</code>, which is the repository they bind hardest and which offered to assent to them sight-unseen.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Layer catalog</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Repository</th><th>Layer</th><th>Role</th><th>Owns</th></tr></thead><tbody><tr><td><code>info-tech-canon</code></td><td>Taxonomy</td><td>—</td><td>ecosystem-wide semantic contracts and terminology</td></tr><tr><td><code>net-kingdom</code></td><td>Taxonomy</td><td>—</td><td>NetKingdom standards of record; publication</td></tr><tr><td><code>key-cape</code></td><td>Tooling</td><td>—</td><td>packaged identity tooling; IAM profile; authentication</td></tr><tr><td><code>OpenBao</code></td><td>Tooling</td><td>—</td><td>secret storage, leases, PKI, dynamic secret engines</td></tr><tr><td><code>user-engine</code></td><td>Engine</td><td>PIP</td><td>users, accounts, memberships</td></tr><tr><td><code>tenant-engine</code></td><td>Engine</td><td>PIP</td><td>tenant-as-an-entity facts</td></tr><tr><td><code>zone-engine</code></td><td>Engine</td><td>PIP</td><td>zone identity and membership — offline reference conformance per its 2026-08-23 disposition</td></tr><tr><td><code>secrets-engine</code></td><td>Engine</td><td>Lifecycle</td><td>credential abstraction, custody, lifecycle</td></tr><tr><td><code>audit-core</code></td><td>Engine</td><td>Evidence</td><td>audit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6)</td></tr><tr><td><code>access-engine</code></td><td>Engine</td><td><strong>PDP</strong></td><td><strong>the policy decision</strong> — the only decision point (§6)</td></tr><tr><td><code>approval-engine</code></td><td>Engine</td><td>PIP</td><td>the approval object — durable, authenticated, consumable, atomically supersedable (§9.4)</td></tr><tr><td><code>maturity-engine</code></td><td>Engine</td><td>PIP</td><td>graded progression against declared criteria and evidence; the gap register; capability readiness (§9.5)</td></tr><tr><td><code>gate-house</code></td><td>Staff</td><td>—</td><td>security doctrine, authority context, curriculum; <strong>conformance review — through <code>maturity-engine</code> (§9.5)</strong></td></tr><tr><td><code>ops-mason</code></td><td>Staff</td><td>PEP-shaped</td><td>building and tearing down access routes and perimeters</td></tr><tr><td><code>ops-warden</code></td><td>Staff</td><td>PEP-shaped</td><td>operational access lanes, stewardship, runbooks; SSH certificate issuance — <strong>declared-gap</strong> (§9.1, §13)</td></tr><tr><td><code>kings-guard</code></td><td>Staff</td><td>—</td><td>adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation <strong>pending</strong>; <strong>proposes</strong> containment, which it does not own (§9.2)</td></tr><tr><td><code>whitehat-security</code></td><td>Staff</td><td>—</td><td>offensive validation</td></tr></tbody></table></div>
|
||||
<p>An <strong>actuation surface</strong> — reduce authority, require step-up, isolate a workload — is catalogued nowhere because it does not exist. See §9.2: it is an Engine concept held at zero, not a Staff capability.</p>
|
||||
<p><code>access-engine</code> is the ruled name for the repository currently called <code>flex-auth</code>; both denote the same authority until the governed rename completes. Execution conditions for that rename are recorded in its migration decision, not here.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>The binding rule</h2>
|
||||
<div class="rule-quote"><p><strong>Staff never touches Tooling directly. It acts only through Engine APIs.</strong></p></div>
|
||||
<p>A Staff repository MUST NOT hold a direct client for a Tooling-layer system — no direct database connection, no direct OpenBao client, no direct cluster mutation — outside the shapes below. This is the architectural form of <em>no privilege from cognition</em>, and it is deliberately mechanically checkable.</p>
|
||||
<p><strong>Scope.</strong> "Tooling-layer system" means a system catalogued as Tooling in §4. Infrastructure the estate runs but has not catalogued — the State Hub, <code>llm-connect</code>, and similar — is outside this rule, because a rule that silently covered them would put every Staff repository in undeclared violation on adoption day: they all write progress events. Such clients SHOULD be recorded in the repository's declaration as non-Tooling for completeness of the check, and the way to bring one under §5 is to catalogue it in §4, deliberately.</p>
|
||||
<p>Raised by <code>ops-warden</code>, which held clients for both and declined to resolve the scope question on gate-house's behalf.</p>
|
||||
<p><strong>The carve-out sunsets.</strong> It is a pressure valve, and a valve left open becomes a second persistence plane under the Staff layer — which §3.4 forbids in spirit. Three rules bound it: every non-Tooling client MUST be listed in the repository's declaration; an uncatalogued store that another layer <strong>reads</strong> MUST, within two review intervals, either be catalogued as Tooling in §4 or be declared a gap under §5.3; and a Staff-owned event bus or memory store MUST NOT become the estate's de facto state plane. Today's instances are the State Hub and <code>llm-connect</code>; tomorrow's are agent memory, tool-call traces, and prompt caches (§3.4).</p>
|
||||
<p>Three shapes are sanctioned. Everything else is a violation.</p>
|
||||
<h3>5.1 Read-only diagnostic observation</h3>
|
||||
<p>A Staff repository MAY read Tooling state for diagnostics where the owning engine exposes no equivalent. It MUST be declared in the repository's <code>INTENT.md</code>. It grants no write, and it is an engine gap to close, not a standing arrangement.</p>
|
||||
<h3>5.2 Conduit</h3>
|
||||
<p>A Staff repository MAY run the <strong>owner's</strong> tool under the <strong>caller's</strong> identity, supplying no authority of its own. The test is the supplied-authority property: the conduit MUST NOT present its own credential, MUST NOT widen what the caller could already do, and MUST be reconstructable as the caller's action in audit.</p>
|
||||
<p>A conduit that presents its own token is not a conduit; it is §5.3 or a violation. This shape MUST be declared, and the no-authority property SHOULD be covered by a test.</p>
|
||||
<p>The reconstructability requirement is an audit-dependent claim and is therefore bounded by §9.6: the archive shows the conduit actions it received, not that it received all of them.</p>
|
||||
<h3>5.3 Declared engine gap</h3>
|
||||
<p>Where a Staff repository must contact Tooling directly and no engine exposes the capability, it MUST declare the contact rather than take an exemption. A declared gap carries, machine-readably:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>capability</code></td><td>what the contact does</td></tr><tr><td><code>intended_owner</code></td><td>the engine that should own it</td></tr><tr><td><code>blocked_on</code></td><td>why it cannot move today</td></tr><tr><td><code>review</code></td><td>a date, not "when convenient"</td></tr></tbody></table></div>
|
||||
<p>A declared gap is <strong>tracked non-conformance</strong>, not conformance. It does not expire on its own and it is not a licence to add more. It exists because a rule offering no lane for a real sanctioned case gets satisfied by relabelling rather than by closing the gap — and a tracked gap is visible, whereas a relabelled one is not.</p>
|
||||
<p>Prior art: <code>ops-warden</code> runs equivalent machinery for delegated lanes (27 catalog entries carrying <code>delegation:</code>, queryable via <code>warden route gaps</code>), and has offered it as reusable.</p>
|
||||
<p><strong>No fourth "operator of third-party Tooling" shape.</strong> It has been proposed, on the argument that someone must operate <code>OpenBao</code> and every operational necessity otherwise looks like a gap. Declined: §5.3 already sanctions the operation while keeping it visible, and a clean "operator" shape would convert a tracked gap into a permanent allowance — the relabelling failure this standard exists to prevent. A permanent operational necessity is a declared gap whose review interval keeps returning, which is the correct amount of friction. If the review becomes ceremonial, that is an argument for closing the gap, not for renaming it.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>One decision point</h2>
|
||||
<p><code>access-engine</code> is the only policy decision point in NetKingdom. No other repository, in any layer, may render or cache authorization decisions.</p>
|
||||
<p>First ruled in <code>zone-engine/INTENT.md</code> §5 — <em>"flex-auth is the policy decision point. It stays the only one."</em> The failure mode, from the same source: <em>"It becomes a second decision point… it would arrive as a small convenience."</em></p>
|
||||
<h3>6.1 Compiled data that determines an outcome is still deciding</h3>
|
||||
<p>A registry, cache, or schema that resolves a result before the engine runs has decided early. Provenance MUST remain reconstructable from the engine's decision record.</p>
|
||||
<h3>6.2 Doctrine reaches the decision as an input, or it is not applied</h3>
|
||||
<p>This rule binds gate-house on the same terms. <strong>An authority ceiling, mandate constraint, or operating-mode restriction that determines an outcome MUST reach the decision either as an input claim on the request or as a rule in the versioned policy package</strong>, so that its application is reconstructable from the decision record.</p>
|
||||
<p>Doctrine that influences outcomes by any other route is a second decision point wearing an author's hat. This is not a limit on gate-house's authorship; it is what keeps that authorship auditable at decision time.</p>
|
||||
<h3>6.3 No Staff repository may host a decision point</h3>
|
||||
<p>A deterministic authority boundary inside a non-deterministic layer contradicts the invariant the estate is built on. gate-house was re-cut on this ground.</p>
|
||||
<h3>6.4 The enforcement point</h3>
|
||||
<p>The standard has been precise about the decision and silent about the gate. A decision that nothing refuses to proceed without is advice.</p>
|
||||
<p>A <strong>PEP</strong> is any runtime that causes a protected side effect. It is a <em>shape</em>, not a repository: <code>ops-warden</code> issuing a certificate, <code>ops-mason</code> opening a route, and any protected system acting on a verdict are all PEP-shaped. Being PEP-shaped does not move a repository out of its layer.</p>
|
||||
<p>Four obligations, and they are normative:</p>
|
||||
<ol><li><strong>No side effect without a decision record, or a recorded stance.</strong> A PEP MUST NOT perform the protected action unless it holds a decision from <code>access-engine</code> identifying the request it was rendered for, <strong>or</strong> its declared §9.3 stance for the applicable scope permits proceeding without one <strong>and the application of that stance is recorded in place of the decision</strong>. The second limb is stricter than silence, not looser: a fail-open result is metadata, never an absent record. <code>ops-warden</code> <code>ca.py</code> writes the zone, the failure mode, and a decision id present only where a decision was rendered.</li></ol>
|
||||
<p>v0.6's unqualified form made the shipped stance §9.3 sanctions into a violation — the same defect as v0.5's §9.1, a rule written for the clean case producing a false result on the adjacent case already sanctioned elsewhere. Raised by <code>ops-warden</code>, which is the reference shape for limb two.</p>
|
||||
<ol><li><strong>No recaching of the verdict beyond its own binding.</strong> A stored verdict replayed <strong>outside the decision's stated binding and lifetime</strong> is a second decision point deciding early (§6.1). Within them it is the decision being used as issued — a session-bound allow under §9.7.1 is used across later requests by construction, and v0.6 forbade what §9.7.1 permits.</li></ol>
|
||||
<p>The test is mechanical, not a matter of implementer judgement: replay is permitted <strong>iff the canonical request digest matches and the decision's lifetime holds</strong>. <code>access-engine</code> computes that digest over normalised subject, action, resource, and context, and it is already in every decision binding. A retry after a transport failure is therefore the same request; a different resource is not.</p>
|
||||
<p><strong>Negative caching is permitted, narrowly.</strong> A cached DENY cannot manufacture authority — §8's asymmetry holds — and protects against retry storms. It is permitted where the refusal is itself recorded against the request that was refused (obligation 4), and where the cache lifetime is declared alongside the stance map. A stale deny is an availability failure and will be misdiagnosed as a policy one, so it must be visible as what it is. Ruled explicitly because it is the first thing an implementer under load reaches for. Raised by <code>access-engine</code>.</p>
|
||||
<ol><li><strong>A declared unreachable-engine stance</strong> (§9.3): total, per zone or equivalent scope, no implicit default, no per-call discretion, published rather than held in code comments or in a dataclass default. <code>ops-warden</code> <code>ADR-0009</code> and its <code>pep-stance.yaml</code> are the reference shape.</li></ol>
|
||||
<p>The published map MUST equal the shipped behaviour, and that equality SHOULD be asserted by a test. A published map free to drift from the code is worse than none, because it invites reliance it cannot support. Raised by <code>ops-warden</code>, which found its own map unpublished while being cited as the reference for this obligation.</p>
|
||||
<ol><li><strong>Reconstructability</strong>, bounded by §9.6.</li></ol>
|
||||
<p>Every PEP-shaped consumer MUST publish its stance map at a path named in its layer declaration, and those maps MUST be inventoried in the §13.1 register until <code>maturity-engine</code> can hold them. §9.3 is otherwise a ruling with no register behind it, and <em>"<code>z0</code>–<code>z2</code> and unknown fail open"</em> becomes the estate's real policy without anyone having compiled it into a versioned package.</p>
|
||||
<p>v0.6 named a register that did not exist — a requirement whose register is missing is a capability catalogued without a surface, by §9.1's own logic. Raised independently by <code>ops-warden</code> and <code>access-engine</code>; §13.1 now exists, and its first inventory has one row, which is itself the finding.</p>
|
||||
<p>Raised by the 2026-08-29 independent assessment: NIST ZTA splits decide from enforce, and this standard had only the first half.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Relationship to the Active Secrets Management Canon</h2>
|
||||
<pre>Staff interactive, non-deterministic ≈ Cognitive Plane
|
||||
Engines deterministic APIs ≈ Authority Plane
|
||||
Tooling deterministic state ≈ Execution Plane
|
||||
Taxonomy cross-cutting language</pre>
|
||||
<p><em>Cognition proposes. Authority disposes. Infrastructure executes.</em> is therefore NetKingdom's layering rule, not only its security maxim. §5 and §6 are that principle applied to repositories rather than to requests.</p>
|
||||
</section>
|
||||
<section id="s8"><h2><span class="sn">08</span>Vocabulary demarcations</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Term</th><th>Belongs to</th><th>Not</th></tr></thead><tbody><tr><td><strong>access lane</strong></td><td>ops-warden, ops-mason (Staff) — how a worker reaches a host</td><td>the decision whether they may</td></tr><tr><td><strong>access rule</strong></td><td>access-engine (Engine) — whether an actor may act</td><td>the route by which they arrive</td></tr><tr><td><strong>control plane</strong></td><td>Engine layer</td><td>a Staff repository's self-description</td></tr><tr><td><strong>doctrine</strong></td><td>gate-house</td><td>a lane owner's runbook</td></tr><tr><td><strong>runbook</strong></td><td>the Staff repository stewarding the lane</td><td>a substitute for doctrine</td></tr><tr><td><strong>posture</strong></td><td>kings-guard publishes; gate-house defines its authority meaning; access-engine renders it</td><td>a privilege source</td></tr></tbody></table></div>
|
||||
<p>Posture carries an asymmetry that MUST hold: adaptive systems may reduce authority, require step-up, or request containment. They MUST NOT probabilistically manufacture additional authority.</p>
|
||||
<p>The asymmetry is what bounds the damage when observation is incomplete (§9.6): suppressed evidence can only prevent a tightening that should have happened, never engineer a loosening. That is an argument for keeping it absolute rather than situational.</p>
|
||||
</section>
|
||||
<section id="s9"><h2><span class="sn">09</span>Capability assignment</h2>
|
||||
<h3>9.1 The catalog may not assign what the rules forbid discharging</h3>
|
||||
<p>A Staff repository MUST NOT be catalogued in §4 as owning a capability it cannot discharge under these rules. Two marks distinguish the two ways that happens, and they are not interchangeable:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Mark</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>pending</strong></td><td>No route exists. No engine exposes the capability, the repository makes no Tooling contact, and the capability is <strong>zero</strong> — not degraded.</td></tr><tr><td><strong>declared-gap</strong></td><td>A route exists through a §5.3 declared gap. The capability <strong>works</strong> and is tracked, with an intended owner and a review date in §13.</td></tr></tbody></table></div>
|
||||
<p>v0.4 had only <code>pending</code>, which forced a false choice. <code>ops-warden</code> holds production-verified SSH certificate issuance through a declared OpenBao contact; marking it <code>pending</code> would have told readers the repository does not do the one thing it demonstrably does daily, while leaving it unmarked left §4 disagreeing with §13. Neither is acceptable, and the defect was in this section rather than in the catalog.</p>
|
||||
<p><code>pending</code> was written for <code>kings-guard</code>'s containment — no route, capability zero — and remains correct there. <code>declared-gap</code> is the case §5.3 was added to sanction. Raised by <code>ops-warden</code>.</p>
|
||||
<p>Both marks apply per capability, not per repository. A repository may hold one capability outright, another under a declared gap, and a third pending.</p>
|
||||
<h3>9.2 Actuation does not exist, and containment is not Staff's to own</h3>
|
||||
<p>Self-healing needs four verbs: observe, evaluate, decide, actuate. Observation is <code>kings-guard</code> and is unstaffed (§12). Evaluation is <code>maturity-engine</code> and is seeded. Decision is <code>access-engine</code> and works. <strong>Actuation has no surface at all</strong>, and a model with no actuation surface describes a diagnosis machine rather than a healing one.</p>
|
||||
<p>v0.5 marked containment <code>pending</code> against <code>kings-guard</code>, which was the right mark on the wrong repository. Containment is not a Staff capability that happens to lack a route: <strong>reduce authority, require step-up, isolate a workload</strong> are authority-changing operations, and under §6 an authority-changing operation is rendered by an Engine and enforced by a PEP (§6.4). A Staff repository proposes containment; it never performs it.</p>
|
||||
<p>The <strong>actuation surface</strong> is therefore an Engine concept — likely a small surface on <code>access-engine</code> together with runtime PEPs — carrying the same reconstructability rules as any other decision: a containment action is a decision record, not a side channel.</p>
|
||||
<p>It is <strong>unowned and held at zero</strong>. <code>access-engine</code> is recorded in §13 as a <em>proposed</em> owner and has explicitly not reviewed it (<code>FLEX-DEC-2026-002</code>). No repository may be catalogued as owning containment until the surface exists — §9.1 applied to the estate's most operationally tempting gap, and the standard's own medicine.</p>
|
||||
<p>Until then <code>kings-guard</code> proposes and judges, its containment claim stays at zero rather than degraded, and no argument may assume the estate can contain anything automatically.</p>
|
||||
<h3>9.3 Degraded mode: two failure cases, two owners</h3>
|
||||
<p>v0.4 collapsed two failures into one rule. They have different owners because one has an evaluator in the path and the other does not.</p>
|
||||
<p><strong>Input degradation — the engine's.</strong> Where <code>access-engine</code> is reachable but cannot reach its own inputs, the deterministic <em>fail to reduced authority</em> default belongs to the engine. This keeps the decision at the decision point and keeps the fallback deterministic, which a Staff-layer fallback could never be.</p>
|
||||
<p><strong>Engine unreachable — necessarily the consumer's.</strong> Where <code>access-engine</code> is not reachable at all, it applies nothing, because it is not running. Whatever happens next is the consumer's behaviour by construction: fail-open is not expressible by a policy decision point, since there is no evaluator in the path to express it. A standard that assigns this to the engine assigns it to nobody.</p>
|
||||
<p>The consumer's residue is bounded rather than free. A protected system MUST declare its unreachable-engine stance ahead of time, per zone or equivalent scope, and that stance MUST be auditable and total — no implicit default, no per-call discretion. <code>ops-warden</code> <code>ADR-0009</code> already satisfies this: a total per-zone map, open for <code>z0</code>–<code>z2</code> and unknown, closed for <code>z3-critical</code>, replacing the global <code>policy.enabled</code> / <code>policy.fail_closed</code> switches it superseded.</p>
|
||||
<p><strong>Unchanged: engine-unavailable is not grounds for a Staff break-glass path.</strong> The distinction is whether an engine is there to ask. A bypass around a <em>reachable</em> engine is a second decision point, and an incident is when an attacker most wants that shortcut. A consumer choosing its declared behaviour when there is no engine to ask is not a bypass; it is the only thing left.</p>
|
||||
<p>Contested by <code>flex-auth</code> (<code>FLEX-DEC-2026-002</code>), which has held since 2026-08-19 that fail-open is not expressible by a PDP, and which noted v0.4 collided with shipped behaviour in a repository that had assented to this standard.</p>
|
||||
<h3>9.4 Approvals are an engine concept, not a Staff or audit concern</h3>
|
||||
<p>The approval object — durable, authenticated entries, distinct-approver counting, atomic supersession, single consumption, revocation without holder cooperation — is owned by <code>approval-engine</code>.</p>
|
||||
<p>It is not Staff's: §3.4 forbids Staff holding state another layer depends on at runtime. It is not the decision point's: an evaluator that owns the object it evaluates is self-dealing. It is not the audit fabric's: an approval needs mutable, in-path, current-state semantics, and an append-only archive is built for the opposite property.</p>
|
||||
<p><code>access-engine</code> consumes approvals as <strong>input claims</strong> under §6.2 and never mutates them. Every issuance, use, supersession, and revocation is emitted to <code>audit-core</code>: the operative state and the evidence record are different artifacts with different owners.</p>
|
||||
<p>The evidence guarantee is bounded, and the bound is <code>audit-core</code>'s <code>docs/integrity.md</code>, not its INTENT principle 6. An in-database hash chain detects a rewritten payload only if the attacker does not also recompute the suffix — which a database owner can. Detection against that class requires the external chain-head attestation, and even with it the store is not WORM, object lock, or archival custody. <code>tamper_evidence</code> is therefore conditional on live preconditions, not a property of the store at rest, and approval events receive exactly the guarantee every other source receives.</p>
|
||||
<p><strong>Emission atomicity is <code>approval-engine</code>'s obligation.</strong> An approval MUST NOT be issued, consumed, superseded, or revoked without the corresponding event being durably queued in the same transaction.</p>
|
||||
<p><strong>The queue MUST be local.</strong> The durable queue MUST live in <code>approval-engine</code>'s own transactional store, and <strong>no synchronous dependency on <code>audit-core</code> may sit inside the state-change transaction</strong>. With a genuine local outbox, fail-closed triggers only when <code>approval-engine</code>'s own store is unavailable — where the change could not have been recorded anyway — and an <code>audit-core</code> outage does not block a revocation. Satisfying the requirement by emitting synchronously to <code>audit-core</code> inside the transaction is also atomic, and turns an audit outage into an inability to revoke: the operation least tolerable to block during an incident, and the same coupling this section rejects for reads. Raised by <code>audit-core</code>. <code>audit-core</code> reports what it received and does not imply it is everything that happened; without atomic emission the evidence half is silently incomplete and nothing detects the gap. This is a condition of <code>audit-core</code>'s assent (<code>AUDIT-IN-0001</code>) and belongs in <code>approval-engine</code>'s contract before the evidence half is treated as load-bearing.</p>
|
||||
<p><code>audit-core</code> MUST NOT expose an approval-validity query. Records, yes; a verdict on whether an approval is still valid, never — a consumer branching on that answer would route an authorization decision through the audit fabric, which is what this section exists to prevent. Callers needing current state ask <code>approval-engine</code>.</p>
|
||||
<h3>9.5 Graded progression is an engine concept</h3>
|
||||
<p>Maturity — how far a subject has progressed against declared criteria and submitted evidence — is owned by <code>maturity-engine</code>. Given the same criteria and the same evidence it MUST return the same level; that determinism is what makes it an Engine rather than an opinion.</p>
|
||||
<p>The division with Staff: <strong>gate-house judges and proposes; maturity-engine computes and remembers.</strong> Interpretation is inference and stays Staff. A criterion that cannot be evaluated by rule is not yet a criterion.</p>
|
||||
<p>This closes a defect in v0.2's own catalog: <code>gate-house</code> was assigned conformance review with no engine to act through, which is exactly the §9.1 problem raised against the containment claim. Staff acts only through Engine APIs, including gate-house.</p>
|
||||
<p><strong>A maturity level MUST NOT be compiled into registry content.</strong> Until <code>access-engine</code>'s decision provenance carries a registry-snapshot digest — a gap it self-declared in §13 — a level reaching a decision through the registry is not reconstructable from the decision record. Levels arrive as request claims or as versioned policy rules. Same constraint, and same reason, as zone stance.</p>
|
||||
<p><strong>A maturity level MUST NOT gate a decision directly.</strong> Under §6.1, compiled data that determines an outcome is still deciding. If a level determines whether an action is permitted, it MUST reach <code>access-engine</code> as an input claim or a versioned policy rule under §6.2, never by a consumer branching on a fetched level.</p>
|
||||
<p>Approvals and maturity are deliberate opposites — a closed binary state machine against an open graded ladder — and neither engine may drift toward the other.</p>
|
||||
<h3>9.6 Evidence proves alteration and truncation, not omission at source</h3>
|
||||
<p>An append-only archive with a verified hash chain proves that records were not <strong>altered or truncated after arrival</strong>. It cannot prove that a record was never sent. Against a compromised or buggy source, a suppressed event leaves the chain perfectly intact and verification reports intact.</p>
|
||||
<p>This bound is estate-wide. Statements of the form <em>"the audit record proves it happened"</em> are unsound; the sound form is <em>"the archive proves the records it holds were not altered or truncated after arrival"</em>. Its mirror is equally unsound: <strong>absence of a record is not evidence of non-occurrence</strong>, and no control may read it as such.</p>
|
||||
<p><strong>Load-bearing versus attributive evidence.</strong> The atomicity obligation attaches to the first, not to both:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Kind</th><th>Test</th><th>Obligation</th></tr></thead><tbody><tr><td><span class="kind ">Load-bearing</span></td><td>a control's soundness depends on the event being present or absent — an approval revocation, a containment action, a denial</td><td>emission MUST be atomic with the state change (§9.4)</td></tr><tr><td><span class="kind ">Attributive</span></td><td>the event supports forensic reconstruction and attribution, and no control branches on its presence</td><td>atomicity SHOULD be sought; where it is deliberately traded away, the trade MUST be declared and completeness MUST NOT be claimed</td></tr></tbody></table></div>
|
||||
<p>Where a repository deliberately makes emission non-atomic — <code>ops-warden</code>'s <code># audit must not block signing</code> is the estate's live example, chosen so that an audit-store failure cannot remove production host access — the trade is legitimate for attributive evidence, MUST be declared where the trail is documented, and MUST NOT be described in terms that imply completeness. The availability argument is real in both directions: making it atomic gives the estate's operational access lane a new dependency on its own evidence store.</p>
|
||||
<p><strong>Consequence for adaptive systems.</strong> Suppression does not degrade observation neutrally, it biases it optimistic, and silently: an event never emitted is never evaluated, so no finding is raised and the last posture stands. A confidence score computed from the richness of the record in hand cannot express doubt about the completeness of the stream — a well-formed observation from a 90%-suppressed stream scores high. That is this section's failure reproduced one layer up, in the consumer.</p>
|
||||
<p>Two things follow.</p>
|
||||
<p><strong>Which control covers which threat.</strong> v0.6 read as though emission atomicity closed this section's opening sentence. It does not, and the decomposition is owed to the reader:</p>
|
||||
<div class="scroll"><table class="tm"><thead><tr><th>Threat</th><th>Covered by</th><th>When</th></tr></thead><tbody><tr><td><strong>Accidental omission</strong> — process dies between mutation and emit</td><td class="">emission atomicity, local outbox (§9.4)</td><td class="">prevented</td></tr><tr><td><strong>Adversarial omission</strong> — a compromised source declines to insert, deletes before drain, or drains to nowhere</td><td class="">cadence and reconciliation</td><td class=""><strong>detected, after the fact</strong></td></tr><tr><td>Adversarial omission at a compromised source</td><td class="">—</td><td class=""><strong>nothing in this model prevents it</strong></td></tr></tbody></table></div>
|
||||
<p>The outbox sits inside the blast radius of the component whose compromise this section posits, so it makes emission atomic against crash and partial failure and nothing more. That residual is real and is stated rather than implied. Raised by <code>audit-core</code>, correcting a remedy it had itself proposed.</p>
|
||||
<ol><li><strong>The §8 asymmetry bounds the damage, and this is its clearest payoff.</strong> Because an adaptive system may only reduce authority and never manufacture it, suppression can only prevent a tightening that should have happened. It cannot be used to engineer a loosening. The harm is a missed reduction, not an invented privilege — which is an argument for keeping the asymmetry absolute.</li><li><strong>Silence is a signal, and for load-bearing evidence it is the only control in its class.</strong> A source of <strong>attributive</strong> evidence SHOULD declare an expected emission cadence; a source of <strong>load-bearing</strong> evidence <strong>MUST</strong>. A drop below the declared rate is a finding in its own right — the stream observed, not only its contents — and needs no Tooling contact, because the source publishes its own stream.</li></ol>
|
||||
<p><strong>Rate monitoring is the wrong form for rare events</strong>, and rare is exactly where the stakes are highest: the most valuable event to suppress is the negative one, and revocations, denials, and containment actions are infrequent by nature. A source emitting a handful of revocations a month has no rate to drop below, and suppression is indistinguishable from a quiet month. For <strong>low-volume load-bearing classes</strong> the required form is therefore <strong>positive reconciliation or a heartbeat</strong>: compare the source's own state transitions against the evidence engine's event count per class and treat divergence as a finding, or assert <em>nothing to report</em> as a signed positive claim that can itself go missing. Rate monitoring never produces a claim that can be missing; a heartbeat does. <code>GH-WP-0002-T04</code> is the reference instance. Raised by <code>audit-core</code>.</p>
|
||||
<p>Raised by <code>audit-core</code> against its own principle; extended by <code>kings-guard</code> from its own evaluator and confidence model.</p>
|
||||
<h3>9.7 Decisions have a lifetime</h3>
|
||||
<p>A single decision point deciding on stale claims is a single decision point deciding wrongly. The model has had no temporal law, and the approval race in §16 was its first symptom.</p>
|
||||
<ol><li><strong>Every allow has an explicit lifetime</strong> — a TTL, or a binding to a session or obligation that ends. An allow with no stated end is a standing grant, and standing grants are what this estate exists to remove.</li><li><strong>Revocation and supersession have a visibility deadline</strong>, and its shape differs by role. A <strong>PEP</strong> has one boundary and MUST state one deadline. A <strong>PDP</strong> MUST state a deadline <strong>per input class</strong>, because a decision is a join over sources with unrelated refresh behaviour — approval-claim freshness, registry snapshot cadence, policy package activation, directory ETag. A single number at a PDP is either a fiction or the worst case, and the worst case is the slowest and least visible input. "Eventually" is not a stance; an unstated deadline is an unbounded replay window.</li></ol>
|
||||
<p>A consequence worth naming: a stated deadline for a fact carried by a registry snapshot is unfalsifiable while decision provenance holds no snapshot digest, since nobody can determine afterwards which snapshot a decision read. The deadline and the digest are one gap seen from two sides, which promotes <code>access-engine</code>'s self-declared provenance gap (§13) from housekeeping to a conformance prerequisite. Raised by <code>access-engine</code> against its own backlog.</p>
|
||||
<ol><li><strong>Consumption is a state change, never an inference.</strong> An approval is consumed by a mutation in <code>approval-engine</code> (§9.4). It MUST NOT be inferred from the existence of a decision record — the decision precedes the action and the action precedes consumption, so a decision record proves an intent to act, not an act.</li><li><strong>Three failure modes are named, and each needs an owner</strong>: an allow rendered then never consumed; a double consumption by racing callers; consumption after the authorized action has already failed. Neither engine closes these alone. Recorded in §16 and <code>GH-WP-0002-T06</code>.</li></ol>
|
||||
<h3>9.8 Partition is not one-dimensional</h3>
|
||||
<p>§9.3 handles <em>engine unreachable</em>. A real estate spends most of its incident time in the band between reachable and gone: partial PIP reachability, clock skew across a decision and its enforcement, and two consumers with different declared stances seeing different worlds at the same moment.</p>
|
||||
<p>Two rules hold today, and the rest is open (§16). A PEP MUST resolve its own stance from its declared map without consulting another consumer — divergent views are expected and are not a coordination problem to be solved at enforcement time. And where clock skew could extend a lifetime under §9.7, the shorter reading governs.</p>
|
||||
</section>
|
||||
<section id="s10"><h2><span class="sn">10</span>Changing layer</h2>
|
||||
<p>A repository's layer is not permanent. <code>zone-engine</code> changed layer in practice when its runtime hypothesis was falsified.</p>
|
||||
<p>A layer change MUST be recorded as a decision, MUST update the repository's <code>INTENT.md</code>, and MUST obtain assent from the repositories whose boundaries move. A repository MUST NOT acquire a new layer's permissions by gradual practice.</p>
|
||||
<p>"No gradual practice" needs a check rather than a sentence. A layer change MUST carry six artifacts, written from the <code>zone-engine</code> case that the procedure should have been derived from in the first place:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Artifact</th><th>Why</th></tr></thead><tbody><tr><td>before/after <code>INTENT.md</code></td><td>the declaration is the conformance surface (§11)</td></tr><tr><td>client inventory</td><td>what the repository holds against Tooling, before and after</td></tr><tr><td>gap inventory</td><td>which §5.3 gaps close, open, or transfer</td></tr><tr><td>assent list</td><td>every repository whose boundary moves</td></tr><tr><td>state-migration decision</td><td>what happens to live state and to consumers reading it</td></tr><tr><td>permission freeze</td><td>no new permissions of the target layer are exercised until the cut completes</td></tr></tbody></table></div>
|
||||
<p>The freeze is the one that makes the rule checkable: a repository mid-change holds its old permissions, not the union of both.</p>
|
||||
</section>
|
||||
<section id="s11"><h2><span class="sn">11</span>Conformance</h2>
|
||||
<p>Conformance has four states, and the distinction between the last two is the point:</p>
|
||||
<div class="scroll"><table><thead><tr><th>State</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>Conforming</strong></td><td>no Tooling contact, or only §5.1/§5.2 shapes, declared</td></tr><tr><td><strong>Blocked-clean</strong></td><td>the capability does not exist because no engine exposes it, and the repository makes <strong>no</strong> Tooling contact — §9.1 <code>pending</code>, and not a non-conformance</td></tr><tr><td><strong>Declared gap</strong></td><td>a §5.3 contact with owner, blocker, and review date — tracked non-conformance</td></tr><tr><td><strong>Undeclared violation</strong></td><td>anything else — a finding</td></tr></tbody></table></div>
|
||||
<p><strong>Blocked-clean is not a lesser state than conforming.</strong> A repository that declined a break-glass path and left a capability at zero has complied at cost; a repository that quietly opened a direct client and declared nothing has not. Any downstream scoring — <code>maturity-engine</code> included (§9.5) — MUST NOT rank the first below the second. Raised by <code>kings-guard</code>, whose three gaps are all of this kind and which would otherwise have been graded down three times for having taken the standard seriously.</p>
|
||||
<p><strong>Who must declare.</strong> A repository the estate authors declares its layer in its own <code>INTENT.md</code>. For a component the estate catalogues but does not author — third-party or vendored, such as <code>OpenBao</code> — the §4 catalog row <strong>is</strong> the declaration, and no <code>INTENT.md</code> obligation attaches. A rule that assigns an obligation the holder cannot discharge is the §9.1 defect applied to conformance rather than capability.</p>
|
||||
<p>A layer stated <em>about</em> a repository by another repository is not a declaration. Review notes, catalog rows, and correspondence record an intent to adopt; only the repository's own file conforms.</p>
|
||||
<p><strong>Declaration form.</strong> Because prose cannot distinguish a declaration from a transcribed review, a declaration MUST carry a machine-readable form: a <code>layer:</code> key in the <code>INTENT.md</code> frontmatter, or an equivalent declaration file. Without it this section asserts a property it cannot deliver — the defect this standard has now corrected three times elsewhere. <code>ops-warden</code> has implemented a reference form (<code>layer.yaml</code>, a conformance script, and a test covering the §5.2 no-authority property) and offered it to the repositories that have yet to declare. Raised by <code>audit-core</code>, which noted that <code>flex-auth</code>'s conforming declaration is legible as one only by following its decision trail.</p>
|
||||
<p>Mechanically checkable:</p>
|
||||
<ul><li>every estate-authored repository in §4 carries a machine-readable layer declaration;</li><li>every direct Tooling client in a Staff repository maps to a declared §5.1, §5.2, or §5.3 entry, and non-Tooling clients are recorded so the check is total;</li><li>no repository other than <code>access-engine</code> exposes an authorization decision surface;</li><li>no §4 capability is catalogued without an engine surface, a <code>pending</code> mark, or a <code>declared-gap</code> mark.</li></ul>
|
||||
<p>Requires review: whether claims stay inside layer permissions; whether compiled or cached data has become an early decision (§6.1); whether doctrine is reaching decisions as declared inputs (§6.2); whether the §8 vocabulary is used correctly.</p>
|
||||
</section>
|
||||
<section id="s12"><h2><span class="sn">12</span>The conformance loop</h2>
|
||||
<p>Doctrine no engine implements is fiction. The loop is normative, not aspirational:</p>
|
||||
<pre>gate-house asserts an invariant
|
||||
→ the engines implement it, or declare a gap
|
||||
→ whitehat-security tries to break it
|
||||
→ kings-guard observes it in operation
|
||||
→ findings return to gate-house as doctrine change</pre>
|
||||
<p>A finding that a rule is unsatisfiable is a <strong>success</strong> of this loop, not a failure of the reporting repository. Four of this standard's five versions exist because a reviewing repository used it.</p>
|
||||
<p><strong>Step four is currently aspiration.</strong> <code>kings-guard</code> has disclosed that it has never observed anything in operation: the pilot is specified and scaffolded, every input is a hand-built fixture, and no test has met a real event. Until it reports otherwise, no argument in this estate may assume an invariant is being watched in practice because §12 lists a repository against that step.</p>
|
||||
</section>
|
||||
<section id="s13"><h2><span class="sn">13</span>Open gaps</h2>
|
||||
<p>Two different things are recorded here, and they are opposite conformance states (§11). A <strong>declared contact</strong> means the repository touches Tooling because no engine exposes the capability. An <strong>unowned capability</strong> means no route exists and the repository makes no contact at all. Reading them as one list would grade restraint as though it were non-conformance.</p>
|
||||
<p>An <code>intended owner</code> is a <strong>proposal to</strong> the named repository, not an assignment <strong>onto</strong> it. §2 keeps ownership in the repository's own <code>INTENT.md</code>, so the register distinguishes proposed from assented.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Gap</th><th>State</th><th>Declared by</th><th>Owner</th><th>Owner status</th></tr></thead><tbody><tr><td>SSH-CA signing write (<code>VaultCA</code>, <code>bao kv put</code>)</td><td>declared-contact</td><td>ops-warden</td><td>secrets-engine</td><td>proposed</td></tr><tr><td>Authentication / assurance evidence</td><td>unowned-capability</td><td>kings-guard</td><td>identity layer + audit-core</td><td><strong>access-engine declined</strong></td></tr><tr><td>Secret-use evidence</td><td>unowned-capability</td><td>kings-guard</td><td>secrets-engine</td><td>proposed</td></tr><tr><td>Actuation / containment surface</td><td>unowned-capability</td><td><strong>gate-house (estate-wide)</strong></td><td>access-engine + runtime engines</td><td>proposed</td></tr><tr><td>Identity and secret observation</td><td>unowned-capability</td><td>kings-guard</td><td>as above</td><td>proposed</td></tr><tr><td>Stance-map register had no implementation</td><td>declared-contact</td><td>ops-warden, access-engine</td><td>gate-house</td><td>resolved in §13.1</td></tr><tr><td>Registry-snapshot digest in decision provenance</td><td>declared-contact</td><td>flex-auth</td><td>flex-auth</td><td>self-declared</td></tr><tr><td>Approval storage and lifecycle</td><td>—</td><td>flex-auth</td><td>approval-engine</td><td>assigned (§9.4)</td></tr><tr><td>Approval evidence</td><td>—</td><td>gate-house</td><td>audit-core</td><td><strong>assented</strong> (<code>AUDIT-IN-0001</code>)</td></tr><tr><td>Approval evidence custody stronger than the shipped bound — WORM, object lock, transparency log</td><td>unowned-capability</td><td>audit-core</td><td>—</td><td>unassigned</td></tr><tr><td>Emission atomicity for approval state changes</td><td>—</td><td>audit-core</td><td>approval-engine</td><td>assigned (§9.4)</td></tr><tr><td>Non-atomic audit emission on the SSH signing lane</td><td>declared-contact</td><td>ops-warden</td><td>ops-warden</td><td>self-declared, attributive (§9.6)</td></tr></tbody></table></div>
|
||||
<p>The actuation row is no longer attributed to <code>kings-guard</code>. §9.2 ruled that containment is not a Staff capability lacking a route, so <code>kings-guard</code> is not its declarer: the gap is estate-wide and blocks every repository's ability to act. Raised by <code>kings-guard</code>, which asked not to carry a row for a capability the standard had just ruled was never theirs.</p>
|
||||
<p><code>access-engine</code> declined authentication and assurance evidence (<code>FLEX-DEC-2026-002</code>): it consumes assurance claims as input and never redefines them, so evidence of authentication belongs to the identity layer and <code>audit-core</code>. It owns evidence of the decision, which it already emits. The containment surface is recorded as proposed and remains <code>pending</code> under §9.2.</p>
|
||||
<p>Whether approvals warrant custody stronger than every other source is doctrine work not yet done; until it is, approval evidence carries the same guarantee as any other source and §9.6 bounds what may be claimed from it.</p>
|
||||
<p><strong>What is normative here, and what is a snapshot.</strong> Three rules are part of this standard and survive wherever the register lives:</p>
|
||||
<ol><li>the two marks — <code>pending</code> and <code>declared-gap</code> (§9.1);</li><li>the owner-status rule — a proposed owner is not an assigned one (§2);</li><li>the scoring rule — <code>blocked-clean</code> MUST NOT rank below conforming (§11).</li></ol>
|
||||
<p><strong>The table above is a snapshot, not statute.</strong> It moves into <code>maturity-engine</code> as soon as that engine can store state, and the <code>state</code> and owner-status columns MUST survive the migration. A standard that is also a backlog keeps attracting findings that belong in the register, and its review interval is far slower than the register's real rate of change.</p>
|
||||
<h3>13.1 PEP stance-map register</h3>
|
||||
<p>Every PEP-shaped consumer publishes an unreachable-engine stance map (§6.4, obligation 3). This is the inventory until <code>maturity-engine</code> can hold it.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Consumer</th><th>Stance map</th><th>Shape</th></tr></thead><tbody><tr><td><code>ops-warden</code></td><td><code>ops-warden/pep-stance.yaml</code></td><td>total per-zone; open <code>z0</code>–<code>z2</code> and unknown, closed <code>z3-critical</code>; test asserts the published map equals the shipped default (<code>ADR-0009</code>)</td></tr><tr><td><code>ops-mason</code></td><td>—</td><td><strong>not published</strong>; catalogued PEP-shaped in §4</td></tr></tbody></table></div>
|
||||
<p><strong>One row is the finding.</strong> The aggregate of consumer stances is the estate's real authorization behaviour, and it is currently one published map and one absence. <code>access-engine</code> has noted it is the repository positioned to notice when that aggregate diverges from what the policy packages say — which it cannot do while the register is nearly empty.</p>
|
||||
</section>
|
||||
<section id="s14"><h2><span class="sn">14</span>Adoption</h2>
|
||||
<p>Status is <strong>accepted</strong>, on the owner's decision of 2026-08-29.</p>
|
||||
<p>Two things that acceptance does and does not mean, kept apart because <code>ops-warden</code> asked for the distinction:</p>
|
||||
<div class="scroll"><table><thead><tr><th><strong>Boundary assent</strong></th><th>given by the four repositories below, at the version named in each record, and undisturbed since</th></tr></thead><tbody><tr><td><strong>Revision review</strong></td><td>each of the four reviewed v0.6 and returned findings; <strong>every change in v0.7 is the adopted remedy of a finding they raised</strong></td></tr><tr><td><strong>Not claimed</strong></td><td>no repository has reviewed v0.7 <em>as text</em>. The first revision review will confirm or correct it</td></tr></tbody></table></div>
|
||||
<p>Accepting a standard nobody has re-read is a deliberate call: the estate learns more from using it than from another round of prose refinement, and the changes in v0.7 were requested rather than invented. Findings against the accepted text remain welcome and are §12's normal business, not an exception.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Repository</th><th>Record</th><th>Outcome</th></tr></thead><tbody><tr><td>flex-auth</td><td><code>FLEX-DEC-2026-001</code></td><td>assent to all three items; one self-declared non-conformance; two rename conditions</td></tr><tr><td>kings-guard</td><td><code>KG-DEC-2026-001</code></td><td>assent; declined the offered §5 relaxation; raised §9.1</td></tr><tr><td>ops-warden</td><td><code>ADR-0010</code></td><td>assent to all three; veto not exercised; offered the §5.3 amendment</td></tr><tr><td>audit-core</td><td><code>AUDIT-IN-0001</code></td><td>assent to the evidence half with conditions; corrected the rationale twice; raised §9.6</td></tr></tbody></table></div>
|
||||
<p>Adoption for a repository means its <code>INTENT.md</code> declares its layer, its ownership claims fall inside that layer, its Tooling contacts are declared under §5, and any shared boundary has been assented to by the other side.</p>
|
||||
<p><strong>Adoption status as of 2026-08-29: seven of sixteen</strong> estate-authored §4 repositories have declared in their own voice — <code>gate-house</code>, <code>flex-auth</code>, <code>kings-guard</code>, <code>ops-warden</code>, <code>audit-core</code>, <code>approval-engine</code>, <code>maturity-engine</code>. The remaining nine — <code>info-tech-canon</code>, <code>net-kingdom</code>, <code>key-cape</code>, <code>user-engine</code>, <code>tenant-engine</code>, <code>zone-engine</code>, <code>secrets-engine</code>, <code>ops-mason</code>, <code>whitehat-security</code> — carry a layering review note authored by <code>gate-house</code> and have not answered it. Those notes state a layer but do not constitute a declaration, and this standard does not claim estate-wide adoption on their basis. Declaration requests are open as intakes in each.</p>
|
||||
</section>
|
||||
<section id="s15"><h2><span class="sn">15</span>Change log</h2>
|
||||
<p>v0.1 → v0.2:</p>
|
||||
<ol><li><strong>§5 restructured</strong> into three sanctioned shapes. Added §5.2 conduit (ops-warden's question, ruled) and §5.3 declared engine gap (ops-warden's amendment, accepted).</li><li><strong>§6.2 added</strong> — doctrine must reach the decision as an input claim or a versioned policy rule (flex-auth's boundary drawn back, accepted).</li><li><strong>§9 added</strong> — the catalog may not assign a capability the rules forbid discharging; containment marked pending; degraded-mode fallback ruled into the engine (kings-guard's finding).</li><li><strong>§11 restructured</strong> — conformance now has three states, distinguishing a tracked gap from an undeclared violation.</li><li><strong>§12 made normative</strong>, with the explicit statement that an unsatisfiability finding is a success of the loop.</li><li><strong>§13 added</strong> — open gaps register, including the unowned approval storage and lifecycle capability.</li><li>§4 catalog gained the pending mark and ops-warden's SSH certificate lane.</li></ol>
|
||||
<p>v0.2 → v0.3:</p>
|
||||
<ol><li><strong>§9.4 added</strong> — approvals assigned to <code>approval-engine</code>, with the operative state and the evidence record separated between it and <code>audit-core</code>.</li><li><strong>§9.5 added</strong> — graded progression assigned to <code>maturity-engine</code>, closing the §9.1 defect in gate-house's own conformance-review claim, and carrying the guardrail that a level may never gate a decision directly.</li><li>§4 catalog gained both engines; gate-house's conformance-review claim now names the engine it acts through.</li><li>§13 register updated: the approval hole is assigned, two new entries added.</li></ol>
|
||||
<p>v0.6 → v0.7, from four reviews:</p>
|
||||
<ol><li><strong>§3.4 is written.</strong> v0.6 announced the human/agent principal separation in §1 and §15 and left §3.4 byte-identical to v0.5 — a silent edit failure. A rule stated about a standard in its own change log is not a rule. Found by <code>kings-guard</code>. The same failure had also dropped two §16 entries, restored here.</li><li><strong>§6.4 obligation 1 rewritten</strong> — it forbade what obligation 3 blesses. A PEP may proceed under its declared §9.3 stance provided the application of that stance is <em>recorded in place of</em> the decision. Stricter than v0.6 where it counts: a fail-open result is metadata, never silence. Raised by <code>ops-warden</code>.</li><li><strong>§6.4 obligation 2 rewritten</strong> — it forbade the session-bound allow §9.7.1 permits. Scoped to replay outside the decision's own binding and lifetime, with the canonical request digest as the mechanical test, and negative caching ruled permitted where the refusal is recorded and the cache lifetime declared. Raised by <code>access-engine</code>.</li><li><strong>§6.4 obligation 3</strong> gained the requirement that the published stance map equal shipped behaviour, asserted by test. <strong>§13.1</strong> now exists as the register §6.4 mandated and v0.6 did not implement.</li><li><strong>§9.6 gained a threat decomposition</strong> — atomicity prevents accidental omission; cadence and reconciliation detect the adversarial case after the fact; nothing prevents it at a compromised source. Raised by <code>audit-core</code> against its own proposed remedy.</li><li><strong>§9.6 cadence is now MUST for load-bearing sources</strong>, with positive reconciliation or a heartbeat as the required form for low-volume classes, because rate monitoring fails exactly where the stakes are highest.</li><li><strong>§9.7.2 splits by role</strong> — a PDP states a deadline per input class, a PEP one at its boundary. Promotes <code>access-engine</code>'s provenance gap to a conformance prerequisite.</li><li><strong>§3.3's Evidence row</strong> is stated as an estate trade rather than a property, leaving independent-recording-before-effect raisable as a declared exception.</li><li><strong>§17</strong> moves the decision-record schema to <code>access-engine</code>, which argued it against its own interest; <code>kings-guard</code> drafts the emission-cadence schema.</li><li><strong>§13</strong> no longer attributes the actuation gap to <code>kings-guard</code>; it is estate-wide. <strong>§19 removed</strong> — a verdict inside a standard grades the document it lives in. <strong>§17/§18</strong> demoted from H1 to H2.</li><li><strong>§20 added</strong> — the Railiance interaction boundary, on <code>railiance-master</code>'s definitions, including that <code>rein-*</code> is not a fifth axis.</li></ol>
|
||||
<p>v0.5 → v0.6, from the independent assessment of 2026-08-29:</p>
|
||||
<ol><li><strong>§3.3 types the engines</strong> — PDP, PIP, Evidence, Lifecycle, with a role column in §4. A new engine is a PIP unless this standard says otherwise, so "we need an engine for X" cannot drift into "X now decides".</li><li><strong>§6.4 names the enforcement point</strong> — a PEP shape with four obligations: no side effect without a decision record, no local recaching of the verdict, a declared unreachable-engine stance, reconstructability. The standard had the decision and not the gate.</li><li><strong>§9.2 replaced</strong> — containment was marked pending against the wrong repository. Actuation is an Engine concept, unowned, held at zero; Staff proposes containment and never performs it.</li><li><strong>§3.4 separates the two Staff principals</strong> — human and agent share the layer but not blast radius: no standing credential, conduit or engine API only, agent memory is not a state plane, every action reconstructable as the caller's.</li><li><strong>§9.7 puts time into the model</strong> — explicit lifetimes, revocation visibility deadlines, consumption as a state change never inferred, and the three race modes named. <strong>§9.8</strong> states what holds under partition and leaves the rest open.</li><li><strong>§17 requires the Taxonomy artifacts</strong> — claim, decision-record, gap-record, and emission-cadence schemas — without which §6.2 and §11 are reviewable but not compileable. Ownership proposed, not assigned.</li><li><strong>§18 composes the sibling standards</strong> — how zone stance, tenancy posture, and a credential lifecycle event each enter a decision as a claim. They were cited in frontmatter and nowhere in the rules.</li><li><strong>§5 gained a sunset</strong> on the uncatalogued-infrastructure carve-out, and §5.3 <strong>declines</strong> a proposed fourth "operator of third-party Tooling" shape: it would convert a tracked gap into a permanent allowance.</li><li><strong>§10 gained the six artifacts</strong> a layer change must carry, written from the <code>zone-engine</code> case, including a permission freeze during the cut.</li><li><strong>§2 lifts the observation rule</strong> — no estate argument may cite observation that has not happened. <strong>§13</strong> separates its three normative rules from the table, which is now a snapshot due to move into <code>maturity-engine</code>.</li><li><strong>§16</strong> the approval custody question is <strong>decided: no</strong>, rather than left open. <strong>§19</strong> records the fitness verdict, including that the estate can propose and decide but cannot yet watch or act.</li></ol>
|
||||
<p>v0.4 → v0.5, all from review findings:</p>
|
||||
<ol><li><strong>§9.1 split into two marks</strong> — <code>pending</code> (no route, capability zero) and <code>declared-gap</code> (route exists under §5.3, capability works and is tracked). v0.4's single mark would have forced a false <code>pending</code> onto ops-warden's production SSH issuance. Raised by <code>ops-warden</code>.</li><li><strong>§9.3 rewritten</strong> — input degradation is the engine's; engine-unreachability is necessarily the consumer's, bounded by a declared, auditable, total stance. Contested by <code>flex-auth</code>: fail-open is not expressible by a PDP, and v0.4 collided with <code>ops-warden</code> <code>ADR-0009</code>.</li><li><strong>§5 gained a scope rule</strong> — "Tooling-layer system" means a §4 Tooling row; uncatalogued infrastructure is outside §5 and recorded rather than policed. Without it every Staff repository was in undeclared violation for writing progress events. Raised by <code>ops-warden</code>.</li><li><strong>§9.4 requires a local outbox</strong> — no synchronous dependency on <code>audit-core</code> inside the state-change transaction, so an audit outage cannot block a revocation. Raised by <code>audit-core</code>.</li><li><strong>§9.5 forbids compiling maturity levels into registry content</strong> until decision provenance carries a registry-snapshot digest. Raised by <code>flex-auth</code>.</li><li><strong>§9.6 gained the load-bearing / attributive distinction</strong>, the mirror rule that absence is not evidence of non-occurrence, the optimistic-bias consequence for adaptive systems, and silence-as-signal. Raised by <code>kings-guard</code> on top of <code>audit-core</code>'s original.</li><li><strong>§11 gained a fourth state</strong> — blocked-clean, which MUST NOT rank below conforming — and a machine-readable declaration form. Raised by <code>kings-guard</code> and <code>audit-core</code>.</li><li><strong>§13 gained state and owner-status columns</strong> — declared-contact versus unowned-capability, proposed versus assented owner. <code>access-engine</code>'s decline of authentication evidence is recorded. Raised by <code>kings-guard</code> and <code>flex-auth</code>.</li><li><strong>§8</strong> records the asymmetry's payoff under incomplete observation; <strong>§12</strong> records that its fourth step is unstaffed; <strong>§14</strong> corrects the adoption arithmetic and the status contradiction.</li></ol>
|
||||
<p>Amended in place while <code>proposed</code>, 2026-08-28: §11 gained the who-must-declare rule after a conformance sweep found the standard required an <code>INTENT.md</code> declaration from <code>OpenBao</code>, which the estate does not author; and §14 gained the honest adoption count.</p>
|
||||
<p>v0.3 → v0.4:</p>
|
||||
<ol><li><strong>§4 catalog gained <code>audit-core</code></strong> as an Engine, on its own declaration. v0.3 named it as an owner in §9.4 and §13 without cataloguing it — a §11 defect in the standard itself, raised by <code>audit-core</code>.</li><li><strong>§9.4 evidence rationale rewritten</strong> to cite <code>audit-core</code>'s shipped <code>docs/integrity.md</code> bound rather than its INTENT principle 6, and to state that <code>tamper_evidence</code> is conditional on live preconditions.</li><li><strong>§9.4 gained emission atomicity</strong> as <code>approval-engine</code>'s obligation, and the prohibition on <code>audit-core</code> exposing an approval-validity query.</li><li><strong>§9.6 added</strong> — evidence proves alteration and truncation, not omission at source. Estate-wide; the sound and unsound forms of the claim are stated.</li><li><strong>§13</strong> — evidence half recorded as assented with conditions; two new gaps: stronger approval custody (unassigned) and emission atomicity (<code>approval-engine</code>).</li></ol>
|
||||
</section>
|
||||
<section id="s16"><h2><span class="sn">16</span>Open questions</h2>
|
||||
<ul><li>~~Whether approvals warrant archival custody stronger than every other audit source.~~ <strong>Decided (§13): no.</strong> Approval evidence carries the same bound as every other source. The acute risk for approvals is <em>omission</em> — a suppressed revocation — and archival custody does not address omission at all; emission atomicity with a local outbox (§9.4) and a detection surface (<code>GH-WP-0002-T04</code>) do. Leaving it open while calling the evidence half load-bearing created a promise the archive cannot cash. If a future requirement genuinely needs WORM or a transparency log, that is a different store with a different owner, raised then.</li><li>Whether SSH certificate issuance evidence is load-bearing or attributive (§9.6). Ruled attributive here on the argument that no control branches on the presence of a signing record; <code>ops-warden</code> asked for the ruling and the trade is genuinely two-sided, so it is flagged rather than settled.</li><li>Who marks an approval consumed, and at what point relative to the decision (§9.4). <code>flex-auth</code> notes the decision precedes the action and the action precedes consumption, so an allow rendered against an approval then never consumed, or consumed twice by a racing caller, is a gap neither engine closes alone. Needed before <code>FLEX-WP-0017</code> T05.</li><li>Whether other §4 repositories are missing layer declarations; <code>audit-core</code> flagged its own absence and asked whether the catalog needs the same correction elsewhere.</li><li>Whether the gap register migrates from this standard into <code>maturity-engine</code> once that engine exists, leaving the standard to state the rules only.</li><li>Whether Tooling warrants subdivision between third-party and homegrown.</li><li>How a future <code>role-engine</code> divides responsibility with <code>access-engine</code>.</li><li>Whether declared gaps need an estate-wide register rather than per-repository declarations; ops-warden's <code>warden route gaps</code> is candidate machinery.</li><li>Whether non-security repositories adopt the same model. The determinism cut is not security-specific; if non-security Staff also may not hold runtime-dependent state, the estate gets one constitution rather than a security ghetto.</li><li>The rest of §9.8: split brain, partial PIP reachability, and clock skew beyond the two rules stated.</li><li>Publication integrity of the Taxonomy layer itself. This standard demands reconstructability of decisions while its own publication path has no digest, freeze, or rollback discipline.</li><li>The fitness verdict formerly at §19 now lives in <code>net-kingdom/history/2026-08-29-layering-standard-assessment.md</code>. A grade inside a standard of record becomes normative by adjacency and ages against the text it grades. Raised by <code>access-engine</code>. Its two substantive points remain live: observation in production is unstaffed (§12) and actuation has no surface (§9.2).</li><li>The <strong>working companion</strong> (<code>net-kingdom/SECURITY-COMPANION.md</code>, v0.2, root of the repository for onboarding) is the operative form of this statute. The statute governs on disagreement, and a disagreement is a finding. The v0.1 gap <code>access-engine</code> found — publish your stance map, but nowhere saying where, and no inventory obligation — is fixed in v0.2 §5.3.</li><li>How the Railiance operational axes meet this model beyond §20's first statement, which is deliberately minimal.</li></ul>
|
||||
</section>
|
||||
<section id="s17"><h2><span class="sn">17</span>Taxonomy artifacts</h2>
|
||||
<p>§6.2 says doctrine reaches a decision as an input claim or a versioned policy rule. As prose that is a rule a reviewer can apply. As an interface it does not exist, because nothing defines what a claim <em>is</em>. §11 calls itself mechanically checkable while resting on that gap.</p>
|
||||
<p>Four artifacts are therefore required, owned by Taxonomy and versioned like any standard:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Artifact</th><th>Contents</th></tr></thead><tbody><tr><td><strong>request-claim schema</strong></td><td>identity, tenant, zone stance, posture, approval, maturity, assurance — each with its issuer and freshness rule</td></tr><tr><td>~~decision-record schema~~</td><td><strong>moved to <code>access-engine</code></strong> — see below</td></tr><tr><td><strong>gap-record schema</strong></td><td>the §5.3 fields — <code>capability</code>, <code>intended_owner</code>, <code>blocked_on</code>, <code>review</code> — plus the §13 <code>state</code> and owner-status</td></tr><tr><td><strong>emission-cadence declaration</strong></td><td>the expected rate a source publishes, so silence is a finding (§9.6)</td></tr></tbody></table></div>
|
||||
<p>Until these exist, §6.2 and §11 are reviewable but not compileable, and every engine invents its own claim shape at its own boundary.</p>
|
||||
<p><strong>The decision-record schema is not Taxonomy's.</strong> A decision record is the PDP's output artifact — the one thing in the estate only <code>access-engine</code> produces — and §2 keeps ownership in the producing repository's own <code>INTENT.md</code>. Taxonomy authoring the schema for an artifact only one engine emits would invert the ownership rule this standard applies everywhere else. <code>access-engine</code> publishes it as a contract; Taxonomy holds only the shared field vocabulary the claim schema references.</p>
|
||||
<p>Raised by <code>access-engine</code> <strong>against its own interest</strong> — the same §2 argument it used to decline authentication evidence, applied where it takes work on rather than off. Symmetry of that kind is what makes the ownership rule credible.</p>
|
||||
<p><strong>The emission-cadence declaration has a drafter.</strong> <code>kings-guard</code> is its only consumer, cannot implement silence-as-signal without it, and has offered to draft it against <code>qonto-assistant</code> and hand it to whichever Taxonomy repository takes ownership — rather than inventing a local shape, which is the drift §17 exists to prevent. Accepted as a draft; ownership still rests with Taxonomy.</p>
|
||||
<p><strong>Ownership is proposed, not assigned.</strong> <code>info-tech-canon</code> holds ecosystem-wide semantic contracts and <code>net-kingdom</code> holds NetKingdom standards of record; the split between them for these four artifacts is theirs to draw, and §2 keeps ownership in the owning repository's <code>INTENT.md</code>. Neither has assented.</p>
|
||||
</section>
|
||||
<section id="s18"><h2><span class="sn">18</span>Composition with the sibling standards</h2>
|
||||
<p>The related-standards list has been frontmatter and little else. If the following sentences cannot be written, the list is decoration — so they are written here rather than in the siblings.</p>
|
||||
<p><strong>Zone stance</strong> (<code>security-zones_v0.1</code>). A zone answers which scrutiny a workload has qualified for; membership is <code>zone-engine</code>'s. The <em>effect</em> of a zone on a decision belongs in a versioned <code>access-engine</code> policy package, never in registry content — that ruling is zone-engine's §5, and §6.1 is its generalization. Zone stance therefore enters a decision as a <strong>claim on the request or a rule in the package</strong>, and a decision that turned on a zone must name the package version that read it.</p>
|
||||
<p><strong>Tenancy posture</strong> (<code>tenancy-posture_v0.1</code>). Posture is a bounded security-state input, published by its owner and never a privilege source (§8). It enters as a <strong>claim</strong>, carries its own freshness, and the §8 asymmetry binds it: posture may tighten a decision and may never loosen one. A posture too stale to trust is a missing claim, and a missing claim is not permission.</p>
|
||||
<p><strong>Credential lifecycle</strong> (<code>credential-management_v0.2</code>). Issuance, rotation, and revocation are <code>secrets-engine</code>'s and <code>OpenBao</code>'s, downstream of a decision — a credential is an artifact of authority, never its source. A lifecycle event becomes an <strong>input</strong> to a later decision as a claim (this credential is current, this lease is bound to this task), never a side channel that changes an outcome without appearing in the decision record. Revocation visibility is bounded by §9.7.</p>
|
||||
<p>Each of the three composes the same way, which is the point: <strong>facts arrive as claims, effects live in versioned policy, and anything that changes an outcome appears in the decision record.</strong></p>
|
||||
</section>
|
||||
<section id="s20"><h2><span class="sn">20</span>The Railiance interaction boundary</h2>
|
||||
<p>Operations is not NetKingdom's. Workload operations are organized by <strong>Railiance</strong>, whose framework repository is <code>railiance-master</code>, and NetKingdom provides the security and approval framework those operations consume. This section states the boundary as it stands today. It is expected to evolve, and it is written here so that evolution is visible rather than inferred.</p>
|
||||
<p>Definitions are <code>railiance-master</code>'s and are restated, not authored, here.</p>
|
||||
<h3>20.1 What Railiance organizes</h3>
|
||||
<p>A <strong>workload</strong> is a managed running deployable. Human commands, credential patterns, broker actions, approvals, and infrastructure resources that are not themselves deployables <strong>are not workloads</strong> — which is why an approval object (§9.4) is not a Railiance axis and never becomes one.</p>
|
||||
<p>Every workload is operated through four composable axes, each answering a different question about the same workload:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Prefix</th><th>Axis</th><th>Question</th></tr></thead><tbody><tr><td><code>railiance-*</code></td><td>ownership</td><td>Who owns this capability?</td></tr><tr><td><code>rail-*</code></td><td>execution contract</td><td>How does this workload run?</td></tr><tr><td><code>rapp-*</code></td><td>managed package</td><td>What exactly is packaged and operated?</td></tr><tr><td><code>reef-*</code></td><td>substrate</td><td>Where is it bound, and as what operational reality?</td></tr></tbody></table></div>
|
||||
<p><code>rein-*</code> is <strong>not a fifth axis</strong>. Reins are <code>glas-harness</code> agent-harness backends; the name echoes <code>rail-*</code> analogically, not taxonomically. Agentic session semantics — session loops, tool policy, harness routing, model selection — belong to <code>glas-harness</code>. When a rein is installed and operated as a managed service it is a workload like any other, packaged and bound through the four axes above.</p>
|
||||
<h3>20.2 What holds today</h3>
|
||||
<p>For any Railiance consumer of NetKingdom security, without exception:</p>
|
||||
<ol><li>Authorization decisions come from <code>access-engine</code> and from nowhere else (§6).</li><li>Approvals are objects in <code>approval-engine</code>, consumed as claims (§9.4).</li><li>Credentials are materialized by <code>secrets-engine</code> <strong>after</strong> a decision, never as a substitute for one.</li><li>Evidence goes to <code>audit-core</code> under the bound in §9.6.</li><li>Anything causing a protected side effect is <strong>PEP-shaped</strong> and owes the four obligations in §6.4 — including a published unreachable-engine stance in the §13.1 register.</li></ol>
|
||||
<h3>20.3 What is not settled</h3>
|
||||
<p>The mapping between the axes and this model is deliberately thin, because guessing it would be worse than admitting it:</p>
|
||||
<ul><li>A <strong><code>rapp-*</code></strong> is the most likely <em>resource</em> a decision is rendered about, but nothing states its identity form in a request claim.</li><li>A <strong><code>rail-*</code></strong> describes how a workload runs and is therefore where PEP shape is most likely to live — but §6.4 obligations attach to repositories, and a rail is a contract, so whether a rail can <em>carry</em> an obligation is unwritten.</li><li>A <strong><code>reef-*</code></strong> answers where a workload is bound, which is adjacent to a security zone (<code>security-zones_v0.1</code>) without being one. <code>zone-engine</code> records that a reef capping availability for everything bound to it is a canon composition problem. That composition is unwritten.</li><li>The <strong><code>railiance-*</code> ownership axis</strong> names who owns a capability, which is adjacent to the principal a decision is rendered for. Adjacent is not equal, and no rule connects them.</li><li><strong><code>glas-harness</code> and reins</strong> hold tool policy and session semantics for agents, while §3.4 rule 2 holds that an agent acts only through a conduit or an engine API. Those two must compose, and neither side may treat its own half as sufficient. That seam is the most consequential of the five, because it is where "tool availability is not permission" is actually enforced or lost.</li></ul>
|
||||
<h3>20.4 How this boundary changes</h3>
|
||||
<p>An interaction boundary between two frameworks is owned by neither alone. Changes to §20 require assent from <code>railiance-master</code> for the axis definitions and from <code>glas-harness</code> for the session and tool-policy seam, on the same terms as any other boundary in this standard (§10). NetKingdom states what a consumer owes; it does not define what a rail, rapp, reef, or rein <em>is</em>.</p>
|
||||
</section><footer><span>netkingdom-security-layer-model-v0.7 · · accepted</span><span>net-kingdom · canon/standards/security-layer-model_v0.7.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>
|
||||
|
|
@ -0,0 +1,461 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="ce198fc2905687ea90a2892346b6860281ac87f8">
|
||||
<meta name="policy-source-digest" content="8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b">
|
||||
<title>NetKingdom Security Layer Model v0.7</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-layer-model-v0.7</span> <span class="stat">accepted</span> <span>gate-house</span> <span>reviewed 2026-08-28</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Layer Model v0.7</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-layer-model_v0.7.md · ce198fc2905687ea90a2892346b6860281ac87f8</code></p><p class="sub">Review due: 2026-11-28</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority and conformance</a></li><li><a href="#s3"><span class="n">3</span>The layers</a></li><li><a href="#s4"><span class="n">4</span>Layer catalog</a></li><li><a href="#s5"><span class="n">5</span>The binding rule</a></li><li><a href="#s6"><span class="n">6</span>One decision point</a></li><li><a href="#s7"><span class="n">7</span>Relationship to the Active Secrets Management Canon</a></li><li><a href="#s8"><span class="n">8</span>Vocabulary demarcations</a></li><li><a href="#s9"><span class="n">9</span>Capability assignment</a></li><li><a href="#s10"><span class="n">10</span>Changing layer</a></li><li><a href="#s11"><span class="n">11</span>Conformance</a></li><li><a href="#s12"><span class="n">12</span>The conformance loop</a></li><li><a href="#s13"><span class="n">13</span>Open gaps</a></li><li><a href="#s14"><span class="n">14</span>Adoption</a></li><li><a href="#s15"><span class="n">15</span>Change log</a></li><li><a href="#s16"><span class="n">16</span>Open questions</a></li><li><a href="#s17"><span class="n">17</span>Taxonomy artifacts</a></li><li><a href="#s18"><span class="n">18</span>Composition with the sibling standards</a></li><li><a href="#s20"><span class="n">20</span>The Railiance interaction boundary</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>This standard states how NetKingdom's IT-security estate is layered, and what each layer may and may not do. It answers one question:</p>
|
||||
<div class="rule-quote"><p><strong>Given a repository, which layer is it in, and what does that permit it to own?</strong></p></div>
|
||||
<p>The layers are distinguished by <strong>determinism</strong> and by <strong>the kind of artifact the layer produces</strong>, not by technical tier, deployment topology, or team.</p>
|
||||
<p>It is not an org chart, not a network model, not a deployment topology, and not a dependency graph. It does not assign work, and it does not replace any repository's boundary contract; it constrains what such a contract may claim.</p>
|
||||
<p><strong>What changed in v0.7.</strong> v0.6 announced a rule it never wrote: §1 and §15 said the standard separates human and agent principals inside Staff, and §3.4 was byte-identical to v0.5. <code>kings-guard</code> found it and put it correctly — <em>a rule stated about a standard in its own change log is not a rule</em>, which is §11's own principle turned on the standard. §3.4 is now written.</p>
|
||||
<p>The rest are collisions between rules written for the clean case: §6.4's first obligation forbade what its third obligation blesses, and its second forbade the session-bound allow §9.7.1 permits. §9.4's atomicity was described as closing a threat it does not close. §19 graded the document it lived in. And §20 records the interaction boundary with Railiance operations, on definitions from <code>railiance-master</code> rather than inference. §15 records the change list.</p>
|
||||
<p><strong>What changed in v0.6.</strong> An independent assessment against industry practice (<code>net-kingdom/history/2026-08-29-layering-standard-assessment.md</code>) found the model sound as a layering constitution and incomplete as a <em>self-healing</em> one: cognition, authority, and execution are specified, but the two verbs that close a healing loop — observe in production and actuate through a deterministic surface — are pending, and one is unstaffed. It also found the Engine layer untyped, so that "we need an engine for X" drifts toward "X now decides", and the enforcement point unnamed.</p>
|
||||
<p>v0.6 types the engines (§3.3), names the enforcement point (§6.4), replaces the containment assignment with an actuation surface held at zero (§9.2), separates human and agent principals inside Staff (§3.4), puts time into the model (§9.7), requires the Taxonomy artifacts that make §6.2 compileable rather than reviewable (§17), and composes the sibling standards it had only cited (§18). Section numbers below §14 are unchanged: the estate cites them.</p>
|
||||
<p><strong>What changed in v0.5.</strong> All four reviewing repositories returned findings on v0.4, and one contested a rule. §9.3 was wrong: it collapsed <em>engine reachable but degraded</em> with <em>engine not reachable at all</em>, and the second case has no evaluator in the path to express anything. §9.1 collapsed <em>no route exists</em> with <em>route exists under a declared gap</em>, which would have forced a false "pending" onto a production capability. §11 claimed mechanical checkability for a rule that cannot be checked in prose. §13 filed two opposite conformance states in one table and recorded proposed owners as owners. §9.6 needed the load-bearing distinction it implied but never drew. §15 records the change list.</p>
|
||||
<p><strong>What changed in v0.4.</strong> <code>audit-core</code> assented to the approval evidence half and corrected the rationale twice. v0.3 rested §9.4 on that repository's INTENT principle 6, which is an aspiration; the shipped bound in its <code>docs/integrity.md</code> is weaker and conditional. More consequentially, no append-only archive can prove <strong>omission at source</strong> — a suppressed revocation leaves the chain intact — which is now stated as an estate-wide doctrine constraint (§9.6) rather than left implicit. <code>audit-core</code> was also referenced as an owner in v0.3 without appearing in the §4 catalog at all; it is catalogued here, as an Engine, on its own declaration. §15 records the change list.</p>
|
||||
<p><strong>What changed in v0.3.</strong> Two engines were seeded to own concepts v0.2 recorded as unowned: <code>approval-engine</code> takes the approval object that §13 left homeless, and <code>maturity-engine</code> takes graded progression — closing a §9.1 defect in gate-house's own catalog claim, which asserted conformance review with no engine to act through. §15 records the change list. v0.3 is <strong>proposed</strong>: the two new engines are seeded by owner direction and have no other side to assent yet, and the evidence half of the approval split needs <code>audit-core</code>'s assent.</p>
|
||||
<p><strong>What changed in v0.2.</strong> v0.1 was assented to by all three repositories whose boundaries moved, and each returned a finding. v0.1 had one lane for a Staff repository that legitimately touches Tooling — read-only diagnostics — which is narrower than the estate as it actually stands, and a rule with no lane for a real sanctioned case is satisfied by relabelling rather than by closing the gap. v0.1 also catalogued a capability (§4, containment) that §5 forbade discharging, and applied its reconstructability test to engines but not to the doctrine gate-house feeds them. §15 records the full change list.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Authority and conformance</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Fact or rule</th><th>Authority</th></tr></thead><tbody><tr><td>The layers, their definitions, and the rules between them</td><td>This standard, owned by gate-house</td></tr><tr><td>Which layer a given repository is in</td><td>This standard, §4 catalog</td></tr><tr><td>What a repository owns within its layer</td><td>That repository's <code>INTENT.md</code> and boundary contract</td></tr><tr><td>Whether a specific request is permitted</td><td><code>access-engine</code> — never this standard</td></tr><tr><td>Whether a Tooling contact is sanctioned</td><td>The declaring repository, under the shapes in §5, reviewable by gate-house</td></tr><tr><td>Security doctrine and invariants</td><td>gate-house</td></tr><tr><td>Publication</td><td>net-kingdom canon</td></tr><tr><td>Whether an invariant is <em>watched in practice</em></td><td>the observing repository's own report — never this standard, and never §12's diagram</td></tr></tbody></table></div>
|
||||
<p>A repository conforms when its <code>INTENT.md</code> declares its layer, its claims fall within that layer's permissions (§3), and its Tooling contacts take one of the sanctioned shapes in §5 or are declared as gaps under §5.3.</p>
|
||||
<p><strong>No estate argument may cite observation that has not happened.</strong> §12 lists <code>kings-guard</code> against the loop's fourth step, and that repository has reported that it has never observed a real event. Until it reports otherwise, no assessment, review, or decision in this estate may treat an invariant as being watched in practice on the strength of the diagram. Lifted here from §12 so it cannot be lost in a summary.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>The layers</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Layer</th><th>Character</th><th>Produces</th><th>Deterministic</th></tr></thead><tbody><tr><td><strong>Taxonomy</strong></td><td>cross-cutting language</td><td>terms, semantic contracts, standards</td><td>n/a — describes</td></tr><tr><td><strong>Tooling</strong></td><td>infrastructure and state</td><td>data structures, persistence</td><td>yes</td></tr><tr><td><strong>Engines</strong></td><td>interfaces for a modeled concept</td><td>APIs, contracts</td><td>yes</td></tr><tr><td><strong>Staff</strong></td><td>management, operations, change, controlling</td><td>specifications, decisions, workplans, tasks</td><td><strong>no</strong></td></tr></tbody></table></div>
|
||||
<h3>3.1 Taxonomy</h3>
|
||||
<p>Cross-cutting language. Taxonomy repositories define terms and semantic contracts so the other layers interoperate without integration by interpretation. They own no runtime position and no state any layer depends on.</p>
|
||||
<p><code>info-tech-canon</code> holds ecosystem-wide semantic contracts. NetKingdom-specific security architecture — including this standard — is net-kingdom canon's.</p>
|
||||
<h3>3.2 Tooling</h3>
|
||||
<p>Deterministic infrastructure: data structures, persistence, and the consistent, performant, scalable keeping of state. Much of it is third-party.</p>
|
||||
<h3>3.3 Engines</h3>
|
||||
<p>Deterministic APIs for a modeled concept — a user, a tenant, a zone, a secret, an access rule. An engine's defining property is that <strong>the same authoritative input state yields the same result</strong>. Engines are where the estate's deterministic guarantees live, and therefore where every enforcement boundary MUST sit.</p>
|
||||
<p>A repository whose core function is inference or judgment fails this test by construction and is Staff, however much of its work happens at runtime.</p>
|
||||
<p><strong>Engines are typed.</strong> "Engine" is one layer but four roles, and collapsing them hides different failure modes. Every §4 Engine row carries a role:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Role</th><th>Meaning</th><th>Outage means</th></tr></thead><tbody><tr><td><strong>PDP</strong></td><td>renders the authorization decision — <code>access-engine</code>, and only it (§6)</td><td>consumer residue (§9.3)</td></tr><tr><td><strong>PIP</strong></td><td>supplies facts a decision consumes as claims — user, tenant, zone, approval, maturity</td><td>input degradation, engine's own fallback (§9.3)</td></tr><tr><td><strong>Evidence</strong></td><td>records what happened and proves integrity of what it holds — <code>audit-core</code></td><td>MUST NOT block the operation being recorded — a default, not a property; see below</td></tr><tr><td><strong>Lifecycle</strong></td><td>a deterministic API over Tooling it owns — <code>secrets-engine</code></td><td>the owning engine's failure semantics</td></tr></tbody></table></div>
|
||||
<p>The roles are why <em>"we need an engine for X"</em> does not mean <em>"X now decides"</em>. A new engine is a PIP unless this standard is amended to say otherwise, and §6 means it can never be a second PDP.</p>
|
||||
<p>The Evidence row's outage rule is an estate <strong>trade</strong>, not a property of evidence engines. Choosing availability there means accepting that a compromised source can suppress a record and that detection is the answer (§9.6). The opposite shape — <em>do not proceed unless an independent custodian already holds the record</em> — is the only one that puts evidence outside the actor's blast radius <strong>before</strong> the act. The estate has not needed it, so it is not ruled out by a table cell: an operation whose control genuinely requires independent recording before effect is a declared exception, raised when needed. Raised by <code>audit-core</code> against its own row.</p>
|
||||
<p>The industry vocabulary is deliberately mirrored here — PDP, PIP, PEP as in NIST ZTA and XACML — because it is how the estate talks to the outside and how a PEP is stopped from quietly becoming a PDP. The determinism cut in §3 stays primary where the two disagree.</p>
|
||||
<h3>3.4 Staff</h3>
|
||||
<p>Interactive and non-deterministic. Staff is the management layer: operations, change, innovation, and controlling. It works through agentic capability — assistants and autonomous agents — and its artifacts are specifications, decisions, workplans, and tasks.</p>
|
||||
<p>Staff repositories MUST NOT hold state that another layer depends on at runtime, and MUST NOT render or cache any decision an Engine is responsible for.</p>
|
||||
<p>Acting at runtime does not make a repository an Engine. Being agentic makes it Staff, and §5 governs how it acts.</p>
|
||||
<p><strong>Two principals, one layer.</strong> Humans and agents are both non-deterministic and both Staff, so they share the layer's permissions. They do not share blast radius. Four rules bind the agent principal specifically:</p>
|
||||
<ol><li><strong>No standing credential.</strong> An agent holds no long-lived credential of its own. Authority is issued per task, time-bounded under §9.7, and attributable to the principal on whose behalf it acts.</li><li><strong>Tool use is a conduit or an Engine API.</strong> An agent acts through §5.2 — the owner's tool under the caller's identity, presenting no authority of its own — or through an engine. There is no third route. Tool availability is not permission: a callable tool means the operation exists, not that this actor may invoke it.</li><li><strong>Agent memory is not a state plane.</strong> Agent memory, tool-call traces, and prompt caches are the agent's own. They MUST NOT become state another layer depends on at runtime unless catalogued as Tooling in §4, which subjects them to §5 like anything else, and to §5's sunset.</li><li><strong>Every agent action is reconstructable as the caller's action</strong>, bounded by §9.6 — the archive shows the actions it received, not that it received all of them.</li></ol>
|
||||
<p>Session semantics — session loops, tool policy, harness routing, model selection — are <strong>not</strong> governed here. They belong to <code>glas-harness</code> and its <code>rein-*</code> backends (§20). This standard governs what an agent may be authorized to do; <code>glas-harness</code> governs how an agent session is conducted. Rule 2 is the seam between them, and neither side may treat its own half as sufficient.</p>
|
||||
<p>v0.6 claimed these rules in its change log and did not write them. Found by <code>kings-guard</code>, which is the repository they bind hardest and which offered to assent to them sight-unseen.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Layer catalog</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Repository</th><th>Layer</th><th>Role</th><th>Owns</th></tr></thead><tbody><tr><td><code>info-tech-canon</code></td><td>Taxonomy</td><td>—</td><td>ecosystem-wide semantic contracts and terminology</td></tr><tr><td><code>net-kingdom</code></td><td>Taxonomy</td><td>—</td><td>NetKingdom standards of record; publication</td></tr><tr><td><code>key-cape</code></td><td>Tooling</td><td>—</td><td>packaged identity tooling; IAM profile; authentication</td></tr><tr><td><code>OpenBao</code></td><td>Tooling</td><td>—</td><td>secret storage, leases, PKI, dynamic secret engines</td></tr><tr><td><code>user-engine</code></td><td>Engine</td><td>PIP</td><td>users, accounts, memberships</td></tr><tr><td><code>tenant-engine</code></td><td>Engine</td><td>PIP</td><td>tenant-as-an-entity facts</td></tr><tr><td><code>zone-engine</code></td><td>Engine</td><td>PIP</td><td>zone identity and membership — offline reference conformance per its 2026-08-23 disposition</td></tr><tr><td><code>secrets-engine</code></td><td>Engine</td><td>Lifecycle</td><td>credential abstraction, custody, lifecycle</td></tr><tr><td><code>audit-core</code></td><td>Engine</td><td>Evidence</td><td>audit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6)</td></tr><tr><td><code>access-engine</code></td><td>Engine</td><td><strong>PDP</strong></td><td><strong>the policy decision</strong> — the only decision point (§6)</td></tr><tr><td><code>approval-engine</code></td><td>Engine</td><td>PIP</td><td>the approval object — durable, authenticated, consumable, atomically supersedable (§9.4)</td></tr><tr><td><code>maturity-engine</code></td><td>Engine</td><td>PIP</td><td>graded progression against declared criteria and evidence; the gap register; capability readiness (§9.5)</td></tr><tr><td><code>gate-house</code></td><td>Staff</td><td>—</td><td>security doctrine, authority context, curriculum; <strong>conformance review — through <code>maturity-engine</code> (§9.5)</strong></td></tr><tr><td><code>ops-mason</code></td><td>Staff</td><td>PEP-shaped</td><td>building and tearing down access routes and perimeters</td></tr><tr><td><code>ops-warden</code></td><td>Staff</td><td>PEP-shaped</td><td>operational access lanes, stewardship, runbooks; SSH certificate issuance — <strong>declared-gap</strong> (§9.1, §13)</td></tr><tr><td><code>kings-guard</code></td><td>Staff</td><td>—</td><td>adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation <strong>pending</strong>; <strong>proposes</strong> containment, which it does not own (§9.2)</td></tr><tr><td><code>whitehat-security</code></td><td>Staff</td><td>—</td><td>offensive validation</td></tr></tbody></table></div>
|
||||
<p>An <strong>actuation surface</strong> — reduce authority, require step-up, isolate a workload — is catalogued nowhere because it does not exist. See §9.2: it is an Engine concept held at zero, not a Staff capability.</p>
|
||||
<p><code>access-engine</code> is the ruled name for the repository currently called <code>flex-auth</code>; both denote the same authority until the governed rename completes. Execution conditions for that rename are recorded in its migration decision, not here.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>The binding rule</h2>
|
||||
<div class="rule-quote"><p><strong>Staff never touches Tooling directly. It acts only through Engine APIs.</strong></p></div>
|
||||
<p>A Staff repository MUST NOT hold a direct client for a Tooling-layer system — no direct database connection, no direct OpenBao client, no direct cluster mutation — outside the shapes below. This is the architectural form of <em>no privilege from cognition</em>, and it is deliberately mechanically checkable.</p>
|
||||
<p><strong>Scope.</strong> "Tooling-layer system" means a system catalogued as Tooling in §4. Infrastructure the estate runs but has not catalogued — the State Hub, <code>llm-connect</code>, and similar — is outside this rule, because a rule that silently covered them would put every Staff repository in undeclared violation on adoption day: they all write progress events. Such clients SHOULD be recorded in the repository's declaration as non-Tooling for completeness of the check, and the way to bring one under §5 is to catalogue it in §4, deliberately.</p>
|
||||
<p>Raised by <code>ops-warden</code>, which held clients for both and declined to resolve the scope question on gate-house's behalf.</p>
|
||||
<p><strong>The carve-out sunsets.</strong> It is a pressure valve, and a valve left open becomes a second persistence plane under the Staff layer — which §3.4 forbids in spirit. Three rules bound it: every non-Tooling client MUST be listed in the repository's declaration; an uncatalogued store that another layer <strong>reads</strong> MUST, within two review intervals, either be catalogued as Tooling in §4 or be declared a gap under §5.3; and a Staff-owned event bus or memory store MUST NOT become the estate's de facto state plane. Today's instances are the State Hub and <code>llm-connect</code>; tomorrow's are agent memory, tool-call traces, and prompt caches (§3.4).</p>
|
||||
<p>Three shapes are sanctioned. Everything else is a violation.</p>
|
||||
<h3>5.1 Read-only diagnostic observation</h3>
|
||||
<p>A Staff repository MAY read Tooling state for diagnostics where the owning engine exposes no equivalent. It MUST be declared in the repository's <code>INTENT.md</code>. It grants no write, and it is an engine gap to close, not a standing arrangement.</p>
|
||||
<h3>5.2 Conduit</h3>
|
||||
<p>A Staff repository MAY run the <strong>owner's</strong> tool under the <strong>caller's</strong> identity, supplying no authority of its own. The test is the supplied-authority property: the conduit MUST NOT present its own credential, MUST NOT widen what the caller could already do, and MUST be reconstructable as the caller's action in audit.</p>
|
||||
<p>A conduit that presents its own token is not a conduit; it is §5.3 or a violation. This shape MUST be declared, and the no-authority property SHOULD be covered by a test.</p>
|
||||
<p>The reconstructability requirement is an audit-dependent claim and is therefore bounded by §9.6: the archive shows the conduit actions it received, not that it received all of them.</p>
|
||||
<h3>5.3 Declared engine gap</h3>
|
||||
<p>Where a Staff repository must contact Tooling directly and no engine exposes the capability, it MUST declare the contact rather than take an exemption. A declared gap carries, machine-readably:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>capability</code></td><td>what the contact does</td></tr><tr><td><code>intended_owner</code></td><td>the engine that should own it</td></tr><tr><td><code>blocked_on</code></td><td>why it cannot move today</td></tr><tr><td><code>review</code></td><td>a date, not "when convenient"</td></tr></tbody></table></div>
|
||||
<p>A declared gap is <strong>tracked non-conformance</strong>, not conformance. It does not expire on its own and it is not a licence to add more. It exists because a rule offering no lane for a real sanctioned case gets satisfied by relabelling rather than by closing the gap — and a tracked gap is visible, whereas a relabelled one is not.</p>
|
||||
<p>Prior art: <code>ops-warden</code> runs equivalent machinery for delegated lanes (27 catalog entries carrying <code>delegation:</code>, queryable via <code>warden route gaps</code>), and has offered it as reusable.</p>
|
||||
<p><strong>No fourth "operator of third-party Tooling" shape.</strong> It has been proposed, on the argument that someone must operate <code>OpenBao</code> and every operational necessity otherwise looks like a gap. Declined: §5.3 already sanctions the operation while keeping it visible, and a clean "operator" shape would convert a tracked gap into a permanent allowance — the relabelling failure this standard exists to prevent. A permanent operational necessity is a declared gap whose review interval keeps returning, which is the correct amount of friction. If the review becomes ceremonial, that is an argument for closing the gap, not for renaming it.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>One decision point</h2>
|
||||
<p><code>access-engine</code> is the only policy decision point in NetKingdom. No other repository, in any layer, may render or cache authorization decisions.</p>
|
||||
<p>First ruled in <code>zone-engine/INTENT.md</code> §5 — <em>"flex-auth is the policy decision point. It stays the only one."</em> The failure mode, from the same source: <em>"It becomes a second decision point… it would arrive as a small convenience."</em></p>
|
||||
<h3>6.1 Compiled data that determines an outcome is still deciding</h3>
|
||||
<p>A registry, cache, or schema that resolves a result before the engine runs has decided early. Provenance MUST remain reconstructable from the engine's decision record.</p>
|
||||
<h3>6.2 Doctrine reaches the decision as an input, or it is not applied</h3>
|
||||
<p>This rule binds gate-house on the same terms. <strong>An authority ceiling, mandate constraint, or operating-mode restriction that determines an outcome MUST reach the decision either as an input claim on the request or as a rule in the versioned policy package</strong>, so that its application is reconstructable from the decision record.</p>
|
||||
<p>Doctrine that influences outcomes by any other route is a second decision point wearing an author's hat. This is not a limit on gate-house's authorship; it is what keeps that authorship auditable at decision time.</p>
|
||||
<h3>6.3 No Staff repository may host a decision point</h3>
|
||||
<p>A deterministic authority boundary inside a non-deterministic layer contradicts the invariant the estate is built on. gate-house was re-cut on this ground.</p>
|
||||
<h3>6.4 The enforcement point</h3>
|
||||
<p>The standard has been precise about the decision and silent about the gate. A decision that nothing refuses to proceed without is advice.</p>
|
||||
<p>A <strong>PEP</strong> is any runtime that causes a protected side effect. It is a <em>shape</em>, not a repository: <code>ops-warden</code> issuing a certificate, <code>ops-mason</code> opening a route, and any protected system acting on a verdict are all PEP-shaped. Being PEP-shaped does not move a repository out of its layer.</p>
|
||||
<p>Four obligations, and they are normative:</p>
|
||||
<ol><li><strong>No side effect without a decision record, or a recorded stance.</strong> A PEP MUST NOT perform the protected action unless it holds a decision from <code>access-engine</code> identifying the request it was rendered for, <strong>or</strong> its declared §9.3 stance for the applicable scope permits proceeding without one <strong>and the application of that stance is recorded in place of the decision</strong>. The second limb is stricter than silence, not looser: a fail-open result is metadata, never an absent record. <code>ops-warden</code> <code>ca.py</code> writes the zone, the failure mode, and a decision id present only where a decision was rendered.</li></ol>
|
||||
<p>v0.6's unqualified form made the shipped stance §9.3 sanctions into a violation — the same defect as v0.5's §9.1, a rule written for the clean case producing a false result on the adjacent case already sanctioned elsewhere. Raised by <code>ops-warden</code>, which is the reference shape for limb two.</p>
|
||||
<ol><li><strong>No recaching of the verdict beyond its own binding.</strong> A stored verdict replayed <strong>outside the decision's stated binding and lifetime</strong> is a second decision point deciding early (§6.1). Within them it is the decision being used as issued — a session-bound allow under §9.7.1 is used across later requests by construction, and v0.6 forbade what §9.7.1 permits.</li></ol>
|
||||
<p>The test is mechanical, not a matter of implementer judgement: replay is permitted <strong>iff the canonical request digest matches and the decision's lifetime holds</strong>. <code>access-engine</code> computes that digest over normalised subject, action, resource, and context, and it is already in every decision binding. A retry after a transport failure is therefore the same request; a different resource is not.</p>
|
||||
<p><strong>Negative caching is permitted, narrowly.</strong> A cached DENY cannot manufacture authority — §8's asymmetry holds — and protects against retry storms. It is permitted where the refusal is itself recorded against the request that was refused (obligation 4), and where the cache lifetime is declared alongside the stance map. A stale deny is an availability failure and will be misdiagnosed as a policy one, so it must be visible as what it is. Ruled explicitly because it is the first thing an implementer under load reaches for. Raised by <code>access-engine</code>.</p>
|
||||
<ol><li><strong>A declared unreachable-engine stance</strong> (§9.3): total, per zone or equivalent scope, no implicit default, no per-call discretion, published rather than held in code comments or in a dataclass default. <code>ops-warden</code> <code>ADR-0009</code> and its <code>pep-stance.yaml</code> are the reference shape.</li></ol>
|
||||
<p>The published map MUST equal the shipped behaviour, and that equality SHOULD be asserted by a test. A published map free to drift from the code is worse than none, because it invites reliance it cannot support. Raised by <code>ops-warden</code>, which found its own map unpublished while being cited as the reference for this obligation.</p>
|
||||
<ol><li><strong>Reconstructability</strong>, bounded by §9.6.</li></ol>
|
||||
<p>Every PEP-shaped consumer MUST publish its stance map at a path named in its layer declaration, and those maps MUST be inventoried in the §13.1 register until <code>maturity-engine</code> can hold them. §9.3 is otherwise a ruling with no register behind it, and <em>"<code>z0</code>–<code>z2</code> and unknown fail open"</em> becomes the estate's real policy without anyone having compiled it into a versioned package.</p>
|
||||
<p>v0.6 named a register that did not exist — a requirement whose register is missing is a capability catalogued without a surface, by §9.1's own logic. Raised independently by <code>ops-warden</code> and <code>access-engine</code>; §13.1 now exists, and its first inventory has one row, which is itself the finding.</p>
|
||||
<p>Raised by the 2026-08-29 independent assessment: NIST ZTA splits decide from enforce, and this standard had only the first half.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Relationship to the Active Secrets Management Canon</h2>
|
||||
<pre>Staff interactive, non-deterministic ≈ Cognitive Plane
|
||||
Engines deterministic APIs ≈ Authority Plane
|
||||
Tooling deterministic state ≈ Execution Plane
|
||||
Taxonomy cross-cutting language</pre>
|
||||
<p><em>Cognition proposes. Authority disposes. Infrastructure executes.</em> is therefore NetKingdom's layering rule, not only its security maxim. §5 and §6 are that principle applied to repositories rather than to requests.</p>
|
||||
</section>
|
||||
<section id="s8"><h2><span class="sn">08</span>Vocabulary demarcations</h2>
|
||||
<div class="scroll"><table><thead><tr><th>Term</th><th>Belongs to</th><th>Not</th></tr></thead><tbody><tr><td><strong>access lane</strong></td><td>ops-warden, ops-mason (Staff) — how a worker reaches a host</td><td>the decision whether they may</td></tr><tr><td><strong>access rule</strong></td><td>access-engine (Engine) — whether an actor may act</td><td>the route by which they arrive</td></tr><tr><td><strong>control plane</strong></td><td>Engine layer</td><td>a Staff repository's self-description</td></tr><tr><td><strong>doctrine</strong></td><td>gate-house</td><td>a lane owner's runbook</td></tr><tr><td><strong>runbook</strong></td><td>the Staff repository stewarding the lane</td><td>a substitute for doctrine</td></tr><tr><td><strong>posture</strong></td><td>kings-guard publishes; gate-house defines its authority meaning; access-engine renders it</td><td>a privilege source</td></tr></tbody></table></div>
|
||||
<p>Posture carries an asymmetry that MUST hold: adaptive systems may reduce authority, require step-up, or request containment. They MUST NOT probabilistically manufacture additional authority.</p>
|
||||
<p>The asymmetry is what bounds the damage when observation is incomplete (§9.6): suppressed evidence can only prevent a tightening that should have happened, never engineer a loosening. That is an argument for keeping it absolute rather than situational.</p>
|
||||
</section>
|
||||
<section id="s9"><h2><span class="sn">09</span>Capability assignment</h2>
|
||||
<h3>9.1 The catalog may not assign what the rules forbid discharging</h3>
|
||||
<p>A Staff repository MUST NOT be catalogued in §4 as owning a capability it cannot discharge under these rules. Two marks distinguish the two ways that happens, and they are not interchangeable:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Mark</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>pending</strong></td><td>No route exists. No engine exposes the capability, the repository makes no Tooling contact, and the capability is <strong>zero</strong> — not degraded.</td></tr><tr><td><strong>declared-gap</strong></td><td>A route exists through a §5.3 declared gap. The capability <strong>works</strong> and is tracked, with an intended owner and a review date in §13.</td></tr></tbody></table></div>
|
||||
<p>v0.4 had only <code>pending</code>, which forced a false choice. <code>ops-warden</code> holds production-verified SSH certificate issuance through a declared OpenBao contact; marking it <code>pending</code> would have told readers the repository does not do the one thing it demonstrably does daily, while leaving it unmarked left §4 disagreeing with §13. Neither is acceptable, and the defect was in this section rather than in the catalog.</p>
|
||||
<p><code>pending</code> was written for <code>kings-guard</code>'s containment — no route, capability zero — and remains correct there. <code>declared-gap</code> is the case §5.3 was added to sanction. Raised by <code>ops-warden</code>.</p>
|
||||
<p>Both marks apply per capability, not per repository. A repository may hold one capability outright, another under a declared gap, and a third pending.</p>
|
||||
<h3>9.2 Actuation does not exist, and containment is not Staff's to own</h3>
|
||||
<p>Self-healing needs four verbs: observe, evaluate, decide, actuate. Observation is <code>kings-guard</code> and is unstaffed (§12). Evaluation is <code>maturity-engine</code> and is seeded. Decision is <code>access-engine</code> and works. <strong>Actuation has no surface at all</strong>, and a model with no actuation surface describes a diagnosis machine rather than a healing one.</p>
|
||||
<p>v0.5 marked containment <code>pending</code> against <code>kings-guard</code>, which was the right mark on the wrong repository. Containment is not a Staff capability that happens to lack a route: <strong>reduce authority, require step-up, isolate a workload</strong> are authority-changing operations, and under §6 an authority-changing operation is rendered by an Engine and enforced by a PEP (§6.4). A Staff repository proposes containment; it never performs it.</p>
|
||||
<p>The <strong>actuation surface</strong> is therefore an Engine concept — likely a small surface on <code>access-engine</code> together with runtime PEPs — carrying the same reconstructability rules as any other decision: a containment action is a decision record, not a side channel.</p>
|
||||
<p>It is <strong>unowned and held at zero</strong>. <code>access-engine</code> is recorded in §13 as a <em>proposed</em> owner and has explicitly not reviewed it (<code>FLEX-DEC-2026-002</code>). No repository may be catalogued as owning containment until the surface exists — §9.1 applied to the estate's most operationally tempting gap, and the standard's own medicine.</p>
|
||||
<p>Until then <code>kings-guard</code> proposes and judges, its containment claim stays at zero rather than degraded, and no argument may assume the estate can contain anything automatically.</p>
|
||||
<h3>9.3 Degraded mode: two failure cases, two owners</h3>
|
||||
<p>v0.4 collapsed two failures into one rule. They have different owners because one has an evaluator in the path and the other does not.</p>
|
||||
<p><strong>Input degradation — the engine's.</strong> Where <code>access-engine</code> is reachable but cannot reach its own inputs, the deterministic <em>fail to reduced authority</em> default belongs to the engine. This keeps the decision at the decision point and keeps the fallback deterministic, which a Staff-layer fallback could never be.</p>
|
||||
<p><strong>Engine unreachable — necessarily the consumer's.</strong> Where <code>access-engine</code> is not reachable at all, it applies nothing, because it is not running. Whatever happens next is the consumer's behaviour by construction: fail-open is not expressible by a policy decision point, since there is no evaluator in the path to express it. A standard that assigns this to the engine assigns it to nobody.</p>
|
||||
<p>The consumer's residue is bounded rather than free. A protected system MUST declare its unreachable-engine stance ahead of time, per zone or equivalent scope, and that stance MUST be auditable and total — no implicit default, no per-call discretion. <code>ops-warden</code> <code>ADR-0009</code> already satisfies this: a total per-zone map, open for <code>z0</code>–<code>z2</code> and unknown, closed for <code>z3-critical</code>, replacing the global <code>policy.enabled</code> / <code>policy.fail_closed</code> switches it superseded.</p>
|
||||
<p><strong>Unchanged: engine-unavailable is not grounds for a Staff break-glass path.</strong> The distinction is whether an engine is there to ask. A bypass around a <em>reachable</em> engine is a second decision point, and an incident is when an attacker most wants that shortcut. A consumer choosing its declared behaviour when there is no engine to ask is not a bypass; it is the only thing left.</p>
|
||||
<p>Contested by <code>flex-auth</code> (<code>FLEX-DEC-2026-002</code>), which has held since 2026-08-19 that fail-open is not expressible by a PDP, and which noted v0.4 collided with shipped behaviour in a repository that had assented to this standard.</p>
|
||||
<h3>9.4 Approvals are an engine concept, not a Staff or audit concern</h3>
|
||||
<p>The approval object — durable, authenticated entries, distinct-approver counting, atomic supersession, single consumption, revocation without holder cooperation — is owned by <code>approval-engine</code>.</p>
|
||||
<p>It is not Staff's: §3.4 forbids Staff holding state another layer depends on at runtime. It is not the decision point's: an evaluator that owns the object it evaluates is self-dealing. It is not the audit fabric's: an approval needs mutable, in-path, current-state semantics, and an append-only archive is built for the opposite property.</p>
|
||||
<p><code>access-engine</code> consumes approvals as <strong>input claims</strong> under §6.2 and never mutates them. Every issuance, use, supersession, and revocation is emitted to <code>audit-core</code>: the operative state and the evidence record are different artifacts with different owners.</p>
|
||||
<p>The evidence guarantee is bounded, and the bound is <code>audit-core</code>'s <code>docs/integrity.md</code>, not its INTENT principle 6. An in-database hash chain detects a rewritten payload only if the attacker does not also recompute the suffix — which a database owner can. Detection against that class requires the external chain-head attestation, and even with it the store is not WORM, object lock, or archival custody. <code>tamper_evidence</code> is therefore conditional on live preconditions, not a property of the store at rest, and approval events receive exactly the guarantee every other source receives.</p>
|
||||
<p><strong>Emission atomicity is <code>approval-engine</code>'s obligation.</strong> An approval MUST NOT be issued, consumed, superseded, or revoked without the corresponding event being durably queued in the same transaction.</p>
|
||||
<p><strong>The queue MUST be local.</strong> The durable queue MUST live in <code>approval-engine</code>'s own transactional store, and <strong>no synchronous dependency on <code>audit-core</code> may sit inside the state-change transaction</strong>. With a genuine local outbox, fail-closed triggers only when <code>approval-engine</code>'s own store is unavailable — where the change could not have been recorded anyway — and an <code>audit-core</code> outage does not block a revocation. Satisfying the requirement by emitting synchronously to <code>audit-core</code> inside the transaction is also atomic, and turns an audit outage into an inability to revoke: the operation least tolerable to block during an incident, and the same coupling this section rejects for reads. Raised by <code>audit-core</code>. <code>audit-core</code> reports what it received and does not imply it is everything that happened; without atomic emission the evidence half is silently incomplete and nothing detects the gap. This is a condition of <code>audit-core</code>'s assent (<code>AUDIT-IN-0001</code>) and belongs in <code>approval-engine</code>'s contract before the evidence half is treated as load-bearing.</p>
|
||||
<p><code>audit-core</code> MUST NOT expose an approval-validity query. Records, yes; a verdict on whether an approval is still valid, never — a consumer branching on that answer would route an authorization decision through the audit fabric, which is what this section exists to prevent. Callers needing current state ask <code>approval-engine</code>.</p>
|
||||
<h3>9.5 Graded progression is an engine concept</h3>
|
||||
<p>Maturity — how far a subject has progressed against declared criteria and submitted evidence — is owned by <code>maturity-engine</code>. Given the same criteria and the same evidence it MUST return the same level; that determinism is what makes it an Engine rather than an opinion.</p>
|
||||
<p>The division with Staff: <strong>gate-house judges and proposes; maturity-engine computes and remembers.</strong> Interpretation is inference and stays Staff. A criterion that cannot be evaluated by rule is not yet a criterion.</p>
|
||||
<p>This closes a defect in v0.2's own catalog: <code>gate-house</code> was assigned conformance review with no engine to act through, which is exactly the §9.1 problem raised against the containment claim. Staff acts only through Engine APIs, including gate-house.</p>
|
||||
<p><strong>A maturity level MUST NOT be compiled into registry content.</strong> Until <code>access-engine</code>'s decision provenance carries a registry-snapshot digest — a gap it self-declared in §13 — a level reaching a decision through the registry is not reconstructable from the decision record. Levels arrive as request claims or as versioned policy rules. Same constraint, and same reason, as zone stance.</p>
|
||||
<p><strong>A maturity level MUST NOT gate a decision directly.</strong> Under §6.1, compiled data that determines an outcome is still deciding. If a level determines whether an action is permitted, it MUST reach <code>access-engine</code> as an input claim or a versioned policy rule under §6.2, never by a consumer branching on a fetched level.</p>
|
||||
<p>Approvals and maturity are deliberate opposites — a closed binary state machine against an open graded ladder — and neither engine may drift toward the other.</p>
|
||||
<h3>9.6 Evidence proves alteration and truncation, not omission at source</h3>
|
||||
<p>An append-only archive with a verified hash chain proves that records were not <strong>altered or truncated after arrival</strong>. It cannot prove that a record was never sent. Against a compromised or buggy source, a suppressed event leaves the chain perfectly intact and verification reports intact.</p>
|
||||
<p>This bound is estate-wide. Statements of the form <em>"the audit record proves it happened"</em> are unsound; the sound form is <em>"the archive proves the records it holds were not altered or truncated after arrival"</em>. Its mirror is equally unsound: <strong>absence of a record is not evidence of non-occurrence</strong>, and no control may read it as such.</p>
|
||||
<p><strong>Load-bearing versus attributive evidence.</strong> The atomicity obligation attaches to the first, not to both:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Kind</th><th>Test</th><th>Obligation</th></tr></thead><tbody><tr><td><span class="kind ">Load-bearing</span></td><td>a control's soundness depends on the event being present or absent — an approval revocation, a containment action, a denial</td><td>emission MUST be atomic with the state change (§9.4)</td></tr><tr><td><span class="kind ">Attributive</span></td><td>the event supports forensic reconstruction and attribution, and no control branches on its presence</td><td>atomicity SHOULD be sought; where it is deliberately traded away, the trade MUST be declared and completeness MUST NOT be claimed</td></tr></tbody></table></div>
|
||||
<p>Where a repository deliberately makes emission non-atomic — <code>ops-warden</code>'s <code># audit must not block signing</code> is the estate's live example, chosen so that an audit-store failure cannot remove production host access — the trade is legitimate for attributive evidence, MUST be declared where the trail is documented, and MUST NOT be described in terms that imply completeness. The availability argument is real in both directions: making it atomic gives the estate's operational access lane a new dependency on its own evidence store.</p>
|
||||
<p><strong>Consequence for adaptive systems.</strong> Suppression does not degrade observation neutrally, it biases it optimistic, and silently: an event never emitted is never evaluated, so no finding is raised and the last posture stands. A confidence score computed from the richness of the record in hand cannot express doubt about the completeness of the stream — a well-formed observation from a 90%-suppressed stream scores high. That is this section's failure reproduced one layer up, in the consumer.</p>
|
||||
<p>Two things follow.</p>
|
||||
<p><strong>Which control covers which threat.</strong> v0.6 read as though emission atomicity closed this section's opening sentence. It does not, and the decomposition is owed to the reader:</p>
|
||||
<div class="scroll"><table class="tm"><thead><tr><th>Threat</th><th>Covered by</th><th>When</th></tr></thead><tbody><tr><td><strong>Accidental omission</strong> — process dies between mutation and emit</td><td class="">emission atomicity, local outbox (§9.4)</td><td class="">prevented</td></tr><tr><td><strong>Adversarial omission</strong> — a compromised source declines to insert, deletes before drain, or drains to nowhere</td><td class="">cadence and reconciliation</td><td class=""><strong>detected, after the fact</strong></td></tr><tr><td>Adversarial omission at a compromised source</td><td class="">—</td><td class=""><strong>nothing in this model prevents it</strong></td></tr></tbody></table></div>
|
||||
<p>The outbox sits inside the blast radius of the component whose compromise this section posits, so it makes emission atomic against crash and partial failure and nothing more. That residual is real and is stated rather than implied. Raised by <code>audit-core</code>, correcting a remedy it had itself proposed.</p>
|
||||
<ol><li><strong>The §8 asymmetry bounds the damage, and this is its clearest payoff.</strong> Because an adaptive system may only reduce authority and never manufacture it, suppression can only prevent a tightening that should have happened. It cannot be used to engineer a loosening. The harm is a missed reduction, not an invented privilege — which is an argument for keeping the asymmetry absolute.</li><li><strong>Silence is a signal, and for load-bearing evidence it is the only control in its class.</strong> A source of <strong>attributive</strong> evidence SHOULD declare an expected emission cadence; a source of <strong>load-bearing</strong> evidence <strong>MUST</strong>. A drop below the declared rate is a finding in its own right — the stream observed, not only its contents — and needs no Tooling contact, because the source publishes its own stream.</li></ol>
|
||||
<p><strong>Rate monitoring is the wrong form for rare events</strong>, and rare is exactly where the stakes are highest: the most valuable event to suppress is the negative one, and revocations, denials, and containment actions are infrequent by nature. A source emitting a handful of revocations a month has no rate to drop below, and suppression is indistinguishable from a quiet month. For <strong>low-volume load-bearing classes</strong> the required form is therefore <strong>positive reconciliation or a heartbeat</strong>: compare the source's own state transitions against the evidence engine's event count per class and treat divergence as a finding, or assert <em>nothing to report</em> as a signed positive claim that can itself go missing. Rate monitoring never produces a claim that can be missing; a heartbeat does. <code>GH-WP-0002-T04</code> is the reference instance. Raised by <code>audit-core</code>.</p>
|
||||
<p>Raised by <code>audit-core</code> against its own principle; extended by <code>kings-guard</code> from its own evaluator and confidence model.</p>
|
||||
<h3>9.7 Decisions have a lifetime</h3>
|
||||
<p>A single decision point deciding on stale claims is a single decision point deciding wrongly. The model has had no temporal law, and the approval race in §16 was its first symptom.</p>
|
||||
<ol><li><strong>Every allow has an explicit lifetime</strong> — a TTL, or a binding to a session or obligation that ends. An allow with no stated end is a standing grant, and standing grants are what this estate exists to remove.</li><li><strong>Revocation and supersession have a visibility deadline</strong>, and its shape differs by role. A <strong>PEP</strong> has one boundary and MUST state one deadline. A <strong>PDP</strong> MUST state a deadline <strong>per input class</strong>, because a decision is a join over sources with unrelated refresh behaviour — approval-claim freshness, registry snapshot cadence, policy package activation, directory ETag. A single number at a PDP is either a fiction or the worst case, and the worst case is the slowest and least visible input. "Eventually" is not a stance; an unstated deadline is an unbounded replay window.</li></ol>
|
||||
<p>A consequence worth naming: a stated deadline for a fact carried by a registry snapshot is unfalsifiable while decision provenance holds no snapshot digest, since nobody can determine afterwards which snapshot a decision read. The deadline and the digest are one gap seen from two sides, which promotes <code>access-engine</code>'s self-declared provenance gap (§13) from housekeeping to a conformance prerequisite. Raised by <code>access-engine</code> against its own backlog.</p>
|
||||
<ol><li><strong>Consumption is a state change, never an inference.</strong> An approval is consumed by a mutation in <code>approval-engine</code> (§9.4). It MUST NOT be inferred from the existence of a decision record — the decision precedes the action and the action precedes consumption, so a decision record proves an intent to act, not an act.</li><li><strong>Three failure modes are named, and each needs an owner</strong>: an allow rendered then never consumed; a double consumption by racing callers; consumption after the authorized action has already failed. Neither engine closes these alone. Recorded in §16 and <code>GH-WP-0002-T06</code>.</li></ol>
|
||||
<h3>9.8 Partition is not one-dimensional</h3>
|
||||
<p>§9.3 handles <em>engine unreachable</em>. A real estate spends most of its incident time in the band between reachable and gone: partial PIP reachability, clock skew across a decision and its enforcement, and two consumers with different declared stances seeing different worlds at the same moment.</p>
|
||||
<p>Two rules hold today, and the rest is open (§16). A PEP MUST resolve its own stance from its declared map without consulting another consumer — divergent views are expected and are not a coordination problem to be solved at enforcement time. And where clock skew could extend a lifetime under §9.7, the shorter reading governs.</p>
|
||||
</section>
|
||||
<section id="s10"><h2><span class="sn">10</span>Changing layer</h2>
|
||||
<p>A repository's layer is not permanent. <code>zone-engine</code> changed layer in practice when its runtime hypothesis was falsified.</p>
|
||||
<p>A layer change MUST be recorded as a decision, MUST update the repository's <code>INTENT.md</code>, and MUST obtain assent from the repositories whose boundaries move. A repository MUST NOT acquire a new layer's permissions by gradual practice.</p>
|
||||
<p>"No gradual practice" needs a check rather than a sentence. A layer change MUST carry six artifacts, written from the <code>zone-engine</code> case that the procedure should have been derived from in the first place:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Artifact</th><th>Why</th></tr></thead><tbody><tr><td>before/after <code>INTENT.md</code></td><td>the declaration is the conformance surface (§11)</td></tr><tr><td>client inventory</td><td>what the repository holds against Tooling, before and after</td></tr><tr><td>gap inventory</td><td>which §5.3 gaps close, open, or transfer</td></tr><tr><td>assent list</td><td>every repository whose boundary moves</td></tr><tr><td>state-migration decision</td><td>what happens to live state and to consumers reading it</td></tr><tr><td>permission freeze</td><td>no new permissions of the target layer are exercised until the cut completes</td></tr></tbody></table></div>
|
||||
<p>The freeze is the one that makes the rule checkable: a repository mid-change holds its old permissions, not the union of both.</p>
|
||||
</section>
|
||||
<section id="s11"><h2><span class="sn">11</span>Conformance</h2>
|
||||
<p>Conformance has four states, and the distinction between the last two is the point:</p>
|
||||
<div class="scroll"><table><thead><tr><th>State</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>Conforming</strong></td><td>no Tooling contact, or only §5.1/§5.2 shapes, declared</td></tr><tr><td><strong>Blocked-clean</strong></td><td>the capability does not exist because no engine exposes it, and the repository makes <strong>no</strong> Tooling contact — §9.1 <code>pending</code>, and not a non-conformance</td></tr><tr><td><strong>Declared gap</strong></td><td>a §5.3 contact with owner, blocker, and review date — tracked non-conformance</td></tr><tr><td><strong>Undeclared violation</strong></td><td>anything else — a finding</td></tr></tbody></table></div>
|
||||
<p><strong>Blocked-clean is not a lesser state than conforming.</strong> A repository that declined a break-glass path and left a capability at zero has complied at cost; a repository that quietly opened a direct client and declared nothing has not. Any downstream scoring — <code>maturity-engine</code> included (§9.5) — MUST NOT rank the first below the second. Raised by <code>kings-guard</code>, whose three gaps are all of this kind and which would otherwise have been graded down three times for having taken the standard seriously.</p>
|
||||
<p><strong>Who must declare.</strong> A repository the estate authors declares its layer in its own <code>INTENT.md</code>. For a component the estate catalogues but does not author — third-party or vendored, such as <code>OpenBao</code> — the §4 catalog row <strong>is</strong> the declaration, and no <code>INTENT.md</code> obligation attaches. A rule that assigns an obligation the holder cannot discharge is the §9.1 defect applied to conformance rather than capability.</p>
|
||||
<p>A layer stated <em>about</em> a repository by another repository is not a declaration. Review notes, catalog rows, and correspondence record an intent to adopt; only the repository's own file conforms.</p>
|
||||
<p><strong>Declaration form.</strong> Because prose cannot distinguish a declaration from a transcribed review, a declaration MUST carry a machine-readable form: a <code>layer:</code> key in the <code>INTENT.md</code> frontmatter, or an equivalent declaration file. Without it this section asserts a property it cannot deliver — the defect this standard has now corrected three times elsewhere. <code>ops-warden</code> has implemented a reference form (<code>layer.yaml</code>, a conformance script, and a test covering the §5.2 no-authority property) and offered it to the repositories that have yet to declare. Raised by <code>audit-core</code>, which noted that <code>flex-auth</code>'s conforming declaration is legible as one only by following its decision trail.</p>
|
||||
<p>Mechanically checkable:</p>
|
||||
<ul><li>every estate-authored repository in §4 carries a machine-readable layer declaration;</li><li>every direct Tooling client in a Staff repository maps to a declared §5.1, §5.2, or §5.3 entry, and non-Tooling clients are recorded so the check is total;</li><li>no repository other than <code>access-engine</code> exposes an authorization decision surface;</li><li>no §4 capability is catalogued without an engine surface, a <code>pending</code> mark, or a <code>declared-gap</code> mark.</li></ul>
|
||||
<p>Requires review: whether claims stay inside layer permissions; whether compiled or cached data has become an early decision (§6.1); whether doctrine is reaching decisions as declared inputs (§6.2); whether the §8 vocabulary is used correctly.</p>
|
||||
</section>
|
||||
<section id="s12"><h2><span class="sn">12</span>The conformance loop</h2>
|
||||
<p>Doctrine no engine implements is fiction. The loop is normative, not aspirational:</p>
|
||||
<pre>gate-house asserts an invariant
|
||||
→ the engines implement it, or declare a gap
|
||||
→ whitehat-security tries to break it
|
||||
→ kings-guard observes it in operation
|
||||
→ findings return to gate-house as doctrine change</pre>
|
||||
<p>A finding that a rule is unsatisfiable is a <strong>success</strong> of this loop, not a failure of the reporting repository. Four of this standard's five versions exist because a reviewing repository used it.</p>
|
||||
<p><strong>Step four is currently aspiration.</strong> <code>kings-guard</code> has disclosed that it has never observed anything in operation: the pilot is specified and scaffolded, every input is a hand-built fixture, and no test has met a real event. Until it reports otherwise, no argument in this estate may assume an invariant is being watched in practice because §12 lists a repository against that step.</p>
|
||||
</section>
|
||||
<section id="s13"><h2><span class="sn">13</span>Open gaps</h2>
|
||||
<p>Two different things are recorded here, and they are opposite conformance states (§11). A <strong>declared contact</strong> means the repository touches Tooling because no engine exposes the capability. An <strong>unowned capability</strong> means no route exists and the repository makes no contact at all. Reading them as one list would grade restraint as though it were non-conformance.</p>
|
||||
<p>An <code>intended owner</code> is a <strong>proposal to</strong> the named repository, not an assignment <strong>onto</strong> it. §2 keeps ownership in the repository's own <code>INTENT.md</code>, so the register distinguishes proposed from assented.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Gap</th><th>State</th><th>Declared by</th><th>Owner</th><th>Owner status</th></tr></thead><tbody><tr><td>SSH-CA signing write (<code>VaultCA</code>, <code>bao kv put</code>)</td><td>declared-contact</td><td>ops-warden</td><td>secrets-engine</td><td>proposed</td></tr><tr><td>Authentication / assurance evidence</td><td>unowned-capability</td><td>kings-guard</td><td>identity layer + audit-core</td><td><strong>access-engine declined</strong></td></tr><tr><td>Secret-use evidence</td><td>unowned-capability</td><td>kings-guard</td><td>secrets-engine</td><td>proposed</td></tr><tr><td>Actuation / containment surface</td><td>unowned-capability</td><td><strong>gate-house (estate-wide)</strong></td><td>access-engine + runtime engines</td><td>proposed</td></tr><tr><td>Identity and secret observation</td><td>unowned-capability</td><td>kings-guard</td><td>as above</td><td>proposed</td></tr><tr><td>Stance-map register had no implementation</td><td>declared-contact</td><td>ops-warden, access-engine</td><td>gate-house</td><td>resolved in §13.1</td></tr><tr><td>Registry-snapshot digest in decision provenance</td><td>declared-contact</td><td>flex-auth</td><td>flex-auth</td><td>self-declared</td></tr><tr><td>Approval storage and lifecycle</td><td>—</td><td>flex-auth</td><td>approval-engine</td><td>assigned (§9.4)</td></tr><tr><td>Approval evidence</td><td>—</td><td>gate-house</td><td>audit-core</td><td><strong>assented</strong> (<code>AUDIT-IN-0001</code>)</td></tr><tr><td>Approval evidence custody stronger than the shipped bound — WORM, object lock, transparency log</td><td>unowned-capability</td><td>audit-core</td><td>—</td><td>unassigned</td></tr><tr><td>Emission atomicity for approval state changes</td><td>—</td><td>audit-core</td><td>approval-engine</td><td>assigned (§9.4)</td></tr><tr><td>Non-atomic audit emission on the SSH signing lane</td><td>declared-contact</td><td>ops-warden</td><td>ops-warden</td><td>self-declared, attributive (§9.6)</td></tr></tbody></table></div>
|
||||
<p>The actuation row is no longer attributed to <code>kings-guard</code>. §9.2 ruled that containment is not a Staff capability lacking a route, so <code>kings-guard</code> is not its declarer: the gap is estate-wide and blocks every repository's ability to act. Raised by <code>kings-guard</code>, which asked not to carry a row for a capability the standard had just ruled was never theirs.</p>
|
||||
<p><code>access-engine</code> declined authentication and assurance evidence (<code>FLEX-DEC-2026-002</code>): it consumes assurance claims as input and never redefines them, so evidence of authentication belongs to the identity layer and <code>audit-core</code>. It owns evidence of the decision, which it already emits. The containment surface is recorded as proposed and remains <code>pending</code> under §9.2.</p>
|
||||
<p>Whether approvals warrant custody stronger than every other source is doctrine work not yet done; until it is, approval evidence carries the same guarantee as any other source and §9.6 bounds what may be claimed from it.</p>
|
||||
<p><strong>What is normative here, and what is a snapshot.</strong> Three rules are part of this standard and survive wherever the register lives:</p>
|
||||
<ol><li>the two marks — <code>pending</code> and <code>declared-gap</code> (§9.1);</li><li>the owner-status rule — a proposed owner is not an assigned one (§2);</li><li>the scoring rule — <code>blocked-clean</code> MUST NOT rank below conforming (§11).</li></ol>
|
||||
<p><strong>The table above is a snapshot, not statute.</strong> It moves into <code>maturity-engine</code> as soon as that engine can store state, and the <code>state</code> and owner-status columns MUST survive the migration. A standard that is also a backlog keeps attracting findings that belong in the register, and its review interval is far slower than the register's real rate of change.</p>
|
||||
<h3>13.1 PEP stance-map register</h3>
|
||||
<p>Every PEP-shaped consumer publishes an unreachable-engine stance map (§6.4, obligation 3). This is the inventory until <code>maturity-engine</code> can hold it.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Consumer</th><th>Stance map</th><th>Shape</th></tr></thead><tbody><tr><td><code>ops-warden</code></td><td><code>ops-warden/pep-stance.yaml</code></td><td>total per-zone; open <code>z0</code>–<code>z2</code> and unknown, closed <code>z3-critical</code>; test asserts the published map equals the shipped default (<code>ADR-0009</code>)</td></tr><tr><td><code>ops-mason</code></td><td>—</td><td><strong>not published</strong>; catalogued PEP-shaped in §4</td></tr></tbody></table></div>
|
||||
<p><strong>One row is the finding.</strong> The aggregate of consumer stances is the estate's real authorization behaviour, and it is currently one published map and one absence. <code>access-engine</code> has noted it is the repository positioned to notice when that aggregate diverges from what the policy packages say — which it cannot do while the register is nearly empty.</p>
|
||||
</section>
|
||||
<section id="s14"><h2><span class="sn">14</span>Adoption</h2>
|
||||
<p>Status is <strong>accepted</strong>, on the owner's decision of 2026-08-29.</p>
|
||||
<p>Two things that acceptance does and does not mean, kept apart because <code>ops-warden</code> asked for the distinction:</p>
|
||||
<div class="scroll"><table><thead><tr><th><strong>Boundary assent</strong></th><th>given by the four repositories below, at the version named in each record, and undisturbed since</th></tr></thead><tbody><tr><td><strong>Revision review</strong></td><td>each of the four reviewed v0.6 and returned findings; <strong>every change in v0.7 is the adopted remedy of a finding they raised</strong></td></tr><tr><td><strong>Not claimed</strong></td><td>no repository has reviewed v0.7 <em>as text</em>. The first revision review will confirm or correct it</td></tr></tbody></table></div>
|
||||
<p>Accepting a standard nobody has re-read is a deliberate call: the estate learns more from using it than from another round of prose refinement, and the changes in v0.7 were requested rather than invented. Findings against the accepted text remain welcome and are §12's normal business, not an exception.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Repository</th><th>Record</th><th>Outcome</th></tr></thead><tbody><tr><td>flex-auth</td><td><code>FLEX-DEC-2026-001</code></td><td>assent to all three items; one self-declared non-conformance; two rename conditions</td></tr><tr><td>kings-guard</td><td><code>KG-DEC-2026-001</code></td><td>assent; declined the offered §5 relaxation; raised §9.1</td></tr><tr><td>ops-warden</td><td><code>ADR-0010</code></td><td>assent to all three; veto not exercised; offered the §5.3 amendment</td></tr><tr><td>audit-core</td><td><code>AUDIT-IN-0001</code></td><td>assent to the evidence half with conditions; corrected the rationale twice; raised §9.6</td></tr></tbody></table></div>
|
||||
<p>Adoption for a repository means its <code>INTENT.md</code> declares its layer, its ownership claims fall inside that layer, its Tooling contacts are declared under §5, and any shared boundary has been assented to by the other side.</p>
|
||||
<p><strong>Adoption status as of 2026-08-29: seven of sixteen</strong> estate-authored §4 repositories have declared in their own voice — <code>gate-house</code>, <code>flex-auth</code>, <code>kings-guard</code>, <code>ops-warden</code>, <code>audit-core</code>, <code>approval-engine</code>, <code>maturity-engine</code>. The remaining nine — <code>info-tech-canon</code>, <code>net-kingdom</code>, <code>key-cape</code>, <code>user-engine</code>, <code>tenant-engine</code>, <code>zone-engine</code>, <code>secrets-engine</code>, <code>ops-mason</code>, <code>whitehat-security</code> — carry a layering review note authored by <code>gate-house</code> and have not answered it. Those notes state a layer but do not constitute a declaration, and this standard does not claim estate-wide adoption on their basis. Declaration requests are open as intakes in each.</p>
|
||||
</section>
|
||||
<section id="s15"><h2><span class="sn">15</span>Change log</h2>
|
||||
<p>v0.1 → v0.2:</p>
|
||||
<ol><li><strong>§5 restructured</strong> into three sanctioned shapes. Added §5.2 conduit (ops-warden's question, ruled) and §5.3 declared engine gap (ops-warden's amendment, accepted).</li><li><strong>§6.2 added</strong> — doctrine must reach the decision as an input claim or a versioned policy rule (flex-auth's boundary drawn back, accepted).</li><li><strong>§9 added</strong> — the catalog may not assign a capability the rules forbid discharging; containment marked pending; degraded-mode fallback ruled into the engine (kings-guard's finding).</li><li><strong>§11 restructured</strong> — conformance now has three states, distinguishing a tracked gap from an undeclared violation.</li><li><strong>§12 made normative</strong>, with the explicit statement that an unsatisfiability finding is a success of the loop.</li><li><strong>§13 added</strong> — open gaps register, including the unowned approval storage and lifecycle capability.</li><li>§4 catalog gained the pending mark and ops-warden's SSH certificate lane.</li></ol>
|
||||
<p>v0.2 → v0.3:</p>
|
||||
<ol><li><strong>§9.4 added</strong> — approvals assigned to <code>approval-engine</code>, with the operative state and the evidence record separated between it and <code>audit-core</code>.</li><li><strong>§9.5 added</strong> — graded progression assigned to <code>maturity-engine</code>, closing the §9.1 defect in gate-house's own conformance-review claim, and carrying the guardrail that a level may never gate a decision directly.</li><li>§4 catalog gained both engines; gate-house's conformance-review claim now names the engine it acts through.</li><li>§13 register updated: the approval hole is assigned, two new entries added.</li></ol>
|
||||
<p>v0.6 → v0.7, from four reviews:</p>
|
||||
<ol><li><strong>§3.4 is written.</strong> v0.6 announced the human/agent principal separation in §1 and §15 and left §3.4 byte-identical to v0.5 — a silent edit failure. A rule stated about a standard in its own change log is not a rule. Found by <code>kings-guard</code>. The same failure had also dropped two §16 entries, restored here.</li><li><strong>§6.4 obligation 1 rewritten</strong> — it forbade what obligation 3 blesses. A PEP may proceed under its declared §9.3 stance provided the application of that stance is <em>recorded in place of</em> the decision. Stricter than v0.6 where it counts: a fail-open result is metadata, never silence. Raised by <code>ops-warden</code>.</li><li><strong>§6.4 obligation 2 rewritten</strong> — it forbade the session-bound allow §9.7.1 permits. Scoped to replay outside the decision's own binding and lifetime, with the canonical request digest as the mechanical test, and negative caching ruled permitted where the refusal is recorded and the cache lifetime declared. Raised by <code>access-engine</code>.</li><li><strong>§6.4 obligation 3</strong> gained the requirement that the published stance map equal shipped behaviour, asserted by test. <strong>§13.1</strong> now exists as the register §6.4 mandated and v0.6 did not implement.</li><li><strong>§9.6 gained a threat decomposition</strong> — atomicity prevents accidental omission; cadence and reconciliation detect the adversarial case after the fact; nothing prevents it at a compromised source. Raised by <code>audit-core</code> against its own proposed remedy.</li><li><strong>§9.6 cadence is now MUST for load-bearing sources</strong>, with positive reconciliation or a heartbeat as the required form for low-volume classes, because rate monitoring fails exactly where the stakes are highest.</li><li><strong>§9.7.2 splits by role</strong> — a PDP states a deadline per input class, a PEP one at its boundary. Promotes <code>access-engine</code>'s provenance gap to a conformance prerequisite.</li><li><strong>§3.3's Evidence row</strong> is stated as an estate trade rather than a property, leaving independent-recording-before-effect raisable as a declared exception.</li><li><strong>§17</strong> moves the decision-record schema to <code>access-engine</code>, which argued it against its own interest; <code>kings-guard</code> drafts the emission-cadence schema.</li><li><strong>§13</strong> no longer attributes the actuation gap to <code>kings-guard</code>; it is estate-wide. <strong>§19 removed</strong> — a verdict inside a standard grades the document it lives in. <strong>§17/§18</strong> demoted from H1 to H2.</li><li><strong>§20 added</strong> — the Railiance interaction boundary, on <code>railiance-master</code>'s definitions, including that <code>rein-*</code> is not a fifth axis.</li></ol>
|
||||
<p>v0.5 → v0.6, from the independent assessment of 2026-08-29:</p>
|
||||
<ol><li><strong>§3.3 types the engines</strong> — PDP, PIP, Evidence, Lifecycle, with a role column in §4. A new engine is a PIP unless this standard says otherwise, so "we need an engine for X" cannot drift into "X now decides".</li><li><strong>§6.4 names the enforcement point</strong> — a PEP shape with four obligations: no side effect without a decision record, no local recaching of the verdict, a declared unreachable-engine stance, reconstructability. The standard had the decision and not the gate.</li><li><strong>§9.2 replaced</strong> — containment was marked pending against the wrong repository. Actuation is an Engine concept, unowned, held at zero; Staff proposes containment and never performs it.</li><li><strong>§3.4 separates the two Staff principals</strong> — human and agent share the layer but not blast radius: no standing credential, conduit or engine API only, agent memory is not a state plane, every action reconstructable as the caller's.</li><li><strong>§9.7 puts time into the model</strong> — explicit lifetimes, revocation visibility deadlines, consumption as a state change never inferred, and the three race modes named. <strong>§9.8</strong> states what holds under partition and leaves the rest open.</li><li><strong>§17 requires the Taxonomy artifacts</strong> — claim, decision-record, gap-record, and emission-cadence schemas — without which §6.2 and §11 are reviewable but not compileable. Ownership proposed, not assigned.</li><li><strong>§18 composes the sibling standards</strong> — how zone stance, tenancy posture, and a credential lifecycle event each enter a decision as a claim. They were cited in frontmatter and nowhere in the rules.</li><li><strong>§5 gained a sunset</strong> on the uncatalogued-infrastructure carve-out, and §5.3 <strong>declines</strong> a proposed fourth "operator of third-party Tooling" shape: it would convert a tracked gap into a permanent allowance.</li><li><strong>§10 gained the six artifacts</strong> a layer change must carry, written from the <code>zone-engine</code> case, including a permission freeze during the cut.</li><li><strong>§2 lifts the observation rule</strong> — no estate argument may cite observation that has not happened. <strong>§13</strong> separates its three normative rules from the table, which is now a snapshot due to move into <code>maturity-engine</code>.</li><li><strong>§16</strong> the approval custody question is <strong>decided: no</strong>, rather than left open. <strong>§19</strong> records the fitness verdict, including that the estate can propose and decide but cannot yet watch or act.</li></ol>
|
||||
<p>v0.4 → v0.5, all from review findings:</p>
|
||||
<ol><li><strong>§9.1 split into two marks</strong> — <code>pending</code> (no route, capability zero) and <code>declared-gap</code> (route exists under §5.3, capability works and is tracked). v0.4's single mark would have forced a false <code>pending</code> onto ops-warden's production SSH issuance. Raised by <code>ops-warden</code>.</li><li><strong>§9.3 rewritten</strong> — input degradation is the engine's; engine-unreachability is necessarily the consumer's, bounded by a declared, auditable, total stance. Contested by <code>flex-auth</code>: fail-open is not expressible by a PDP, and v0.4 collided with <code>ops-warden</code> <code>ADR-0009</code>.</li><li><strong>§5 gained a scope rule</strong> — "Tooling-layer system" means a §4 Tooling row; uncatalogued infrastructure is outside §5 and recorded rather than policed. Without it every Staff repository was in undeclared violation for writing progress events. Raised by <code>ops-warden</code>.</li><li><strong>§9.4 requires a local outbox</strong> — no synchronous dependency on <code>audit-core</code> inside the state-change transaction, so an audit outage cannot block a revocation. Raised by <code>audit-core</code>.</li><li><strong>§9.5 forbids compiling maturity levels into registry content</strong> until decision provenance carries a registry-snapshot digest. Raised by <code>flex-auth</code>.</li><li><strong>§9.6 gained the load-bearing / attributive distinction</strong>, the mirror rule that absence is not evidence of non-occurrence, the optimistic-bias consequence for adaptive systems, and silence-as-signal. Raised by <code>kings-guard</code> on top of <code>audit-core</code>'s original.</li><li><strong>§11 gained a fourth state</strong> — blocked-clean, which MUST NOT rank below conforming — and a machine-readable declaration form. Raised by <code>kings-guard</code> and <code>audit-core</code>.</li><li><strong>§13 gained state and owner-status columns</strong> — declared-contact versus unowned-capability, proposed versus assented owner. <code>access-engine</code>'s decline of authentication evidence is recorded. Raised by <code>kings-guard</code> and <code>flex-auth</code>.</li><li><strong>§8</strong> records the asymmetry's payoff under incomplete observation; <strong>§12</strong> records that its fourth step is unstaffed; <strong>§14</strong> corrects the adoption arithmetic and the status contradiction.</li></ol>
|
||||
<p>Amended in place while <code>proposed</code>, 2026-08-28: §11 gained the who-must-declare rule after a conformance sweep found the standard required an <code>INTENT.md</code> declaration from <code>OpenBao</code>, which the estate does not author; and §14 gained the honest adoption count.</p>
|
||||
<p>v0.3 → v0.4:</p>
|
||||
<ol><li><strong>§4 catalog gained <code>audit-core</code></strong> as an Engine, on its own declaration. v0.3 named it as an owner in §9.4 and §13 without cataloguing it — a §11 defect in the standard itself, raised by <code>audit-core</code>.</li><li><strong>§9.4 evidence rationale rewritten</strong> to cite <code>audit-core</code>'s shipped <code>docs/integrity.md</code> bound rather than its INTENT principle 6, and to state that <code>tamper_evidence</code> is conditional on live preconditions.</li><li><strong>§9.4 gained emission atomicity</strong> as <code>approval-engine</code>'s obligation, and the prohibition on <code>audit-core</code> exposing an approval-validity query.</li><li><strong>§9.6 added</strong> — evidence proves alteration and truncation, not omission at source. Estate-wide; the sound and unsound forms of the claim are stated.</li><li><strong>§13</strong> — evidence half recorded as assented with conditions; two new gaps: stronger approval custody (unassigned) and emission atomicity (<code>approval-engine</code>).</li></ol>
|
||||
</section>
|
||||
<section id="s16"><h2><span class="sn">16</span>Open questions</h2>
|
||||
<ul><li>~~Whether approvals warrant archival custody stronger than every other audit source.~~ <strong>Decided (§13): no.</strong> Approval evidence carries the same bound as every other source. The acute risk for approvals is <em>omission</em> — a suppressed revocation — and archival custody does not address omission at all; emission atomicity with a local outbox (§9.4) and a detection surface (<code>GH-WP-0002-T04</code>) do. Leaving it open while calling the evidence half load-bearing created a promise the archive cannot cash. If a future requirement genuinely needs WORM or a transparency log, that is a different store with a different owner, raised then.</li><li>Whether SSH certificate issuance evidence is load-bearing or attributive (§9.6). Ruled attributive here on the argument that no control branches on the presence of a signing record; <code>ops-warden</code> asked for the ruling and the trade is genuinely two-sided, so it is flagged rather than settled.</li><li>Who marks an approval consumed, and at what point relative to the decision (§9.4). <code>flex-auth</code> notes the decision precedes the action and the action precedes consumption, so an allow rendered against an approval then never consumed, or consumed twice by a racing caller, is a gap neither engine closes alone. Needed before <code>FLEX-WP-0017</code> T05.</li><li>Whether other §4 repositories are missing layer declarations; <code>audit-core</code> flagged its own absence and asked whether the catalog needs the same correction elsewhere.</li><li>Whether the gap register migrates from this standard into <code>maturity-engine</code> once that engine exists, leaving the standard to state the rules only.</li><li>Whether Tooling warrants subdivision between third-party and homegrown.</li><li>How a future <code>role-engine</code> divides responsibility with <code>access-engine</code>.</li><li>Whether declared gaps need an estate-wide register rather than per-repository declarations; ops-warden's <code>warden route gaps</code> is candidate machinery.</li><li>Whether non-security repositories adopt the same model. The determinism cut is not security-specific; if non-security Staff also may not hold runtime-dependent state, the estate gets one constitution rather than a security ghetto.</li><li>The rest of §9.8: split brain, partial PIP reachability, and clock skew beyond the two rules stated.</li><li>Publication integrity of the Taxonomy layer itself. This standard demands reconstructability of decisions while its own publication path has no digest, freeze, or rollback discipline.</li><li>The fitness verdict formerly at §19 now lives in <code>net-kingdom/history/2026-08-29-layering-standard-assessment.md</code>. A grade inside a standard of record becomes normative by adjacency and ages against the text it grades. Raised by <code>access-engine</code>. Its two substantive points remain live: observation in production is unstaffed (§12) and actuation has no surface (§9.2).</li><li>The <strong>working companion</strong> (<code>net-kingdom/SECURITY-COMPANION.md</code>, v0.2, root of the repository for onboarding) is the operative form of this statute. The statute governs on disagreement, and a disagreement is a finding. The v0.1 gap <code>access-engine</code> found — publish your stance map, but nowhere saying where, and no inventory obligation — is fixed in v0.2 §5.3.</li><li>How the Railiance operational axes meet this model beyond §20's first statement, which is deliberately minimal.</li></ul>
|
||||
</section>
|
||||
<section id="s17"><h2><span class="sn">17</span>Taxonomy artifacts</h2>
|
||||
<p>§6.2 says doctrine reaches a decision as an input claim or a versioned policy rule. As prose that is a rule a reviewer can apply. As an interface it does not exist, because nothing defines what a claim <em>is</em>. §11 calls itself mechanically checkable while resting on that gap.</p>
|
||||
<p>Four artifacts are therefore required, owned by Taxonomy and versioned like any standard:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Artifact</th><th>Contents</th></tr></thead><tbody><tr><td><strong>request-claim schema</strong></td><td>identity, tenant, zone stance, posture, approval, maturity, assurance — each with its issuer and freshness rule</td></tr><tr><td>~~decision-record schema~~</td><td><strong>moved to <code>access-engine</code></strong> — see below</td></tr><tr><td><strong>gap-record schema</strong></td><td>the §5.3 fields — <code>capability</code>, <code>intended_owner</code>, <code>blocked_on</code>, <code>review</code> — plus the §13 <code>state</code> and owner-status</td></tr><tr><td><strong>emission-cadence declaration</strong></td><td>the expected rate a source publishes, so silence is a finding (§9.6)</td></tr></tbody></table></div>
|
||||
<p>Until these exist, §6.2 and §11 are reviewable but not compileable, and every engine invents its own claim shape at its own boundary.</p>
|
||||
<p><strong>The decision-record schema is not Taxonomy's.</strong> A decision record is the PDP's output artifact — the one thing in the estate only <code>access-engine</code> produces — and §2 keeps ownership in the producing repository's own <code>INTENT.md</code>. Taxonomy authoring the schema for an artifact only one engine emits would invert the ownership rule this standard applies everywhere else. <code>access-engine</code> publishes it as a contract; Taxonomy holds only the shared field vocabulary the claim schema references.</p>
|
||||
<p>Raised by <code>access-engine</code> <strong>against its own interest</strong> — the same §2 argument it used to decline authentication evidence, applied where it takes work on rather than off. Symmetry of that kind is what makes the ownership rule credible.</p>
|
||||
<p><strong>The emission-cadence declaration has a drafter.</strong> <code>kings-guard</code> is its only consumer, cannot implement silence-as-signal without it, and has offered to draft it against <code>qonto-assistant</code> and hand it to whichever Taxonomy repository takes ownership — rather than inventing a local shape, which is the drift §17 exists to prevent. Accepted as a draft; ownership still rests with Taxonomy.</p>
|
||||
<p><strong>Ownership is proposed, not assigned.</strong> <code>info-tech-canon</code> holds ecosystem-wide semantic contracts and <code>net-kingdom</code> holds NetKingdom standards of record; the split between them for these four artifacts is theirs to draw, and §2 keeps ownership in the owning repository's <code>INTENT.md</code>. Neither has assented.</p>
|
||||
</section>
|
||||
<section id="s18"><h2><span class="sn">18</span>Composition with the sibling standards</h2>
|
||||
<p>The related-standards list has been frontmatter and little else. If the following sentences cannot be written, the list is decoration — so they are written here rather than in the siblings.</p>
|
||||
<p><strong>Zone stance</strong> (<code>security-zones_v0.1</code>). A zone answers which scrutiny a workload has qualified for; membership is <code>zone-engine</code>'s. The <em>effect</em> of a zone on a decision belongs in a versioned <code>access-engine</code> policy package, never in registry content — that ruling is zone-engine's §5, and §6.1 is its generalization. Zone stance therefore enters a decision as a <strong>claim on the request or a rule in the package</strong>, and a decision that turned on a zone must name the package version that read it.</p>
|
||||
<p><strong>Tenancy posture</strong> (<code>tenancy-posture_v0.1</code>). Posture is a bounded security-state input, published by its owner and never a privilege source (§8). It enters as a <strong>claim</strong>, carries its own freshness, and the §8 asymmetry binds it: posture may tighten a decision and may never loosen one. A posture too stale to trust is a missing claim, and a missing claim is not permission.</p>
|
||||
<p><strong>Credential lifecycle</strong> (<code>credential-management_v0.2</code>). Issuance, rotation, and revocation are <code>secrets-engine</code>'s and <code>OpenBao</code>'s, downstream of a decision — a credential is an artifact of authority, never its source. A lifecycle event becomes an <strong>input</strong> to a later decision as a claim (this credential is current, this lease is bound to this task), never a side channel that changes an outcome without appearing in the decision record. Revocation visibility is bounded by §9.7.</p>
|
||||
<p>Each of the three composes the same way, which is the point: <strong>facts arrive as claims, effects live in versioned policy, and anything that changes an outcome appears in the decision record.</strong></p>
|
||||
</section>
|
||||
<section id="s20"><h2><span class="sn">20</span>The Railiance interaction boundary</h2>
|
||||
<p>Operations is not NetKingdom's. Workload operations are organized by <strong>Railiance</strong>, whose framework repository is <code>railiance-master</code>, and NetKingdom provides the security and approval framework those operations consume. This section states the boundary as it stands today. It is expected to evolve, and it is written here so that evolution is visible rather than inferred.</p>
|
||||
<p>Definitions are <code>railiance-master</code>'s and are restated, not authored, here.</p>
|
||||
<h3>20.1 What Railiance organizes</h3>
|
||||
<p>A <strong>workload</strong> is a managed running deployable. Human commands, credential patterns, broker actions, approvals, and infrastructure resources that are not themselves deployables <strong>are not workloads</strong> — which is why an approval object (§9.4) is not a Railiance axis and never becomes one.</p>
|
||||
<p>Every workload is operated through four composable axes, each answering a different question about the same workload:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Prefix</th><th>Axis</th><th>Question</th></tr></thead><tbody><tr><td><code>railiance-*</code></td><td>ownership</td><td>Who owns this capability?</td></tr><tr><td><code>rail-*</code></td><td>execution contract</td><td>How does this workload run?</td></tr><tr><td><code>rapp-*</code></td><td>managed package</td><td>What exactly is packaged and operated?</td></tr><tr><td><code>reef-*</code></td><td>substrate</td><td>Where is it bound, and as what operational reality?</td></tr></tbody></table></div>
|
||||
<p><code>rein-*</code> is <strong>not a fifth axis</strong>. Reins are <code>glas-harness</code> agent-harness backends; the name echoes <code>rail-*</code> analogically, not taxonomically. Agentic session semantics — session loops, tool policy, harness routing, model selection — belong to <code>glas-harness</code>. When a rein is installed and operated as a managed service it is a workload like any other, packaged and bound through the four axes above.</p>
|
||||
<h3>20.2 What holds today</h3>
|
||||
<p>For any Railiance consumer of NetKingdom security, without exception:</p>
|
||||
<ol><li>Authorization decisions come from <code>access-engine</code> and from nowhere else (§6).</li><li>Approvals are objects in <code>approval-engine</code>, consumed as claims (§9.4).</li><li>Credentials are materialized by <code>secrets-engine</code> <strong>after</strong> a decision, never as a substitute for one.</li><li>Evidence goes to <code>audit-core</code> under the bound in §9.6.</li><li>Anything causing a protected side effect is <strong>PEP-shaped</strong> and owes the four obligations in §6.4 — including a published unreachable-engine stance in the §13.1 register.</li></ol>
|
||||
<h3>20.3 What is not settled</h3>
|
||||
<p>The mapping between the axes and this model is deliberately thin, because guessing it would be worse than admitting it:</p>
|
||||
<ul><li>A <strong><code>rapp-*</code></strong> is the most likely <em>resource</em> a decision is rendered about, but nothing states its identity form in a request claim.</li><li>A <strong><code>rail-*</code></strong> describes how a workload runs and is therefore where PEP shape is most likely to live — but §6.4 obligations attach to repositories, and a rail is a contract, so whether a rail can <em>carry</em> an obligation is unwritten.</li><li>A <strong><code>reef-*</code></strong> answers where a workload is bound, which is adjacent to a security zone (<code>security-zones_v0.1</code>) without being one. <code>zone-engine</code> records that a reef capping availability for everything bound to it is a canon composition problem. That composition is unwritten.</li><li>The <strong><code>railiance-*</code> ownership axis</strong> names who owns a capability, which is adjacent to the principal a decision is rendered for. Adjacent is not equal, and no rule connects them.</li><li><strong><code>glas-harness</code> and reins</strong> hold tool policy and session semantics for agents, while §3.4 rule 2 holds that an agent acts only through a conduit or an engine API. Those two must compose, and neither side may treat its own half as sufficient. That seam is the most consequential of the five, because it is where "tool availability is not permission" is actually enforced or lost.</li></ul>
|
||||
<h3>20.4 How this boundary changes</h3>
|
||||
<p>An interaction boundary between two frameworks is owned by neither alone. Changes to §20 require assent from <code>railiance-master</code> for the axis definitions and from <code>glas-harness</code> for the session and tool-policy seam, on the same terms as any other boundary in this standard (§10). NetKingdom states what a consumer owes; it does not define what a rail, rapp, reef, or rein <em>is</em>.</p>
|
||||
</section><footer><span>netkingdom-security-layer-model-v0.7 · · accepted</span><span>net-kingdom · canon/standards/security-layer-model_v0.7.md · ce198fc2905687ea90a2892346b6860281ac87f8</span></footer></main></div></div></html>
|
||||
239
build/standards/security-scenario-composition/v0.1/index.html
Normal file
239
build/standards/security-scenario-composition/v0.1/index.html
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
|
||||
<meta name="policy-source-digest" content="17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937">
|
||||
<title>NetKingdom Security Scenario Composition v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-scenario-composition-v0.1</span> <span class="stat">proposed</span> <span>net-kingdom</span> <span>reviewed 2026-08-23</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Scenario Composition v0.1</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-scenario-composition_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2026-11-23</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority boundary</a></li><li><a href="#s3"><span class="n">3</span>Scenario input</a></li><li><a href="#s4"><span class="n">4</span>Fail-closed selection rules</a></li><li><a href="#s5"><span class="n">5</span>Trust sequencing</a></li><li><a href="#s6"><span class="n">6</span>Composition output</a></li><li><a href="#s7"><span class="n">7</span>Conformance</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>This contract defines the deterministic, plan-only boundary between a requested NetKingdom capability set and the independently owned playbook entry points that can realize it. It consumes conformant Playbook Capability Contract v0.1 declarations and produces an owner-routed responsibility, trust, parameter, and readiness handoff.</p>
|
||||
<p>Composition answers <strong>what is selected, in which trust order, with which safe parameters, and who must execute and evidence it</strong>. It does not run a playbook, mint a credential, infer authority, or declare a runtime ready.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Authority boundary</h2>
|
||||
<ul><li>NetKingdom owns scenario intent, provider selection, parameter-policy checks, trust sequencing, and the composed responsibility map.</li><li>The declaration owner owns the playbook or stable entry point, execution, rollback, and readiness evidence.</li><li>Railiance retains deployment execution for Railiance-owned declarations.</li><li>A composed plan is not authorization to execute. Any approval, custody, credential, or change-window gate named by an owner remains in force.</li></ul>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Scenario input</h2>
|
||||
<p>Scenario files conform to <code>canon/schemas/security-scenario_v0.1.schema.json</code>:</p>
|
||||
<pre>id: scenario:c0-local-identity-reference
|
||||
authority: netkingdom
|
||||
initial_trust:
|
||||
- bare_host_trust
|
||||
requires:
|
||||
capabilities:
|
||||
- c0.bootstrap-identity
|
||||
providers:
|
||||
c0.bootstrap-identity: net-kingdom.local-identity
|
||||
parameter_overrides:
|
||||
net-kingdom.local-identity:
|
||||
bootstrap_username: bootstrap-admin</pre>
|
||||
<p><code>authority</code> uses the Playbook Capability Contract vocabulary: <code>platform</code>, <code>netkingdom</code>, or <code>tenant</code>. Parameter sensitivity and tuning-authority rules are applied before a plan is emitted.</p>
|
||||
<p><code>initial_trust</code> lists trust states established outside this composition. The composer never assumes an initial trust state. A required state must be present there or be satisfied by an earlier selected declaration.</p>
|
||||
<p><code>providers</code> pins a required capability to an exact declaration id. A pin is mandatory when more than one valid declaration provides the capability. A pin may not name an unrequested capability or a declaration that does not provide the keyed capability.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Fail-closed selection rules</h2>
|
||||
<p>Composition fails when any of the following is true:</p>
|
||||
<ul><li>a declaration is invalid or declaration ids are duplicated;</li><li>a capability is unknown, duplicated, or has no provider;</li><li>multiple providers match and the scenario does not pin one;</li><li>a provider pin does not match the requested capability;</li><li>an override targets an unselected declaration or unknown parameter;</li><li>an override violates type, constraint, sensitivity, or tuning authority;</li><li>a required parameter has neither a default nor an override;</li><li>a required trust state cannot be established without a cycle or inference.</li></ul>
|
||||
<p>Selection order never resolves ambiguity. Filesystem order, catalog order, lexical order, and prior deployment state are not provider authority.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>Trust sequencing</h2>
|
||||
<p>The composer starts only with the scenario's explicit <code>initial_trust</code> set. It then selects the lexically first <strong>eligible</strong> declaration, where eligible means all of that declaration's required trust states have already been established. After the step, and only for composition purposes, the declaration's satisfied states become available to later steps.</p>
|
||||
<p>Lexical ordering makes independent eligible steps reproducible; it does not grant one provider precedence during selection. If no remaining declaration is eligible, composition fails and reports the unresolved trust states.</p>
|
||||
<p>Readiness checks attached to a satisfied state are obligations for the owning executor. They are not marked satisfied merely because the plan contains them.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>Composition output</h2>
|
||||
<p>A successful output has <code>apiVersion: netkingdom.io/security-scenario-composition/v0.1</code> and <code>kind: SecurityScenarioComposition</code>. It contains:</p>
|
||||
<ul><li>the requested capabilities and exact selected declaration ids;</li><li>effective parameter values with their source, sensitivity, and tuning authority;</li><li>ordered execution handoffs containing owner, repository, entry point, required trust, produced trust, and readiness obligations;</li><li>a flattened responsibility map attributable to declaration ids;</li><li>the final <em>planned</em> trust-state set;</li><li>an explicit <code>execution.permitted: false</code> boundary.</li></ul>
|
||||
<p>The output is non-secret planning material. Declarations and scenarios must use secret references rather than secret values as required by the Playbook Capability Contract.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Conformance</h2>
|
||||
<p>Use the canonical tool:</p>
|
||||
<pre>python3 tools/security-scenario-composer/security_scenario_composer.py \
|
||||
--scenario <scenario.yaml> <declaration.yaml> [<declaration.yaml> ...]</pre>
|
||||
<p>Exit zero means the declarations and scenario compose deterministically. It does not mean the plan was executed or its readiness evidence was observed.</p>
|
||||
</section><footer><span>netkingdom-security-scenario-composition-v0.1 · · proposed</span><span>net-kingdom · canon/standards/security-scenario-composition_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="ce198fc2905687ea90a2892346b6860281ac87f8">
|
||||
<meta name="policy-source-digest" content="17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937">
|
||||
<title>NetKingdom Security Scenario Composition v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-scenario-composition-v0.1</span> <span class="stat">proposed</span> <span>net-kingdom</span> <span>reviewed 2026-08-23</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Scenario Composition v0.1</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-scenario-composition_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</code></p><p class="sub">Review due: 2026-11-23</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority boundary</a></li><li><a href="#s3"><span class="n">3</span>Scenario input</a></li><li><a href="#s4"><span class="n">4</span>Fail-closed selection rules</a></li><li><a href="#s5"><span class="n">5</span>Trust sequencing</a></li><li><a href="#s6"><span class="n">6</span>Composition output</a></li><li><a href="#s7"><span class="n">7</span>Conformance</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>This contract defines the deterministic, plan-only boundary between a requested NetKingdom capability set and the independently owned playbook entry points that can realize it. It consumes conformant Playbook Capability Contract v0.1 declarations and produces an owner-routed responsibility, trust, parameter, and readiness handoff.</p>
|
||||
<p>Composition answers <strong>what is selected, in which trust order, with which safe parameters, and who must execute and evidence it</strong>. It does not run a playbook, mint a credential, infer authority, or declare a runtime ready.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Authority boundary</h2>
|
||||
<ul><li>NetKingdom owns scenario intent, provider selection, parameter-policy checks, trust sequencing, and the composed responsibility map.</li><li>The declaration owner owns the playbook or stable entry point, execution, rollback, and readiness evidence.</li><li>Railiance retains deployment execution for Railiance-owned declarations.</li><li>A composed plan is not authorization to execute. Any approval, custody, credential, or change-window gate named by an owner remains in force.</li></ul>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Scenario input</h2>
|
||||
<p>Scenario files conform to <code>canon/schemas/security-scenario_v0.1.schema.json</code>:</p>
|
||||
<pre>id: scenario:c0-local-identity-reference
|
||||
authority: netkingdom
|
||||
initial_trust:
|
||||
- bare_host_trust
|
||||
requires:
|
||||
capabilities:
|
||||
- c0.bootstrap-identity
|
||||
providers:
|
||||
c0.bootstrap-identity: net-kingdom.local-identity
|
||||
parameter_overrides:
|
||||
net-kingdom.local-identity:
|
||||
bootstrap_username: bootstrap-admin</pre>
|
||||
<p><code>authority</code> uses the Playbook Capability Contract vocabulary: <code>platform</code>, <code>netkingdom</code>, or <code>tenant</code>. Parameter sensitivity and tuning-authority rules are applied before a plan is emitted.</p>
|
||||
<p><code>initial_trust</code> lists trust states established outside this composition. The composer never assumes an initial trust state. A required state must be present there or be satisfied by an earlier selected declaration.</p>
|
||||
<p><code>providers</code> pins a required capability to an exact declaration id. A pin is mandatory when more than one valid declaration provides the capability. A pin may not name an unrequested capability or a declaration that does not provide the keyed capability.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Fail-closed selection rules</h2>
|
||||
<p>Composition fails when any of the following is true:</p>
|
||||
<ul><li>a declaration is invalid or declaration ids are duplicated;</li><li>a capability is unknown, duplicated, or has no provider;</li><li>multiple providers match and the scenario does not pin one;</li><li>a provider pin does not match the requested capability;</li><li>an override targets an unselected declaration or unknown parameter;</li><li>an override violates type, constraint, sensitivity, or tuning authority;</li><li>a required parameter has neither a default nor an override;</li><li>a required trust state cannot be established without a cycle or inference.</li></ul>
|
||||
<p>Selection order never resolves ambiguity. Filesystem order, catalog order, lexical order, and prior deployment state are not provider authority.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>Trust sequencing</h2>
|
||||
<p>The composer starts only with the scenario's explicit <code>initial_trust</code> set. It then selects the lexically first <strong>eligible</strong> declaration, where eligible means all of that declaration's required trust states have already been established. After the step, and only for composition purposes, the declaration's satisfied states become available to later steps.</p>
|
||||
<p>Lexical ordering makes independent eligible steps reproducible; it does not grant one provider precedence during selection. If no remaining declaration is eligible, composition fails and reports the unresolved trust states.</p>
|
||||
<p>Readiness checks attached to a satisfied state are obligations for the owning executor. They are not marked satisfied merely because the plan contains them.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>Composition output</h2>
|
||||
<p>A successful output has <code>apiVersion: netkingdom.io/security-scenario-composition/v0.1</code> and <code>kind: SecurityScenarioComposition</code>. It contains:</p>
|
||||
<ul><li>the requested capabilities and exact selected declaration ids;</li><li>effective parameter values with their source, sensitivity, and tuning authority;</li><li>ordered execution handoffs containing owner, repository, entry point, required trust, produced trust, and readiness obligations;</li><li>a flattened responsibility map attributable to declaration ids;</li><li>the final <em>planned</em> trust-state set;</li><li>an explicit <code>execution.permitted: false</code> boundary.</li></ul>
|
||||
<p>The output is non-secret planning material. Declarations and scenarios must use secret references rather than secret values as required by the Playbook Capability Contract.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Conformance</h2>
|
||||
<p>Use the canonical tool:</p>
|
||||
<pre>python3 tools/security-scenario-composer/security_scenario_composer.py \
|
||||
--scenario <scenario.yaml> <declaration.yaml> [<declaration.yaml> ...]</pre>
|
||||
<p>Exit zero means the declarations and scenario compose deterministically. It does not mean the plan was executed or its readiness evidence was observed.</p>
|
||||
</section><footer><span>netkingdom-security-scenario-composition-v0.1 · · proposed</span><span>net-kingdom · canon/standards/security-scenario-composition_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</span></footer></main></div></div></html>
|
||||
320
build/standards/security-zones/v0.1/index.html
Normal file
320
build/standards/security-zones/v0.1/index.html
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="d4e57e63126d2cca1d381c025170e4b1f678c3f3">
|
||||
<meta name="policy-source-digest" content="32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906">
|
||||
<title>NetKingdom Security Zones v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-zones-v0.1</span> <span class="stat">proposed</span> <span>zone-engine</span> <span>reviewed 2026-08-22</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Zones v0.1</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-zones_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</code></p><p class="sub">Review due: 2026-11-22</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority and conformance</a></li><li><a href="#s3"><span class="n">3</span>Resolution is authoritative</a></li><li><a href="#s4"><span class="n">4</span>Zone catalog</a></li><li><a href="#s5"><span class="n">5</span>Stance and failure-mode model</a></li><li><a href="#s6"><span class="n">6</span>Declaration in `tenancy.yaml`</a></li><li><a href="#s7"><span class="n">7</span>Compilation and resolved view</a></li><li><a href="#s8"><span class="n">8</span>Membership-change observability</a></li><li><a href="#s9"><span class="n">9</span>Time-boxed exceptions</a></li><li><a href="#s10"><span class="n">10</span>Adoption</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>A security zone is a named workload-admission standard. It answers which scrutiny a workload has qualified for; control-owner policy then answers what a particular control does in that zone. A zone is not a repository label, a credential lane, a network segment, a reef, or a temporary exception.</p>
|
||||
<p>This standard is a sibling of <code>tenancy-posture_v0.1</code>. It owns zone identity, membership, admission, resolution, and the time-boxed exception lifecycle. <code>flex-auth</code> remains the only PDP for decisions it renders. Every other control continues to be owned and evaluated at its existing enforcement point.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Authority and conformance</h2>
|
||||
<p>Authority is split deliberately:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Fact or rule</th><th>Authority</th></tr></thead><tbody><tr><td>Workload identity and requested membership</td><td>Workload's responsible repo</td></tr><tr><td>Zone identities and admission standards</td><td>This standard, owned by zone-engine</td></tr><tr><td>Per-zone stance</td><td>Owner of the control that renders the effect</td></tr><tr><td>Failure behavior when a dependency is unavailable</td><td>Owner of the PEP</td></tr><tr><td>Publication</td><td>net-kingdom canon</td></tr></tbody></table></div>
|
||||
<p>Conformance is <strong>accuracy, not altitude</strong>. A lower zone declared with evidence is conformant. A stricter zone that the workload cannot evidence is not. Changing membership to evade a control is not an exception mechanism.</p>
|
||||
<p>The workload is the sole <strong>zone</strong> policy subject. It includes independently governed application, automation, maintenance, and operational/control-plane execution units. The workload executing a tunnel, credential broker, policy service, or maintenance operation requires identity; the tunnel, grant, lane, pattern, action, human, or agent does not become a workload merely because a control observes it. Those retain native typed identity and caller/resource context.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Resolution is authoritative</h2>
|
||||
<p>The stable <code>workload_identity.name</code> is the join key. Runtime principals, resources, credential lanes, and controls reference it explicitly. A resolver MUST NOT infer membership from repository ownership, paths, lane types, actor classes, environment, criticality, reefs, organization posture, or naming conventions.</p>
|
||||
<p>For a managed deployable, the authoritative cross-repository reference is the Repo Manager v1 tuple <code>(rapp_id, workload_identity.name)</code>, optionally narrowed by <code>deployable</code>. A catalog also declares whether its subject is workload applicable. Resolution returns both the reference result and admission result:</p>
|
||||
<ul><li><code>satisfied</code> — the workload has an authoritative identity and evidence meeting the declared zone's floor; <code>effective_zone</code> is the declared zone;</li><li><code>unsatisfied</code> — identity and membership are declared, but evidence is below the admission floor; <code>effective_zone</code> is <code>unknown</code>;</li><li><code>unknown</code> — identity, membership, or a required floor/evidence input cannot be resolved for a workload-applicable subject; <code>effective_zone</code> is <code>unknown</code>;</li><li><code>not-applicable</code> — the owning catalog explicitly establishes that the subject is an action, actor, lane, pattern, or resource rather than a workload; no zone is resolved and the control uses that subject's native identity.</li></ul>
|
||||
<p><code>unknown</code> is a result, not a zone. A control MUST declare an explicit treatment for it. That treatment may deny, escalate, or use a reviewed build-stage rule; it never manufactures membership and never grants an exception.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Zone catalog</h2>
|
||||
<p>The initial catalog follows the estate's existing <code>M0</code>–<code>M3</code> maturity ladder and adds one non-monotone continuity band required by ops-warden <code>ADR-0006</code>.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Zone id</th><th>Admission floor</th><th>Environment/data boundary</th><th>Purpose</th></tr></thead><tbody><tr><td><code>z0-experimental</code></td><td><code>M0</code> promotion evidence</td><td>Synthetic only; no real credential or user data</td><td>Experiments where advisory controls preserve iteration speed</td></tr><tr><td><code>z1-operational</code></td><td><code>M1</code> promotion evidence</td><td>Production secret handling for at most <code>internal</code> data</td><td>Early operational workloads with an owned front door</td></tr><tr><td><code>z2-protected</code></td><td><code>M2</code> promotion evidence</td><td><code>prod</code>; at most <code>confidential</code> data</td><td>Production workloads requiring review, SLO/on-call, and incident readiness</td></tr><tr><td><code>z3-critical</code></td><td><code>M3</code> promotion evidence</td><td><code>prod</code>; at most <code>restricted</code> data</td><td>Critical or regulated workloads with the strongest normal failure behavior</td></tr><tr><td><code>z2-continuity</code></td><td><code>M2</code> plus dependency/recovery evidence</td><td><code>prod</code>; at most <code>confidential</code> data</td><td>Foundational access or recovery workloads where fail-closed dependency cycles would cause an outage</td></tr></tbody></table></div>
|
||||
<p><code>z2-continuity</code> is a sibling of <code>z2-protected</code>, not a relaxation of its admission floor. It exists because enforcement stance is not monotone: a tunnel or credential-issuance path can require production scrutiny while deliberately remaining fail-open for one availability-sensitive control. Placement on a reef is never evidence for any zone.</p>
|
||||
<h3>4.1 Context floor</h3>
|
||||
<p>The declared zone must be at least as strict as the workload context requires. The context floor is the maximum of every resolved input:</p>
|
||||
<div class="scroll"><table><thead><tr><th><code>criticality</code></th><th>Minimum maturity</th></tr></thead><tbody><tr><td><code>low</code></td><td><code>M0</code></td></tr><tr><td><code>medium</code></td><td><code>M1</code></td></tr><tr><td><code>high</code></td><td><code>M2</code></td></tr><tr><td><code>critical</code></td><td><code>M3</code></td></tr></tbody></table></div>
|
||||
<p>Data-class floors are consumed from the canon-owned classification mapping; the current <code>synthetic → M0</code>, <code>internal → M1</code>, <code>confidential → M2</code>, and <code>restricted → M3</code> mapping is already used by ops-warden. An absent or unresolved floor makes admission <code>unknown</code>. In particular, <code>public</code> is disclosure policy and <code>synthetic</code> is data origin: they are not aliases, and this standard does not invent a floor while info-tech-canon's ruling is pending.</p>
|
||||
<p><code>organization_posture</code> may select a versioned control profile, but it never changes identity, membership, or admission.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>Stance and failure-mode model</h2>
|
||||
<p>Every owner-qualified control publishes a total mapping over all zone ids plus <code>unknown</code>. There is no implicit default.</p>
|
||||
<ul><li><code>enforced</code> — the control's deny or escalation effect changes the outcome;</li><li><code>advisory</code> — the control evaluates fully and records the outcome it would have produced, but does not block;</li><li><code>exempt</code> — the control is deliberately not evaluated for this zone and emits the versioned exemption reason.</li></ul>
|
||||
<p>Failure mode is separate and PEP-side:</p>
|
||||
<ul><li><code>fail_closed</code> — an unavailable or invalid evaluator produces the control's safe blocking/escalation outcome;</li><li><code>fail_open</code> — the PEP proceeds and records that the control was unavailable.</li></ul>
|
||||
<p><code>exempt</code> has no failure mode. A local control with no remote dependency uses <code>fail_closed</code> to describe evaluator errors. Changing stance is a policy-package change owned by the control owner, not a membership change.</p>
|
||||
<h3>5.1 Initial build-stage control profile</h3>
|
||||
<p>This profile is the v0.1 proposal for the first consumer. It is not stored in a workload's <code>zones:</code> declaration. flex-auth owns the pre-sign rows; ops-warden owns the other rows and every PEP failure mode.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Zone</th><th>flex-auth pre-sign stance / ops-warden PEP</th><th>agent high-risk read boundary</th><th><code>warden plan</code> zone rule</th></tr></thead><tbody><tr><td><code>z0-experimental</code></td><td><code>advisory</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>advisory</code>; evaluator failure cannot produce <code>autonomous</code></td></tr><tr><td><code>z1-operational</code></td><td><code>advisory</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>advisory</code>; evaluator failure cannot produce <code>autonomous</code></td></tr><tr><td><code>z2-protected</code></td><td><code>enforced</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; minimum <code>founder_required</code> when the zone rule matches</td></tr><tr><td><code>z3-critical</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; minimum <code>founder_required</code> when the zone rule matches</td></tr><tr><td><code>z2-continuity</code></td><td><code>enforced</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; minimum <code>founder_required</code> when the zone rule matches</td></tr><tr><td><code>unknown</code></td><td><code>advisory</code> / <code>fail_open</code> under the versioned <code>build</code> profile</td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; never <code>autonomous</code> from zone evidence</td></tr></tbody></table></div>
|
||||
<p>The <code>unknown</code> pre-sign treatment is an explicit organization-build policy, not a permissive membership default. It must change through a versioned control profile when the organization posture graduates.</p>
|
||||
<p>The agent read boundary stays enforced in every zone: build-stage permissiveness does not extend to disclosing high-risk credentials. For a missing lane risk:</p>
|
||||
<ul><li><code>z0-experimental</code> may resolve to <code>standard</code> only when admission proves the lane can expose synthetic material exclusively;</li><li><code>z1-operational</code> and <code>z2-protected</code> resolve to at least <code>high</code>;</li><li><code>z3-critical</code> resolves to <code>critical</code>, treated by the boundary as at least <code>high</code>; and</li><li>unresolved membership resolves to at least <code>high</code>.</li></ul>
|
||||
<p>An explicit grade always remains preferable. <code>accepted</code> is an acceptance record, not a risk grade.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>Declaration in <code>tenancy.yaml</code></h2>
|
||||
<p>For a single-service declaration, <code>zones:</code> is a sibling of <code>tenancy:</code> and <code>workload_identity</code>. For a <code>services:</code> declaration, both <code>workload_identity</code> and <code>zones</code> occur inside the same service entry. A multi-service file MUST NOT use a top-level <code>zones:</code> block.</p>
|
||||
<p>Every managed running deployable has an authoritative <code>rapp-*/declarations/rapp.yaml</code>. Its <code>workload_identity.declaration_ref</code> points to that declaration, and consuming catalogs reference it using the Repo Manager v1 tuple. A pre-rapp deployable 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>; this does not turn a human action or infrastructure resource into a fictional rapp or workload.</p>
|
||||
<pre>schema_version: "0.1"
|
||||
framework: netkingdom-tenancy-posture
|
||||
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
|
||||
tenancy:
|
||||
# tenancy-posture_v0.1 content omitted
|
||||
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: "2026-08-22"
|
||||
review_due: "2026-11-22"</pre>
|
||||
<p>The <code>zones:</code> block contains only membership evidence. It MUST NOT contain control stance, failure mode, organization posture, or exceptions.</p>
|
||||
<p>Required fields are:</p>
|
||||
<ul><li><code>standard</code> — exactly <code>security-zones_v0.1</code>;</li><li><code>membership</code> — one zone id from §4;</li><li><code>responsible_party</code> — the party answering for this membership;</li><li><code>justification</code> — why the zone fits the workload's actual context;</li><li><code>context</code> — the evidenced <code>maturity</code>, <code>criticality</code>, and <code>data_classification</code> used for admission. A managed workload's latter two values must agree with its resolved rapp projection; <code>n/a</code> requires an evidence-backed reason;</li><li><code>evidence</code> — one or more references and the admission facts each supports;</li><li><code>reviewed</code> and <code>review_due</code> — ISO dates, with review due after review.</li></ul>
|
||||
<p>Permanent membership changes are reviewed source changes. A change to a lower floor also records its reason and approver in the change review. Temporary relaxation uses an exception and never changes <code>membership</code>.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Compilation and resolved view</h2>
|
||||
<p>Compilation produces a workload-addressable resolved record. At minimum it contains:</p>
|
||||
<pre>workload_id: ops-bridge-tunnel
|
||||
workload_ref:
|
||||
applicability: applicable
|
||||
rapp_id: null # required for a managed deployable
|
||||
name: ops-bridge-tunnel
|
||||
deployable: null # optional for a managed deployable
|
||||
identity_binding: ssh-certificate/ops-warden/agt-ops-bridge
|
||||
declared_zone: z2-continuity
|
||||
admission: satisfied
|
||||
effective_zone: z2-continuity
|
||||
membership_revision: sha256:<digest>
|
||||
guarantees:
|
||||
- authoritative-workload-identity
|
||||
- explicit-zone-membership
|
||||
- non-inferred-resolution
|
||||
- enforcement-time-exception-expiry
|
||||
controls:
|
||||
- id: flex-auth/pre-sign
|
||||
policy_owner: flex-auth
|
||||
stance: enforced
|
||||
pep_owner: ops-warden
|
||||
failure_mode: fail_open
|
||||
policy_ref: <versioned-package></pre>
|
||||
<p>The <code>membership_revision</code> covers the authoritative workload binding, <code>zones:</code> block, and source revision. Control results include their policy/profile version and any active exception id and expiry. This is the machine-readable answer to “which zone is this workload in, and what applies there?” It may be compiled into existing consumer artifacts; it is not a synchronous zone-engine lookup.</p>
|
||||
<p>For managed deployables, compilation consumes the exact Repo Manager reference projection:</p>
|
||||
<pre>workload_ref:
|
||||
applicability: applicable
|
||||
rapp_id: rapp-issue-core
|
||||
name: issue-core
|
||||
deployable: issue-core # optional</pre>
|
||||
<p>The owning catalog uses <code>applicability: not-applicable</code> for a native non-workload subject. Absence of either applicability or an expected reference is <code>unknown</code>, not <code>not-applicable</code>. Zone-engine consumes these outcomes; it does not parse a path or repository name to repair them.</p>
|
||||
<p>For flex-auth's pre-sign control, the governed workload is the target of the certificate or grant, so the compiler writes <code>workload_id</code>, <code>security_zone</code>, <code>security_zone_admission</code>, and <code>security_zone_revision</code> on the resource attributes. Caller identity remains in the subject. A control that governs the requesting workload must declare that role explicitly and use separately named caller-workload attributes.</p>
|
||||
<p>The dormant <code>trust_zone: platform</code> constant is not security-zone membership and MUST be retired before adoption. The new concept is named <code>security_zone</code>; the two fields must not coexist as competing zone sources.</p>
|
||||
</section>
|
||||
<section id="s8"><h2><span class="sn">08</span>Membership-change observability</h2>
|
||||
<p>A membership change becomes effective only through a reviewed declaration and a newly compiled artifact. The compiler emits the source and membership revision, rejects ambiguous identities, and reports additions, removals, and changes against the preceding snapshot. Controls expose the membership revision in their decision or verdict evidence.</p>
|
||||
<p>A zone that can be widened by editing an unversioned runtime label is not conformant.</p>
|
||||
</section>
|
||||
<section id="s9"><h2><span class="sn">09</span>Time-boxed exceptions</h2>
|
||||
<p>The normative lifecycle is the ZONE-WP-0001-T04 decision in <code>zone-engine/docs/exception-lifecycle-2026-08-22.md</code>: only the control owner's designated authority grants a named-workload, named-zone, named-control relaxation within a declared maximum duration. Enforcement applies it only for <code>not_before <= now < not_after</code>; invalid or unevaluable records are inactive, expiry restores the base rule automatically, and no minted credential, lease, or session may outlive the exception.</p>
|
||||
<p>Exceptions live with the versioned control policy or PEP configuration and are evaluated where their effects occur. This requires no zone-engine runtime.</p>
|
||||
</section>
|
||||
<section id="s10"><h2><span class="sn">10</span>Adoption</h2>
|
||||
<p>Net-kingdom published this standard at revision <code>337484a</code>. Adoption requires:</p>
|
||||
<ol><li>flex-auth and ops-warden accept the initial control profile or publish a versioned replacement with total zone and <code>unknown</code> coverage;</li><li>at least two workload owners declare authoritative identities and zones;</li><li>a third consumer compiles or reads the resolved view; and</li><li>ops-warden retires <code>policy.enabled</code> and the dormant <code>trust_zone</code> constant in the same migration.</li></ol>
|
||||
<p>All four gates were met on 2026-08-22. The owning zone-engine repository records the exact consumer revisions, tests, resolved membership digests, and live caller decision in <code>docs/evidence/security-zone-adoption-2026-08-22.md</code>.</p>
|
||||
</section><footer><span>netkingdom-security-zones-v0.1 · · proposed</span><span>net-kingdom · canon/standards/security-zones_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3</span></footer></main></div></div></html>
|
||||
320
build/standards/security-zones/v0.1/revisions/0.1/index.html
Normal file
320
build/standards/security-zones/v0.1/revisions/0.1/index.html
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="ce198fc2905687ea90a2892346b6860281ac87f8">
|
||||
<meta name="policy-source-digest" content="32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906">
|
||||
<title>NetKingdom Security Zones v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||
|
||||
</style>
|
||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-zones-v0.1</span> <span class="stat">proposed</span> <span>zone-engine</span> <span>reviewed 2026-08-22</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Zones v0.1</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-zones_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</code></p><p class="sub">Review due: 2026-11-22</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority and conformance</a></li><li><a href="#s3"><span class="n">3</span>Resolution is authoritative</a></li><li><a href="#s4"><span class="n">4</span>Zone catalog</a></li><li><a href="#s5"><span class="n">5</span>Stance and failure-mode model</a></li><li><a href="#s6"><span class="n">6</span>Declaration in `tenancy.yaml`</a></li><li><a href="#s7"><span class="n">7</span>Compilation and resolved view</a></li><li><a href="#s8"><span class="n">8</span>Membership-change observability</a></li><li><a href="#s9"><span class="n">9</span>Time-boxed exceptions</a></li><li><a href="#s10"><span class="n">10</span>Adoption</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
|
||||
<p>A security zone is a named workload-admission standard. It answers which scrutiny a workload has qualified for; control-owner policy then answers what a particular control does in that zone. A zone is not a repository label, a credential lane, a network segment, a reef, or a temporary exception.</p>
|
||||
<p>This standard is a sibling of <code>tenancy-posture_v0.1</code>. It owns zone identity, membership, admission, resolution, and the time-boxed exception lifecycle. <code>flex-auth</code> remains the only PDP for decisions it renders. Every other control continues to be owned and evaluated at its existing enforcement point.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>Authority and conformance</h2>
|
||||
<p>Authority is split deliberately:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Fact or rule</th><th>Authority</th></tr></thead><tbody><tr><td>Workload identity and requested membership</td><td>Workload's responsible repo</td></tr><tr><td>Zone identities and admission standards</td><td>This standard, owned by zone-engine</td></tr><tr><td>Per-zone stance</td><td>Owner of the control that renders the effect</td></tr><tr><td>Failure behavior when a dependency is unavailable</td><td>Owner of the PEP</td></tr><tr><td>Publication</td><td>net-kingdom canon</td></tr></tbody></table></div>
|
||||
<p>Conformance is <strong>accuracy, not altitude</strong>. A lower zone declared with evidence is conformant. A stricter zone that the workload cannot evidence is not. Changing membership to evade a control is not an exception mechanism.</p>
|
||||
<p>The workload is the sole <strong>zone</strong> policy subject. It includes independently governed application, automation, maintenance, and operational/control-plane execution units. The workload executing a tunnel, credential broker, policy service, or maintenance operation requires identity; the tunnel, grant, lane, pattern, action, human, or agent does not become a workload merely because a control observes it. Those retain native typed identity and caller/resource context.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Resolution is authoritative</h2>
|
||||
<p>The stable <code>workload_identity.name</code> is the join key. Runtime principals, resources, credential lanes, and controls reference it explicitly. A resolver MUST NOT infer membership from repository ownership, paths, lane types, actor classes, environment, criticality, reefs, organization posture, or naming conventions.</p>
|
||||
<p>For a managed deployable, the authoritative cross-repository reference is the Repo Manager v1 tuple <code>(rapp_id, workload_identity.name)</code>, optionally narrowed by <code>deployable</code>. A catalog also declares whether its subject is workload applicable. Resolution returns both the reference result and admission result:</p>
|
||||
<ul><li><code>satisfied</code> — the workload has an authoritative identity and evidence meeting the declared zone's floor; <code>effective_zone</code> is the declared zone;</li><li><code>unsatisfied</code> — identity and membership are declared, but evidence is below the admission floor; <code>effective_zone</code> is <code>unknown</code>;</li><li><code>unknown</code> — identity, membership, or a required floor/evidence input cannot be resolved for a workload-applicable subject; <code>effective_zone</code> is <code>unknown</code>;</li><li><code>not-applicable</code> — the owning catalog explicitly establishes that the subject is an action, actor, lane, pattern, or resource rather than a workload; no zone is resolved and the control uses that subject's native identity.</li></ul>
|
||||
<p><code>unknown</code> is a result, not a zone. A control MUST declare an explicit treatment for it. That treatment may deny, escalate, or use a reviewed build-stage rule; it never manufactures membership and never grants an exception.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Zone catalog</h2>
|
||||
<p>The initial catalog follows the estate's existing <code>M0</code>–<code>M3</code> maturity ladder and adds one non-monotone continuity band required by ops-warden <code>ADR-0006</code>.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Zone id</th><th>Admission floor</th><th>Environment/data boundary</th><th>Purpose</th></tr></thead><tbody><tr><td><code>z0-experimental</code></td><td><code>M0</code> promotion evidence</td><td>Synthetic only; no real credential or user data</td><td>Experiments where advisory controls preserve iteration speed</td></tr><tr><td><code>z1-operational</code></td><td><code>M1</code> promotion evidence</td><td>Production secret handling for at most <code>internal</code> data</td><td>Early operational workloads with an owned front door</td></tr><tr><td><code>z2-protected</code></td><td><code>M2</code> promotion evidence</td><td><code>prod</code>; at most <code>confidential</code> data</td><td>Production workloads requiring review, SLO/on-call, and incident readiness</td></tr><tr><td><code>z3-critical</code></td><td><code>M3</code> promotion evidence</td><td><code>prod</code>; at most <code>restricted</code> data</td><td>Critical or regulated workloads with the strongest normal failure behavior</td></tr><tr><td><code>z2-continuity</code></td><td><code>M2</code> plus dependency/recovery evidence</td><td><code>prod</code>; at most <code>confidential</code> data</td><td>Foundational access or recovery workloads where fail-closed dependency cycles would cause an outage</td></tr></tbody></table></div>
|
||||
<p><code>z2-continuity</code> is a sibling of <code>z2-protected</code>, not a relaxation of its admission floor. It exists because enforcement stance is not monotone: a tunnel or credential-issuance path can require production scrutiny while deliberately remaining fail-open for one availability-sensitive control. Placement on a reef is never evidence for any zone.</p>
|
||||
<h3>4.1 Context floor</h3>
|
||||
<p>The declared zone must be at least as strict as the workload context requires. The context floor is the maximum of every resolved input:</p>
|
||||
<div class="scroll"><table><thead><tr><th><code>criticality</code></th><th>Minimum maturity</th></tr></thead><tbody><tr><td><code>low</code></td><td><code>M0</code></td></tr><tr><td><code>medium</code></td><td><code>M1</code></td></tr><tr><td><code>high</code></td><td><code>M2</code></td></tr><tr><td><code>critical</code></td><td><code>M3</code></td></tr></tbody></table></div>
|
||||
<p>Data-class floors are consumed from the canon-owned classification mapping; the current <code>synthetic → M0</code>, <code>internal → M1</code>, <code>confidential → M2</code>, and <code>restricted → M3</code> mapping is already used by ops-warden. An absent or unresolved floor makes admission <code>unknown</code>. In particular, <code>public</code> is disclosure policy and <code>synthetic</code> is data origin: they are not aliases, and this standard does not invent a floor while info-tech-canon's ruling is pending.</p>
|
||||
<p><code>organization_posture</code> may select a versioned control profile, but it never changes identity, membership, or admission.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>Stance and failure-mode model</h2>
|
||||
<p>Every owner-qualified control publishes a total mapping over all zone ids plus <code>unknown</code>. There is no implicit default.</p>
|
||||
<ul><li><code>enforced</code> — the control's deny or escalation effect changes the outcome;</li><li><code>advisory</code> — the control evaluates fully and records the outcome it would have produced, but does not block;</li><li><code>exempt</code> — the control is deliberately not evaluated for this zone and emits the versioned exemption reason.</li></ul>
|
||||
<p>Failure mode is separate and PEP-side:</p>
|
||||
<ul><li><code>fail_closed</code> — an unavailable or invalid evaluator produces the control's safe blocking/escalation outcome;</li><li><code>fail_open</code> — the PEP proceeds and records that the control was unavailable.</li></ul>
|
||||
<p><code>exempt</code> has no failure mode. A local control with no remote dependency uses <code>fail_closed</code> to describe evaluator errors. Changing stance is a policy-package change owned by the control owner, not a membership change.</p>
|
||||
<h3>5.1 Initial build-stage control profile</h3>
|
||||
<p>This profile is the v0.1 proposal for the first consumer. It is not stored in a workload's <code>zones:</code> declaration. flex-auth owns the pre-sign rows; ops-warden owns the other rows and every PEP failure mode.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Zone</th><th>flex-auth pre-sign stance / ops-warden PEP</th><th>agent high-risk read boundary</th><th><code>warden plan</code> zone rule</th></tr></thead><tbody><tr><td><code>z0-experimental</code></td><td><code>advisory</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>advisory</code>; evaluator failure cannot produce <code>autonomous</code></td></tr><tr><td><code>z1-operational</code></td><td><code>advisory</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>advisory</code>; evaluator failure cannot produce <code>autonomous</code></td></tr><tr><td><code>z2-protected</code></td><td><code>enforced</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; minimum <code>founder_required</code> when the zone rule matches</td></tr><tr><td><code>z3-critical</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; minimum <code>founder_required</code> when the zone rule matches</td></tr><tr><td><code>z2-continuity</code></td><td><code>enforced</code> / <code>fail_open</code></td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; minimum <code>founder_required</code> when the zone rule matches</td></tr><tr><td><code>unknown</code></td><td><code>advisory</code> / <code>fail_open</code> under the versioned <code>build</code> profile</td><td><code>enforced</code> / <code>fail_closed</code></td><td><code>enforced</code>; never <code>autonomous</code> from zone evidence</td></tr></tbody></table></div>
|
||||
<p>The <code>unknown</code> pre-sign treatment is an explicit organization-build policy, not a permissive membership default. It must change through a versioned control profile when the organization posture graduates.</p>
|
||||
<p>The agent read boundary stays enforced in every zone: build-stage permissiveness does not extend to disclosing high-risk credentials. For a missing lane risk:</p>
|
||||
<ul><li><code>z0-experimental</code> may resolve to <code>standard</code> only when admission proves the lane can expose synthetic material exclusively;</li><li><code>z1-operational</code> and <code>z2-protected</code> resolve to at least <code>high</code>;</li><li><code>z3-critical</code> resolves to <code>critical</code>, treated by the boundary as at least <code>high</code>; and</li><li>unresolved membership resolves to at least <code>high</code>.</li></ul>
|
||||
<p>An explicit grade always remains preferable. <code>accepted</code> is an acceptance record, not a risk grade.</p>
|
||||
</section>
|
||||
<section id="s6"><h2><span class="sn">06</span>Declaration in <code>tenancy.yaml</code></h2>
|
||||
<p>For a single-service declaration, <code>zones:</code> is a sibling of <code>tenancy:</code> and <code>workload_identity</code>. For a <code>services:</code> declaration, both <code>workload_identity</code> and <code>zones</code> occur inside the same service entry. A multi-service file MUST NOT use a top-level <code>zones:</code> block.</p>
|
||||
<p>Every managed running deployable has an authoritative <code>rapp-*/declarations/rapp.yaml</code>. Its <code>workload_identity.declaration_ref</code> points to that declaration, and consuming catalogs reference it using the Repo Manager v1 tuple. A pre-rapp deployable 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>; this does not turn a human action or infrastructure resource into a fictional rapp or workload.</p>
|
||||
<pre>schema_version: "0.1"
|
||||
framework: netkingdom-tenancy-posture
|
||||
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
|
||||
tenancy:
|
||||
# tenancy-posture_v0.1 content omitted
|
||||
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: "2026-08-22"
|
||||
review_due: "2026-11-22"</pre>
|
||||
<p>The <code>zones:</code> block contains only membership evidence. It MUST NOT contain control stance, failure mode, organization posture, or exceptions.</p>
|
||||
<p>Required fields are:</p>
|
||||
<ul><li><code>standard</code> — exactly <code>security-zones_v0.1</code>;</li><li><code>membership</code> — one zone id from §4;</li><li><code>responsible_party</code> — the party answering for this membership;</li><li><code>justification</code> — why the zone fits the workload's actual context;</li><li><code>context</code> — the evidenced <code>maturity</code>, <code>criticality</code>, and <code>data_classification</code> used for admission. A managed workload's latter two values must agree with its resolved rapp projection; <code>n/a</code> requires an evidence-backed reason;</li><li><code>evidence</code> — one or more references and the admission facts each supports;</li><li><code>reviewed</code> and <code>review_due</code> — ISO dates, with review due after review.</li></ul>
|
||||
<p>Permanent membership changes are reviewed source changes. A change to a lower floor also records its reason and approver in the change review. Temporary relaxation uses an exception and never changes <code>membership</code>.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Compilation and resolved view</h2>
|
||||
<p>Compilation produces a workload-addressable resolved record. At minimum it contains:</p>
|
||||
<pre>workload_id: ops-bridge-tunnel
|
||||
workload_ref:
|
||||
applicability: applicable
|
||||
rapp_id: null # required for a managed deployable
|
||||
name: ops-bridge-tunnel
|
||||
deployable: null # optional for a managed deployable
|
||||
identity_binding: ssh-certificate/ops-warden/agt-ops-bridge
|
||||
declared_zone: z2-continuity
|
||||
admission: satisfied
|
||||
effective_zone: z2-continuity
|
||||
membership_revision: sha256:<digest>
|
||||
guarantees:
|
||||
- authoritative-workload-identity
|
||||
- explicit-zone-membership
|
||||
- non-inferred-resolution
|
||||
- enforcement-time-exception-expiry
|
||||
controls:
|
||||
- id: flex-auth/pre-sign
|
||||
policy_owner: flex-auth
|
||||
stance: enforced
|
||||
pep_owner: ops-warden
|
||||
failure_mode: fail_open
|
||||
policy_ref: <versioned-package></pre>
|
||||
<p>The <code>membership_revision</code> covers the authoritative workload binding, <code>zones:</code> block, and source revision. Control results include their policy/profile version and any active exception id and expiry. This is the machine-readable answer to “which zone is this workload in, and what applies there?” It may be compiled into existing consumer artifacts; it is not a synchronous zone-engine lookup.</p>
|
||||
<p>For managed deployables, compilation consumes the exact Repo Manager reference projection:</p>
|
||||
<pre>workload_ref:
|
||||
applicability: applicable
|
||||
rapp_id: rapp-issue-core
|
||||
name: issue-core
|
||||
deployable: issue-core # optional</pre>
|
||||
<p>The owning catalog uses <code>applicability: not-applicable</code> for a native non-workload subject. Absence of either applicability or an expected reference is <code>unknown</code>, not <code>not-applicable</code>. Zone-engine consumes these outcomes; it does not parse a path or repository name to repair them.</p>
|
||||
<p>For flex-auth's pre-sign control, the governed workload is the target of the certificate or grant, so the compiler writes <code>workload_id</code>, <code>security_zone</code>, <code>security_zone_admission</code>, and <code>security_zone_revision</code> on the resource attributes. Caller identity remains in the subject. A control that governs the requesting workload must declare that role explicitly and use separately named caller-workload attributes.</p>
|
||||
<p>The dormant <code>trust_zone: platform</code> constant is not security-zone membership and MUST be retired before adoption. The new concept is named <code>security_zone</code>; the two fields must not coexist as competing zone sources.</p>
|
||||
</section>
|
||||
<section id="s8"><h2><span class="sn">08</span>Membership-change observability</h2>
|
||||
<p>A membership change becomes effective only through a reviewed declaration and a newly compiled artifact. The compiler emits the source and membership revision, rejects ambiguous identities, and reports additions, removals, and changes against the preceding snapshot. Controls expose the membership revision in their decision or verdict evidence.</p>
|
||||
<p>A zone that can be widened by editing an unversioned runtime label is not conformant.</p>
|
||||
</section>
|
||||
<section id="s9"><h2><span class="sn">09</span>Time-boxed exceptions</h2>
|
||||
<p>The normative lifecycle is the ZONE-WP-0001-T04 decision in <code>zone-engine/docs/exception-lifecycle-2026-08-22.md</code>: only the control owner's designated authority grants a named-workload, named-zone, named-control relaxation within a declared maximum duration. Enforcement applies it only for <code>not_before <= now < not_after</code>; invalid or unevaluable records are inactive, expiry restores the base rule automatically, and no minted credential, lease, or session may outlive the exception.</p>
|
||||
<p>Exceptions live with the versioned control policy or PEP configuration and are evaluated where their effects occur. This requires no zone-engine runtime.</p>
|
||||
</section>
|
||||
<section id="s10"><h2><span class="sn">10</span>Adoption</h2>
|
||||
<p>Net-kingdom published this standard at revision <code>337484a</code>. Adoption requires:</p>
|
||||
<ol><li>flex-auth and ops-warden accept the initial control profile or publish a versioned replacement with total zone and <code>unknown</code> coverage;</li><li>at least two workload owners declare authoritative identities and zones;</li><li>a third consumer compiles or reads the resolved view; and</li><li>ops-warden retires <code>policy.enabled</code> and the dormant <code>trust_zone</code> constant in the same migration.</li></ol>
|
||||
<p>All four gates were met on 2026-08-22. The owning zone-engine repository records the exact consumer revisions, tests, resolved membership digests, and live caller decision in <code>docs/evidence/security-zone-adoption-2026-08-22.md</code>.</p>
|
||||
</section><footer><span>netkingdom-security-zones-v0.1 · · proposed</span><span>net-kingdom · canon/standards/security-zones_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</span></footer></main></div></div></html>
|
||||
|
|
@ -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: "2026-08-22"
|
||||
review_due: "2026-11-22"</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 <path>...</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 <path>...</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.<level></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:<grouping>:<name></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:<grouping>:<name></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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,496 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><meta charset="utf-8">
|
||||
<meta name="policy-source-revision" content="ce198fc2905687ea90a2892346b6860281ac87f8">
|
||||
<meta name="policy-source-digest" content="27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876">
|
||||
<title>NetKingdom Tenancy Posture v0.1</title>
|
||||
<style>
|
||||
:root{
|
||||
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
|
||||
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
|
||||
--rule:#D3D7DC; --rule-strong:#B6BCC3;
|
||||
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
|
||||
--clay:#8A3A2C; --clay-soft:#F2DFDA;
|
||||
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
|
||||
--chip-fg:#F6F7F8;
|
||||
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
|
||||
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
|
||||
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
|
||||
--measure:66ch;
|
||||
}
|
||||
@media (prefers-color-scheme:dark){
|
||||
:root:not([data-theme="light"]){
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
|
||||
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
|
||||
--rule:#2A3138; --rule-strong:#3B444D;
|
||||
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
|
||||
--clay:#D08A76; --clay-soft:#3A211B;
|
||||
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
|
||||
--chip-fg:#12161A;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0; background:var(--paper); color:var(--ink);
|
||||
font-family:var(--font-body); font-size:17px; line-height:1.62;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
|
||||
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
|
||||
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
|
||||
|
||||
/* ---------- rail ---------- */
|
||||
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
|
||||
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
|
||||
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
|
||||
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
|
||||
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
|
||||
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
|
||||
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
|
||||
.eyebrow .stat{color:var(--clay)}
|
||||
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
|
||||
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
|
||||
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
|
||||
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
|
||||
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
|
||||
|
||||
/* ---------- typography ---------- */
|
||||
section{margin-bottom:60px;scroll-margin-top:24px}
|
||||
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
|
||||
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
|
||||
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
|
||||
p{margin:0 0 15px;max-width:var(--measure)}
|
||||
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
|
||||
li{margin-bottom:7px}
|
||||
strong{font-weight:600}
|
||||
em{font-style:italic}
|
||||
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
|
||||
a{color:var(--brass)}
|
||||
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
|
||||
|
||||
/* ---------- devices ---------- */
|
||||
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.callout p:last-child{margin-bottom:0}
|
||||
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
|
||||
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
|
||||
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
|
||||
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
|
||||
.hard p:last-child{margin-bottom:0}
|
||||
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
|
||||
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
|
||||
|
||||
/* ---------- tables ---------- */
|
||||
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
|
||||
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
|
||||
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
|
||||
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
|
||||
td:first-child{color:var(--ink);font-weight:600}
|
||||
tbody tr:last-child td{border-bottom:none}
|
||||
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
|
||||
|
||||
/* ---------- ladders ---------- */
|
||||
.breakout{margin:34px 0 40px}
|
||||
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
|
||||
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
|
||||
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
|
||||
.ladders{display:grid;gap:26px}
|
||||
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
|
||||
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
|
||||
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
|
||||
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
|
||||
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
|
||||
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
|
||||
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
|
||||
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
|
||||
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
|
||||
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
|
||||
.rung.na{opacity:.42}
|
||||
|
||||
/* ---------- matrix ---------- */
|
||||
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
|
||||
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
|
||||
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
|
||||
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
|
||||
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
|
||||
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
|
||||
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
|
||||
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
|
||||
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
|
||||
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
|
||||
.rlab{min-height:60px}
|
||||
.clab{padding-top:7px;min-height:22px}
|
||||
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
|
||||
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
|
||||
.mnote .k{display:flex;align-items:center;gap:7px}
|
||||
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
|
||||
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
|
||||
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
|
||||
@media (max-width:640px){
|
||||
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
|
||||
.mcell{min-height:52px;padding:4px}
|
||||
.pin{font-size:8px;padding:1px 3px}
|
||||
.rlab{min-height:52px}
|
||||
}
|
||||
|
||||
/* ---------- methodology ---------- */
|
||||
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
|
||||
.verb{background:var(--surface);padding:18px 18px 20px}
|
||||
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
|
||||
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
|
||||
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
|
||||
|
||||
/* ---------- questions ---------- */
|
||||
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
|
||||
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
|
||||
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
|
||||
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
|
||||
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
|
||||
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
|
||||
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
|
||||
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
|
||||
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
|
||||
|
||||
/* ---------- misc ---------- */
|
||||
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
|
||||
.numbers .v{color:var(--ink);font-weight:600}
|
||||
.numbers .k{color:var(--ink-3)}
|
||||
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
|
||||
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
|
||||
.alt:last-of-type{border-bottom:none}
|
||||
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
|
||||
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
|
||||
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
|
||||
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.tm td,.tm th{text-align:center}
|
||||
.tm td:first-child,.tm th:first-child{text-align:left}
|
||||
.yes{color:var(--l4);font-weight:700}
|
||||
.no{color:var(--clay);font-weight:700}
|
||||
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
|
||||
.kind.adv{border-color:var(--clay);color:var(--clay)}
|
||||
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
|
||||
.route{background:var(--surface);padding:16px 18px}
|
||||
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
|
||||
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
|
||||
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
|
||||
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
|
||||
@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-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 · ce198fc2905687ea90a2892346b6860281ac87f8</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>
|
||||
<section id="s0"><h2><span class="sn">00</span>Terminology: axes, not planes</h2>
|
||||
<p><code>docs/platform-identity-security-architecture.md</code> — accepted, 2026-07-23 — already uses <strong>plane</strong> for a trust and deployment layer: the <em>bootstrap plane</em>, the <em>platform control plane</em>, and <em>tenant planes</em>. That meaning is established, ratified, and owned by this repo.</p>
|
||||
<p>Drafts 1–4 of this document, written elsewhere, used <strong>plane</strong> for something different: an independent dimension of concern. Two incompatible senses of one word inside one canon is exactly the concept-ownership collision the estate has been careful about elsewhere, and the newcomer yields.</p>
|
||||
<p>This framework therefore describes six <strong>axes</strong>. They are orthogonal to NetKingdom's planes, not a subdivision of them:</p>
|
||||
<ul><li>A <strong>plane</strong> is <em>where</em> something runs and what trust it carries — bootstrap, platform control, tenant.</li><li>An <strong>axis</strong> is <em>which property</em> of tenancy is being described — identity, authorization, enforcement, placement, retention, availability.</li></ul>
|
||||
<p>A workload in the tenant plane has a position on all six axes. A platform control plane service does too. The two vocabularies compose and neither replaces the other.</p>
|
||||
<p>The rename is also an improvement. A posture vector is literally a point in six-dimensional space, and "axis" says that where "plane" did not.</p>
|
||||
</section>
|
||||
<section id="s1"><h2><span class="sn">01</span>Context</h2>
|
||||
<p>Drafts 1–4 opened by claiming the estate "has never written down what it is building". Relocation proved that wrong, and the correction is worth keeping visible: <code>docs/platform-identity-security-architecture.md</code> has described the trust model, the tenant model and a capability progression since 2026-07-23. The accurate claim is narrower — <strong>what was missing is a way to say how far a given service has got, and to hold several answers at once.</strong> Seven documents cover slices of the subject and none of them does that:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Document</th><th>Covers</th><th>Status</th></tr></thead><tbody><tr><td><code>iam-profile_v0.3</code> (NetKingdom)</td><td>Tenant identifier shape, <code>tenant_roles</code> claim, staleness rules</td><td>Ratified</td></tr><tr><td><code>tenant-engine-boundary-contract_v0.1</code> (NetKingdom)</td><td>Who owns tenant records, roles, plan assignment</td><td>Ratified</td></tr><tr><td><code>business-app-service-contract_v0.1</code> §1 (Custodian)</td><td>Business apps: instance-per-client, tenant-keyed data</td><td>Ratified</td></tr><tr><td><code>rapp-postgres</code> ADR-0001</td><td>Consumer + tenant isolation in PostgreSQL</td><td>Proposed, governs one repo</td></tr><tr><td><code>rapp-postgres</code> ADR-0002</td><td>Per-consumer retention and the erasure horizon</td><td>Proposed, governs one repo</td></tr><tr><td><code>shared-platform-relational-storage_v0.1</code></td><td>The stacked-boundary gap</td><td>Routed 2026-08-10, <strong>still unratified</strong></td></tr><tr><td><code>platform-identity-security-architecture</code> (NetKingdom)</td><td>Trust model, planes, tenant model, capability progression</td><td>Accepted 2026-07-23</td></tr></tbody></table></div>
|
||||
<p>This document is downstream of that architecture and must not restate it. It answers one question the architecture leaves open: given the model, <strong>where is this particular service today, and how would anyone know?</strong></p>
|
||||
<p>Four failures existed when drafting began.</p>
|
||||
<p><strong>The gap was diagnosed once and the fix stalled.</strong> The v0.1 draft was written to fill this hole and has sat unratified in neither canon directory. §20 attaches a ratification path so this one does not join it.</p>
|
||||
<p><strong>Placement was owned by nobody.</strong> <code>user-engine-pg</code> and <code>target-revenue-pg</code> are dedicated; <code>apps-pg</code>, <code>net-kingdom-pg</code>, <code>platform-pg</code>, <code>state-hub-db</code> and <code>forgejo-db</code> are shared. Both live, neither written down. <code>tenant-engine</code> raised this with <code>railiance-platform</code> on 2026-08-16. Draft-8 resolves the authority split in §8.2.</p>
|
||||
<p><strong>Two contradictory defaults were already ratified.</strong> Business apps get instance-per-client; platform services pool. Nothing says which shape a new service takes, and no definition separates the categories. Decision 4.4.1 now supplies the default; §19.4 retains the missing classification rule.</p>
|
||||
<p><strong>There is no honest way to describe a repo that is not there yet.</strong> The estate absorbs repos with weak or absent tenant separation. Today such a repo is simply non-conformant, leaving it two bad options: misrepresent its posture, or stay outside the framework.</p>
|
||||
</section>
|
||||
<section id="s2"><h2><span class="sn">02</span>What this document is</h2>
|
||||
<p><strong>A framework, not a model.</strong> It specifies no single correct implementation. It supplies terminology (§3, §4), a declaration (§5), a conformance rule (§6), methodology (§12), and evidence definitions (§13).</p>
|
||||
<p>A service is conformant when its declared posture is accurate and its trajectory recorded. A service is non-conformant when it claims a level it cannot evidence — regardless of how high or low that level is.</p>
|
||||
</section>
|
||||
<section id="s3"><h2><span class="sn">03</span>Six orthogonal axes</h2>
|
||||
<p>"Is this multi-tenant?" is treated as one question. It is six, and they are independent:</p>
|
||||
<div class="scroll"><table><thead><tr><th>Axis</th><th>Question</th><th>Vocabulary owner</th></tr></thead><tbody><tr><td><strong>Identity (I)</strong></td><td>How is a tenant named and validated?</td><td><code>tenant-engine</code> / IAM Profile</td></tr><tr><td><strong>Authorization (A)</strong></td><td>How is a request bound to the tenants it may act for?</td><td><code>flex-auth</code></td></tr><tr><td><strong>Enforcement (E)</strong></td><td>Where, mechanically, is the tenant boundary enforced?</td><td>This framework</td></tr><tr><td><strong>Placement (P)</strong></td><td>Which substrate holds a tenant's data?</td><td><code>railiance-platform</code></td></tr><tr><td><strong>Retention (R)</strong></td><td>How long does data persist, and how is it erased?</td><td>The storage platform; policy by the consumer</td></tr><tr><td><strong>Availability (V)</strong></td><td>What failure can the complete service path survive, and within what recovery objective?</td><td>The delivering service; substrate facts by its providers</td></tr></tbody></table></div>
|
||||
<p>Conflation produces errors today. <code>rapp-postgres</code>'s <code>PostgresConsumer</code> carries <code>tenantIsolation: consumer-service-boundary</code> — an <strong>E</strong>-axis fact in a <strong>P</strong>-axis artifact, reading as though storage enforces something it does not. The "dedicated versus shared" argument mixes P (capacity, blast radius) with E (correctness).</p>
|
||||
<p>The axes are separated <em>precisely so each may sit at a different level</em>.</p>
|
||||
<p><span class="dec">Decision 3.1:</span> every document, declaration and plan tier that says "isolation" MUST name which axis it means.</p>
|
||||
<p><span class="dec">Decision 3.2:</span> the axes couple at their tops and the couplings MUST be stated where they apply, not used to argue the axes are one:</p>
|
||||
<ul><li><code>E4</code> is reachable only at <code>P3</code> or above.</li><li><code>R</code>'s erasure horizon is bounded below by <code>P</code> — on shared substrate, a consumer's horizon is the instance maximum (§4.5).</li><li><code>R4</code> by key destruction is bounded by the <strong>key boundary</strong>, which is an E-axis property. Shredding a single tenant's data requires the application to encrypt under a per-tenant key before writing; the storage platform cannot supply it. <strong>Reaching the top of the retention ladder is not a retention project.</strong></li><li><code>V</code> composes as the <strong>minimum across the critical request path</strong>, not the maximum of its components. A replicated application on a single-instance database is not V2. A tested degraded mode may remove a dependency from that path, but the bypass itself is part of the V evidence.</li></ul>
|
||||
<p><span class="dec">Decision 3.3 — scope.</span> The P and R ladders describe a service's <strong>primary datastore</strong>. The V ladder describes the service's complete critical request path, including providers it synchronously depends on. Caches, search indices, message queues and background jobs are named leak surfaces in the external baselines and are assessed separately, not silently covered by a datastore level. A declaration names material secondary stores and asynchronous paths as exceptions rather than implying that one vector proves them safe.</p>
|
||||
</section>
|
||||
<section id="s4"><h2><span class="sn">04</span>Graduated levels</h2>
|
||||
<p>Each axis carries an ordered ladder. Higher is stronger, not better: the right level is the one a service can evidence and its risk warrants.</p>
|
||||
<h3>4.1 Identity (I)</h3>
|
||||
<div class="breakout"><div class="ladders">
|
||||
<div class="ladder"><div class="pname">Identity<span>axis I</span></div><div class="rungs"><div class="rung r0"><span class="code">I0</span><span class="txt">No tenant concept. Data not attributable to a tenant.</span></div><div class="rung r1"><span class="code">I1</span><span class="txt">A local tenant notion exists but is not canonical, <strong>or</strong> the tenant is taken from the request rather than from a verified token.</span></div><div class="rung r2"><span class="code">I2</span><span class="txt">Canonical identifiers, bound at the identity provider and carried as a verified claim, <strong>and verified by this service on its own inbound calls</strong>.</span></div><div class="rung r3"><span class="code">I3</span><span class="txt">I2 plus capability roles honoured, with live <code>tenant-engine</code> re-query for privileged, destructive, credential-vending or <code>aal2</code>-class decisions.</span></div><div class="rung na"><span class="code">—</span><span class="txt">Ladder ends at I3.</span></div></div></div>
|
||||
</div></div>
|
||||
<p>I1 now explicitly absorbs request-supplied tenant identifiers. "Never trust client-supplied tenant IDs without validation" is a named anti-pattern; a service reading the tenant from a header is at I1 however canonical the string.</p>
|
||||
<p><strong>An axis is assessed on a service's own inbound surface, never on its authority over the concept.</strong> <code>tenant-engine</code> is the source of existence for tenant records and is nonetheless at I1, because it takes the acting identity from the request body rather than from a verified token. Draft-5 conflated these by naming the authority inside the I2 definition, which made the level describing canonical identity unclaimable by the service that provides it. Corrected on tenant-engine's review — a reader would otherwise assume the authority must be at I2 by definition.</p>
|
||||
<p><code>business-app-service-contract</code> §2.1 sets app-local accounts as the v1 baseline for business apps — a sanctioned low level with recorded triggers for moving up. That is the pattern this framework generalises.</p>
|
||||
<h3>4.2 Authorization (A)</h3>
|
||||
<div class="breakout"><div class="ladders">
|
||||
<div class="ladder"><div class="pname">Authorization<span>axis A</span></div><div class="rungs"><div class="rung r0"><span class="code">A0</span><span class="txt">No authorization, or tenant context not carried.</span></div><div class="rung r1"><span class="code">A1</span><span class="txt">Ad-hoc checks scattered through handlers.</span></div><div class="rung r2"><span class="code">A2</span><span class="txt">A single local authorization boundary; tenant context bound once, centrally.</span></div><div class="rung r3"><span class="code">A3</span><span class="txt">Decisions delegated to <code>flex-auth</code> as PDP, with live re-query where the IAM Profile requires it.</span></div><div class="rung r4"><span class="code">A4</span><span class="txt">A3 over a <strong>standard</strong> PDP interface (OpenID AuthZEN Authorization API 1.0), so the decision point is swappable and the enforcement point is not coupled to one engine's request shape.</span></div></div></div>
|
||||
</div></div>
|
||||
<p><strong>This ladder describes enforcement points.</strong> A decision point cannot occupy A3 — "delegated to <code>flex-auth</code>" is not something <code>flex-auth</code> can do. A service that <em>is</em> a PDP declares <strong>two numbers</strong>: its own inbound level, and the maximum it enables for consumers. <code>flex-auth</code> reads <code>A0, enables A3</code> — accurate, and considerably more alarming than <code>A3</code>, which is the point. Raised by <code>flex-auth</code>, whose absence from the §5 worked examples was this surfacing implicitly.</p>
|
||||
<p>A4 is new. The specification reached Final in January 2026 and Keycloak shipped experimental support in May; the argument for it is <strong>interoperability</strong> — a swappable decision point and an enforcement point not coupled to one engine's request shape.</p>
|
||||
<p><em>Correction from flex-auth's review:</em> earlier drafts also justified A4 as ending the copying of action strings between repos. <strong>It does not.</strong> AuthZEN standardises the envelope — subject, action, resource, context, endpoint — and deliberately does not standardise the action vocabulary or the policy language. At A4, <code>tenant.guardrail.set</code> still has to be agreed and still gets copied. Those are two problems with different fixes, and the cheaper one is not A4: <code>flex-auth</code>'s registry already carries action definitions per system and could serve them read-only. The vocabulary argument is withdrawn.</p>
|
||||
<p><strong>Internal service-to-service calls are in scope for this axis.</strong> "Skipping tenant validation for internal services" is a named anti-pattern and our estate is mostly internal calls.</p>
|
||||
<p><em>Correction from flex-auth's review:</em> earlier drafts asserted that <code>flex-auth</code> calls <code>tenant-engine</code> synchronously on the authorization path. <strong>That is not true.</strong> The adapter is built and complete and has no non-test caller, so the IAM Profile's live re-query exists and is unwired — which is also why <code>flex-auth</code> cannot reach I3. Built-and-unwired is the worst of the three states because it reads as capability.</p>
|
||||
<p>The requirement, narrowed on their proposal because the original was too strong to be met and would have made <code>tenant-engine</code> a hard availability dependency of every decision in the estate:</p>
|
||||
<div class="rule-quote"><p>Tenant context MUST be carried on every internal hop and MUST NOT be re-derived from a service identity. It MUST be revalidated against <code>tenant-engine</code> at least once per request chain — at the service that holds or mutates the tenant's data, or before a privileged, destructive, credential-vending or <code>aal2</code>-class decision, whichever comes first. A hop that neither holds tenant data nor makes such a decision may carry the context without revalidating it.</p></div>
|
||||
<p><strong>And carrying tenant context is worthless without an authenticated hop to carry it over.</strong> <code>flex-auth</code> found this in itself: it carries tenant context faithfully and cannot distinguish "user-engine asking on behalf of tenant X" from "any pod asking on behalf of tenant X".</p>
|
||||
<h3>4.3 Enforcement (E)</h3>
|
||||
<div class="breakout"><div class="ladders">
|
||||
<div class="ladder"><div class="pname">Enforcement<span>axis E</span></div><div class="rungs"><div class="rung r0"><span class="code">E0</span><span class="txt">None. Data not tenant-keyed; separation incidental or absent.</span></div><div class="rung r1"><span class="code">E1</span><span class="txt">Data tenant-keyed, filtering applied per query at call sites.</span></div><div class="rung r2"><span class="code">E2</span><span class="txt">Filtering centralised at a single service-side choke point binding authenticated identity to permitted tenants.</span></div><div class="rung r3"><span class="code">E3</span><span class="txt">E2 <strong>plus</strong> platform-assisted filtering: row-level security keyed on a tenant GUC set transaction-locally, or an equivalent enforced data-access layer.</span></div><div class="rung r4"><span class="code">E4</span><span class="txt">Structural: the credential a workload holds cannot address another tenant's data at all. Requires per-tenant credentials and per-tenant substrate.</span></div></div></div>
|
||||
</div></div>
|
||||
<p><strong>Correction from draft-2.</strong> Draft-2 described E3 as something "the application cannot trivially route around". That is false and it was this document overclaiming in exactly the way §6 prohibits. Any session can re-issue <code>SET</code> on a custom GUC, so an attacker with SQL execution can reset the tenant and read across the boundary. What E3 buys is precise, and the ladder must say so:</p>
|
||||
<div class="scroll"><table class="tm"><thead><tr><th>Threat</th><th>E1</th><th>E2</th><th>E3</th><th>E4</th></tr></thead><tbody><tr><td>A developer forgets a tenant predicate</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr><tr><td>A new code path bypasses the choke point</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td></tr><tr><td>SQL injection reaching the connection</td><td class="no">✗</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td></tr><tr><td>The application process is compromised</td><td class="no">✗</td><td class="no">✗</td><td class="no">✗</td><td class="yes">✓</td></tr></tbody></table></div>
|
||||
<p>E3 is a strong control against <strong>accident</strong> — the common case, and the one that causes real breaches — and no control at all against <strong>compromise</strong>. Only E4 holds against both, because the credential itself cannot address another tenant's data.</p>
|
||||
<p><strong>Correction: E3 layers on E2, it does not replace it.</strong> External practice treats application-layer and database-layer filtering as complementary. A service that dropped its choke point on reaching E3 would be <em>worse</em> off, since E3 fails open under injection. Claiming E3 therefore requires the E2 evidence artifact as well.</p>
|
||||
<p><strong>Correction: the GUC is set transaction-locally.</strong> Draft-2 said "at pool checkout", which is session scope and the wrong instrument. Under a pooler in statement mode, <code>SET</code> leaks between clients and returns other tenants' rows — a failure that appears only under production concurrency and produces no error. Use <code>SET LOCAL</code> inside an explicit transaction.</p>
|
||||
<p><strong>Platform enforcement is a platform obligation.</strong> Reaching E3 requires the storage platform to <em>offer</em> the mechanism: provisioned policies, a documented GUC contract, and a probe. Where a consumer wants E3 and the platform has not supplied it, the gap is the platform's. §19.6 asks <code>rapp-postgres</code> to define that contract, which must carry <code>FORCE ROW LEVEL SECURITY</code> on every tenant table (without it the table owner bypasses policies silently, and ADR-0001 already established that our migration role owns the tables it creates), no <code>BYPASSRLS</code> on leased roles, <code>SECURITY INVOKER</code> for ordinary logic, and an <code>EXPLAIN</code> comparison because RLS disables functional indexes built on non-leakproof functions.</p>
|
||||
<p><strong>Not all data is tenant-keyed, and the ladder must not pretend otherwise.</strong> A registry whose rows <em>are</em> the tenants has no per-tenant predicate to scope a policy by; enforcing one would break the service's function rather than secure it. <code>tenant-engine</code>'s <code>tenants</code> table is the worked example — <code>key-cape</code> enumerates it at token issuance and <code>flex-auth</code> queries it live, both of which are cross-tenant reads by design.</p>
|
||||
<p>A service with mixed data shapes declares <strong>E-level plus a registry exception</strong>: the level its tenant-keyed tables hold, and a named list of tables excluded because they are registries rather than tenant data. The exception is part of the claim and is reviewable; an unnamed exception is an overclaim. Without this, mixed-shape services either overclaim or stay at E2 permanently, and <code>tenant-engine</code> declined to claim E3 on precisely that reasoning.</p>
|
||||
<p><strong>Default expectation</strong> for a new platform service: E2 at first serve, E3 recorded as target. Services whose cross-tenant exposure would be a reportable breach SHOULD target E3 or above.</p>
|
||||
<h3>4.4 Placement (P)</h3>
|
||||
<div class="breakout"><div class="ladders">
|
||||
<div class="ladder"><div class="pname">Placement<span>axis P</span></div><div class="rungs"><div class="rung r0"><span class="code">P0</span><span class="txt">Shares a database with another consumer.</span></div><div class="rung r1"><span class="code">P1</span><span class="txt">Database per consumer, shared cluster.</span></div><div class="rung r2"><span class="code">P2</span><span class="txt">Dedicated cluster per consumer.</span></div><div class="rung r3"><span class="code">P3</span><span class="txt">Dedicated cluster per tenant.</span></div><div class="rung r4"><span class="code">P4</span><span class="txt">P3 plus separate region or jurisdiction.</span></div></div></div>
|
||||
</div></div>
|
||||
<p>Enforcement and placement are independent axes. Plotted together, with where each service actually sits — parenthesised entries are targets or defaults rather than current positions, and <code>—</code> marks a cell the coupling in §3.2 makes unreachable:</p>
|
||||
<div class="matrix-shell"><div class="ylab">Enforcement →</div><div class="mgrid"><div class="rlab">E4</div><div class="mcell void"></div><div class="mcell void"></div><div class="mcell void"></div><div class="mcell tint4"><span class="pin ghost">business app</span></div><div class="mcell tint4"></div><div class="rlab">E3</div><div class="mcell tint3"></div><div class="mcell tint3"><span class="pin ghost">target</span></div><div class="mcell tint3"></div><div class="mcell tint3"></div><div class="mcell tint3"></div><div class="rlab">E2</div><div class="mcell tint2"><span class="pin ghost">tenant-engine</span></div><div class="mcell tint2"><span class="pin">audit-core</span></div><div class="mcell tint2"></div><div class="mcell tint2"></div><div class="mcell tint2"></div><div class="rlab">E1</div><div class="mcell tint1"><span class="pin ghost">absorbed repo</span></div><div class="mcell tint1"></div><div class="mcell tint1"></div><div class="mcell tint1"></div><div class="mcell tint1"></div><div class="rlab">E0</div><div class="mcell"></div><div class="mcell"></div><div class="mcell"></div><div class="mcell"></div><div class="mcell"></div><div class="rlab"></div><div class="clab">P0</div><div class="clab">P1</div><div class="clab">P2</div><div class="clab">P3</div><div class="clab">P4</div></div></div><div class="mnote"><span class="k"><span class="sw"></span>Where a service sits today</span><span class="k"><span class="sw g"></span>Target or default</span><span class="k"><span class="sw v"></span>Unreachable at this placement</span></div>
|
||||
<p><strong>P0 → P1 → P2 is movement along the horizontal axis only.</strong> Those steps buy consumer isolation, capacity predictability, independent retention and a smaller operational blast radius. They do not raise the tenant boundary by one step. Only P3 makes E4 reachable. This is the most misusable fact in the framework and §11 governs how it may be described.</p>
|
||||
<p><span class="dec">Decision 4.4.1:</span> P1 is the default for platform services; P3 for client-facing business apps, as already ratified. A service unsure which it is must resolve that first (§19.4).</p>
|
||||
<p><span class="dec">Decision 4.4.2 — placement scopes to data substrate.</span> Identity-provider placement (realm-per-tenant versus Organizations) is the same silo/pool decision on a different substrate, is live in our estate, and is undecided. Realm-per-tenant carries a stated ceiling around 5–20 tenants, far below our target. Recorded here as a parallel question (§19.7), not folded into P.</p>
|
||||
<h3>4.5 Retention and erasure (R)</h3>
|
||||
<p>New in draft-3. Implemented abstractly by the storage platform for any dataset; policy is built on top of that interface by the consumer or its governance layer. Reference implementation: <code>rapp-postgres</code> ADR-0002.</p>
|
||||
<div class="breakout"><div class="ladders">
|
||||
<div class="ladder"><div class="pname">Retention<span>axis R</span></div><div class="rungs"><div class="rung r0"><span class="code">R0</span><span class="txt">No retention or deletion position. Data kept indefinitely by default; no deletion path exists.</span></div><div class="rung r1"><span class="code">R1</span><span class="txt">Platform default retention applies (N=30 days). The consumer has declared no requirement.</span></div><div class="rung r2"><span class="code">R2</span><span class="txt">Retention declared as N days per dataset; the <strong>erasure horizon</strong> is published, and the consumer makes no promise shorter than it.</span></div><div class="rung r3"><span class="code">R3</span><span class="txt">Policy-driven deletion: the consumer or its governance layer declares what is due, the platform sweeps whole datasets on that instruction and evidences each run.</span></div><div class="rung r4"><span class="code">R4</span><span class="txt">Verified erasure: data proven unrecoverable across live storage, backups and derived copies, by one of the two routes below.</span></div></div></div>
|
||||
</div></div>
|
||||
<p><strong>R4 has two routes and a service MUST name which one it uses.</strong></p>
|
||||
<div class="scroll"><table><thead><tr><th>Route</th><th>Mechanism</th><th>Cost</th></tr></thead><tbody><tr><td><strong>Horizon-elapsed</strong></td><td>Wait out the published erasure horizon; the data ages out of every retained copy.</td><td>Available to everyone, proves little, and the wait is set by a co-resident's retention requirement rather than your own.</td></tr><tr><td><strong>Key-destroyed</strong></td><td>Encrypt per entity, then destroy the key. Retained copies survive but are unreadable.</td><td>Requires per-entity keys, strong encryption, and an auditable destruction record. Immediate.</td></tr></tbody></table></div>
|
||||
<p><span class="dec">Decision 4.5.3 — key destruction is not sufficient on its own.</span> The key-destroyed route requires that <strong>no retained commitment reveals the erased content</strong>. Found by <code>audit-core</code>, and it is a general defect rather than a fact about them:</p>
|
||||
<ul><li>A SHA-256 over a canonical record whose fields are low-entropy — event type, actor, tenant, subject, timestamp — is a <strong>confirmation oracle</strong>. Anyone holding the hash can guess the payload, hash the guess, and confirm a match. Destroying the key does not make the content unrecoverable while that hash survives.</li><li>Shreddability is <strong>not retrofittable</strong> onto an integrity chain that commits to cleartext. It has to be built as encrypt-then-hash at accept time, with the chain committing to ciphertext. Retrofitting means rewriting the chain — the exact thing a tamper-evident log exists to make detectable.</li></ul>
|
||||
<p>So a service claiming R4 by key destruction must show that its retained commitments — hashes, chains, indexes, search keys — do not reveal what was erased. The remedies are an HMAC under a per-subject key that dies with the key, or a per-record salt destroyed alongside it. <strong><code>audit-core</code> cannot reach R4 under its current design and targets R2; a fleet R4 target must exempt it explicitly.</strong></p>
|
||||
<p><strong>Regulatory standing of the key-destroyed route, stated carefully because overclaiming here is worse than anywhere else in this framework.</strong> Data protection authorities have accepted key destruction as erasure where physical deletion would be manifestly disproportionate, and the practice is recognised under conditions — strong encryption, irreversible destruction, and an auditable record of it. <strong>The EDPB has not formally endorsed it as Article 17 erasure.</strong> A service reaching R4 by key destruction is making a defensible claim, not a settled one, and must say so rather than reporting a clean "deleted".</p>
|
||||
<p>Three further properties.</p>
|
||||
<p><strong>The erasure horizon is the interval between deleting data and it ceasing to be recoverable from anything the platform holds.</strong> Deleting a row does not remove it from yesterday's backup. With an N-day window, deleted data remains recoverable for N days. That is the difference between "deleted" and "erased" and the estate had never written it down.</p>
|
||||
<p><strong>On shared substrate, retention is not per-consumer.</strong> Physical backup is instance-wide — one WAL stream, one window — so the instance retention is <em>derived</em> as the maximum across co-resident consumers, and every consumer's horizon is that maximum. A consumer declaring 7 days beside one declaring 90 gets 90. This is the retention analogue of ADR-0001's blast-radius disclosure: state the coupling rather than imply an isolation that is not there.</p>
|
||||
<p><strong>Retention is therefore a placement trigger.</strong> A consumer needing a horizon shorter than the instance floor cannot have one at P1. It moves to P2 for a reason with nothing to do with performance — which is exactly why it needs recording, since nobody looks for a retention argument when reviewing placement.</p>
|
||||
<p><span class="dec">Decision 4.5.4 — a retention promise binds both R and P.</span> A tier making a retention claim records an R minimum and a maximum erasure horizon in days. It also requires P2 or above <strong>unless</strong> its provider contract guarantees that the shared-substrate horizon stays within that maximum and rejects or notifies before a co-resident change would extend it. A bare <code>R2</code> minimum is insufficient: at P1 another consumer can change the promise without changing the tier or its holder.</p>
|
||||
<p>Deletion splits mechanism from policy. The platform deletes whole <strong>datasets</strong> on instruction and records an opaque policy reference it never interprets, so every deletion traces to what authorised it. Rows are not a dataset: row expiry is the consumer's own DML under its migration lease. Dropping a consumer's whole database is an operator-gated offboarding step, never a scheduled one.</p>
|
||||
<h3>4.6 Availability (V)</h3>
|
||||
<p>New in draft-8. <code>adaptive-pricing</code> found that §11 required availability claims to map to a minimum level while the framework supplied no availability vocabulary. Placement is not a substitute: a dedicated cluster can still be a single instance on a single node.</p>
|
||||
<div class="breakout"><div class="ladders">
|
||||
<div class="ladder"><div class="pname">Availability<span>axis V</span></div><div class="rungs"><div class="rung r0"><span class="code">V0</span><span class="txt">No availability or recovery position. Recovery is untested or depends on improvisation.</span></div><div class="rung r1"><span class="code">V1</span><span class="txt">Restart or recreate recovery in one failure domain is documented and exercised. Interruption is expected; this is recovery, not failover.</span></div><div class="rung r2"><span class="code">V2</span><span class="txt">Redundant instances provide automated service failover, with measured RTO/RPO; a shared failure domain or critical dependency may remain.</span></div><div class="rung r3"><span class="code">V3</span><span class="txt">The complete critical path survives loss of one declared failure domain, with measured RTO/RPO from an exercise.</span></div><div class="rung r4"><span class="code">V4</span><span class="txt">The complete critical path survives regional loss through tested multi-region failover, with measured RTO/RPO.</span></div></div></div>
|
||||
</div></div>
|
||||
<p><span class="dec">Decision 4.6.1 — V is end-to-end.</span> A service declares the minimum across the components and synchronous providers required to serve the operation. An application with three replicas over a V1 database is V1. A status page or replica count is not evidence of a higher level.</p>
|
||||
<p><span class="dec">Decision 4.6.2 — availability claims name the operation.</span> A read-only degraded mode and a mutation path may have different V levels. Decision 5.2 applies: declare the paths and quote the minimum unless the customer-facing claim explicitly and unambiguously names the narrower operation.</p>
|
||||
</section>
|
||||
<section id="s5"><h2><span class="sn">05</span>The posture vector</h2>
|
||||
<p>A service states one level per axis, plus a target, review dates, evidence and any exceptions. <code>current</code> is the highest <strong>evidenced</strong> level; a control present in code but still awaiting the evidence required by §13 goes in <code>implemented</code>, not in <code>current</code>:</p>
|
||||
<pre>schema_version: "0.1"
|
||||
framework: netkingdom-tenancy-posture
|
||||
service: example-service
|
||||
role: tenant-data-service
|
||||
tenancy:
|
||||
current: { I: 2, A: 3, E: 2, P: 1, R: 1, V: 1 }
|
||||
implemented: { E: 3 }
|
||||
target: { I: 2, A: 3, E: 3, P: 1, R: 2, V: 2 }
|
||||
reviewed: "2026-08-17"
|
||||
review_due: "2027-02-17"
|
||||
service_class: interactive
|
||||
gap:
|
||||
E: "RLS is implemented; the §13 E3 probe is still absent."
|
||||
R: "Retention declared; erasure horizon not yet published to consumers."
|
||||
V: "Automated failover is not implemented or exercised."
|
||||
evidence:
|
||||
A3: "docs/evidence/authorization-denial.md"
|
||||
E2: "docs/evidence/cross-tenant-review.md"
|
||||
P1: "rapp-postgres/docs/evidence/isolation-2026-08-10.md"</pre>
|
||||
<p><strong>Placement exceptions.</strong> Draft-2 assigned one P level per service, which cannot express the vertically partitioned model — most tenants pooled, some dedicated — that §11's isolation tiers require. A tier requiring <code>P2</code> bought by three tenants would put the service at two levels at once, forcing an over- or under-claim. Placement is therefore declared as a default plus exceptions:</p>
|
||||
<pre> placement_exceptions:
|
||||
- tenants: ["tenant:enterprise:*"]
|
||||
P: 3
|
||||
reason: "isolation tier; see adaptive-pricing tier definition"</pre>
|
||||
<p>A service with exceptions must be able to say which tenants are on which substrate. That mapping is a first-class artifact, not archaeology.</p>
|
||||
<p><span class="dec">Decision 5.5 — a provider declares what it makes reachable, not where it sits.</span> The six ladders describe a <em>consumer</em> of infrastructure. They describe a <em>provider</em> of it badly, and <code>railiance-platform</code>'s review demonstrated how badly: <code>apps-pg</code> is <code>I0 A0 E0</code> because a database has no tenant concept, carries no tenant claim and applies no tenant predicate. Those zeros are <strong>structural, not weak</strong> — the cluster is exactly as strong as its consumers make it.</p>
|
||||
<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: "2026-08-22"
|
||||
review_due: "2026-11-22"</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 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), 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 <path>...</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>
|
||||
<ul><li>Declaring <code>E0</code> is conformant. Concealing <code>E0</code> is not.</li><li>A repo may be absorbed at any posture. It may not be absorbed silently.</li><li>No service is blocked from the estate for being low on a ladder. Services MAY be blocked from <em>specific work</em> — serving a tenant grouping, holding a data class, carrying a plan tier — by requirements expressed as minimum levels.</li><li>Downgrading is permitted and must be declared. A regression found by guarding is a defect; a regression declared in advance is a decision.</li><li><strong>A low level may be permanent by design, and the declaration must be able to say so.</strong> <code>flex-auth</code> is <code>I1</code> and always will be: a decision point evaluates the claims it is handed, and verifying its own inputs would make it the identity provider its scope refuses to be. A <code>target</code> equal to <code>current</code> with a reason is a settled position, not a stalled trajectory, and §12's guard must not nag it as though it were one.</li></ul>
|
||||
<p><span class="dec">Decision 6.1 — downgrades propagate.</span> Before a planned downgrade of a current level or a provider's available level, the declaring repo MUST resolve the tier definitions and consumers that reference it. A downgrade below a recorded minimum blocks the change until the claim is changed, the workload is moved, or the affected owner explicitly accepts the gap. An unplanned regression is an incident and triggers the same notifications. Updating <code>tenancy.yaml</code> without notifying dependants is declaration drift, not a completed downgrade.</p>
|
||||
<p>Without the axis separation, "not rigorous about tenant separation" is one verdict a repo passes or fails. With it, the same repo is <code>I1 A1 E1 P0 R0 V0</code> with a path — a plan, not an indictment.</p>
|
||||
</section>
|
||||
<section id="s7"><h2><span class="sn">07</span>Portability across placement levels</h2>
|
||||
<p>Movement between P levels must be operational, not a rebuild:</p>
|
||||
<ul><li>Connect by injected credential only — no cluster, host, namespace or database name in source.</li><li>Own a whole database, never tables inside someone else's.</li><li>Idempotent schema creation.</li><li>No cross-database joins or co-location assumptions.</li></ul>
|
||||
<p><span class="dec">Decision 7.1:</span> mandatory at P1 and above. At P3, SHOULD rather than MUST — a per-client instance that never moves is not misconformant for naming its own database.</p>
|
||||
</section>
|
||||
<section id="s8"><h2><span class="sn">08</span>Placement triggers</h2>
|
||||
<p>Recorded at provisioning time: noisy neighbour on a latency-critical path; a compliance or residency requirement; a plan tier requiring a higher minimum; an erasure horizon that no longer fits (§4.5); connection or memory ceiling reached.</p>
|
||||
<p><span class="dec">Decision 8.1:</span> triggers MUST be <em>monitored</em>, not merely recorded. A trigger in a YAML comment nobody re-reads is documentation, not control.</p>
|
||||
<p><span class="dec">Decision 8.2 — split authority, machine-reconciled.</span> <code>railiance-platform</code> owns the placement rule; the package repo owns the substrate numbers and enforcement; the consuming repo owns its workload requirements; <code>adaptive-pricing</code> owns any tier minimum. <code>adaptive-pricing</code> declined a standing co-signature and the framework accepts the replacement: typed tier minima are joined to consumer and provider declarations at tier definition and whenever one changes. A machine-checkable constraint must not depend on somebody remembering to collect a signature.</p>
|
||||
<p><span class="dec">Decision 8.2.1 — trigger monitoring has an owner.</span> The provider monitors capacity ceilings and co-residency; the consumer monitors latency, compliance and erasure requirements; <code>adaptive-pricing</code> monitors tier-definition changes. The placement owner reconciles those signals. A trigger marked <code>unmonitored</code> is an explicit gap and cannot support a customer assurance claim.</p>
|
||||
<h3>8.3 Service class — a placement input, never a priority</h3>
|
||||
<p>A latency-critical consumer and a batch consumer can share an instance today with nothing distinguishing them. <code>tenant-engine</code> sits on <code>flex-auth</code>'s synchronous authorization path and chose a 5s statement timeout for that reason; <code>audit-core</code>, co-resident, is not latency-critical. Nothing prioritises between them.</p>
|
||||
<p><strong>The framework does not add a QoS axis, because the platform cannot enforce one.</strong> Community PostgreSQL has no resource governor: no per-role CPU or I/O priority, no resource queues, no workload classes. Those exist in EDB's enterprise variant, in Greenplum, and in SQL Server — not in what we run. A declared priority level would therefore be an unenforced claim sitting in a declaration, which is precisely what retiring <code>tenantIsolation</code> was about. An axis implies graduation and enforcement; this has neither.</p>
|
||||
<p><span class="dec">Decision 8.3.1 — co-residents are equal.</span> On shared substrate no consumer's query yields to another's. A consumer whose latency requirement cannot survive an unprioritised neighbour must escalate to <code>P2</code>. That is the honest mechanism and it is the only one we have.</p>
|
||||
<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>
|
||||
<p>Short-lived leased credentials re-read at connection checkout, with overlap-first rotation, bound the residual risk at every E level below E4: a leaked credential expires rather than persisting. Stronger than the industry norm of a long-lived per-service secret.</p>
|
||||
<p><span class="dec">Decision 9.1:</span> static long-lived database credentials are not a sanctioned path for any service above E0.</p>
|
||||
<p><span class="dec">Decision 9.2 — the rule extends to consumer-facing credentials.</span> Draft-6 named database access only. <code>audit-core</code> pointed out that its <em>ingest</em> credentials are static long-lived bearer tokens, rotated by publishing a second alongside the first — and that the argument applies with <strong>more</strong> force to the credential that actually carries the tenant claim than to the one that reaches the database behind it. Read as an accidental omission; it was. Consumer-facing credentials are named in. Where a service cannot yet meet this, it is a stated gap rather than a silent exclusion.</p>
|
||||
</section>
|
||||
<section id="s10"><h2><span class="sn">10</span>Blast radius must be published</h2>
|
||||
<p><span class="dec">Decision 10.1:</span> every platform holding consumer data MUST publish, in concrete terms, what a leaked runtime credential can and cannot reach at the levels it operates. <code>rapp-postgres</code> ADR-0001 §5 is the reference. Where the model cannot provide a guarantee, the platform says so and names the escalation.</p>
|
||||
<p><span class="dec">Decision 10.2 — quotas are disclosed, not discovered.</span> The same obligation extends from what a leaked credential can reach to what the platform will refuse to do for you. Every consumer MUST be told, at provisioning, the throttles and quotas enforced against it — connection limits, statement timeouts, idle-transaction timeouts — and told again when they change. A consumer learning its statement timeout by hitting it in production is a disclosure failure, not a consumer bug. This is how <code>tenant-engine</code> was provisioned, by good practice rather than by rule; the rule now exists.</p>
|
||||
</section>
|
||||
<section id="s11"><h2><span class="sn">11</span>Commercial expression</h2>
|
||||
<ul><li><strong>11.1</strong> Plan tiers are expressed <em>internally</em> as typed assurance requirements. A tier may require <code>E3 P2 R2 V2</code> and a maximum erasure horizon; it need not print those labels anywhere customer-facing.</li><li><strong>11.2</strong> Marketing and product language is free. No requirement to expose level labels or this document. "Dedicated infrastructure", "isolated tenancy", "private instance" all remain available.</li><li><strong>11.3</strong> The constraint is on <strong>evidence, not vocabulary</strong>. A customer-facing isolation, availability or retention claim must map to a minimum level the delivering service actually holds, recorded once when the tier is defined. The review is internal and happens at tier definition — not per campaign.</li><li><strong>11.4</strong> Two hard lines, because these reach contracts and compliance questionnaires:</li><li>A claim that another tenant <strong>cannot</strong> reach the customer's data requires <strong>E4</strong>.</li><li>A claim that deleted data <strong>is gone</strong> requires <strong>R4</strong>, or an erasure horizon disclosed alongside it. Where R4 is reached by key destruction, the claim is defensible but not settled law (§4.5) — it may be made, and it may not be made in language that implies a regulator has blessed it.</li><li>A claim that service survives loss of a zone requires <strong>V3</strong>; regional-loss language requires <strong>V4</strong>. "High availability" without a named failure and measured recovery objective is not an assurance claim this framework can evidence.</li><li><strong>11.5 — sanctioned honest language.</strong> The strong prohibitions above must not leave a commercial writer with only silence:</li><li>E3 may be described as database-backed defence against an omitted tenant filter; it must not be paraphrased as "another tenant cannot reach".</li><li>P2 may be described as a dedicated service database cluster with an independent capacity and restore boundary; it is not tenant-dedicated.</li><li>R2 may state the declared retention and published erasure horizon.</li><li>V1 may state exercised restart recovery in one failure domain and must say that interruption and single-domain loss remain.</li><li><strong>11.6 — authority and reconciliation.</strong> The tier definition is authoritative for the minimum and customer wording. <code>tenancy.yaml</code> is authoritative for the delivering service's current level; provider declarations are authoritative for what infrastructure makes available. None is derived by copying another. Approval joins them and fails closed on a missing, stale or insufficient declaration. A performance-differentiated tier requires P2 or an enforceable resource governor; service class alone grants no priority.</li></ul>
|
||||
</section>
|
||||
<section id="s12"><h2><span class="sn">12</span>Methodology — analyze, establish, improve, guard</h2>
|
||||
<p><strong>Analyze.</strong> Assess a repo against the ladders; produce <code>tenancy.current</code> with reasoning recorded. Applies to new and absorbed services alike.</p>
|
||||
<p><strong>Establish.</strong> Declare the target and gap. The target is set by data class, tenant groupings served and plan tiers carried — not by ambition.</p>
|
||||
<p><strong>Improve.</strong> Move one axis at a time. Raising P while leaving E untouched is the characteristic misstep.</p>
|
||||
<p><strong>Guard.</strong> Verify continuously that the declared posture holds — <strong>against the service's own declaration</strong>, not a universal maximum. Nobody must prove every service is at E4; the check is that none is below what it declared.</p>
|
||||
<p>Regression found by guarding is a defect; regression declared in advance is a decision. The estate has been bitten twice by silent pin rollbacks producing ordinary-looking 403s and 404s rather than errors. Posture regression looks the same — an RLS context leak returns correct-looking rows for the wrong tenant. Guarding must be designed for invisible failure, not for crashes.</p>
|
||||
</section>
|
||||
<section id="s13"><h2><span class="sn">13</span>Evidence per level</h2>
|
||||
<p><span class="dec">Decision 13.1:</span> a current level is claimed only with its evidence artifact present. This turns §6's accuracy rule from an honour system into a check. <code>implemented</code> records a control observed in code or configuration whose required artifact is still absent; it never satisfies a tier minimum.</p>
|
||||
<p><span class="dec">Decision 13.1a — the floor needs no artifact, only a reason.</span> Found independently by <code>audit-core</code> and <code>flex-auth</code>: the table below defines artifacts from <code>I2</code>, <code>A2</code>, <code>E1</code>, <code>P1</code>, <code>R2</code> upward and none below, so a literal 13.1 made the lowest rungs unclaimable — including §5's own worked example of a conformant absorbed repo, <code>I1 A1 E1 P0 R0</code>, which could not satisfy it on any axis. A rule that forbids the declaration §6 exists to permit is a defect in the rule.</p>
|
||||
<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.<level></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>
|
||||
</section>
|
||||
<section id="s14"><h2><span class="sn">14</span>Adoption stance — structure, not tooling</h2>
|
||||
<p><span class="dec">Decision 14.1:</span> external research is design input. This estate adopts published standards and structural patterns; it does not adopt tooling unless that tooling is an established industry standard with broad application. Everything else is built ground-up, so it can be optimised and refactored as the estate sees fit.</p>
|
||||
<div class="scroll"><table><thead><tr><th>Class</th><th>Stance</th></tr></thead><tbody><tr><td>Security baselines (OWASP Multi-Tenant Security Cheat Sheet, API Security Top 10)</td><td>Adopt as the external reference our ladders answer to</td></tr><tr><td>Standards bodies (OpenID AuthZEN 1.0)</td><td>Adopt — this is what A4 is</td></tr><tr><td>Reference taxonomies (Azure tenancy models, AWS SaaS Lens, cell architecture)</td><td>Adopt as structure</td></tr><tr><td>Engine behaviour (PostgreSQL RLS mechanics)</td><td>Facts, not tooling</td></tr><tr><td>Third-party analyzers and test frameworks</td><td><strong>Do not adopt.</strong> Take their rule taxonomies as checklists for probes we write ourselves</td></tr></tbody></table></div>
|
||||
<p>The practical effect is small and good: <code>rapp-postgres</code> already owns a ground-up probe harness — bash and psql, no dependency tree — that found four real defects in its own provisioning SQL. The evidence artifacts in §13 become new probes in a tool we control. One idea worth reimplementing from the external survey is <strong>policy-diff classification</strong>: labelling a change to an enforcement policy as safe or breaking <em>before</em> it lands.</p>
|
||||
</section>
|
||||
<section id="s15"><h2><span class="sn">15</span>Alternatives considered</h2>
|
||||
<p><strong>One fixed model with a single set of characteristics</strong> (draft-1). <em>Rejected:</em> cannot describe a repo that is not there yet, forcing absorbed repos to misrepresent their posture or stay outside. A framework that can only describe its own end state is not a framework.</p>
|
||||
<p><strong>A maturity model with a single overall level.</strong> <em>Rejected:</em> collapses the axis separation. A service strong on identity and weak on enforcement has a specific, actionable gap; one composite score hides it and invites averaging.</p>
|
||||
<p><strong>Prohibiting row-level security</strong> (draft-2's inherited position). <em>Rejected in draft-2, refined in draft-3:</em> RLS is a real rung against the common threat. The error was never RLS — it was describing E3 in E4's language.</p>
|
||||
<p><strong>Schema-per-consumer in one database.</strong> <em>Rejected:</em> <code>pg_catalog</code> is readable per-database, so every co-resident enumerates every other's table and column names regardless of grants. Retained as a describable state, never a target.</p>
|
||||
<p><strong>Mandating E4 for everyone.</strong> <em>Rejected:</em> the tenant taxonomy includes <code>consumer</code> (private individuals) and <code>family</code>. A cluster per private individual is economically impossible; the taxonomy is itself evidence pooling is required.</p>
|
||||
<p><strong>Per-consumer physical backup retention.</strong> <em>Rejected:</em> CNPG retention is a property of the instance's WAL archive. There is no mechanism, and claiming it would be a fabricated guarantee. Hence the derived maximum in §4.5.</p>
|
||||
<p><strong>Platform-scheduled row expiry.</strong> <em>Rejected:</em> requires the platform to hold DML authority over consumer schemas and interpret consumer data semantics, both forbidden by ADR-0001. The consumer's migration lease is the correct instrument.</p>
|
||||
<p><strong>Leaving each repo to its own model.</strong> <em>Rejected:</em> the status quo, which produced two contradictory ratified defaults and an unowned placement question.</p>
|
||||
</section>
|
||||
<section id="s16"><h2><span class="sn">16</span>Held against outside practice</h2>
|
||||
<p><strong>The graduated reframe is corroborated, not invented here.</strong> Microsoft's tenancy-model guidance states it almost verbatim: <em>"Instead of viewing isolation as a discrete property, consider it a spectrum. You can deploy components of your architecture that are more isolated or less isolated than other components in the same architecture."</em> The same guidance derives our E↔P coupling independently — shared deployment means enforcement lives in application code; dedicated deployment means it is structural.</p>
|
||||
<p><strong>Stronger than typical.</strong> Most multi-tenancy literature models one boundary, tenant-to-tenant. This estate has <strong>two stacked boundaries</strong>: platform-service to platform-service, and tenant to tenant inside a consumer. Naming them separately and refusing to enforce both with one mechanism is uncommon and correct. Graduated per-axis levels also beat the silo/pool/bridge trichotomy, which is approximately our P axis with the other four missing — which is why it cannot express "pooled infrastructure, structurally enforced boundary".</p>
|
||||
<p><strong>Weaker than typical.</strong> The pool model's standard mitigation is a <em>verified</em> enforcement layer every service is demonstrably routed through. We have the concept and none of the verification (§13.3).</p>
|
||||
<p><strong>Adopted without naming it.</strong> Short-lived leased credentials re-read at checkout beat the long-lived-secret norm. §9 promotes it to a tenancy control.</p>
|
||||
<p><strong>Still unexplored.</strong> Neither P nor R describes a <strong>cell</strong> — a slice of infrastructure with a <em>fixed maximum size</em>, sized so one cell's failure is survivable and cell count scales linearly. <code>platform-pg</code> is, in these terms, an uncapped cell: §17 computes a ceiling and nothing enforces it (§19.8).</p>
|
||||
<p>Sources: the five research digests in <code>the-custodian/research/2026-08-17-adr008-*</code>, which carry full citations for the claims in this section.</p>
|
||||
</section>
|
||||
<section id="s17"><h2><span class="sn">17</span>Scaling demands</h2>
|
||||
<p>Derived from the live <code>platform-pg</code> specification. Connection arithmetic is exact; the per-backend memory estimate remains unmeasured and is explicitly a gap in <code>rapp-postgres</code> ADR-0004.</p>
|
||||
<pre>instances: 1 (no HA; single-node rail)
|
||||
max_connections: 100
|
||||
memory limit: 1Gi
|
||||
per consumer: 14 connections (12 runtime + 2 migration)</pre>
|
||||
<p><strong>The hard connection bound is roughly six declarations; the enforceable operational ceiling is four.</strong> Seven declarations request 98 of 100 connections before CNPG's instance manager, metrics exporter and reserved slots. Every one of them is politely inside its declared 14-connection allowance; the instance still fails. ADR-0004 sets four because memory is expected to bind first and fails by OOM-killing every co-resident rather than refusing one connection.</p>
|
||||
<p>That distinction matters because our governance addresses the wrong shape. Per-consumer <code>connection_limit</code>, <code>statement_timeout</code> and <code>idle_in_transaction_session_timeout</code> guard well against <strong>one greedy consumer</strong>. They do nothing about <strong>the aggregate of many modest ones</strong>, which is the second and less intuitive noisy-neighbour failure and the one this number describes. Two workload consumers plus the isolation probe occupy three of the four declared slots. The next workload request must trigger measurement and the overflow decision before admission.</p>
|
||||
<p><strong>Memory likely binds first.</strong> 100 backends against 1Gi is ~10MB per backend. Connection exhaustion errors clearly; memory pressure OOM-kills and degrades every co-resident at once.</p>
|
||||
<p><strong>E3 and pooling.</strong> <em>Corrected from draft-2, which had this backwards.</em> Transaction-scoped context (<code>SET LOCAL</code> inside an explicit transaction) is what makes E3 <strong>safe</strong> under a pooler. Statement-level pooling is what breaks it, serving other tenants' rows under concurrency with no error. E3 constrains which pooling mode is available, not whether pooling is available.</p>
|
||||
<p><strong>Retention consumes the volume.</strong> WAL accumulates with the window, and §4.5 makes the window the maximum across consumers. A consumer declaring a long retention extends everyone's horizon <em>and</em> everyone's storage draw against a 20Gi volume.</p>
|
||||
<p><strong>Restore time couples all consumers.</strong> Physical backup is instance-wide, so a consumer's RTO is a function of <em>total</em> instance size, not its own.</p>
|
||||
<p><strong><code>platform-pg</code> is V1.</strong> <code>instances: 1</code> on a single-node rail provides exercised restart recovery and no failover. P1 describes its consumer placement and says nothing about this availability fact; the new V axis carries it.</p>
|
||||
</section>
|
||||
<section id="s18"><h2><span class="sn">18</span>Consequences</h2>
|
||||
<ul><li>The estate gains one vocabulary and a way to be honest about partial adoption.</li><li>Absorbed repos get a described state and a path instead of a failing grade.</li><li><code>tenantIsolation</code> in <code>PostgresConsumer</code> is revealed as a mislabelled field.</li><li>The verification problem becomes tractable: guard against declaration.</li><li>Draft-2's RLS prohibition is reversed and its E3 description corrected; <code>rapp-postgres</code> acquires an obligation to define and offer the mechanism.</li><li>Adding a consumer with long retention <strong>silently extends everyone's erasure horizon</strong>. This must reach the consumer review checklist, not only this document.</li><li>A service selling an isolation tier must maintain a tenant→substrate mapping it does not have today.</li><li>Availability becomes an end-to-end, evidenced property rather than an inference from replica count or placement.</li><li>Nothing here changes a running system.</li></ul>
|
||||
</section>
|
||||
<section id="s19"><h2><span class="sn">19</span>Review resolutions and residual questions</h2>
|
||||
<ol><li><strong><code>tenantIsolation</code> field — resolved.</strong> <code>rapp-postgres</code> retired it. A consumer declaration asks for mechanisms; posture lives in the consumer's <code>tenancy.yaml</code>.</li><li><strong>Placement ownership — resolved.</strong> §8.2 records the split. The policy has one owner; typed tier requirements replace the declined commercial co-signature.</li><li><strong>E2, E3 and noisy-neighbour evidence</strong> — <strong>owned as of 2026-08-17</strong> by <code>whitehat-security</code> (WHITEHAT-WP-0001), an independent adversarial evidence facility seeded for this purpose. <code>audit-core</code> and <code>tenant-engine</code> were right to decline it as fleet-scope work; the answer was a home of its own rather than a volunteer.</li></ol>
|
||||
<p><strong>Owned by NetKingdom</strong> — corrected 2026-08-17; an earlier revision of this section proposed otherwise on independence grounds and was overruled. Offensive security is security work and belongs with the repo that owns security. The facility is framed offensively rather than as a conformance checker: it is pointed at infrastructure we choose, our own estate among them, and conformance testing is one use of a general capability.</p>
|
||||
<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>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:<grouping>:<name></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-14 · proposed</span><span>net-kingdom · canon/standards/tenancy-posture_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8</span></footer></main></div></div></html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue