docs(canon): reconcile workload and tenant grouping semantics
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
tegwick 2026-08-22 14:53:31 +02:00
parent ad2057acff
commit bee22db620
21 changed files with 1118 additions and 59 deletions

1
.gitignore vendored
View file

@ -83,6 +83,7 @@ instance/
# Sphinx documentation
docs/_build/
docs/architecture/.ast_cache/
# PyBuilder
.pybuilder/

View file

@ -19,6 +19,8 @@
| workplan | NK-WP-0024 | finished | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| workplan | NK-WP-0025 | finished | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| workplan | NK-WP-0026 | finished | — | workplans/NK-WP-0026-flex-auth-caller-identity-rollout.md |
| workplan | NK-WP-0027 | blocked | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| workplan | NK-WP-0028 | finished | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| task | ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |
| task | ADHOC-2026-07-02-T02 | done | — | workplans/ADHOC-2026-07-02.md |
| task | ADHOC-2026-08-14-T01 | done | — | workplans/ADHOC-2026-08-14.md |
@ -29,20 +31,20 @@
| task | NET-WP-0020-T03 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
| task | NET-WP-0020-T04 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
| task | NET-WP-0020-T05 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
| task | NK-WP-0009-T1 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T2 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T3 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T4 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T5 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T6 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0011-T1 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T2 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T3 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T4 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T5 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T6 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T7 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T8 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0009-T01 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T02 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T03 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T04 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T05 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0009-T06 | todo | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| task | NK-WP-0011-T01 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T02 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T03 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T04 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T05 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T06 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T07 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0011-T08 | todo | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| task | NK-WP-0021-T01 | done | — | workplans/NK-WP-0021-activity-core-ops-sso-operators.md |
| task | NK-WP-0021-T02 | done | — | workplans/NK-WP-0021-activity-core-ops-sso-operators.md |
| task | NK-WP-0021-T03 | done | — | workplans/NK-WP-0021-activity-core-ops-sso-operators.md |
@ -75,5 +77,15 @@
| task | NK-WP-0025-T05 | done | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| task | NK-WP-0026-T01 | done | — | workplans/NK-WP-0026-flex-auth-caller-identity-rollout.md |
| task | NK-WP-0026-T02 | done | — | workplans/NK-WP-0026-flex-auth-caller-identity-rollout.md |
| task | NK-WP-0027-T01 | done | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T02 | wait | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T03 | wait | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T04 | done | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T05 | done | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T06 | wait | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0028-T01 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| task | NK-WP-0028-T02 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| task | NK-WP-0028-T03 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| task | NK-WP-0028-T04 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
| intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |

View file

@ -21,6 +21,9 @@
"type": "string",
"minLength": 1
},
"workload_identity": {
"$ref": "#/$defs/workloadIdentity"
},
"tenancy": {
"$ref": "#/$defs/tenancy"
},
@ -28,7 +31,7 @@
"$ref": "#/$defs/provider"
},
"zones": {
"description": "Reserved for security-zone membership (tenancy-posture_v0.1 Decision 5.6). Its internal shape is defined by the NetKingdom security-zone standard drafted by zone-engine, not by this schema, and is deliberately unconstrained here until that standard lands. Present so a conformant combined declaration is not rejected by this validator."
"$ref": "#/$defs/zoneDeclaration"
},
"evidence": {
"$ref": "#/$defs/evidence"
@ -83,6 +86,16 @@
"provider"
]
},
{
"required": [
"workload_identity"
]
},
{
"required": [
"zones"
]
},
{
"required": [
"evidence"
@ -92,12 +105,30 @@
}
}
],
"allOf": [
{
"if": {
"required": [
"zones"
]
},
"then": {
"required": [
"workload_identity"
]
}
}
],
"additionalProperties": false,
"$defs": {
"serviceName": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._-]*$"
},
"repoName": {
"type": "string",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
},
"axisName": {
"enum": [
"I",
@ -209,6 +240,183 @@
"minLength": 1
}
},
"identityBinding": {
"type": "object",
"required": [
"scheme",
"authority",
"subject",
"principal_type"
],
"properties": {
"scheme": {
"type": "string",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
"description": "Identity mechanism, for example iam-profile, kubernetes-service-account, ssh-certificate, or openbao-auth-role."
},
"authority": {
"type": "string",
"minLength": 1,
"description": "Authoritative issuer or registry for this principal."
},
"subject": {
"type": "string",
"minLength": 1,
"description": "Exact principal value asserted by the authority."
},
"principal_type": {
"enum": [
"service",
"agent"
],
"description": "IAM Profile principal type. A human identity is caller context and cannot be the sole workload identity."
},
"environment": {
"type": "string",
"minLength": 1
},
"evidence": {
"$ref": "#/$defs/stringList"
}
},
"additionalProperties": false
},
"workloadIdentity": {
"type": "object",
"required": [
"name",
"kind",
"responsible_repo",
"identity_bindings"
],
"properties": {
"name": {
"$ref": "#/$defs/serviceName",
"description": "Stable workload id. It must equal the containing service field."
},
"kind": {
"enum": [
"application",
"platform-service",
"automation",
"operational-control-plane",
"maintenance-job"
]
},
"responsible_repo": {
"$ref": "#/$defs/repoName",
"description": "Repository accountable for the workload identity and zone declaration."
},
"declaration_ref": {
"type": "string",
"minLength": 1,
"description": "Authoritative owner declaration. Required by RMGR-ADR-004 for a managed deployable, for example rapp-user-engine/declarations/rapp.yaml."
},
"identity_bindings": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/identityBinding"
}
}
},
"additionalProperties": false
},
"zoneEvidence": {
"type": "object",
"required": [
"ref",
"supports"
],
"properties": {
"ref": {
"type": "string",
"minLength": 1
},
"supports": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
}
}
},
"additionalProperties": false
},
"zoneDeclaration": {
"type": "object",
"required": [
"standard",
"membership",
"responsible_party",
"justification",
"context",
"evidence",
"reviewed",
"review_due"
],
"properties": {
"standard": {
"const": "security-zones_v0.1"
},
"membership": {
"enum": [
"z0-experimental",
"z1-operational",
"z2-protected",
"z3-critical",
"z2-continuity"
]
},
"responsible_party": {
"type": "string",
"minLength": 1
},
"justification": {
"type": "string",
"minLength": 1
},
"context": {
"type": "object",
"required": [
"maturity",
"criticality",
"data_classification"
],
"properties": {
"maturity": {
"enum": ["M0", "M1", "M2", "M3"]
},
"criticality": {
"enum": ["low", "medium", "high", "critical", "n/a"]
},
"data_classification": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
"evidence": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/zoneEvidence"
}
},
"reviewed": {
"type": "string",
"format": "date"
},
"review_due": {
"type": "string",
"format": "date"
}
},
"additionalProperties": false
},
"paths": {
"type": "object",
"propertyNames": {
@ -408,6 +616,9 @@
"type": "string",
"minLength": 1
},
"workload_identity": {
"$ref": "#/$defs/workloadIdentity"
},
"tenancy": {
"$ref": "#/$defs/tenancy"
},
@ -419,8 +630,25 @@
},
"notes": {
"$ref": "#/$defs/stringList"
},
"zones": {
"$ref": "#/$defs/zoneDeclaration"
}
},
"allOf": [
{
"if": {
"required": [
"zones"
]
},
"then": {
"required": [
"workload_identity"
]
}
}
],
"additionalProperties": false
}
}

View file

@ -5,11 +5,11 @@ title: "NetKingdom IAM Profile v0.3"
domain: netkingdom
status: accepted
owner: net-kingdom
last_reviewed: "2026-07-23"
last_reviewed: "2026-08-22"
review_interval: 6m
version: "0.3"
created: "2026-07-23"
updated: "2026-07-23"
updated: "2026-08-22"
scope: core-platform
supersedes:
- canon/standards/iam-profile_v0.2.md
@ -236,7 +236,7 @@ flex-auth resource/action semantics.
`tenant` is required for every token accepted by profile consumers.
Tenant identifiers follow `tenant:<grouping>:<name>`, where `<grouping>` is
one of the taxonomy ratified by ADR-0013:
one of the taxonomy ratified by ADR-0013 at identifier creation:
```text
trial - test/trial/showcase tenants only
@ -265,9 +265,14 @@ 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.
The grouping segment is **onboarding-risk / entity-shape** classification
only. It does not gate which capability roles (below) a tenant may hold —
see Tenant Roles.
The grouping segment is an immutable record of the tenant's
**onboarding-time** 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 `grouping` field held by
`tenant-engine`; consumers MUST NOT split `tenant` 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.
## Tenant Roles

View file

@ -1,10 +1,21 @@
---
id: security-zones_v0.1
id: netkingdom-security-zones-v0.1
type: standard
title: "NetKingdom Security Zones v0.1"
domain: netkingdom
status: proposed
version: "0.1"
owner: zone-engine
publication_owner: net-kingdom
date: "2026-08-22"
created: "2026-08-22"
updated: "2026-08-22"
last_reviewed: "2026-08-22"
review_interval: 3m
source_revision: "zone-engine@a510393"
standard_token: security-zones_v0.1
related:
- canon/standards/tenancy-posture_v0.1.md
- repo-manager/docs/adr-004-authoritative-workload-declarations.md
---
# NetKingdom Security Zones v0.1
@ -313,8 +324,8 @@ conformant.
## 9. Time-boxed exceptions
The normative lifecycle is the T04 decision in
`docs/exception-lifecycle-2026-08-22.md`: only the control owner's designated
The normative lifecycle is the ZONE-WP-0001-T04 decision in
`zone-engine/docs/exception-lifecycle-2026-08-22.md`: 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
`not_before <= now < not_after`; invalid or unevaluable records are inactive,

View file

@ -6,11 +6,11 @@ domain: netkingdom
status: proposed
version: "0.1"
created: "2026-08-17"
updated: "2026-08-19"
updated: "2026-08-22"
scope: multi-tenancy-security-framework
revision: "draft-9"
revision: "draft-12"
owner: net-kingdom
last_reviewed: "2026-08-19"
last_reviewed: "2026-08-22"
review_interval: 6m
declaration_schema: canon/schemas/tenancy-posture_v0.1.schema.json
adr:
@ -28,7 +28,7 @@ related:
## Status
**Proposed, draft-9; ratification-ready.** Relocated from
**Proposed, draft-12; ratification-ready.** Relocated from
`the-custodian/canon/architecture` 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
@ -64,6 +64,24 @@ and the tenant-engine boundary contract, not in the work-factory canon.
`zones:` in `tenancy.yaml` so the estate keeps one declaration surface, and it
records the reef/`P`/`V` reconciliation as an open defect of this document
rather than of the repo that noticed it (Decision 8.4).
- **draft-10** answers `zone-engine`'s `ZONE-WP-0001-T03`. 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 `unknown` without zone inference
(Decision 5.6.1). It also closes the textual reef/`P` boundary while leaving
the substrate-provider declaration and mechanical `V` join as implementation
work (Decision 8.4.2).
- **draft-11** makes that ruling declarable. A `zones:` block now requires an
authoritative `workload_identity` beside it, including for non-`rapp`
operational workloads; multi-service files carry both per service. Missing
identity remains absence rather than a guessed join (Decision 5.6.2).
- **draft-12** reconciles that declaration with RMGR-ADR-004 and the published
`security-zones_v0.1` 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 `not-applicable`, while omitted or unresolved workload references
remain `unknown` (Decision 5.6.2).
**Reviewed by all six. The score:** six repos found three live defects in their
own code by reading the ladders — `tenant-engine`'s unfiltered
@ -650,6 +668,106 @@ joined by machine.
repos. It is an *input* to stance selection and should be read by the zone
model, not absorbed into a declaration.
**Decision 5.6.1 — the workload remains the sole zone policy subject, and
absence resolves to `unknown`.** Here **workload** 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.
A repository, credential lane, grant template, pattern, or software package is
not an alternative **zone** policy subject. A broker runtime is a workload; the
grants and lanes it handles retain native resource identity and may explicitly
be `not-applicable` to workload resolution. Every managed running deployable,
including operational and tooling runtimes, has one authoritative
`rapp-*/declarations/rapp.yaml` under RMGR-ADR-004. A pre-rapp managed runtime
is migration debt and resolves `unknown`. An independently governed operational
execution unit that is not a managed deployable may declare directly in its
responsible repo's `tenancy.yaml`. The declaration surface broadens to cover
real workloads; the subject model does not broaden merely to totalize an
incomplete registry.
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 `unknown`.
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
`unknown` — including denial, escalation, or a build-stage rule — but that
treatment is stance, not membership. `unknown` never silently inherits a
permissive zone or exception.
**Decision 5.6.2 — zone membership requires an authoritative workload binding
in the same declaration entry.** A bare service name is a label, not identity
evidence. Any single-service declaration carrying `zones:` MUST also carry
`workload_identity`; in a layer repo using `services:`, both fields live on the
same service entry. Top-level `zones:` is not valid for a multi-service file,
because it would make membership ambiguous.
The binding states:
- `name` — the stable workload id, exactly equal to the declaration's
`service`;
- `kind` — application, platform service, automation, operational control
plane, or maintenance job;
- `responsible_repo` — the repository accountable for the identity and zone
declaration;
- one or more `identity_bindings`, each naming the identity scheme,
authoritative issuer or registry, exact subject, IAM Profile principal type,
and optional environment and evidence; and
- for a managed deployable, a `declaration_ref` to its authoritative rapp
declaration. Consuming catalogs reference it with the exact Repo Manager v1
tuple `(rapp_id, workload_identity.name)` and optional `deployable`.
An identity binding uses `principal_type: service` or `agent`. 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 `tenancy.yaml`; native actions and resources do not
acquire a fictional workload or rapp merely to enter policy.
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 `unknown`.
```yaml
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"
```
Worked examples after applying the evidence rule and minimum-across-paths rule
consistently:
@ -702,7 +820,8 @@ normative schema is
declaration but do not replace it. The schema carries `current`, `implemented`,
`target`, `reviewed`, `review_due`, `gap`, `placement_exceptions`,
`service_class` (§8.3), per-path detail (§5.2), and provider reachability
(§5.5). From the `net-kingdom` repo, owners validate one or more declarations
(§5.5), plus the workload identity prerequisite for zone membership (§5.6.2).
From the `net-kingdom` repo, owners validate one or more declarations
with `uv run tools/tenancy-posture/validate.py <path>...`; the validator applies
the JSON Schema and the evidence, date, implemented/current and provider-range
rules that JSON Schema alone cannot express.
@ -845,6 +964,14 @@ not. `P` grades **tenant data isolation within a datastore**; a reef is a named
rung meaning "single node, shared control plane, risk accepted", and inventing
one would be the fabrication §6 prohibits.
A reef binding therefore satisfies none of the `P` 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 `V`, because availability composes across the complete
critical path and the substrate can impose a ceiling.
The live consequence is on `V`, not `P`. §4.6 already warns that "a dedicated
cluster can still be a single instance on a single node", and Decision 4.6.1
makes `V` the minimum across the synchronous path. `reef-railiance` is
@ -1265,12 +1392,10 @@ nothing about this availability fact; the new V axis carries it.
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.
**Routed elsewhere, deliberately.** The tenant identifier
`tenant:<grouping>:<name>` embeds headcount bands (`small`, `medium`, `large`)
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 `tenant-engine` and NetKingdom canon. Folding it
in here would overreach.
**Tenant grouping ambiguity — resolved outside this framework.** ADR-0013
revision 2 makes the identifier segment immutable onboarding-time history and
the `tenant-engine` record authoritative for current grouping. Consumers do not
parse current policy or spend-ceiling inputs from `tenant:<grouping>:<name>`.
## 20. Ratification path

View file

@ -6,7 +6,7 @@ domain: netkingdom
status: accepted
version: "0.1"
created: "2026-07-23"
updated: "2026-07-23"
updated: "2026-08-22"
scope: tenant-domain-boundaries
adr:
- docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md
@ -55,16 +55,22 @@ deployments.
| Resource kind | Source of truth | tenant-engine relation | Boundary rule |
| --- | --- | --- | --- |
| Tenant record (existence, grouping, name/slug) | `tenant-engine` | Canonical owner | Grouping value must be one of ADR-0013's taxonomy, or the reserved ungrouped `platform`/`coulomb` identifiers |
| Tenant record (existence, current grouping, name/slug) | `tenant-engine` | Canonical owner | Current grouping must be one of ADR-0013's taxonomy, or the reserved ungrouped `platform`/`coulomb` classifications; the identifier segment is historical and must not be used as current grouping |
| Tenant capability roles | `tenant-engine` | Canonical owner | Non-exclusive; grant/revoke are audited mutations (see Grant Contract below), never free-form field updates |
| Plan/subscription assignment | `tenant-engine` | Canonical owner | References an `adaptive-pricing` plan id; does not define plan terms |
| Pricing-model / plan definitions | `adaptive-pricing` | Consume by reference only | `tenant-engine` must not cache plan terms beyond what's needed to resolve which roles a plan currently grants |
| Guardrail/quota policy (spend limits, entity/action counts) | `tenant-engine` (reserved) | Canonical owner once designed | Not implemented by this contract version — reserved namespace only, see Guardrail Policy section |
| `tenant_roles` token claim | `tenant-engine` (live) / `key-cape` (cached copy) | `tenant-engine` is authoritative; the token claim is a point-in-time cache | Privileged/high-stakes decisions MUST re-query `tenant-engine` live — see IAM Profile v0.3, "Tenant Roles" section — never trust the cached claim alone |
| Tenant identifier claim shape (`tenant`) | NetKingdom / IAM Profile contract | `tenant-engine` validates against it; does not mint the claim format | The wire format stays owned by the profile; `tenant-engine` owns which concrete tenant values currently exist |
| Tenant identifier claim shape (`tenant`) | NetKingdom / IAM Profile contract | `tenant-engine` validates against it; does not mint the claim format | The wire format stays owned by the profile; the identifier is immutable, including its historical grouping segment; `tenant-engine` owns which concrete tenant values currently exist |
| User/membership records scoped by a tenant | `user-engine` | No relation | `tenant_id` is the only key shared between the two services; `tenant-engine` never stores or reads user data |
| Authorization decisions | `flex-auth` | Data source only | `tenant-engine` never enforces access itself; it answers queries `flex-auth`'s policy packages issue |
The tenant identifier and current grouping are intentionally independent after
creation. `tenant-engine` MAY change the record's `grouping` through an audited
mutation without renaming `tenant_id`. Reads and domain events expose current
grouping explicitly. No API, guardrail, pricing, or authorization consumer may
recover current grouping by parsing `tenant_id`.
## Tenant Role & Plan Grant Contract
Every role grant or revocation is an audited mutation, not a direct field

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0006
type: architecture-decision-record
title: "Recursive Multi-Tenant Identity and Authorization Architecture"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-05-17"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0006 - Recursive Multi-Tenant Identity and Authorization Architecture
**Status:** Accepted

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0007
type: architecture-decision-record
title: "Security Orchestration Boundary"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-05-18"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0007 - Security Orchestration Boundary
**Status:** Accepted

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0008
type: architecture-decision-record
title: "Object Storage STS Credential Vending Boundary"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-05-18"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0008 - Object Storage STS Credential Vending Boundary
**Status:** Accepted

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0010
type: architecture-decision-record
title: "Orchestration vs Dependency, and Self-Coherent Intent"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-05-21"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0010 - Orchestration vs Dependency, and Self-Coherent Intent
**Status:** Accepted (repo classification subject to ongoing refinement)

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0011
type: architecture-decision-record
title: "NetKingdom IAM Profile Ownership And Version Governance"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-05-22"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0011 - NetKingdom IAM Profile Ownership And Version Governance
**Status:** Accepted

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0012
type: architecture-decision-record
title: "Playbook Capability Contract Ownership"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-05-22"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0012 - Playbook Capability Contract Ownership
**Status:** Accepted

View file

@ -1,7 +1,20 @@
---
id: NK-ADR-0013
type: architecture-decision-record
title: "Tenant Onboarding Grouping Taxonomy"
status: accepted
owner: net-kingdom
revision: "2"
decided: "2026-07-23"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0013 - Tenant Onboarding Grouping Taxonomy
**Status:** Accepted
**Date:** 2026-07-23
**Amended:** 2026-08-22 (current classification versus historical identifier segment)
**Deciders:** Bernd Worsch, Codex
## Context
@ -54,12 +67,26 @@ association - a legal association of people
agentic - financially enabled AI entities
```
This grouping is deliberately **orthogonal to capability role**
(`PLTF`/`IAM`/`VEN`/`CUS`, still unratified): the grouping describes *what
kind of entity a tenant is and how it was onboarded*; role describes *what
it does on the platform*. Both may need to be carried as tenant metadata,
but never conflated into one identifier segment — that conflation is exactly
what this ADR avoids.
The taxonomy has two deliberately different uses:
- At creation, the identifier's grouping segment records the tenant's
onboarding-time classification. The complete identifier is immutable, so
this segment is historical after creation.
- The tenant record's `grouping` field records current classification. It may
change as the entity changes and is authoritative for present-day policy,
including guardrails and spend ceilings.
No consumer may parse the identifier's middle segment and treat it as current
grouping. Consumers needing current grouping MUST read it from `tenant-engine`.
Identifier creation still validates the segment against this vocabulary;
historical does not mean free-form or optional.
Grouping is deliberately **orthogonal to capability role**
(`PLTF`/`IAM`/`VEN`/`CUS`, subsequently ratified by ADR-0014): grouping
describes *what kind of entity a tenant is and its current onboarding-risk
classification*; role describes *what it does on the platform*. Both are
carried as tenant metadata, but never conflated into the immutable identifier
segment — that conflation is exactly what this ADR avoids.
`tenant:platform` and `tenant:coulomb` remain **reserved, ungrouped
identifiers outside this taxonomy**: `tenant:platform` is the control-plane
@ -101,10 +128,15 @@ section — not a new versioned profile document.
already reflects this decision (`tenant:friendly:binky`).
- Future tenant onboarding work should classify a tenant against this list
before minting an identifier, rather than reaching for a role word.
- The capability-role model (`PLTF`/`IAM`/`VEN`/`CUS`) remains a separate,
still-unratified dimension; this ADR does not ratify that model, only
avoids colliding with it. If/when it is ratified, role metadata should be
carried alongside — not instead of — the grouping segment decided here.
- Tenant identifiers never change when current grouping changes. The middle
segment is creation-time history; `tenant-engine` is authoritative for the
current grouping value.
- Policy and commercial consumers, including spend-ceiling resolution, MUST
query `tenant-engine` and MUST NOT derive current grouping by splitting a
tenant identifier.
- The capability-role model (`PLTF`/`IAM`/`VEN`/`CUS`) remains a separate
dimension, now ratified by ADR-0014. Role metadata is carried alongside —
not instead of — current grouping and the historical identifier segment.
- `tenant:platform` and `tenant:coulomb` are reserved outside the taxonomy,
pending Bernd's explicit confirmation (see Decision).
@ -138,6 +170,8 @@ resolved before the first non-Coulomb tenant goes live, not after.
reviewable change).
- Confirm the `tenant:platform`/`tenant:coulomb` reserved/ungrouped
treatment explicitly.
- If/when the `PLTF`/`IAM`/`VEN`/`CUS` capability-role model is ratified,
define how role metadata is carried alongside the grouping segment
decided here.
- ADR-0014 and the Tenant Engine Boundary Contract define how capability-role
metadata is carried alongside grouping.
- Keep `tenant-engine`'s identifier parser vocabulary-validating for creation
and lookup compatibility, but do not expose parsed grouping as current
classification.

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0014
type: architecture-decision-record
title: "Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-07-23"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0014 - Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership
**Status:** Accepted

View file

@ -1,3 +1,15 @@
---
id: NK-ADR-0015
type: architecture-decision-record
title: "NetKingdom Railiance Workload Packaging and Relational Platform"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-08-11"
last_reviewed: "2026-08-22"
review_interval: 12m
---
# ADR-0015 - NetKingdom Railiance Workload Packaging and Relational Platform
**Status:** Accepted

View file

@ -0,0 +1,109 @@
# Reef posture-provider contract
Status: proposal for `repo-manager` agreement under `NK-WP-0027-T02`
Date: 2026-08-22
Canon owner: `net-kingdom`
Reef vocabulary and declaration owner: `repo-manager` / `railiance-master`
## Purpose
A reef states where workloads may be placed and which residual substrate risk
has been accepted. It is not a `P` level. It can nevertheless bound a posture
axis: the current single-member `reef-railiance` can never make V2 failover
reachable, regardless of how many replicas a workload declares.
This proposal extends Tenancy Posture Decision 5.5's provider shape to reefs
without moving reef vocabulary into NetKingdom. The block belongs in the
authoritative `declarations/reef.yaml`; it does not create a second reef file or
duplicate the derived `bound_rapps` projection.
## Proposed declaration block
```yaml
posture_provider:
framework: netkingdom-tenancy-posture
reviewed: "2026-08-22"
review_due: "2027-02-22"
axes:
V:
available: 0
maximum: 1
conditions:
- A bound workload documents and exercises restart or recreation recovery in this failure domain.
- The workload names every synchronous dependency used by the claimed operation.
evidence:
- evidence/verification/rail-runtime-2026-08-21.json
- evidence/admission/rail-kubernetes-baseline.json
reason: One observed member carries the control plane, etcd, and workloads; no automated failover is reachable.
```
The `reef-railiance` values are deliberately conservative:
- `available: 0` means the reef offers no unconditional, reusable recovery
guarantee to every bound workload today. A Ready node is not recovery
evidence.
- `maximum: 1` means a named workload can reach V1 by satisfying and evidencing
the restart/recreate conditions. One failure domain makes V2 unreachable.
The owner may raise `available` when the reef publishes a reusable recovery
guarantee that consumers can cite. Adding independent members and an exercised
failover path may raise `maximum`; topology alone does not.
## Semantics
Each declared axis contains:
| Field | Meaning |
| --- | --- |
| `available` | Highest level the provider guarantees unconditionally to every binding in scope, with current evidence. |
| `maximum` | Highest level a named consumer can reach after satisfying the listed conditions. |
| `conditions` | Consumer or binding work required above `available`; empty only when `available == maximum`. |
| `evidence` | Source-linked, current artifacts supporting the provider facts. |
| `reason` | Why the ceiling exists, especially when structural or accepted. |
A reef declares only axes it materially bounds. A compute reef normally
declares `V`; it does not declare `P` merely because data-bearing workloads sit
on it. A provider-delegated storage reef may bound `P`, `R`, or `V`, but only
where the substrate contract actually makes those properties reachable.
`available` and `maximum` use the axis vocabulary from
`tenancy-posture_v0.1`. `available` MUST NOT exceed `maximum`. Review dates and
evidence discipline follow Decisions 5.4, 5.5, and §13.
## Mechanical reconciliation
For each workload operation and each bound reef:
1. Resolve the reef from the workload's authoritative placement declaration.
Missing or conflicting placement is `unknown`; do not select a default reef.
2. Load the reef's `posture_provider` block from `declarations/reef.yaml`.
3. Reject a workload claim above the reef's `maximum`.
4. For a claim above `available`, require machine-readable evidence that every
listed condition is satisfied for that workload and operation.
5. Compose `V` with every synchronous provider using Decision 4.6.1's minimum
rule. A reef ceiling is one input, not the whole availability claim.
6. Emit the reef declaration revision and evidence references in the
reconciliation result so the decision is reproducible.
The result is one of `satisfied`, `unsatisfied`, or `unknown`. `unknown` covers
an absent provider block, unresolved reef binding, stale review, missing
evidence, or an unrecognised axis value. It is never converted to a permissive
ceiling.
## Ownership boundary
- `net-kingdom` owns the meaning of provider reachability and the `P`/`V`
composition rules.
- `repo-manager` / `railiance-master` owns whether and how this block is added
to the reef schema.
- Each `reef-*` repo owns its values, evidence, and review.
- The workload owner owns its claim and evidence that provider conditions are
met.
- The reconciler reports; it does not manufacture acceptance or posture.
T03 may implement the join only after `repo-manager` agrees the authoritative
field name and carrier. The semantics above are the canon-side acceptance
criteria; the exact reef-schema spelling remains the reef owner's decision.

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import importlib.util
import json
import pathlib
import unittest
@ -10,6 +11,7 @@ SPEC = importlib.util.spec_from_file_location("tenancy_posture_validate", MODULE
assert SPEC and SPEC.loader
VALIDATE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(VALIDATE)
SCHEMA = json.loads(VALIDATE.SCHEMA.read_text(encoding="utf-8"))
def declaration() -> dict:
@ -30,10 +32,38 @@ def declaration() -> dict:
}
def zone_declaration() -> dict:
return {
"standard": "security-zones_v0.1",
"membership": "z2-continuity",
"responsible_party": "ops-warden",
"justification": "foundational access path",
"context": {
"maturity": "M2",
"criticality": "high",
"data_classification": "confidential",
},
"evidence": [
{
"ref": "docs/evidence/example-zone.md",
"supports": ["M2", "continuity-dependency"],
}
],
"reviewed": "2026-08-22",
"review_due": "2026-11-22",
}
class SemanticValidationTests(unittest.TestCase):
def validate(self, document: dict) -> list[str]:
return VALIDATE.validate_semantics(document, pathlib.Path("tenancy.yaml"))
def validate_full(self, document: dict, tmp_path: pathlib.Path) -> list[str]:
import yaml
tmp_path.write_text(yaml.safe_dump(document), encoding="utf-8")
return VALIDATE.validate(tmp_path, SCHEMA)
def test_floor_vector_with_reasons_is_valid(self) -> None:
self.assertEqual([], self.validate(declaration()))
@ -68,6 +98,140 @@ class SemanticValidationTests(unittest.TestCase):
}
self.assertIn("service names must be unique", self.validate(document)[0])
def test_workload_identity_name_must_match_service(self) -> None:
document = declaration()
document["workload_identity"] = {
"name": "different",
"kind": "operational-control-plane",
"responsible_repo": "example",
"identity_bindings": [
{
"scheme": "iam-profile",
"authority": "key-cape",
"subject": "example-prod",
"principal_type": "service",
}
],
}
self.assertIn(
"workload_identity.name must equal service", self.validate(document)[0]
)
def test_workload_identity_bindings_are_unique(self) -> None:
document = declaration()
binding = {
"scheme": "iam-profile",
"authority": "key-cape",
"subject": "example-prod",
"principal_type": "service",
}
document["workload_identity"] = {
"name": "example",
"kind": "platform-service",
"responsible_repo": "example",
"identity_bindings": [binding, binding],
}
self.assertIn(
"workload identity bindings must be unique", self.validate(document)[0]
)
def test_zones_require_authoritative_workload_identity(self) -> None:
import tempfile
document = declaration()
document["zones"] = zone_declaration()
with tempfile.TemporaryDirectory() as directory:
path = pathlib.Path(directory) / "tenancy.yaml"
errors = self.validate_full(document, path)
self.assertTrue(any("workload_identity" in error for error in errors))
def test_operational_workload_may_declare_zones(self) -> None:
import tempfile
document = declaration()
document["workload_identity"] = {
"name": "example",
"kind": "operational-control-plane",
"responsible_repo": "ops-warden",
"identity_bindings": [
{
"scheme": "ssh-certificate",
"authority": "ops-warden",
"subject": "agt-example",
"principal_type": "agent",
"environment": "prod",
}
],
}
document["zones"] = zone_declaration()
with tempfile.TemporaryDirectory() as directory:
path = pathlib.Path(directory) / "tenancy.yaml"
self.assertEqual([], self.validate_full(document, path))
def test_zone_membership_uses_canonical_catalog(self) -> None:
import tempfile
document = declaration()
document["workload_identity"] = {
"name": "example",
"kind": "application",
"responsible_repo": "example",
"declaration_ref": "rapp-example/declarations/rapp.yaml",
"identity_bindings": [
{
"scheme": "iam-profile",
"authority": "key-cape",
"subject": "example-prod",
"principal_type": "service",
}
],
}
document["zones"] = zone_declaration()
document["zones"]["membership"] = "permissive-default"
with tempfile.TemporaryDirectory() as directory:
path = pathlib.Path(directory) / "tenancy.yaml"
errors = self.validate_full(document, path)
self.assertTrue(any("membership" in error for error in errors))
def test_zone_review_due_must_be_after_reviewed(self) -> None:
document = declaration()
document["zones"] = zone_declaration()
document["zones"]["review_due"] = document["zones"]["reviewed"]
self.assertIn(
"zones.review_due must be after zones.reviewed",
self.validate(document)[0],
)
def test_multi_service_zones_must_not_be_top_level(self) -> None:
import tempfile
entry = declaration()
document = {
"schema_version": "0.1",
"framework": "netkingdom-tenancy-posture",
"services": [entry],
"zones": zone_declaration(),
}
with tempfile.TemporaryDirectory() as directory:
path = pathlib.Path(directory) / "tenancy.yaml"
errors = self.validate_full(document, path)
self.assertTrue(errors)
def test_multi_service_zone_requires_identity_on_same_entry(self) -> None:
import tempfile
entry = declaration()
entry["zones"] = zone_declaration()
document = {
"schema_version": "0.1",
"framework": "netkingdom-tenancy-posture",
"services": [entry],
}
with tempfile.TemporaryDirectory() as directory:
path = pathlib.Path(directory) / "tenancy.yaml"
errors = self.validate_full(document, path)
self.assertTrue(any("workload_identity" in error for error in errors))
if __name__ == "__main__":
unittest.main()

View file

@ -38,6 +38,30 @@ def validate_semantics(document: dict[str, Any], path: pathlib.Path) -> list[str
for entry in entries:
name = entry["service"]
workload_identity = entry.get("workload_identity")
if workload_identity:
if workload_identity["name"] != name:
errors.append(
f"{name}: workload_identity.name must equal service"
)
bindings = workload_identity["identity_bindings"]
binding_keys = [
(
binding["scheme"],
binding["authority"],
binding["subject"],
binding.get("environment"),
)
for binding in bindings
]
if len(binding_keys) != len(set(binding_keys)):
errors.append(f"{name}: workload identity bindings must be unique")
zones = entry.get("zones")
if zones:
zone_reviewed = dt.date.fromisoformat(zones["reviewed"])
zone_review_due = dt.date.fromisoformat(zones["review_due"])
if zone_review_due <= zone_reviewed:
errors.append(f"{name}: zones.review_due must be after zones.reviewed")
posture = entry["tenancy"]
current = posture["current"]
reason = posture.get("reason", {})

View file

@ -1,18 +1,19 @@
---
id: NK-WP-0027
type: workplan
title: "Reconcile the reef taxonomy with the P and V ladders"
title: "Reconcile reef placement and security-zone canon dependencies"
domain: infotech
repo: net-kingdom
status: proposed
status: blocked
owner: net-kingdom
topic_slug: netkingdom
planning_priority: P2
planning_priority: P1
created: "2026-08-19"
updated: "2026-08-19"
updated: "2026-08-22"
state_hub_workstream_id: "a30a33e4-f980-4934-9efb-d61a75e6ae81"
---
# NK-WP-0027 — Reefs, placement, and what a substrate makes reachable
# NK-WP-0027 — Reefs, placement, and security-zone canon dependencies
Raised by answering `zone-engine`'s `ZONE-WP-0001-T01`. `zone-engine` asked
whether canon should reconcile security zones with reefs, expecting the answer
@ -37,20 +38,61 @@ accurately by its own reading and be wrong by the standard's own composition
rule. This is Decision 5.5's provider-declaration finding one layer down: a
reef is a provider with nowhere to say what it makes reachable.
## Activation review — 2026-08-22
The original reef finding remains valid and correctly owned. Review against
draft-9 found T01 substantially written already in Decision 8.4.2, but the text
did not explicitly say which Decision 3.2 couplings a reef binding fails to
satisfy. Draft-10 now closes that textual gap. T02 and T03 remain real: the reef
provider declaration and its mechanical join to consumer `V` do not yet exist.
The review also took in a second canon question handed over by
`ZONE-WP-0001-T03`. Measured coverage is nine declared `rapp` workloads, one of
27 credential lanes joinable, 13 plausible but undeclared consumers, and 13
operational controls with no workload-shaped path. The ruling is recorded as
Tenancy Posture Decision 5.6.1:
- the **workload remains the sole policy subject**;
- workload includes independently governed application, automation, and
operational/control-plane execution units behind SSH, tunnels, brokers,
credential flows, policy machinery, and maintenance activity;
- lanes, grants, patterns, repositories, actors, and packages do not become
substitute policy subjects;
- every managed deployable, including operational/tooling runtimes, resolves
through its authoritative rapp declaration; a real operational execution
unit that is not a managed deployable may declare locally; and
- absent authoritative identity or membership resolves to `unknown`, never an
inferred or silently permissive zone.
An explicit control rule may decide how to treat `unknown`; that is stance and
does not manufacture membership. This preserves the build-stage flexibility
already accepted by `ADR-0006` without encoding it as a false zone fact.
## T01 — Establish the reef and `P` boundary
```task
id: NK-WP-0027-T01
status: todo
status: done
priority: medium
state_hub_task_id: "a1dfa4ec-f946-425e-8183-782eef7763c4"
```
**Establish the boundary between a reef and the `P` ladder in canon text.** Say
what each answers, why a reef is not a `P` level, and which of `P`'s couplings
(§3.2) a reef binding does and does not satisfy. Do not extend the `P` ladder.
**Done 2026-08-22.** Tenancy Posture Decision 8.4.2 now says explicitly that a
reef binding records compute substrate and accepted residual risk, satisfies no
`P` coupling by itself, and participates only as a possible `V` ceiling across
the critical path.
## T02 — Extend provider declarations to reefs
```task
id: NK-WP-0027-T02
status: todo
status: wait
priority: medium
state_hub_task_id: "601a0c5f-3f77-419f-9731-25e247424b31"
```
**Extend Decision 5.5's provider declaration to substrate providers, and agree
@ -60,10 +102,30 @@ sentence `apps-pg` now owes its consumers. `reef-railiance`'s first line is
almost certainly a `V` ceiling. This is a proposal to `repo-manager`, not a
canon fiat: it owns the reef vocabulary and the acceptance record.
**In progress 2026-08-22.** The canon-side boundary and proposed provider shape
are ready. Agreement and the authoritative reef declaration surface remain with
`repo-manager`.
**Contract proposal 2026-08-22.**
`docs/reef-posture-provider-contract.md` now proposes a `posture_provider`
block inside authoritative `declarations/reef.yaml`, reusing Decision 5.5's
`available`/`maximum`/conditions/evidence semantics. For `reef-railiance` it
proposes V0 available and V1 maximum: no unconditional reusable recovery
guarantee exists, while a named workload can evidence restart/recreate recovery
inside the one failure domain. The field name and reef-schema adoption remain
subject to `repo-manager` agreement.
**Waiting:** `repo-manager` must confirm or amend the `reef.yaml` carrier,
field name, and conservative V0/V1 values. NetKingdom cannot make that
reef-vocabulary decision on its behalf.
## T03 — Reconcile reef ceilings mechanically
```task
id: NK-WP-0027-T03
status: wait
priority: low
state_hub_task_id: "b53bfb83-1df9-4548-a62f-628cb55ece4d"
```
**Join reef ceilings to consumer `V` declarations mechanically.** Waits on T02.
@ -72,6 +134,104 @@ a machine reconciles. Until then a consumer bound to a reef declares `V` with
the reef named as a synchronous dependency, which is already required by
Decision 4.6.1 and is not being done.
**Design advanced 2026-08-22.** The proposal defines a three-valued join:
`satisfied`, `unsatisfied`, or `unknown`. It rejects claims above the reef
maximum, requires condition evidence above its unconditional available level,
and composes V across synchronous providers. Implementation still waits on the
authoritative reef field name from T02.
## T04 — Rule on zone policy subject and absence
```task
id: NK-WP-0027-T04
status: done
priority: high
```
**Rule on the security-zone policy subject and absence semantics for
`ZONE-WP-0001-T03`.** Keep workload as the sole subject, define it broadly
enough to cover operational/control-plane execution, and state whether missing
identity or membership may inherit a zone.
**Done 2026-08-22.** Decision 5.6.1 defines operational execution units as
workloads, rejects lanes/actors/patterns as substitute subjects, and requires
unresolved membership to return `unknown` without inference. A control may
apply an explicit fail-safe stance to `unknown`; it may not relabel it.
## T05 — Broaden workload declaration coverage
```task
id: NK-WP-0027-T05
status: done
priority: high
```
**Broaden authoritative workload declaration coverage beyond managed `rapp`
applications.** In the `security-zones_v0.1` publication review, require a
stable workload identity and responsible party for operational/control-plane
units, and a machine-readable join from credential/control resources to the
workload they serve. Do not require `rapp` packaging merely to gain identity,
and do not infer the join from path strings or repository ownership. Coordinate
the declaration boundary with `zone-engine`, `repo-manager`, and the affected
control owners.
**Done 2026-08-22.** Tenancy Posture draft-12 Decision 5.6.2 and its schema now
require `workload_identity` whenever a `zones:` block is present. The binding
names the stable workload id, kind, responsible repo, and one or more exact
authority/subject/principal-type tuples. Multi-service declarations carry both
fields per service; top-level multi-service membership is rejected. Following
RMGR-ADR-004, every managed deployable uses its authoritative rapp declaration
and consumer references use `(rapp_id, workload_identity.name)` plus optional
`deployable`. Only a non-managed operational execution unit declares locally;
native actions, actors, lanes, patterns, and resources are explicitly
`not-applicable`, while omissions stay `unknown`. The published
`canon/standards/security-zones_v0.1.md` proposal now has a closed schema shape
for its five memberships, admission context, evidence, and review dates. The
validator rejects service/id mismatch, duplicate bindings, and invalid review
windows; tests cover missing identity and a valid operational workload.
## T06 — Resolve the `DataClassification` mismatch
```task
id: NK-WP-0027-T06
status: wait
priority: high
```
**Resolve the `DataClassification` vocabulary mismatch surfaced by
`ZONE-WP-0001-T03`.** `rapp-policy-nexus` declares `public`, while ops-warden's
`dataclass_floor` maps only `synthetic`, `internal`, `confidential`, and
`restricted`. Do not alias `public` to `synthetic`: public describes disclosure
policy, while synthetic describes data origin and whether values are real.
Route the vocabulary decision to `info-tech-canon`, which owns
`DataClassification`, and then update the workload-maturity mapping with its
ruling. Until the mapping is authoritative, a compiler must report the maturity
floor as unresolved rather than guess.
**Routed 2026-08-22.** The mismatch and non-equivalence are confirmed; owner
consultation is sent and the downstream mapping is pending.
**Waiting:** `info-tech-canon` owns `DataClassification` and must rule its
ordering relative to synthetic-data provenance. The proposed downstream
`public -> M1` mapping remains explicitly non-authoritative until that answer.
## Current gate
The zone-engine publication candidate at `a510393` has been reviewed and
published as the proposed NetKingdom `security-zones_v0.1` standard. All
locally actionable canon and schema work is complete. The remaining chain
is externally owned:
1. `repo-manager` agrees or amends the `reef.yaml` provider carrier (T02).
2. NetKingdom implements the mechanical reef ceiling join against that accepted
carrier (T03).
3. `info-tech-canon` rules the `public`/synthetic relationship, after which
ops-warden can update `dataclass_floor` (T06).
The workplan is `blocked` rather than left nominally active with every open task
at `wait`. No human intervention is required yet; owner responses are the
ordinary next input.
## Related
- `canon/standards/tenancy-posture_v0.1.md` Decisions 4.6.1, 5.5, 8.4.1, 8.4.2
@ -80,3 +240,7 @@ Decision 4.6.1 and is not being done.
is not readiness
- `zone-engine/workplans/ZONE-WP-0001-security-zone-model.md` — where the
question came from, and why it is not answered there
- `zone-engine/docs/exception-lifecycle-2026-08-22.md` — already requires
authoritative workload ids and fail-safe exception evaluation
- `docs/reef-posture-provider-contract.md` — concrete T02/T03 carrier and join
proposal awaiting reef-owner agreement

View file

@ -0,0 +1,80 @@
---
id: NK-WP-0028
type: workplan
title: "Publish zone canon and clarify tenant grouping semantics"
domain: infotech
repo: net-kingdom
status: finished
owner: codex
topic_slug: netkingdom
planning_priority: P1
created: "2026-08-22"
updated: "2026-08-22"
---
# NK-WP-0028 — Canon publication and grouping semantics
Authorized by the operator on 2026-08-22 after repository triage. This work
publishes the zone-engine owner draft, reconciles its authoritative workload
reference semantics with Tenancy Posture, resolves ADR-0013's now-load-bearing
grouping ambiguity, and makes the existing ADR set publication-addressable.
## Publish and integrate Security Zones v0.1
```task
id: NK-WP-0028-T01
status: done
priority: high
```
Reviewed zone-engine revision `a510393` against Tenancy Posture Decisions 5.6.1
and 5.6.2 and RMGR-ADR-004. Published it as proposed canon at
`canon/standards/security-zones_v0.1.md`, retaining workload-only zone
membership, explicit `not-applicable` for native non-workload subjects, and
`unknown` for missing or ambiguous workload references. Replaced the tenancy
schema's placeholder `zones:` object with the standard's five memberships,
admission context, evidence, and review fields.
## Clarify current grouping versus historical identifier segment
```task
id: NK-WP-0028-T02
status: done
priority: high
```
Amended NK-ADR-0013 so the immutable tenant identifier retains its
onboarding-time grouping segment as history while `tenant-engine.grouping` is
the mutable, authoritative current classification. Consumers may not derive
current policy, guardrail, or spend-ceiling inputs by splitting a tenant id.
Propagated the ruling to IAM Profile v0.3 and the Tenant Engine Boundary
Contract.
## Add publication metadata and ignore generated architecture cache
```task
id: NK-WP-0028-T03
status: done
priority: medium
```
Added unique `NK-ADR-*` identifiers plus owner, revision, review date, and
review interval metadata to ADR-0006 through ADR-0015 that exist in this repo.
Recorded the security-zone source revision and added the generated architecture
AST cache to `.gitignore`.
## Verification
```task
id: NK-WP-0028-T04
status: done
priority: medium
```
Verified the JSON schema parses, all 14 tenancy-posture unit tests pass, and the
repository diff has no whitespace errors. Seven estate declarations were also
checked: five validate; the previously-routed flex-auth `implemented A2` drift
and railiance-platform apps-pg R2/V1 evidence drift remain owner work and are
not regressions from this change. Workplan state was reconciled with State Hub.
Remaining external decisions stay tracked as waits in NK-WP-0027 and
NK-WP-0022.