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

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