diff --git a/.gitignore b/.gitignore index 2d105c2..2608138 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/architecture/.ast_cache/ # PyBuilder .pybuilder/ diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 71e7039..b512195 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -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 | diff --git a/canon/schemas/tenancy-posture_v0.1.schema.json b/canon/schemas/tenancy-posture_v0.1.schema.json index 3159319..bf45787 100644 --- a/canon/schemas/tenancy-posture_v0.1.schema.json +++ b/canon/schemas/tenancy-posture_v0.1.schema.json @@ -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 } } diff --git a/canon/standards/iam-profile_v0.3.md b/canon/standards/iam-profile_v0.3.md index f0a0bd6..a8e0d4a 100644 --- a/canon/standards/iam-profile_v0.3.md +++ b/canon/standards/iam-profile_v0.3.md @@ -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::`, where `` 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 diff --git a/canon/standards/security-zones_v0.1.md b/canon/standards/security-zones_v0.1.md index 8b5a5dd..50a1899 100644 --- a/canon/standards/security-zones_v0.1.md +++ b/canon/standards/security-zones_v0.1.md @@ -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, diff --git a/canon/standards/tenancy-posture_v0.1.md b/canon/standards/tenancy-posture_v0.1.md index 4d38ad8..c85ec86 100644 --- a/canon/standards/tenancy-posture_v0.1.md +++ b/canon/standards/tenancy-posture_v0.1.md @@ -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 ...`; 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::` 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::`. ## 20. Ratification path diff --git a/canon/standards/tenant-engine-boundary-contract_v0.1.md b/canon/standards/tenant-engine-boundary-contract_v0.1.md index 77cc973..fdad05f 100644 --- a/canon/standards/tenant-engine-boundary-contract_v0.1.md +++ b/canon/standards/tenant-engine-boundary-contract_v0.1.md @@ -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 diff --git a/docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md b/docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md index a2a32d3..f0a8e29 100644 --- a/docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md +++ b/docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md @@ -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 diff --git a/docs/adr/ADR-0007-security-orchestration-boundary.md b/docs/adr/ADR-0007-security-orchestration-boundary.md index afcf850..6571d3d 100644 --- a/docs/adr/ADR-0007-security-orchestration-boundary.md +++ b/docs/adr/ADR-0007-security-orchestration-boundary.md @@ -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 diff --git a/docs/adr/ADR-0008-object-storage-sts-credential-vending.md b/docs/adr/ADR-0008-object-storage-sts-credential-vending.md index 6dd312f..3c5fc7f 100644 --- a/docs/adr/ADR-0008-object-storage-sts-credential-vending.md +++ b/docs/adr/ADR-0008-object-storage-sts-credential-vending.md @@ -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 diff --git a/docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md b/docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md index 142c665..4f7c0a2 100644 --- a/docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md +++ b/docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md @@ -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) diff --git a/docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md b/docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md index f22dad6..9e02a24 100644 --- a/docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md +++ b/docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md @@ -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 diff --git a/docs/adr/ADR-0012-playbook-capability-contract-ownership.md b/docs/adr/ADR-0012-playbook-capability-contract-ownership.md index f1cc3e9..9ec584f 100644 --- a/docs/adr/ADR-0012-playbook-capability-contract-ownership.md +++ b/docs/adr/ADR-0012-playbook-capability-contract-ownership.md @@ -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 diff --git a/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md b/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md index 1c8ceb1..f60c51a 100644 --- a/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md +++ b/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md @@ -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. diff --git a/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md b/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md index 81d03c9..a7f01fa 100644 --- a/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md +++ b/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md @@ -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 diff --git a/docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md b/docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md index 05d2c4f..bea3323 100644 --- a/docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md +++ b/docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md @@ -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 diff --git a/docs/reef-posture-provider-contract.md b/docs/reef-posture-provider-contract.md new file mode 100644 index 0000000..72eca58 --- /dev/null +++ b/docs/reef-posture-provider-contract.md @@ -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. diff --git a/tools/tenancy-posture/test_validate.py b/tools/tenancy-posture/test_validate.py index 7c262ca..6047324 100644 --- a/tools/tenancy-posture/test_validate.py +++ b/tools/tenancy-posture/test_validate.py @@ -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() diff --git a/tools/tenancy-posture/validate.py b/tools/tenancy-posture/validate.py index 7cc486e..10b798c 100644 --- a/tools/tenancy-posture/validate.py +++ b/tools/tenancy-posture/validate.py @@ -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", {}) diff --git a/workplans/NK-WP-0027-reef-placement-reconciliation.md b/workplans/NK-WP-0027-reef-placement-reconciliation.md index 846a44c..a6f404f 100644 --- a/workplans/NK-WP-0027-reef-placement-reconciliation.md +++ b/workplans/NK-WP-0027-reef-placement-reconciliation.md @@ -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 diff --git a/workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md b/workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md new file mode 100644 index 0000000..72d4c2d --- /dev/null +++ b/workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md @@ -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.