From 8c58f8bfa1fe6cb1b9e30d09cdea75147cd2368d Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 19 Aug 2026 21:20:34 +0200 Subject: [PATCH] Hand the zone model to zone-engine; keep WP-0032 as the consumer side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zone-engine is seeded and owns the security zone model as ZONE-WP-0001. Under ADR-0005 ops-warden implements one lane narrowly and routes the rest, and an estate-wide enforcement model is not a lane to absorb — it was ops-warden's deferred flip that exposed the gap, not ops-warden's model to define. WARDEN-WP-0032 is rewritten as the consumer side: hand the estate inputs to ZONE-WP-0001-T02 (27 catalog lanes, the actor inventory, the three posture axes, the three controls the model must express, and the compiled-registry path), then replace policy.enabled with a zone-aware control and amend ADR-0006 to say ops-warden follows the model rather than owning it. ADR-0006 and SCOPE updated to point at zone-engine, which joins the related repositories table. Co-Authored-By: Claude Opus 5 --- SCOPE.md | 5 +- WORK-RECORDS.md | 1 + .../ADR-0006-enforcement-is-zone-scoped.md | 8 +- workplans/WARDEN-WP-0032-security-zones.md | 238 +++++------------- 4 files changed, 65 insertions(+), 187 deletions(-) diff --git a/SCOPE.md b/SCOPE.md index e62657b..529463b 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -278,7 +278,7 @@ for the rest. | WP | Status | Focus | | --- | --- | --- | | WP-0027 | `backlog` | Tamper-resistant credential governance + mass rotation/lockdown (Strand B follow-on to WP-0026) | -| WP-0032 | `proposed` | Security zones — scope enforcement to bands of differing rigidity (`ADR-0006`); unblocks the pre-sign gate | +| WP-0032 | `proposed` | Adopt security zones as a consumer — retire the global `policy.enabled` (model owned by `zone-engine`) | | WP-0030 | `proposed` | Delegation register — record intended owner + blocker on every interim lane, `warden route gaps`, promotion gate | Remaining production distance is also in other repos' lanes (see Known gaps). @@ -356,7 +356,7 @@ Remaining production distance is also in other repos' lanes (see Known gaps). `callerAuth.mode: enforce` (FLEX-WP-0016) — the gate is **ready and verified** (`decision:f3f7c88f9585582a`, anonymous `/v1/check` -> 401). `policy.enabled` remains **false by decision**, not by blocker: `ADR-0006` scopes enforcement to - security zones, which WP-0032 defines. Re-check with + security zones, which `zone-engine` defines (`ZONE-WP-0001`). Re-check with `scripts/check_policy_caller_identity.py` - **Workload posture:** WP-0015 shipped (standard, descriptors, `warden policy`, conformance checker, dev doubles); canon landing owner-driven @@ -439,6 +439,7 @@ Downstream: `ops-bridge` (primary), kaizen agents, CI automations, human operato | `secrets-engine` | Owner-native secret-exec front door (`secrets-engine exec/route`); ops-warden routes provisioned secret lanes to it (WP-0019) and holds 6 more as interim proxies pending its front doors | | `tenant-engine` | Intended owner of tenant/client secret front doors; ops-warden holds 3 tenant lanes as interim proxies (WP-0028 pattern, WP-0030 register) | | `user-engine` | End-user identity/account lifecycle; no ops-warden lane today — route rather than absorb | +| `zone-engine` | Owns the security zone model and exception lifecycle (`ADR-0006`); ops-warden is its first consumer | | `state-hub` | Workplan registry | --- diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 41706d4..092b18e 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -120,3 +120,4 @@ | task | WARDEN-WP-0032-T03 | todo | — | workplans/WARDEN-WP-0032-security-zones.md | | task | WARDEN-WP-0032-T04 | todo | — | workplans/WARDEN-WP-0032-security-zones.md | | task | WARDEN-WP-0032-T05 | todo | — | workplans/WARDEN-WP-0032-security-zones.md | +| task | WARDEN-WP-0032-T06 | todo | — | workplans/WARDEN-WP-0032-security-zones.md | diff --git a/docs/adr/ADR-0006-enforcement-is-zone-scoped.md b/docs/adr/ADR-0006-enforcement-is-zone-scoped.md index ab3d354..9a8a031 100644 --- a/docs/adr/ADR-0006-enforcement-is-zone-scoped.md +++ b/docs/adr/ADR-0006-enforcement-is-zone-scoped.md @@ -13,7 +13,7 @@ created: "2026-08-19" updated: "2026-08-19" last_reviewed: "2026-08-19" review_interval: 6m -enforced_by: "warden.yaml policy.enabled; scripts/check_policy_caller_identity.py; WARDEN-WP-0032" +enforced_by: "warden.yaml policy.enabled; scripts/check_policy_caller_identity.py; zone-engine ZONE-WP-0001; WARDEN-WP-0032" supersedes: "" successor: "" --- @@ -61,7 +61,7 @@ does not enable a fail-closed authorization gate globally. Before differing rigidity, each declaring what is enforced, what is advisory, and what is exempt — and the gate must be scoped to them. -Concretely, until WARDEN-WP-0032 defines the zone model: +Concretely, until `zone-engine`'s `ZONE-WP-0001` defines the zone model: - `policy.enabled` stays `false`. Its readiness is evidence, not a mandate. - A gate that is *ready* is recorded as ready. Readiness is not a reason to @@ -94,7 +94,9 @@ the zone model rather than merged with a caveat in its description. - `WARDEN-WP-0031` — the calling side that made the flip possible (T05 deferred under this ADR) -- `WARDEN-WP-0032` — the zone model this record defers to +- `zone-engine` `ZONE-WP-0001` — the zone model this record defers to, seeded + 2026-08-19 as the owning repo +- `WARDEN-WP-0032` — ops-warden's consumer-side adoption - `wiki/WorkloadSecurityPosture.md` — the two axes already shipped (WP-0015) - `wiki/PolicyGatedSigning.md` — the gate itself - `history/2026-08-19-flex-auth-caller-identity-evidence.md` — readiness evidence diff --git a/workplans/WARDEN-WP-0032-security-zones.md b/workplans/WARDEN-WP-0032-security-zones.md index 6d343fa..956688a 100644 --- a/workplans/WARDEN-WP-0032-security-zones.md +++ b/workplans/WARDEN-WP-0032-security-zones.md @@ -1,7 +1,7 @@ --- id: WARDEN-WP-0032 type: workplan -title: "Security zones — bands of enforcement rigidity, and where they live" +title: "Adopt security zones as a consumer — retire the global policy.enabled" domain: infotech repo: ops-warden status: proposed @@ -9,112 +9,41 @@ owner: ops-warden topic_slug: netkingdom planning_priority: P1 depends_on_workplans: - - WARDEN-WP-0015 - - WARDEN-WP-0029 - WARDEN-WP-0031 created: "2026-08-19" updated: "2026-08-19" --- -# WARDEN-WP-0032 — Security zones +# WARDEN-WP-0032 — Adopt security zones as a consumer `ADR-0006` defers `policy.enabled: true` until enforcement can be scoped to a -zone. This workplan builds the model it defers to, and — first — settles who -owns it. +zone. **The zone model itself is no longer ops-warden's work.** It moved to +`zone-engine` as `ZONE-WP-0001` on 2026-08-19, where it belongs under `ADR-0005` +— ops-warden implements one lane narrowly and routes the rest, and an +estate-wide enforcement model is not a lane it should absorb. -## The problem, precisely +What stays here is the consumer side: ops-warden was the repo whose deferred +flip exposed the gap, it holds the controls the model must be able to express, +and it is the first consumer the model has to satisfy. -ops-warden can express *how exposed* something is and *how mature* it is. It -cannot express *how rigidly this band of the estate is governed right now*. +## What ops-warden owes zone-engine -| Axis | Shipped | Answers | -| --- | --- | --- | -| Environment posture (`dev`/`test`/`prod`) | WP-0015 | Where does this run? | -| Workload maturity (`M0`–`M3`) | WP-0015 | How ready is this workload? | -| `organization_posture: build` | WP-0029 | What state is the org in? | -| **Enforcement stance** | **missing** | **Is this control enforced here, and what happens when it fails?** | +Inputs, not designs. `ZONE-WP-0001-T02` derives the model from the real estate, +and most of that estate is ops-warden's: -Because the fourth is missing, every enforcement control ops-warden ships is a -repo-wide boolean. `policy.enabled` is the first to become flippable, and -flipping it would make flex-auth a hard dependency of every `warden sign` — -including the certificates the ops-bridge tunnels depend on, one of which -carries the policy call itself. Across an estate mid-refactor that hardens -exactly the access needed to perform the refactor. - -The three existing axes *describe*. Nothing *decides*. A zone is the object that -decides. - -## What already exists — do not reinvent these - -Four estate mechanisms are adjacent, and getting the relationships right is most -of the design work. - -**`net-kingdom/canon/standards/tenancy-posture_v0.1.md`** — the closest -structural analogue and the model to follow: six orthogonal axes, graduated -levels, a per-repo declaration file (`tenancy.yaml`) carrying current / -implemented / target vectors with evidence and a review date, and §6 -"conformance is accuracy, not altitude". Its §14 adoption stance is -*"structure, not tooling"* — a direct precedent about sequencing, and one this -workplan should not quietly break. - -**Reefs** (`repo-manager/docs/RailianceAppDeploymentGuide.md`) — `reef-railiance`, -`reef-storage`, named in a rapp's `bound_reefs`. These are **substrate** -placement zones with real risk statements attached (`reef-railiance` carries a -single-node/shared-control-plane residual risk requiring explicit acceptance). -A security zone is *not* a reef — reef answers *where does it run*, zone answers -*how rigidly is it governed* — but they are correlated, and a taxonomy that -ignores reefs will fight them. Also note repo-manager's own ADR-0006: *topology -is not readiness*. The same caution applies here: **placement is not posture.** - -**flex-auth** — the PDP, `service_class: latency-critical`, which already -consumes a *compiled registry snapshot* -(`registry/flex-auth/production_registry_snapshot.json`, built by -`scripts/build_flex_auth_registry.py`). This matters enormously for T03: zone -membership can reach the decision point by compilation, with no runtime lookup -and no new service in the request path. - -**`warden plan`** (WP-0029) — already classifies a need as `autonomous` / -`founder_required` / `unroutable` from properties of the thing, returning the -`reasons` that produced the verdict. Zone-aware enforcement should extend this -verdict machinery rather than grow a parallel one. - -## The question that changes everything else: who owns this? - -Three placements are defensible. **T01 settles it before any modelling**, -because the answer changes who writes T02–T05 and in which repo they live. - -**(a) NetKingdom canon + per-repo declaration.** A `security-zones_v0.1.md` -standard beside `tenancy-posture_v0.1.md`; each repo declares its lanes' -zones in a `zones.yaml` the way it already declares `tenancy.yaml`; flex-auth -enforces from the compiled registry. ops-warden drafts and offers, exactly as it -did for the WP-0015 maturity model. *Strongest fit to precedent; no new runtime -dependency; matches "structure, not tooling".* - -**(b) A new `zone-engine` repo.** A runtime authority owning zone identity, -membership, and lifecycle, in the family of `tenant-engine` / `user-engine` / -`secrets-engine` — each of which has a boundary contract in canon. *Justified -if and only if zone membership needs minting, an API other components query, or -— the real candidate — **time-boxed exceptions with expiry and an audit trail of -who widened what**. A standing classification does not need an engine; a -relaxation that must expire probably does.* - -**(c) flex-auth policy data.** Zones as attributes in the policy package, no new -artefact anywhere. *Cheapest, and wrong if zones must govern controls flex-auth -does not evaluate — the agent read-boundary (ADR-0004) and `warden desk` -escalation are ops-warden-side and would fall outside.* - -**Recommendation to be tested in T01, not assumed:** start at (a), and name -(b) as an explicit promotion with a written trigger, rather than seeding an -engine before the model exists. An empty `zone-engine` would end up defining -zones by accident — through whatever its first schema happened to be — which is -the failure `ADR-0001` names in a different register. The counter-argument -deserves a fair hearing: if time-boxed refactor exceptions are in scope from day -one, (b) may be right immediately, because an expiring grant is state and state -wants an owner. - -Under `ADR-0005` ops-warden does not absorb an estate-wide model on its own -authority. A zone model only ops-warden honours is worth much less than one -flex-auth enforces and secrets-engine reads. +- **27 routing catalog lanes** (`registry/routing/catalog.yaml`) with `risk`, + `status`, and `delegation` already on each — the likeliest membership inputs. +- **The actor inventory** (`adm` / `agt` / `atm`) and its TTL policy. +- **The three posture axes already shipped** — environment and maturity + (WP-0015), `organization_posture` (WP-0029) — including the honest answer to + whether `organization_posture` should be folded into zones rather than run + beside them. +- **Three controls the model must be able to express**: the flex-auth pre-sign + gate (`policy.enabled` + `fail_closed`), the agent read-boundary on + `risk: high` lanes (`ADR-0004`), and the `warden plan` escalation verdicts. +- **The compiled-registry path** (`scripts/build_flex_auth_registry.py`) — how + membership can reach flex-auth without a lookup in a latency-critical decision + path. ## Tasks @@ -124,111 +53,56 @@ status: todo priority: high ``` -**Settle ownership and placement.** Take the three options above to -`net-kingdom` (canon) and `flex-auth` (the PDP that would enforce), with the -time-boxed-exception question stated explicitly since it is what decides -between (a) and (b). Record the outcome as a decision with its reasoning — if -it lands outside ops-warden, this workplan's remaining tasks move to the owning -repo and ops-warden keeps only its consumer-side tasks. +**Hand the estate inputs to `ZONE-WP-0001-T02`.** Not a design proposal — the +lanes, actors, axes, and controls above, with the exceptions ops-warden already +knows do not fit cleanly (the interim delegation lanes from WP-0030 are the +obvious candidates: a lane covered on someone else's behalf may not sit in the +same zone as one ops-warden owns outright). -Deliverable: a decision record, and coordination messages sent. Do not model -anything before this closes. +Include the `organization_posture` fold-in question honestly, including the case +against keeping it. ```task id: WARDEN-WP-0032-T02 -status: todo +status: wait priority: high ``` -**Derive the zones from the real estate, not from first principles.** Walk the -27 catalog lanes, the actor inventory (`adm`/`agt`/`atm`), and the workloads -carrying posture descriptors, and find the bands that actually exist. The test -of the model is that it partitions today's estate without a residue of -exceptions; the exceptions found are the most informative output. +**Replace `policy.enabled` with a zone-aware control.** Waits on +`ZONE-WP-0001-T03` and `T05`. Reads the zone of the actor being signed for and +that zone's failure mode. Ship deprecation and migration in the same change — +leaving both is the second-source-of-truth failure `ADR-0001` exists to prevent. -Must answer explicitly: +Re-run `scripts/check_policy_caller_identity.py` before enabling anything: the +WP-0031 evidence (`decision:f3f7c88f9585582a`, 2026-08-19) will be stale, and +re-establishing it is cheap precisely so this is a re-check rather than a re-do. -- Is a zone a property of the **actor**, the **lane**, the **workload**, or the - **moment**? The CoulombCore decommission shows the last one is real and is the - hardest to model — a time-boxed relaxation is a different object from a - standing zone, and conflating them produces a permanent hole with a - temporary-sounding name. -- Do zones **compose with or fold in** the three existing axes? A fourth - independent axis multiplies states; absorbing `organization_posture` may be - the honest move. -- How do zones relate to **reefs**? Correlated, not identical. Say so in the - model rather than leaving readers to assume either. +This closes `WARDEN-WP-0031-T05`. ```task id: WARDEN-WP-0032-T03 -status: todo -priority: high +status: wait +priority: medium ``` -**Model stance and failure mode per zone per control.** For each control: -enforced / advisory / exempt, *and* fail-closed or fail-open. The failure mode -is the part with teeth — `fail_closed` is what turns a dead tunnel into an -outage, and it is the reason `policy.enabled` was deferred. - -Controls in scope at minimum: the flex-auth pre-sign gate (`policy.enabled`, -`fail_closed`), the agent read-boundary on `risk: high` lanes (`ADR-0004`), and -the `warden plan` escalation verdicts (WP-0029). - -Prefer **compilation over runtime lookup** for anything flex-auth evaluates: it -is latency-critical and already consumes a built registry snapshot. Adding a -synchronous zone lookup to the decision path would be a regression dressed as an -architecture. +**Declare ops-warden's zones** in whatever format `ZONE-WP-0001-T05` settles, +alongside the existing posture descriptors. Carry the conformance rule: +*accuracy, not altitude*. Declaring a stricter zone than can be evidenced is the +failure mode that looks like progress. ```task id: WARDEN-WP-0032-T04 -status: todo -priority: high -``` - -**Declaration format and conformance rule.** Follow `tenancy.yaml`: a per-repo -declaration, with evidence, a `reviewed` date and a `review_due`, and the §6 -rule carried over — **accuracy, not altitude**. A repo declaring a stricter zone -than it can evidence is the failure mode to design against, because it is the -one that looks like progress. - -Include how a zone assignment is *changed*, and how that change is observed. A -zone that can be quietly widened is not a boundary. If time-boxed exceptions -exist, their expiry must be enforced by something, not by an intention. - -```task -id: WARDEN-WP-0032-T05 -status: todo +status: wait priority: medium ``` -**Make `policy.enabled` zone-aware and retire the global boolean.** The -successor control reads the zone of the actor being signed for and that zone's -failure mode. Ship deprecation and migration in the same change — leaving both -is the second-source-of-truth failure `ADR-0001` exists to prevent. +**Amend `ADR-0006`.** Once zones exist and enforcement scoping is owned by +`zone-engine`, ADR-0006 must say that ops-warden *follows* the model rather than +owning it — a superseding record, never an in-place edit. Update `SCOPE.md`, +`wiki/WorkloadSecurityPosture.md`, and `wiki/PolicyGatedSigning.md` with it. -This closes `WARDEN-WP-0031-T05`, cancelled under `ADR-0006`. Re-run -`scripts/check_policy_caller_identity.py` first: the WP-0031 evidence -(`decision:f3f7c88f9585582a`, 2026-08-19) will be stale, and re-establishing it -is cheap precisely so this is a re-check rather than a re-do. +## Related -```task -id: WARDEN-WP-0032-T06 -status: todo -priority: medium -``` - -**Close out honestly.** Update `SCOPE.md`, `wiki/WorkloadSecurityPosture.md`, -and `wiki/PolicyGatedSigning.md`. If the model landed in canon or in another -repo, `ADR-0006` gains a successor or an amendment recording where enforcement -scoping now lives — ops-warden follows it rather than owning it, and the ADR -must say which. - -## Deliberately out of scope - -- **Network segmentation.** "Zone" is an overloaded word; this model is about - *enforcement rigidity*, not routing or NetworkPolicy. Say so wherever the term - first appears, or the two will be conflated within a quarter. -- **Re-litigating the three existing axes.** They stay as they are unless T02 - finds a fold-in. -- **Zones for anything ops-warden does not consume**, unless T01 hands the model - to an owner who needs the wider scope. +- `zone-engine` `ZONE-WP-0001` — the model, and where this work is led from +- `ADR-0006` — enforcement is zone-scoped, never a global flag +- `WARDEN-WP-0031` — the deferred flip and its readiness evidence