feat: finish ZONE-WP-0003 Engine/PIP freeze and claim mapping

Declare the layer in layer.yaml, check it against INTENT.md, and fail
make check on a new Tooling client or HTTP decision surface. Record the
six statute §10 artifacts for the 2026-08-23 cut, name access-engine on
the README, and offer a non-schema PIP field mapping to Taxonomy.

Assistant: grok
Assistant-Session: 01a04ceb-0745-7ae1-9e26-0d10e5d52b8b
This commit is contained in:
tegwick 2026-08-29 12:49:24 +02:00
parent e9d0fecede
commit acfd93fc86
11 changed files with 682 additions and 35 deletions

View file

@ -34,7 +34,8 @@ companion: net-kingdom/SECURITY-COMPANION.md
> surface, or stance compiled into membership would each be a layer change,
> and a layer change needs the six artifacts in statute §10, including a
> permission freeze. The 2026-08-23 cut already happened; it does not reopen
> by practice.
> by practice. The six statute §10 artifacts are gathered in
> `history/2026-08-29-layer-change-artifacts.md`.
>
> **`access-engine` is the only PDP.** It is the ruled name for the repository
> currently called `flex-auth`. This file's original §5 — *"flex-auth is the

View file

@ -1,10 +1,13 @@
.PHONY: canon-lineage check exception-fixtures reference-fixtures test
.PHONY: canon-lineage check exception-fixtures layer-conformance reference-fixtures test
check: test reference-fixtures exception-fixtures
check: test reference-fixtures exception-fixtures layer-conformance
test:
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -v
layer-conformance:
PYTHONDONTWRITEBYTECODE=1 python3 tools/check_layer_conformance.py
reference-fixtures:
PYTHONDONTWRITEBYTECODE=1 python3 tools/resolve_zones.py --manifest fixtures/manifests/reference.yaml --control-profile profiles/netkingdom-build-v0.1.yaml >/dev/null

View file

@ -1,13 +1,15 @@
# zone-engine
Offline reference conformance for NetKingdom **security zones**. This repository
validates workload membership and admission, projects only explicit
owner-versioned control profiles, checks time-boxed exception fixtures, and
verifies the lineage of the canonical standard.
Engine-layer **PIP** for NetKingdom **security-zone** identity and membership,
retained as offline reference conformance. This repository validates workload
membership and admission, projects only explicit owner-versioned control
profiles, checks time-boxed exception fixtures, and verifies the lineage of
the canonical standard.
It is not a service or policy decision point. Canon is published by
`net-kingdom`; flex-auth and each enforcement-point owner retain live policy
authority.
It is not a live engine, not a PEP, and not a policy decision point. Canon is
published by `net-kingdom`. `access-engine` (currently `flex-auth`) is the
only PDP; each enforcement-point owner retains live policy and failure-mode
authority. Layer declaration: `INTENT.md` frontmatter and `layer.yaml`.
## Checks

View file

@ -28,10 +28,11 @@ Declared in `INTENT.md` frontmatter, in this repository's own voice:
| Tooling contacts | none |
| Conformance | conforming for Tooling contact; the live Engine API is not a missing capability this repository currently owes |
The machine-readable `layer.yaml` form and a conformance check that fails a
new Tooling client or a new decision surface are not yet evidenced here.
Until they are, the declaration is the `INTENT.md` frontmatter and the prose
in that file.
The machine-readable declaration is `layer.yaml`. `tools/check_layer_conformance.py`
fails if that file disagrees with `INTENT.md` frontmatter, if a Tooling-layer
client appears under `tools/`, or if an HTTP authorization decision surface
appears. The six statute §10 artifacts for the already-completed layer cut
are `history/2026-08-29-layer-change-artifacts.md`.
`access-engine` (currently `flex-auth`) is the only PDP. Zone stance and
failure mode remain owner policy and PEP configuration. This repository
@ -82,8 +83,8 @@ This repository provides:
- an offline exception checker with an explicit evaluation instant, covering
grant authority, maximum duration, exclusive expiry, overlap, renewal,
wildcard rejection, and durable-authority bounds; and
- reusable manifests, profiles, exception fixtures, evidence, and 29 unit
tests.
- reusable manifests, profiles, exception fixtures, evidence, and unit
tests covering resolver, exceptions, lineage, and layer conformance.
The resolver consumes already-located declarations and already-resolved
workload-reference projections. It does not discover a fleet, repair an
@ -91,9 +92,10 @@ ambiguous join, read a live clock, or publish a consumer registry. Its profile
fixture records owner-approved v0.1 behavior; only the referenced owner policy
and PEP configuration can change a live effect.
The resolver output is a reference membership/admission record. It is not yet
a statute §17 request-claim. Taxonomy owns that schema and has not published
it. This repository must not ship a competing dialect.
The resolver output is a reference membership/admission record. It is not a
statute §17 request-claim. Taxonomy owns that schema and has not published
it. The field mapping offered to Taxonomy is `docs/pip-claim-boundary.md`.
This repository must not ship a competing dialect.
## Authority boundary
@ -140,6 +142,8 @@ decision point.
- Reproducing or extending v0.1 conformance fixtures.
- Checking direct declarations or explicit Repo Manager projections offline.
- Mapping those membership facts onto a future request-claim
(`docs/pip-claim-boundary.md`) without shipping a schema.
- Verifying a versioned owner profile is total and fully attributed.
- Testing an owner exception implementation against the lifecycle boundaries.
- Reviewing a net-kingdom canon lifecycle/content change, including the

View file

@ -10,7 +10,7 @@
| --- | --- | --- | --- | --- |
| workplan | ZONE-WP-0001 | finished | — | workplans/ZONE-WP-0001-security-zone-model.md |
| workplan | ZONE-WP-0002 | finished | — | workplans/ZONE-WP-0002-reference-contract-hardening-and-retirement.md |
| workplan | ZONE-WP-0003 | ready | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| workplan | ZONE-WP-0003 | finished | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0001-T01 | done | — | workplans/ZONE-WP-0001-security-zone-model.md |
| task | ZONE-WP-0001-T02 | done | — | workplans/ZONE-WP-0001-security-zone-model.md |
| task | ZONE-WP-0001-T03 | done | — | workplans/ZONE-WP-0001-security-zone-model.md |
@ -25,9 +25,9 @@
| task | ZONE-WP-0002-T05 | done | — | workplans/ZONE-WP-0002-reference-contract-hardening-and-retirement.md |
| task | ZONE-WP-0002-T06 | done | — | workplans/ZONE-WP-0002-reference-contract-hardening-and-retirement.md |
| task | ZONE-WP-0002-T07 | done | — | workplans/ZONE-WP-0002-reference-contract-hardening-and-retirement.md |
| task | ZONE-WP-0003-T01 | todo | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T01 | done | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T02 | done | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T03 | todo | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T04 | todo | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T05 | todo | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T03 | done | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T04 | done | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| task | ZONE-WP-0003-T05 | done | — | workplans/ZONE-WP-0003-security-layer-model-alignment.md |
| intake | ZONE-IN-0001 | closed | — | intakes/intakes.md |

View file

@ -0,0 +1,72 @@
# PIP claim boundary — zone identity and membership
This note maps the facts this Engine/PIP already emits onto the facts a
future request-claim must carry. It is **not** a schema, not a wire format,
and not estate canon.
Statute §17 assigns the request-claim schema to Taxonomy and records that
ownership as proposed, not assigned. Statute §18 says zone stance enters a
decision as a **claim on the request or a rule in a versioned
`access-engine` policy package**, never as compiled registry content.
Inventing a competing dialect here is the drift §17 exists to prevent.
Offered to `gate-house` / `net-kingdom` (Taxonomy publication) and to
`access-engine` (currently `flex-auth`, the consumer of the claim). Assent
is not required to keep the 2026-08-23 freeze.
## Facts this PIP owns today
Emitted by `tools/resolve_zones.py` as a reference membership/admission
record, given already-located declarations and already-resolved Repo
Manager projections:
| Field | Meaning |
| --- | --- |
| `identity_bindings` | Authoritative workload identity bindings |
| `workload_ref` | Explicit workload reference (`rapp_id`, name, deployable, applicability) |
| `declared_zone` | Responsible party's declared membership |
| `effective_zone` | Admitted zone, or `unknown` / none |
| `admission` | `satisfied`, `unsatisfied`, `unknown`, or `not-applicable` |
| `admission_reason` | Why that result, without inference |
| `membership_revision` | Digest bound to identity, membership, workload ref, and source revision |
| `source` | Declaration path or projection source |
| `source_revision` | Caller-supplied source revision; absence yields no source-bound digest |
| `guarantees` | What the record actually promises (non-inference, source binding, …) |
Optional control-profile projection (`policy_owner`, `pep_owner`,
`policy_ref`, stance, failure mode) is **not** a PIP fact. It is owner data
copied through for evidence, and only the referenced owner policy or PEP
configuration may change a live effect.
Input `zones.reviewed` / `zones.review_due` are validated and are not
currently copied onto the output record. Freshness the resolver *does*
bind is `source_revision` via `membership_revision`.
## Mapping onto a future request-claim
Until Taxonomy publishes the schema, this is the contribution, not a type:
| Claim slot (statute §17) | This PIP's field | Must not carry |
| --- | --- | --- |
| Issuer | this repository, plus `source` / `source_revision` | a second PDP identity |
| Subject | `identity_bindings` and `workload_ref` | path, repo, reef, actor, or lane as a substitute subject |
| Zone membership | `declared_zone`, `effective_zone`, `admission`, `admission_reason` | stance, failure mode, allow/deny |
| Freshness | `source_revision` and `membership_revision` | an implicit "current" with no revision |
| Effect of the zone | — | **not a PIP field**; `access-engine` policy package or a versioned rule (statute §18) |
A decision that turned on a zone must name the policy-package version that
read it. Membership may be compiled into the registry `access-engine`
already loads only as a **fact**. Compiled data that determines an outcome
is still deciding (statute §6.1).
## Forbidden
- Shipping a zone-engine claim JSON Schema, protobuf, or `/claims` endpoint.
- Encoding stance or failure mode as if they were membership.
- Compiling these facts into registry content that changes allow/deny
without a policy-package revision.
- Treating a missing claim as permission. `unknown` and `not-applicable`
stay distinct; absence is not `z0`.
The freeze in `history/2026-08-29-layer-change-artifacts.md` holds while
this mapping waits on Taxonomy.

View file

@ -0,0 +1,97 @@
---
id: ZONE-LAYER-CHANGE-2026-08-23
type: layer-change-record
title: "Statute §10 artifacts for the 2026-08-23 zone-engine layer cut"
status: accepted
recorded: "2026-08-29"
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
---
# Statute §10 artifacts — the 2026-08-23 layer cut
Statute §10 writes the layer-change procedure from this repository's case:
the runtime hypothesis was falsified, and the Engine remained as offline
reference conformance. The six artifacts are gathered here so a reviewer
does not have to reconstruct them from workplan history.
A later live API, decision surface, or compiled stance would be a **new**
cut and would need its own six artifacts plus assent from every repository
whose boundary moves. This record does not authorize that.
## 1. Before / after `INTENT.md`
| | Before (through 2026-08-23) | After (2026-08-29) |
| --- | --- | --- |
| Desired sentence | Not yet sayable | Sayable; standard adopted |
| Runtime | Hypothesis under test | Falsified; cheaper answer taken |
| Layer | Unstated in this repository's voice | Engine, role PIP |
| Surface | Assumed an engine might be needed | Offline reference conformance |
| PDP line | `flex-auth` is the only decision point | Same ruling; ruled name `access-engine` (currently `flex-auth`) |
Before-state: `history/2026-08-23-retain-reference-decision.md`,
`history/2026-08-23-scope-against-intent.md`, and the 2026-08-23 body of
`INTENT.md` retained as design history.
After-state: `INTENT.md` frontmatter (`layer: Engine`, `role: PIP`) and the
2026-08-29 declaration prose. Machine-readable twin: `layer.yaml`.
## 2. Client inventory
| Contact | Before | After |
| --- | --- | --- |
| Tooling-layer clients (`OpenBao`, `key-cape`, datastore) | none | none |
| HTTP authorization decision surface | none | none |
| Live Engine API | none | none |
| Python tools | offline resolver, exception checker, lineage checker | same |
| Non-Tooling | State Hub work records via CLI / agent sessions | same; recorded in `layer.yaml` |
There was no Tooling client to retire and none to add. `tools/` imports the
standard library and PyYAML only.
## 3. Gap inventory
| Kind | Before | After |
| --- | --- | --- |
| §5.3 declared Tooling contacts | none | none |
| Unowned capabilities this repository currently owes | none | none |
| Live Engine API | not owed; catalog records the offline form | still not owed |
A missing live API is not a §5.3 gap. Closing it in place would reverse the
cut this record describes.
## 4. Assent list
No new boundary moved on 2026-08-29. The cut of 2026-08-23 was already
assented in practice by the two consuming owners:
| Repository | Evidence | Boundary |
| --- | --- | --- |
| flex-auth (`access-engine`) | `FLEX-WP-0016` adoption; profile owner in `profiles/netkingdom-build-v0.1.yaml`; review of ZONE-WP-0001 | remains the only PDP; stance stays in its policy package |
| ops-warden | retired `policy.enabled`; PEP failure modes; adoption evidence 2026-08-22/23 | consumes membership; owns unreachable-engine stance |
| net-kingdom | published `security-zones_v0.1` (still `proposed`) | canon publication |
| gate-house | §4 catalog row names the offline disposition; `ZONE-IN-0001` requested the own-voice declaration | layer model |
A future live surface would require fresh assent from at least
`access-engine` and `ops-warden`.
## 5. State-migration decision
`history/2026-08-23-retain-reference-decision.md`. There was no live state
to migrate: no API, no store, no consumer registry. Consumers already read
declarations in owning repos and policy in owner packages. Retention is of
fixtures, lineage, and offline tools.
## 6. Permission freeze
Until a later cut carries its own six artifacts:
- no live API, daemon, database, controller, scheduler, or reload path;
- no authorization decision surface and no cached verdict;
- no stance compiled into membership, and no local profile change that
alters a live effect;
- no PEP, and no unreachable-engine stance map published from this
repository;
- no Tooling-layer client.
`tools/check_layer_conformance.py` is the freeze's check: a new OpenBao
import or an `/authorize` helper fails `make check`.

65
layer.yaml Normal file
View file

@ -0,0 +1,65 @@
# zone-engine — NetKingdom security layer declaration
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md
# Companion: net-kingdom/SECURITY-COMPANION.md v0.2
# Declaration: INTENT.md (this repository's own voice, §11)
# Validate: python3 tools/check_layer_conformance.py
#
# §11 requires a machine-readable form because prose cannot distinguish a
# declaration from a transcribed review. Shape adapted from kings-guard's
# no-contact declaration, offered estate-wide under §11.
#
# Catalogued as Engine / PIP for zone identity and membership, offline
# reference conformance per the 2026-08-23 disposition. There is no
# Tooling contact, no PEP, and no authorization decision surface.
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.7"
repository: zone-engine
layer: engine
role: pip
declared_by: INTENT.md
declared_at: "2026-08-29"
# §4 catalog entry, transcribed so drift is visible. The standard is
# authoritative for the row; this records what we understand ourselves
# to have been assigned.
catalog_entry:
owns:
- zone identity and membership
form: offline reference conformance (2026-08-23 disposition)
pep_shaped: false
# §5 / §11: every direct contact with a Tooling-layer system (a §4 Tooling
# row), one entry each. Empty is a claim. tools/check_layer_conformance.py
# fails if a Tooling client or a decision surface appears under tools/.
tooling_contacts: []
# No pep_stance path. This repository causes no protected side effect.
# §11 requires non-Tooling clients to be recorded so the check is total.
# There is no library client in this repository. Work-record writes happen
# through the State Hub CLI / agent sessions, not through tools/.
non_tooling_clients:
- id: state-hub-work-records
target: state-hub
layer: not-catalogued
operation: "HTTP via statehub CLI / agent sessions for work records and progress events"
write: true
in_tree: false
note: >-
Outside §5 by the v0.5 scope rule: "Tooling-layer system" means a §4
Tooling row, and state-hub is not one. Recorded, not policed. Carries no
security authority and no secret payload. No import of a State Hub
client exists under tools/.
# A live Engine API is not an unowned capability this repository currently
# owes. The catalog records the offline form. Growing one would be a §10
# layer change, not a gap to close in place.
unowned_capabilities: []
declared_shapes:
"5.1": []
"5.2": []
"5.3": []

View file

@ -0,0 +1,106 @@
from pathlib import Path
from tempfile import TemporaryDirectory
import unittest
import yaml
from tools import check_layer_conformance as layer
INTENT = """---
layer: Engine
role: PIP
standard: netkingdom-security-layer-model
standard_version: "0.7"
---
# INTENT
"""
DECL = {
"layer": "engine",
"role": "pip",
"repository": "zone-engine",
"standard_version": "0.7",
"tooling_contacts": [],
"unowned_capabilities": [],
}
def _write_tree(root: Path, *, decl=None, intent=None, tools=None) -> None:
(root / "layer.yaml").write_text(yaml.safe_dump(decl if decl is not None else DECL))
(root / "INTENT.md").write_text(intent if intent is not None else INTENT)
tools_dir = root / "tools"
tools_dir.mkdir()
(tools_dir / "noop.py").write_text("import json\n")
if tools:
for name, body in tools.items():
(tools_dir / name).write_text(body)
class LayerDeclarationTest(unittest.TestCase):
def test_declaration_exists_and_matches_intent(self):
decl = yaml.safe_load(layer.DECL.read_text())
intent = layer.load_intent()
self.assertEqual(decl["repository"], "zone-engine")
self.assertEqual(decl["layer"], "engine")
self.assertEqual(decl["role"], "pip")
self.assertEqual(decl["standard_version"], "0.7")
self.assertEqual(decl["tooling_contacts"], [])
self.assertIsNone(decl.get("pep_stance"))
self.assertEqual(intent["layer"].lower(), "engine")
self.assertEqual(intent["role"].lower(), "pip")
self.assertEqual(layer.check_declaration_agrees(decl, intent), [])
def test_checker_passes_on_the_real_tree(self):
code, errors, _reports = layer.evaluate()
self.assertEqual(code, 0, errors)
self.assertEqual(errors, [])
class LayerCheckerFailureTest(unittest.TestCase):
def test_missing_declaration_is_exit_2(self):
with TemporaryDirectory() as directory:
root = Path(directory)
(root / "INTENT.md").write_text(INTENT)
(root / "tools").mkdir()
code, errors, _ = layer.evaluate(root=root)
self.assertEqual(code, 2)
self.assertTrue(any("no declaration" in item for item in errors))
def test_intent_disagreement_is_exit_2(self):
with TemporaryDirectory() as directory:
root = Path(directory)
_write_tree(root, intent=INTENT.replace("Engine", "Staff").replace("PIP", "null"))
code, errors, _ = layer.evaluate(root=root)
self.assertEqual(code, 2)
self.assertTrue(any("disagrees" in item for item in errors))
def test_undeclared_tooling_client_is_exit_1(self):
with TemporaryDirectory() as directory:
root = Path(directory)
_write_tree(root, tools={"vault.py": "import hvac\n\nclient = hvac.Client()\n"})
code, errors, _ = layer.evaluate(root=root)
self.assertEqual(code, 1)
self.assertTrue(any("hvac" in item for item in errors))
def test_http_decision_surface_is_exit_1(self):
with TemporaryDirectory() as directory:
root = Path(directory)
_write_tree(
root,
tools={
"api.py": (
"import fastapi\n\n"
"app = fastapi.FastAPI()\n\n"
"@app.post('/v1/check')\n"
"def check():\n"
" return {'allow': True}\n"
)
},
)
code, errors, _ = layer.evaluate(root=root)
self.assertEqual(code, 1)
joined = "\n".join(errors)
self.assertIn("fastapi", joined)
self.assertIn("/v1/check", joined)

View file

@ -0,0 +1,293 @@
#!/usr/bin/env python3
"""Check zone-engine against the NetKingdom security layer model (§5, §6, §11).
Read-only. This repository's position is:
Engine / PIP for zone identity and membership, offline reference
conformance, no Tooling-layer client, no HTTP authorization decision
surface, no PEP.
§11 requires a machine-readable declaration that agrees with INTENT.md
frontmatter. This script is what makes that claim checkable: it fails if
layer.yaml is missing, disagrees with INTENT.md, or if a Tooling client or
decision surface appears under tools/.
The failure it exists to catch is a *convenience* a live lookup, an
OpenBao client, or an /authorize helper "just for this consumer". That is
this repository's original §7 falsifier, now statute §6.
Review dates are reported, never enforced: a date-triggered failure breaks
the build on a calendar day with no code change.
Exit 0 clean, 1 undeclared contact or decision surface, 2 declaration
malformed or in disagreement with INTENT.md.
"""
from __future__ import annotations
import argparse
import ast
import sys
from datetime import date
from pathlib import Path
from typing import Any, Iterable, Mapping
import yaml
ROOT = Path(__file__).resolve().parents[1]
TOOLS = ROOT / "tools"
DECL = ROOT / "layer.yaml"
INTENT = ROOT / "INTENT.md"
TOOLING_IMPORTS = {
"hvac": "OpenBao / Vault client",
"bao": "OpenBao client",
"kubernetes": "cluster client",
"kubernetes_asyncio": "cluster client",
"psycopg": "direct database connection",
"psycopg2": "direct database connection",
"asyncpg": "direct database connection",
"sqlalchemy": "direct database connection",
"pymysql": "direct database connection",
"redis": "direct datastore connection",
"ldap3": "direct LDAP client (key-cape tooling)",
"python_ldap": "direct LDAP client (key-cape tooling)",
"docker": "container runtime client",
}
HTTP_SURFACE_IMPORTS = {
"flask": "HTTP framework",
"fastapi": "HTTP framework",
"starlette": "HTTP framework",
"aiohttp": "HTTP framework",
"tornado": "HTTP framework",
"bottle": "HTTP framework",
"quart": "HTTP framework",
"sanic": "HTTP framework",
"django": "HTTP framework",
"gunicorn": "HTTP server",
"uvicorn": "HTTP server",
"hypercorn": "HTTP server",
"waitress": "HTTP server",
}
DECISION_PATHS = ("/authorize", "/v1/check", "/v1/authorize")
class ConformanceError(ValueError):
"""Declaration is missing, unparseable, or disagrees with INTENT.md."""
def _frontmatter(path: Path) -> dict[str, Any]:
text = path.read_text()
if not text.startswith("---\n") or "\n---\n" not in text[4:]:
raise ConformanceError(f"{path.name} requires YAML frontmatter")
raw = text.split("\n---\n", 1)[0][4:]
value = yaml.safe_load(raw) or {}
if not isinstance(value, Mapping):
raise ConformanceError(f"{path.name} frontmatter must be a mapping")
return dict(value)
def load_declaration(path: Path = DECL) -> dict[str, Any]:
if not path.exists():
raise ConformanceError(f"no declaration at {path.name} (§11)")
try:
data = yaml.safe_load(path.read_text())
except yaml.YAMLError as exc:
raise ConformanceError(f"{path.name} is not parseable: {exc}") from exc
if not isinstance(data, Mapping):
raise ConformanceError(f"{path.name} must be a mapping")
for key in ("layer", "role", "repository", "tooling_contacts", "standard_version"):
if key not in data:
raise ConformanceError(f"{path.name} missing required key '{key}' (§11)")
return dict(data)
def load_intent(path: Path = INTENT) -> dict[str, Any]:
if not path.exists():
raise ConformanceError(f"no INTENT.md at {path}")
return _frontmatter(path)
def _norm(value: Any) -> str:
return str(value or "").strip().lower()
def check_declaration_agrees(
decl: Mapping[str, Any], intent: Mapping[str, Any]
) -> list[str]:
errors: list[str] = []
if _norm(decl.get("layer")) != "engine":
errors.append(f"declared layer is '{decl.get('layer')}', expected 'engine'")
if _norm(decl.get("role")) != "pip":
errors.append(f"declared role is '{decl.get('role')}', expected 'pip'")
if _norm(intent.get("layer")) != _norm(decl.get("layer")):
errors.append(
f"INTENT.md layer '{intent.get('layer')}' disagrees with "
f"layer.yaml '{decl.get('layer')}'"
)
if _norm(intent.get("role")) != _norm(decl.get("role")):
errors.append(
f"INTENT.md role '{intent.get('role')}' disagrees with "
f"layer.yaml '{decl.get('role')}'"
)
if decl.get("repository") != "zone-engine":
errors.append(f"declared repository is '{decl.get('repository')}'")
if decl.get("pep_stance"):
errors.append("pep_stance is set; this repository is not PEP-shaped")
contacts = decl.get("tooling_contacts")
if contacts not in ([], None):
errors.append(
"tooling_contacts is not empty; a new contact needs a §10 cut, "
"not a quiet row"
)
return errors
def imported_modules(path: Path) -> set[str]:
try:
tree = ast.parse(path.read_text())
except SyntaxError:
return set()
found: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
found.update(alias.name.split(".")[0] for alias in node.names)
elif isinstance(node, ast.ImportFrom):
if node.level == 0 and node.module:
found.add(node.module.split(".")[0])
return found
def string_literals(path: Path) -> list[str]:
try:
tree = ast.parse(path.read_text())
except SyntaxError:
return []
found: list[str] = []
for node in ast.walk(tree):
if isinstance(node, ast.Constant) and isinstance(node.value, str):
found.append(node.value)
return found
def scan_tooling(directory: Path) -> list[tuple[Path, str, str]]:
hits: list[tuple[Path, str, str]] = []
for path in sorted(directory.rglob("*.py")):
for module in sorted(imported_modules(path)):
if module in TOOLING_IMPORTS:
hits.append((path, module, TOOLING_IMPORTS[module]))
return hits
def scan_decision_surfaces(directory: Path) -> list[tuple[Path, str, str]]:
hits: list[tuple[Path, str, str]] = []
for path in sorted(directory.rglob("*.py")):
if path.resolve() == Path(__file__).resolve():
continue
for module in sorted(imported_modules(path)):
if module in HTTP_SURFACE_IMPORTS:
hits.append((path, module, HTTP_SURFACE_IMPORTS[module]))
if module == "http":
# http.server is the stdlib decision-adjacent live surface.
text = path.read_text()
if "http.server" in text:
hits.append((path, "http.server", "stdlib HTTP server"))
for literal in string_literals(path):
for needle in DECISION_PATHS:
if needle in literal:
hits.append((path, literal, f"authorization path {needle}"))
return hits
def overdue_reviews(decl: Mapping[str, Any], today: date | None = None) -> list[str]:
today = today or date.today()
notes: list[str] = []
for cap in decl.get("unowned_capabilities") or []:
review = cap.get("review")
if not review:
continue
try:
due = date.fromisoformat(str(review))
except ValueError:
notes.append(f"{cap.get('id')}: unparseable review date {review!r}")
continue
if due < today:
notes.append(f"{cap.get('id')}: review overdue ({review})")
return notes
def evaluate(
*,
root: Path | None = None,
tools: Path | None = None,
decl_path: Path | None = None,
intent_path: Path | None = None,
) -> tuple[int, list[str], list[str]]:
"""Return (exit_code, errors, reports). Exit 2 for declaration, 1 for tree."""
root = root or ROOT
tools = tools or (root / "tools")
decl_path = decl_path or (root / "layer.yaml")
intent_path = intent_path or (root / "INTENT.md")
try:
decl = load_declaration(decl_path)
intent = load_intent(intent_path)
except ConformanceError as exc:
return 2, [str(exc)], []
errors = check_declaration_agrees(decl, intent)
if errors:
return 2, errors, []
reports = overdue_reviews(decl)
tree_errors: list[str] = []
for path, module, what in scan_tooling(tools):
rel = path.relative_to(root) if path.is_relative_to(root) else path
tree_errors.append(f"{rel}: imports '{module}'{what}")
for path, token, what in scan_decision_surfaces(tools):
rel = path.relative_to(root) if path.is_relative_to(root) else path
tree_errors.append(f"{rel}: {what} ({token})")
if tree_errors:
return 1, tree_errors, reports
return 0, [], reports
def main(argv: Iterable[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--report", action="store_true", help="print the declaration summary")
args = parser.parse_args(list(argv) if argv is not None else None)
code, errors, reports = evaluate()
if args.report:
try:
decl = load_declaration()
except ConformanceError as exc:
print(f"FAIL: {exc}", file=sys.stderr)
return 2
print(
f"zone-engine — layer {decl['layer']}, role {decl['role']}, "
f"standard v{decl['standard_version']}"
)
print(f" tooling contacts declared: {len(decl.get('tooling_contacts') or [])}")
print(f" pep_stance: {decl.get('pep_stance')!r}")
for note in reports:
print(f" review: {note}")
if code == 2:
print("FAIL: layer declaration malformed or disagrees with INTENT.md (§11)", file=sys.stderr)
for item in errors:
print(f" {item}", file=sys.stderr)
return 2
if code == 1:
print("FAIL: undeclared Tooling client or HTTP decision surface (§6, §11)", file=sys.stderr)
for item in errors:
print(f" {item}", file=sys.stderr)
print("", file=sys.stderr)
print(" A live API, Tooling client, or /authorize helper is a layer change.", file=sys.stderr)
print(" Do not declare it to make the check pass.", file=sys.stderr)
return 1
if not args.report:
print(f"OK: Engine/PIP declaration agrees with INTENT.md; no Tooling client or decision surface in {TOOLS.relative_to(ROOT)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -4,7 +4,7 @@ type: workplan
title: "Declare Engine/PIP and freeze the offline zone-membership surface"
domain: infotech
repo: zone-engine
status: ready
status: finished
owner: zone-engine
assignee: grok
lane: yellow
@ -12,6 +12,10 @@ topic_slug: netkingdom
planning_priority: P1
created: "2026-08-29"
updated: "2026-08-29"
quality_dod: DoD-Ok
quality_dod_at: "2026-08-29"
quality_dod_by: grok
quality_dod_note: "layer.yaml agrees with INTENT.md; make check covers the freeze (no Tooling client, no decision surface); §10 artifacts and PIP claim mapping are cited; README uses the ruled PDP name; no live API was added."
origin: "history/2026-08-29-security-layer-model-scope-against-intent.md"
context_paths:
- "GOAL.md"
@ -91,7 +95,7 @@ and the `flex-auth` repository rename are intentionally excluded.
```task
id: ZONE-WP-0003-T01
status: todo
status: done
priority: high
state_hub_task_id: "ca687f30-2ad5-5243-8e48-efc34c78465c"
```
@ -149,7 +153,7 @@ counting this repository among the undeclared nine.
```task
id: ZONE-WP-0003-T03
status: todo
status: done
priority: medium
state_hub_task_id: "828a4c67-9644-5b27-ac97-d79563848dfc"
```
@ -174,7 +178,7 @@ them from workplan history, and `INTENT.md` or `SCOPE.md` cites the note.
```task
id: ZONE-WP-0003-T04
status: todo
status: done
priority: medium
state_hub_task_id: "bd4955b6-dfb8-59e5-be20-f4f8ab93ae8e"
```
@ -194,7 +198,7 @@ SCOPE.md GOAL.md` is the consumer-facing surface.
```task
id: ZONE-WP-0003-T05
status: todo
status: done
priority: medium
state_hub_task_id: "ee883d61-4d27-5223-826c-c37f66e3bcb4"
```
@ -221,14 +225,14 @@ and no new wire format has shipped.
## Acceptance criteria
- [ ] `layer.yaml` declares Engine / PIP, no Tooling contacts, no PEP map.
- [ ] `make check` fails a new undeclared Tooling client or decision surface.
- [x] `layer.yaml` declares Engine / PIP, no Tooling contacts, no PEP map.
- [x] `make check` fails a new undeclared Tooling client or decision surface.
- [x] `ZONE-IN-0001` is closed on the own-voice declaration.
- [x] `gate-house` has been notified.
- [ ] Statute §10 artifacts for the 2026-08-23 cut are gathered in one place.
- [ ] Consumer-facing docs use `access-engine` as the ruled PDP name.
- [ ] PIP facts are mapped for a future claim schema without shipping one.
- [ ] No live API, daemon, decision surface, or compiled stance was added.
- [x] Statute §10 artifacts for the 2026-08-23 cut are gathered in one place.
- [x] Consumer-facing docs use `access-engine` as the ruled PDP name.
- [x] PIP facts are mapped for a future claim schema without shipping one.
- [x] No live API, daemon, decision surface, or compiled stance was added.
## Out of scope