From 030362085a8efb834708344b57d4943627d0d5b7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 00:28:23 +0200 Subject: [PATCH 01/57] fix(workplans): qualify ad-hoc identifiers with the repository prefix `ADHOC-YYYY-MM-DD` is unique per date but not per repository, so any two repos opening an ad-hoc on the same day collide. The 2026-08-26 fleet projection reset refused 9 records for exactly this reason. Canon (work-record-types_v0.1, CUST-WP-0066) settled the form as `{PREFIX}-WP-ADHOC-YYYY-MM-DD`, filename unchanged, and grandfathered existing ids on the condition they are never *silently* re-derived. This is the explicit migration that clause allows for. The hub id is derived from the record id, so a changed id is a different record: stale state_hub_*_id fields are dropped and fix-consistency re-derives. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006 --- workplans/ADHOC-2026-06-27.md | 6 ++---- workplans/ADHOC-2026-06-29.md | 6 ++---- workplans/ADHOC-2026-08-11.md | 12 ++++-------- workplans/ADHOC-2026-08-17.md | 15 +++++---------- 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/workplans/ADHOC-2026-06-27.md b/workplans/ADHOC-2026-06-27.md index cda844a..dfbe370 100644 --- a/workplans/ADHOC-2026-06-27.md +++ b/workplans/ADHOC-2026-06-27.md @@ -1,5 +1,5 @@ --- -id: ADHOC-2026-06-27 +id: WARDEN-WP-ADHOC-2026-06-27 type: workplan title: "Ad Hoc Tasks — 2026-06-27" domain: infotech @@ -9,7 +9,6 @@ owner: claude topic_slug: custodian created: "2026-06-27" updated: "2026-06-27" -state_hub_workstream_id: "142b171b-c34b-4a45-91a5-c77e6d07ec6f" --- # Ad Hoc Tasks — 2026-06-27 @@ -19,10 +18,9 @@ Low-risk opportunistic fixes completed directly during the consolidation session ### T01 — Fix stale `warden` CLI install + make it usable outside the repo ```task -id: ADHOC-2026-06-27-T01 +id: WARDEN-WP-ADHOC-2026-06-27-T01 status: done priority: medium -state_hub_task_id: "867c72c9-9904-400f-8542-04264e5856c2" ``` issue-core reported (msg `70bcf238`) that the `warden` CLI on `~/.local/bin` lacked diff --git a/workplans/ADHOC-2026-06-29.md b/workplans/ADHOC-2026-06-29.md index 34aac41..7346a3d 100644 --- a/workplans/ADHOC-2026-06-29.md +++ b/workplans/ADHOC-2026-06-29.md @@ -1,5 +1,5 @@ --- -id: ADHOC-2026-06-29 +id: WARDEN-WP-ADHOC-2026-06-29 type: workplan title: "Ad Hoc Tasks — 2026-06-29" domain: infotech @@ -9,7 +9,6 @@ owner: claude topic_slug: custodian created: "2026-06-29" updated: "2026-06-29" -state_hub_workstream_id: "1c0460b7-bc8a-48db-96d4-681bce18ac91" --- # Ad Hoc Tasks — 2026-06-29 @@ -17,10 +16,9 @@ state_hub_workstream_id: "1c0460b7-bc8a-48db-96d4-681bce18ac91" ### T01 — Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4) ```task -id: ADHOC-2026-06-29-T01 +id: WARDEN-WP-ADHOC-2026-06-29-T01 status: done priority: medium -state_hub_task_id: "371235cc-b9d3-4103-b09f-e4e01cc83c5b" ``` flex-auth (msg `ea00620b`) asked ops-warden to help close FLEX-WP-0007 T4 (joint OpenBao diff --git a/workplans/ADHOC-2026-08-11.md b/workplans/ADHOC-2026-08-11.md index 4f234fb..a8734b0 100644 --- a/workplans/ADHOC-2026-08-11.md +++ b/workplans/ADHOC-2026-08-11.md @@ -1,5 +1,5 @@ --- -id: ADHOC-2026-08-11 +id: WARDEN-WP-ADHOC-2026-08-11 type: workplan title: "Ad Hoc Tasks — 2026-08-11" domain: infotech @@ -9,7 +9,6 @@ owner: claude topic_slug: custodian created: "2026-08-11" updated: "2026-08-15" -state_hub_workstream_id: "bc6dd3e3-ab2b-567a-9574-30d2650f20a2" --- # Ad Hoc Tasks — 2026-08-11 @@ -17,10 +16,9 @@ state_hub_workstream_id: "bc6dd3e3-ab2b-567a-9574-30d2650f20a2" ### T01 — Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite) ```task -id: ADHOC-2026-08-11-T01 +id: WARDEN-WP-ADHOC-2026-08-11-T01 status: done priority: medium -state_hub_task_id: "ddd0c95c-1196-5251-a4ca-6ef441c2f377" ``` rapp-postgres (msg `96907986`, residual from RAPP-POSTGRES-WP-0002-T04) reported the @@ -39,10 +37,9 @@ an anchor that does not exist. The intended heading is `## Routing catalog index ### T02 — Triage the stale ops-warden inbox (11 unread, C-28/C-29) ```task -id: ADHOC-2026-08-11-T02 +id: WARDEN-WP-ADHOC-2026-08-11-T02 status: done priority: medium -state_hub_task_id: "fda35ad4-f526-5012-a2be-ef6b5dcbc8af" ``` `fix-consistency` flagged 11 unread messages older than 3 days, two of them as possible @@ -73,10 +70,9 @@ read. Inbox is now empty. ### T03 — warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover ```task -id: ADHOC-2026-08-11-T03 +id: WARDEN-WP-ADHOC-2026-08-11-T03 status: done priority: medium -state_hub_task_id: "7ef4b6b0-26bf-5456-8429-8cf8458c2ddc" ``` secrets-engine (msg `80456912`, 2026-06-29) is holding a validated non-mutating dry-run diff --git a/workplans/ADHOC-2026-08-17.md b/workplans/ADHOC-2026-08-17.md index 15f35e5..431ecd6 100644 --- a/workplans/ADHOC-2026-08-17.md +++ b/workplans/ADHOC-2026-08-17.md @@ -1,5 +1,5 @@ --- -id: ADHOC-2026-08-17 +id: WARDEN-WP-ADHOC-2026-08-17 type: workplan title: "Ad Hoc Tasks — 2026-08-17" domain: infotech @@ -9,7 +9,6 @@ owner: claude topic_slug: custodian created: "2026-08-17" updated: "2026-08-17" -state_hub_workstream_id: "8376eb89-36f2-52b0-a59a-2954b2e49349" --- # Ad Hoc Tasks — 2026-08-17 @@ -21,10 +20,9 @@ workload-identity owner. ### T01 — Answer flex-auth: how should `/v1/check` authenticate its callers? ```task -id: ADHOC-2026-08-17-T01 +id: WARDEN-WP-ADHOC-2026-08-17-T01 status: done priority: high -state_hub_task_id: "b980d1f7-2fcc-58dd-97d8-6e9b9ba57bc9" ``` flex-auth (msg `130a148c`, FLEX-WP-0015 T02) reported that `POST /v1/check` and @@ -69,10 +67,9 @@ Their four questions answered in `wiki/NetKingdomSecurityMap.md` ### T02 — user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only ```task -id: ADHOC-2026-08-17-T02 +id: WARDEN-WP-ADHOC-2026-08-17-T02 status: done priority: medium -state_hub_task_id: "e76b9b3e-189b-5a4f-8815-4ccf2057507c" ``` user-engine (msg `2af4a124`) answered the open confirm question on @@ -95,10 +92,9 @@ consumes it and has no authority over custody, rotation, or issuance. ### T03 — key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker ```task -id: ADHOC-2026-08-17-T03 +id: WARDEN-WP-ADHOC-2026-08-17-T03 status: done priority: medium -state_hub_task_id: "edfb0626-5022-5f49-aaf9-08e0d1002b90" ``` key-cape (msg `099b7cba`) acknowledged the lane staying interim on ops-warden. @@ -118,10 +114,9 @@ workplan. ### T04 — Session hygiene ```task -id: ADHOC-2026-08-17-T04 +id: WARDEN-WP-ADHOC-2026-08-17-T04 status: done priority: low -state_hub_task_id: "62109af6-f095-5e7b-9258-0fcec29d30e8" ``` - [x] `uv run pytest -q` → 338 passed, 4 deselected (routing anchor + no-double-source From 37c387bd34b361539c5eef61a4bdada3537dd41f Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 00:34:02 +0200 Subject: [PATCH 02/57] fix(workplans): qualify archived ad-hoc identifiers The live-file pass missed these: archived ad-hocs carry a YYMMDD- filename prefix, so the ADHOC-* glob did not match them. They still derive from the forge, so they are live records rather than dead files. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006 --- workplans/archived/260707-ADHOC-2026-07-07.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/workplans/archived/260707-ADHOC-2026-07-07.md b/workplans/archived/260707-ADHOC-2026-07-07.md index d2929fb..aadfbbd 100644 --- a/workplans/archived/260707-ADHOC-2026-07-07.md +++ b/workplans/archived/260707-ADHOC-2026-07-07.md @@ -1,5 +1,5 @@ --- -id: ADHOC-2026-07-07 +id: WARDEN-WP-ADHOC-2026-07-07 type: workplan title: "Ad Hoc Tasks — 2026-07-07" domain: infotech @@ -9,7 +9,6 @@ owner: grok topic_slug: custodian created: "2026-07-07" updated: "2026-07-07" -state_hub_workstream_id: "2e8bc783-2e37-4b86-a691-177b18329783" --- > **Terminology note:** Historical text in this archived workplan may use the legacy term "workstream". The fleet term is **workplan** (`canon/standards/workplan-terminology-fleet_v0.1.md`). @@ -19,10 +18,9 @@ state_hub_workstream_id: "2e8bc783-2e37-4b86-a691-177b18329783" ### T01 — Roll out proxy pipe fix (be3b4a2) ```task -id: ADHOC-2026-07-07-T01 +id: WARDEN-WP-ADHOC-2026-07-07-T01 status: done priority: high -state_hub_task_id: "55b6de52-b958-4b72-94eb-4ba1b8da2629" ``` `warden access` failed on `reuse-surface-hub-write-token` because `shlex.split` From 347e47ce8e11d33c3840fd1ca2248c63c036956d Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 20:33:43 +0200 Subject: [PATCH 03/57] Note NetKingdom layering review in INTENT Records this repository's layer in the NetKingdom IT-security layer model (Taxonomy / Tooling / Engines / Staff) and what should change in this INTENT as a result. Links to the review that established the model: gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md The note flags pending adaptation only; the body is unchanged. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9 --- INTENT.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/INTENT.md b/INTENT.md index 0f0919e..adb4c81 100644 --- a/INTENT.md +++ b/INTENT.md @@ -1,5 +1,20 @@ # INTENT +> **NetKingdom layering review — 2026-08-28.** This repository's role was reviewed +> against the NetKingdom IT-security layer model: **Taxonomy → Tooling → Engines → +> Staff**, layered by determinism and by the kind of artifact each layer produces. +> Findings and the argument behind them: +> `gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md`. +> The model as currently stated is `gate-house/INTENT.md` § "Where Gate House Sits"; +> it is ruled to become a `net-kingdom/canon/standards/` standard, not yet written. +> +> The layer rule that binds every repository: **Staff never touches tooling +> directly. It acts only through engine APIs.** +> +> **This repository is Staff — interactive, non-deterministic; operational stewardship.** Add the layer label and the Staff invariant. Two substantive changes. (1) The **"NetKingdom Security Literacy"** section is evidence that the security curriculum had no owner; it now has one. Doctrine and curriculum move to gate-house, and this section becomes lane-specific runbooks that reference gate-house doctrine rather than restating it. The boundary is **doctrine versus runbook**. (2) The literacy and routing tables should add gate-house — currently every plane is listed and gate-house appears nowhere — routing doctrine and authority-model questions there, while continuing to route policy decisions to access-engine. Record the lane/rule demarcation as in ops-mason. +> +> *This note records what should change. The body below is not yet adapted.* + > This file captures **why this repository exists**, the **direction it is > moving toward**, and the **kind of system it is meant to become**. > It is intentionally **aspirational and stable**, not a description of From 15730bb65004554f8ae889d7f46fe1124235914a Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 21:21:08 +0200 Subject: [PATCH 04/57] Point layering note at the published standard The layer model is now published as net-kingdom/canon/standards/security-layer-model_v0.1.md (proposed) and ratified by gate-house GH-DEC-2026-001. The note previously said the standard was not yet written. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9 --- INTENT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/INTENT.md b/INTENT.md index adb4c81..caa3c68 100644 --- a/INTENT.md +++ b/INTENT.md @@ -5,8 +5,8 @@ > Staff**, layered by determinism and by the kind of artifact each layer produces. > Findings and the argument behind them: > `gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md`. -> The model as currently stated is `gate-house/INTENT.md` § "Where Gate House Sits"; -> it is ruled to become a `net-kingdom/canon/standards/` standard, not yet written. +> The model is `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed), +> ratified by `gate-house/decisions/decisions.md` GH-DEC-2026-001. > > The layer rule that binds every repository: **Staff never touches tooling > directly. It acts only through engine APIs.** From 467635e84b99757336ee49d7f0dbf107607d0560 Mon Sep 17 00:00:00 2001 From: repo-manager Date: Fri, 28 Aug 2026 21:30:28 +0200 Subject: [PATCH 05/57] repo.work.create_intake WARDEN-IN-0001 correlation_id: 65d40cdd-5894-440e-9c95-c6bcfe259b66 reason: Request assent for GH-DEC-2026-001 boundaries source: repo-manager Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9 --- intakes/intakes.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 intakes/intakes.md diff --git a/intakes/intakes.md b/intakes/intakes.md new file mode 100644 index 0000000..a7f4472 --- /dev/null +++ b/intakes/intakes.md @@ -0,0 +1,35 @@ +# Intake records + +## WARDEN-IN-0001 — Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation + +```yaml +id: WARDEN-IN-0001 +kind: intake +title: 'Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule + demarcation' +status: open +origin: cross-repo +origin_ref: gate-house GH-DEC-2026-001 +priority: medium +owner: ops-warden +requested_by: gate-house +standard: net-kingdom/canon/standards/security-layer-model_v0.1.md +description: 'gate-house asks ops-warden to assent to three boundary items. (1) ops-warden + is Staff, bound by the rule that Staff acts only through Engine APIs and never touches + Tooling directly (standard section 5). (2) Doctrine versus runbook: the NetKingdom + Security Literacy section in ops-warden INTENT is evidence the security curriculum + had no owner; it now has one in gate-house. Proposal is that doctrine and curriculum + move to gate-house and that section becomes lane-specific runbooks referencing gate-house + doctrine rather than restating it. ops-warden keeps the lanes it stewards and everything + operational about them. (3) The access lane/rule demarcation, normative in standard + section 8: ops-warden and ops-mason own access lanes — how a worker reaches a host; + access-engine owns access rules — whether they may. This demarcation is the condition + attached to renaming flex-auth to access-engine, so ops-warden effectively holds + a veto on that name. Also requested: add gate-house to the Security Literacy and + routing tables — currently every plane is listed and gate-house appears nowhere + — routing doctrine and authority-model questions there while continuing to route + policy decisions to access-engine. If moving the curriculum out leaves ops-warden + unable to instruct its own workers, say so; the boundary is wrong if it does.' +created: '2026-08-28T19:30:28.087109Z' +updated: '2026-08-28T19:30:28.087109Z' +``` From 85d3078bae0b624b364a819d6903ab6307fbff49 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 21:35:57 +0200 Subject: [PATCH 06/57] Refresh work-record index Regenerated by fix-consistency; adds the inbound assent intake. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9 --- WORK-RECORDS.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 0bc6497..9b8146f 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -8,10 +8,10 @@ | Kind | ID | Status | Lane | Source | | --- | --- | --- | --- | --- | -| workplan | ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md | -| workplan | ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md | -| workplan | ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md | -| workplan | ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md | +| workplan | WARDEN-WP-ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md | +| workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md | +| workplan | WARDEN-WP-ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md | +| workplan | WARDEN-WP-ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md | | workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | | workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md | | workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md | @@ -30,15 +30,15 @@ | workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md | | workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md | | workplan | WARDEN-WP-0033 | active | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| task | ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | -| task | ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | -| task | ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | -| task | ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md | -| task | ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md | -| task | ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md | -| task | ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md | -| task | ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md | -| task | ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md | +| task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | +| task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | +| task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | +| task | WARDEN-WP-ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md | +| task | WARDEN-WP-ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md | +| task | WARDEN-WP-ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md | +| task | WARDEN-WP-ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md | +| task | WARDEN-WP-ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md | +| task | WARDEN-WP-ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md | | task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | | task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | | task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | @@ -129,3 +129,4 @@ | task | WARDEN-WP-0033-T04 | wait | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | +| intake | WARDEN-IN-0001 | open | — | intakes/intakes.md | From 70d8b503f04dd0366ea2193d6efaf262ad129de7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 21:47:44 +0200 Subject: [PATCH 07/57] Assent to the NetKingdom security layer model (WARDEN-IN-0001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gate-house asked ops-warden to assent to three boundary items ratified in GH-DEC-2026-001. All three are assented in ADR-0010. Staff: accepted. Grepping section 5 as it invites turned up a real non-conformance — src/warden/vault.py is a direct OpenBao client performing a write, and so is `warden desk`'s `bao kv put`. Section 5's only escape hatch is read-only diagnostics, which does not cover a signing write, so both are declared in INTENT.md as an engine gap with intended owner secrets-engine and the blocker "no engine exposes an SSH-CA surface" — ADR-0003 turned inward rather than an exemption argued for. taint.py is metadata-only and declared under the read-only allowance; `warden access` proxies run under the caller's identity and supply no authority of their own. Doctrine versus runbook: accepted. NetKingdom Security Literacy becomes a lane routing runbook that references gate-house doctrine instead of restating it. It had also become a prose second source for registry/routing/catalog.yaml, which ADR-0001 already rules against. Lane versus rule: assented unconditionally, and the access-engine veto is not exercised. One request on sequencing only — a window where both names resolve. gate-house added to the routing tables in INTENT.md and SCOPE.md. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- .claude/rules/architecture.md | 2 + .repo-manager/index.json | 2215 +++++++++++++++++ INTENT.md | 85 +- SCOPE.md | 4 +- docs/adr/ADR-0010-ops-warden-is-staff.md | 109 + docs/adr/README.md | 1 + .../2026-08-28-security-layer-model-assent.md | 171 ++ intakes/intakes.md | 23 +- 8 files changed, 2579 insertions(+), 31 deletions(-) create mode 100644 .repo-manager/index.json create mode 100644 docs/adr/ADR-0010-ops-warden-is-staff.md create mode 100644 history/2026-08-28-security-layer-model-assent.md diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md index 6f6cde3..ee357fb 100644 --- a/.claude/rules/architecture.md +++ b/.claude/rules/architecture.md @@ -16,6 +16,8 @@ distinction that matters here. | `ADR-0006` | Enforcement is zone-scoped, never a global flag | | `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade | | `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after | +| `ADR-0009` | Adopt security-zones v0.1 as a consumer; membership is compiled, never inferred | +| `ADR-0010` | ops-warden is Staff: it owns access lanes, never access rules; doctrine belongs to gate-house | ### Owned versus inherited — check `owner:` before changing anything diff --git a/.repo-manager/index.json b/.repo-manager/index.json new file mode 100644 index 0000000..a0e6bd2 --- /dev/null +++ b/.repo-manager/index.json @@ -0,0 +1,2215 @@ +{ + "schema": "repo_manager.index.v1", + "slug": "layer-model-assent", + "repo_root": "/home/worsch/ops-warden", + "head_sha": "467635e84b99757336ee49d7f0dbf107607d0560", + "observed_at": "2026-08-28T19:30:29.828983Z", + "source_fingerprint": "7dfae4f3d33f6a9503a2c7326b925efb378613e772b39c4dd9b0640a35b85654", + "source_files": [ + ".repo-classification.yaml", + "INTENT.md", + "intakes/intakes.md", + "workplans/ADHOC-2026-06-27.md", + "workplans/ADHOC-2026-06-29.md", + "workplans/ADHOC-2026-08-11.md", + "workplans/ADHOC-2026-08-17.md", + "workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md", + "workplans/WARDEN-WP-0017-access-front-door-discoverability.md", + "workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md", + "workplans/WARDEN-WP-0019-route-to-secrets-engine.md", + "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "workplans/WARDEN-WP-0022-audit-trail-and-activity.md", + "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "workplans/WARDEN-WP-0027-credential-governance-lockdown.md", + "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "workplans/WARDEN-WP-0030-delegation-register.md", + "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "workplans/WARDEN-WP-0032-security-zones.md", + "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "workplans/archived/260515-WARDEN-WP-0002-correctness-and-completeness.md", + "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "workplans/archived/260617-WARDEN-WP-0004-repo-hygiene-and-hub-sync.md", + "workplans/archived/260617-WARDEN-WP-0005-openbao-doc-alignment.md", + "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "workplans/archived/260617-WARDEN-WP-0007-policy-gate-and-production-verify.md", + "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "workplans/archived/260623-WARDEN-WP-0009-flex-auth-policy-gate-production.md", + "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "workplans/archived/260707-ADHOC-2026-07-07.md" + ], + "work_records": [ + { + "kind": "workplan", + "id": "WARDEN-WP-ADHOC-2026-06-27", + "status": "finished", + "title": "Ad Hoc Tasks \u2014 2026-06-27", + "source_path": "workplans/ADHOC-2026-06-27.md", + "uuid": null, + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-06-27-T01", + "status": "done", + "title": "T01 \u2014 Fix stale `warden` CLI install + make it usable outside the repo", + "source_path": "workplans/ADHOC-2026-06-27.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-06-27", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-ADHOC-2026-06-29", + "status": "finished", + "title": "Ad Hoc Tasks \u2014 2026-06-29", + "source_path": "workplans/ADHOC-2026-06-29.md", + "uuid": null, + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-06-29-T01", + "status": "done", + "title": "T01 \u2014 Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4)", + "source_path": "workplans/ADHOC-2026-06-29.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-06-29", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-ADHOC-2026-08-11", + "status": "finished", + "title": "Ad Hoc Tasks \u2014 2026-08-11", + "source_path": "workplans/ADHOC-2026-08-11.md", + "uuid": null, + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-11-T01", + "status": "done", + "title": "T01 \u2014 Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite)", + "source_path": "workplans/ADHOC-2026-08-11.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-11", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-11-T02", + "status": "done", + "title": "T02 \u2014 Triage the stale ops-warden inbox (11 unread, C-28/C-29)", + "source_path": "workplans/ADHOC-2026-08-11.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-11", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-11-T03", + "status": "done", + "title": "T03 \u2014 warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover", + "source_path": "workplans/ADHOC-2026-08-11.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-11", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-ADHOC-2026-08-17", + "status": "finished", + "title": "Ad Hoc Tasks \u2014 2026-08-17", + "source_path": "workplans/ADHOC-2026-08-17.md", + "uuid": null, + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-17-T01", + "status": "done", + "title": "T01 \u2014 Answer flex-auth: how should `/v1/check` authenticate its callers?", + "source_path": "workplans/ADHOC-2026-08-17.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-17", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-17-T02", + "status": "done", + "title": "T02 \u2014 user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only", + "source_path": "workplans/ADHOC-2026-08-17.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-17", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-17-T03", + "status": "done", + "title": "T03 \u2014 key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker", + "source_path": "workplans/ADHOC-2026-08-17.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-17", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-08-17-T04", + "status": "done", + "title": "T04 \u2014 Session hygiene", + "source_path": "workplans/ADHOC-2026-08-17.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-08-17", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0016", + "status": "finished", + "title": "ops-bridge cert_command pilot \u2014 readiness gate + handoff", + "source_path": "workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md", + "uuid": "a56da8db-38bc-4bbe-8671-823360ec9245", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0016-T01", + "status": "done", + "title": "T1 \u2014 Read-only `cert_command` readiness preflight", + "source_path": "workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md", + "uuid": "fea84495-dbec-480a-b42b-90e39f414b78", + "parent_id": "WARDEN-WP-0016", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0016-T02", + "status": "done", + "title": "T2 \u2014 Offline cert_command contract smoke", + "source_path": "workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md", + "uuid": "e34ae1a8-2ba9-4324-8d1a-005d61dae478", + "parent_id": "WARDEN-WP-0016", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0016-T03", + "status": "done", + "title": "T3 \u2014 Playbook gate + ops-bridge handoff", + "source_path": "workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md", + "uuid": "330e01f4-4927-4280-b0e0-49d35b4416d6", + "parent_id": "WARDEN-WP-0016", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0016-T04", + "status": "done", + "title": "T4 \u2014 INTENT/SCOPE alignment", + "source_path": "workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md", + "uuid": "4726f5bb-4ffd-484f-8674-91ee5658434f", + "parent_id": "WARDEN-WP-0016", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0017", + "status": "finished", + "title": "Access front-door discoverability \u2014 stop reading as SSH-only", + "source_path": "workplans/WARDEN-WP-0017-access-front-door-discoverability.md", + "uuid": "cf8b392e-7624-4585-8935-a85e29202935", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0017-T01", + "status": "done", + "title": "T1 \u2014 CLI discoverability: route role + access framing", + "source_path": "workplans/WARDEN-WP-0017-access-front-door-discoverability.md", + "uuid": "6e98df42-b5b4-49f8-a444-3c6346c8abd7", + "parent_id": "WARDEN-WP-0017", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0017-T02", + "status": "done", + "title": "T2 \u2014 Agent rule + SCOPE reframe", + "source_path": "workplans/WARDEN-WP-0017-access-front-door-discoverability.md", + "uuid": "6e2a7067-1afc-4f38-8d99-4d5c36a4661c", + "parent_id": "WARDEN-WP-0017", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0017-T03", + "status": "done", + "title": "T3 \u2014 Federated capability registration", + "source_path": "workplans/WARDEN-WP-0017-access-front-door-discoverability.md", + "uuid": "7199625b-e78e-4495-8ca0-076100ae9f08", + "parent_id": "WARDEN-WP-0017", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0018", + "status": "finished", + "title": "Activate whynot-design npm publish lane + resolvable readiness flag", + "source_path": "workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md", + "uuid": "1256aca2-5979-4d21-818e-0de42c5d811b", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0018-T01", + "status": "done", + "title": "T1 \u2014 Concrete catalog entry + playbook", + "source_path": "workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md", + "uuid": "189d0883-22b9-42dc-bda0-89460509a87d", + "parent_id": "WARDEN-WP-0018", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0018-T02", + "status": "done", + "title": "T2 \u2014 `resolvable` readiness flag + stable-id resolution", + "source_path": "workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md", + "uuid": "b5dc1013-5334-43ff-afd6-1f99d521358f", + "parent_id": "WARDEN-WP-0018", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0018-T03", + "status": "done", + "title": "T3 \u2014 Close the loop", + "source_path": "workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md", + "uuid": "95b00ef8-477a-4f0d-bd71-6154fba401f5", + "parent_id": "WARDEN-WP-0018", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0019", + "status": "finished", + "title": "Route secret-exec lanes to secrets-engine (route-primary, proxy fallback)", + "source_path": "workplans/WARDEN-WP-0019-route-to-secrets-engine.md", + "uuid": "5e49abb6-497f-4640-a484-2da5f39a7c4e", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0019-T01", + "status": "done", + "title": "T1 \u2014 Catalog + CLI: surface the owner-native exec front door", + "source_path": "workplans/WARDEN-WP-0019-route-to-secrets-engine.md", + "uuid": "ea153605-7a14-4db7-8bce-d780ea143f8a", + "parent_id": "WARDEN-WP-0019", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0019-T02", + "status": "done", + "title": "T2 \u2014 Agent rule, SCOPE, playbook", + "source_path": "workplans/WARDEN-WP-0019-route-to-secrets-engine.md", + "uuid": "96059b8a-8938-4763-b3d0-cc5a0eb2465c", + "parent_id": "WARDEN-WP-0019", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0020", + "status": "finished", + "title": "ops-warden worker \u2014 autonomous coordination via llm-connect", + "source_path": "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "uuid": "c906ba1d-f991-4fb0-b113-59432ddf87c0", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0020-T01", + "status": "done", + "title": "T1 \u2014 Worker scaffold (llm-connect-independent, safe)", + "source_path": "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "uuid": "979c2d9b-0803-442f-aa2e-acb02bac07e9", + "parent_id": "WARDEN-WP-0020", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0020-T02", + "status": "done", + "title": "T2 \u2014 llm-connect brain", + "source_path": "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "uuid": "52d281b2-7d48-44f5-b77e-80e3ed500b5f", + "parent_id": "WARDEN-WP-0020", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0020-T03", + "status": "done", + "title": "T3 \u2014 Action dispatch + guardrails (full-auto in-scope)", + "source_path": "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "uuid": "3a71965e-42d5-4258-9761-aced804c88e7", + "parent_id": "WARDEN-WP-0020", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0020-T04", + "status": "done", + "title": "T4 \u2014 Scheduled trigger", + "source_path": "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "uuid": "7f77ea6d-c281-42c5-ad25-2a0bb9fd68de", + "parent_id": "WARDEN-WP-0020", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0020-T05", + "status": "done", + "title": "T5 \u2014 Docs / SCOPE / INTENT", + "source_path": "workplans/WARDEN-WP-0020-ops-warden-worker.md", + "uuid": "6e7ae317-7f8b-468a-bb5c-b08093ed43a0", + "parent_id": "WARDEN-WP-0020", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0021", + "status": "finished", + "title": "Enable the scheduled worker tick \u2014 conservative inbox triage, unattended", + "source_path": "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "uuid": "8c487014-b630-4016-a4f0-31b971a473d2", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0021-T01", + "status": "done", + "title": "T1 \u2014 Scheduler install + enablement + kill switch", + "source_path": "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "uuid": "10451fe6-7fab-4ae0-8494-e6cfdfbcf8cf", + "parent_id": "WARDEN-WP-0021", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0021-T02", + "status": "done", + "title": "T2 \u2014 Scheduled-run robustness (graceful degradation)", + "source_path": "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "uuid": "1f35f816-1af5-46ff-b48c-1715f3ae5784", + "parent_id": "WARDEN-WP-0021", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0021-T03", + "status": "done", + "title": "T3 \u2014 Operator visibility (see new drafts)", + "source_path": "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "uuid": "3c7f6423-8db0-4bc6-b67d-078d9d929c6d", + "parent_id": "WARDEN-WP-0021", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0021-T04", + "status": "done", + "title": "T4 \u2014 Review\u2192send loop (`warden worker approve`)", + "source_path": "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "uuid": "dabc9fc0-abb1-4e9d-b87e-5f0c5950693c", + "parent_id": "WARDEN-WP-0021", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0021-T05", + "status": "done", + "title": "T5 \u2014 Runbook + SCOPE", + "source_path": "workplans/WARDEN-WP-0021-enable-scheduled-worker-tick.md", + "uuid": "9915da96-1b33-4d0f-b752-408ea8d43333", + "parent_id": "WARDEN-WP-0021", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0022", + "status": "finished", + "title": "Audit trail + `warden activity` \u2014 one place to see what ops-warden did", + "source_path": "workplans/WARDEN-WP-0022-audit-trail-and-activity.md", + "uuid": "fc8afa28-68a7-4250-a19e-9754829f0cd5", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0022-T01", + "status": "done", + "title": "T1 \u2014 Unified audit event log", + "source_path": "workplans/WARDEN-WP-0022-audit-trail-and-activity.md", + "uuid": "7f8f768a-4c62-4096-bad8-912cea0f35a7", + "parent_id": "WARDEN-WP-0022", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0022-T02", + "status": "done", + "title": "T2 \u2014 Instrument the actions", + "source_path": "workplans/WARDEN-WP-0022-audit-trail-and-activity.md", + "uuid": "e7ae4037-ca79-4557-81f0-bfb8478ff647", + "parent_id": "WARDEN-WP-0022", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0022-T03", + "status": "done", + "title": "T3 \u2014 `warden activity` command", + "source_path": "workplans/WARDEN-WP-0022-audit-trail-and-activity.md", + "uuid": "4439bdd8-1461-47df-8b0b-048df7384a68", + "parent_id": "WARDEN-WP-0022", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0022-T04", + "status": "done", + "title": "T4 \u2014 Tests, runbook, SCOPE", + "source_path": "workplans/WARDEN-WP-0022-audit-trail-and-activity.md", + "uuid": "bdfb8703-7a79-43e7-913b-19d61722f164", + "parent_id": "WARDEN-WP-0022", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0023", + "status": "finished", + "title": "INTENT\u2013SCOPE Alignment Closeout", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "7bad1ec4-a7c2-4980-b8f9-49a7f5408574", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T01", + "status": "done", + "title": "T01 \u2014 Persist gap analysis", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "52485c90-87fe-40b1-9db5-a51ebb957dd5", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T02", + "status": "done", + "title": "T02 \u2014 Refresh INTENT.md", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "9a9b3631-8948-45af-ace1-c19ee74ace4d", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T03", + "status": "done", + "title": "T03 \u2014 Production integration coordination pack", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "26f23798-494b-45fc-baa8-af27bdffa038", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T04", + "status": "done", + "title": "T04 \u2014 `warden sign` broker hint when `VAULT_TOKEN` unset", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "85e324f9-273d-4740-a202-9c4e8fb122ae", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T05", + "status": "done", + "title": "T05 \u2014 Catalog draft-lane promotion checklist", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "82608692-2845-41e1-a498-90ed53780748", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T06", + "status": "done", + "title": "T06 \u2014 SCOPE and workplan consistency", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "79ca7b9a-554e-4952-9393-a29b100f6190", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0023-T07", + "status": "done", + "title": "T07 \u2014 Sequence WP-0022 audit implementation", + "source_path": "workplans/WARDEN-WP-0023-intent-scope-alignment-closeout.md", + "uuid": "1f3b3b33-974e-49bf-be4a-9d50b702c2a4", + "parent_id": "WARDEN-WP-0023", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0024", + "status": "finished", + "title": "Experiential Memory Across Worker, Agent Sessions, And OpenRouter", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "5d9fafb3-f9b6-43bf-b259-5f5301daa2e9", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T01", + "status": "done", + "title": "T01 - Canonical memory store and discovery", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "6305f1bc-c016-4298-adc2-a07d52b6aca5", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T02", + "status": "done", + "title": "T02 - Session recording hooks in CLI commands", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "242a4d9a-5375-4df8-8d43-063b0491d202", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T03", + "status": "done", + "title": "T03 - Memory-aware worker tick", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "176fcae1-e4e5-481f-9a83-e9a7000fac1a", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T04", + "status": "done", + "title": "T04 - Agent session activation helper", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "16501557-6cde-44ea-bc6f-1726cb7ec070", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T05", + "status": "done", + "title": "T05 - Cross-runtime continuity", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "55ae679c-c08f-4afd-8646-9f5f3019f86e", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T06", + "status": "done", + "title": "T06 - OpenRouter efficiency layer", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "fc2dffcf-7184-4f3d-8653-d26dc18a9afc", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0024-T07", + "status": "done", + "title": "T07 - Operator and agent documentation", + "source_path": "workplans/WARDEN-WP-0024-experiential-memory-and-agent-sessions.md", + "uuid": "ca2aaf23-833f-49a6-a49b-a0b659208f5f", + "parent_id": "WARDEN-WP-0024", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0025", + "status": "finished", + "title": "Forgejo admin PAT OpenBao lane (CCR-2026-0006)", + "source_path": "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "uuid": "70c11222-d8e7-5936-99f7-7d626a4a5deb", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0025-T01", + "status": "done", + "title": "T1 \u2014 Draft CCR + policy metadata", + "source_path": "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "uuid": "2c288bf0-b39c-5f5b-ad25-eed3da826dc3", + "parent_id": "WARDEN-WP-0025", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0025-T02", + "status": "done", + "title": "T2 \u2014 ops-warden catalog + playbook", + "source_path": "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "uuid": "01b81595-f9e5-5746-b0dd-2075189cb00e", + "parent_id": "WARDEN-WP-0025", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0025-T03", + "status": "done", + "title": "T3 \u2014 Platform-operator approval + metadata apply", + "source_path": "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "uuid": "279b74d7-3240-5ca0-897d-b1ddffd23c4e", + "parent_id": "WARDEN-WP-0025", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0025-T04", + "status": "done", + "title": "T4 \u2014 Attended PAT provision + verification", + "source_path": "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "uuid": "4e21232c-62a1-5115-acce-edfbcfa84e48", + "parent_id": "WARDEN-WP-0025", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0025-T05", + "status": "done", + "title": "T5 \u2014 Notify downstream consumers", + "source_path": "workplans/WARDEN-WP-0025-forgejo-admin-api-token-lane.md", + "uuid": "f8c7c70b-b9b6-5980-a25b-7f11033b81a2", + "parent_id": "WARDEN-WP-0025", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0026", + "status": "finished", + "title": "Credential disclosure hygiene + rotation guidance (Strand A)", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "331c7620-bd34-5acd-9135-591985b568e5", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T01", + "status": "done", + "title": "Task: Capabilities-based lane verification", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "1cb22a40-b7c6-560a-a805-7766a5786dcc", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T02", + "status": "done", + "title": "Task: Safe access transport (no stdout values)", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "bcb7da96-0a28-5484-bf3e-06e97acf5873", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T03", + "status": "done", + "title": "Task: Masking display filter (defense-in-depth)", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "d90b0628-fa1d-527d-99c3-28a7ed933e52", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T04", + "status": "done", + "title": "Task: Agent read-boundary on high-risk lanes", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "827fa67d-5f69-5fac-bdce-9903b1b909fb", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T05", + "status": "done", + "title": "Task: EXPOSED taint convention", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "09ef8727-31da-59ac-aac7-2d47924569fe", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T06", + "status": "done", + "title": "Task: Rotation / re-establishment guidance registry", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "a2e1544e-e501-57eb-a40e-9a2147cef12a", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0026-T07", + "status": "done", + "title": "Task: Incident lessons + first worked lane (CCR-2026-0004)", + "source_path": "workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md", + "uuid": "62d8286f-7954-52a8-bce6-6a16072e5246", + "parent_id": "WARDEN-WP-0026", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0027", + "status": "active", + "title": "Tamper-resistant credential governance + mass rotation/lockdown (Strand B)", + "source_path": "workplans/WARDEN-WP-0027-credential-governance-lockdown.md", + "uuid": "21528e8d-a049-523d-9ae1-da7a27cb8bbf", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0027-T01", + "status": "cancel", + "title": "Task: Executable mass rotation driver", + "source_path": "workplans/WARDEN-WP-0027-credential-governance-lockdown.md", + "uuid": "b5691939-9d84-5115-9618-0f8839010d14", + "parent_id": "WARDEN-WP-0027", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0027-T02", + "status": "progress", + "title": "Task: Graded lockdown / break-glass with explicit trust-root", + "source_path": "workplans/WARDEN-WP-0027-credential-governance-lockdown.md", + "uuid": "cae498ee-6307-5d32-9f1b-a471cfcc2536", + "parent_id": "WARDEN-WP-0027", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0027-T03", + "status": "cancel", + "title": "Task: Tamper-evident policy governance + reconcile", + "source_path": "workplans/WARDEN-WP-0027-credential-governance-lockdown.md", + "uuid": "7dbedcdc-dd5a-551c-bff1-0702fea0a9cf", + "parent_id": "WARDEN-WP-0027", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0028", + "status": "finished", + "title": "Tenant secret custody \u2014 NetKingdom pattern for client/tenant secrets", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "6b66228a-199a-5b85-b5e2-a7afeaab903b", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T01", + "status": "done", + "title": "T01 \u2014 Canon note: tenant secret path + ownership", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "9982a884-7a50-593e-861e-cc8d2343a0ba", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T02", + "status": "done", + "title": "T02 \u2014 Align binky-control integration plan to production path", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "d7d7ee9d-2ecf-5492-8a13-0b747d899456", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T03", + "status": "done", + "title": "T03 \u2014 Enable `tenants` mount + extend CCR tooling + policy/role", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "e0b675ef-9c08-5f89-8f13-ef4249ca2364", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T04", + "status": "done", + "title": "T04 \u2014 ops-warden catalog + playbook + rotation", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "9405b13d-4045-519b-8e3f-79a891323397", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T05", + "status": "done", + "title": "T05 \u2014 Founder provision (Red) + first scan evidence", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "f17bba95-bc53-5c2a-8b44-7df9e42b2341", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T06", + "status": "done", + "title": "T06 \u2014 Generalize \"tenant secret onboarding\" playbook", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "76bd01a0-1d03-5ff9-8f59-a1b44763979d", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0028-T07", + "status": "done", + "title": "T07 \u2014 secrets-engine alignment decision (record only)", + "source_path": "workplans/WARDEN-WP-0028-tenant-secret-custody.md", + "uuid": "46c8f8a9-3bbe-568f-8a45-07b690874273", + "parent_id": "WARDEN-WP-0028", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0029", + "status": "finished", + "title": "Policy front door: posture-aware access planning + founder interaction surface", + "source_path": "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "uuid": "bbb3d9ec-d88d-5088-b4c0-55bfba0a10cf", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0029-T02", + "status": "done", + "title": "T02 \u2014 Declared organization posture (build phase)", + "source_path": "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "uuid": "6c213024-6601-5116-b52f-d6711dc0587d", + "parent_id": "WARDEN-WP-0029", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0029-T05", + "status": "done", + "title": "T05 \u2014 Catalog freshness + agent guidance", + "source_path": "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "uuid": "c82d8745-4af4-5b89-ab49-06d8a902e592", + "parent_id": "WARDEN-WP-0029", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0029-T01", + "status": "done", + "title": "T01 \u2014 `warden plan` decision front door", + "source_path": "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "uuid": "34db38fa-cb99-5ced-9a12-85176a7f2b44", + "parent_id": "WARDEN-WP-0029", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0029-T04", + "status": "done", + "title": "T04 \u2014 Retire file-drop patterns from playbooks", + "source_path": "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "uuid": "717f57da-fbc4-5a4d-9f8c-c1c3fa75e0ee", + "parent_id": "WARDEN-WP-0029", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0029-T03", + "status": "done", + "title": "T03 \u2014 Founder interaction surface (local web approval page)", + "source_path": "workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md", + "uuid": "47c781d2-768b-5777-b971-b5227fe41f5c", + "parent_id": "WARDEN-WP-0029", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0030", + "status": "finished", + "title": "Delegation register \u2014 make gap-covering interim, visible, and retirable", + "source_path": "workplans/WARDEN-WP-0030-delegation-register.md", + "uuid": "da3367d5-890c-52c6-aa54-1bdd0f277342", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0030-T01", + "status": "done", + "title": "T01 \u2014 Interim custodianship doctrine", + "source_path": "workplans/WARDEN-WP-0030-delegation-register.md", + "uuid": "6f88876e-434c-5718-9c8d-ec6bf64ae4aa", + "parent_id": "WARDEN-WP-0030", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0030-T02", + "status": "done", + "title": "T02 \u2014 `delegation:` metadata + backfill", + "source_path": "workplans/WARDEN-WP-0030-delegation-register.md", + "uuid": "b02e8da6-57ca-5f9f-9405-9b0624498e3a", + "parent_id": "WARDEN-WP-0030", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0030-T03", + "status": "done", + "title": "T03 \u2014 `warden route gaps` + conformance test", + "source_path": "workplans/WARDEN-WP-0030-delegation-register.md", + "uuid": "f5e0f5af-45d8-5c82-9de2-d640d7d0a1f7", + "parent_id": "WARDEN-WP-0030", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0030-T04", + "status": "done", + "title": "T04 \u2014 Promotion gate", + "source_path": "workplans/WARDEN-WP-0030-delegation-register.md", + "uuid": "b808a749-e1d7-5708-aabf-91732dd76abd", + "parent_id": "WARDEN-WP-0030", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0030-T05", + "status": "done", + "title": "T05 \u2014 Publish the register to the owners", + "source_path": "workplans/WARDEN-WP-0030-delegation-register.md", + "uuid": "b0188ec4-4860-57c8-8030-45904a132190", + "parent_id": "WARDEN-WP-0030", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0031", + "status": "finished", + "title": "Calling-side identity for flex-auth, so policy.enabled can flip", + "source_path": "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "uuid": "739bad25-2345-5f4f-aaa3-cc4cd8c71f6c", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0031-T01", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "uuid": "d3b7c701-bcdd-53f9-aa72-6f289bf5909b", + "parent_id": "WARDEN-WP-0031", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0031-T02", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "uuid": "b77b3c80-a168-564a-9b7f-3063aefc3c2e", + "parent_id": "WARDEN-WP-0031", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0031-T03", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "uuid": "4245155e-6c71-5425-b574-11f61e1d4461", + "parent_id": "WARDEN-WP-0031", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0031-T04", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "uuid": "f3834af7-2a08-51dd-bf31-8ce8550de699", + "parent_id": "WARDEN-WP-0031", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0031-T05", + "status": "cancel", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0031-policy-caller-identity.md", + "uuid": "3f6dc609-89db-52eb-a6f9-d2fd271be821", + "parent_id": "WARDEN-WP-0031", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0032", + "status": "finished", + "title": "Adopt security zones as a consumer \u2014 retire the global policy.enabled", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "38c6a5f3-fb0d-5230-be85-f9e3ffc850f6", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T01", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "b03a5caa-0bb5-5cdd-bb99-32c694b0da29", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T02", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "b3f41c85-a293-58ad-ac27-9f8110c51266", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T03", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "d04a737b-ecdf-5747-ba25-20dadd99d3bc", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T04", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "c7879127-3dba-55c0-853c-a10775736873", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T05", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "1d968627-83f4-59cd-84ca-0f9f35e435ff", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T06", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "4294084c-bf3f-5aa6-b84d-5173121882ff", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0032-T07", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0032-security-zones.md", + "uuid": "6b4bbbed-2864-5fe9-82be-22ff9543f6f4", + "parent_id": "WARDEN-WP-0032", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0033", + "status": "active", + "title": "Native lane handoff \u2014 review secrets-engine's catalog admission, and fix what it exposed", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "4627d89b-4b00-562a-81e9-76e96f90fa7e", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0033-T01", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "154f2f03-387d-5fe6-a0f5-1929c46a2bd8", + "parent_id": "WARDEN-WP-0033", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0033-T02", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "6996d07f-63bb-5708-a171-68c4b1bbddde", + "parent_id": "WARDEN-WP-0033", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0033-T03", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "a736f983-94da-5a4a-aaf9-5114485518a6", + "parent_id": "WARDEN-WP-0033", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0033-T04", + "status": "wait", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "5acac140-a586-5db3-b231-bbf236710786", + "parent_id": "WARDEN-WP-0033", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0033-T05", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "75051d17-399b-5129-860b-ae00dae91c47", + "parent_id": "WARDEN-WP-0033", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0033-T06", + "status": "done", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "uuid": "94f77f5a-f919-5328-832c-ba1d24c6431b", + "parent_id": "WARDEN-WP-0033", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0001", + "status": "archived", + "title": "OpsWarden Initial Implementation", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "c3118cc6-adfb-428c-a9c6-edd0ee152ae6", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T1", + "status": "done", + "title": "T1 \u2014 Repository bootstrap", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "6d643e9d-5e97-4224-9d82-87267b5ba6bc", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T2", + "status": "done", + "title": "T2 \u2014 Models and config", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "c66fc65a-0b16-4ba2-9e70-a83d875572ec", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T3", + "status": "done", + "title": "T3 \u2014 LocalCA backend", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "a5a41e58-1c6d-42a9-9b11-2088f17c29b5", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T4", + "status": "done", + "title": "T4 \u2014 VaultCA backend", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "b2067ee6-c9ce-423b-9d60-0d28069fb304", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T5", + "status": "done", + "title": "T5 \u2014 Principals inventory", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "6d13f8cd-1850-44c9-b769-b21250348319", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T6", + "status": "done", + "title": "T6 \u2014 CLI commands", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "656a4615-92bb-4b5d-9406-e86d24fa15d0", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T7", + "status": "done", + "title": "T7 \u2014 Scorecard runner", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "7818bcc5-f40e-4793-b117-d36f653ffeed", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T8", + "status": "done", + "title": "T8 \u2014 ops-ssh-wrapper script", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "e9c28152-5785-4995-83a5-439985ed3db9", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T9", + "status": "done", + "title": "T9 \u2014 Tests", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "950139ab-cc17-4f1d-9a17-d5744e402ddf", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0001-T10", + "status": "done", + "title": "T10 \u2014 Documentation", + "source_path": "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", + "uuid": "271d6759-e359-41ce-80e4-76c574634a87", + "parent_id": "WARDEN-WP-0001", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0002", + "status": "archived", + "title": "OpsWarden Correctness and Operational Completeness", + "source_path": "workplans/archived/260515-WARDEN-WP-0002-correctness-and-completeness.md", + "uuid": "5a9fba2c-6161-49a4-a231-e750fa4ab572", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0002-T1", + "status": "done", + "title": "T1 \u2014 TTL max enforcement per ActorType", + "source_path": "workplans/archived/260515-WARDEN-WP-0002-correctness-and-completeness.md", + "uuid": "b0d0b5f7-a181-4590-be26-c48ae28cd964", + "parent_id": "WARDEN-WP-0002", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0002-T2", + "status": "done", + "title": "T2 \u2014 Stale cert cleanup command", + "source_path": "workplans/archived/260515-WARDEN-WP-0002-correctness-and-completeness.md", + "uuid": "aeeefbad-c0bd-4ae8-a3fe-9f72321b4caa", + "parent_id": "WARDEN-WP-0002", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0002-T3", + "status": "done", + "title": "T3 \u2014 Outgoing signatures log", + "source_path": "workplans/archived/260515-WARDEN-WP-0002-correctness-and-completeness.md", + "uuid": "0194d24f-a8fe-4f6d-88e6-addea3542c0e", + "parent_id": "WARDEN-WP-0002", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0003", + "status": "archived", + "title": "OpsWarden Test Coverage and Code Quality", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "cb2bbf3c-848a-4af6-ba64-8361e64cd4d7", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0003-T1", + "status": "done", + "title": "T1 \u2014 VaultCA tests", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "eff074ce-c027-4df5-8006-0990296592ac", + "parent_id": "WARDEN-WP-0003", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0003-T2", + "status": "done", + "title": "T2 \u2014 LocalCA.generate_keypair tests", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "ddfe5331-0a3b-4783-bdf4-f5ebcdf7965c", + "parent_id": "WARDEN-WP-0003", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0003-T3", + "status": "done", + "title": "T3 \u2014 CLI tests", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "040ce3a1-0efb-4816-a2d9-357162dd1612", + "parent_id": "WARDEN-WP-0003", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0003-T4", + "status": "done", + "title": "T4 \u2014 Real ssh-keygen integration test", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "434fb008-103f-410c-85fd-e77b33e61fe4", + "parent_id": "WARDEN-WP-0003", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0003-T5", + "status": "done", + "title": "T5 \u2014 File permissions enforcement (mode 600)", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "ac146fe6-d1fd-4186-91bd-6f098de72449", + "parent_id": "WARDEN-WP-0003", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0003-T6", + "status": "done", + "title": "T6 \u2014 warden status --state-dir override", + "source_path": "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", + "uuid": "1c9f1987-7b11-43c1-a5e3-c2fd8d1c1589", + "parent_id": "WARDEN-WP-0003", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0004", + "status": "archived", + "title": "OpsWarden Repo Hygiene and Hub Sync", + "source_path": "workplans/archived/260617-WARDEN-WP-0004-repo-hygiene-and-hub-sync.md", + "uuid": "3c4b6e68-550a-4fc6-a804-95f1f68936c3", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0004-T01", + "status": "done", + "title": "T1 \u2014 Update orientation docs", + "source_path": "workplans/archived/260617-WARDEN-WP-0004-repo-hygiene-and-hub-sync.md", + "uuid": "f9d3926c-8637-411c-a477-2960b754704c", + "parent_id": "WARDEN-WP-0004", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0004-T02", + "status": "done", + "title": "T2 \u2014 Fill agent rules", + "source_path": "workplans/archived/260617-WARDEN-WP-0004-repo-hygiene-and-hub-sync.md", + "uuid": "86c764a5-62fc-45fe-a8d2-332d6554a976", + "parent_id": "WARDEN-WP-0004", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0004-T03", + "status": "done", + "title": "T3 \u2014 Archive finished workplans", + "source_path": "workplans/archived/260617-WARDEN-WP-0004-repo-hygiene-and-hub-sync.md", + "uuid": "d3e54e63-ce98-4632-bc08-0e2667f19f12", + "parent_id": "WARDEN-WP-0004", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0004-T04", + "status": "done", + "title": "T4 \u2014 Sync State Hub", + "source_path": "workplans/archived/260617-WARDEN-WP-0004-repo-hygiene-and-hub-sync.md", + "uuid": "51729695-262f-4fe4-9c38-f99ee046d32a", + "parent_id": "WARDEN-WP-0004", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0005", + "status": "archived", + "title": "OpsWarden OpenBao-First Documentation Alignment", + "source_path": "workplans/archived/260617-WARDEN-WP-0005-openbao-doc-alignment.md", + "uuid": "57f6ebf8-0ef3-4686-9a73-3f9d38288be9", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0005-T01", + "status": "done", + "title": "T1 \u2014 OpsWardenConfig.md", + "source_path": "workplans/archived/260617-WARDEN-WP-0005-openbao-doc-alignment.md", + "uuid": "bbbc4dda-9634-4c04-86e5-94b96c021b43", + "parent_id": "WARDEN-WP-0005", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0005-T02", + "status": "done", + "title": "T2 \u2014 Cross-reference updates", + "source_path": "workplans/archived/260617-WARDEN-WP-0005-openbao-doc-alignment.md", + "uuid": "6391cb82-896e-405a-a59b-36640e6480ba", + "parent_id": "WARDEN-WP-0005", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0006", + "status": "archived", + "title": "NetKingdom Alignment and Operational Access Stewardship", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "a5c9f24b-1ad4-46da-bc8e-b99897f8e302", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T01", + "status": "done", + "title": "T1 \u2014 Credential routing runbook", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "ffc6a0c2-4312-4584-be7a-c8411cb01899", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T02", + "status": "done", + "title": "T2 \u2014 Actor inventory patterns", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "3816463d-7dfd-469d-9324-fd7880b50608", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T03", + "status": "done", + "title": "T3 \u2014 NetKingdom cross-links (ops-warden side)", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "f158366a-5746-48b8-acce-472dce8f925e", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T04", + "status": "done", + "title": "T4 \u2014 NetKingdom canon patch (coordination)", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "e40e4395-8f01-4f79-a539-d0de8e427321", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T05", + "status": "done", + "title": "T5 \u2014 OpenBao SSH engine operational checklist", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "a94e20a2-970b-4a0c-bd23-8510b841b938", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T06", + "status": "done", + "title": "T6 \u2014 Policy-gated signing design (design only)", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "b10a4b4d-bfa1-4f49-b6a5-f339f1e6a2e1", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0006-T07", + "status": "done", + "title": "T7 \u2014 Re-assess INTENT \u2194 SCOPE", + "source_path": "workplans/archived/260617-WARDEN-WP-0006-netkingdom-alignment-and-access-stewardship.md", + "uuid": "ef8b5c57-2343-4cfc-9fee-48db1e56f69a", + "parent_id": "WARDEN-WP-0006", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0007", + "status": "archived", + "title": "Policy Gate and Production OpenBao Verification", + "source_path": "workplans/archived/260617-WARDEN-WP-0007-policy-gate-and-production-verify.md", + "uuid": "3718ac07-2fa2-47d0-a02a-c9a7b83a5ba9", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0007-T01", + "status": "done", + "title": "T1 \u2014 Production OpenBao verification evidence", + "source_path": "workplans/archived/260617-WARDEN-WP-0007-policy-gate-and-production-verify.md", + "uuid": "344540ad-5912-4118-b406-450b96e13c40", + "parent_id": "WARDEN-WP-0007", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0007-T02", + "status": "done", + "title": "T2 \u2014 Policy config and flex-auth client", + "source_path": "workplans/archived/260617-WARDEN-WP-0007-policy-gate-and-production-verify.md", + "uuid": "05424ddf-5fe9-43a1-a2f8-c47235a012c8", + "parent_id": "WARDEN-WP-0007", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0007-T03", + "status": "done", + "title": "T3 \u2014 Wire policy gate into sign/issue", + "source_path": "workplans/archived/260617-WARDEN-WP-0007-policy-gate-and-production-verify.md", + "uuid": "f5ae8e6e-8cce-4526-b18c-0452a135af49", + "parent_id": "WARDEN-WP-0007", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0007-T04", + "status": "done", + "title": "T4 \u2014 Tests and docs", + "source_path": "workplans/archived/260617-WARDEN-WP-0007-policy-gate-and-production-verify.md", + "uuid": "ea921d56-033b-4619-8032-61af7992e610", + "parent_id": "WARDEN-WP-0007", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0008", + "status": "finished", + "title": "Production SSH Path and Stewardship Closeout", + "source_path": "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "uuid": "a174963a-4ff1-4565-b19f-896cd4ff14a0", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0008-T01", + "status": "done", + "title": "T1 \u2014 Post-WP-0007 INTENT/SCOPE reassessment", + "source_path": "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "uuid": "05379da4-79d0-4742-8638-9e9565cccf72", + "parent_id": "WARDEN-WP-0008", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0008-T02", + "status": "done", + "title": "T2 \u2014 Production OpenBao end-to-end sign verification", + "source_path": "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "uuid": "b1a1831d-b2b3-4204-95f6-04dc7f29f67c", + "parent_id": "WARDEN-WP-0008", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0008-T03", + "status": "done", + "title": "T3 \u2014 State Hub task status canon migration", + "source_path": "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "uuid": "876827c4-4a86-4e58-9a1f-ac87045dc903", + "parent_id": "WARDEN-WP-0008", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0008-T04", + "status": "done", + "title": "T4 \u2014 Production config example and archive hygiene", + "source_path": "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "uuid": "75b9f366-3d7a-419d-98ad-bc10ab90a697", + "parent_id": "WARDEN-WP-0008", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0008-T05", + "status": "cancel", + "title": "T5 \u2014 flex-auth policy gate production readiness (coordination)", + "source_path": "workplans/archived/260618-WARDEN-WP-0008-production-ssh-path-and-stewardship-closeout.md", + "uuid": "03b412a5-5b99-42df-a154-733dd4156000", + "parent_id": "WARDEN-WP-0008", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0009", + "status": "archived", + "title": "flex-auth Policy Gate Production Readiness", + "source_path": "workplans/archived/260623-WARDEN-WP-0009-flex-auth-policy-gate-production.md", + "uuid": "9213b262-e2f5-480e-a5bc-56635d5eb4c9", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0009-T01", + "status": "done", + "title": "T1 \u2014 flex-auth policy package confirmation", + "source_path": "workplans/archived/260623-WARDEN-WP-0009-flex-auth-policy-gate-production.md", + "uuid": "f988ed2e-0f63-4e89-abc4-183a7f23ddc2", + "parent_id": "WARDEN-WP-0009", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0009-T02", + "status": "done", + "title": "T2 \u2014 Production enablement and smoke", + "source_path": "workplans/archived/260623-WARDEN-WP-0009-flex-auth-policy-gate-production.md", + "uuid": "9d0fabc2-10ef-426d-a3d2-d4970d377029", + "parent_id": "WARDEN-WP-0009", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0010", + "status": "archived", + "title": "Access Routing \u2014 Charter and Pointer Catalog", + "source_path": "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "uuid": "e93de9fd-0192-4d02-bb7c-5e859fb76b9b", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0010-T01", + "status": "done", + "title": "T1 \u2014 INTENT wording", + "source_path": "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "uuid": "589081a6-d1f5-47b4-bec0-e82d9c3444f4", + "parent_id": "WARDEN-WP-0010", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0010-T02", + "status": "done", + "title": "T2 \u2014 Routing-role wiki page", + "source_path": "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "uuid": "9ac333f7-5fc4-4fa2-82f3-d5ece8ff0d92", + "parent_id": "WARDEN-WP-0010", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0010-T03", + "status": "done", + "title": "T3 \u2014 Pointer catalog schema + seed", + "source_path": "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "uuid": "59e0f480-694a-482a-b35e-b7bc4930aa41", + "parent_id": "WARDEN-WP-0010", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0010-T04", + "status": "done", + "title": "T4 \u2014 Routing index in CredentialRouting.md", + "source_path": "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "uuid": "aabd28c0-db2d-4267-be98-95be272c687d", + "parent_id": "WARDEN-WP-0010", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0010-T05", + "status": "done", + "title": "T5 \u2014 Registry and repo-boundary alignment", + "source_path": "workplans/archived/260624-WARDEN-WP-0010-access-routing-charter.md", + "uuid": "3335a689-922c-4319-98d0-4263ab13790b", + "parent_id": "WARDEN-WP-0010", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0011", + "status": "archived", + "title": "Routing Lookup CLI", + "source_path": "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "uuid": "0a520f8e-01b4-48f1-9af3-2f3f69fd0672", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0011-T01", + "status": "done", + "title": "T1 \u2014 Catalog loader and models", + "source_path": "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "uuid": "55b8422c-ad3c-4084-9e00-acaa4c360906", + "parent_id": "WARDEN-WP-0011", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0011-T02", + "status": "done", + "title": "T2 \u2014 `warden route list` and `show`", + "source_path": "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "uuid": "60b679c5-79bd-4186-b5a6-ac576931f06c", + "parent_id": "WARDEN-WP-0011", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0011-T03", + "status": "done", + "title": "T3 \u2014 `warden route find`", + "source_path": "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "uuid": "d307701f-0117-44f0-80fd-ca6f7ae06f42", + "parent_id": "WARDEN-WP-0011", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0011-T04", + "status": "done", + "title": "T4 \u2014 Tests", + "source_path": "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "uuid": "00a76e0f-8ab6-4f9a-ac6a-00eae633342c", + "parent_id": "WARDEN-WP-0011", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0011-T05", + "status": "done", + "title": "T5 \u2014 Doc consistency + drift guard", + "source_path": "workplans/archived/260624-WARDEN-WP-0011-routing-guide-cli.md", + "uuid": "bf848375-eca7-4116-bb1d-fb7df6395c70", + "parent_id": "WARDEN-WP-0011", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0013", + "status": "archived", + "title": "Production Integration & Stewardship Closeout", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "4678c41a-c1d0-48cd-9988-4ea0380e8258", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0013-T01", + "status": "done", + "title": "T1 \u2014 Post-gap reassessment and SCOPE refresh", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "de46f9a2-bf11-4651-a23c-430c63f396c8", + "parent_id": "WARDEN-WP-0013", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0013-T02", + "status": "done", + "title": "T2 \u2014 Archive hygiene (WP-0010, WP-0011)", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "1b35321d-63ad-40da-a1aa-0b66190a0733", + "parent_id": "WARDEN-WP-0013", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0013-T03", + "status": "done", + "title": "T3 \u2014 ops-bridge cert_command migration playbook", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "ad8588b2-9ae9-4f94-bd77-8025851a38f5", + "parent_id": "WARDEN-WP-0013", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0013-T04", + "status": "done", + "title": "T4 \u2014 Operator OpenBao token hygiene runbook", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "5cb35829-32eb-4d59-97a1-f4d92ce8e239", + "parent_id": "WARDEN-WP-0013", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0013-T05", + "status": "done", + "title": "T5 \u2014 Principals inventory drift check", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "4025cd32-89f8-42c3-b1e8-eaf78497d91f", + "parent_id": "WARDEN-WP-0013", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0013-T06", + "status": "done", + "title": "T6 \u2014 Policy gate production enablement checklist", + "source_path": "workplans/archived/260624-WARDEN-WP-0013-production-integration-and-stewardship-closeout.md", + "uuid": "51663f65-79cb-4108-87c8-9721f9476259", + "parent_id": "WARDEN-WP-0013", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0012", + "status": "finished", + "title": "Routing Scenario Playbooks", + "source_path": "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "uuid": "a7e712a0-02f8-4f83-944e-6b207e77bc4c", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0012-T01", + "status": "done", + "title": "T1 \u2014 issue-core ingestion key playbook", + "source_path": "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "uuid": "830bb512-0288-4dba-9dd4-ccfd28a4921f", + "parent_id": "WARDEN-WP-0012", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0012-T02", + "status": "done", + "title": "T2 \u2014 Inter-Hub and bootstrap lanes", + "source_path": "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "uuid": "7726a703-6e00-4e49-9380-ed3fb3268827", + "parent_id": "WARDEN-WP-0012", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0012-T03", + "status": "done", + "title": "T3 \u2014 ops-bridge tunnel migration", + "source_path": "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "uuid": "9fb397f0-0abb-48f5-bb62-7e77edae93bb", + "parent_id": "WARDEN-WP-0012", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0012-T04", + "status": "done", + "title": "T4 \u2014 Platform secret scenarios (LLM, STS, DB)", + "source_path": "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "uuid": "edcf4ed7-f18d-4a92-a42d-8cc7ca0ab792", + "parent_id": "WARDEN-WP-0012", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0012-T05", + "status": "done", + "title": "T5 \u2014 Drift review cadence", + "source_path": "workplans/archived/260627-WARDEN-WP-0012-routing-scenario-playbooks.md", + "uuid": "db98d655-8551-487b-9413-41bf97fc06e1", + "parent_id": "WARDEN-WP-0012", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0014", + "status": "finished", + "title": "Operator Access Assist \u2014 warden access front door", + "source_path": "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "uuid": "3c30b2ed-6ede-4b95-a438-fde6da6f6633", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0014-T01", + "status": "done", + "title": "T1 \u2014 Catalog schema: structured handoff fields", + "source_path": "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "uuid": "abb0e722-6524-4224-8638-6ee1573ed3e0", + "parent_id": "WARDEN-WP-0014", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0014-T02", + "status": "done", + "title": "T2 \u2014 `warden access` advisory surface", + "source_path": "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "uuid": "c1497263-7124-459f-b63a-d0c0c7005c86", + "parent_id": "WARDEN-WP-0014", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0014-T03", + "status": "done", + "title": "T3 \u2014 OpenBao proxy lane (`--fetch` / `--exec`)", + "source_path": "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "uuid": "6d3eb0e4-309c-4065-893e-6c4053fb0db2", + "parent_id": "WARDEN-WP-0014", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0014-T04", + "status": "done", + "title": "T4 \u2014 key-cape / login orchestration lane", + "source_path": "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "uuid": "481997e4-193d-4724-84a6-61cbc2940153", + "parent_id": "WARDEN-WP-0014", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0014-T05", + "status": "done", + "title": "T5 \u2014 Docs, security model, and INTENT/SCOPE alignment", + "source_path": "workplans/archived/260627-WARDEN-WP-0014-operator-access-assist.md", + "uuid": "a5eb616e-4edf-42db-a4fb-bf296cdb92bc", + "parent_id": "WARDEN-WP-0014", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0015", + "status": "finished", + "title": "Workload Security Posture \u2014 env posture \u00d7 maturity + conformance", + "source_path": "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "uuid": "99f4a0e1-853c-456f-8aa7-8ff0f318ea65", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0015-T01", + "status": "done", + "title": "T1 \u2014 Author the two-axis Workload Security Posture standard (canon-bound)", + "source_path": "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "uuid": "85aeb676-a593-4056-986a-db14d4c5209f", + "parent_id": "WARDEN-WP-0015", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0015-T02", + "status": "done", + "title": "T2 \u2014 Machine-readable posture descriptors (both axes)", + "source_path": "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "uuid": "011fb0af-154d-40f4-a03e-3172c325321a", + "parent_id": "WARDEN-WP-0015", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0015-T03", + "status": "done", + "title": "T3 \u2014 Conformance checker (incl. secret-flow lattice)", + "source_path": "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "uuid": "c1a0e987-19d0-478e-ac08-2dbe98e64e09", + "parent_id": "WARDEN-WP-0015", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0015-T04", + "status": "done", + "title": "T4 \u2014 Dev-tier contract-double fixture library", + "source_path": "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "uuid": "e556fd2e-4e39-4c7d-bd94-b4330e4bef45", + "parent_id": "WARDEN-WP-0015", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0015-T05", + "status": "done", + "title": "T5 \u2014 INTENT/SCOPE alignment + canon contributions", + "source_path": "workplans/archived/260627-WARDEN-WP-0015-secret-lifecycle-tiering.md", + "uuid": "298c9b09-4a5a-41bf-a3bd-6c572385236b", + "parent_id": "WARDEN-WP-0015", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-ADHOC-2026-07-07", + "status": "finished", + "title": "Ad Hoc Tasks \u2014 2026-07-07", + "source_path": "workplans/archived/260707-ADHOC-2026-07-07.md", + "uuid": null, + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-ADHOC-2026-07-07-T01", + "status": "done", + "title": "T01 \u2014 Roll out proxy pipe fix (be3b4a2)", + "source_path": "workplans/archived/260707-ADHOC-2026-07-07.md", + "uuid": null, + "parent_id": "WARDEN-WP-ADHOC-2026-07-07", + "extra": {} + }, + { + "kind": "intake", + "id": "WARDEN-IN-0001", + "status": "open", + "title": "Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation", + "source_path": "intakes/intakes.md", + "uuid": null, + "parent_id": null, + "extra": { + "record": { + "id": "WARDEN-IN-0001", + "kind": "intake", + "title": "Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation", + "status": "open", + "origin": "cross-repo", + "origin_ref": "gate-house GH-DEC-2026-001", + "priority": "medium", + "owner": "ops-warden", + "requested_by": "gate-house", + "standard": "net-kingdom/canon/standards/security-layer-model_v0.1.md", + "description": "gate-house asks ops-warden to assent to three boundary items. (1) ops-warden is Staff, bound by the rule that Staff acts only through Engine APIs and never touches Tooling directly (standard section 5). (2) Doctrine versus runbook: the NetKingdom Security Literacy section in ops-warden INTENT is evidence the security curriculum had no owner; it now has one in gate-house. Proposal is that doctrine and curriculum move to gate-house and that section becomes lane-specific runbooks referencing gate-house doctrine rather than restating it. ops-warden keeps the lanes it stewards and everything operational about them. (3) The access lane/rule demarcation, normative in standard section 8: ops-warden and ops-mason own access lanes \u2014 how a worker reaches a host; access-engine owns access rules \u2014 whether they may. This demarcation is the condition attached to renaming flex-auth to access-engine, so ops-warden effectively holds a veto on that name. Also requested: add gate-house to the Security Literacy and routing tables \u2014 currently every plane is listed and gate-house appears nowhere \u2014 routing doctrine and authority-model questions there while continuing to route policy decisions to access-engine. If moving the curriculum out leaves ops-warden unable to instruct its own workers, say so; the boundary is wrong if it does.", + "created": "2026-08-28T19:30:28.087109Z", + "updated": "2026-08-28T19:30:28.087109Z" + } + } + } + ], + "events": [ + { + "type": "repo.command.applied", + "command": "repo.work.create_intake", + "operation": "create", + "correlation_id": "65d40cdd-5894-440e-9c95-c6bcfe259b66", + "kind": "intake", + "id": "WARDEN-IN-0001", + "git_sha": "467635e84b99757336ee49d7f0dbf107607d0560", + "files_touched": [ + "intakes/intakes.md" + ], + "source": "repo-manager", + "emitted_at": "2026-08-28T19:30:29.829125Z" + } + ] +} diff --git a/INTENT.md b/INTENT.md index caa3c68..d1c0a97 100644 --- a/INTENT.md +++ b/INTENT.md @@ -1,19 +1,26 @@ # INTENT -> **NetKingdom layering review — 2026-08-28.** This repository's role was reviewed -> against the NetKingdom IT-security layer model: **Taxonomy → Tooling → Engines → -> Staff**, layered by determinism and by the kind of artifact each layer produces. -> Findings and the argument behind them: -> `gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md`. -> The model is `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed), -> ratified by `gate-house/decisions/decisions.md` GH-DEC-2026-001. +> **Layer: Staff.** ops-warden is a Staff repository under the NetKingdom +> IT-security layer model (Taxonomy -> Tooling -> Engines -> Staff, layered by +> determinism and by the kind of artifact each layer produces): +> `net-kingdom/canon/standards/security-layer-model_v0.1.md`, owned by gate-house, +> ratified as `gate-house/decisions/decisions.md` GH-DEC-2026-001. > -> The layer rule that binds every repository: **Staff never touches tooling -> directly. It acts only through engine APIs.** +> **The binding rule: Staff never touches Tooling directly. It acts only through +> Engine APIs.** ops-warden holds no state another layer depends on at runtime and +> renders no authorization decision — it consumes them from access-engine. > -> **This repository is Staff — interactive, non-deterministic; operational stewardship.** Add the layer label and the Staff invariant. Two substantive changes. (1) The **"NetKingdom Security Literacy"** section is evidence that the security curriculum had no owner; it now has one. Doctrine and curriculum move to gate-house, and this section becomes lane-specific runbooks that reference gate-house doctrine rather than restating it. The boundary is **doctrine versus runbook**. (2) The literacy and routing tables should add gate-house — currently every plane is listed and gate-house appears nowhere — routing doctrine and authority-model questions there, while continuing to route policy decisions to access-engine. Record the lane/rule demarcation as in ops-mason. +> **Declared exception (standard section 5).** `src/warden/vault.py` (`VaultCA`) is a +> direct OpenBao client that performs a write, and `warden desk` shells `bao kv put` +> for paste-once provisioning. Both are non-conformant. Intended owner: +> **secrets-engine**; blocked on: no engine exposes an SSH certificate signing +> surface; reviewed quarterly. `src/warden/taint.py` reads KV metadata only, declared +> under the read-only observation allowance. `warden access --fetch/--exec/--wrap` +> runs the owner's tool under **the caller's** identity and supplies no authority of +> its own (`ADR-0002`). > -> *This note records what should change. The body below is not yet adapted.* +> Assent, reasoning, and the amendment offered back to gate-house: +> `history/2026-08-28-security-layer-model-assent.md` and `ADR-0010`. > This file captures **why this repository exists**, the **direction it is > moving toward**, and the **kind of system it is meant to become**. @@ -92,30 +99,51 @@ owns one lane and points at the rest: --- -## NetKingdom Security Literacy +## Lane routing — who owns which need -ops-warden should be fluent in the platform architecture documented in -`net-kingdom` — especially: +**This is a runbook, not doctrine.** Security doctrine, the authority model, and the +security curriculum are **gate-house's** (`security-layer-model_v0.1` section 8). +ops-warden references them and does not restate them. What follows is lane +stewardship: which subsystem owns which need, and what ops-warden does about it. -| Plane / component | Role in access | ops-warden relationship | -| --- | --- | --- | -| **key-cape / Keycloak** | Identity — who is the actor, MFA, IAM Profile claims | Instruct identity path; do not re-implement OIDC | -| **flex-auth + Topaz** | Authorization — may this actor perform this action | Caller-side policy gate shipped (opt-in); production flip is flex-auth's | -| **OpenBao** | Runtime secrets — API keys, dynamic creds, leases, audit | Instruct custody paths; SSH engine is signing backend only; proxy reads as caller when `exec_capable` | -| **secrets-engine** | Owner-native secret-exec (`secrets-engine exec`) | Route provisioned exec lanes (e.g. npm publish); ops-warden does not hold tokens | -| **railiance-platform** (credential broker) | Scoped lease grants (`credential exec`) | Route `warden-sign` token needs; ops-warden does not mint OpenBao tokens | -| **tenant-engine** | Tenant/client secret custody and front door | Route tenant lanes once fronted; ops-warden's current tenant proxies are interim (§9) | -| **user-engine** | End-user identity and account lifecycle | No ops-warden lane today; route rather than absorb | -| **ops-warden** | Operational SSH certificates — short-lived host access | **Own and issue** this lane | -| **ops-bridge** | Tunnel transport — consumes certs via `cert_command` | Primary consumer; document integration | -| **railiance-infra** | Host principals, force-command, SSH hardening | Instruct host-side deployment; do not own Ansible | -| **railiance-platform** (deploy) | OpenBao/K8s/platform service deployment | Instruct production endpoints; do not deploy clusters | +The machine-readable form is `registry/routing/catalog.yaml`, and the executable form +is `warden plan ""` / `warden route find`. Prefer either over this table — it is +orientation, and the catalog is the source of truth (`ADR-0001`). + +| Component | Layer | Owns | ops-warden relationship | +| --- | --- | --- | --- | +| **gate-house** | Staff | Security doctrine, invariants, authority ceilings, authority context, conformance review, curriculum | **Route doctrine and authority-model questions here.** Not policy decisions — those go to access-engine | +| **access-engine** (`flex-auth`) | Engine | **The policy decision** — whether an actor may act. The only decision point in NetKingdom | Consume decisions; caller-side pre-sign gate. ops-warden never renders or caches one | +| **key-cape / Keycloak** | Tooling | Identity — who the actor is, MFA, IAM Profile claims | Instruct the identity path; do not re-implement OIDC | +| **OpenBao** | Tooling | Runtime secrets — API keys, dynamic creds, leases, audit | Instruct custody paths; proxy reads as the caller when `exec_capable`. Direct client use is the declared exception above | +| **secrets-engine** | Engine | Credential abstraction, custody, lifecycle; owner-native exec | Route provisioned exec lanes (e.g. npm publish). **Intended owner of the SSH-CA surface** | +| **tenant-engine** | Engine | Tenant/client secret custody and front door | Route tenant lanes once fronted; current tenant proxies are interim (section 9) | +| **user-engine** | Engine | Users, accounts, memberships | No ops-warden lane today; route rather than absorb | +| **zone-engine** | Engine | Zone identity and membership | Consume compiled membership; ops-warden declares `z1-operational` (`ADR-0009`) | +| **railiance-platform** (broker) | — | Scoped lease grants (`credential exec`) | Route `warden-sign` token needs; ops-warden does not mint OpenBao tokens | +| **ops-mason** | Staff | Building and tearing down access routes and perimeters | Peer lane owner; same lane/rule demarcation applies | +| **ops-warden** | Staff | **Operational access lanes** — short-lived SSH certificates, routing, stewardship, runbooks | **Own and issue** the SSH lane | +| **ops-bridge** | Staff | Tunnel transport — consumes certs via `cert_command` | Primary consumer; document integration | +| **railiance-infra** | — | Host principals, force-command, SSH hardening | Instruct host-side deployment; do not own Ansible | +| **kings-guard** | Staff | Adaptive defence, observation, containment; publishes posture | Posture may reduce authority, never manufacture it | + +### Access lane versus access rule + +Normative, per `security-layer-model_v0.1` section 8 and assented to in `ADR-0010`: + +- **access lane** — ops-warden and ops-mason. *How* a worker reaches a host. +- **access rule** — access-engine. *Whether* they may. + +ops-warden owns the route and never the decision. A question about whether an actor +may do something is not an ops-warden question, however it arrives. Canonical references: +- `net-kingdom/canon/standards/security-layer-model_v0.1.md` (layers, section 5, section 8) - `net-kingdom/docs/platform-identity-security-architecture.md` - `net-kingdom/docs/responsibility-map.md` - `wiki/AccessManagementDirective.md` (ops SSH actor model) +- `.claude/rules/credential-routing.md` (agent-facing runbook — stays inline by design) --- @@ -138,7 +166,8 @@ Canonical references: | Need | Route to | | --- | --- | | OIDC login, MFA, human identity claims | key-cape / Keycloak (NetKingdom IAM Profile) | -| Policy decision — may actor X access resource Y | flex-auth | +| Security doctrine, invariants, authority model | gate-house | +| Policy decision — may actor X access resource Y | access-engine (`flex-auth`) | | API keys, provider secrets, DB creds, object-storage STS | OpenBao (+ flex-auth policy where required) | | Inter-Hub operator keys, LLM provider credentials | OpenBao or approved operator secret store | | Tunnel lifecycle, port forwarding | ops-bridge | diff --git a/SCOPE.md b/SCOPE.md index ae19ee4..46c9eec 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -156,6 +156,7 @@ superseding ADR, never an in-place edit. | `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade | | `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after | | `ADR-0009` | Adopt security-zones v0.1 and compile explicit workload membership; PEP failure mode is per zone | +| `ADR-0010` | ops-warden is Staff — it owns access lanes, never access rules; the direct OpenBao client is a declared engine gap, not an exemption | Rules we follow but do not own — NetKingdom canon, the IAM profile, the credential-management standard, the-custodian's ADR-001 workplan convention — are @@ -433,11 +434,12 @@ Downstream: `ops-bridge` (primary), kaizen agents, CI automations, human operato | Repo | Relationship | | --- | --- | +| `gate-house` | Owns security doctrine, invariants, authority context, and conformance review; ops-warden routes doctrine questions there and references rather than restates them (`ADR-0010`) | | `net-kingdom` | Canonical security architecture; ops-warden aligns to it | | `ops-bridge` | Primary cert_command consumer | | `railiance-infra` | Host-side SSH principals and hardening | | `railiance-platform` | OpenBao deployment and platform secrets | -| `flex-auth` | Authorization; policy package shipped (FLEX-WP-0006); runtime deploy FLEX-WP-0007 | +| `flex-auth` | Authorization — ruled name `access-engine`; the only policy decision point. Policy package shipped (FLEX-WP-0006); runtime deploy FLEX-WP-0007 | | `key-cape` | Identity / IAM Profile lightweight mode | | `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) | diff --git a/docs/adr/ADR-0010-ops-warden-is-staff.md b/docs/adr/ADR-0010-ops-warden-is-staff.md new file mode 100644 index 0000000..65e4ed2 --- /dev/null +++ b/docs/adr/ADR-0010-ops-warden-is-staff.md @@ -0,0 +1,109 @@ +--- +id: ops-warden-adr-0010 +type: adr +title: "ADR-0010 — ops-warden is Staff: lanes, not rules, and one declared engine gap" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-08-28" +updated: "2026-08-28" +last_reviewed: "2026-08-28" +review_interval: 3m +enforced_by: "INTENT.md layer declaration; docs/adr/ADR-0002; docs/adr/ADR-0003; docs/adr/ADR-0005; registry/routing/catalog.yaml delegation fields" +supersedes: "" +successor: "" +--- + +# ADR-0010 — ops-warden is Staff: lanes, not rules, and one declared engine gap + +## Status + +Accepted 2026-08-28, answering intake `WARDEN-IN-0001` from gate-house, which +carries decision `GH-DEC-2026-001`. The standard being adopted — +`net-kingdom/canon/standards/security-layer-model_v0.1.md` — is `proposed`, and was +proposed pending assent from flex-auth, kings-guard, and ops-warden. This ADR is +ops-warden's half of that assent. + +## Context + +The estate acquired overlapping claims to the same responsibility, most visibly two +repositories describing themselves as the authorization control plane. The layer +model resolves the overlap by layering repositories on determinism — Taxonomy, +Tooling, Engines, Staff — and by two rules: Staff never touches Tooling directly +(§5), and `access-engine` is the only policy decision point (§6). + +ops-warden is assigned Staff. Two demarcations follow that touch this repository: +the security curriculum it had been carrying belongs to gate-house, and the words +*access lane* and *access rule* are bound to different owners. + +Full reasoning: `history/2026-08-28-security-layer-model-assent.md`. + +## Decision + +**1. ops-warden is Staff and declares it.** `INTENT.md` carries the layer label and +the §5 invariant. ops-warden holds no state another layer depends on at runtime and +renders no authorization decision — it consumes them. + +**2. Lanes, not rules.** ops-warden owns *how* a worker reaches a host: SSH +certificate issuance, the routing catalog, `warden access`, `warden plan`, +`cert_command`. It never owns *whether* a worker may — that is `access-engine` +(today `flex-auth`), and ops-warden neither renders nor caches that decision. This +restates what `ADR-0002` and `ADR-0005` already bind; it is recorded here because +the demarcation is now normative estate-wide and other repositories rely on +ops-warden holding to it. The ruled rename `flex-auth` → `access-engine` is assented +to; ops-warden asks only for a window in which both names resolve. + +**3. Doctrine goes to gate-house; runbooks stay here.** ops-warden does not restate +security doctrine, the authority model, or the curriculum. It references +gate-house's. It keeps everything operational about the lanes it stewards: which +subsystem owns which need, how to obtain a credential lane by lane, and conformance +evidence for its own lanes. `.claude/rules/credential-routing.md` is runbook, not +curriculum, and stays inlined in this and every other repository. + +**4. One declared engine gap, not an exemption.** `src/warden/vault.py` (`VaultCA`) +is a direct OpenBao client performing a write from a Staff repository. It is a §5 +non-conformance. ops-warden declares it rather than arguing it away: + +- **intended owner:** `secrets-engine` (credential abstraction, custody, lifecycle) +- **blocked on:** no engine exposes an SSH certificate signing surface +- **review:** with this ADR, every 3 months + +Until that surface exists, ops-warden continues to sign — refusing to would remove +production host access to close a documentation gap — and reports the position as +open. `warden desk`'s `bao kv put` is declared on the same terms. `taint.py` is +metadata-only observation, declared under §5's read-only allowance. `proxy.py` +supplies no authority of its own: it runs the owner's tool under the caller's +identity and is governed by `ADR-0002`. + +This is `ADR-0003` turned inward. ops-warden has required an intended owner and a +blocker on 27 catalog lanes it holds for other repositories; it holds itself to the +same record. + +## Consequences + +ops-warden's conformance under §10 is *declared non-conformant with a tracked +closure path*, not clean. That is the accurate state and it is the state that gets +fixed, because it names an owner who can fix it. + +An amendment to §5 has been offered to gate-house — a second sanctioned shape +alongside read-only diagnostics: a declared engine gap carrying intended owner, +blocker, and review date, machine-readable so §10 can tell a tracked gap from an +undeclared violation. It is offered, not assumed; §5 stays gate-house's to write. If +gate-house declines it, ops-warden's position is a plain non-conformance and is +reported as one. + +The `NetKingdom Security Literacy` section stops being a prose second source for +`registry/routing/catalog.yaml`, which `ADR-0001` had already ruled against for +catalog procedure. + +## Related + +- `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed, gate-house) +- `gate-house/decisions/decisions.md` — `GH-DEC-2026-001` +- `history/2026-08-28-security-layer-model-assent.md` +- `ADR-0001`, `ADR-0002`, `ADR-0003`, `ADR-0005`, `ADR-0009` +- `WARDEN-IN-0001` diff --git a/docs/adr/README.md b/docs/adr/README.md index 9de6ae1..8678e25 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -71,3 +71,4 @@ and the publication is a defect. | `ADR-0007` | Build-stage permissiveness stops at credential disclosure | ops-warden | | `ADR-0008` | A lane's risk grade covers every field its path discloses | ops-warden | | `ADR-0009` | Adopt security-zones v0.1; compile explicit membership and select PEP failure mode per zone | ops-warden | +| `ADR-0010` | ops-warden is Staff: it owns access lanes, never access rules; the direct OpenBao client is a declared engine gap | ops-warden, and gate-house as the standard's owner | diff --git a/history/2026-08-28-security-layer-model-assent.md b/history/2026-08-28-security-layer-model-assent.md new file mode 100644 index 0000000..286b855 --- /dev/null +++ b/history/2026-08-28-security-layer-model-assent.md @@ -0,0 +1,171 @@ +# Security layer model — ops-warden's assent (WARDEN-IN-0001) + +**Date:** 2026-08-28 +**Intake:** `WARDEN-IN-0001` +**Requested by:** gate-house, ratified as `GH-DEC-2026-001` +**Standard:** `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed) +**Outcome:** assent to all three items; one declared non-conformance and one +proposed amendment to the standard. + +--- + +## What was asked + +gate-house asked ops-warden to assent to three boundary items: + +1. **ops-warden is Staff**, bound by §5 — Staff acts only through Engine APIs and + never holds a direct Tooling client. +2. **Doctrine versus runbook** — the security curriculum moves to gate-house; the + `NetKingdom Security Literacy` section in `INTENT.md` becomes lane-specific + runbooks that reference gate-house doctrine rather than restating it. +3. **The access lane / access rule demarcation** (§8) — ops-warden and ops-mason own + *lanes* (how a worker reaches a host); access-engine owns *rules* (whether they + may). This is the condition attached to renaming flex-auth to access-engine, so + ops-warden effectively holds a veto on that name. + +Plus: add gate-house to the literacy and routing tables, and say so if moving the +curriculum out leaves ops-warden unable to instruct its own workers. + +--- + +## Item 1 — Staff, and the §5 problem it exposes + +**Assent to the layer.** Staff is the right assignment and not a demotion. +ops-warden's artifacts are exactly what §3.4 describes: routing decisions, +workplans, runbooks, an audit trail. Its one production lane is non-deterministic +in the sense that matters — it is an operator front door, not a contract. + +**But §5 is violated today, and by the one lane ops-warden permanently owns.** +The rule is deliberately greppable, so grepping is the honest response: + +| Path | Tooling contact | Kind | Whose credential | +| --- | --- | --- | --- | +| `src/warden/vault.py` (`VaultCA.sign`) | `POST /v1//sign/` | **write** | broker-issued token held by ops-warden's process | +| `src/warden/desk.py` | `bao kv put` (paste-once provisioning) | **write** | founder's, at the desk | +| `src/warden/taint.py` | `bao kv metadata get` | read (metadata only, never data) | caller's | +| `src/warden/proxy.py` (`warden access --fetch/--exec/--wrap`) | catalog `fetch_command` | read | **the caller's own** | + +Two of these are not really ops-warden's clients. `proxy.py` runs the owner's tool +under the caller's identity and supplies no authority of its own — that is +`ADR-0002`, conduit not broker, and it is arguably outside §5's target. `taint.py` +reads metadata only, and fits §5's read-only-observation allowance once declared. + +**`VaultCA` does not have that defence.** It is a direct OpenBao client, in a Staff +repository, performing a write, presenting a token from its own environment. It is +production-verified and it is the SSH lane — the single thing ops-warden owns +permanently. Under §5 as written, adopting this standard puts ops-warden's core +lane in violation on the day it is adopted. + +The escape hatch §5 offers does not fit: it covers *read-only observation for +diagnostics*, and signing is a write. The route §5 prescribes does fit — + +> *A Staff repository needing a capability no engine exposes MUST raise that as an +> engine gap, not solve it locally.* + +— and no engine exposes SSH certificate signing. `secrets-engine` owns credential +abstraction, custody and lifecycle, which is the layer this belongs in, but it +fronts no SSH-CA API today. + +**So ops-warden assents and declares the non-conformance rather than negotiating an +exemption.** `VaultCA` is recorded in `INTENT.md` as a declared §5 exception with a +named intended owner (`secrets-engine`), a blocker (no SSH-CA engine surface), and a +review date. That is `ADR-0003` — cover gaps, never silently own them — applied to +ops-warden itself instead of to someone else's lane. + +### Proposed amendment to the standard + +§5 has exactly one shape for a Staff repository that legitimately touches Tooling: +read-only diagnostics. That shape is too narrow to describe the estate as it exists, +and a rule with no lane for a real, sanctioned case gets satisfied by relabelling +rather than by closing the gap. + +Recommend §5 gain a second shape: a **declared engine gap** — a Staff repository MAY +hold a Tooling client for a capability no engine exposes, provided it is declared in +`INTENT.md` with an intended owner, the blocker, and a review date, and provided the +declaration is machine-readable so the conformance check in §10 can distinguish a +tracked gap from an undeclared violation. + +ops-warden already runs this machinery for other repositories' lanes: 27 catalog +entries carry `delegation:` with `intended_owner` and `blocked_on`, and +`warden route gaps` lists them (WP-0030). It is offered, not imposed — the standard +is gate-house's. + +--- + +## Item 2 — Doctrine versus runbook + +**Assent.** The `NetKingdom Security Literacy` section is what gate-house says it is: +evidence that the curriculum had no owner, so it accreted in whatever `INTENT.md` +was open. That is the same failure `risk-nexus` names for findings and the same one +`ADR-0001` prevents for catalog procedure. ops-warden has argued this rule twice +against other repositories; it applies here. + +The boundary, drawn precisely: + +| Moves to gate-house | Stays with ops-warden | +| --- | --- | +| Why the planes are separated; the authority model | Which subsystem owns which credential need | +| What "posture", "zone", "authority ceiling" mean | How to obtain a cert, a lease, a login — per lane | +| The security curriculum a worker is taught | The runbook a worker executes | +| Doctrine a lane must conform to | Evidence of conformance for ops-warden's lanes | + +**gate-house's test question, answered: no, it does not leave ops-warden unable to +instruct its workers — and the reason is worth recording.** What actually instructs +an ops-warden worker is not the prose in `INTENT.md`. It is `warden plan ""`, +`warden route find`, and `.claude/rules/credential-routing.md`, which is inlined into +every repository's agent instructions precisely because credential routing is +high-frequency and high-risk. That surface is executable, lane-specific, and +unambiguously runbook. It does not depend on the literacy table, and moving doctrine +out does not weaken it. + +If anything the move improves it: the literacy table has been a second, prose copy of +what `registry/routing/catalog.yaml` states machine-readably, which is the +double-source failure `ADR-0001` exists to stop. + +**One thing must not move with it.** `.claude/rules/credential-routing.md` stays +inline in this repository and in every other. It is not doctrine and not a +curriculum; it is the anti-pattern list an agent needs *before* it acts, and a +reference to a document in another repository would not be read in time. + +--- + +## Item 3 — Access lane versus access rule + +**Assent, unconditionally, and the veto on `access-engine` is not exercised.** + +ops-warden is already built this way. `ADR-0005` implements one lane narrowly and +routes everything else; `ADR-0002` makes it a conduit that never decides; `ADR-0009` +has ops-warden compile membership attributes and apply a zone's failure mode while +flex-auth owns the stance. ops-warden consumes decisions; it has never rendered one. +The demarcation costs nothing because it describes what is already true. + +`access-engine` is also the better name. ops-warden's own routing table has had to +say "authorization" for the decision and "access" for the route for a year, and the +collision is visible in every playbook. + +**One operational condition, on execution rather than on the ruling.** The rename is +598 references across 82 files in this repository alone — catalog `owner:` fields, +`registry/flex-auth/`, `src/warden/policy.py`, the production registry snapshot +builder, playbooks, and the `.claude/rules/` files that other repositories inline. +Ops-warden asks for a deprecation window in which both names resolve, rather than a +flag day; ops-warden will do its own migration inside that window. This is a request +about sequencing, not a reservation about the name. + +--- + +## Item 4 — gate-house is missing from every table + +Correct, and fixed in this pass. gate-house is added to the literacy/routing table +in `INTENT.md` as the owner of doctrine, invariants, authority context, and +conformance review — with the routing rule stated explicitly: **doctrine and +authority-model questions go to gate-house; policy decisions continue to go to +access-engine.** Those are different questions and the distinction is the whole +point of §6. + +--- + +## Recorded as + +- `ADR-0010` — ops-warden is Staff; lanes not rules; the declared §5 exception +- `INTENT.md` — layer declaration, reworked routing table, gate-house row +- `WARDEN-IN-0001` — closed, outcome `assented` diff --git a/intakes/intakes.md b/intakes/intakes.md index a7f4472..2535d91 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -7,7 +7,8 @@ id: WARDEN-IN-0001 kind: intake title: 'Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation' -status: open +status: closed +outcome: assented origin: cross-repo origin_ref: gate-house GH-DEC-2026-001 priority: medium @@ -30,6 +31,24 @@ description: 'gate-house asks ops-warden to assent to three boundary items. (1) — routing doctrine and authority-model questions there while continuing to route policy decisions to access-engine. If moving the curriculum out leaves ops-warden unable to instruct its own workers, say so; the boundary is wrong if it does.' +notes: 'Assented to all three items in ADR-0010, with reasoning in + history/2026-08-28-security-layer-model-assent.md. (1) Staff accepted; the section 5 + binding rule exposed a real non-conformance — src/warden/vault.py is a direct + OpenBao client performing a write, as is warden desk''s bao kv put. Declared in + INTENT.md as an engine gap with intended owner secrets-engine and blocker "no engine + exposes an SSH-CA surface", not negotiated as an exemption; taint.py declared under + the read-only allowance; warden access proxies run under the caller''s identity. + An amendment is offered back to gate-house: a second sanctioned shape in section 5 for + a declared engine gap carrying intended owner, blocker and review date, machine-readable + so section 10 can tell a tracked gap from an undeclared violation. (2) Doctrine versus + runbook accepted; the literacy section is now a lane routing runbook referencing + gate-house doctrine. Answering gate-house''s test question: it does not leave ops-warden + unable to instruct its workers, because what instructs them is warden plan / warden route + and .claude/rules/credential-routing.md, which stays inline by design. (3) The lane/rule + demarcation assented unconditionally and the access-engine veto not exercised — ops-warden + already consumes decisions and renders none. One request on sequencing only: a deprecation + window in which both names resolve (598 references across 82 files here). gate-house added + to the routing tables in INTENT.md and SCOPE.md.' created: '2026-08-28T19:30:28.087109Z' -updated: '2026-08-28T19:30:28.087109Z' +updated: '2026-08-28T21:05:00Z' ``` From f7664005631cd9efcf36903953b45f910f7d79c2 Mon Sep 17 00:00:00 2001 From: repo-manager Date: Fri, 28 Aug 2026 21:52:03 +0200 Subject: [PATCH 08/57] chore(registrar): assign State Hub identifiers Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- WORK-RECORDS.md | 2 +- intakes/intakes.md | 1 + workplans/ADHOC-2026-06-27.md | 1 + workplans/ADHOC-2026-06-29.md | 1 + workplans/ADHOC-2026-08-11.md | 1 + workplans/ADHOC-2026-08-17.md | 1 + workplans/archived/260707-ADHOC-2026-07-07.md | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 9b8146f..8bbeb08 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -129,4 +129,4 @@ | task | WARDEN-WP-0033-T04 | wait | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| intake | WARDEN-IN-0001 | open | — | intakes/intakes.md | +| intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | diff --git a/intakes/intakes.md b/intakes/intakes.md index 2535d91..4a91201 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -51,4 +51,5 @@ notes: 'Assented to all three items in ADR-0010, with reasoning in to the routing tables in INTENT.md and SCOPE.md.' created: '2026-08-28T19:30:28.087109Z' updated: '2026-08-28T21:05:00Z' +state_hub_intake_id: "01a049ed-bbbc-7520-bc7c-6b0912ca534a" ``` diff --git a/workplans/ADHOC-2026-06-27.md b/workplans/ADHOC-2026-06-27.md index dfbe370..e7e0a23 100644 --- a/workplans/ADHOC-2026-06-27.md +++ b/workplans/ADHOC-2026-06-27.md @@ -9,6 +9,7 @@ owner: claude topic_slug: custodian created: "2026-06-27" updated: "2026-06-27" +state_hub_workstream_id: "a222c91f-3bb5-58a4-b6b2-f0fb18cdd5c3" --- # Ad Hoc Tasks — 2026-06-27 diff --git a/workplans/ADHOC-2026-06-29.md b/workplans/ADHOC-2026-06-29.md index 7346a3d..d078876 100644 --- a/workplans/ADHOC-2026-06-29.md +++ b/workplans/ADHOC-2026-06-29.md @@ -9,6 +9,7 @@ owner: claude topic_slug: custodian created: "2026-06-29" updated: "2026-06-29" +state_hub_workstream_id: "13fa845f-852e-55ec-a2a5-2296996e0216" --- # Ad Hoc Tasks — 2026-06-29 diff --git a/workplans/ADHOC-2026-08-11.md b/workplans/ADHOC-2026-08-11.md index a8734b0..a25c69f 100644 --- a/workplans/ADHOC-2026-08-11.md +++ b/workplans/ADHOC-2026-08-11.md @@ -9,6 +9,7 @@ owner: claude topic_slug: custodian created: "2026-08-11" updated: "2026-08-15" +state_hub_workstream_id: "9f99cc64-4682-5f20-b13e-89af2b6f7c70" --- # Ad Hoc Tasks — 2026-08-11 diff --git a/workplans/ADHOC-2026-08-17.md b/workplans/ADHOC-2026-08-17.md index 431ecd6..1b0b62f 100644 --- a/workplans/ADHOC-2026-08-17.md +++ b/workplans/ADHOC-2026-08-17.md @@ -9,6 +9,7 @@ owner: claude topic_slug: custodian created: "2026-08-17" updated: "2026-08-17" +state_hub_workstream_id: "5c6c2bbb-b944-5afd-b89c-20d865518849" --- # Ad Hoc Tasks — 2026-08-17 diff --git a/workplans/archived/260707-ADHOC-2026-07-07.md b/workplans/archived/260707-ADHOC-2026-07-07.md index aadfbbd..fc02506 100644 --- a/workplans/archived/260707-ADHOC-2026-07-07.md +++ b/workplans/archived/260707-ADHOC-2026-07-07.md @@ -9,6 +9,7 @@ owner: grok topic_slug: custodian created: "2026-07-07" updated: "2026-07-07" +state_hub_workstream_id: "90568b1e-8395-5c67-9c69-851ed08ff3d3" --- > **Terminology note:** Historical text in this archived workplan may use the legacy term "workstream". The fleet term is **workplan** (`canon/standards/workplan-terminology-fleet_v0.1.md`). From d7f4ebcfe0769dc704b37f5dbac14957ec826318 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 21:55:33 +0200 Subject: [PATCH 09/57] Re-emit the high-risk path artifact after the NetKingdom SSO lanes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit c374d41 added net-kingdom-lldap-bind-credential and net-kingdom-privacyidea-admin-token as `risk: high` and did not re-run the emitter, so registry/generated/high-risk-data-paths.yaml still described the catalog at 0fae090. railiance-platform consumes that file instead of hand-maintaining its deny list, and it has been reading a census two lanes short since 2026-08-23. This is precisely the drift WARDEN-WP-0033-T03 built the guard for — a lane graded high after the last emit silently failing to reach the consumer. The guard fired; nothing had acted on it. The deny list itself does not move: both lanes are blocked on their OpenBao path being published, so they land in `no_concrete_path` and concrete_path_count stays 14. What changes is the count the consumer sees — 23 high-risk lanes, two of which have no address yet. That is the honest signal and the reason the bucket is listed rather than omitted. check_agent_read_boundary.py still reports 0 uncovered. The workload-join census moves 9 -> 11 not-applicable: both lanes are provider/control-plane credentials rather than workload delivery lanes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- registry/generated/high-risk-data-paths.yaml | 10 ++++++---- tests/test_workload_join.py | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 6c2c341..a0b7ea0 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,12 +10,12 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-08-22T23:31:10Z" +generated_at: "2026-08-28T19:54:29Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "0fae0904ce8d8694338dd53a8a79abec5fec788d" -catalog_revision_date: "2026-08-23T01:31:05+02:00" +catalog_revision: "c374d41a4925720ae7706dfda020537b60a07eb9" +catalog_revision_date: "2026-08-23T21:43:12+02:00" catalog_dirty: false -high_risk_lane_count: 21 +high_risk_lane_count: 23 concrete_path_count: 14 # Graded high but not a single KV address -- a routing pattern, a broker @@ -23,6 +23,8 @@ concrete_path_count: 14 no_concrete_path: - database-dynamic-credentials - inter-hub-bootstrap-ssh + - net-kingdom-lldap-bind-credential + - net-kingdom-privacyidea-admin-token - object-storage-sts - openbao-api-key - openbao-platform-admin-login diff --git a/tests/test_workload_join.py b/tests/test_workload_join.py index 559a875..dab4df8 100644 --- a/tests/test_workload_join.py +++ b/tests/test_workload_join.py @@ -14,7 +14,9 @@ def test_repo_catalog_uses_only_explicit_workload_references(): assert report["ok"] is True assert len(report["resolved"]) == 3 assert len(report["unknown"]) == 17 - assert len(report["not_applicable"]) == 9 + # 11 since WARDEN-WP-0033: the two NetKingdom SSO lanes (c374d41) are + # provider/control-plane credentials, not workload delivery lanes. + assert len(report["not_applicable"]) == 11 assert {row["lane"] for row in report["resolved"]} == { "ops-warden-warden-sign-token", "issue-core-ingestion-api-key", From ee94c189385271194f22a53998b2c6e12e961ea9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 22:00:09 +0200 Subject: [PATCH 10/57] =?UTF-8?q?WARDEN-WP-0033=20finished=20=E2=80=94=20k?= =?UTF-8?q?ey-cape=20accepted=20the=20issuance=20question=20five=20days=20?= =?UTF-8?q?ago?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T04 was the last open task, waiting on key-cape to accept or refuse ownership of the coding-agent OpenBao issuance identity. They accepted, in KEY-WP-0009-T03, on 2026-08-23: codex-railiance-platform is published in their config/service-clients.example.yaml with subject service:codex:railiance-platform, role coding-agent, scope openbao:login, 15m lifetime, and the service-auth semantics in docs/openbao-service-auth-contract.md. The split is the one we routed for — KeyCape issues, railiance-platform binds the OpenBao role, OpenBao enforces, no secret value in either repo. We found it by reading their repository. KEY-WP-0009-T04 records replying to ops-warden; the inbox has zero messages from key-cape, read or unread. The task sat `wait` on an answer that already existed. That is T05's own lesson arriving on T04: a blocker is a claim about the world at a date. So the same pass re-verified the two lanes pointing at key-cape against their source instead of bumping dates: - rapp-qonto-keycape-client -> verified: source-read. KEY-WP-0009-T02 did add bounded service-auth, but that is client_credentials JWT issuance for OpenBao machine login and does not front this client_secret_basic exchange or its rotation. Blocker stands, now with evidence rather than memory. - key-cape-oidc-login -> asked of key-cape today, which the entry had recorded as still outstanding since 2026-08-21. Also cleared the inbox that hid this: 9 stale unread, all superseded by shipped work, with late closes sent to secrets-engine and llm-connect on the two threads that had asked ops-warden something and never got an answer. 391 tests pass, ruff clean, boundary coverage 0 uncovered. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- registry/generated/high-risk-data-paths.yaml | 4 +-- registry/routing/catalog.yaml | 10 +++--- .../WARDEN-WP-0033-native-lane-handoff.md | 33 +++++++++++++++++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index a0b7ea0..35d494b 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,11 +10,11 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-08-28T19:54:29Z" +generated_at: "2026-08-28T19:58:57Z" source: ops-warden/registry/routing/catalog.yaml catalog_revision: "c374d41a4925720ae7706dfda020537b60a07eb9" catalog_revision_date: "2026-08-23T21:43:12+02:00" -catalog_dirty: false +catalog_dirty: true high_risk_lane_count: 23 concrete_path_count: 14 diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 0ab246b..09f690e 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -281,8 +281,8 @@ entries: delegation: mode: interim intended_owner: key-cape - blocked_on: "REFUSED by secrets-engine 2026-08-21: login, MFA and identity-token issuance are key-cape/Keycloak's; secrets-engine may consume OIDC for OpenBao auth but does not own the login capability. ops-warden agrees — intended_owner corrected from secrets-engine to key-cape. Not yet asked of key-cape." - reviewed: "2026-08-21" + blocked_on: "REFUSED by secrets-engine 2026-08-21: login, MFA and identity-token issuance are key-cape/Keycloak's; secrets-engine may consume OIDC for OpenBao auth but does not own the login capability. ops-warden agrees — intended_owner corrected from secrets-engine to key-cape. Asked of key-cape 2026-08-28; KEY-WP-0009 accepting issuance ownership for machine identities is the precedent that makes it answerable." + reviewed: "2026-08-28" verified: asked-and-waiting # Login lane (WP-0014 T4) — interactive auth bootstrap, not a secret read. No # secret-read gate (you have no identity yet) and no caller-auth precheck (the @@ -710,9 +710,9 @@ entries: delegation: mode: interim intended_owner: key-cape - blocked_on: "client_secret_basic exchange is a key-cape protocol procedure, not a KV read; KEY-WP-0008 finished 2026-08-15 without a key-cape-native exchange/rotation command, and the workplan that would add one is not yet opened (key-cape acked interim 2026-08-15)" - reviewed: "2026-08-17" - verified: unverified + blocked_on: "client_secret_basic exchange is a key-cape protocol procedure, not a KV read; still no key-cape-native exchange/rotation command. Re-checked against key-cape source 2026-08-28: KEY-WP-0009 finished 2026-08-23 and did add bounded service-auth (per-client tokenLifetime, docs/openbao-service-auth-contract.md), but that is client_credentials JWT issuance for OpenBao machine login — it does not front this client_secret_basic exchange or its rotation. The server advertises client_secret_basic (src/internal/server/oidc/discovery.go) without exposing an owner command for it. Blocker stands." + reviewed: "2026-08-28" + verified: source-read risk: high workload_ref: applicability: applicable diff --git a/workplans/WARDEN-WP-0033-native-lane-handoff.md b/workplans/WARDEN-WP-0033-native-lane-handoff.md index 1e20015..55cd015 100644 --- a/workplans/WARDEN-WP-0033-native-lane-handoff.md +++ b/workplans/WARDEN-WP-0033-native-lane-handoff.md @@ -4,14 +4,14 @@ type: workplan title: "Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed" domain: infotech repo: ops-warden -status: active +status: finished owner: ops-warden topic_slug: netkingdom planning_priority: P1 depends_on_workplans: - WARDEN-WP-0030 created: "2026-08-21" -updated: "2026-08-23" +updated: "2026-08-28" state_hub_workstream_id: "4627d89b-4b00-562a-81e9-76e96f90fa7e" --- @@ -160,7 +160,7 @@ railiance-platform may deny more, deny less, or dispute a grade (`ADR-0002`). ```task id: WARDEN-WP-0033-T04 -status: wait +status: done priority: medium state_hub_task_id: "5acac140-a586-5db3-b231-bbf236710786" ``` @@ -212,6 +212,33 @@ accept that target identity with an authoritative workplan/interface, or refuse and name the actual owner. T04 remains `wait` until one of those two answers is recorded; the live AppRole is operational evidence, not an ownership answer. +**Answered — accepted. Closed 2026-08-28.** `key-cape` accepted issuance +ownership in `KEY-WP-0009-T03` (`status: done`, 2026-08-23): the non-secret static +registration `codex-railiance-platform` is published in +`key-cape/config/service-clients.example.yaml` — subject +`service:codex:railiance-platform`, tenant `tenant:coulomb`, role `coding-agent`, +scope `openbao:login`, `client_credentials`, 15-minute lifetime — with the +service-auth claims, renewal, expiry and failure semantics in +`key-cape/docs/openbao-service-auth-contract.md` (`T02`). The split is exactly the +one the routing asked for: KeyCape owns JWT issuance and client disablement, +railiance-platform owns the exact-bound OpenBao role and policy, OpenBao owns +enforcement, and no secret value enters either repository. + +**The answer was found by reading their repo, not by receiving it.** +`KEY-WP-0009-T04` records replying to ops-warden; no such message exists in the +ops-warden inbox — 0 messages from `key-cape`, read or unread. The acceptance had +been sitting done for five days while this task sat `wait` on it. + +That is the T05 lesson landing on T04: a blocker is a claim about the world at a +date, and this one had stopped being true. `verified: source-read` is doing real +work here — it is the difference between "we asked" and "we checked". The +mechanism is right; what it needs is for someone to run it, which is why the +lanes below were re-verified in the same pass rather than date-bumped. + +Consequence for `ADR-0004`: the honour-system `WARDEN_AGENT_ID` marker now has a +real issuance identity behind it on the OpenBao side. ops-warden remains a +consumer of that identity and does not own it. + ```task id: WARDEN-WP-0033-T05 status: done From 61c992923c3c99604eddcbe3653cbe8d92e29e37 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 22:01:47 +0200 Subject: [PATCH 11/57] WARDEN-WP-0027-T02: the owner gate closed five days ago MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit railiance-platform accepted ops-warden revision 0fae0904 on 2026-08-23, in RPF-WP-0017 (status: finished), together with railiance-infra approval at 186b030 and all five acceptance criteria met. T02 has been sitting `progress` on a gate that was already open. Verified rather than trusted: the receipt at bc1966da hashes to d2ba444ed16989590325697e69d25283dc75a9432c29a72e627e80bf9fd987e4, matching their record exactly. One reason it went unnoticed is an identifier mismatch — T02 cites the remediation interface as RAILIANCE-WP-0026-T01, but it is RPF-WP-0017-T01 in the owner repo, and the cited id resolves to an unrelated workplan there. Their acceptance is source acceptance only and authorizes no live drill, so T02 stays progress: what unblocks is preparing a NEW scenario, which needs a fresh human GO and is the platform owner s to execute. Surfaced, not taken. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- ...-WP-0027-credential-governance-lockdown.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/workplans/WARDEN-WP-0027-credential-governance-lockdown.md b/workplans/WARDEN-WP-0027-credential-governance-lockdown.md index 840f291..a111c31 100644 --- a/workplans/WARDEN-WP-0027-credential-governance-lockdown.md +++ b/workplans/WARDEN-WP-0027-credential-governance-lockdown.md @@ -222,6 +222,39 @@ test results, and infra acceptance. T02 remains `progress` until that owner gate is open; acceptance will permit preparation of a new scenario, not execution or reuse of the terminal one. +**Owner gate CLOSED — accepted 2026-08-23, found 2026-08-28.** +`railiance-platform` accepted the exact revision `0fae0904`. Recorded in their +`RPF-WP-0017-attended-login-output-containment.md` (`status: finished`), which +also records railiance-infra's independent approval at `186b030` and marks all +five acceptance criteria met. The receipt digest was verified here rather than +taken on trust: `docs/evidence/RAILIANCE-WP-0026-T01-ops-warden-receipt.json` at +ops-warden `bc1966da` hashes to +`d2ba444ed16989590325697e69d25283dc75a9432c29a72e627e80bf9fd987e4`, matching +their record exactly. + +Note the identifier: the remediation interface recorded above as +`RAILIANCE-WP-0026-T01` is `RPF-WP-0017-T01` in the owner's repo. Searching for +the cited id finds an unrelated workplan, which is part of why this sat unnoticed. + +**Their acceptance is source acceptance only and authorizes no live OIDC or +drill** — their words, and the boundary holds. So T02's state changes but its +`Done when` does not: what was blocked was *preparing a new scenario*, and that +is now permitted. The terminal NO-GO scenario and its receipts remain unusable. + +**Third instance of the same failure this session.** The acceptance existed for +five days; no message reached ops-warden. Identically, `key-cape` accepted the +WP-0033-T04 question on 2026-08-23 with no message, and nine unread messages +were sitting on already-superseded threads. The `verified:` field added by +WP-0033-T05 was built for exactly this and it works — what does not work is +waiting for a counterparty to tell you. Re-checking a blocker means reading the +owner's repository. + +**Remaining to close T02:** one attended production emergency seal/unseal drill, +requiring a new scenario id, fresh owner receipts from platform/infra/master, a +fully parameterized green preflight, and a new human GO. It is executed by the +platform owner, never by a coding agent. That is an operator decision, not an +agent one, so T02 stays `progress` and the decision is surfaced rather than taken. + ## Task: Tamper-evident policy governance + reconcile ```task From a45280f30da866254e3d00661bf875b6456466ea Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Fri, 28 Aug 2026 22:02:58 +0200 Subject: [PATCH 12/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-08-28: - update .custodian-brief.md for ops-warden Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- .custodian-brief.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index a0eb681..eb06359 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,29 +2,17 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-08-25 18:17 UTC +**Last synced:** 2026-08-28 20:02 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams -### Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed -Progress: 5/6 done | workplan_id: `4627d89b-4b00-562a-81e9-76e96f90fa7e` - -**Open tasks:** -- ! Tasks `5acac140` - ### Tamper-resistant credential governance + mass rotation/lockdown (Strand B) Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` **Open tasks:** - ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee` -## Inbox Hygiene - -**Stale unread:** 9 message(s) older than 3 day(s) — triage at session start. -**Missing thread_id:** 7 unread message(s) lack supersession chains. -- ! secrets-engine: Re: Need the warden-sign live apply/handoff (FLEX-WP-0007 T4) `80456912` - --- ## MCP Orientation (when available) From f289465b90fcdffe2c8bf4a51cc71607747126f9 Mon Sep 17 00:00:00 2001 From: repo-manager Date: Fri, 28 Aug 2026 22:40:24 +0200 Subject: [PATCH 13/57] repo.work.create_intake WARDEN-IN-0002 correlation_id: 676e92a9-dc2e-4101-a31c-a584962c25df reason: Propose layer model v0.3 for review source: repo-manager Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9 --- intakes/intakes.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/intakes/intakes.md b/intakes/intakes.md index 4a91201..192d793 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -53,3 +53,40 @@ created: '2026-08-28T19:30:28.087109Z' updated: '2026-08-28T21:05:00Z' state_hub_intake_id: "01a049ed-bbbc-7520-bc7c-6b0912ca534a" ``` + +## WARDEN-IN-0002 — Review requested: security layer model v0.3 — and does maturity-engine absorb warden route gaps? + +```yaml +id: WARDEN-IN-0002 +kind: intake +title: 'Review requested: security layer model v0.3 — and does maturity-engine absorb + warden route gaps?' +status: open +origin: cross-repo +origin_ref: net-kingdom security-layer-model_v0.3 +priority: medium +owner: ops-warden +requested_by: gate-house +description: 'v0.3 is proposed and changes sections 4, 9 and 13 only; the v0.2 assent + record stands. Two new engines: approval-engine (section 9.4) and maturity-engine + (section 9.5). THE QUESTION FOR YOU concerns section 5.3, which exists because you + offered the amendment. v0.3 gives declared gaps an owner: maturity-engine takes + the gap register with intended_owner, blocked_on and review dates, and section 13 + now says the register in the standard is interim and should not outlive that engine. + You offered warden route gaps and the 27 delegation catalog entries as reusable + prior art. So the question is whether that machinery should MOVE, be MIRRORED, or + STAY. Our tentative reading, which we want tested rather than accepted: routing + is yours and stays yours — warden route find answers where a credential need goes, + and that is lane knowledge, not maturity. What might move is the readiness half: + whether a declared gap is still within its review date, and whether an intended + owner has an engine surface yet. If splitting those creates two sources for one + fact, that is worse than either option and we would rather hear it now. Your SSH-CA + signing write would be tracked in maturity-engine as a declared gap with intended + owner secrets-engine and a review date — that is reporting your own non-conformance + to an engine, so we would rather you assent to it than discover it. Also note approval-engine + (section 9.4): it owns the approval object, not the approval workflow, so ops-warden + lanes needing approval consume a claim rather than implementing one. Assent, revision, + or rejection acceptable.' +created: '2026-08-28T20:40:24.957468Z' +updated: '2026-08-28T20:40:24.957468Z' +``` From 57d39ede0f6c85fde3f2d4f3eb0b08b5f671e385 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 28 Aug 2026 22:43:06 +0200 Subject: [PATCH 14/57] Refresh work-record index Regenerated by fix-consistency; adds the inbound v0.3 review intake. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2564823@bnt-lap001 Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9 --- WORK-RECORDS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 8bbeb08..5425d47 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -29,7 +29,7 @@ | workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md | | workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md | | workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md | -| workplan | WARDEN-WP-0033 | active | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | +| workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -126,7 +126,8 @@ | task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T02 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T03 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| task | WARDEN-WP-0033-T04 | wait | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | +| task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | +| intake | WARDEN-IN-0002 | open | — | intakes/intakes.md | From f815bb35caad1d157f5407576abaa02ffec02b2b Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 02:45:29 +0200 Subject: [PATCH 15/57] =?UTF-8?q?Implement=20=C2=A75.3=20machine-readably?= =?UTF-8?q?=20=E2=80=94=20layer.yaml,=20checker,=20conformance=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The security layer model moved v0.1 -> v0.4 (accepted) after ops-warden's assent. Both §5 asks from ADR-0010 were adopted: §5.2 now sanctions the conduit shape on the supplied-authority property, and §5.3 is the declared engine gap amendment, carrying the four fields verbatim and crediting ops-warden's delegation machinery as prior art. Which creates an obligation. §5.3 requires those fields MACHINE-READABLY, and §11 makes "every direct Tooling client maps to a declared §5.1/§5.2/§5.3 entry" a mechanical check. ops-warden's declaration was prose in INTENT.md — the repo that proposed the shape was not implementing it. layer.yaml is the map: 5 contacts (2 declared gaps, 1 read-only observation, 2 conduits) plus the non-Tooling clients recorded explicitly so the check is total rather than silently selective. scripts/check_layer_conformance.py enforces it and found three undeclared modules on its first run — all false positives (help text, a docstring, and the doubles library that SIMULATES bao rather than calling it), which is why the scan now matches invocation shapes instead of the word: an httpx call built against the configured OpenBao address, or an argv whose first element is the bao binary. tests/test_layer_conformance.py adds the §5.2 test the standard says SHOULD exist: _caller_env() returns the caller's environment unchanged, and proxy.py is asserted not to reference X-Vault-Token, approle login, or token create — a conduit that presents its own token is not a conduit. No assertion on review dates, deliberately: a date-triggered failure breaks the build on a calendar day with no code change, the same reasoning WP-0033-T05 recorded for blocker staleness. 398 tests pass, ruff clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- .repo-manager/index.json | 66 +++++--- INTENT.md | 16 +- layer.yaml | 123 ++++++++++++++ registry/generated/high-risk-data-paths.yaml | 8 +- scripts/check_layer_conformance.py | 164 +++++++++++++++++++ tests/test_layer_conformance.py | 98 +++++++++++ 6 files changed, 448 insertions(+), 27 deletions(-) create mode 100644 layer.yaml create mode 100644 scripts/check_layer_conformance.py create mode 100644 tests/test_layer_conformance.py diff --git a/.repo-manager/index.json b/.repo-manager/index.json index a0e6bd2..95e4806 100644 --- a/.repo-manager/index.json +++ b/.repo-manager/index.json @@ -1,10 +1,10 @@ { "schema": "repo_manager.index.v1", - "slug": "layer-model-assent", + "slug": "layer-model-v03-review", "repo_root": "/home/worsch/ops-warden", - "head_sha": "467635e84b99757336ee49d7f0dbf107607d0560", - "observed_at": "2026-08-28T19:30:29.828983Z", - "source_fingerprint": "7dfae4f3d33f6a9503a2c7326b925efb378613e772b39c4dd9b0640a35b85654", + "head_sha": "f289465b90fcdffe2c8bf4a51cc71607747126f9", + "observed_at": "2026-08-28T20:40:26.143458Z", + "source_fingerprint": "a9ae0d530804e039aae34b4902d8609723e9bba4385a501b9a4027911c3b6302", "source_files": [ ".repo-classification.yaml", "INTENT.md", @@ -55,7 +55,7 @@ "status": "finished", "title": "Ad Hoc Tasks \u2014 2026-06-27", "source_path": "workplans/ADHOC-2026-06-27.md", - "uuid": null, + "uuid": "a222c91f-3bb5-58a4-b6b2-f0fb18cdd5c3", "parent_id": null, "extra": {} }, @@ -75,7 +75,7 @@ "status": "finished", "title": "Ad Hoc Tasks \u2014 2026-06-29", "source_path": "workplans/ADHOC-2026-06-29.md", - "uuid": null, + "uuid": "13fa845f-852e-55ec-a2a5-2296996e0216", "parent_id": null, "extra": {} }, @@ -95,7 +95,7 @@ "status": "finished", "title": "Ad Hoc Tasks \u2014 2026-08-11", "source_path": "workplans/ADHOC-2026-08-11.md", - "uuid": null, + "uuid": "9f99cc64-4682-5f20-b13e-89af2b6f7c70", "parent_id": null, "extra": {} }, @@ -135,7 +135,7 @@ "status": "finished", "title": "Ad Hoc Tasks \u2014 2026-08-17", "source_path": "workplans/ADHOC-2026-08-17.md", - "uuid": null, + "uuid": "5c6c2bbb-b944-5afd-b89c-20d865518849", "parent_id": null, "extra": {} }, @@ -1192,7 +1192,7 @@ { "kind": "workplan", "id": "WARDEN-WP-0033", - "status": "active", + "status": "finished", "title": "Native lane handoff \u2014 review secrets-engine's catalog admission, and fix what it exposed", "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", "uuid": "4627d89b-4b00-562a-81e9-76e96f90fa7e", @@ -1232,7 +1232,7 @@ { "kind": "task", "id": "WARDEN-WP-0033-T04", - "status": "wait", + "status": "done", "title": "Tasks", "source_path": "workplans/WARDEN-WP-0033-native-lane-handoff.md", "uuid": "5acac140-a586-5db3-b231-bbf236710786", @@ -2155,7 +2155,7 @@ "status": "finished", "title": "Ad Hoc Tasks \u2014 2026-07-07", "source_path": "workplans/archived/260707-ADHOC-2026-07-07.md", - "uuid": null, + "uuid": "90568b1e-8395-5c67-9c69-851ed08ff3d3", "parent_id": null, "extra": {} }, @@ -2172,17 +2172,18 @@ { "kind": "intake", "id": "WARDEN-IN-0001", - "status": "open", + "status": "closed", "title": "Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation", "source_path": "intakes/intakes.md", - "uuid": null, + "uuid": "01a049ed-bbbc-7520-bc7c-6b0912ca534a", "parent_id": null, "extra": { "record": { "id": "WARDEN-IN-0001", "kind": "intake", "title": "Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation", - "status": "open", + "status": "closed", + "outcome": "assented", "origin": "cross-repo", "origin_ref": "gate-house GH-DEC-2026-001", "priority": "medium", @@ -2190,8 +2191,35 @@ "requested_by": "gate-house", "standard": "net-kingdom/canon/standards/security-layer-model_v0.1.md", "description": "gate-house asks ops-warden to assent to three boundary items. (1) ops-warden is Staff, bound by the rule that Staff acts only through Engine APIs and never touches Tooling directly (standard section 5). (2) Doctrine versus runbook: the NetKingdom Security Literacy section in ops-warden INTENT is evidence the security curriculum had no owner; it now has one in gate-house. Proposal is that doctrine and curriculum move to gate-house and that section becomes lane-specific runbooks referencing gate-house doctrine rather than restating it. ops-warden keeps the lanes it stewards and everything operational about them. (3) The access lane/rule demarcation, normative in standard section 8: ops-warden and ops-mason own access lanes \u2014 how a worker reaches a host; access-engine owns access rules \u2014 whether they may. This demarcation is the condition attached to renaming flex-auth to access-engine, so ops-warden effectively holds a veto on that name. Also requested: add gate-house to the Security Literacy and routing tables \u2014 currently every plane is listed and gate-house appears nowhere \u2014 routing doctrine and authority-model questions there while continuing to route policy decisions to access-engine. If moving the curriculum out leaves ops-warden unable to instruct its own workers, say so; the boundary is wrong if it does.", + "notes": "Assented to all three items in ADR-0010, with reasoning in history/2026-08-28-security-layer-model-assent.md. (1) Staff accepted; the section 5 binding rule exposed a real non-conformance \u2014 src/warden/vault.py is a direct OpenBao client performing a write, as is warden desk's bao kv put. Declared in INTENT.md as an engine gap with intended owner secrets-engine and blocker \"no engine exposes an SSH-CA surface\", not negotiated as an exemption; taint.py declared under the read-only allowance; warden access proxies run under the caller's identity. An amendment is offered back to gate-house: a second sanctioned shape in section 5 for a declared engine gap carrying intended owner, blocker and review date, machine-readable so section 10 can tell a tracked gap from an undeclared violation. (2) Doctrine versus runbook accepted; the literacy section is now a lane routing runbook referencing gate-house doctrine. Answering gate-house's test question: it does not leave ops-warden unable to instruct its workers, because what instructs them is warden plan / warden route and .claude/rules/credential-routing.md, which stays inline by design. (3) The lane/rule demarcation assented unconditionally and the access-engine veto not exercised \u2014 ops-warden already consumes decisions and renders none. One request on sequencing only: a deprecation window in which both names resolve (598 references across 82 files here). gate-house added to the routing tables in INTENT.md and SCOPE.md.", "created": "2026-08-28T19:30:28.087109Z", - "updated": "2026-08-28T19:30:28.087109Z" + "updated": "2026-08-28T21:05:00Z", + "state_hub_intake_id": "01a049ed-bbbc-7520-bc7c-6b0912ca534a" + } + } + }, + { + "kind": "intake", + "id": "WARDEN-IN-0002", + "status": "open", + "title": "Review requested: security layer model v0.3 \u2014 and does maturity-engine absorb warden route gaps?", + "source_path": "intakes/intakes.md", + "uuid": null, + "parent_id": null, + "extra": { + "record": { + "id": "WARDEN-IN-0002", + "kind": "intake", + "title": "Review requested: security layer model v0.3 \u2014 and does maturity-engine absorb warden route gaps?", + "status": "open", + "origin": "cross-repo", + "origin_ref": "net-kingdom security-layer-model_v0.3", + "priority": "medium", + "owner": "ops-warden", + "requested_by": "gate-house", + "description": "v0.3 is proposed and changes sections 4, 9 and 13 only; the v0.2 assent record stands. Two new engines: approval-engine (section 9.4) and maturity-engine (section 9.5). THE QUESTION FOR YOU concerns section 5.3, which exists because you offered the amendment. v0.3 gives declared gaps an owner: maturity-engine takes the gap register with intended_owner, blocked_on and review dates, and section 13 now says the register in the standard is interim and should not outlive that engine. You offered warden route gaps and the 27 delegation catalog entries as reusable prior art. So the question is whether that machinery should MOVE, be MIRRORED, or STAY. Our tentative reading, which we want tested rather than accepted: routing is yours and stays yours \u2014 warden route find answers where a credential need goes, and that is lane knowledge, not maturity. What might move is the readiness half: whether a declared gap is still within its review date, and whether an intended owner has an engine surface yet. If splitting those creates two sources for one fact, that is worse than either option and we would rather hear it now. Your SSH-CA signing write would be tracked in maturity-engine as a declared gap with intended owner secrets-engine and a review date \u2014 that is reporting your own non-conformance to an engine, so we would rather you assent to it than discover it. Also note approval-engine (section 9.4): it owns the approval object, not the approval workflow, so ops-warden lanes needing approval consume a claim rather than implementing one. Assent, revision, or rejection acceptable.", + "created": "2026-08-28T20:40:24.957468Z", + "updated": "2026-08-28T20:40:24.957468Z" } } } @@ -2201,15 +2229,15 @@ "type": "repo.command.applied", "command": "repo.work.create_intake", "operation": "create", - "correlation_id": "65d40cdd-5894-440e-9c95-c6bcfe259b66", + "correlation_id": "676e92a9-dc2e-4101-a31c-a584962c25df", "kind": "intake", - "id": "WARDEN-IN-0001", - "git_sha": "467635e84b99757336ee49d7f0dbf107607d0560", + "id": "WARDEN-IN-0002", + "git_sha": "f289465b90fcdffe2c8bf4a51cc71607747126f9", "files_touched": [ "intakes/intakes.md" ], "source": "repo-manager", - "emitted_at": "2026-08-28T19:30:29.829125Z" + "emitted_at": "2026-08-28T20:40:26.143617Z" } ] } diff --git a/INTENT.md b/INTENT.md index d1c0a97..86254a7 100644 --- a/INTENT.md +++ b/INTENT.md @@ -3,8 +3,9 @@ > **Layer: Staff.** ops-warden is a Staff repository under the NetKingdom > IT-security layer model (Taxonomy -> Tooling -> Engines -> Staff, layered by > determinism and by the kind of artifact each layer produces): -> `net-kingdom/canon/standards/security-layer-model_v0.1.md`, owned by gate-house, -> ratified as `gate-house/decisions/decisions.md` GH-DEC-2026-001. +> `net-kingdom/canon/standards/security-layer-model_v0.4.md` (**accepted**), owned by +> gate-house, ratified as `gate-house/decisions/decisions.md` GH-DEC-2026-001. +> ops-warden's assent is recorded there as `ADR-0010`. > > **The binding rule: Staff never touches Tooling directly. It acts only through > Engine APIs.** ops-warden holds no state another layer depends on at runtime and @@ -19,8 +20,15 @@ > runs the owner's tool under **the caller's** identity and supplies no authority of > its own (`ADR-0002`). > -> Assent, reasoning, and the amendment offered back to gate-house: -> `history/2026-08-28-security-layer-model-assent.md` and `ADR-0010`. +> **The declaration is `layer.yaml`, not this note.** v0.4 §5.3 requires a gap to +> carry `capability`, `intended_owner`, `blocked_on` and `review` *machine-readably*, +> and §11 makes "every direct Tooling client maps to a declared §5.1/§5.2/§5.3 entry" +> a mechanical check. `layer.yaml` is that map; `scripts/check_layer_conformance.py` +> and `tests/test_layer_conformance.py` enforce it, so a new undeclared OpenBao +> client fails on the commit that introduces it. +> +> Assent, reasoning, and the amendment offered back to gate-house — adopted as +> v0.4 §5.3: `history/2026-08-28-security-layer-model-assent.md` and `ADR-0010`. > This file captures **why this repository exists**, the **direction it is > moving toward**, and the **kind of system it is meant to become**. diff --git a/layer.yaml b/layer.yaml new file mode 100644 index 0000000..23d099a --- /dev/null +++ b/layer.yaml @@ -0,0 +1,123 @@ +# ops-warden — NetKingdom security layer declaration +# +# Framework: net-kingdom/canon/standards/security-layer-model_v0.4.md +# Assent: docs/adr/ADR-0010 (ops-warden's own voice, per §11 "who must declare") +# Validate: python3 scripts/check_layer_conformance.py +# +# §11 makes one check mechanical: "every direct Tooling client in a Staff +# repository maps to a declared §5.1, §5.2, or §5.3 entry". This file is that +# map. It is machine-readable because §5.3 requires it to be — ops-warden +# proposed that shape and is implementing it rather than declaring in prose. +# +# Conformance rule inherited from tenancy.yaml: accuracy, not altitude. A +# declared gap is TRACKED NON-CONFORMANCE (§11), never a claim of conformance. + +schema_version: "0.1" +framework: netkingdom-security-layer-model +standard_version: "0.4" +repository: ops-warden +layer: staff +declared_by: docs/adr/ADR-0010 +declared_at: "2026-08-29" + +# Every direct contact with a Tooling-layer system (§4), one entry each. +tooling_contacts: + + - id: ssh-ca-signing-write + shape: "5.3" # declared engine gap + module: src/warden/vault.py + symbol: VaultCA.sign + tooling: OpenBao + operation: "HTTP POST /v1//sign/ with X-Vault-Token" + write: true + capability: "Sign a short-lived SSH certificate for an adm/agt/atm actor" + intended_owner: secrets-engine + blocked_on: >- + No engine exposes an SSH certificate signing surface. secrets-engine owns + credential abstraction, custody and lifecycle, which is the layer this + belongs in, but fronts no SSH-CA API today. + review: "2026-11-28" + note: >- + Production-verified and in daily use. This is the one lane ops-warden owns + permanently (§4). Signing continues while the gap is open: refusing would + remove production host access to close a documentation gap. + + - id: desk-paste-once-provision + shape: "5.3" + module: src/warden/desk.py + symbol: _provision_to_openbao + tooling: OpenBao + operation: "bao kv put =- (value on stdin, never argv)" + write: true + capability: "Founder paste-once provisioning of a secret straight into OpenBao" + intended_owner: secrets-engine + blocked_on: >- + No engine exposes an attended provisioning surface for a value the founder + holds and no automated path can produce. + review: "2026-11-28" + note: >- + Attended and founder-operated (WP-0029). The value reaches OpenBao without + passing through a terminal, an argv, or the audit log. + + - id: taint-metadata-read + shape: "5.1" # read-only diagnostic observation + module: src/warden/taint.py + symbol: fetch_taint_status + tooling: OpenBao + operation: "bao kv metadata get -format=json " + write: false + capability: "Report EXPOSED taint (custom_metadata) without reading secret data" + intended_owner: secrets-engine + blocked_on: >- + No engine exposes a disclosure-taint query. Metadata-only by construction — + reading the data would be the 2026-07-16 vector this exists to avoid. + review: "2026-11-28" + + - id: access-proxy-conduit + shape: "5.2" # conduit + module: src/warden/proxy.py + symbol: proxy_fetch, proxy_attended_login_exec + tooling: OpenBao, key-cape + operation: "Runs the catalog-declared owner fetch_command as a child process" + write: false + capability: "warden access --fetch/--exec/--out/--wrap for exec_capable lanes" + supplied_authority: none + evidence: + no_own_credential: src/warden/proxy.py::_caller_env + test: tests/test_proxy.py::test_conduit_supplies_no_authority_of_its_own + audit: "audit.jsonl records the caller, the lane, and the outcome; never a value" + note: >- + The §5.2 test is the supplied-authority property: ops-warden presents no + credential of its own, cannot widen what the caller could already do, and + the action reconstructs as the caller's. Governed by ADR-0002. + + - id: caller-identity-token + shape: "5.2" + module: src/warden/caller_identity.py + symbol: resolve_caller_token + tooling: OpenBao + operation: "Runs the operator-configured caller_auth command, or reads token env" + write: false + capability: "Establish the caller's own identity for the pre-sign policy gate" + supplied_authority: none + detection: voluntary # runs an operator-configured command, so no fixed + # argv shape to scan for; declared rather than omitted + note: >- + Obtains the CALLER's credential by the operator's configured means; adds no + authority. Never mints, and never persists what it resolves. + +# Contacts that are deliberately NOT Tooling contacts, recorded so the check is +# total rather than silently selective. +non_tooling_clients: + + - module: src/warden/policy.py + target: access-engine (flex-auth) + rationale: "Engine API — §5 permits it; this is the shape §5 prescribes." + + - module: src/warden/worker.py + target: state-hub, llm-connect + rationale: >- + Not catalogued in §4. The layer catalog scopes the security estate, and + neither the State Hub nor llm-connect appears in it, so no §5 shape applies + on the standard's own terms. Raised with gate-house 2026-08-29 as a scope + question rather than resolved unilaterally — see the assessment note. diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 35d494b..78d3ce6 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,11 +10,11 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-08-28T19:58:57Z" +generated_at: "2026-08-29T00:44:23Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "c374d41a4925720ae7706dfda020537b60a07eb9" -catalog_revision_date: "2026-08-23T21:43:12+02:00" -catalog_dirty: true +catalog_revision: "ee94c189385271194f22a53998b2c6e12e961ea9" +catalog_revision_date: "2026-08-28T22:00:09+02:00" +catalog_dirty: false high_risk_lane_count: 23 concrete_path_count: 14 diff --git a/scripts/check_layer_conformance.py b/scripts/check_layer_conformance.py new file mode 100644 index 0000000..5313a34 --- /dev/null +++ b/scripts/check_layer_conformance.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Check ops-warden against the NetKingdom security layer model (§5, §11). + +Read-only. Makes §11's second mechanical check real: + + every direct Tooling client in a Staff repository maps to a declared + §5.1, §5.2, or §5.3 entry + +The failure this catches is a *new* direct OpenBao contact appearing in +src/warden/ without an entry in layer.yaml — an undeclared violation (§11), +which is a finding rather than a tracked gap. It deliberately does NOT check +the review dates: a date-triggered failure breaks the build on a calendar day +with no code change (the reasoning recorded in WARDEN-WP-0033-T05), so +staleness is reported and left to `--report`, never to CI. + +Exit 0 clean, 1 undeclared contact found, 2 declaration malformed. +""" +from __future__ import annotations + +import argparse +import re +import sys +from datetime import date +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / "src" / "warden" +DECL = ROOT / "layer.yaml" + +VALID_SHAPES = {"5.1", "5.2", "5.3"} + +# A direct Tooling contact is an *invocation*, not a mention. Matching the word +# "bao" caught help text, a docstring, and the dev-tier doubles library that +# simulates bao rather than calling it — three false positives on first run. +# So match the two shapes that actually execute: +# 1. an HTTP request built against the OpenBao address +# 2. an argv list whose first element is the bao binary +TOOLING_PATTERNS = ( + # httpx call whose URL is built from the configured OpenBao/Vault address + re.compile(r"""\bhttpx\.\w+\(|url\s*=\s*f?["'].*\{self\._cfg\.addr\}"""), + # argv construction: [bao_bin, ...] / ["bao", ...] / [bao_binary, ...] + re.compile(r"""\[\s*(?:["']bao["']|bao_bin\b|bao_binary\b)\s*,"""), +) + +# httpx alone is not a Tooling contact — policy.py calls an Engine and worker.py +# calls the State Hub. A module matching only the httpx pattern counts as a +# contact only if it also references the OpenBao address configuration. +ADDR_HINT = re.compile(r"""_cfg\.addr|VAULT_ADDR|BAO_ADDR""") + +# Modules that talk to an Engine or to something outside the §4 catalog. Listed +# in layer.yaml under non_tooling_clients and excluded from the scan with it. +def _excluded(decl: dict) -> set[str]: + return {e["module"].split("/")[-1] for e in decl.get("non_tooling_clients", [])} + + +def load_declaration() -> dict: + if not DECL.exists(): + print(f"MISSING: {DECL} — ops-warden must declare in its own voice (§11)") + raise SystemExit(2) + decl = yaml.safe_load(DECL.read_text()) + for key in ("layer", "repository", "standard_version", "tooling_contacts"): + if key not in decl: + print(f"MALFORMED: layer.yaml has no {key!r}") + raise SystemExit(2) + for c in decl["tooling_contacts"]: + if c.get("shape") not in VALID_SHAPES: + print(f"MALFORMED: {c.get('id')} has shape {c.get('shape')!r}, not one of {sorted(VALID_SHAPES)}") + raise SystemExit(2) + # §5.3 carries four fields, machine-readably. That is the whole point of + # the shape; a gap missing them is prose wearing a schema. + if c["shape"] == "5.3": + for field in ("capability", "intended_owner", "blocked_on", "review"): + if not c.get(field): + print(f"MALFORMED: §5.3 entry {c['id']!r} is missing {field!r}") + raise SystemExit(2) + # §5.2's test is the supplied-authority property. + if c["shape"] == "5.2" and c.get("supplied_authority") != "none": + print(f"MALFORMED: §5.2 conduit {c['id']!r} must declare supplied_authority: none") + raise SystemExit(2) + return decl + + +def scan_modules() -> dict[str, list[int]]: + """Return {module_name: [line numbers]} for direct Tooling contacts.""" + found: dict[str, list[int]] = {} + for path in sorted(SRC.rglob("*.py")): + if path.name.startswith("test_"): + continue + text = path.read_text() + hits: list[int] = [] + for n, line in enumerate(text.splitlines(), 1): + stripped = line.strip() + if stripped.startswith("#") or stripped.startswith('"'): + continue + if any(p.search(line) for p in TOOLING_PATTERNS): + hits.append(n) + if hits: + # An httpx-only match needs the OpenBao address to be a Tooling + # contact; otherwise it is an Engine or non-catalogued call. + argv_shape = any(TOOLING_PATTERNS[1].search(ln) for ln in text.splitlines()) + if argv_shape or ADDR_HINT.search(text): + found[path.name] = hits + return found + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--report", action="store_true", help="also print the declaration and gap review dates") + args = ap.parse_args() + + decl = load_declaration() + declared = {c["module"].split("/")[-1] for c in decl["tooling_contacts"]} + excluded = _excluded(decl) + found = scan_modules() + + undeclared = {m: lines for m, lines in found.items() if m not in declared and m not in excluded} + # A voluntary declaration has no fixed argv shape to detect (an + # operator-configured command). Over-declaring is safe; not reporting it as + # stale keeps the signal meaningful. + voluntary = { + c["module"].split("/")[-1] + for c in decl["tooling_contacts"] + if c.get("detection") == "voluntary" + } + stale_decls = declared - set(found) - voluntary + + if args.report: + print(f"{decl['repository']} — layer: {decl['layer']} (model v{decl['standard_version']})") + print(f"declared by {decl['declared_by']}\n") + for c in decl["tooling_contacts"]: + line = f" §{c['shape']} {c['id']:<28} {c['module']}" + if c["shape"] == "5.3": + overdue = str(c["review"]) < date.today().isoformat() + line += f" -> {c['intended_owner']} review {c['review']}" + if overdue: + line += " [REVIEW OVERDUE]" + print(line) + gaps = [c for c in decl["tooling_contacts"] if c["shape"] == "5.3"] + print(f"\n{len(gaps)} declared gap(s) — tracked non-conformance, not conformance (§11).") + + ok = True + if undeclared: + ok = False + print("\nUNDECLARED TOOLING CONTACT — a finding under §11, not a tracked gap:") + for m, lines in sorted(undeclared.items()): + print(f" src/warden/{m}: line(s) {', '.join(map(str, lines[:6]))}") + print("\nAdd a §5.1/§5.2/§5.3 entry to layer.yaml, or route it through an engine.") + + if stale_decls: + print("\nNote: declared but no contact found (module removed or refactored?):") + for m in sorted(stale_decls): + print(f" {m}") + + if ok and not args.report: + print(f"PASS — {len(found)} module(s) with Tooling contact, all declared.") + elif ok: + print("\nPASS — every direct Tooling contact maps to a declared shape.") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py new file mode 100644 index 0000000..86b39a0 --- /dev/null +++ b/tests/test_layer_conformance.py @@ -0,0 +1,98 @@ +"""Layer-model conformance (security-layer-model_v0.4 §5, §11). + +Two things are checked here. §11 makes one of them mechanical: every direct +Tooling client maps to a declared shape. §5.2 asks for the other: the conduit's +supplied-authority property covered by a test. + +Deliberately absent: any assertion on a §5.3 review date. A date-triggered +failure breaks the build on a calendar day with no code change, punishing +whoever commits next rather than whoever owns the gap — the same reasoning +recorded in WARDEN-WP-0033-T05 for blocker staleness. +""" +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[1] + + +def _decl() -> dict: + return yaml.safe_load((ROOT / "layer.yaml").read_text()) + + +class TestDeclaration: + def test_declares_staff_layer_in_its_own_voice(self): + d = _decl() + assert d["repository"] == "ops-warden" + assert d["layer"] == "staff" + # §11: "only the repository's own file, in its own voice, conforms." + assert d["declared_by"] == "docs/adr/ADR-0010" + + def test_every_tooling_contact_maps_to_a_declared_shape(self): + """§11 mechanical check — the guard against a new undeclared client.""" + result = subprocess.run( + [sys.executable, str(ROOT / "scripts" / "check_layer_conformance.py")], + capture_output=True, + text=True, + ) + assert result.returncode == 0, ( + f"undeclared Tooling contact — a finding under §11, not a tracked gap:\n" + f"{result.stdout}{result.stderr}" + ) + + def test_declared_gaps_carry_all_four_fields(self): + """§5.3 is machine-readable or it is prose wearing a schema.""" + for c in _decl()["tooling_contacts"]: + if c["shape"] == "5.3": + for field in ("capability", "intended_owner", "blocked_on", "review"): + assert c.get(field), f"{c['id']} missing {field}" + + def test_gaps_are_not_counted_as_conformance(self): + """§11: a declared gap is tracked non-conformance. Keep that visible.""" + text = (ROOT / "layer.yaml").read_text() + assert "TRACKED NON-CONFORMANCE" in text.upper() + + +class TestConduitSuppliesNoAuthority: + """§5.2: 'MUST NOT present its own credential, MUST NOT widen what the + caller could already do.' The standard says this SHOULD be covered by a + test; this is that test.""" + + def test_conduit_supplies_no_authority_of_its_own(self, monkeypatch): + from warden import proxy + + monkeypatch.setenv("VAULT_TOKEN", "caller-own-token") + monkeypatch.setenv("HOME", "/home/nobody") + before = dict(os.environ) + + env = proxy._caller_env() + + # The child environment IS the caller's environment — nothing added, + # nothing removed, no ops-warden credential injected. + assert env == before, ( + "conduit altered the caller's environment; §5.2 requires it to " + "supply no authority of its own" + ) + assert env["VAULT_TOKEN"] == "caller-own-token" + + def test_conduit_declares_supplied_authority_none(self): + conduits = [c for c in _decl()["tooling_contacts"] if c["shape"] == "5.2"] + assert conduits, "no §5.2 conduit declared — proxy.py is one" + for c in conduits: + assert c["supplied_authority"] == "none" + + def test_proxy_holds_no_credential_constant(self): + """A conduit that presents its own token is not a conduit (§5.2).""" + src = (ROOT / "src" / "warden" / "proxy.py").read_text() + # It may name token ENV VARS to detect caller auth; it must not carry a + # token value or mint one. + for forbidden in ("X-Vault-Token", "auth/approle/login", "token create"): + assert forbidden not in src, ( + f"proxy.py references {forbidden!r} — that is presenting or " + f"minting authority, not conducting the caller's" + ) From ec625873fbb3dec3969ba6c4a6395eed003a36fb Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 02:46:50 +0200 Subject: [PATCH 16/57] Review layer model v0.4; correct an unsound audit claim it exposes Assessment in history/2026-08-29-layer-model-v04-review.md. No objection to the ruling; both ops-warden amendments were adopted (5.2 conduit, 5.3 declared gap). Three findings, one against us. The one against us is real. 9.6 requires emission atomic with the state change for load-bearing evidence. ops-warden ca.py carries `pass # audit must not block signing` and AuditTrail.md advertises that the trail never blocks the primary action, so a failed append loses the event while the cert still issues -- a suppressed event leaving the chain intact, which is exactly what 9.6 describes. Whether to make it atomic is gate-house doctrine, not ops-wardens call: it would give the estates operational access lane a new dependency on its own evidence store. But one half of the fix is ours regardless -- the trail must not be read as complete. AuditTrail.md now says absence of a record is not evidence of absence, which it did not. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- history/2026-08-29-layer-model-v04-review.md | 136 +++++++++++++++++++ wiki/AuditTrail.md | 9 ++ 2 files changed, 145 insertions(+) create mode 100644 history/2026-08-29-layer-model-v04-review.md diff --git a/history/2026-08-29-layer-model-v04-review.md b/history/2026-08-29-layer-model-v04-review.md new file mode 100644 index 0000000..a548399 --- /dev/null +++ b/history/2026-08-29-layer-model-v04-review.md @@ -0,0 +1,136 @@ +# Security Layer Model v0.4 — ops-warden's review + +**Date:** 2026-08-29 +**Reviewed:** `net-kingdom/canon/standards/security-layer-model_v0.4.md` (accepted) +**Prior position:** `ADR-0010`, assent to v0.1 (`WARDEN-IN-0001`) +**Outcome:** no objection to the ruling; three findings, one of them against ops-warden. + +--- + +## What v0.4 did with ops-warden's amendment + +Both §5 asks from `ADR-0010` were adopted. + +**§5.3 declared engine gap** is the amendment ops-warden offered, adopted with the +four fields intact (`capability`, `intended_owner`, `blocked_on`, `review`), the +rationale preserved — *a rule offering no lane for a real sanctioned case gets +satisfied by relabelling rather than by closing the gap* — and the framing that +matters most kept explicit: **a declared gap is tracked non-conformance, not +conformance**. ops-warden's delegation machinery is cited as prior art. + +**§5.2 conduit** resolves the question ops-warden flagged rather than assumed. The +test is the supplied-authority property, which is the right test: it turns on what +the repository presents, not on what it touches. *"A conduit that presents its own +token is not a conduit"* is a sharper statement of `ADR-0002` than `ADR-0002` makes. + +**This created an obligation ops-warden had not met.** §5.3 requires the fields +*machine-readably* and §11 makes the mapping a mechanical check; ops-warden's +declaration was prose in `INTENT.md`. Fixed in this pass: `layer.yaml`, +`scripts/check_layer_conformance.py`, `tests/test_layer_conformance.py`. The +checker found three undeclared modules on first run, all false positives — help +text, a docstring, and the doubles library that *simulates* `bao` — which is why +it now matches invocation shapes rather than the word. + +--- + +## Finding 1 — §9.1 and §5.3 disagree, and ops-warden's §4 row is the instance + +§9.1: *a Staff repository MUST NOT be catalogued in §4 as owning a capability that +requires a Tooling contact no engine exposes*; where intended but unbuilt, the +entry **MUST be marked pending** and the gap declared under §5.3. + +ops-warden's §4 row reads `operational access lanes, stewardship, runbooks; SSH +certificate issuance` — with no pending mark. And §13 lists *SSH-CA signing write +(`VaultCA`, `bao kv put`) — declared by ops-warden — intended owner secrets-engine*. + +So the catalog asserts ownership of a capability that requires a Tooling contact no +engine exposes, unmarked. By §9.1's own text that is a defect. But the available +fix is worse than the defect: **marking it pending would be false.** SSH issuance is +production-verified and in daily use. `pending` would tell a reader ops-warden does +not yet do the one thing it demonstrably does. + +The root cause is that §9.1 collapses two different states: + +| State | Example | Capability today | +| --- | --- | --- | +| No route exists at all | kings-guard containment (§9.2) | **zero** | +| Route exists via a declared §5.3 gap | ops-warden SSH issuance | **working, tracked** | + +§5.3 exists precisely to sanction the second. §9.1 was written for the first — it +was raised by kings-guard, about containment, and correctly fixed *for that case*. +Applied to the adjacent case it produces a false catalog. + +**Recommendation:** give §9.1 two marks rather than one — `pending` where no route +exists, and `declared-gap` where the capability is discharged under §5.3 and +registered in §13. Both are honest; today's binary forces a choice between a false +label and an unmarked violation. + +This is the §12 loop working as designed, and §12 already says so: a finding that a +rule is unsatisfiable is a success of the loop. + +--- + +## Finding 2 — §5's scope is undefined for infrastructure §4 does not catalogue + +§5 forbids *a direct client for a Tooling-layer system*. §4 catalogues the security +estate, and only `key-cape` and `OpenBao` are Tooling rows. + +ops-warden holds an HTTP client for the **State Hub** and for **llm-connect** +(`src/warden/worker.py`). Neither appears in §4. Both are infrastructure a Staff +repository holds a direct client for. + +The question is not rhetorical, because the answers diverge sharply: + +- **If they are Tooling**, then every Staff repository in the estate is in + undeclared violation on adoption day — they all write progress events — and + §11's second mechanical check fails estate-wide. +- **If they are not**, §5 should say so, because *"a Tooling-layer system"* reads + considerably broader than *"a repository in the §4 Tooling rows"*. + +ops-warden has recorded both under `non_tooling_clients` in `layer.yaml` with the +reasoning stated, rather than resolving it unilaterally. The scope is gate-house's +to set. + +--- + +## Finding 3 — §9.6 lands on ops-warden, and ops-warden does not satisfy it + +This is the one against us, and it is the most consequential item in the review. + +§9.6 consequence 1: *any system whose evidence is load-bearing MUST make emission +atomic with the state change it records. An archive cannot retrofit completeness.* + +**ops-warden's audit emission is deliberately non-atomic.** `src/warden/ca.py:90` +carries `pass # audit must not block signing`, and `wiki/AuditTrail.md` states the +trail *"never blocks the primary action"*. If the audit append fails, the +certificate is still issued and the event is simply lost — a suppressed event that +leaves the chain perfectly intact, which is the exact failure §9.6 describes. + +That was a considered availability choice: an audit-disk problem should not remove +production host access. §9.6 now makes it a conformance question, and the trade is +real in both directions: + +- make emission atomic → an audit write failure fails the sign, and the estate's + operational access lane acquires a new dependency on its own evidence store; +- leave it → signing evidence cannot be treated as complete, and anything reasoning + from *"there is no record of a sign"* is unsound. + +**ops-warden has not changed it, and is not going to decide this alone** — §9.6 is +estate doctrine and the question is whether SSH signing evidence is load-bearing in +gate-house's sense. What ops-warden can say is that the second horn is currently +true and undocumented: `wiki/AuditTrail.md` does not warn that absence of a record +is not evidence of absence. That correction is ops-warden's regardless of the +ruling, and is the smaller half of the fix. + +Note also that §5.2 requires a conduit action to be *"reconstructable as the +caller's action in audit"* — an audit-dependent claim, and therefore bounded by +§9.6. Worth a cross-reference so the two rules do not drift apart. + +--- + +## Offered + +`layer.yaml` + `check_layer_conformance.py` + `test_layer_conformance.py` is a +working reference implementation of §5.3 and of §11's second mechanical check. Eight +of fifteen estate repositories have yet to declare (§14). If it is useful as a +pattern to point them at, it is offered — as the delegation machinery was. diff --git a/wiki/AuditTrail.md b/wiki/AuditTrail.md index 78b34df..7f0b22a 100644 --- a/wiki/AuditTrail.md +++ b/wiki/AuditTrail.md @@ -32,6 +32,15 @@ fresh file starts. high-entropy runs). Signing and proxy paths swallow audit failures so gatekeeping never blocks the primary action — but tests prove values cannot be written. +> **Absence of a record is not evidence of absence.** Because emission never +> blocks the primary action (`src/warden/ca.py`), a failed append loses the event +> while the action still happens. This trail proves that the records it holds were +> not altered or truncated; it does **not** prove that every action produced one. +> Do not reason from a missing entry. This is the estate-wide bound in +> `security-layer-model_v0.4` §9.6 — completeness is the source's obligation, and +> whether ops-warden must make signing emission atomic is an open question with +> gate-house (`history/2026-08-29-layer-model-v04-review.md`, Finding 3). + --- ## Query From 94f32bd160020664c9c8f66dc5ba62421b00f7a6 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 10:20:49 +0200 Subject: [PATCH 17/57] =?UTF-8?q?Review=20layer=20model=20v0.6;=20publish?= =?UTF-8?q?=20the=20PEP=20stance=20map=20=C2=A76.4=20requires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three v0.4 findings were acted on — §9.1 split into pending/declared-gap and §5's scope rule adopted as recommended and credited, and §9.6 ruled via the load-bearing/attributive distinction with ops-warden's `# audit must not block signing` named as the estate's live example. Checked the favourable ruling rather than accepting it. §9.6's test is "no control branches on its presence": the only consumer of audit.jsonl is `warden activity`, which displays. Nothing gates on a signing record, so the lane is genuinely attributive. AuditTrail.md now records the ruling instead of the open question, and states that the trade must be revisited if a control ever gates on the trail. CONFORMANCE ACTION. §6.4 obligation 3 requires a stance map "published rather than held in code", and requires every PEP-shaped consumer to publish one so the maps can be inventoried — naming ADR-0009 as the reference shape. ops-warden was not doing it: the map lived in PolicyConfig.failure_modes, a dataclass default. Not a code comment, but not published either. pep-stance.yaml publishes it, and the test asserts the published map EQUALS the shipped default. A published map that may drift from the code is worse than no map, because it invites reliance it cannot support. Two findings sent to gate-house, in history/2026-08-29-layer-model-v06-review.md: §6.4 obligation 1 (no side effect without a decision record) contradicts obligation 3 and §9.3, with ops-warden's blessed fail-open stance as the instance; and §6.4 mandates a stance-map inventory in §13 that §13 does not implement — where ops-warden is currently the only PEP to have published one. 402 tests pass, ruff clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- history/2026-08-29-layer-model-v06-review.md | 156 +++++++++++++++++++ layer.yaml | 5 + pep-stance.yaml | 69 ++++++++ tests/test_layer_conformance.py | 35 +++++ wiki/AuditTrail.md | 9 +- 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 history/2026-08-29-layer-model-v06-review.md create mode 100644 pep-stance.yaml diff --git a/history/2026-08-29-layer-model-v06-review.md b/history/2026-08-29-layer-model-v06-review.md new file mode 100644 index 0000000..857a78a --- /dev/null +++ b/history/2026-08-29-layer-model-v06-review.md @@ -0,0 +1,156 @@ +# Security Layer Model v0.6 — ops-warden's review + +**Date:** 2026-08-29 +**Reviewed:** `security-layer-model_v0.6.md` (proposed), plus v0.5 and the companion +**Prior positions:** `ADR-0010` (v0.1 assent); `history/2026-08-29-layer-model-v04-review.md` +**Outcome:** no objection; one conformance action taken, two findings, one accepted SHOULD. + +--- + +## Disposition of ops-warden's v0.4 findings + +All three were acted on, two of them exactly as recommended. + +| Finding | Outcome | +| --- | --- | +| §9.1 forces a false `pending` onto working capability | **Adopted** — v0.5 split it into `pending` and `declared-gap`, credited to ops-warden | +| §5 scope undefined for uncatalogued infrastructure | **Adopted** — "Tooling-layer system" now means a §4 Tooling row; the State Hub case is recorded, not policed | +| §9.6 atomicity lands on ops-warden's signing lane | **Ruled** — the load-bearing / attributive distinction, with ops-warden's `# audit must not block signing` named as the estate's live example | + +The §9.6 ruling deserves a note, because it went in ops-warden's favour and that is +a reason to check it rather than accept it. The test is *"no control branches on +its presence"*. Verified: the only consumer of `audit.jsonl` is `warden activity` +(`cli.py`), which displays. Nothing gates on a signing record — not the agent +read-boundary, not `warden plan`, not the scorecard. The lane is genuinely +attributive and the trade is legitimate on the standard's own terms. The two +obligations that attach — declare it, never claim completeness — were already met +in `wiki/AuditTrail.md`, now updated to record the ruling rather than the open +question. **If a future ops-warden control ever gates on this trail, the trade has +to be revisited before that ships**, and that is recorded there. + +`layer.yaml` is named in §11 as the estate's reference declaration form, including +the "record non-Tooling clients so the check is total" property. Offered again to +the repositories that have yet to declare. + +--- + +## Conformance action taken — the stance map was not published + +§6.4 obligation 3 requires a declared unreachable-engine stance that is total, per +zone, with no implicit default, *"published rather than held in code comments"* — +and §6.4 requires **every** PEP-shaped consumer to publish its map so the maps can +be inventoried. `ADR-0009` is named as the reference shape. + +ops-warden was not doing this. The map lived in `PolicyConfig.failure_modes`, a +dataclass default in `src/warden/config.py`. That is not a code *comment*, but it +is not published either — it is merely written down, and a consumer of the estate +had no way to read ops-warden's stance without reading ops-warden's source. + +Published as `pep-stance.yaml`, with the property that makes publishing worth +anything: `tests/test_layer_conformance.py` asserts the published map is **equal to +the shipped default**. A published map that may drift from the code is worse than +no map, because it invites reliance it cannot support. The file also records the +obligation-2 position (verdict never cached; input claims cached under their own +freshness rules) and the obligation-4 bound (§9.6 attributive). + +--- + +## Finding 1 — §6.4 obligation 1 contradicts obligation 3, and ops-warden is the instance + +> **1. No side effect without a decision record.** A PEP MUST NOT perform the +> protected action unless it holds a decision from `access-engine` identifying the +> request it was rendered for. + +> **3. A declared unreachable-engine stance (§9.3):** total, per zone... `ops-warden` +> `ADR-0009` is the reference shape. + +These cannot both be absolute. ops-warden's declared stance — blessed by §9.3 as +*"the only thing left"* when there is no engine to ask — is `fail_open` for `z0`–`z2` +and `unknown`. Applying it means issuing a certificate **without holding a +decision**, which obligation 1 forbids without qualification. + +So the same section names ops-warden as the reference shape for obligation 3 while +obligation 1 makes ops-warden's shipped behaviour a violation. §9.3 settled the +substance; §6.4 restates it in a form that takes it back. + +**Recommendation.** Bound obligation 1 by obligation 3: + +> A PEP MUST NOT perform the protected action unless it holds a decision from +> `access-engine` identifying the request it was rendered for, **or its declared +> §9.3 stance for the applicable scope permits proceeding without one and the +> application of that stance is recorded in place of the decision**. + +This is not a weakening. It is stricter than today's text in the case that matters: +it makes the *recorded application of the stance* mandatory, rather than leaving +"no decision record" as a silent state. ops-warden already does this — `ca.py` +writes `policy_zone`, `policy_failure_mode` and `policy_decision_id` (present only +where a decision was rendered) into both the signatures log and `audit.jsonl`, per +`ADR-0009` rule 4: *a fail-open signing result is metadata, not silence*. + +This is the same shape as the v0.4 §9.1 finding: a rule written for the clean case, +correct there, producing a false result on the adjacent case the standard has +already sanctioned elsewhere. + +--- + +## Finding 2 — §6.4 creates a register that §13 does not implement + +§6.4: *"Every PEP-shaped consumer MUST publish its stance map, and those maps MUST +be inventoried — in `maturity-engine` once it exists, **in §13 until then**."* + +§13 contains no stance-map rows. It records declared contacts and unowned +capabilities; there is no column, row, or section for a PEP stance. So the +obligation names a register that does not exist yet, and the failure mode §6.4 +itself warns about — *"`z0`–`z2` and unknown fail open" becoming the estate's real +policy without anyone having compiled it* — is exactly what the missing register +permits. + +**Recommendation.** Either add a stance-map table to §13 with the same +state/owner-status discipline the gap table has, or state that the inventory waits +for `maturity-engine` and mark the obligation pending under §9.1's own logic — a +requirement whose register does not exist is a capability catalogued without a +surface. ops-warden's row is ready to paste: + +| PEP | Protected action | Scope | Stance | Published | +| --- | --- | --- | --- | --- | +| `ops-warden` | SSH certificate issuance | security-zone | open `z0`–`z2`+unknown, closed `z3`/n-a | `ops-warden/pep-stance.yaml` | + +The second half matters more than the first: **ops-warden is currently the only +PEP that has published one**, so an inventory today would contain one row and that +is itself the finding. `ops-mason` is named PEP-shaped in the same paragraph. + +--- + +## Accepted, not yet done — §9.6 emission cadence + +§9.6: *"A source SHOULD declare an expected emission cadence, and a drop below it +SHOULD become a finding in its own right."* + +ops-warden declares none. This is a genuine SHOULD and the reasoning behind it is +sound — it converts the suppression blind spot into something detectable without +any Tooling contact, because the source publishes its own stream. It is not done +here because a cadence asserted without evidence is worse than none: ops-warden's +signing volume is operator-driven and bursty, and a fabricated baseline would +generate findings that mean nothing. Deriving one from the existing trail is +tractable and is recorded as ops-warden's to do, not gate-house's to chase. + +--- + +## On the pace + +Six versions in two days, with four repositories' findings absorbed and credited, +is the §12 loop working at a rate the estate has not seen before. Two cautions, +offered as an interested consumer rather than as objections: + +1. **§13 already says it should not be statute, and it is right.** The register has + grown every version. Moving it to `maturity-engine` is the stated plan; until + that exists, each version of the standard is also a snapshot of a backlog, and + the two have very different review intervals. +2. **The standard is `proposed` again at v0.6**, and the four repositories that + assented did so to v0.1. ops-warden's `ADR-0010` assent covers the three + boundary items, and nothing in v0.2–v0.6 has disturbed them — the layer, the + lane/rule demarcation, and doctrine-versus-runbook all stand. But the + `assented_by` list carries assent forward across five revisions, and a reader + could take it as assent to the current text. Worth distinguishing *assented to + the boundary* from *reviewed the current revision*; ops-warden has now done + both, and this note is the second. diff --git a/layer.yaml b/layer.yaml index 23d099a..be3ec97 100644 --- a/layer.yaml +++ b/layer.yaml @@ -20,6 +20,11 @@ layer: staff declared_by: docs/adr/ADR-0010 declared_at: "2026-08-29" +# §6.4 — ops-warden is PEP-shaped (it causes a protected side effect: issuing a +# certificate). Its unreachable-engine stance map is published separately, and +# asserted equal to shipped behaviour by tests/test_layer_conformance.py. +pep_stance: pep-stance.yaml + # Every direct contact with a Tooling-layer system (§4), one entry each. tooling_contacts: diff --git a/pep-stance.yaml b/pep-stance.yaml new file mode 100644 index 0000000..cf4e83f --- /dev/null +++ b/pep-stance.yaml @@ -0,0 +1,69 @@ +# ops-warden — PEP unreachable-engine stance map +# +# Framework: net-kingdom/canon/standards/security-layer-model_v0.6.md §6.4, §9.3 +# Rule of record: docs/adr/ADR-0009 +# Validate: pytest tests/test_layer_conformance.py -k stance +# +# §6.4 obligation 3 requires a declared unreachable-engine stance that is total, +# scoped per zone, carries no implicit default and no per-call discretion, and is +# "published rather than held in code comments". §6.4 further requires every +# PEP-shaped consumer to PUBLISH its map so the maps can be inventoried. This +# file is ops-warden's, published because a map that lives only in a dataclass +# default is not published — it is merely written down. +# +# The property that makes this worth reading: it is asserted equal to the shipped +# default in src/warden/config.py (PolicyConfig.failure_modes) by +# tests/test_layer_conformance.py. A published map that may drift from the code +# is worse than none, because it invites reliance it cannot support. + +schema_version: "0.1" +framework: netkingdom-security-layer-model +standard_version: "0.6" +repository: ops-warden +pep_shape: true +declared_by: docs/adr/ADR-0009 + +protected_action: "SSH certificate issuance (warden sign / cert_command)" +decision_engine: access-engine # flex-auth until the governed rename +scope: security-zone # security-zones_v0.1 membership of the TARGET workload + +# Total by construction: every zone in security-zones_v0.1, plus the two +# non-zone outcomes. No implicit default — an unlisted value is a config error, +# not a permissive fallback. +stance: + z0-experimental: fail_open + z1-operational: fail_open + z2-protected: fail_open + z2-continuity: fail_open + z3-critical: fail_closed + unknown: fail_open # versioned build profile (ADR-0009); explicit, never inferred + not-applicable: fail_closed + +# What happens when the stance is applied. §6.4 obligation 1 requires a decision +# record for a protected side effect; where the engine is unreachable there is no +# decision to hold, so ops-warden records the APPLICATION OF THE STANCE instead. +# See the assessment note: obligation 1 as written admits no such case. +on_apply: + recorded_fields: + - policy_zone + - policy_failure_mode + - policy_decision_id # present only where a decision was actually rendered + - outcome + written_to: + - "signatures log (src/warden/ca.py)" + - "audit.jsonl (src/warden/audit.py)" + never_recorded: "any secret material, any certificate private key" + +# §6.4 obligation 2 — the verdict is never cached. Input claims (zone membership, +# compiled from the flex-auth registry snapshot) are cached under their own +# freshness rules; the answer is not. +verdict_caching: none +input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebuilt by scripts/build_flex_auth_registry.py" + +# §6.4 obligation 4 — reconstructability, bounded by §9.6. ops-warden's audit +# emission on this lane is deliberately non-atomic and therefore ATTRIBUTIVE, not +# load-bearing: no control branches on the presence of a signing record +# (`warden activity` displays it; nothing gates on it). Registered in §13. +reconstructability: + bound: "§9.6 attributive — completeness is not claimed" + declared_at: wiki/AuditTrail.md diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py index 86b39a0..59284aa 100644 --- a/tests/test_layer_conformance.py +++ b/tests/test_layer_conformance.py @@ -96,3 +96,38 @@ class TestConduitSuppliesNoAuthority: f"proxy.py references {forbidden!r} — that is presenting or " f"minting authority, not conducting the caller's" ) + + +class TestPepStanceMap: + """§6.4: every PEP-shaped consumer MUST publish its unreachable-engine + stance map, total and per zone, 'published rather than held in code'. + ADR-0009 is named as the reference shape, so it should actually hold.""" + + def _stance(self) -> dict: + return yaml.safe_load((ROOT / "pep-stance.yaml").read_text()) + + def test_published_map_equals_shipped_behaviour(self): + """The whole point. A published map that may drift from the code is + worse than none, because it invites reliance it cannot support.""" + from warden.config import PolicyConfig + + assert self._stance()["stance"] == PolicyConfig().failure_modes + + def test_stance_is_total_over_the_zone_model(self): + """§6.4 obligation 3: total, no implicit default.""" + stance = self._stance()["stance"] + required = { + "z0-experimental", "z1-operational", "z2-protected", + "z2-continuity", "z3-critical", "unknown", "not-applicable", + } + assert required <= set(stance), f"stance not total; missing {required - set(stance)}" + assert set(stance.values()) <= {"fail_open", "fail_closed"} + + def test_critical_zone_fails_closed(self): + """ADR-0009's one non-negotiable row.""" + assert self._stance()["stance"]["z3-critical"] == "fail_closed" + + def test_verdict_is_never_cached(self): + """§6.4 obligation 2: caching an input claim is permitted; caching the + answer is a second decision point deciding early (§6.1).""" + assert self._stance()["verdict_caching"] == "none" diff --git a/wiki/AuditTrail.md b/wiki/AuditTrail.md index 7f0b22a..aba9036 100644 --- a/wiki/AuditTrail.md +++ b/wiki/AuditTrail.md @@ -38,8 +38,13 @@ never blocks the primary action — but tests prove values cannot be written. > not altered or truncated; it does **not** prove that every action produced one. > Do not reason from a missing entry. This is the estate-wide bound in > `security-layer-model_v0.4` §9.6 — completeness is the source's obligation, and -> whether ops-warden must make signing emission atomic is an open question with -> gate-house (`history/2026-08-29-layer-model-v04-review.md`, Finding 3). +> **Ruled 2026-08-29** (`security-layer-model_v0.6` §9.6): this trail is +> **attributive**, not load-bearing — no control branches on the presence of a +> signing record — so the non-atomic trade is legitimate, and the obligations are +> to declare it (this note) and never to claim completeness. Atomicity is required +> only where a control's soundness depends on an event being present or absent. +> Registered in the standard's §13 open-gap table as self-declared. If a future +> control ever gates on this trail, the trade must be revisited before it ships. --- From fd08950231aea2f03710775f3c4c114695b40757 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 14:50:55 +0200 Subject: [PATCH 18/57] Align INTENT and SCOPE to layer model v0.7; assess gaps; open WARDEN-WP-0034 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standard is accepted at v0.7, with SECURITY-COMPANION.md v0.2 as its operative form. Four ops-warden findings were adopted between v0.4 and v0.7 — §9.1's two marks, §5's Tooling scope rule, §6.4 obligation 1's second limb, and §13.1's existence — and both ops-warden declaration artifacts are now cited in the text as the estate's reference forms. INTENT.md gains frontmatter (layer: Staff, pep_shaped: true) because §11 requires a machine-readable declaration and prose cannot distinguish a declaration from a transcribed review. The note now covers the agent principal (§3.4), the PEP shape, the attributive evidence position, and the role the companion assigns: the estate is told to ask ops-warden which lane, which credential, which route. SCOPE.md records what is actually shipped against v0.7 and the honest conformance state — declared gap, which is tracked non-conformance, not conformance. The assessment checked every obligation against shipped code rather than intent. Three gaps survive: - §9.7.2 requires a PEP to state one revocation visibility deadline. Ours is unstated, and the honest value is uncomfortable: the cert TTL, up to 48h. A cert outlives revocation of the decision that authorized it — no CRL, no KRL distribution. That is a design property never written down, which is exactly what §9.7.2 exists to force into the open. - §3.4 rule 1 forbids standing credentials and requires issued, attributable authority. ADR-0004's boundary keys on WARDEN_AGENT_ID, which an agent sets about itself. key-cape now issues a real coding-agent identity, so the ops-warden half can stop being advisory. - §9.6 cadence remains undeclared. Attributive, so SHOULD not MUST, but silence through two reviews is the one outcome that is not defensible. WARDEN-WP-0034 addresses all three, plus the discoverability gap the companion creates and two items to route rather than absorb. 402 tests pass, ruff clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- INTENT.md | 90 +++++++--- SCOPE.md | 29 ++- .../2026-08-29-v07-scope-intent-assessment.md | 157 +++++++++++++++++ ...DEN-WP-0034-layer-model-v07-conformance.md | 165 ++++++++++++++++++ 4 files changed, 411 insertions(+), 30 deletions(-) create mode 100644 history/2026-08-29-v07-scope-intent-assessment.md create mode 100644 workplans/WARDEN-WP-0034-layer-model-v07-conformance.md diff --git a/INTENT.md b/INTENT.md index 86254a7..3522a93 100644 --- a/INTENT.md +++ b/INTENT.md @@ -1,34 +1,67 @@ +--- +layer: Staff +role: null # Engines only: PDP | PIP | Evidence | Lifecycle +pep_shaped: true # §6.4 — issuing a certificate is a protected side effect +standard: net-kingdom/canon/standards/security-layer-model_v0.7.md +standard_version: "0.7" +companion: net-kingdom/SECURITY-COMPANION.md +declaration: layer.yaml +pep_stance: pep-stance.yaml +assent: docs/adr/ADR-0010 +--- + # INTENT -> **Layer: Staff.** ops-warden is a Staff repository under the NetKingdom -> IT-security layer model (Taxonomy -> Tooling -> Engines -> Staff, layered by -> determinism and by the kind of artifact each layer produces): -> `net-kingdom/canon/standards/security-layer-model_v0.4.md` (**accepted**), owned by -> gate-house, ratified as `gate-house/decisions/decisions.md` GH-DEC-2026-001. -> ops-warden's assent is recorded there as `ADR-0010`. +> **ops-warden is Staff, and PEP-shaped.** Declared here in its own voice, per +> `security-layer-model_v0.7` §11 — a layer stated *about* a repository by another +> repository is not a declaration. The standard is **accepted**; its operative form +> is `net-kingdom/SECURITY-COMPANION.md`. ops-warden's assent is `ADR-0010`, and its +> reviews of v0.4, v0.6 and v0.7 are in `history/`. > -> **The binding rule: Staff never touches Tooling directly. It acts only through -> Engine APIs.** ops-warden holds no state another layer depends on at runtime and -> renders no authorization decision — it consumes them from access-engine. +> **Staff** because ops-warden's core function is judgement, not computation: it +> decides which lane a need belongs to and stewards the paths through the estate's +> rules. Its artifacts are runbooks, routing decisions, workplans. **PEP-shaped** +> because issuing a certificate is a protected side effect — a shape, not a layer +> (§6.4). ops-warden renders no authorization decision and never will; it consumes +> them from `access-engine`. > -> **Declared exception (standard section 5).** `src/warden/vault.py` (`VaultCA`) is a -> direct OpenBao client that performs a write, and `warden desk` shells `bao kv put` -> for paste-once provisioning. Both are non-conformant. Intended owner: -> **secrets-engine**; blocked on: no engine exposes an SSH certificate signing -> surface; reviewed quarterly. `src/warden/taint.py` reads KV metadata only, declared -> under the read-only observation allowance. `warden access --fetch/--exec/--wrap` -> runs the owner's tool under **the caller's** identity and supplies no authority of -> its own (`ADR-0002`). +> **The estate's front door for paths.** The companion routes the whole estate here: +> *"For how to get something done — which lane, which credential, which route — ask +> `ops-warden`. This document says what the rules are; ops-warden stewards the paths +> through them."* That is a standing obligation, not a compliment: every rule +> gate-house writes needs a path someone can actually walk, and ops-warden owes the +> estate that path. > -> **The declaration is `layer.yaml`, not this note.** v0.4 §5.3 requires a gap to -> carry `capability`, `intended_owner`, `blocked_on` and `review` *machine-readably*, -> and §11 makes "every direct Tooling client maps to a declared §5.1/§5.2/§5.3 entry" -> a mechanical check. `layer.yaml` is that map; `scripts/check_layer_conformance.py` -> and `tests/test_layer_conformance.py` enforce it, so a new undeclared OpenBao -> client fails on the commit that introduces it. +> **The declarations are files, not this note** — prose cannot distinguish a +> declaration from a transcribed review (§11): > -> Assent, reasoning, and the amendment offered back to gate-house — adopted as -> v0.4 §5.3: `history/2026-08-28-security-layer-model-assent.md` and `ADR-0010`. +> | File | Declares | Enforced by | +> | --- | --- | --- | +> | `layer.yaml` | every Tooling contact, mapped to a §5.1/§5.2/§5.3 shape, plus the non-Tooling clients so the check is total | `scripts/check_layer_conformance.py`, `tests/test_layer_conformance.py` | +> | `pep-stance.yaml` | the unreachable-engine stance map (§6.4 obl. 3), total per zone | test asserts the published map **equals** the shipped default | +> | `tenancy.yaml` | tenancy posture, and `z1-operational` zone membership | `ADR-0009` | +> +> Both are cited in the standard as the estate's reference forms (§11, §6.4, §13.1). +> +> **Two declared engine gaps (§5.3), tracked non-conformance and not conformance.** +> `VaultCA` signs over a direct OpenBao client and `warden desk` shells `bao kv put`. +> Intended owner **secrets-engine**; blocked on no engine exposing an SSH-CA or +> attended-provisioning surface; reviewed quarterly; registered in statute §13. +> ops-warden keeps signing while the gap is open — refusing would remove production +> host access to close a documentation gap. +> +> **The agent principal (§3.4).** ops-warden is operated by agents as well as people, +> and they share the layer but not the blast radius. No standing credential; tool use +> is a conduit or an engine API and there is no third route; **tool availability is +> not permission**, which is exactly what `ADR-0004`'s read-boundary enforces; agent +> memory is not a state plane. Session semantics belong to `glas-harness`, not here. +> +> **Evidence (§9.6).** ops-warden's audit trail is **attributive**, not load-bearing: +> no control branches on the presence of a signing record. Emission is deliberately +> non-atomic so an audit-store failure cannot remove production host access — a trade +> the standard sanctions, declared in `wiki/AuditTrail.md`, registered in §13. If any +> future control ever gates on this trail, that trade must be revisited before it +> ships. > This file captures **why this repository exists**, the **direction it is > moving toward**, and the **kind of system it is meant to become**. @@ -110,7 +143,7 @@ owns one lane and points at the rest: ## Lane routing — who owns which need **This is a runbook, not doctrine.** Security doctrine, the authority model, and the -security curriculum are **gate-house's** (`security-layer-model_v0.1` section 8). +security curriculum are **gate-house's** (`security-layer-model_v0.7` §8). ops-warden references them and does not restate them. What follows is lane stewardship: which subsystem owns which need, and what ops-warden does about it. @@ -137,7 +170,7 @@ orientation, and the catalog is the source of truth (`ADR-0001`). ### Access lane versus access rule -Normative, per `security-layer-model_v0.1` section 8 and assented to in `ADR-0010`: +Normative, per `security-layer-model_v0.7` §8 and assented to in `ADR-0010`: - **access lane** — ops-warden and ops-mason. *How* a worker reaches a host. - **access rule** — access-engine. *Whether* they may. @@ -147,7 +180,8 @@ may do something is not an ops-warden question, however it arrives. Canonical references: -- `net-kingdom/canon/standards/security-layer-model_v0.1.md` (layers, section 5, section 8) +- `net-kingdom/SECURITY-COMPANION.md` — the operative form; start here +- `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted; §5 shapes, §6.4 PEP, §8 vocabulary) - `net-kingdom/docs/platform-identity-security-architecture.md` - `net-kingdom/docs/responsibility-map.md` - `wiki/AccessManagementDirective.md` (ops SSH actor model) diff --git a/SCOPE.md b/SCOPE.md index 46c9eec..fb9ed06 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -85,6 +85,28 @@ cutover, flex-auth publishing the zone-aware pre-sign stance package, the owner-driven WP-0015 canon landing, and — newly named — the missing owner front doors that keep ops-warden holding interim lanes (secrets-engine, tenant-engine). +### Layer-model conformance (v0.7, accepted) + +ops-warden declares **Staff**, **PEP-shaped**, in `INTENT.md` frontmatter and in its +own voice — `security-layer-model_v0.7` §11. Shipped declaration artifacts, both +cited in the standard as the estate's reference forms: + +| Artifact | Declares | Status | +| --- | --- | --- | +| `layer.yaml` | 5 Tooling contacts mapped to §5.1/§5.2/§5.3 shapes + non-Tooling clients so the check is total | shipped; named reference form (§11) | +| `pep-stance.yaml` | unreachable-engine stance map, total per zone | shipped; registered in statute §13.1 (§6.4 obl. 3) | +| `scripts/check_layer_conformance.py` | every direct Tooling client maps to a declared shape | shipped; CI-enforced | +| `tests/test_layer_conformance.py` | the §5.2 no-authority property, and published stance map **equals** shipped default | shipped, 11 tests | + +Conformance state under §11: **declared gap** — tracked non-conformance, not +conformance. Two §5.3 contacts (`VaultCA` signing write, `warden desk` `bao kv put`), +intended owner `secrets-engine`, registered in statute §13. + +Four ops-warden findings have been adopted into the standard: §9.1's two marks +(`pending` vs `declared-gap`), §5's Tooling scope rule, §6.4 obligation 1's second +limb, and §13.1's existence. Reviews: `history/2026-08-29-layer-model-v04-review.md`, +`-v06-review.md`, `-v07-scope-intent-assessment.md`. + ### Issue vs route ops-warden executes exactly one lane with its own authority and routes/assists the rest. @@ -156,7 +178,7 @@ superseding ADR, never an in-place edit. | `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade | | `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after | | `ADR-0009` | Adopt security-zones v0.1 and compile explicit workload membership; PEP failure mode is per zone | -| `ADR-0010` | ops-warden is Staff — it owns access lanes, never access rules; the direct OpenBao client is a declared engine gap, not an exemption | +| `ADR-0010` | ops-warden is Staff and PEP-shaped — it owns access lanes, never access rules; the direct OpenBao client is a declared engine gap, not an exemption | Rules we follow but do not own — NetKingdom canon, the IAM profile, the credential-management standard, the-custodian's ADR-001 workplan convention — are @@ -434,7 +456,7 @@ Downstream: `ops-bridge` (primary), kaizen agents, CI automations, human operato | Repo | Relationship | | --- | --- | -| `gate-house` | Owns security doctrine, invariants, authority context, and conformance review; ops-warden routes doctrine questions there and references rather than restates them (`ADR-0010`) | +| `gate-house` | Owns the security layer model, doctrine, invariants, authority context, and conformance review. ops-warden routes doctrine questions there, and the companion routes the estate's *path* questions back to ops-warden (`ADR-0010`) | | `net-kingdom` | Canonical security architecture; ops-warden aligns to it | | `ops-bridge` | Primary cert_command consumer | | `railiance-infra` | Host-side SSH principals and hardening | @@ -488,6 +510,9 @@ keywords: [access, credential, secret, npm, token, api-key, openbao, key-cape, l | `wiki/CredentialRouting.md` | Which subsystem for each credential need | | `wiki/WorkloadSecurityPosture.md` | Secret-store posture, workload maturity, and blocker triage | | `registry/routing/catalog.yaml` | Machine-readable routing pointer catalog | +| `net-kingdom/SECURITY-COMPANION.md` | **The estate's operative security rules — start here** | +| `layer.yaml` | Layer declaration: every Tooling contact and its §5 shape | +| `pep-stance.yaml` | Unreachable-engine stance map (§6.4); equals shipped behaviour by test | | `tenancy.yaml` | Declared tenancy posture (`I1 A1 E0 P n/a R n/a V0`) and why each axis sits where it does | | `wiki/NetKingdomSecurityMap.md` | Platform security component map | | `examples/warden.production.example.yaml` | Production warden.yaml template | diff --git a/history/2026-08-29-v07-scope-intent-assessment.md b/history/2026-08-29-v07-scope-intent-assessment.md new file mode 100644 index 0000000..f8a2196 --- /dev/null +++ b/history/2026-08-29-v07-scope-intent-assessment.md @@ -0,0 +1,157 @@ +# v0.7 conformance — INTENT vs SCOPE gap assessment + +**Date:** 2026-08-29 +**Standard:** `security-layer-model_v0.7.md` (**accepted**) + `SECURITY-COMPANION.md` v0.2 +**Prior:** `ADR-0010`; v0.4 and v0.6 reviews in `history/` +**Method:** each v0.7 obligation checked against shipped code, not against intent. + +--- + +## Summary + +ops-warden is **conformant on every obligation it can discharge alone except three**, +and holds two declared §5.3 gaps that are tracked, registered and owned elsewhere. +The three genuine gaps are §9.7.2 (no stated revocation visibility deadline — a MUST), +§3.4 rule 1 (the agent read-boundary keys on an honour-system marker rather than an +issued identity), and §9.6's cadence, which is a SHOULD for an attributive source and +remains undone for an honest reason. + +One new obligation is not a rule at all but a role: the companion routes the entire +estate to ops-warden for *how to get something done*. Nothing in the repo answers a +layer or declaration question today, and that is now a discoverability gap. + +Four ops-warden findings were adopted into the standard between v0.4 and v0.7 — §9.1's +two marks, §5's Tooling scope rule, §6.4 obligation 1's second limb, and §13.1's +existence. That is the conformance loop working; it is not a reason to assume the next +pass finds nothing. + +--- + +## Obligation-by-obligation + +### Conformant, shipped, evidenced + +| Obligation | Evidence | +| --- | --- | +| §11 declare layer in own voice, machine-readably | `INTENT.md` frontmatter (`layer: Staff`, `pep_shaped: true`) + `layer.yaml` — cited in §11 as the estate's reference form | +| §5 every Tooling contact maps to a shape; non-Tooling recorded so the check is total | `layer.yaml` 5 contacts + 2 exclusions; `scripts/check_layer_conformance.py` | +| §5.2 conduit supplies no authority | `proxy.py::_caller_env`; `tests/test_layer_conformance.py::test_conduit_supplies_no_authority_of_its_own` | +| §6.4 obl. 1 no side effect without a decision **or a recorded stance** | `ca.py` writes `policy_zone`, `policy_failure_mode`, `policy_decision_id` (present only where rendered). ops-warden is the named reference for limb two | +| §6.4 obl. 2 no verdict recaching | `policy.py` caches nothing — verified by inspection, not by claim | +| §6.4 obl. 3 stance map published, at a path named in the declaration, equal to shipped behaviour, asserted by test | `pep-stance.yaml`, named in `layer.yaml`; test asserts equality with `PolicyConfig().failure_modes`; registered in statute §13.1 | +| §9.3 stance total, per zone, no implicit default | 7 rows covering every zone plus `unknown` and `not-applicable` | +| §9.6 evidence claims bounded | `wiki/AuditTrail.md` declares the attributive trade and states absence is not evidence of absence | +| §9.7.1 every allow has an explicit lifetime | TTL enforced per `ActorType` — `adm` 48h, `agt` 24h, `atm` 8h | +| §3.4 rule 2 tool use is a conduit or engine API | `warden access` is the conduit; `ADR-0004` enforces that tool availability is not permission | + +### Declared gaps — tracked non-conformance, owned elsewhere + +Both registered in statute §13, intended owner `secrets-engine`, reviewed quarterly. +Neither is closable by ops-warden: closing them means another repository shipping a +surface. + +- **`VaultCA` signing write** — no engine exposes SSH-CA signing. +- **`warden desk` `bao kv put`** — no engine exposes attended provisioning. + +Nothing in v0.7 changes their status. The right ops-warden behaviour is to keep them +declared, keep the review dates honest, and not quietly grow a third. + +--- + +## The three real gaps + +### G1 — §9.7.2: no stated revocation visibility deadline (MUST) + +> *A **PEP** has one boundary and MUST state one deadline… an unstated deadline is +> an unbounded replay window.* + +ops-warden states none, and the honest answer is uncomfortable: **the effective +window is the certificate TTL — up to 48 hours.** A certificate issued under an allow +stays valid for its full TTL even if the decision that authorized it is revoked or +superseded the next minute. ops-warden has no revocation channel for an issued cert: +there is no CRL, no KRL distribution, and host-side `auth_principals` is +`railiance-infra`'s. + +This is not a documentation gap. It is a design property that has never been written +down, and §9.7.2 exists precisely to force it into the open. Two things follow: + +1. The deadline must be **stated** — `adm` 48h / `agt` 24h / `atm` 8h — in + `pep-stance.yaml`, as what it is rather than as an aspiration. +2. Whether 48h is *acceptable* is a separate question, and it is partly + `railiance-infra`'s (KRL distribution) and partly ours (TTL policy). Stating it is + ours and is cheap; shortening it is a joint change. + +Stating a bad number is better than stating none: an unstated deadline is an +unbounded replay window, and this one is bounded and already implemented. + +### G2 — §3.4 rule 1: the agent boundary rests on an honour-system marker + +> *No standing credential. Authority is issued per task, time-bounded under §9.7, +> and attributable to the principal on whose behalf it acts.* + +`ADR-0004`'s read-boundary triggers when `WARDEN_AGENT_ID` is set — an environment +variable the agent sets **about itself**. An agent that does not set it is not +recognised as an agent. ops-warden has known this (`WARDEN-WP-0033-T04` recorded it +as "an honour-system marker on the ops-warden side"), and it was tolerable while no +issued agent identity existed. + +One now does. `key-cape` accepted issuance ownership in `KEY-WP-0009-T03`: +`codex-railiance-platform`, subject `service:codex:railiance-platform`, role +`coding-agent`, scope `openbao:login`, 15-minute lifetime. The OpenBao side is +enforced by `railiance-platform`'s policy, which is the half that actually holds. + +So the gap is narrower than it looks and worth stating precisely: **the OpenBao-side +boundary is real; the ops-warden-side boundary is advisory.** ops-warden should key +its read-boundary on the issued identity where one is present, and treat +`WARDEN_AGENT_ID` as a fallback that fails *toward* the boundary rather than away +from it. That is a change in this repo and does not need another repo to move. + +### G3 — §9.6 emission cadence (SHOULD, for an attributive source) + +Unchanged from the v0.6 review and still honest: ops-warden declares no expected +cadence because its signing volume is operator-driven and bursty, and a fabricated +baseline generates findings that mean nothing. v0.7 makes cadence a **MUST for +load-bearing sources**; ops-warden's trail is attributive, so it remains a SHOULD. + +Deriving a real baseline from the existing trail is tractable and is ops-warden's to +do. It should be derived and declared, or explicitly deferred with a reason — not +left silent, which is what it is today. + +--- + +## The role the companion assigns, and what it costs + +> *"For how to get something done in NetKingdom — which lane, which credential, which +> route — ask `ops-warden`. This document says what the rules are; ops-warden stewards +> the paths through them."* + +This is the largest change in ops-warden's INTENT surface and it is not a rule, so it +does not appear in any conformance check. The estate has been told to come here. + +**Today the repo answers credential questions and no others.** `warden route` and +`warden plan` cover lanes, owners and acts. Nothing answers *"which layer am I"*, +*"how do I declare"*, *"I am PEP-shaped, what do I owe"* — the questions the companion +and the standard's adoption status (eight of fifteen repositories undeclared) actually +generate. + +ops-warden has already built the reference artifacts those repositories need, and the +standard points at them by name in §11 and §6.4. What is missing is the path: a +discoverable route from *"I read the companion"* to *"here is the file to copy and the +check to run"*. That is exactly the stewardship ops-warden claims, applied to the +estate's newest rule rather than to its credential lanes. + +Also worth noting, and not ops-warden's to fix: §13.1's register has one row, and +`ops-mason` — catalogued PEP-shaped in the same paragraph — has published nothing. +The standard says one row is itself the finding. + +--- + +## What does not need doing + +- **No new ADR.** `ADR-0010` holds: Staff, lanes not rules, declared gaps not + exemptions. v0.2–v0.7 refined the rules around it and disturbed none of its three + positions. The reviews extend it; a superseding record would add ceremony without + changing a decision. +- **No change to the two §5.3 gaps.** They are correctly declared and owned elsewhere. +- **No re-assent.** ops-warden assented to the boundary in `ADR-0010` and has now + reviewed three revisions on their merits, which is the stronger position. diff --git a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md new file mode 100644 index 0000000..b233411 --- /dev/null +++ b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md @@ -0,0 +1,165 @@ +--- +id: WARDEN-WP-0034 +type: workplan +title: "Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule" +domain: infotech +repo: ops-warden +status: ready +owner: ops-warden +topic_slug: netkingdom +planning_priority: P1 +depends_on_workplans: + - WARDEN-WP-0030 +created: "2026-08-29" +updated: "2026-08-29" +--- + +# WARDEN-WP-0034 — Layer model v0.7 conformance + +`security-layer-model_v0.7` is **accepted**. ops-warden declared Staff and PEP-shaped, +shipped the two declaration artifacts the standard now cites as estate reference forms, +and had four findings adopted into the text between v0.4 and v0.7. + +The assessment in `history/2026-08-29-v07-scope-intent-assessment.md` checked every +v0.7 obligation against shipped code rather than intent. Three gaps survive, plus a +role the companion assigns that no conformance check will ever catch. + +## Why a workplan and not an adhoc + +T01 is a `MUST` that is currently unstated. T02 changes an enforcement boundary and +touches `ADR-0004`. T04 is a cross-repo stewardship commitment against a standard eight +repositories have yet to adopt. The convention reserves adhocs for low-risk work +completed directly; none of these qualify. + +## Tasks + +```task +id: WARDEN-WP-0034-T01 +status: todo +priority: high +``` + +**State the revocation visibility deadline (§9.7.2, a MUST).** + +ops-warden states none, and the honest value is the certificate TTL: **up to 48 hours**. +A cert issued under an allow remains valid for its full TTL even if the authorizing +decision is revoked the next minute. There is no CRL, no KRL distribution, and host-side +`auth_principals` belongs to `railiance-infra`. + +Add the deadline to `pep-stance.yaml` as what it is — `adm` 48h / `agt` 24h / `atm` 8h — +with the mechanism named (TTL expiry, no revocation channel) rather than implied. Assert +it against the shipped `ActorType` TTL policy by test, the same way the stance map is +asserted equal to shipped behaviour: a stated deadline free to drift from the code has +the same defect as a stated stance free to drift. + +**Done when:** the deadline is published, test-bound to the TTL policy, and the absence +of a revocation channel is stated rather than left to inference. + +**Not in scope:** shortening it. Whether 48h is acceptable is a joint question with +`railiance-infra` (KRL distribution) and is T05's to raise, not this task's to decide. + +```task +id: WARDEN-WP-0034-T02 +status: todo +priority: high +``` + +**Bind the agent read-boundary to an issued identity (§3.4 rule 1).** + +`ADR-0004`'s boundary triggers on `WARDEN_AGENT_ID` — a variable the agent sets about +itself. An agent that omits it is not recognised as one. §3.4 rule 1 now requires +authority to be *issued* per task and attributable to the principal acted for. + +The identity exists as of `KEY-WP-0009-T03`: `codex-railiance-platform`, subject +`service:codex:railiance-platform`, role `coding-agent`, 15-minute lifetime, with +`railiance-platform` enforcing the OpenBao-side policy. + +Key the boundary on the issued identity where one is present; keep `WARDEN_AGENT_ID` as +a fallback that fails **toward** the boundary, never away from it. State plainly in the +ADR trail which half is enforced and which is advisory: the OpenBao-side deny is real, +the ops-warden-side refusal is a courtesy that a determined caller can decline. + +**Done when:** an agent presenting the issued identity is recognised without setting +`WARDEN_AGENT_ID`, the fallback still refuses on `risk: high` lanes, and the +enforced/advisory split is written down. + +**Watch:** do not turn this into ops-warden validating a token — that is verifying an +identity claim, adjacent to deciding, and `ADR-0002`/§6 both point away from it. Read +the identity; do not adjudicate it. + +```task +id: WARDEN-WP-0034-T03 +status: todo +priority: medium +``` + +**Derive an emission cadence, or defer it with a reason (§9.6).** + +ops-warden's trail is attributive, so cadence is a SHOULD rather than v0.7's MUST for +load-bearing sources. It has been silent through two reviews, which is the one outcome +that is not defensible. + +Derive a baseline from the existing `audit.jsonl` and signatures log. If the signal is +too bursty to support a threshold — plausible, since volume is operator-driven — record +that as the finding with the distribution that shows it, and declare the deferral in +`pep-stance.yaml`. A measured "no useful baseline" is a result; silence is not. + +**Done when:** either a declared cadence with its derivation, or a declared deferral +carrying the data that justifies it. + +```task +id: WARDEN-WP-0034-T04 +status: todo +priority: medium +``` + +**Answer the question the companion sends the estate here to ask.** + +> *"For how to get something done — which lane, which credential, which route — ask +> `ops-warden`."* + +Today the repo answers credential questions and no others. `warden route` and +`warden plan` cover lanes, owners and acts. Nothing answers *"which layer am I"*, *"how +do I declare"*, *"I am PEP-shaped, what do I owe"* — and eight of fifteen catalogued +repositories have yet to declare. + +Provide the path, not the doctrine (that boundary is `ADR-0010`'s and does not move): +a routing entry and a short playbook that carry a reader from the companion to the files +to copy — `layer.yaml`, `pep-stance.yaml`, `check_layer_conformance.py`, +`test_layer_conformance.py` — and the check to run. The standard already names these in +§11 and §6.4; what is missing is the route to them. + +**Done when:** `warden route find "how do I declare my layer"` resolves, and the +playbook is reachable from the catalog. **Not** a restatement of the companion — a +pointer layer, per `ADR-0001`. + +```task +id: WARDEN-WP-0034-T05 +status: todo +priority: low +``` + +**Two things to raise rather than absorb.** + +Both are other repositories' to own; ops-warden's obligation is to route them, not to +fix them (`ADR-0003`, `ADR-0005`). + +1. **`ops-mason` has published no stance map.** §13.1's register has one row and the + standard says that is itself the finding. `ops-mason` is catalogued PEP-shaped in + the same paragraph and is ops-warden's peer lane owner. Offer the reference form; + do not write their map. +2. **The 48-hour replay window from T01.** Once stated, raise with `railiance-infra` + whether KRL distribution is worth building, and with `access-engine` whether a + decision lifetime shorter than the cert TTL is meaningful when nothing can recall + the cert. State the question; let the owners answer. + +**Done when:** both are routed with reasoning, and the answers recorded either way — +including a refusal, which is an equally good answer. + +## Related + +- `history/2026-08-29-v07-scope-intent-assessment.md` — the gap analysis behind this plan +- `history/2026-08-29-layer-model-v04-review.md`, `-v06-review.md` — the two prior reviews +- `security-layer-model_v0.7.md` §3.4, §6.4, §9.6, §9.7, §11, §13.1 +- `net-kingdom/SECURITY-COMPANION.md` v0.2 +- `ADR-0002`, `ADR-0003`, `ADR-0004`, `ADR-0005`, `ADR-0009`, `ADR-0010` From 2e3ff772f485f5ba3e858a3adbf778cebe05d44a Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 14:55:36 +0200 Subject: [PATCH 19/57] Refresh work-record index for WARDEN-WP-0034 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YWBMovyFoy9RRrfL7zKvPJ Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- WORK-RECORDS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 5425d47..d1e0eb4 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -30,6 +30,7 @@ | workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md | | workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md | | workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | +| workplan | WARDEN-WP-0034 | ready | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -129,5 +130,10 @@ | task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | +| task | WARDEN-WP-0034-T01 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T02 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T03 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T04 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T05 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | | intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | | intake | WARDEN-IN-0002 | open | — | intakes/intakes.md | From d18d18e313da6ac0f0e8116cf92b707ffa89daab Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Sat, 29 Aug 2026 14:56:20 +0200 Subject: [PATCH 20/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-08-29: - update .custodian-brief.md for ops-warden Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- .custodian-brief.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index eb06359..01b0f6c 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,11 +2,21 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-08-28 20:02 UTC +**Last synced:** 2026-08-29 12:56 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams +### Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule +Progress: 0/5 done | workplan_id: `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef` + +**Open tasks:** +- · Tasks `8b3bdb9f` +- · Tasks `3318ee1a` +- · Tasks `a891b32c` +- · Tasks `94e73daa` +- · Tasks `7d1b3c82` + ### Tamper-resistant credential governance + mass rotation/lockdown (Strand B) Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` From 4e267179db741b27a3e62f81f753cd9752c97412 Mon Sep 17 00:00:00 2001 From: repo-manager Date: Sat, 29 Aug 2026 14:56:21 +0200 Subject: [PATCH 21/57] chore(registrar): assign State Hub identifiers Assistant: claude-code Assistant-Model: opus Assistant-Process: 4014535@bnt-lap001 Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c --- intakes/intakes.md | 1 + workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/intakes/intakes.md b/intakes/intakes.md index 192d793..0765cd7 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -89,4 +89,5 @@ description: 'v0.3 is proposed and changes sections 4, 9 and 13 only; the v0.2 a or rejection acceptable.' created: '2026-08-28T20:40:24.957468Z' updated: '2026-08-28T20:40:24.957468Z' +state_hub_intake_id: "01a04d97-94cd-7b49-8019-a91c7fce8adb" ``` diff --git a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md index b233411..8e52f00 100644 --- a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md +++ b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md @@ -12,6 +12,7 @@ depends_on_workplans: - WARDEN-WP-0030 created: "2026-08-29" updated: "2026-08-29" +state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef" --- # WARDEN-WP-0034 — Layer model v0.7 conformance @@ -37,6 +38,7 @@ completed directly; none of these qualify. id: WARDEN-WP-0034-T01 status: todo priority: high +state_hub_task_id: "8b3bdb9f-d2c2-5b3e-89e2-417bf3e37484" ``` **State the revocation visibility deadline (§9.7.2, a MUST).** @@ -62,6 +64,7 @@ of a revocation channel is stated rather than left to inference. id: WARDEN-WP-0034-T02 status: todo priority: high +state_hub_task_id: "3318ee1a-b5d9-5d39-baf7-9c42a8bc7b55" ``` **Bind the agent read-boundary to an issued identity (§3.4 rule 1).** @@ -91,6 +94,7 @@ the identity; do not adjudicate it. id: WARDEN-WP-0034-T03 status: todo priority: medium +state_hub_task_id: "a891b32c-b0a7-59f6-a5cd-977be65c09ca" ``` **Derive an emission cadence, or defer it with a reason (§9.6).** @@ -111,6 +115,7 @@ carrying the data that justifies it. id: WARDEN-WP-0034-T04 status: todo priority: medium +state_hub_task_id: "94e73daa-f74d-51fd-8639-68896a4066ee" ``` **Answer the question the companion sends the estate here to ask.** @@ -137,6 +142,7 @@ pointer layer, per `ADR-0001`. id: WARDEN-WP-0034-T05 status: todo priority: low +state_hub_task_id: "7d1b3c82-9b96-5087-a53a-496212909029" ``` **Two things to raise rather than absorb.** From b4c1d3900a528bbb9f2714d59403757426185d63 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 00:13:26 +0200 Subject: [PATCH 22/57] Fix contained OpenBao login handoff Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- src/warden/proxy.py | 15 ++++---- tests/test_proxy.py | 36 +++++++++++++++++++ ...N-WP-0034-attended-login-openbao-output.md | 36 +++++++++++++++++++ 3 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 workplans/WARDEN-WP-0034-attended-login-openbao-output.md diff --git a/src/warden/proxy.py b/src/warden/proxy.py index 0acd379..1fd3e2a 100644 --- a/src/warden/proxy.py +++ b/src/warden/proxy.py @@ -12,8 +12,9 @@ intact. Three guardrails are enforced here in code: the tool with inherited stdout/stderr so the value never enters warden's memory; sanctioned exec/file transports hold it only for their bounded handoff. The high-risk attended-login lane is stricter: it captures every client byte inside - an isolated helper session, permits no output, self-revokes, and cleans up. Audit - records are metadata only. + an isolated helper session, never returns that output, requires successful + persistence to a private token helper, self-revokes, and cleans up. Audit records + are metadata only. * **G3 — policy gate before fetch.** The CLI runs ``check_fetch_policy`` before calling anything here; this module refuses to run an unresolved command template. @@ -380,9 +381,11 @@ def proxy_attended_login_exec( """Run an attended login and one silent child inside a private helper home. The default home is proven writable before the OIDC client starts. Login, - child, and revocation output are captured and discarded. Any non-empty output, - persistence defect, or non-zero result fails closed; any possibly issued token - is revoked before the isolated helper directory is removed. + child, and revocation output are captured and discarded. A successful login + may return client output only after the private helper has been populated; + persistence defects and non-zero results fail closed. The reviewed child must + remain silent. Any possibly issued token is revoked before the isolated helper + directory is removed. """ if not child_argv: raise ProxyError( @@ -428,7 +431,7 @@ def proxy_attended_login_exec( except (OSError, ProxyError): helper_valid = False - if login.returncode != 0 or login_output.strip() or not helper_valid: + if login.returncode != 0 or not helper_valid: revoked = _revoke_contained( resolved.argv[0], env=env, possible_output=login_output ) diff --git a/tests/test_proxy.py b/tests/test_proxy.py index cac3a97..e049b16 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -391,6 +391,42 @@ def test_attended_login_unexpected_output_is_contained_revoked_and_cleaned( assert not (tmp_path / ".warden-attended-login").exists() +def test_attended_login_contained_success_output_never_escapes(monkeypatch, tmp_path, capsys): + monkeypatch.setattr(Path, "home", lambda: tmp_path) + sentinel = "hvs.NONPRODUCTION_CONTAINED_LOGIN" + child_ran = False + + def fake_run(argv, **kw): + nonlocal child_ran + helper = Path(kw["env"]["HOME"]) / ".vault-token" + if argv[:2] == ["bao", "login"]: + helper.write_text(sentinel) + helper.chmod(0o600) + return subprocess.CompletedProcess( + argv, + 0, + stdout=json.dumps({"auth": {"client_token": sentinel}}).encode(), + stderr=b"", + ) + if argv == ["reviewed-child"]: + child_ran = True + return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"") + if argv[:3] == ["bao", "token", "revoke"]: + return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"") + raise AssertionError(argv) + + monkeypatch.setattr("warden.proxy.subprocess.run", fake_run) + assert proxy_attended_login_exec( + ResolvedFetch(argv=["bao", "login", "-no-print"]), + child_argv=["reviewed-child"], + ) == 0 + captured = capsys.readouterr() + assert child_ran is True + assert sentinel not in captured.out + assert sentinel not in captured.err + assert not (tmp_path / ".warden-attended-login").exists() + + def test_real_catalog_login_entry_is_login_lane(): from warden.routing import load_catalog e = load_catalog(_repo_catalog()).get("key-cape-oidc-login") diff --git a/workplans/WARDEN-WP-0034-attended-login-openbao-output.md b/workplans/WARDEN-WP-0034-attended-login-openbao-output.md new file mode 100644 index 0000000..0b616b8 --- /dev/null +++ b/workplans/WARDEN-WP-0034-attended-login-openbao-output.md @@ -0,0 +1,36 @@ +--- +id: WARDEN-WP-0034 +type: workplan +title: "Accept contained OpenBao login output only after helper persistence" +domain: infotech +repo: ops-warden +status: active +owner: codex +topic_slug: attended-login-openbao-output +created: "2026-09-01" +updated: "2026-09-01" +--- + +## Repair attended-login handoff + +```task +id: WARDEN-WP-0034-T01 +status: done +priority: high +``` + +Allow a successful OpenBao login to proceed when its output is fully contained +and the private mode-0600 token helper is populated. Continue failing closed on +non-zero login, missing persistence, child output, revocation failure, or cleanup +failure. + +## Verify live contained operation + +```task +id: WARDEN-WP-0034-T02 +status: progress +priority: high +``` + +Run the proxy regression suite, reinstall the CLI, and complete one governed +OpenBao platform-admin operation with deterministic self-revocation. From 8f01eefb1e958d060adfb78ea214464b730edd4d Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 00:24:18 +0200 Subject: [PATCH 23/57] Preserve Warden config in attended child Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- src/warden/proxy.py | 4 ++++ tests/test_proxy.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/warden/proxy.py b/src/warden/proxy.py index 1fd3e2a..65e4455 100644 --- a/src/warden/proxy.py +++ b/src/warden/proxy.py @@ -403,6 +403,10 @@ def proxy_attended_login_exec( root, session, root_created = _prepare_attended_login_home() helper = session / _TOKEN_HELPER_NAME env = _caller_env() + if not env.get("WARDEN_CONFIG"): + caller_config = Path.home() / ".config" / "warden" / "warden.yaml" + if caller_config.is_file(): + env["WARDEN_CONFIG"] = str(caller_config) env["HOME"] = str(session) env.pop("BAO_TOKEN", None) env.pop("VAULT_TOKEN", None) diff --git a/tests/test_proxy.py b/tests/test_proxy.py index e049b16..b67b122 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -427,6 +427,34 @@ def test_attended_login_contained_success_output_never_escapes(monkeypatch, tmp_ assert not (tmp_path / ".warden-attended-login").exists() +def test_attended_login_preserves_caller_warden_config_for_reviewed_child( + monkeypatch, tmp_path +): + monkeypatch.setattr(Path, "home", lambda: tmp_path) + monkeypatch.delenv("WARDEN_CONFIG", raising=False) + caller_config = tmp_path / ".config" / "warden" / "warden.yaml" + caller_config.parent.mkdir(parents=True) + caller_config.write_text("backend: local\n") + seen_config = None + + def fake_run(argv, **kw): + nonlocal seen_config + helper = Path(kw["env"]["HOME"]) / ".vault-token" + if argv[:2] == ["bao", "login"]: + helper.write_text("non-production-test-double") + helper.chmod(0o600) + if argv == ["reviewed-child"]: + seen_config = kw["env"].get("WARDEN_CONFIG") + return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"") + + monkeypatch.setattr("warden.proxy.subprocess.run", fake_run) + assert proxy_attended_login_exec( + ResolvedFetch(argv=["bao", "login", "-no-print"]), + child_argv=["reviewed-child"], + ) == 0 + assert seen_config == str(caller_config) + + def test_real_catalog_login_entry_is_login_lane(): from warden.routing import load_catalog e = load_catalog(_repo_catalog()).get("key-cape-oidc-login") From 4fee839b1138c60642bd6e0210cf8bf541333747 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 00:46:28 +0200 Subject: [PATCH 24/57] feat: route Policy Nexus source credential Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- registry/routing/catalog.yaml | 36 ++++++++++++ tests/test_routing.py | 26 +++++++++ tests/test_workload_join.py | 2 +- .../policy-nexus-forgejo-source-read.md | 55 +++++++++++++++++++ ...N-WP-0034-attended-login-openbao-output.md | 8 ++- ...-policy-nexus-forgejo-source-read-route.md | 36 ++++++++++++ 6 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 wiki/playbooks/policy-nexus-forgejo-source-read.md create mode 100644 workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 09f690e..7b91b40 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -242,6 +242,42 @@ entries: - "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish NPM_AUTH_TOKEN=@file` (value from a mode-0600 file)." - "Verify capabilities-safe, then confirm publish works via `secrets-engine exec --catalog whynot-design-npm-publish -- npm whoami` (value used, not printed)." + - id: policy-nexus-forgejo-source-read + title: Policy Nexus Forgejo private-source repository read token + # Read-only at Forgejo, but disclosure grants estate-wide private source access. + # Agent callers therefore remain inside Warden's sanctioned transport boundary. + risk: high + workload_ref: + applicability: applicable + unknown_reason: "policy-nexus-actions has not published an authoritative workload identity declaration." + need_keywords: [policy-nexus, policy, nexus, forgejo, private, source, repository, read, token, actions, FORGEJO_SOURCE_TOKEN] + owner_repo: railiance-platform + subsystem: OpenBao + Forgejo Actions + warden_executes: false + wiki_ref: wiki/playbooks/policy-nexus-forgejo-source-read.md#worker-checklist + canon_ref: railiance-platform/credential-change-requests/CCR-2026-0014-policy-nexus-forgejo-source-read.yaml + reviewed: "2026-09-01" + status: active + delegation: + mode: native + intended_owner: railiance-platform + reviewed: "2026-09-01" + verified: source-read + auth_method: "bao login -method=oidc -path=netkingdom role=policy-nexus-forgejo-source-workload-kv-read" + path_template: "platform/workloads/policy-nexus/forgejo-source-read" + fetch_command: "bao kv get -field=FORGEJO_SOURCE_TOKEN platform/workloads/policy-nexus/forgejo-source-read" + policy_ref: "flex-auth check secret.read:policy-nexus" + exec_capable: true + lane: secret + rotation: + method: rotate + owner: railiance-platform + automatable: false + steps: + - "Mint a replacement PAT for the restricted policy-nexus-source identity with scope exactly read:repository; retain the predecessor until verification passes." + - "Use the attended railiance-platform bootstrap to update OpenBao and the coulomb/policy-nexus FORGEJO_SOURCE_TOKEN Actions secret without exposing the value." + - "Pass one exact-commit candidate workflow, then revoke the predecessor PAT and record bounded non-secret evidence in CCR-2026-0014." + - id: flex-auth-policy-check title: Authorization decision — may this actor perform this action # Returns an authorization decision; no credential flows (WARDEN-WP-0032-T05). diff --git a/tests/test_routing.py b/tests/test_routing.py index f280dda..1e5d691 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -153,6 +153,32 @@ def test_whynot_design_npm_lane_is_concrete_and_resolvable(): assert "platform/workloads/coulomb/whynot-design/npm-publish" in e.fetch_command +def test_policy_nexus_source_read_lane_is_exact_high_risk_and_resolvable(): + catalog = load_catalog(_repo_catalog()) + entry = catalog.get("policy-nexus-forgejo-source-read") + assert entry is not None and entry.is_active and entry.exec_capable + assert entry.resolvable is True + assert entry.risk == "high" + assert entry.owner_repo == "railiance-platform" + assert entry.fetch_command == ( + "bao kv get -field=FORGEJO_SOURCE_TOKEN " + "platform/workloads/policy-nexus/forgejo-source-read" + ) + assert entry.path_template == "platform/workloads/policy-nexus/forgejo-source-read" + assert entry.auth_method.endswith( + "role=policy-nexus-forgejo-source-workload-kv-read" + ) + assert entry.delegation is not None and entry.delegation.mode == "native" + + +def test_route_find_policy_nexus_source_read_prefers_concrete_lane(): + catalog = load_catalog(_repo_catalog()) + matches = catalog.find( + "policy nexus Forgejo private source repository read token Actions", limit=1 + ) + assert matches[0].id == "policy-nexus-forgejo-source-read" + + def test_generic_and_template_lanes_not_resolvable(): catalog = load_catalog(_repo_catalog()) # generic openbao lane has /; login lane has . diff --git a/tests/test_workload_join.py b/tests/test_workload_join.py index dab4df8..d87e5dd 100644 --- a/tests/test_workload_join.py +++ b/tests/test_workload_join.py @@ -13,7 +13,7 @@ def test_repo_catalog_uses_only_explicit_workload_references(): report = build(ROOT / "registry/routing/catalog.yaml", Path.home()) assert report["ok"] is True assert len(report["resolved"]) == 3 - assert len(report["unknown"]) == 17 + assert len(report["unknown"]) == 18 # 11 since WARDEN-WP-0033: the two NetKingdom SSO lanes (c374d41) are # provider/control-plane credentials, not workload delivery lanes. assert len(report["not_applicable"]) == 11 diff --git a/wiki/playbooks/policy-nexus-forgejo-source-read.md b/wiki/playbooks/policy-nexus-forgejo-source-read.md new file mode 100644 index 0000000..6e704c7 --- /dev/null +++ b/wiki/playbooks/policy-nexus-forgejo-source-read.md @@ -0,0 +1,55 @@ +# Policy Nexus Forgejo source-read token + +Date: 2026-09-01 +Catalog: `policy-nexus-forgejo-source-read` (status `active`, `resolvable: true`) +Owner: `railiance-platform` (OpenBao and credential lifecycle) + +This lane carries the dedicated Forgejo PAT used by Policy Nexus Actions to +fetch exact archives from private owner repositories. The Forgejo identity is +restricted to organization-wide repository-code read and the PAT scope is +exactly `read:repository`. It has no repository write, package, organization +administration, instance administration, cluster, or deployment authority. + +The authoritative lifecycle and evidence record is +`railiance-platform/credential-change-requests/CCR-2026-0014-policy-nexus-forgejo-source-read.yaml`. +Warden is a governed conduit and never owns, persists, caches, or logs the PAT. + +## Owner-confirmed lane + +| Field | Value | +| --- | --- | +| OpenBao path | `platform/workloads/policy-nexus/forgejo-source-read` | +| Primary field | `FORGEJO_SOURCE_TOKEN` | +| Read policy | `workload-kv-read-policy-nexus-forgejo-source` | +| OIDC role | `policy-nexus-forgejo-source-workload-kv-read` | +| Forgejo identity | `policy-nexus-source` | +| Forgejo team | `policy-nexus-source-readers` | +| Actions consumer | `coulomb/policy-nexus` secret `FORGEJO_SOURCE_TOKEN` | + +## Worker checklist + +1. Authenticate as an approved operator through the exact OIDC role. Do not + substitute the Forgejo admin PAT or a broader OpenBao identity. + +2. Confirm the route before use: + + ```bash + warden route show policy-nexus-forgejo-source-read --json | jq .resolvable + ``` + + The result must be `true`, and the rendered fetch must contain no placeholder. + +3. Agent callers must use a sanctioned high-risk transport. Pass the value only + to the bounded source-consuming child: + + ```bash + warden access policy-nexus-forgejo-source-read \ + --field FORGEJO_SOURCE_TOKEN --exec -- + ``` + + Do not use raw `--fetch`, place the value on argv, or write it to chat, Git, + State Hub, workflow logs, or a persistent temporary file. + +4. For rotation or compromise, follow CCR-2026-0014. A replacement is not active + until its scope and negative permissions are verified and one exact-commit + Policy Nexus candidate workflow succeeds; revoke the predecessor afterward. diff --git a/workplans/WARDEN-WP-0034-attended-login-openbao-output.md b/workplans/WARDEN-WP-0034-attended-login-openbao-output.md index 0b616b8..8dee644 100644 --- a/workplans/WARDEN-WP-0034-attended-login-openbao-output.md +++ b/workplans/WARDEN-WP-0034-attended-login-openbao-output.md @@ -4,7 +4,7 @@ type: workplan title: "Accept contained OpenBao login output only after helper persistence" domain: infotech repo: ops-warden -status: active +status: finished owner: codex topic_slug: attended-login-openbao-output created: "2026-09-01" @@ -28,9 +28,13 @@ failure. ```task id: WARDEN-WP-0034-T02 -status: progress +status: done priority: high ``` Run the proxy regression suite, reinstall the CLI, and complete one governed OpenBao platform-admin operation with deterministic self-revocation. + +Completed 2026-09-01. The installed CLI completed the governed Policy Nexus +Forgejo source bootstrap with all child output contained, then revoked and +removed its isolated helper session. diff --git a/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md b/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md new file mode 100644 index 0000000..79e4e0d --- /dev/null +++ b/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md @@ -0,0 +1,36 @@ +--- +id: WARDEN-WP-0035 +type: workplan +title: "Register the Policy Nexus Forgejo source-read route" +domain: infotech +repo: ops-warden +status: active +owner: codex +topic_slug: policy-nexus-forgejo-source-read +created: "2026-09-01" +updated: "2026-09-01" +--- + +## Register the exact high-risk lane + +```task +id: WARDEN-WP-0035-T01 +status: done +priority: high +``` + +Add the exact OpenBao path, field, OIDC role, owner pointer, and rotation +boundary from railiance-platform CCR-2026-0014. The entry must be concrete and +resolvable while remaining subject to Warden's high-risk agent read boundary. + +## Verify routing and governed use + +```task +id: WARDEN-WP-0035-T02 +status: progress +priority: high +``` + +Pass catalog, route-selection, proxy, and policy tests; reinstall the CLI; prove +the installed route resolves and can hand the value only to a sanctioned child +transport without printing or persisting it. From eddb5d426b20c84777e69e4a822ca1a2397e41f4 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 00:47:25 +0200 Subject: [PATCH 25/57] chore: refresh high-risk credential paths Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- registry/generated/high-risk-data-paths.yaml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 78d3ce6..e7f858e 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,13 +10,13 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-08-29T00:44:23Z" +generated_at: "2026-08-31T22:46:47Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "ee94c189385271194f22a53998b2c6e12e961ea9" -catalog_revision_date: "2026-08-28T22:00:09+02:00" +catalog_revision: "4fee839b1138c60642bd6e0210cf8bf541333747" +catalog_revision_date: "2026-09-01T00:46:28+02:00" catalog_dirty: false -high_risk_lane_count: 23 -concrete_path_count: 14 +high_risk_lane_count: 24 +concrete_path_count: 15 # Graded high but not a single KV address -- a routing pattern, a broker # grant, or a non-KV lane. Nothing here for a policy to deny. @@ -77,6 +77,11 @@ paths: metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets owner_repo: railiance-platform fields: null # field set not established -- unknown, not one + - id: policy-nexus-forgejo-source-read + data_path: platform/data/workloads/policy-nexus/forgejo-source-read + metadata_path: platform/metadata/workloads/policy-nexus/forgejo-source-read + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one - id: railiance-backup-offsite-lane data_path: platform/data/workloads/railiance/backup/offsite-lane metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane From 31694ab64ddc8a855a28cc6b04e6b1de5a86fca8 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Tue, 1 Sep 2026 00:50:08 +0200 Subject: [PATCH 26/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-09-01: - update .custodian-brief.md for ops-warden Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- .custodian-brief.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index 01b0f6c..830ff11 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-08-29 12:56 UTC +**Last synced:** 2026-08-31 22:49 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -23,6 +23,11 @@ Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` **Open tasks:** - ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee` +## Inbox Hygiene + +**Stale unread:** 1 message(s) older than 3 day(s) — triage at session start. +**Missing thread_id:** 6 unread message(s) lack supersession chains. + --- ## MCP Orientation (when available) From 9770d6ad661fcd2fe8c2982fb4f31e7ee9a2d961 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 00:51:52 +0200 Subject: [PATCH 27/57] docs: close Warden credential workplans Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- ...-WP-0035-policy-nexus-forgejo-source-read-route.md | 11 +++++++++-- ...> WARDEN-WP-0036-attended-login-openbao-output.md} | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) rename workplans/{WARDEN-WP-0034-attended-login-openbao-output.md => WARDEN-WP-0036-attended-login-openbao-output.md} (93%) diff --git a/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md b/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md index 79e4e0d..af56f05 100644 --- a/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md +++ b/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md @@ -4,7 +4,7 @@ type: workplan title: "Register the Policy Nexus Forgejo source-read route" domain: infotech repo: ops-warden -status: active +status: finished owner: codex topic_slug: policy-nexus-forgejo-source-read created: "2026-09-01" @@ -27,10 +27,17 @@ resolvable while remaining subject to Warden's high-risk agent read boundary. ```task id: WARDEN-WP-0035-T02 -status: progress +status: done priority: high ``` Pass catalog, route-selection, proxy, and policy tests; reinstall the CLI; prove the installed route resolves and can hand the value only to a sanctioned child transport without printing or persisting it. + +Completed 2026-09-01. All 406 selected tests passed, including the generated +high-risk data-path boundary. The no-cache installed CLI resolves the exact +lane, and `warden plan` returns only sanctioned `--exec`, `--out`, and `--wrap` +transports for an agent caller. Policy Nexus Actions run 32 separately proved +the installed credential against the complete private-source fetch and release +path without exposing the value. diff --git a/workplans/WARDEN-WP-0034-attended-login-openbao-output.md b/workplans/WARDEN-WP-0036-attended-login-openbao-output.md similarity index 93% rename from workplans/WARDEN-WP-0034-attended-login-openbao-output.md rename to workplans/WARDEN-WP-0036-attended-login-openbao-output.md index 8dee644..8607194 100644 --- a/workplans/WARDEN-WP-0034-attended-login-openbao-output.md +++ b/workplans/WARDEN-WP-0036-attended-login-openbao-output.md @@ -1,5 +1,5 @@ --- -id: WARDEN-WP-0034 +id: WARDEN-WP-0036 type: workplan title: "Accept contained OpenBao login output only after helper persistence" domain: infotech @@ -14,7 +14,7 @@ updated: "2026-09-01" ## Repair attended-login handoff ```task -id: WARDEN-WP-0034-T01 +id: WARDEN-WP-0036-T01 status: done priority: high ``` @@ -27,7 +27,7 @@ failure. ## Verify live contained operation ```task -id: WARDEN-WP-0034-T02 +id: WARDEN-WP-0036-T02 status: done priority: high ``` From 529feeac49a8cde94f68776fc45557c90e2a5a5a Mon Sep 17 00:00:00 2001 From: repo-manager Date: Tue, 1 Sep 2026 00:51:59 +0200 Subject: [PATCH 28/57] repo.work.assign_missing_identifiers source: repo-manager reason: deterministic projection registration Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- workplans/ADHOC-2026-06-27.md | 1 + workplans/ADHOC-2026-06-29.md | 1 + workplans/ADHOC-2026-08-11.md | 3 +++ workplans/ADHOC-2026-08-17.md | 4 ++++ .../WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | 3 +++ workplans/WARDEN-WP-0036-attended-login-openbao-output.md | 3 +++ workplans/archived/260707-ADHOC-2026-07-07.md | 1 + 7 files changed, 16 insertions(+) diff --git a/workplans/ADHOC-2026-06-27.md b/workplans/ADHOC-2026-06-27.md index e7e0a23..d75908f 100644 --- a/workplans/ADHOC-2026-06-27.md +++ b/workplans/ADHOC-2026-06-27.md @@ -22,6 +22,7 @@ Low-risk opportunistic fixes completed directly during the consolidation session id: WARDEN-WP-ADHOC-2026-06-27-T01 status: done priority: medium +state_hub_task_id: "9176b560-8ca5-5143-888d-479857fe60f0" ``` issue-core reported (msg `70bcf238`) that the `warden` CLI on `~/.local/bin` lacked diff --git a/workplans/ADHOC-2026-06-29.md b/workplans/ADHOC-2026-06-29.md index d078876..b46be0f 100644 --- a/workplans/ADHOC-2026-06-29.md +++ b/workplans/ADHOC-2026-06-29.md @@ -20,6 +20,7 @@ state_hub_workstream_id: "13fa845f-852e-55ec-a2a5-2296996e0216" id: WARDEN-WP-ADHOC-2026-06-29-T01 status: done priority: medium +state_hub_task_id: "62540533-f4ca-5176-9237-32adbeb292ee" ``` flex-auth (msg `ea00620b`) asked ops-warden to help close FLEX-WP-0007 T4 (joint OpenBao diff --git a/workplans/ADHOC-2026-08-11.md b/workplans/ADHOC-2026-08-11.md index a25c69f..3d858fe 100644 --- a/workplans/ADHOC-2026-08-11.md +++ b/workplans/ADHOC-2026-08-11.md @@ -20,6 +20,7 @@ state_hub_workstream_id: "9f99cc64-4682-5f20-b13e-89af2b6f7c70" id: WARDEN-WP-ADHOC-2026-08-11-T01 status: done priority: medium +state_hub_task_id: "0771d121-278c-556e-9509-841cf6e657c3" ``` rapp-postgres (msg `96907986`, residual from RAPP-POSTGRES-WP-0002-T04) reported the @@ -41,6 +42,7 @@ an anchor that does not exist. The intended heading is `## Routing catalog index id: WARDEN-WP-ADHOC-2026-08-11-T02 status: done priority: medium +state_hub_task_id: "0ed58145-732f-5102-b6a8-b931d9b6ba08" ``` `fix-consistency` flagged 11 unread messages older than 3 days, two of them as possible @@ -74,6 +76,7 @@ read. Inbox is now empty. id: WARDEN-WP-ADHOC-2026-08-11-T03 status: done priority: medium +state_hub_task_id: "337ae793-c6b0-59e9-8a07-3a7ccba237aa" ``` secrets-engine (msg `80456912`, 2026-06-29) is holding a validated non-mutating dry-run diff --git a/workplans/ADHOC-2026-08-17.md b/workplans/ADHOC-2026-08-17.md index 1b0b62f..65d7595 100644 --- a/workplans/ADHOC-2026-08-17.md +++ b/workplans/ADHOC-2026-08-17.md @@ -24,6 +24,7 @@ workload-identity owner. id: WARDEN-WP-ADHOC-2026-08-17-T01 status: done priority: high +state_hub_task_id: "04a2f8f9-e70b-5eed-ad87-343c8f9ef501" ``` flex-auth (msg `130a148c`, FLEX-WP-0015 T02) reported that `POST /v1/check` and @@ -71,6 +72,7 @@ Their four questions answered in `wiki/NetKingdomSecurityMap.md` id: WARDEN-WP-ADHOC-2026-08-17-T02 status: done priority: medium +state_hub_task_id: "0e815282-2fad-5c8d-be34-398e492737d0" ``` user-engine (msg `2af4a124`) answered the open confirm question on @@ -96,6 +98,7 @@ consumes it and has no authority over custody, rotation, or issuance. id: WARDEN-WP-ADHOC-2026-08-17-T03 status: done priority: medium +state_hub_task_id: "e21781d9-a35d-5916-b335-d12131f97a22" ``` key-cape (msg `099b7cba`) acknowledged the lane staying interim on ops-warden. @@ -118,6 +121,7 @@ workplan. id: WARDEN-WP-ADHOC-2026-08-17-T04 status: done priority: low +state_hub_task_id: "b15724e0-c27a-5260-a810-4dd25bff2228" ``` - [x] `uv run pytest -q` → 338 passed, 4 deselected (routing anchor + no-double-source diff --git a/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md b/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md index af56f05..1210b15 100644 --- a/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md +++ b/workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md @@ -9,6 +9,7 @@ owner: codex topic_slug: policy-nexus-forgejo-source-read created: "2026-09-01" updated: "2026-09-01" +state_hub_workstream_id: "45aec8d3-94b3-586e-b019-a47e656efafa" --- ## Register the exact high-risk lane @@ -17,6 +18,7 @@ updated: "2026-09-01" id: WARDEN-WP-0035-T01 status: done priority: high +state_hub_task_id: "dd84f2be-0143-540c-9c16-74f0fd129260" ``` Add the exact OpenBao path, field, OIDC role, owner pointer, and rotation @@ -29,6 +31,7 @@ resolvable while remaining subject to Warden's high-risk agent read boundary. id: WARDEN-WP-0035-T02 status: done priority: high +state_hub_task_id: "1fa8f778-3e46-5f44-86c4-cab8628b7e60" ``` Pass catalog, route-selection, proxy, and policy tests; reinstall the CLI; prove diff --git a/workplans/WARDEN-WP-0036-attended-login-openbao-output.md b/workplans/WARDEN-WP-0036-attended-login-openbao-output.md index 8607194..594b5ae 100644 --- a/workplans/WARDEN-WP-0036-attended-login-openbao-output.md +++ b/workplans/WARDEN-WP-0036-attended-login-openbao-output.md @@ -9,6 +9,7 @@ owner: codex topic_slug: attended-login-openbao-output created: "2026-09-01" updated: "2026-09-01" +state_hub_workstream_id: "d844c96e-152d-53fa-bff6-e072125ef66c" --- ## Repair attended-login handoff @@ -17,6 +18,7 @@ updated: "2026-09-01" id: WARDEN-WP-0036-T01 status: done priority: high +state_hub_task_id: "7eb8b9c9-1285-5ada-a17b-1d5bfbb8ba59" ``` Allow a successful OpenBao login to proceed when its output is fully contained @@ -30,6 +32,7 @@ failure. id: WARDEN-WP-0036-T02 status: done priority: high +state_hub_task_id: "d22bab05-c38b-561f-95de-6c146ce7c6cf" ``` Run the proxy regression suite, reinstall the CLI, and complete one governed diff --git a/workplans/archived/260707-ADHOC-2026-07-07.md b/workplans/archived/260707-ADHOC-2026-07-07.md index fc02506..31e9d27 100644 --- a/workplans/archived/260707-ADHOC-2026-07-07.md +++ b/workplans/archived/260707-ADHOC-2026-07-07.md @@ -22,6 +22,7 @@ state_hub_workstream_id: "90568b1e-8395-5c67-9c69-851ed08ff3d3" id: WARDEN-WP-ADHOC-2026-07-07-T01 status: done priority: high +state_hub_task_id: "bf985c95-bea6-5057-94f9-9cfa7e1c9dd8" ``` `warden access` failed on `reuse-surface-hub-write-token` because `shlex.split` From 8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 01:27:55 +0200 Subject: [PATCH 29/57] chore: refresh Warden work-record indexes Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- .repo-manager/index.json | 171 ++++++++++++++++++++++++++++++++------- WORK-RECORDS.md | 6 ++ 2 files changed, 150 insertions(+), 27 deletions(-) diff --git a/.repo-manager/index.json b/.repo-manager/index.json index 95e4806..ae60c11 100644 --- a/.repo-manager/index.json +++ b/.repo-manager/index.json @@ -1,10 +1,10 @@ { "schema": "repo_manager.index.v1", - "slug": "layer-model-v03-review", + "slug": "ops-warden", "repo_root": "/home/worsch/ops-warden", - "head_sha": "f289465b90fcdffe2c8bf4a51cc71607747126f9", - "observed_at": "2026-08-28T20:40:26.143458Z", - "source_fingerprint": "a9ae0d530804e039aae34b4902d8609723e9bba4385a501b9a4027911c3b6302", + "head_sha": "529feeac49a8cde94f68776fc45557c90e2a5a5a", + "observed_at": "2026-08-31T22:59:28.638497Z", + "source_fingerprint": "6f60b2dbcb864a3f8e45a97c247ab142a765487187e67edf52bdb57027087bd4", "source_files": [ ".repo-classification.yaml", "INTENT.md", @@ -31,6 +31,9 @@ "workplans/WARDEN-WP-0031-policy-caller-identity.md", "workplans/WARDEN-WP-0032-security-zones.md", "workplans/WARDEN-WP-0033-native-lane-handoff.md", + "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md", + "workplans/WARDEN-WP-0036-attended-login-openbao-output.md", "workplans/archived/260515-WARDEN-WP-0001-initial-implementation.md", "workplans/archived/260515-WARDEN-WP-0002-correctness-and-completeness.md", "workplans/archived/260515-WARDEN-WP-0003-test-coverage-and-quality.md", @@ -65,7 +68,7 @@ "status": "done", "title": "T01 \u2014 Fix stale `warden` CLI install + make it usable outside the repo", "source_path": "workplans/ADHOC-2026-06-27.md", - "uuid": null, + "uuid": "9176b560-8ca5-5143-888d-479857fe60f0", "parent_id": "WARDEN-WP-ADHOC-2026-06-27", "extra": {} }, @@ -85,7 +88,7 @@ "status": "done", "title": "T01 \u2014 Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4)", "source_path": "workplans/ADHOC-2026-06-29.md", - "uuid": null, + "uuid": "62540533-f4ca-5176-9237-32adbeb292ee", "parent_id": "WARDEN-WP-ADHOC-2026-06-29", "extra": {} }, @@ -105,7 +108,7 @@ "status": "done", "title": "T01 \u2014 Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite)", "source_path": "workplans/ADHOC-2026-08-11.md", - "uuid": null, + "uuid": "0771d121-278c-556e-9509-841cf6e657c3", "parent_id": "WARDEN-WP-ADHOC-2026-08-11", "extra": {} }, @@ -115,7 +118,7 @@ "status": "done", "title": "T02 \u2014 Triage the stale ops-warden inbox (11 unread, C-28/C-29)", "source_path": "workplans/ADHOC-2026-08-11.md", - "uuid": null, + "uuid": "0ed58145-732f-5102-b6a8-b931d9b6ba08", "parent_id": "WARDEN-WP-ADHOC-2026-08-11", "extra": {} }, @@ -125,7 +128,7 @@ "status": "done", "title": "T03 \u2014 warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover", "source_path": "workplans/ADHOC-2026-08-11.md", - "uuid": null, + "uuid": "337ae793-c6b0-59e9-8a07-3a7ccba237aa", "parent_id": "WARDEN-WP-ADHOC-2026-08-11", "extra": {} }, @@ -145,7 +148,7 @@ "status": "done", "title": "T01 \u2014 Answer flex-auth: how should `/v1/check` authenticate its callers?", "source_path": "workplans/ADHOC-2026-08-17.md", - "uuid": null, + "uuid": "04a2f8f9-e70b-5eed-ad87-343c8f9ef501", "parent_id": "WARDEN-WP-ADHOC-2026-08-17", "extra": {} }, @@ -155,7 +158,7 @@ "status": "done", "title": "T02 \u2014 user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only", "source_path": "workplans/ADHOC-2026-08-17.md", - "uuid": null, + "uuid": "0e815282-2fad-5c8d-be34-398e492737d0", "parent_id": "WARDEN-WP-ADHOC-2026-08-17", "extra": {} }, @@ -165,7 +168,7 @@ "status": "done", "title": "T03 \u2014 key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker", "source_path": "workplans/ADHOC-2026-08-17.md", - "uuid": null, + "uuid": "e21781d9-a35d-5916-b335-d12131f97a22", "parent_id": "WARDEN-WP-ADHOC-2026-08-17", "extra": {} }, @@ -175,7 +178,7 @@ "status": "done", "title": "T04 \u2014 Session hygiene", "source_path": "workplans/ADHOC-2026-08-17.md", - "uuid": null, + "uuid": "b15724e0-c27a-5260-a810-4dd25bff2228", "parent_id": "WARDEN-WP-ADHOC-2026-08-17", "extra": {} }, @@ -1259,6 +1262,126 @@ "parent_id": "WARDEN-WP-0033", "extra": {} }, + { + "kind": "workplan", + "id": "WARDEN-WP-0034", + "status": "ready", + "title": "Layer model v0.7 conformance \u2014 state the deadline, bind the agent boundary, steward the estate's newest rule", + "source_path": "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "uuid": "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0034-T01", + "status": "todo", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "uuid": "8b3bdb9f-d2c2-5b3e-89e2-417bf3e37484", + "parent_id": "WARDEN-WP-0034", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0034-T02", + "status": "todo", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "uuid": "3318ee1a-b5d9-5d39-baf7-9c42a8bc7b55", + "parent_id": "WARDEN-WP-0034", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0034-T03", + "status": "todo", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "uuid": "a891b32c-b0a7-59f6-a5cd-977be65c09ca", + "parent_id": "WARDEN-WP-0034", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0034-T04", + "status": "todo", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "uuid": "94e73daa-f74d-51fd-8639-68896a4066ee", + "parent_id": "WARDEN-WP-0034", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0034-T05", + "status": "todo", + "title": "Tasks", + "source_path": "workplans/WARDEN-WP-0034-layer-model-v07-conformance.md", + "uuid": "7d1b3c82-9b96-5087-a53a-496212909029", + "parent_id": "WARDEN-WP-0034", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0035", + "status": "finished", + "title": "Register the Policy Nexus Forgejo source-read route", + "source_path": "workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md", + "uuid": "45aec8d3-94b3-586e-b019-a47e656efafa", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0035-T01", + "status": "done", + "title": "Register the exact high-risk lane", + "source_path": "workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md", + "uuid": "dd84f2be-0143-540c-9c16-74f0fd129260", + "parent_id": "WARDEN-WP-0035", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0035-T02", + "status": "done", + "title": "Verify routing and governed use", + "source_path": "workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md", + "uuid": "1fa8f778-3e46-5f44-86c4-cab8628b7e60", + "parent_id": "WARDEN-WP-0035", + "extra": {} + }, + { + "kind": "workplan", + "id": "WARDEN-WP-0036", + "status": "finished", + "title": "Accept contained OpenBao login output only after helper persistence", + "source_path": "workplans/WARDEN-WP-0036-attended-login-openbao-output.md", + "uuid": "d844c96e-152d-53fa-bff6-e072125ef66c", + "parent_id": null, + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0036-T01", + "status": "done", + "title": "Repair attended-login handoff", + "source_path": "workplans/WARDEN-WP-0036-attended-login-openbao-output.md", + "uuid": "7eb8b9c9-1285-5ada-a17b-1d5bfbb8ba59", + "parent_id": "WARDEN-WP-0036", + "extra": {} + }, + { + "kind": "task", + "id": "WARDEN-WP-0036-T02", + "status": "done", + "title": "Verify live contained operation", + "source_path": "workplans/WARDEN-WP-0036-attended-login-openbao-output.md", + "uuid": "d22bab05-c38b-561f-95de-6c146ce7c6cf", + "parent_id": "WARDEN-WP-0036", + "extra": {} + }, { "kind": "workplan", "id": "WARDEN-WP-0001", @@ -2165,7 +2288,7 @@ "status": "done", "title": "T01 \u2014 Roll out proxy pipe fix (be3b4a2)", "source_path": "workplans/archived/260707-ADHOC-2026-07-07.md", - "uuid": null, + "uuid": "bf985c95-bea6-5057-94f9-9cfa7e1c9dd8", "parent_id": "WARDEN-WP-ADHOC-2026-07-07", "extra": {} }, @@ -2204,7 +2327,7 @@ "status": "open", "title": "Review requested: security layer model v0.3 \u2014 and does maturity-engine absorb warden route gaps?", "source_path": "intakes/intakes.md", - "uuid": null, + "uuid": "01a04d97-94cd-7b49-8019-a91c7fce8adb", "parent_id": null, "extra": { "record": { @@ -2219,25 +2342,19 @@ "requested_by": "gate-house", "description": "v0.3 is proposed and changes sections 4, 9 and 13 only; the v0.2 assent record stands. Two new engines: approval-engine (section 9.4) and maturity-engine (section 9.5). THE QUESTION FOR YOU concerns section 5.3, which exists because you offered the amendment. v0.3 gives declared gaps an owner: maturity-engine takes the gap register with intended_owner, blocked_on and review dates, and section 13 now says the register in the standard is interim and should not outlive that engine. You offered warden route gaps and the 27 delegation catalog entries as reusable prior art. So the question is whether that machinery should MOVE, be MIRRORED, or STAY. Our tentative reading, which we want tested rather than accepted: routing is yours and stays yours \u2014 warden route find answers where a credential need goes, and that is lane knowledge, not maturity. What might move is the readiness half: whether a declared gap is still within its review date, and whether an intended owner has an engine surface yet. If splitting those creates two sources for one fact, that is worse than either option and we would rather hear it now. Your SSH-CA signing write would be tracked in maturity-engine as a declared gap with intended owner secrets-engine and a review date \u2014 that is reporting your own non-conformance to an engine, so we would rather you assent to it than discover it. Also note approval-engine (section 9.4): it owns the approval object, not the approval workflow, so ops-warden lanes needing approval consume a claim rather than implementing one. Assent, revision, or rejection acceptable.", "created": "2026-08-28T20:40:24.957468Z", - "updated": "2026-08-28T20:40:24.957468Z" + "updated": "2026-08-28T20:40:24.957468Z", + "state_hub_intake_id": "01a04d97-94cd-7b49-8019-a91c7fce8adb" } } } ], "events": [ { - "type": "repo.command.applied", - "command": "repo.work.create_intake", - "operation": "create", - "correlation_id": "676e92a9-dc2e-4101-a31c-a584962c25df", - "kind": "intake", - "id": "WARDEN-IN-0002", - "git_sha": "f289465b90fcdffe2c8bf4a51cc71607747126f9", - "files_touched": [ - "intakes/intakes.md" - ], + "type": "repo.reconciled", + "workplan_count": 41, + "task_count": 183, "source": "repo-manager", - "emitted_at": "2026-08-28T20:40:26.143617Z" + "emitted_at": "2026-08-31T22:59:28.638627Z" } ] } diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index d1e0eb4..80d81c3 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -31,6 +31,8 @@ | workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md | | workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | workplan | WARDEN-WP-0034 | ready | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | +| workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -135,5 +137,9 @@ | task | WARDEN-WP-0034-T03 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | | task | WARDEN-WP-0034-T04 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | | task | WARDEN-WP-0034-T05 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0035-T01 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | +| task | WARDEN-WP-0035-T02 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | +| task | WARDEN-WP-0036-T01 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | +| task | WARDEN-WP-0036-T02 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | | intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | | intake | WARDEN-IN-0002 | open | — | intakes/intakes.md | From 60cf826c4336de660a7a91fc302e55ac6a9f129b Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Fri, 4 Sep 2026 23:19:11 +0200 Subject: [PATCH 30/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-09-04: - update .custodian-brief.md for ops-warden Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06e40-0824-7e22-a781-6c13d8b75c65 --- .custodian-brief.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index 830ff11..5350f5b 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-08-31 22:49 UTC +**Last synced:** 2026-09-04 21:19 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -23,10 +23,12 @@ Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` **Open tasks:** - ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee` -## Inbox Hygiene +### Repoint the whynot-design npm lane to Forgejo +Progress: 1/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e` -**Stale unread:** 1 message(s) older than 3 day(s) — triage at session start. -**Missing thread_id:** 6 unread message(s) lack supersession chains. +**Open tasks:** +- ! Verify the existing credential against Forgejo `afc4d325` +- ! Prove routing and publication `a8b1b855` --- ## MCP Orientation (when available) From efabe526d86135591369e8621d1955c415479285 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Fri, 4 Sep 2026 23:31:05 +0200 Subject: [PATCH 31/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-09-04: - update .custodian-brief.md for ops-warden Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06e40-0824-7e22-a781-6c13d8b75c65 --- .custodian-brief.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index 5350f5b..83e0764 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,20 +2,16 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-09-04 21:19 UTC +**Last synced:** 2026-09-04 21:30 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams ### Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule -Progress: 0/5 done | workplan_id: `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef` +Progress: 4/5 done | workplan_id: `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef` **Open tasks:** -- · Tasks `8b3bdb9f` -- · Tasks `3318ee1a` -- · Tasks `a891b32c` -- · Tasks `94e73daa` -- · Tasks `7d1b3c82` +- ! Tasks `7d1b3c82` ### Tamper-resistant credential governance + mass rotation/lockdown (Strand B) Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` From 81f28ff81498ee2881ca98f0f903434d19cb6293 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 4 Sep 2026 23:39:11 +0200 Subject: [PATCH 32/57] feat(routing): repoint whynot npm lane to Forgejo Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06d83-1cbc-71f2-b0dc-e0f48cedae43 --- registry/routing/catalog.yaml | 14 ++-- tests/test_routing.py | 14 +++- wiki/playbooks/whynot-design-npm-publish.md | 37 +++++++-- ...-WP-0037-whynot-design-forgejo-npm-lane.md | 79 +++++++++++++++++++ 4 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 7b91b40..5811077 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -196,19 +196,19 @@ entries: lane: ceremony - id: whynot-design-npm-publish - title: whynot-design npm publish token (@whynot/design → coulomb Gitea registry) + title: whynot-design npm publish token (@whynot/design → coulomb Forgejo registry) # Publish rights to the package registry — a leaked token is a supply-chain write, not a read (WARDEN-WP-0032-T05). risk: high workload_ref: applicability: applicable unknown_reason: "whynot-design has not published an authoritative workload identity declaration." - need_keywords: [whynot-design, whynot, npm, publish, npm_auth_token, gitea, registry, coulomb, package] + need_keywords: [whynot-design, whynot, npm, publish, npm_auth_token, forgejo, registry, coulomb, package] owner_repo: railiance-platform subsystem: OpenBao warden_executes: false wiki_ref: wiki/playbooks/whynot-design-npm-publish.md#worker-checklist canon_ref: net-kingdom/docs/platform-identity-security-architecture.md - reviewed: "2026-06-29" + reviewed: "2026-09-04" status: active delegation: mode: native @@ -238,9 +238,9 @@ entries: owner: railiance-platform automatable: false steps: - - "In the coulomb Gitea/Forgejo registry, revoke the current @whynot/design publish token and generate a new one (scope: package read/write) for the whynot-design publish identity." + - "In the coulomb Forgejo registry, revoke the current @whynot/design publish token and generate a new one (scope: package read/write) for the whynot-design publish identity." - "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish NPM_AUTH_TOKEN=@file` (value from a mode-0600 file)." - - "Verify capabilities-safe, then confirm publish works via `secrets-engine exec --catalog whynot-design-npm-publish -- npm whoami` (value used, not printed)." + - "Verify capabilities-safe, then publish a fresh version and confirm it with Forgejo-supported `npm view @` through the governed execution lane (value used, not printed)." - id: policy-nexus-forgejo-source-read title: Policy Nexus Forgejo private-source repository read token @@ -325,8 +325,8 @@ entries: # point is to obtain one). warden runs it interactively as the caller and never # captures the resulting token — the owner tool writes it to the caller's store. lane: login - auth_method: "browser OIDC via key-cape / Keycloak" - fetch_command: "bao login -method=oidc role=" + auth_method: "browser OIDC via key-cape / Keycloak at auth/netkingdom" + fetch_command: "bao login -no-print -method=oidc -path=netkingdom role=" exec_capable: true - id: ops-bridge-tunnel diff --git a/tests/test_routing.py b/tests/test_routing.py index 1e5d691..8c319e1 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -144,13 +144,19 @@ def test_route_find_vault_token_ops_warden_prefers_broker_lane(): def test_whynot_design_npm_lane_is_concrete_and_resolvable(): - """The provisioned npm publish lane has no placeholders and reports resolvable.""" + """The provisioned Forgejo npm lane is exact, concrete, and resolvable.""" catalog = load_catalog(_repo_catalog()) e = catalog.get("whynot-design-npm-publish") assert e is not None and e.is_active and e.exec_capable assert e.resolvable is True + assert "Forgejo registry" in e.title + assert "forgejo" in e.need_keywords + assert "gitea" not in e.need_keywords assert "<" not in e.fetch_command and ">" not in e.fetch_command assert "platform/workloads/coulomb/whynot-design/npm-publish" in e.fetch_command + rotation = " ".join(e.rotation.steps) + assert "Forgejo-supported `npm view @`" in rotation + assert "Gitea" not in rotation def test_policy_nexus_source_read_lane_is_exact_high_risk_and_resolvable(): @@ -183,7 +189,11 @@ def test_generic_and_template_lanes_not_resolvable(): catalog = load_catalog(_repo_catalog()) # generic openbao lane has /; login lane has . assert catalog.get("openbao-api-key").resolvable is False - assert catalog.get("key-cape-oidc-login").resolvable is False + login = catalog.get("key-cape-oidc-login") + assert login.resolvable is False + assert login.fetch_command == ( + "bao login -no-print -method=oidc -path=netkingdom role=" + ) def test_platform_admin_login_lane_is_exact_and_non_value_bearing(): diff --git a/wiki/playbooks/whynot-design-npm-publish.md b/wiki/playbooks/whynot-design-npm-publish.md index af0c92a..4f26d05 100644 --- a/wiki/playbooks/whynot-design-npm-publish.md +++ b/wiki/playbooks/whynot-design-npm-publish.md @@ -1,11 +1,17 @@ # whynot-design npm publish token -Date: 2026-06-29 +Date: 2026-09-04 Catalog: `whynot-design-npm-publish` (status `active`, `resolvable: true`) Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f617fc) -The `NPM_AUTH_TOKEN` that publishes `@whynot/design` to the coulomb Gitea npm registry -(`https://gitea.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold +> **Rotation required (2026-09-04).** The OIDC role and OpenBao read path are +> healthy, but the stored credential failed a real Forgejo publish. Version +> `@whynot/design@0.4.2` was published and integrity-verified through the +> plan-authorized Forgejo admin recovery lane. Treat this dedicated lane as +> unverified for writes until its package token is rotated and re-proven. + +The `NPM_AUTH_TOKEN` that publishes `@whynot/design` to the coulomb Forgejo npm registry +(`https://forgejo.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold this token** — it is the access front door: `warden access` proxies the read from OpenBao **as the caller** and never persists, caches, or logs the value. @@ -42,13 +48,20 @@ this token** — it is the access front door: `warden access` proxies the read f for this lane (SECRETS-WP-0003, decision e6381a56); ops-warden routes to it: ```bash secrets-engine route whynot-design-npm-publish --json # pointer / readiness + secrets-engine exec --catalog whynot-design-npm-publish -- \ + npm view @whynot/design@ version \ + --registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/ secrets-engine exec --catalog whynot-design-npm-publish -- npm publish ``` **ops-warden transparent fallback** — same lane via the `warden access` proxy (fetches as - you, holds nothing). Field-verified flags (whynot-design, @whynot/design@0.4.0): + you, holds nothing). The project `.npmrc` must point both the `@whynot` scope and + token fragment at `forgejo.coulomb.social`: ```bash # --exec needs the env-var name. The zone-aware policy gate always runs first. + warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \ + --exec -- npm view @whynot/design@ version \ + --registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/ warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \ --exec -- npm publish warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN --fetch @@ -63,9 +76,19 @@ this token** — it is the access front door: `warden access` proxies the read f `resolvable: true` means the lane is concrete and `--fetch` will run; a template lane reports `false`. -4. **Publish is outward-facing and immutable.** `npm publish` is irreversible and public. - Even once the token resolves, hold for an explicit operator "yes, publish" — do not - auto-run it from an agent. +4. **Publish is outward-facing and immutable.** Before publishing, confirm that + `package.json#publishConfig.registry` is exactly the Forgejo URL above, verify the + intended version and `npm pack --dry-run` contents, and obtain explicit operator + approval. `npm publish` is irreversible; do not auto-run it from an agent. + +5. **Record non-secret release evidence.** After the owner publishes, record only the + package coordinate (for example `@whynot/design@0.4.2`), registry URL, authenticated + install result, and release-content verification. Never record the token or npm + configuration generated for its delivery. + + Forgejo advertises `npm view`, search, install, publish, unpublish, and dist-tag + support; it does not advertise `npm whoami`. Use the exact-version lookup above + rather than treating `npm whoami` failure as a credential failure. --- diff --git a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md new file mode 100644 index 0000000..64a9501 --- /dev/null +++ b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md @@ -0,0 +1,79 @@ +--- +id: WARDEN-WP-0037 +type: workplan +title: "Repoint the whynot-design npm lane to Forgejo" +domain: infotech +repo: ops-warden +status: active +owner: codex +topic_slug: whynot-design-forgejo-npm-lane +created: "2026-09-04" +updated: "2026-09-04" +state_hub_workstream_id: "42a097db-1c24-558e-a724-030bb2b4443e" +--- + +## Verify the existing credential against Forgejo + +```task +id: WARDEN-WP-0037-T01 +status: done +priority: high +state_hub_task_id: "afc4d325-1c6d-5c25-aaf7-2118aa8f86c6" +``` + +Use only the governed execution transport to test the existing publish identity +against the canonical Forgejo npm endpoint. Do not print or persist the token. + +2026-09-04: `warden plan` classified the exact verification as autonomous and +selected this lane. A contained login using the documented +`whynot-design-workload-kv-read` role proved `read` on the exact OpenBao data +path, and the governed child proved the secret is present. A real 0.4.2 publish +through that credential then failed. No value was printed or persisted and each +contained session self-revoked. The lane therefore needs token rotation, not an +OpenBao policy repair. + +## Repoint the catalog and playbook + +```task +id: WARDEN-WP-0037-T02 +status: done +priority: high +state_hub_task_id: "7ceb2f74-496c-54e8-bf5a-3c49f994ff23" +``` + +Replace the retired Gitea endpoint and discovery language with Forgejo while +preserving the established OpenBao path, field, and least-privilege boundary. +This delivers the npm facet of routed intake +`01a06e07-f2f9-7586-9745-b359eb0025b6`; its inter-hub SSH facet remains separate. + +Completed 2026-09-04. The catalog title, discovery keywords, rotation guidance, +and worker playbook now name the canonical Forgejo registry at +`https://forgejo.coulomb.social/api/packages/coulomb/npm/`. The OpenBao path, +field, owner, native-exec pointer, and high-risk boundary are unchanged. A +regression assertion prevents the retired Gitea discovery term and guidance +from returning. + +## Prove routing and publication + +```task +id: WARDEN-WP-0037-T03 +status: wait +priority: high +state_hub_task_id: "a8b1b855-ab34-5835-b9fd-5f48bc0b6817" +``` + +Pass catalog and routing tests, verify the checkout route, and record the exact +Forgejo package release proven through the lane. + +2026-09-04: repo-local verification is complete: the checkout route ranks +`whynot-design-npm-publish` first for a Forgejo npm publish need, reports the +canonical Forgejo title and verification command, all focused +routing/access/proxy tests pass (`145 passed`). + +The sibling repo's `@whynot/design@0.4.2` release (105 files; IR +`designVersion` 0.4.2; five visual tests pass) was published through the +plan-authorized `forgejo-admin-api-token` one-time recovery lane. An +authenticated exact-version lookup proved that Forgejo's remote integrity +matches the local dry-run. T03 waits only on rotating the dedicated package +token and proving the next fresh version through `whynot-design-npm-publish`; +the package migration itself is complete. From 89b117f640f62f73ae9ad3c08d58078d0f36a9f8 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 4 Sep 2026 23:42:24 +0200 Subject: [PATCH 33/57] docs(workplan): persist Forgejo token rotation gate Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a06d83-1cbc-71f2-b0dc-e0f48cedae43 --- workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md index 64a9501..a1db405 100644 --- a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md +++ b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md @@ -59,6 +59,8 @@ from returning. id: WARDEN-WP-0037-T03 status: wait priority: high +needs_human: true +intervention_note: "Rotate the dedicated Forgejo package token, then prove a fresh publish and exact npm view through this lane." state_hub_task_id: "a8b1b855-ab34-5835-b9fd-5f48bc0b6817" ``` From 00145d705e88eeb9a880a85a3cc15a5205dde2d6 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 5 Sep 2026 01:19:48 +0200 Subject: [PATCH 34/57] feat: complete local layer model v0.7 conformance work Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4 --- ...-agent-read-boundary-on-high-risk-lanes.md | 27 ++++++--- layer.yaml | 4 +- pep-stance.yaml | 39 +++++++++++- registry/routing/catalog.yaml | 18 ++++++ src/warden/access.py | 29 ++++++++- src/warden/cli.py | 5 +- tests/test_access.py | 29 ++++++++- tests/test_layer_conformance.py | 19 ++++++ tests/test_proxy.py | 21 +++++++ tests/test_routing.py | 12 ++++ tests/test_workload_join.py | 7 ++- .../playbooks/netkingdom-layer-declaration.md | 37 ++++++++++++ ...DEN-WP-0034-layer-model-v07-conformance.md | 59 ++++++++++++++++--- 13 files changed, 279 insertions(+), 27 deletions(-) create mode 100644 wiki/playbooks/netkingdom-layer-declaration.md diff --git a/docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md b/docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md index 08e5516..5069487 100644 --- a/docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md +++ b/docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md @@ -5,15 +5,15 @@ title: "ADR-0004 — High-risk lanes refuse raw value streaming to agent session domain: infotech repo: ops-warden status: accepted -version: "1.0" -revision: "1" +version: "1.1" +revision: "2" owner: ops-warden binds: "ops-warden; any agent runtime calling warden access" created: "2026-07-20" -updated: "2026-08-18" -last_reviewed: "2026-08-18" +updated: "2026-09-04" +last_reviewed: "2026-09-04" review_interval: 6m -enforced_by: "src/warden/access.py (exit 7); OpenBao policy agent-high-risk-boundary" +enforced_by: "src/warden/access.py + src/warden/cli.py (exit 7); OpenBao policy agent-high-risk-boundary" supersedes: "" successor: "" --- @@ -48,6 +48,13 @@ refuses to stream the raw value and exits 7.** The agent is not blocked from doi its work — `--out`, `--exec`, `--wrap` and `--fingerprint` all remain available. It is blocked from doing its work *in a way that writes the secret into a transcript*. +**Revision 2 also recognizes the issued coding-agent subject.** When +`WARDEN_POLICY_SUBJECT` is the KeyCape-issued +`service:codex:railiance-platform` identity, the same refusal applies without +`WARDEN_AGENT_ID`. The legacy variable remains a fail-toward-safety fallback. +Ops-warden reads this identity marker; it does not validate the token or decide +whether the principal is authorized. + **The boundary is enforced at the credential store as well as at the CLI.** The OpenBao policy `agent-high-risk-boundary` denies data-read on those paths for agent tokens, allowing metadata and capabilities only. A control that lives solely in our @@ -73,10 +80,12 @@ retry correctly. Changing it is a breaking change to every agent runtime. **`risk: high` becomes a load-bearing catalog field** rather than documentation. Classifying a lane now changes runtime behaviour, so it must be set deliberately. -**We accept that `--unsafe-stdout` still exists for humans.** The boundary keys on -`WARDEN_AGENT_ID`, so an agent that does not set it is not caught. That is a known -limit: this ADR raises the floor for cooperating runtimes and hardens the store -behind them; it does not claim to stop a determined caller. +**We accept that `--unsafe-stdout` still exists for humans.** The CLI recognizes the +issued subject when the runtime presents it and otherwise relies on +`WARDEN_AGENT_ID`; both markers remain caller-visible and therefore advisory. This +ADR raises the floor for cooperating runtimes and hardens the store behind them; it +does not claim to stop a determined caller. The OpenBao +`agent-high-risk-boundary` policy is the enforced control. ## Related diff --git a/layer.yaml b/layer.yaml index be3ec97..ea8e961 100644 --- a/layer.yaml +++ b/layer.yaml @@ -1,6 +1,6 @@ # ops-warden — NetKingdom security layer declaration # -# Framework: net-kingdom/canon/standards/security-layer-model_v0.4.md +# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md # Assent: docs/adr/ADR-0010 (ops-warden's own voice, per §11 "who must declare") # Validate: python3 scripts/check_layer_conformance.py # @@ -14,7 +14,7 @@ schema_version: "0.1" framework: netkingdom-security-layer-model -standard_version: "0.4" +standard_version: "0.7" repository: ops-warden layer: staff declared_by: docs/adr/ADR-0010 diff --git a/pep-stance.yaml b/pep-stance.yaml index cf4e83f..6364a80 100644 --- a/pep-stance.yaml +++ b/pep-stance.yaml @@ -1,6 +1,6 @@ # ops-warden — PEP unreachable-engine stance map # -# Framework: net-kingdom/canon/standards/security-layer-model_v0.6.md §6.4, §9.3 +# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md §6.4, §9.3, §9.7 # Rule of record: docs/adr/ADR-0009 # Validate: pytest tests/test_layer_conformance.py -k stance # @@ -18,7 +18,7 @@ schema_version: "0.1" framework: netkingdom-security-layer-model -standard_version: "0.6" +standard_version: "0.7" repository: ops-warden pep_shape: true declared_by: docs/adr/ADR-0009 @@ -67,3 +67,38 @@ input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebu reconstructability: bound: "§9.6 attributive — completeness is not claimed" declared_at: wiki/AuditTrail.md + +# §9.7.2 — the certificate-verification PEP has no active revocation channel. +# A previously allowed certificate remains usable until its enforced TTL expires, +# so the visibility deadline is exactly the maximum issued lifetime per actor type. +revocation_visibility: + boundary: "SSH certificate verification at the target host" + mechanism: ttl_expiry + revocation_channel: none + deadline_hours: + adm: 48 + agt: 24 + atm: 8 + note: >- + No CRL or KRL distribution exists. Host-side authorization belongs to + railiance-infra; shortening or actively recalling this window is a joint + design question, not an implied capability of ops-warden. + +# §9.6 — signing evidence is attributive, not load-bearing. Measurement as of +# 2026-09-04 found three signature records across 79 calendar days, concentrated +# on only two active days (1 event on 2026-06-17; 2 within 40 minutes on +# 2026-08-22). That bursty operator-driven sample cannot support a useful rate +# threshold, so cadence is explicitly deferred instead of fabricated. +emission_cadence: + classification: attributive + status: deferred + measured_as_of: "2026-09-04" + observed_window: + first: "2026-06-17T23:18:33Z" + last: "2026-09-04T21:21:30Z" + signature_records: 3 + active_signature_days: 2 + daily_counts: + "2026-06-17": 1 + "2026-08-22": 2 + reason: "operator-driven issuance is too sparse and bursty for a meaningful rate threshold" diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 5811077..3be856d 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -40,6 +40,24 @@ version: 1 entries: + - id: netkingdom-layer-declaration + title: NetKingdom layer declaration and PEP stance-map route + risk: standard + workload_ref: + applicability: not-applicable + reason: "Repository conformance guidance, not a workload credential or runtime action." + need_keywords: [how, declare, layer, declaration, netkingdom, security, companion, pep, pep-shaped, stance, conformance, tooling, staff, engine] + owner_repo: ops-warden + subsystem: NetKingdom layer-conformance routing + warden_executes: false + wiki_ref: wiki/playbooks/netkingdom-layer-declaration.md#worker-checklist + canon_ref: net-kingdom/canon/standards/security-layer-model_v0.7.md + reviewed: "2026-09-04" + status: active + delegation: + mode: permanent + reviewed: "2026-09-04" + - id: ssh-cert-host-access title: Short-lived SSH certificate for host / ops reachability # Emits a signed certificate — a public artifact. The private key never leaves the caller (WARDEN-WP-0032-T05). diff --git a/src/warden/access.py b/src/warden/access.py index ad3af27..0b60704 100644 --- a/src/warden/access.py +++ b/src/warden/access.py @@ -12,13 +12,21 @@ reuses `expand_handoff` to build the command it runs as the caller. """ from __future__ import annotations +import os from dataclasses import dataclass -from typing import Optional +from typing import Mapping, Optional from warden.config import ConfigError, load_config from warden.routing.models import RouteEntry +# KeyCape KEY-WP-0009-T03 is the issued coding-agent identity available today. +# This is intentionally an exact subject allowlist, not a guess based on every +# ``service:*`` identity. OpenBao validates and enforces the credential; this +# module only recognizes the already-issued subject for the advisory CLI guard. +ISSUED_AGENT_SUBJECTS = frozenset({"service:codex:railiance-platform"}) + + @dataclass class ExpandedHandoff: """Handoff templates with `` substituted when a domain is supplied. @@ -34,6 +42,25 @@ class ExpandedHandoff: exec_capable: bool +def agent_read_boundary_identity( + environ: Mapping[str, str] | None = None, +) -> str | None: + """Return the issued/fallback agent marker used by the advisory read guard. + + ``WARDEN_POLICY_SUBJECT`` carries the principal identity used by the policy + request. When it names an issued coding-agent subject, prefer it over the + self-declared legacy marker. This function does not validate a token or + render an authorization decision; OpenBao's agent policy is the enforced + boundary. ``WARDEN_AGENT_ID`` remains a fail-toward-safety fallback. + """ + env = os.environ if environ is None else environ + issued_subject = str(env.get("WARDEN_POLICY_SUBJECT") or "").strip() + if issued_subject in ISSUED_AGENT_SUBJECTS: + return issued_subject + fallback = str(env.get("WARDEN_AGENT_ID") or "").strip() + return fallback or None + + def _sub_domain(value: Optional[str], domain: Optional[str]) -> Optional[str]: if value and domain: return value.replace("", domain) diff --git a/src/warden/cli.py b/src/warden/cli.py index b28d6da..dcea9c2 100644 --- a/src/warden/cli.py +++ b/src/warden/cli.py @@ -2,7 +2,6 @@ from __future__ import annotations import json -import os from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Annotated, List, Optional @@ -1309,7 +1308,9 @@ def _access_proxy( # T04 — agent identity on a high-risk lane: never stream raw secret data. # Agents may use sanctioned transports (--out / --exec / --wrap / --fingerprint). - agent_id = os.environ.get("WARDEN_AGENT_ID", "").strip() + from warden.access import agent_read_boundary_identity + + agent_id = agent_read_boundary_identity() raw_value_stream = ( not is_login and not do_exec and not wrap and not out_path and not fingerprint ) diff --git a/tests/test_access.py b/tests/test_access.py index f5765d9..5e10a7d 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -6,7 +6,7 @@ from pathlib import Path from typer.testing import CliRunner -from warden.access import expand_handoff, policy_gate_status +from warden.access import agent_read_boundary_identity, expand_handoff, policy_gate_status from warden.cli import app from warden.routing.models import RouteEntry @@ -64,6 +64,33 @@ def test_policy_gate_status_no_config(monkeypatch, tmp_path): assert "advisory" in policy_gate_status() +def test_issued_coding_agent_subject_drives_read_boundary_without_legacy_marker(): + env = {"WARDEN_POLICY_SUBJECT": "service:codex:railiance-platform"} + assert agent_read_boundary_identity(env) == "service:codex:railiance-platform" + + +def test_legacy_agent_marker_remains_fail_toward_safety_fallback(): + env = {"WARDEN_AGENT_ID": "grok"} + assert agent_read_boundary_identity(env) == "grok" + + +def test_non_agent_policy_subject_does_not_manufacture_agent_identity(): + env = {"WARDEN_POLICY_SUBJECT": "iam:bernd"} + assert agent_read_boundary_identity(env) is None + + +def test_explicit_empty_environment_does_not_inherit_process_identity(monkeypatch): + monkeypatch.setenv("WARDEN_AGENT_ID", "codex") + monkeypatch.setenv("WARDEN_POLICY_SUBJECT", "service:codex:railiance-platform") + assert agent_read_boundary_identity({}) is None + assert agent_read_boundary_identity() == "service:codex:railiance-platform" + + +def test_operator_subject_does_not_override_legacy_agent_marker(): + env = {"WARDEN_POLICY_SUBJECT": "iam:bernd", "WARDEN_AGENT_ID": "codex"} + assert agent_read_boundary_identity(env) == "codex" + + # --- CLI ------------------------------------------------------------------- def test_access_advisory_output(monkeypatch): diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py index 59284aa..b10b7f7 100644 --- a/tests/test_layer_conformance.py +++ b/tests/test_layer_conformance.py @@ -30,6 +30,7 @@ class TestDeclaration: d = _decl() assert d["repository"] == "ops-warden" assert d["layer"] == "staff" + assert d["standard_version"] == "0.7" # §11: "only the repository's own file, in its own voice, conforms." assert d["declared_by"] == "docs/adr/ADR-0010" @@ -131,3 +132,21 @@ class TestPepStanceMap: """§6.4 obligation 2: caching an input claim is permitted; caching the answer is a second decision point deciding early (§6.1).""" assert self._stance()["verdict_caching"] == "none" + + def test_revocation_visibility_deadline_equals_enforced_ttl_policy(self): + """§9.7.2: a published replay window must not drift from issuance.""" + from warden.models import ActorType, MAX_TTL_HOURS + + published = self._stance()["revocation_visibility"] + expected = {actor.value: MAX_TTL_HOURS[actor] for actor in ActorType} + assert published["deadline_hours"] == expected + assert published["mechanism"] == "ttl_expiry" + assert published["revocation_channel"] == "none" + + def test_attributive_emission_cadence_deferral_carries_measurement(self): + cadence = self._stance()["emission_cadence"] + assert cadence["classification"] == "attributive" + assert cadence["status"] == "deferred" + assert cadence["observed_window"]["signature_records"] == 3 + assert cadence["observed_window"]["active_signature_days"] == 2 + assert cadence["reason"] diff --git a/tests/test_proxy.py b/tests/test_proxy.py index b67b122..9955add 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -590,6 +590,27 @@ def test_access_agent_high_risk_raw_stream_refused(tmp_path, monkeypatch): assert "agent read-boundary" in r.output.lower() or "risk=high" in r.output.lower() +def test_access_issued_agent_subject_refuses_raw_stream_without_legacy_marker( + tmp_path, monkeypatch +): + """WP-0034 T02: the issued KeyCape subject is sufficient for exit 7.""" + _proxy_env(monkeypatch, tmp_path) + monkeypatch.setenv("VAULT_TOKEN", "caller-token") + monkeypatch.delenv("WARDEN_AGENT_ID", raising=False) + monkeypatch.setenv( + "WARDEN_POLICY_SUBJECT", "service:codex:railiance-platform" + ) + r = runner.invoke( + app, + [ + "access", "railiance-backup-offsite-lane", + "--fetch", "--unsafe-stdout", + ], + ) + assert r.exit_code == 7, r.output + assert "service:codex:railiance-platform" in r.output + + def test_access_agent_high_risk_fingerprint_allowed(tmp_path, monkeypatch): """Agents may use --fingerprint on high-risk lanes (no raw value).""" _proxy_env(monkeypatch, tmp_path) diff --git a/tests/test_routing.py b/tests/test_routing.py index 8c319e1..cd3d6e8 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -143,6 +143,18 @@ def test_route_find_vault_token_ops_warden_prefers_broker_lane(): assert matches[0].id == "ops-warden-warden-sign-token" +def test_route_find_layer_declaration_resolves_to_reference_playbook(): + catalog = load_catalog(_repo_catalog()) + entry = catalog.find("how do I declare my layer", limit=1)[0] + assert entry.id == "netkingdom-layer-declaration" + assert entry.owner_repo == "ops-warden" + assert entry.wiki_ref.endswith( + "netkingdom-layer-declaration.md#worker-checklist" + ) + assert entry.canon_ref.endswith("security-layer-model_v0.7.md") + assert entry.exec_capable is False + + def test_whynot_design_npm_lane_is_concrete_and_resolvable(): """The provisioned Forgejo npm lane is exact, concrete, and resolvable.""" catalog = load_catalog(_repo_catalog()) diff --git a/tests/test_workload_join.py b/tests/test_workload_join.py index d87e5dd..170cd8c 100644 --- a/tests/test_workload_join.py +++ b/tests/test_workload_join.py @@ -14,9 +14,10 @@ def test_repo_catalog_uses_only_explicit_workload_references(): assert report["ok"] is True assert len(report["resolved"]) == 3 assert len(report["unknown"]) == 18 - # 11 since WARDEN-WP-0033: the two NetKingdom SSO lanes (c374d41) are - # provider/control-plane credentials, not workload delivery lanes. - assert len(report["not_applicable"]) == 11 + # 12 since WARDEN-WP-0034: the layer-declaration route is repository + # conformance guidance, not a workload delivery lane. The two NetKingdom SSO + # lanes (c374d41) remain provider/control-plane credentials as well. + assert len(report["not_applicable"]) == 12 assert {row["lane"] for row in report["resolved"]} == { "ops-warden-warden-sign-token", "issue-core-ingestion-api-key", diff --git a/wiki/playbooks/netkingdom-layer-declaration.md b/wiki/playbooks/netkingdom-layer-declaration.md new file mode 100644 index 0000000..dc0cd77 --- /dev/null +++ b/wiki/playbooks/netkingdom-layer-declaration.md @@ -0,0 +1,37 @@ +# NetKingdom layer declaration route + +Date: 2026-09-04 +Catalog: `netkingdom-layer-declaration` +Doctrine owner: `gate-house` +Path steward: `ops-warden` + +This page is a route from the accepted NetKingdom security companion to the +reference declaration artifacts. It does not redefine the layer model. + +## Worker checklist + +1. Read `net-kingdom/SECURITY-COMPANION.md`, then use the accepted + `net-kingdom/canon/standards/security-layer-model_v0.7.md` for the normative + layer, Tooling-contact, and PEP obligations. +2. Use ops-warden's `layer.yaml` as the machine-readable declaration reference. + Adapt its repository, layer, contacts, and ownership facts; do not copy + ops-warden-specific claims as your own. +3. If the repository is PEP-shaped, use `pep-stance.yaml` as the stance-map + reference and publish the resulting path in the repository's layer declaration. +4. Adapt `scripts/check_layer_conformance.py` and + `tests/test_layer_conformance.py`, then run both checks in the declaring repo. +5. Send the declaration and any stance-map inventory pointer to `gate-house` for + doctrine/register review. Route credential or operational-lane questions back + through `warden route`; do not place doctrine in the routing catalog. + +Reference checks in this checkout: + +```bash +python3 scripts/check_layer_conformance.py +pytest tests/test_layer_conformance.py +``` + +## Ownership boundary + +`gate-house` owns what the model requires. Each repository owns the truth of its +own declaration. `ops-warden` owns only this discoverable path to those sources. diff --git a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md index 8e52f00..7d779ab 100644 --- a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md +++ b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md @@ -4,14 +4,14 @@ type: workplan title: "Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule" domain: infotech repo: ops-warden -status: ready +status: active owner: ops-warden topic_slug: netkingdom planning_priority: P1 depends_on_workplans: - WARDEN-WP-0030 created: "2026-08-29" -updated: "2026-08-29" +updated: "2026-09-05" state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef" --- @@ -36,7 +36,7 @@ completed directly; none of these qualify. ```task id: WARDEN-WP-0034-T01 -status: todo +status: done priority: high state_hub_task_id: "8b3bdb9f-d2c2-5b3e-89e2-417bf3e37484" ``` @@ -60,9 +60,16 @@ of a revocation channel is stated rather than left to inference. **Not in scope:** shortening it. Whether 48h is acceptable is a joint question with `railiance-infra` (KRL distribution) and is T05's to raise, not this task's to decide. +Completed 2026-09-04. `pep-stance.yaml` now publishes TTL expiry as the only +revocation mechanism, explicitly records that no CRL/KRL channel exists, and +states `adm=48h`, `agt=24h`, and `atm=8h`. The conformance test derives the +expected map from `ActorType`/`MAX_TTL_HOURS`, so declaration and issuance +policy cannot drift independently. The layer and stance declarations now name +the accepted v0.7 standard. + ```task id: WARDEN-WP-0034-T02 -status: todo +status: done priority: high state_hub_task_id: "3318ee1a-b5d9-5d39-baf7-9c42a8bc7b55" ``` @@ -90,9 +97,18 @@ enforced/advisory split is written down. identity claim, adjacent to deciding, and `ADR-0002`/§6 both point away from it. Read the identity; do not adjudicate it. +Completed 2026-09-04. The advisory CLI guard now prefers the exact issued +KeyCape subject `service:codex:railiance-platform` from +`WARDEN_POLICY_SUBJECT`, then falls back to `WARDEN_AGENT_ID`. Tests prove the +issued subject triggers exit 7 without the legacy marker, the fallback remains, +and an operator subject does not manufacture an agent identity. ADR-0004 +revision 2 records the enforced/advisory split: Warden reads the marker and +does not validate a token; OpenBao's `agent-high-risk-boundary` is the real +enforcement. + ```task id: WARDEN-WP-0034-T03 -status: todo +status: done priority: medium state_hub_task_id: "a891b32c-b0a7-59f6-a5cd-977be65c09ca" ``` @@ -111,9 +127,16 @@ that as the finding with the distribution that shows it, and declare the deferra **Done when:** either a declared cadence with its derivation, or a declared deferral carrying the data that justifies it. +Completed 2026-09-04 with an explicit measured deferral. The signatures log +contains three issuances across the 79-day observation window, on only two +active days: one on 2026-06-17 and two within 40 minutes on 2026-08-22. The +unified audit carries the latter two events. `pep-stance.yaml` records the +counts, dates, attributive classification, and reason that this sparse, +operator-driven burst cannot support a meaningful rate threshold. + ```task id: WARDEN-WP-0034-T04 -status: todo +status: done priority: medium state_hub_task_id: "94e73daa-f74d-51fd-8639-68896a4066ee" ``` @@ -138,9 +161,15 @@ to copy — `layer.yaml`, `pep-stance.yaml`, `check_layer_conformance.py`, playbook is reachable from the catalog. **Not** a restatement of the companion — a pointer layer, per `ADR-0001`. +Completed 2026-09-04. Catalog entry `netkingdom-layer-declaration` resolves the +exact query to `wiki/playbooks/netkingdom-layer-declaration.md`. The short +playbook points at the accepted companion/statute, the four reference artifacts +and checks, and the gate-house review path while explicitly leaving doctrine +with gate-house and declaration truth with each repository. + ```task id: WARDEN-WP-0034-T05 -status: todo +status: wait priority: low state_hub_task_id: "7d1b3c82-9b96-5087-a53a-496212909029" ``` @@ -162,8 +191,24 @@ fix them (`ADR-0003`, `ADR-0005`). **Done when:** both are routed with reasoning, and the answers recorded either way — including a refusal, which is an equally good answer. +Raised 2026-09-04 and waiting for answers. Ops-mason received the stance-map +reference and classification question (message `6ddd6cc7`). Railiance-infra +received the KRL-versus-TTL question (message `3bfa306e`). Flex-auth, as the +current access-engine implementation, received the decision-lifetime semantics +question (message `02c99a74`). No implementation or policy decision was assigned +to another owner by these messages. + ## Related +Review 2026-09-05: reviewed the pending implementation and fixed the identity +helper so an explicitly empty environment does not inherit the process identity. +Regression coverage also proves an operator subject cannot override the legacy +agent marker. Refreshed the generated high-risk path artifact after the catalog +changes. T05 remains waiting: no corresponding reply was present in the inbox, +and the checked owner workplans contained no resolution of these questions. +Validation: 415 unit tests and four SSH integration tests pass; Ruff, the +layer-conformance script, and the declaration-route CLI smoke check pass. + - `history/2026-08-29-v07-scope-intent-assessment.md` — the gap analysis behind this plan - `history/2026-08-29-layer-model-v04-review.md`, `-v06-review.md` — the two prior reviews - `security-layer-model_v0.7.md` §3.4, §6.4, §9.6, §9.7, §11, §13.1 From afc50354dffa636d0dbc6ee60d27dd754091fe61 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 5 Sep 2026 01:22:37 +0200 Subject: [PATCH 35/57] chore: refresh high-risk paths for committed catalog Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4 --- registry/generated/high-risk-data-paths.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index e7f858e..3b410dc 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,10 +10,10 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-08-31T22:46:47Z" +generated_at: "2026-09-04T23:20:26Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "4fee839b1138c60642bd6e0210cf8bf541333747" -catalog_revision_date: "2026-09-01T00:46:28+02:00" +catalog_revision: "00145d705e88eeb9a880a85a3cc15a5205dde2d6" +catalog_revision_date: "2026-09-05T01:19:48+02:00" catalog_dirty: false high_risk_lane_count: 24 concrete_path_count: 15 From 37b8f39bd0c7ee87ab053af5b14880f199a3d8b2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 5 Sep 2026 01:26:57 +0200 Subject: [PATCH 36/57] chore: synchronize work-record index Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4 --- WORK-RECORDS.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 80d81c3..b028a1a 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -30,9 +30,10 @@ | workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md | | workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md | | workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| workplan | WARDEN-WP-0034 | ready | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| workplan | WARDEN-WP-0034 | active | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | | workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | | workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | +| workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -132,14 +133,17 @@ | task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| task | WARDEN-WP-0034-T01 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | -| task | WARDEN-WP-0034-T02 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | -| task | WARDEN-WP-0034-T03 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | -| task | WARDEN-WP-0034-T04 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | -| task | WARDEN-WP-0034-T05 | todo | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T01 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T02 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T03 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T04 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | +| task | WARDEN-WP-0034-T05 | wait | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md | | task | WARDEN-WP-0035-T01 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | | task | WARDEN-WP-0035-T02 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | | task | WARDEN-WP-0036-T01 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | | task | WARDEN-WP-0036-T02 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | +| task | WARDEN-WP-0037-T01 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | +| task | WARDEN-WP-0037-T02 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | +| task | WARDEN-WP-0037-T03 | wait | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | | intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | | intake | WARDEN-IN-0002 | open | — | intakes/intakes.md | From 96dbb22aab9f528aaf182b428d27b6f435027c76 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 5 Sep 2026 10:24:19 +0200 Subject: [PATCH 37/57] docs: use canonical State Hub reads with caller attribution Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102 --- AGENTS.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index fdcd749..77b50ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -221,6 +221,19 @@ Install bundled memory: `make install-all` then `make check-memory` from ## Workplan Convention (ADR-001) +### State Hub reads and caller identity + +Use `GET /workplans/{id}` and `GET /tasks/?workplan_id=` for reads. +Do not send the legacy `workstream_id` query parameter or call `/workstreams/` +routes (410 Gone). Keep `state_hub_workstream_id` in file frontmatter unchanged. +Send `X-StateHub-Component: ops-warden` on direct HTTP requests so operational +reviews can attribute callers. Example: + +```bash +curl -fsS -H 'X-StateHub-Component: ops-warden' \ + 'http://127.0.0.1:8000/tasks/?workplan_id=' +``` + Work items originate as files in this repo — not in the hub. The hub is a read/cache/index layer that rebuilds from files. From c1330049d9535f701676aa955d56f2138bb70d19 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 14:55:02 +0200 Subject: [PATCH 38/57] docs: narrow qonto blocker, record key-cape lane ownership key-cape corrected two blockers that had stopped being true after our 2026-08-28 source-read: - rapp-qonto-keycape-client: `keycape service-token` (2026-09-05) is the native exchange the blocker recorded as absent, and `keycape verify-client` (2026-09-08) is rotation step 3 as one command. Narrowed to steps 1-2 -- successor generation and the CAS write -- rather than cleared, as they asked. rotation.automatable -> false so a future executable driver is not told a lane with no admitted custody transport is drivable; the per-step truth moves into the steps. - key-cape-oidc-login: ownership ACCEPTED by key-cape, so verified moves from asked-and-waiting to owner-confirmed. Lane stays interim -- acceptance covers the identity half, while the fetch_command yields an OpenBao token whose mount, role mapping and enforcement are not key-cape's. Two tests pinned `key-cape-oidc-login` to sitting `asked-and-waiting`, so answering the question broke them -- they failed on good news. Both now assert the property instead: an unverified blocker is stale regardless of date, over whatever lanes are in that state. Verifying the routing answer against our own front door turned up a defect: `warden plan` returns `autonomous` for a custody *write* and answers it with read transports, because it has no read-versus-mutate intent. Recorded as WARDEN-WP-0038 (proposed) -- the WP-0033-T06 shape, as a class this time. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- registry/generated/high-risk-data-paths.yaml | 4 +- registry/routing/catalog.yaml | 24 ++- tests/test_routing.py | 43 +++-- workplans/ADHOC-2026-09-08.md | 172 ++++++++++++++++++ .../WARDEN-WP-0038-plan-mutation-intent.md | 71 ++++++++ 5 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 workplans/ADHOC-2026-09-08.md create mode 100644 workplans/WARDEN-WP-0038-plan-mutation-intent.md diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 3b410dc..27bb658 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,11 +10,11 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-09-04T23:20:26Z" +generated_at: "2026-09-08T12:52:59Z" source: ops-warden/registry/routing/catalog.yaml catalog_revision: "00145d705e88eeb9a880a85a3cc15a5205dde2d6" catalog_revision_date: "2026-09-05T01:19:48+02:00" -catalog_dirty: false +catalog_dirty: true high_risk_lane_count: 24 concrete_path_count: 15 diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 3be856d..fdd76e0 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -335,9 +335,9 @@ entries: delegation: mode: interim intended_owner: key-cape - blocked_on: "REFUSED by secrets-engine 2026-08-21: login, MFA and identity-token issuance are key-cape/Keycloak's; secrets-engine may consume OIDC for OpenBao auth but does not own the login capability. ops-warden agrees — intended_owner corrected from secrets-engine to key-cape. Asked of key-cape 2026-08-28; KEY-WP-0009 accepting issuance ownership for machine identities is the precedent that makes it answerable." - reviewed: "2026-08-28" - verified: asked-and-waiting + blocked_on: "ACCEPTED by key-cape 2026-09-08 (msg 12f1bdfa): key-cape owns interactive login, OIDC identity-token issuance and MFA for this lane, confirming the 2026-08-28 correction of intended_owner from secrets-engine (whose 2026-08-21 refusal was right). Lane stays interim because the accepted ownership covers only the identity half: the fetch_command yields an OPENBAO token, and the netkingdom auth mount, the role= mapping, the token store and its enforcement are OpenBao's, with role/policy definition railiance-platform's. keycape login is not a drop-in — it yields a key-cape issuer JWT written to a private file, a different credential type with a different verifier (KEY-WP-0014-T04). Retires when a key-cape-native front door yields the credential this route's consumers expect, or when the route is deliberately re-contracted to a JWT. Verified against key-cape docs/native-authentication.md, docs/openbao-service-auth-contract.md, config/service-clients.example.yaml, src/internal/server/oidc/discovery.go." + reviewed: "2026-09-08" + verified: owner-confirmed # Login lane (WP-0014 T4) — interactive auth bootstrap, not a secret read. No # secret-read gate (you have no identity yet) and no caller-auth precheck (the # point is to obtain one). warden runs it interactively as the caller and never @@ -764,9 +764,9 @@ entries: delegation: mode: interim intended_owner: key-cape - blocked_on: "client_secret_basic exchange is a key-cape protocol procedure, not a KV read; still no key-cape-native exchange/rotation command. Re-checked against key-cape source 2026-08-28: KEY-WP-0009 finished 2026-08-23 and did add bounded service-auth (per-client tokenLifetime, docs/openbao-service-auth-contract.md), but that is client_credentials JWT issuance for OpenBao machine login — it does not front this client_secret_basic exchange or its rotation. The server advertises client_secret_basic (src/internal/server/oidc/discovery.go) without exposing an owner command for it. Blocker stands." - reviewed: "2026-08-28" - verified: source-read + blocked_on: "Narrowed 2026-09-08 to rotation steps 1-2 only: successor generation and the CAS write to both custodians (platform/workloads/rapp-qonto/keycape-client field client_secret, and sso/keycape-rapp-qonto-client key client-secret) remain custody/deployment acts with no admitted execution and rollback contract, and no admitted ops-warden lane authorizes them. The key-cape-native exchange now exists (keycape service-token, 2026-09-05, KEY-WP-0014-T03) and step 3 verification exists as one command (keycape verify-client, 2026-09-08, including predecessor rejection and identical-secret detection); both are documented in key-cape/docs/native-authentication.md. The prior blocker recorded both as absent, which was accurate on 2026-08-28 and is not accurate now — corrected by key-cape (msg 08d42f47). Re-checked against key-cape source 2026-09-08." + reviewed: "2026-09-08" + verified: owner-confirmed risk: high workload_ref: applicability: applicable @@ -781,11 +781,15 @@ entries: rotation: method: rotate owner: key-cape - automatable: true + # Not drivable end to end: steps 1-2 are custody/deployment acts with no + # admitted execution transport, so an executable driver (WARDEN-WP-0027 + # Strand B) must not treat this lane as automatable. Step 3 alone is one + # owner command today — the precision lives in the steps, not the flag. + automatable: false steps: - - "Generate a fresh newline-free secret through an approved execution transport; never print it." - - "Write the same value to OpenBao and sso/keycape-rapp-qonto-client, then restart KeyCape." - - "Verify positive qonto:read exchange plus wrong-secret and excessive-scope denial without printing tokens." + - "Generate a fresh newline-free secret through an approved execution transport; never print it. NOT automatable — no admitted execution and rollback contract (see delegation.blocked_on)." + - "CAS-write the same value to platform/workloads/rapp-qonto/keycape-client and sso/keycape-rapp-qonto-client preserving siblings, then restart KeyCape in the agreed window. NOT automatable — same blocker; on failure reconcile both custodians to the same version before declaring rollback complete." + - "Verify positive qonto:read exchange plus predecessor-secret and excessive-scope denial without printing tokens: run the owner command keycape verify-client (key-cape/docs/native-authentication.md)." - id: net-kingdom-lldap-bind-credential title: NetKingdom LLDAP bind credential for identity and privacyIDEA resolver diff --git a/tests/test_routing.py b/tests/test_routing.py index cd3d6e8..58a3ce4 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -994,12 +994,31 @@ def test_asked_and_waiting_is_not_verification(): def test_stale_gaps_flags_unverified_even_when_the_date_is_today(): + """Property, not a named lane. + + Originally pinned to `key-cape-oidc-login` sitting `asked-and-waiting`; + key-cape answered on 2026-09-08, so the fixture lane moved and the test + failed on someone else's good news rather than on a defect. The claim being + made is about the verification method, so assert that instead: any lane + whose blocker was never re-established is stale on the day it was reviewed. + """ catalog = load_catalog(_repo_catalog()) - stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=date(2026, 8, 21))} - # Asked of key-cape on 2026-08-21 and unanswered -- zero days old, still stale. - assert "key-cape-oidc-login" in stale - # Confirmed by the owner the same day -- fresh. - assert "issue-core-ingestion-api-key" not in stale + today = date(2026, 9, 8) + stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=today)} + unverified = [ + e for e in catalog.gaps(include_draft=True) + if not e.effective_delegation.is_verified + ] + for entry in unverified: + assert entry.id in stale, f"{entry.id} is unverified but not stale" + # A lane the owner confirmed today is fresh, so staleness is not vacuous. + fresh = [ + e.id for e in catalog.gaps(include_draft=True) + if e.effective_delegation.is_verified + and e.effective_delegation.reviewed == today.isoformat() + ] + assert fresh, "expected at least one owner-confirmed-today lane" + assert not (set(fresh) & stale) def test_invalid_verification_method_rejected(tmp_path): @@ -1027,10 +1046,10 @@ def test_cli_route_gaps_fail_on_stale_exits_3(repo_catalog_env): assert result.exit_code == 3 rows = json.loads(result.stdout) assert any(r["stale"] for r in rows) - # An asked-and-waiting lane stays stale until it is verified, regardless of - # how many calendar days have elapsed since the request. - assert any( - r["stale"] - and r["verified"] == "asked-and-waiting" - for r in rows - ) + # A lane whose blocker was never re-established stays stale regardless of + # how many calendar days have elapsed since it was reviewed. Asserted over + # the verification method rather than one named lane -- see + # test_stale_gaps_flags_unverified_even_when_the_date_is_today. + for row in rows: + if row["verified"] in ("asked-and-waiting", "unverified"): + assert row["stale"], f"{row['id']} is unverified but not stale" diff --git a/workplans/ADHOC-2026-09-08.md b/workplans/ADHOC-2026-09-08.md new file mode 100644 index 0000000..faec8c8 --- /dev/null +++ b/workplans/ADHOC-2026-09-08.md @@ -0,0 +1,172 @@ +--- +id: WARDEN-WP-ADHOC-2026-09-08 +type: workplan +title: "Ad Hoc Tasks — 2026-09-08" +domain: infotech +repo: ops-warden +status: finished +owner: claude +topic_slug: netkingdom +created: "2026-09-08" +updated: "2026-09-08" +--- + +# Ad Hoc Tasks — 2026-09-08 + +Four key-cape messages, all follow-on from `WARDEN-WP-0033`. Two carry answers +ops-warden's catalog was waiting for; both make a recorded blocker less true than +it reads. This is the T05 mechanism paying out — and this time the owner ran it +against us rather than us running it against them. + +### T01 — Narrow the `rapp-qonto-keycape-client` blocker (msg `08d42f47`) + +```task +id: WARDEN-WP-ADHOC-2026-09-08-T01 +status: done +priority: high +``` + +The blocker recorded "still no key-cape-native exchange/rotation command", +`verified: source-read` against key-cape source on 2026-08-28. Accurate then; +two things landed after that read: + +- `keycape service-token` (2026-09-05, KEY-WP-0014-T03) — the client_secret_basic + exchange the blocker records as absent. +- `keycape verify-client` (2026-09-08) — rotation step 3 as one command, + including predecessor rejection and identical-secret detection. + +**Narrowed, not cleared**, as key-cape asked. What remains genuinely blocked is +steps 1-2: successor generation and the CAS write to both custodians. Those are +custody and deployment acts, key-cape deliberately ships no command for them, and +no admitted ops-warden lane authorizes them either (T03). + +`verified` moved to `owner-confirmed` — the correction came from the owner. + +`rotation.automatable` flipped `true` → `false`, which key-cape explicitly asked +us not to do "either way" without precision. Done with the precision they wanted, +in the only place the schema has room for it: the flag is a single hint consumed +by a future executable driver (`WARDEN-WP-0027` Strand B), and a driver told +`true` would attempt custody acts with no admitted transport — the dangerous +direction. So the flag fails safe and the *steps* now carry the per-step truth, +each naming whether it is automatable and why. Offered them a schema change if a +per-step field would serve better; not built on speculation. + +### T02 — Record key-cape's acceptance of `key-cape-oidc-login` (msg `12f1bdfa`) + +```task +id: WARDEN-WP-ADHOC-2026-09-08-T02 +status: done +priority: high +``` + +key-cape **accepted** ownership: interactive login, OIDC identity-token issuance +and MFA. This confirms the 2026-08-28 correction of `intended_owner` from +secrets-engine to key-cape, and confirms secrets-engine's 2026-08-21 refusal was +right. `verified: asked-and-waiting` → `owner-confirmed`. + +**The lane stays `interim`.** Acceptance covers the identity half only. The +`fetch_command` yields an *OpenBao* token — the `netkingdom` mount, the +`role=` mapping, the token store and its enforcement are OpenBao's, with +role and policy definition railiance-platform's. `keycape login` yields a +key-cape issuer JWT to a private file: different credential type, different +verifier, different enforcement owner. Accepting an owner is not the same as the +owner fronting the lane, and `ADR-0003` says the interim cover is retired when +the front door exists, not when ownership is agreed. Retirement condition is now +recorded on the entry. + +Their reading of the risk is right and we confirmed it: `warden_executes: false` +with a `wiki_ref` pointer, so per `ADR-0001` this is a pointer layer, not a code +path with programmatic consumers. `risk: standard` and +`workload_ref: not-applicable` are correct — a human identity act with no KV +path, consistent with how `ADR-0008` treats the other non-KV lanes. + +### T03 — Answer "who executes Qonto rotation steps 1-2" (msg `0a6c9d88`) + +```task +id: WARDEN-WP-ADHOC-2026-09-08-T03 +status: done +priority: high +``` + +Answer: **nobody, admittedly, today** — and ops-warden must not invent one. + +Steps 1-2 mutate custody on `platform/workloads/rapp-qonto/keycape-client` and +`sso/keycape-rapp-qonto-client`. Custody of platform workload paths is +railiance-platform's; the admitted execution front door for provisioned +secret-exec is `secrets-engine exec`. ops-warden owns neither and absorbing it +would be `ADR-0002` and `ADR-0005` violated in one act. Routed to +railiance-platform, with secrets-engine named as the transport owner — and +secrets-engine independently confirmed the same boundary three days earlier +(msg `15f0c0ca`): they will not rewrite a production lane's custody state from a +coordination message without owner confirmation. + +key-cape was right not to treat a generic `warden route` match as authorization. +See T04 for what our own tool did when asked. + +### T04 — `warden plan` returned `autonomous` for a custody *write* (defect) + +```task +id: WARDEN-WP-ADHOC-2026-09-08-T04 +status: done +priority: high +``` + +Verifying T03 against our own front door: + +``` +warden plan "generate a successor client secret ... and CAS-write it to + OpenBao platform/workloads/... and sso/..." --json + verdict: autonomous + lane_id: rapp-qonto-keycape-client + commands: warden access ... --exec / --out / --wrap +``` + +A need that says *generate* and *CAS-write* resolved to `autonomous` and was +answered with three **read** transports. This is the `WARDEN-WP-0033-T06` shape +recurring — a generic keyword match producing a confident wrong act — except T06 +fixed one route by adding an exact lane, and the defect is in the classifier. +`warden plan` has no notion of read versus mutate intent, so every write need +matches the lane that *reads* the same path and comes back `autonomous`. + +Not fixed here: a classifier is design work with an approval-semantics blast +radius, which the convention says is a workplan, not an adhoc. +Promoted to **`WARDEN-WP-0038`** (`proposed`). + +Worth stating plainly: key-cape distrusted this output on principle and was right +to. Had they trusted it, our tool would have told them to proceed autonomously on +an unadmitted custody mutation. + +### T05 — Acknowledge the KEY-WP-0009 handoff (msg `5bc58847`) + +```task +id: WARDEN-WP-ADHOC-2026-09-08-T05 +status: done +priority: low +``` + +Informational; no route retired or changed. Their routing note matches ours: +"login / OIDC / MFA" → key-cape means **issuance only**, and secret *values* +route to OpenBao custody. Nothing to change in `wiki/CredentialRouting.md`. + +Also records that the KEY-WP-0009 reply ops-warden reported never arriving indeed +never existed (KEY-WP-0014-T02, audited 2026-09-05). Our 2026-08-28 finding was +right, and the correction is theirs, recorded. + +### T06 — Unpin two tests from a transient catalog state + +```task +id: WARDEN-WP-ADHOC-2026-09-08-T06 +status: done +priority: medium +``` + +`test_stale_gaps_flags_unverified_even_when_the_date_is_today` and +`test_cli_route_gaps_fail_on_stale_exits_3` both asserted that +`key-cape-oidc-login` specifically sat `asked-and-waiting`. T02 answering that +question broke them — **the tests failed on good news**, not on a defect, and a +test that fails when a counterparty replies will get suppressed rather than read. + +Both now assert the property they were always about: any lane whose blocker was +never re-established is stale regardless of its date, over whatever lanes are in +that state. The named-lane version of the claim already exists as a unit test on +`Delegation.is_verified`, which is where a fixture belongs. diff --git a/workplans/WARDEN-WP-0038-plan-mutation-intent.md b/workplans/WARDEN-WP-0038-plan-mutation-intent.md new file mode 100644 index 0000000..a37d8ac --- /dev/null +++ b/workplans/WARDEN-WP-0038-plan-mutation-intent.md @@ -0,0 +1,71 @@ +--- +id: WARDEN-WP-0038 +type: workplan +title: "warden plan must distinguish reading a secret from mutating one" +domain: infotech +repo: ops-warden +status: proposed +owner: ops-warden +topic_slug: netkingdom +planning_priority: P1 +depends_on_workplans: + - WARDEN-WP-0029 +created: "2026-09-08" +updated: "2026-09-08" +--- + +# WARDEN-WP-0038 — Read versus mutate intent in the policy front door + +## The defect + +`warden plan` scores a need against catalog lanes by keyword overlap and returns +a verdict. It has no notion of what the caller wants to *do*. So a need that +says *generate a successor secret and CAS-write it to two custodians* matches the +lane that **reads** that path, scores well on the shared nouns, and returns: + +``` +verdict: autonomous +commands: warden access --exec / --out / --wrap +``` + +Three read transports offered as the answer to a write, under the verdict that +means "proceed without the founder". Reproduced 2026-09-08 against +`rapp-qonto-keycape-client` (`ADHOC-2026-09-08-T04`). + +## Why this is worse than a bad suggestion + +`warden plan` is the documented first step before drafting any founder credential +step (`.claude/rules/credential-routing.md`), and `autonomous` is consumed as an +authorization signal by agents that have no other check. `WARDEN-WP-0029` shipped +the verdict *because* instinct was not auditable; a verdict that is confidently +wrong in the permissive direction is worse than no verdict. + +key-cape hit this and refused to rely on it — "we are not treating a generic +match as authorization" — which is the only reason it surfaced as a design +question rather than as an unadmitted custody mutation. The control that caught +it was a counterparty's caution. That is not a control. + +`WARDEN-WP-0033-T06` is the same shape (rapp-postgres, generic +`openbao-api-key` match → `paste_once_provision` for an act that could not work) +and was fixed by adding one exact lane plus one regression. That fixed the +instance. This is the class. + +## Shape of the fix (to be designed, not assumed) + +- Classify need intent — read / mutate / establish — before scoring, and never + answer a mutate need with a read lane's transports. +- A mutate need against a lane ops-warden does not own is `unroutable` with a CCR + stub naming the custody owner, not `autonomous`. `ADR-0003`: cover gaps, never + silently own them; a permissive verdict on someone else's custody act is a + silent claim of ownership. +- Consider whether `rotation.steps` should be reachable from `plan` at all, given + they are advisory prose and several are explicitly not automatable. +- Regression from the reproduced case, in the manner of T06. + +## Open question for design + +Whether intent belongs in the matcher or in the lane. A lane could declare which +acts it admits (`read`, `rotate`, `provision`) and `plan` could refuse any act a +lane does not declare — stricter, and it fails closed on lanes that say nothing, +which is the majority today. That is a catalog schema change and needs its own +argument. From b9ca0cc3f59c9bf7888ae4088cea3fec8ae33a50 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 14:55:03 +0200 Subject: [PATCH 39/57] chore: refresh high-risk paths for committed catalog Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- registry/generated/high-risk-data-paths.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 27bb658..aa3dd0e 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,11 +10,11 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-09-08T12:52:59Z" +generated_at: "2026-09-08T12:55:03Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "00145d705e88eeb9a880a85a3cc15a5205dde2d6" -catalog_revision_date: "2026-09-05T01:19:48+02:00" -catalog_dirty: true +catalog_revision: "c1330049d9535f701676aa955d56f2138bb70d19" +catalog_revision_date: "2026-09-08T14:55:02+02:00" +catalog_dirty: false high_risk_lane_count: 24 concrete_path_count: 15 From 89a8dea2a1e7b03d06dae258e31485b07f347608 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Tue, 8 Sep 2026 14:57:31 +0200 Subject: [PATCH 40/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-09-08: - update .custodian-brief.md for ops-warden Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- .custodian-brief.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index 83e0764..d2940f5 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-09-04 21:30 UTC +**Last synced:** 2026-09-08 12:57 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -20,12 +20,15 @@ Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` - ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee` ### Repoint the whynot-design npm lane to Forgejo -Progress: 1/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e` +Progress: 2/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e` **Open tasks:** -- ! Verify the existing credential against Forgejo `afc4d325` - ! Prove routing and publication `a8b1b855` +## Inbox Hygiene + +**Missing thread_id:** 4 unread message(s) lack supersession chains. + --- ## MCP Orientation (when available) From 11e5e8be0fbbc0aaeb6e914567c936ef5d4342d4 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 14:57:42 +0200 Subject: [PATCH 41/57] chore: record hub ids for 2026-09-08 workplans Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- WORK-RECORDS.md | 8 ++++++++ workplans/ADHOC-2026-09-08.md | 7 +++++++ workplans/WARDEN-WP-0038-plan-mutation-intent.md | 1 + 3 files changed, 16 insertions(+) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index b028a1a..5472aaf 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -12,6 +12,7 @@ | workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md | | workplan | WARDEN-WP-ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md | | workplan | WARDEN-WP-ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md | +| workplan | WARDEN-WP-ADHOC-2026-09-08 | finished | — | workplans/ADHOC-2026-09-08.md | | workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | | workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md | | workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md | @@ -34,6 +35,7 @@ | workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | | workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | | workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | +| workplan | WARDEN-WP-0038 | proposed | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -43,6 +45,12 @@ | task | WARDEN-WP-ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md | | task | WARDEN-WP-ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md | | task | WARDEN-WP-ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md | +| task | WARDEN-WP-ADHOC-2026-09-08-T01 | done | — | workplans/ADHOC-2026-09-08.md | +| task | WARDEN-WP-ADHOC-2026-09-08-T02 | done | — | workplans/ADHOC-2026-09-08.md | +| task | WARDEN-WP-ADHOC-2026-09-08-T03 | done | — | workplans/ADHOC-2026-09-08.md | +| task | WARDEN-WP-ADHOC-2026-09-08-T04 | done | — | workplans/ADHOC-2026-09-08.md | +| task | WARDEN-WP-ADHOC-2026-09-08-T05 | done | — | workplans/ADHOC-2026-09-08.md | +| task | WARDEN-WP-ADHOC-2026-09-08-T06 | done | — | workplans/ADHOC-2026-09-08.md | | task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | | task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | | task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md | diff --git a/workplans/ADHOC-2026-09-08.md b/workplans/ADHOC-2026-09-08.md index faec8c8..c1ae7c3 100644 --- a/workplans/ADHOC-2026-09-08.md +++ b/workplans/ADHOC-2026-09-08.md @@ -9,6 +9,7 @@ owner: claude topic_slug: netkingdom created: "2026-09-08" updated: "2026-09-08" +state_hub_workstream_id: "182f3aff-8c8a-5766-8a68-5fe35f423f7a" --- # Ad Hoc Tasks — 2026-09-08 @@ -24,6 +25,7 @@ against us rather than us running it against them. id: WARDEN-WP-ADHOC-2026-09-08-T01 status: done priority: high +state_hub_task_id: "29cef28a-d93b-5318-bfdb-69dbe11fa3ba" ``` The blocker recorded "still no key-cape-native exchange/rotation command", @@ -57,6 +59,7 @@ per-step field would serve better; not built on speculation. id: WARDEN-WP-ADHOC-2026-09-08-T02 status: done priority: high +state_hub_task_id: "8efb8013-7b90-52ef-a17c-d63f5ce3752c" ``` key-cape **accepted** ownership: interactive login, OIDC identity-token issuance @@ -86,6 +89,7 @@ path, consistent with how `ADR-0008` treats the other non-KV lanes. id: WARDEN-WP-ADHOC-2026-09-08-T03 status: done priority: high +state_hub_task_id: "bd621821-2116-51c9-a09a-a3660b5e5557" ``` Answer: **nobody, admittedly, today** — and ops-warden must not invent one. @@ -109,6 +113,7 @@ See T04 for what our own tool did when asked. id: WARDEN-WP-ADHOC-2026-09-08-T04 status: done priority: high +state_hub_task_id: "9ada01d2-b14b-579e-921c-275820f255db" ``` Verifying T03 against our own front door: @@ -142,6 +147,7 @@ an unadmitted custody mutation. id: WARDEN-WP-ADHOC-2026-09-08-T05 status: done priority: low +state_hub_task_id: "06bf8508-d1a2-51b4-b951-02539524ebb9" ``` Informational; no route retired or changed. Their routing note matches ours: @@ -158,6 +164,7 @@ right, and the correction is theirs, recorded. id: WARDEN-WP-ADHOC-2026-09-08-T06 status: done priority: medium +state_hub_task_id: "a0b88db9-2886-54dd-a925-9e0521044aa0" ``` `test_stale_gaps_flags_unverified_even_when_the_date_is_today` and diff --git a/workplans/WARDEN-WP-0038-plan-mutation-intent.md b/workplans/WARDEN-WP-0038-plan-mutation-intent.md index a37d8ac..a18a3f3 100644 --- a/workplans/WARDEN-WP-0038-plan-mutation-intent.md +++ b/workplans/WARDEN-WP-0038-plan-mutation-intent.md @@ -12,6 +12,7 @@ depends_on_workplans: - WARDEN-WP-0029 created: "2026-09-08" updated: "2026-09-08" +state_hub_workstream_id: "751ad530-e44b-52c4-b70e-cb47568a0179" --- # WARDEN-WP-0038 — Read versus mutate intent in the policy front door From 31d9b6671c58be824fbe58c3449de6fa80092821 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 16:46:00 +0200 Subject: [PATCH 42/57] Refuse explicit policy authentication and binding denials before side effects Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- src/warden/policy.py | 11 ++++ tests/test_policy_http_refusal.py | 48 ++++++++++++++ tests/test_proxy.py | 22 +++++++ wiki/PolicyGatedSigning.md | 5 ++ .../WARDEN-WP-0039-explicit-policy-refusal.md | 65 +++++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 tests/test_policy_http_refusal.py create mode 100644 workplans/WARDEN-WP-0039-explicit-policy-refusal.md diff --git a/src/warden/policy.py b/src/warden/policy.py index 140c8c4..31b9161 100644 --- a/src/warden/policy.py +++ b/src/warden/policy.py @@ -131,6 +131,12 @@ def check_sign_policy(cfg: PolicyConfig, spec: CertSpec) -> str | None: response = httpx.post(url, json=request, headers=headers, timeout=10.0) response.raise_for_status() except httpx.HTTPStatusError as e: + if e.response.status_code in {401, 403}: + spec.policy_outcome = "deny" + raise CAError( + f"flex-auth refused sign policy check (HTTP {e.response.status_code}); " + "caller authentication or system binding was rejected" + ) from e _evaluator_failure( f"flex-auth rejected sign policy check (HTTP {e.response.status_code}) " f"for security zone {zone!r}", @@ -213,6 +219,11 @@ def check_fetch_policy( response = httpx.post(url, json=request, headers=headers, timeout=10.0) response.raise_for_status() except httpx.HTTPStatusError as e: + if e.response.status_code in {401, 403}: + raise CAError( + f"flex-auth refused fetch policy check (HTTP {e.response.status_code}); " + "caller authentication or system binding was rejected" + ) from e _evaluator_failure( f"flex-auth rejected fetch policy check (HTTP {e.response.status_code})", fail_closed=fail_closed, diff --git a/tests/test_policy_http_refusal.py b/tests/test_policy_http_refusal.py new file mode 100644 index 0000000..09fed46 --- /dev/null +++ b/tests/test_policy_http_refusal.py @@ -0,0 +1,48 @@ +"""An explicit caller refusal cannot authorize a CA or credential side effect.""" +from unittest.mock import patch + +import httpx +import pytest + +from warden.ca import CAError +from warden.config import PolicyConfig +from warden.models import ActorType, CertSpec +from warden.policy import check_fetch_policy, check_sign_policy + + +@pytest.mark.parametrize("status", [401, 403]) +@pytest.mark.parametrize("failure_mode", ["fail_open", "fail_closed"]) +@pytest.mark.parametrize("operation", ["sign", "fetch"]) +def test_explicit_refusal_blocks_under_every_outage_mode(tmp_path, status, failure_mode, operation): + cfg = PolicyConfig(flex_auth_url="http://pdp.test") + cfg.failure_modes["unknown"] = failure_mode + public_key = tmp_path / "id.pub" + public_key.write_text("ssh-ed25519 AAAA test\n") + spec = CertSpec(actor_name="agt-example", actor_type=ActorType.AGT, + pubkey_path=public_key, ttl_hours=1, principals=["agt"]) + response = httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check"), + text="untrusted response body must not be exposed") + with patch("warden.policy.httpx.post", return_value=response): + with pytest.raises(CAError, match=f"HTTP {status}") as error: + if operation == "sign": + check_sign_policy(cfg, spec) + else: + check_fetch_policy(cfg, need_id="forgejo-admin-api-token", + owner_repo="railiance-platform", domain=None) + assert "untrusted response" not in str(error.value) + if operation == "sign": + assert spec.policy_outcome == "deny" + + +@pytest.mark.parametrize("status", [500, 503]) +@pytest.mark.parametrize("failure_mode", ["fail_open", "fail_closed"]) +def test_actual_evaluator_failure_retains_declared_outage_mode(status, failure_mode): + cfg = PolicyConfig(flex_auth_url="http://pdp.test") + cfg.failure_modes["unknown"] = failure_mode + response = httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check")) + with patch("warden.policy.httpx.post", return_value=response): + if failure_mode == "fail_closed": + with pytest.raises(CAError, match=f"HTTP {status}"): + check_fetch_policy(cfg, need_id="example", owner_repo="example", domain=None) + else: + assert check_fetch_policy(cfg, need_id="example", owner_repo="example", domain=None) is None diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 9955add..34f961e 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -234,6 +234,28 @@ def test_cli_proxy_requires_caller_auth(monkeypatch, tmp_path): assert r.exit_code == 3 +@pytest.mark.parametrize("status", [401, 403]) +def test_cli_explicit_policy_refusal_never_fetches_or_starts_child(monkeypatch, tmp_path, status): + import httpx + + _proxy_env(monkeypatch, tmp_path) + cfg = tmp_path / "warden.yaml" + cfg.write_text(cfg.read_text() + "policy:\n flex_auth_url: http://pdp.test\n") + monkeypatch.setenv("VAULT_TOKEN", "caller-test-value") + monkeypatch.setattr( + "warden.policy.httpx.post", + lambda *a, **k: httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check")), + ) + calls = [] + for name in ("proxy_exec", "proxy_fetch", "proxy_fetch_to_file", "proxy_fetch_wrapped"): + monkeypatch.setattr("warden.proxy." + name, lambda *a, **k: calls.append(True)) + result = runner.invoke(app, ["access", "forgejo-admin-api-token", "--exec", "--field", "API_TOKEN", "--", "true"]) + assert result.exit_code == 4 + assert f"HTTP {status}" in result.output + assert "fail_open applied" not in result.output + assert calls == [] + + def test_cli_proxy_rejects_retired_no_policy_bypass(monkeypatch, tmp_path): _proxy_env(monkeypatch, tmp_path) monkeypatch.setenv("VAULT_TOKEN", "caller") diff --git a/wiki/PolicyGatedSigning.md b/wiki/PolicyGatedSigning.md index b711a32..07e89d2 100644 --- a/wiki/PolicyGatedSigning.md +++ b/wiki/PolicyGatedSigning.md @@ -134,6 +134,11 @@ truth. ## Caller identity +An HTTP 401 or 403 from the policy service refuses the operation under every +outage profile. It is an explicit caller-authentication or system-binding refusal, +not evaluator unavailability. Resolve the admitted caller and resource contract; +do not retry with a different resource owner or disable caller enforcement. + The production flex-auth pin authenticates ops-warden with Kubernetes TokenReview and binds `resource.system: ops-warden` to `system:serviceaccount:ops-warden:ops-warden`. Supported token sources are: diff --git a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md new file mode 100644 index 0000000..05920ae --- /dev/null +++ b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md @@ -0,0 +1,65 @@ +--- +id: WARDEN-WP-0039 +type: workplan +title: "Preserve explicit policy caller refusals before credential and CA effects" +domain: infotech +repo: ops-warden +status: active +owner: codex +topic_slug: custodian +created: "2026-09-08" +updated: "2026-09-08" +origin: residual +origin_ref: HFACT-WP-0001 +--- + +The factory critical-path review reproduced a live HTTP 403 from the reachable +PDP for the configured ops-warden caller and a railiance-platform secret read. +`check_fetch_policy` treated it as evaluator unavailability and used unknown-zone +fail-open. Caller-auth rejection is an explicit refusal, not a transport outage. + +## Preserve explicit authentication and binding refusals + +```task +id: WARDEN-WP-0039-T01 +status: done +priority: high +assignee: the-custodian +``` + +HTTP 401 and 403 stop signing and credential fetch irrespective of the outage +profile. Do not expose response bodies or alter system identity to bypass the +refusal. Preserve configured behavior for genuine evaluator failures. Prove +both permissive and closed outage profiles and refusal before child execution. + +## Verify and publish the correction + +```task +id: WARDEN-WP-0039-T02 +status: progress +priority: high +assignee: the-custodian +``` + +Run the policy and proxy suites, retain a value-free live refusal receipt, +publish the reviewed source, and verify the installed CLI refuses before any +credential transport. A refusal is not a successful credential-read admission. + +## Resolve the credential proxy's admitted policy binding + +```task +id: WARDEN-WP-0039-T03 +status: wait +priority: high +assignee: the-custodian +blocking_reason: "The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership." +``` + +Consume the existing native-lane handoff (WARDEN-WP-0033 / SECRETS-WP-0006) +and flex-auth caller contract. Establish whether this interim transport needs an +admitted dedicated policy route or must finish its native handoff. Retain the +refusal until that contract yields positive and wrong-caller/owner/tenant +negative evidence. No credential read, secret generation, or policy grant is +authorized by this workplan alone. HFACT-WP-0001-T03 consumes this return. + +Validation: 429 tests passed (4 integration tests deselected by the repository default); Ruff passed for changed Python files. Full tests used the declared phase-memory source and an isolated temporary memory store. The focused policy/proxy suite passed 69 tests. The existing authenticated SSH policy probe still returns HTTP 200/ALLOW, decision:f3f7c88f9585582a; the credential-owner request returns 403. No CA issue or credential read was performed by these probes. From 19bb75c614d01ca42aa8b6f2bcec38fea1dd65cf Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 16:58:22 +0200 Subject: [PATCH 43/57] fix(install): refresh Warden source when reinstalling the CLI Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- Makefile | 6 +++--- workplans/WARDEN-WP-0039-explicit-policy-refusal.md | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0a71247..4f9b5f9 100644 --- a/Makefile +++ b/Makefile @@ -24,12 +24,12 @@ sync: uv sync install-warden: - uv tool install . --force + uv tool install . --force --reinstall-package ops-warden install-memory: @test -d "$(PHASE_MEMORY_REPO)" || (echo "ERROR: phase-memory not found at $(PHASE_MEMORY_REPO). Clone it or set PHASE_MEMORY_REPO."; exit 1) @test -f "$(PHASE_MEMORY_REPO)/pyproject.toml" || (echo "ERROR: $(PHASE_MEMORY_REPO) is not a phase-memory checkout."; exit 1) - uv tool install . --with-editable "$(PHASE_MEMORY_REPO)" --force + uv tool install . --with-editable "$(PHASE_MEMORY_REPO)" --force --reinstall-package ops-warden install: install-warden @@ -46,4 +46,4 @@ test: PYTHONPATH=src:$(PHASE_MEMORY_REPO)/src uv run pytest lint: - uv run ruff check . \ No newline at end of file + uv run ruff check . diff --git a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md index 05920ae..fbedd69 100644 --- a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md +++ b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md @@ -45,6 +45,11 @@ Run the policy and proxy suites, retain a value-free live refusal receipt, publish the reviewed source, and verify the installed CLI refuses before any credential transport. A refusal is not a successful credential-read admission. +The owner installer initially reused a cached wheel despite `--force`, leaving +the pre-fix policy code installed. Both Makefile installation targets now use +`--reinstall-package ops-warden` to refresh this package from the reviewed source. +Verify the installed module as well as the checkout before the live refusal test. + ## Resolve the credential proxy's admitted policy binding ```task From 541cd9043f22cfc572ed17f321a59899d75736a7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 17:03:58 +0200 Subject: [PATCH 44/57] docs(warden): record installed refusal proof and live binding residual Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- workplans/WARDEN-WP-0039-explicit-policy-refusal.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md index fbedd69..8d8aafe 100644 --- a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md +++ b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md @@ -4,7 +4,7 @@ type: workplan title: "Preserve explicit policy caller refusals before credential and CA effects" domain: infotech repo: ops-warden -status: active +status: blocked owner: codex topic_slug: custodian created: "2026-09-08" @@ -36,7 +36,7 @@ both permissive and closed outage profiles and refusal before child execution. ```task id: WARDEN-WP-0039-T02 -status: progress +status: done priority: high assignee: the-custodian ``` @@ -68,3 +68,5 @@ negative evidence. No credential read, secret generation, or policy grant is authorized by this workplan alone. HFACT-WP-0001-T03 consumes this return. Validation: 429 tests passed (4 integration tests deselected by the repository default); Ruff passed for changed Python files. Full tests used the declared phase-memory source and an isolated temporary memory store. The focused policy/proxy suite passed 69 tests. The existing authenticated SSH policy probe still returns HTTP 200/ALLOW, decision:f3f7c88f9585582a; the credential-owner request returns 403. No CA issue or credential read was performed by these probes. + +Installed verification: source guard present after refreshed owner installation (`19bb75c`). `warden access forgejo-admin-api-token --exec --field API_TOKEN -- true` exits 4 with explicit HTTP 403 before credential transport or child execution. The previous fail-open warning is absent. The probe requested no output value; no credential was fetched. T01/T02 are complete; T03 owns the remaining exact policy-binding admission. From 589d9067da022155197790d52a1579723130a6b2 Mon Sep 17 00:00:00 2001 From: repo-manager Date: Tue, 8 Sep 2026 17:04:02 +0200 Subject: [PATCH 45/57] repo.work.assign_missing_identifiers source: repo-manager reason: deterministic projection registration Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- workplans/WARDEN-WP-0039-explicit-policy-refusal.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md index 8d8aafe..bda2c13 100644 --- a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md +++ b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md @@ -11,6 +11,7 @@ created: "2026-09-08" updated: "2026-09-08" origin: residual origin_ref: HFACT-WP-0001 +state_hub_workstream_id: "ae44a935-6fca-514c-a385-4550dd2b1fe8" --- The factory critical-path review reproduced a live HTTP 403 from the reachable @@ -25,6 +26,7 @@ id: WARDEN-WP-0039-T01 status: done priority: high assignee: the-custodian +state_hub_task_id: "eaad08d9-ae47-5f50-8278-ea402a0e2869" ``` HTTP 401 and 403 stop signing and credential fetch irrespective of the outage @@ -39,6 +41,7 @@ id: WARDEN-WP-0039-T02 status: done priority: high assignee: the-custodian +state_hub_task_id: "ac85cf00-391c-5c63-b8f4-ed5bd57684e5" ``` Run the policy and proxy suites, retain a value-free live refusal receipt, @@ -58,6 +61,7 @@ status: wait priority: high assignee: the-custodian blocking_reason: "The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership." +state_hub_task_id: "8ca28b63-2e04-5333-b635-011b124c1806" ``` Consume the existing native-lane handoff (WARDEN-WP-0033 / SECRETS-WP-0006) From 084bca783baaecd1d251cfd8e50cb342d94a8ee1 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Tue, 8 Sep 2026 20:23:59 +0200 Subject: [PATCH 46/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-09-08: - update .custodian-brief.md for ops-warden Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- .custodian-brief.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index d2940f5..1376e43 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-09-08 12:57 UTC +**Last synced:** 2026-09-08 18:23 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -25,6 +25,13 @@ Progress: 2/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e` **Open tasks:** - ! Prove routing and publication `a8b1b855` +### Preserve explicit policy caller refusals before credential and CA effects +Progress: 2/3 done | workplan_id: `ae44a935-6fca-514c-a385-4550dd2b1fe8` + +**Open tasks:** +- ! Resolve the credential proxy's admitted policy binding `8ca28b63` + *(wait: The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership.)* + ## Inbox Hygiene **Missing thread_id:** 4 unread message(s) lack supersession chains. From 3a01b8f1b50d5f20cbbbfca1aaec619f95cacf2c Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 8 Sep 2026 20:35:52 +0200 Subject: [PATCH 47/57] chore(records): index the policy refusal workplan and residual Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- WORK-RECORDS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 5472aaf..fc10fce 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -36,6 +36,7 @@ | workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | | workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | | workplan | WARDEN-WP-0038 | proposed | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md | +| workplan | WARDEN-WP-0039 | blocked | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -153,5 +154,8 @@ | task | WARDEN-WP-0037-T01 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | | task | WARDEN-WP-0037-T02 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | | task | WARDEN-WP-0037-T03 | wait | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | +| task | WARDEN-WP-0039-T01 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | +| task | WARDEN-WP-0039-T02 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | +| task | WARDEN-WP-0039-T03 | wait | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | | intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | | intake | WARDEN-IN-0002 | open | — | intakes/intakes.md | From df48ee96e012fd460e234aede63b1db41ae25070 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 9 Sep 2026 14:40:05 +0200 Subject: [PATCH 48/57] feat: assert flex-auth ceiling keys are declared, not assumed flex-auth fixed enrichment so registry facts beat caller-supplied ones (FLEX-DEC-2026-012) and asked each consumer to confirm the ceiling and allowlist keys are actually declared -- the fix wins only where the registry HAS a value, and a manifest omitting max_ttl_hours hands that ceiling back to the caller. Confirmed, and made durable rather than read once. scripts/check_flex_auth_manifest_coverage.py audits both ways a ceiling gets handed back: an actor with no manifest resource at all (warden sign names ssh-cert:actor/ whether or not the snapshot was rebuilt -- an honour-system step in SCOPE.md), and a resource missing one of the seven keys. A null is treated as absent, because for enrichment it is. Also asserts the property their exploitability assessment rested on and nothing here held: ops-warden sends no resource.attributes. It was true when they read it, secrets-engine sends them on every request, and it was one refactor from silently stopping being true. Current state: no gap. 4 actors, 4 resources, all seven keys declared. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- scripts/check_flex_auth_manifest_coverage.py | 161 +++++++++++++++++++ tests/test_flex_auth_registry.py | 83 ++++++++++ tests/test_policy.py | 44 +++++ 3 files changed, 288 insertions(+) create mode 100644 scripts/check_flex_auth_manifest_coverage.py diff --git a/scripts/check_flex_auth_manifest_coverage.py b/scripts/check_flex_auth_manifest_coverage.py new file mode 100644 index 0000000..755ba02 --- /dev/null +++ b/scripts/check_flex_auth_manifest_coverage.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Assert every ssh-certificate resource declares its ceilings in the manifest. + +Read-only. Reads `inventory.yaml` and the flex-auth registry snapshot; touches +no network, no OpenBao, and no secret material. + +Why this exists (FLEX-DEC-2026-012, 2026-09-07). flex-auth's enrichment used to +overlay registry facts additive-if-absent, so a caller-supplied value for a key +won and the registry's ceiling never applied. That is fixed on their side: +registry facts now win. But they win only where the registry HAS a value — + + "A resource whose manifest omits max_ttl_hours hands that ceiling back to + the caller, and registering the resource is not sufficient; the specific + key must be present." + +So there are two ways to hand a ceiling back, and this check covers both: + + 1. An actor with no manifest resource at all. `warden sign` names + `ssh-cert:actor/` for any actor in inventory, whether or not the + snapshot was regenerated. Adding an actor and forgetting to rebuild is an + honour-system step in `SCOPE.md`, and honour-system steps are what + `ADR-0004`'s WARDEN_AGENT_ID marker taught us to stop relying on. + 2. A manifest resource missing one of the ceiling or allowlist keys. The + builder emits all of them today; this asserts it stays true, including for + resources added by hand or by a future code path. + +ops-warden sends no `resource.attributes` on a CheckRequest (`src/warden/policy.py`), +which is why FLEX-DEC-2026-012 was defence-in-depth rather than a live hole for +this repo. That property is asserted separately in `tests/test_policy.py`; this +check covers the half that survives it — if anything ever does reach a request +field, the ceiling it would have to beat must actually exist. + +Usage: + python scripts/check_flex_auth_manifest_coverage.py \ + --inventory examples/inventory.seed.yaml \ + --registry registry/flex-auth/production_registry_snapshot.json [--json] + +Exit: 0 covered, 2 coverage gap, 1 usage/IO error. +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +import yaml + +#: Every key ops-warden's shipped policy package branches on as a ceiling or an +#: allowlist. Named by flex-auth in FLEX-DEC-2026-012; kept here rather than +#: derived from the package so a package edit that drops a branch does not +#: silently shrink what this check requires. +REQUIRED_CEILING_KEYS = ( + "actor_id", + "actor_type", + "allowed_principals", + "allowed_subjects", + "max_ttl_hours", + "security_zone", + "security_zone_admission", +) + +RESOURCE_TYPE = "ssh-certificate" + + +def _resources(registry: dict[str, Any]) -> list[dict[str, Any]]: + out: list[dict[str, Any]] = [] + for manifest in registry.get("resource_manifests") or []: + out.extend(manifest.get("resources") or []) + return out + + +def audit(inventory: dict[str, Any], registry: dict[str, Any]) -> dict[str, Any]: + actors = sorted((inventory.get("actors") or {}).keys()) + resources = _resources(registry) + by_id = {str(r.get("id")): r for r in resources} + + unregistered: list[str] = [] + missing_keys: list[dict[str, Any]] = [] + + for name in actors: + if f"ssh-cert:actor/{name}" not in by_id: + unregistered.append(name) + + # Check every ssh-certificate resource in the manifest, not only the ones an + # inventory actor maps to: a resource flex-auth can be asked about is one it + # holds, regardless of where it came from. + for resource in resources: + if str(resource.get("type")) != RESOURCE_TYPE: + continue + attributes = resource.get("attributes") or {} + absent = [ + key for key in REQUIRED_CEILING_KEYS + # A null is not a declaration: flex-auth overlays a registry value + # only where one exists, so `key: null` hands the ceiling back + # exactly as an absent key does. + if attributes.get(key) is None + ] + if absent: + missing_keys.append({"resource": str(resource.get("id")), "missing": absent}) + + # Not a security defect — a stale resource has ceilings, it just has no + # actor. Reported so drift is visible rather than accumulating silently. + orphaned = sorted( + rid for rid, r in by_id.items() + if str(r.get("type")) == RESOURCE_TYPE + and rid.removeprefix("ssh-cert:actor/") not in actors + ) + + return { + "actors": len(actors), + "resources": sum(1 for r in resources if str(r.get("type")) == RESOURCE_TYPE), + "required_keys": list(REQUIRED_CEILING_KEYS), + "unregistered_actors": unregistered, + "resources_missing_keys": missing_keys, + "orphaned_resources": orphaned, + "covered": not unregistered and not missing_keys, + } + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--inventory", default="examples/inventory.seed.yaml", type=Path) + parser.add_argument( + "--registry", + default="registry/flex-auth/production_registry_snapshot.json", + type=Path, + ) + parser.add_argument("--json", action="store_true", dest="as_json") + args = parser.parse_args(argv) + + try: + inventory = yaml.safe_load(args.inventory.read_text()) or {} + registry = json.loads(args.registry.read_text()) + except (OSError, ValueError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + + report = audit(inventory, registry) + report["inventory"] = str(args.inventory) + report["registry"] = str(args.registry) + + if args.as_json: + print(json.dumps(report, indent=2)) + else: + print(f"inventory : {args.inventory} ({report['actors']} actors)") + print(f"registry : {args.registry} ({report['resources']} ssh-certificate resources)") + for name in report["unregistered_actors"]: + print(f" MISSING {name} — no manifest resource; every ceiling is caller-supplied") + for row in report["resources_missing_keys"]: + print(f" UNDECLARED {row['resource']} — {', '.join(row['missing'])}") + for rid in report["orphaned_resources"]: + print(f" orphaned {rid} — manifest resource with no inventory actor") + print("covered" if report["covered"] else "NOT COVERED") + + return 0 if report["covered"] else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_flex_auth_registry.py b/tests/test_flex_auth_registry.py index c329d27..70dd26f 100644 --- a/tests/test_flex_auth_registry.py +++ b/tests/test_flex_auth_registry.py @@ -81,3 +81,86 @@ def test_compiler_joins_explicit_workload_reference_to_resolved_zone(tmp_path): assert attrs["security_zone"] == "z2-continuity" assert attrs["security_zone_admission"] == "satisfied" assert attrs["security_zone_revision"] == "sha256:zone-revision" + + +# --- manifest ceiling coverage (FLEX-DEC-2026-012) ---------------------------- +# +# flex-auth fixed enrichment so registry facts beat caller-supplied ones, then +# asked every consumer to confirm the ceiling keys are actually declared: the +# fix wins only where the registry HAS a value. These assert the two ways a +# ceiling gets handed back to the caller. + +import importlib.util # noqa: E402 + +import pytest # noqa: E402 + +_REPO = ROOT +_spec = importlib.util.spec_from_file_location( + "check_flex_auth_manifest_coverage", + _REPO / "scripts" / "check_flex_auth_manifest_coverage.py", +) +_coverage = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_coverage) +REQUIRED_CEILING_KEYS = _coverage.REQUIRED_CEILING_KEYS +audit = _coverage.audit + +_build_spec = importlib.util.spec_from_file_location("build_flex_auth_registry", SCRIPT) +_builder = importlib.util.module_from_spec(_build_spec) +_build_spec.loader.exec_module(_builder) +build_registry = _builder.build_registry + + +def _repo_inventory_and_registry(): + inventory = yaml.safe_load((_REPO / "examples" / "inventory.seed.yaml").read_text()) + registry = json.loads( + (_REPO / "registry" / "flex-auth" / "production_registry_snapshot.json").read_text() + ) + return inventory, registry + + +def test_shipped_snapshot_declares_every_ceiling_key(): + """The confirmation flex-auth asked for, as a test rather than a reading.""" + report = audit(*_repo_inventory_and_registry()) + assert report["unregistered_actors"] == [] + assert report["resources_missing_keys"] == [] + assert report["covered"] is True + + +def test_actor_with_no_manifest_resource_is_a_coverage_gap(): + """`warden sign` names ssh-cert:actor/ whether or not the snapshot was rebuilt.""" + inventory, registry = _repo_inventory_and_registry() + inventory["actors"]["adm-added-after-the-last-build"] = { + "type": "adm", "principals": ["adm-full"], "ttl_hours": 48, + } + report = audit(inventory, registry) + assert report["unregistered_actors"] == ["adm-added-after-the-last-build"] + assert report["covered"] is False + + +@pytest.mark.parametrize("key", REQUIRED_CEILING_KEYS) +def test_dropping_any_single_ceiling_key_is_a_coverage_gap(key): + inventory, registry = _repo_inventory_and_registry() + resource = registry["resource_manifests"][0]["resources"][0] + resource["attributes"].pop(key) + report = audit(inventory, registry) + assert report["resources_missing_keys"] == [ + {"resource": resource["id"], "missing": [key]} + ] + assert report["covered"] is False + + +@pytest.mark.parametrize("key", REQUIRED_CEILING_KEYS) +def test_a_null_ceiling_is_not_a_declaration(key): + """flex-auth overlays a registry value only where one exists.""" + inventory, registry = _repo_inventory_and_registry() + resource = registry["resource_manifests"][0]["resources"][0] + resource["attributes"][key] = None + report = audit(inventory, registry) + assert report["covered"] is False + + +def test_builder_output_is_covered_by_construction(tmp_path): + """Every resource the builder emits carries the full ceiling set.""" + inventory = yaml.safe_load((_REPO / "examples" / "inventory.seed.yaml").read_text()) + report = audit(inventory, build_registry(inventory)) + assert report["covered"] is True diff --git a/tests/test_policy.py b/tests/test_policy.py index 0e2f948..8257e23 100644 --- a/tests/test_policy.py +++ b/tests/test_policy.py @@ -307,3 +307,47 @@ def test_advisory_decision_is_recorded_and_does_not_block(tmp_path): assert check_sign_policy(cfg, spec) == "decision:advisory" assert spec.policy_zone == "unknown" assert spec.policy_outcome == "audit_only" + + +def test_check_request_asserts_no_resource_attributes(tmp_path, monkeypatch): + """The property FLEX-DEC-2026-012 turned on, asserted rather than assumed. + + flex-auth's enrichment used to overlay registry facts additive-if-absent, so + a caller-supplied `resource.attributes` value won and the registry ceiling + never applied. Their exploitability assessment rested on ops-warden sending + no `resource.attributes` at all -- true when they read it, and nothing here + held it true. secrets-engine sends them on every request, so this is a + property of this code rather than of the protocol. + + Ceilings must arrive from the registry. If a future change ever needs to send + an attribute, this test is the place that argument gets made. + """ + from warden import policy as policy_mod + + pubkey = tmp_path / "id.pub" + pubkey.write_text("ssh-ed25519 AAAA test\n") + cfg = PolicyConfig(flex_auth_url="http://flex-auth.test") + seen = {} + + class _Response: + status_code = 200 + + def raise_for_status(self): + return None + + def json(self): + return {"effect": "allow", "id": "decision:49350f1064f674d7"} + + def fake_post(url, json=None, headers=None, timeout=None): + seen["body"] = json + return _Response() + + monkeypatch.setattr(policy_mod.httpx, "post", fake_post) + policy_mod.check_sign_policy(cfg, _spec(pubkey)) + + assert "attributes" not in seen["body"]["resource"] + # The requested TTL is a policy input (ttl_out_of_bounds is denied against + # the registry ceiling), so it must travel as context, never as a resource + # attribute that would be compared against itself. + assert seen["body"]["context"]["ttl_hours"] == 24 + assert set(seen["body"]["resource"]) == {"id", "type", "system", "tenant"} From 8e1b6211162c400184e8981b97a5ea39cc98d4cc Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 9 Sep 2026 14:42:20 +0200 Subject: [PATCH 49/57] docs: assent to v0.8 obligation 3, and price its adoption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gate-house circulated security-layer-model v0.8, whose §6.4 obligation 3 makes our `unknown: fail_open` cell non-conformant, and asked to be argued with rather than obeyed. Finding 1 -- assent. They wrote the falsifier into GH-DEC-2026-009: a scope genuinely unknown AND genuinely low-consequence, expected to be a §5.1 read-only diagnostic. We looked and do not have one. The stance map governs `warden sign` -- a credential-issuing side effect -- so §5.1 does not reach it and the argument stands. Being unclassifiable must not buy permissiveness. Finding 2 -- adopting it today would be a global fail-closed flag in all but name. Of four signing targets, zero resolve to a zone and three are `unknown`, so the cell would fail closed on essentially every certificate during a flex-auth outage -- including the SSH certificate needed to reach the host and repair flex-auth. That is exactly ADR-0006's rejected configuration reached by another route. Asked for a dated transition gated on coverage, or failing that for §13.1 to record coverage alongside stance: a row reading `unknown: fail_closed` while every target is unknown is conformant and misleading. Not flipping the cell. It is a proposed standard, 18 of our 18 unknown lanes are unknown because another repo has not declared, and ADR-0009 rule 3 forbids closing that with inference -- a stricter stance is not a licence to manufacture the membership that makes it survivable. WARDEN-WP-0040 records the order: classify the continuity path, raise coverage by asking owners, then supersede ADR-0009's unknown row. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- history/2026-09-09-layer-model-v08-review.md | 119 +++++++++++++++++ ...-0040-unknown-zone-fail-closed-adoption.md | 122 ++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 history/2026-09-09-layer-model-v08-review.md create mode 100644 workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md diff --git a/history/2026-09-09-layer-model-v08-review.md b/history/2026-09-09-layer-model-v08-review.md new file mode 100644 index 0000000..bc04aa6 --- /dev/null +++ b/history/2026-09-09-layer-model-v08-review.md @@ -0,0 +1,119 @@ +# security-layer-model v0.8 — ops-warden review + +**Date:** 2026-09-09 +**Reviewing:** `net-kingdom/canon/standards/security-layer-model_v0.8.md` (status +`proposed`, net-kingdom@66eeaba), circulated for assent by gate-house +**Prior reviews:** `2026-08-29-layer-model-v04-review.md`, `-v06-review.md`, +`-v07-scope-intent-assessment.md` + +## The cell in question + +v0.8 §6.4 obligation 3 adds: *unknown is not a zone and MUST resolve to +`fail_closed`.* `pep-stance.yaml` declares `unknown: fail_open` under `ADR-0009`. +§13.1 records the cell as non-conformant. + +## Finding 1 — assent. The rule is right and our falsifier check fails. + +gate-house wrote the falsifier into GH-DEC-2026-009's reversal: a scope that is +genuinely unknown **and** genuinely low-consequence, where failing closed costs +availability with no security gain. Their expectation is that any such path is a +§5.1 read-only diagnostic carrying no PEP obligation at all. + +We looked for one on our side and do not have it. The stance map governs exactly +one protected action — `warden sign`, SSH certificate issuance. That is a +credential-issuing side effect, not a diagnostic; §5.1 does not reach it. So the +predicted escape hatch is absent here, and the argument stands unrebutted on its +own terms: + +- §9.3 licenses trading availability for openness **per zone**, knowingly, for a + named scope. Where the scope is unknown that trade was never made for the + request in hand, so resolving permissively does not extend a considered + decision — it invents one. +- Unknown is the cheapest state to induce. Our own `unknown` reasons are + `workload_resolution_absent` and `catalog_applicability_absent` — an + unregistered workload or a missing declaration, neither of which requires a + credential to arrange. + +We accept that being unclassifiable must not buy permissiveness. The cell is +wrong as doctrine. + +## Finding 2 — the rule is right and adopting it today would be a global fail-closed flag in all but name + +This is the finding we would like recorded, and it is about adoption cost, not +about the rule. + +The stance map applies only when the engine is unreachable, so the cell's bite is +proportional to how much of the target population sits in `unknown`. Measured +today: + +| Population | Resolved to a zone | `unknown` | `not-applicable` | +| --- | --- | --- | --- | +| Signing targets (actor resources in the flex-auth snapshot) | **0** | **3** | 1 | +| Routing catalog lanes | 3 | 18 | 12 | + +Every signing target that resolves at all resolves to `unknown`. So +`unknown: fail_closed` adopted today does not fail closed on an edge case; it +fails closed on **essentially every certificate ops-warden issues** whenever +flex-auth is unreachable. + +That is the precise configuration `ADR-0006` rejected and `ADR-0009` replaced: one +value making the decision engine a uniform dependency of every signing path, +*including the continuity paths needed to repair that dependency*. If flex-auth is +unreachable and the operator needs an SSH certificate to reach the host and +restore it, `unknown: fail_closed` denies the certificate — and the target is +`unknown` precisely because nobody has classified the repair path. + +The zone model already answers this: classify continuity paths `z2-continuity`, +whose stance is `fail_open`, and the deadlock does not arise. We accept that as +the right answer. The point is that it is *work not yet done*, and the reason it +is not done is not ours to fix — 18 of our 18 unknown lanes are unknown because +another repo has not published an authoritative workload identity declaration. +`ADR-0009` rule 3 forbids us from closing that gap with inference, correctly. + +So as written, v0.8 §6.4 obligation 3 transfers an availability cost onto the +consumer for a classification gap the consumer cannot close alone, and it does so +at the moment of assent rather than at the moment the gap closes. + +**What we ask for**, in preference order: + +1. **§6.4 obligation 3 names a transition.** A consumer may declare + `unknown: fail_open` as a dated, published transitional state with a + classification-coverage figure attached, converting to `fail_closed` on + coverage rather than on calendar. This keeps the doctrine and prices the + adoption honestly. +2. Failing that, **§13.1 records classification coverage alongside stance.** A + register row reading `unknown: fail_closed` while 100% of that consumer's + targets are unknown is conformant and materially misleading; a reader cannot + tell a strict consumer from an unclassified one. This is the same defect + §11's *published map equals shipped behaviour* rule exists to prevent, one + level up: the map is now accurate about itself and inaccurate about its + effect. + +## Finding 3 — §12's derived-artifact rule, small and in agreement + +Six instances in one week matches our experience. `registry/generated/high-risk-data-paths.yaml` +carries `catalog_revision` and a `catalog_dirty` flag for exactly the reason §12 +now generalises: a derived artifact whose revision does not describe the file it +came from is worse than one with no revision, because the stale one is trusted. +No change requested; recording the agreement since the rule is being generalised +from other repos' incidents. + +## What ops-warden is doing about it + +Not flipping the cell on a `proposed` standard, and not flipping it before +coverage exists — that ordering is `ADR-0006`'s whole holding. `WARDEN-WP-0040` +records the adoption sequence: raise signing-target classification coverage +first, then convert the cell, with the deadlock case classified before rather +than after. If v0.8 is accepted with obligation 3 unchanged, ops-warden's cell +remains a **declared gap** under §11 — tracked non-conformance with a named +owner and a route, which is the mark §9.1 exists for and which we argued for in +the v0.6 round. + +## Axis declaration (second change touching us) + +`pep-stance.yaml` already carries `scope: security-zone`. No change needed. We +agree with rejecting a forced migration onto zones — secrets-engine scoping by +catalog stage is an accurate statement about their control, and making them +assert a zone they cannot know would trade a real fact for a conformant fiction. +The unavailability of cross-axis aggregation is better recorded than papered +over, and §13.1 now says so. diff --git a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md new file mode 100644 index 0000000..c20c5e8 --- /dev/null +++ b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md @@ -0,0 +1,122 @@ +--- +id: WARDEN-WP-0040 +type: workplan +title: "Adopt unknown -> fail_closed behind signing-target classification coverage" +domain: infotech +repo: ops-warden +status: proposed +owner: ops-warden +topic_slug: netkingdom +planning_priority: P1 +depends_on_workplans: + - WARDEN-WP-0032 + - WARDEN-WP-0034 +created: "2026-09-09" +updated: "2026-09-09" +--- + +# WARDEN-WP-0040 — `unknown` → `fail_closed`, in the right order + +security-layer-model v0.8 §6.4 obligation 3 (proposed, 2026-09-06) rules that +`unknown` is not a zone and must resolve to `fail_closed`. ops-warden's +`pep-stance.yaml` declares `unknown: fail_open` under `ADR-0009`. + +**We agree with the rule.** `history/2026-09-09-layer-model-v08-review.md` records +the assent and why our falsifier check failed: the cell governs certificate +issuance, not a §5.1 read-only diagnostic, so nothing rescues it. + +**We disagree with adopting it first.** Measured 2026-09-09: of four signing +targets, zero resolve to a zone, three are `unknown`, one is `not-applicable`. +Flipping the cell today makes flex-auth a hard dependency of essentially every +certificate — `ADR-0006`'s rejected configuration, reached by a different route, +including the continuity path needed to repair flex-auth itself. + +So: coverage first, then the cell. That ordering is the whole holding of +`ADR-0006` and the reason `ADR-0009` replaced a global switch with a zone map. + +## Tasks + +```task +id: WARDEN-WP-0040-T01 +status: todo +priority: high +``` + +**Classify the continuity path before anything else changes.** + +The deadlock is specific and worth naming before it is designed around: flex-auth +unreachable → operator needs an SSH certificate to reach the host → target is +`unknown` because nobody classified the repair path → `fail_closed` denies it. + +`z2-continuity` exists for this and its stance is `fail_open`, so the fix is +classification, not an exception. Determine which actor(s) constitute the +repair path, establish whether the declaration is ops-warden's to make (our own +`tenancy.yaml` covers ops-warden as a workload; the actors' target workloads may +not be ours), and route what is not. + +Blocks T03. Converting the cell with the repair path unclassified is the one +outcome this workplan exists to prevent. + +```task +id: WARDEN-WP-0040-T02 +status: todo +priority: high +``` + +**Raise signing-target coverage, by asking owners — never by inferring.** + +Three actor resources report `workload_resolution_absent`: +`agt-codex-interhub-bootstrap`, `agt-state-hub-bridge`, `atm-backup-daily`. +Their owners (ops-bridge and the backup execution unit among them) have not +published an authoritative workload identity declaration. + +`ADR-0009` rule 3 forbids closing this with path or repository-name inference, +and that prohibition holds under pressure from this workplan specifically — +adopting a stricter stance is not a licence to manufacture the membership that +makes it survivable. + +Route to each owner with the concrete consequence: while the declaration is +absent their actor cannot be issued a certificate during a flex-auth outage once +the cell converts. That is a better ask than a generic request to publish a +declaration, and it is true. + +Report coverage with `scripts/report_workload_join.py`; this task is done when +coverage is stated, not when it reaches a threshold — the threshold is T03's +gate, and owners may legitimately decline. + +```task +id: WARDEN-WP-0040-T03 +status: wait +priority: high +``` + +**Convert the cell and supersede `ADR-0009`'s unknown row.** + +Gated on T01, and on v0.8 (or its successor) reaching `accepted`. Do not convert +against a `proposed` standard. + +`ADR-0009` is `owner: ops-warden`, so changing it means a superseding ADR, never +an in-place edit (`.claude/rules/architecture.md`). The superseding record should +carry the v0.8 argument rather than restate it: unknown is not a zone, so a §9.3 +per-zone trade was never made for that request. + +`pep-stance.yaml` is asserted equal to `PolicyConfig.failure_modes` by +`tests/test_layer_conformance.py`, so the map and the code convert in one commit +or the test fails — which is the property that makes the map worth publishing. + +```task +id: WARDEN-WP-0040-T04 +status: todo +priority: medium +``` + +**Return findings to gate-house and track the transition ask.** + +Sent 2026-09-09 with the review. Two asks, in preference order: obligation 3 +names a dated transitional state gated on coverage; or failing that, §13.1 +records classification coverage alongside stance, so a register row cannot read +`unknown: fail_closed` while every target is unknown. + +If both are declined, that is an answer and this workplan proceeds unchanged — +the cell stays a **declared gap** under §11 with this workplan as its route, +which is what the `declared-gap` mark (our own v0.6 finding) exists to express. From 83fdd08f881ce6a5bd9c3fbcb4dc79d1ab62b4d5 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 9 Sep 2026 14:43:44 +0200 Subject: [PATCH 50/57] fix: name the npm KV field, not the env var it becomes secrets-engine corrected our claim (msg 15f0c0ca): `npm_token` is the KV field on the whynot-design publish lane; `NPM_AUTH_TOKEN` is the environment variable their publication-scope policy injects. Their doc lists the two as separate rows and we had copied the env var in as the field name, so our `fetch_command` named a field that does not exist -- `bao kv get -field=NPM_AUTH_TOKEN` could only ever have failed. This is ADR-0001's failure mode, not a typo: a pointer layer restating an owner's procedure and getting it wrong. Corrected from the owner's statement rather than re-derived here, and the catalog now records the distinction inline so the env var does not get copied back in. The path is a separate and still-open question. secrets-engine declined to resolve it unilaterally -- which location backs the lane for reads is railiance-platform's custody state -- so the path is unchanged and routed to them rather than moved on a coordination message. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- registry/routing/catalog.yaml | 12 ++++++++++-- wiki/playbooks/whynot-design-npm-publish.md | 12 +++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index fdd76e0..7b9914a 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -241,7 +241,15 @@ entries: # form is superseded; do not reintroduce it. auth_method: "bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read" path_template: "platform/workloads/coulomb/whynot-design/npm-publish" - fetch_command: "bao kv get -field=NPM_AUTH_TOKEN platform/workloads/coulomb/whynot-design/npm-publish" + # Field corrected 2026-09-09 on the owner's statement (secrets-engine msg + # 15f0c0ca): `npm_token` is the KV FIELD; `NPM_AUTH_TOKEN` is the environment + # variable the publication-scope policy injects, and ops-warden had copied the + # env var in as the field name. That is ADR-0001's failure mode exactly — a + # pointer layer restating an owner's procedure and getting it wrong — so it is + # corrected from the owner's doc (secrets-engine docs/whynot-design-real-publish-closeout.md), + # never re-derived here. The path is a separate question, open with + # railiance-platform; do not change it on this correction. + fetch_command: "bao kv get -field=npm_token platform/workloads/coulomb/whynot-design/npm-publish" policy_ref: "flex-auth check secret.read:whynot-design" exec_capable: true lane: secret @@ -257,7 +265,7 @@ entries: automatable: false steps: - "In the coulomb Forgejo registry, revoke the current @whynot/design publish token and generate a new one (scope: package read/write) for the whynot-design publish identity." - - "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish NPM_AUTH_TOKEN=@file` (value from a mode-0600 file)." + - "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish npm_token=@file` (value from a mode-0600 file). The field is `npm_token`; `NPM_AUTH_TOKEN` is the injected env var, not a KV key." - "Verify capabilities-safe, then publish a fresh version and confirm it with Forgejo-supported `npm view @` through the governed execution lane (value used, not printed)." - id: policy-nexus-forgejo-source-read diff --git a/wiki/playbooks/whynot-design-npm-publish.md b/wiki/playbooks/whynot-design-npm-publish.md index 4f26d05..4ffc934 100644 --- a/wiki/playbooks/whynot-design-npm-publish.md +++ b/wiki/playbooks/whynot-design-npm-publish.md @@ -10,7 +10,8 @@ Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f61 > plan-authorized Forgejo admin recovery lane. Treat this dedicated lane as > unverified for writes until its package token is rotated and re-proven. -The `NPM_AUTH_TOKEN` that publishes `@whynot/design` to the coulomb Forgejo npm registry +The npm publish token for `@whynot/design` on the coulomb Forgejo npm registry +(KV field `npm_token`; reaching the publish command as `NPM_AUTH_TOKEN`) (`https://forgejo.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold this token** — it is the access front door: `warden access` proxies the read from OpenBao **as the caller** and never persists, caches, or logs the value. @@ -22,7 +23,8 @@ this token** — it is the access front door: `warden access` proxies the read f | Field | Value | | --- | --- | | OpenBao path | `platform/workloads/coulomb/whynot-design/npm-publish` | -| Field | `NPM_AUTH_TOKEN` | +| KV field | `npm_token` | +| Injected env var | `NPM_AUTH_TOKEN` (set by the publication-scope policy; not a KV key) | | KV mount | `platform` | | Read policy | `workload-kv-read-whynot-design-npm-publish` | | OIDC login | `bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read` | @@ -59,12 +61,12 @@ this token** — it is the access front door: `warden access` proxies the read f token fragment at `forgejo.coulomb.social`: ```bash # --exec needs the env-var name. The zone-aware policy gate always runs first. - warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \ + warden access whynot-design-npm-publish --field npm_token \ --exec -- npm view @whynot/design@ version \ --registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/ - warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \ + warden access whynot-design-npm-publish --field npm_token \ --exec -- npm publish - warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN --fetch + warden access whynot-design-npm-publish --field npm_token --fetch ``` On either path the value transits to you (or the child env) and never enters ops-warden's memory, disk, or audit log. From 1e3eb8df2ac73677c971ce72506f4187e92c9b05 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 9 Sep 2026 14:43:48 +0200 Subject: [PATCH 51/57] chore: refresh high-risk paths for committed catalog Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- registry/generated/high-risk-data-paths.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index aa3dd0e..2ad306a 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,10 +10,10 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-09-08T12:55:03Z" +generated_at: "2026-09-09T12:43:48Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "c1330049d9535f701676aa955d56f2138bb70d19" -catalog_revision_date: "2026-09-08T14:55:02+02:00" +catalog_revision: "83fdd08f881ce6a5bd9c3fbcb4dc79d1ab62b4d5" +catalog_revision_date: "2026-09-09T14:43:44+02:00" catalog_dirty: false high_risk_lane_count: 24 concrete_path_count: 15 From 3aedd8f8441decc3fdf1aa6484f0818c1498aae2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 9 Sep 2026 16:40:46 +0200 Subject: [PATCH 52/57] docs: record the answers received and the questions routed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six inbound messages attended, three of them carrying real asks. flex-auth FLEX-DEC-2026-004 answers WARDEN-WP-0034-T05's decision-lifetime question: a decision lifetime shorter than the certificate TTL is meaningful, but only as authority to ISSUE, never to USE an already-issued certificate. The question had mistaken a decision lifetime for a credential lifetime. They declined to move the §9.7.2 revocation residue to their side; that refusal is right and the stance map is unchanged. T05 still waits on ops-mason and railiance-infra. WARDEN-WP-0039-T03 routed to flex-auth: is there an admitted contract for a delegated credential read where caller and resource owner differ? Three outcomes named as equally acceptable, including that there should be no such contract and the interim proxy transport is itself the defect -- which would shorten WP-0033 rather than block it. Two easy fixes ruled out in writing: broadening the caller binding, and relabelling resource.system as ops-warden so the binding matches. The second would make the audit trail assert we own credentials we deliberately do not, by editing a field instead of making an argument. WARDEN-WP-0037: npm path routed to railiance-platform, catalog unchanged pending their answer. secrets-engine refused to resolve it from a coordination message and was right; asserting our own pointer is authoritative because it is ours would route around that. The ask names a location only, and flags that a `bao kv get` answer would be the 2026-07-16 disclosure vector on a risk: high lane. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- ...DEN-WP-0034-layer-model-v07-conformance.md | 30 +++++++++++++++++++ ...-WP-0037-whynot-design-forgejo-npm-lane.md | 30 +++++++++++++++++++ .../WARDEN-WP-0039-explicit-policy-refusal.md | 28 +++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md index 7d779ab..81a8f19 100644 --- a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md +++ b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md @@ -198,6 +198,36 @@ current access-engine implementation, received the decision-lifetime semantics question (message `02c99a74`). No implementation or policy decision was assigned to another owner by these messages. +**One of three answered, 2026-09-09.** `flex-auth` answered the decision-lifetime +question as `FLEX-DEC-2026-004` (flex-auth commit `6a6464f`), and answered it +rather than declining it: a decision lifetime shorter than the certificate TTL is +meaningful, but only as authority to **issue**, never as authority to **use** an +already-issued certificate. The action is `sign` and it completes at issuance, so +the decision is consumed once the certificate exists and no field in the decision +record reaches the artifact. 15m against a 48h `adm` certificate was never an +inconsistency to reconcile — the question mistook a decision lifetime for a +credential lifetime. + +Two obligations land on this side and are accepted: no replay of an allow past +`expires_at`, and a fresh Check per sign. The second is where the value is, and it +is real rather than nominal because claims are not cached PDP-side (deadline 0 for +the claim class), so a revoked principal stops obtaining *new* certificates +immediately. + +`pep-stance.yaml` §9.7.2 is unchanged and stays here: flex-auth explicitly declined +to move the residue to their side, on the ground that a PDP recalling an issued +credential would be "the same shape of lie as fail-open". Declining it was the +right call even though absorbing it would have made their record look tidier. + +The TTL lever they named — capping requested TTL per actor class or zone, already a +policy input since `ttl_out_of_bounds` is denied before OpenBao is reached — is +noted and deliberately not requested. Shortening `adm` from 48h has an operator +cost, and issuance on this lane is too sparse (3 signature records across 79 days, +`pep-stance.yaml` `emission_cadence`) to price it from intuition. + +T05 stays `wait` on the remaining two: `ops-mason` (no stance map published) and +`railiance-infra` (KRL distribution versus TTL expiry). Neither has replied. + ## Related Review 2026-09-05: reviewed the pending implementation and fixed the identity diff --git a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md index a1db405..38a4d5b 100644 --- a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md +++ b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md @@ -79,3 +79,33 @@ authenticated exact-version lookup proved that Forgejo's remote integrity matches the local dry-run. T03 waits only on rotating the dedicated package token and proving the next fresh version through `whynot-design-npm-publish`; the package migration itself is complete. + +**2026-09-09 — field claim corrected, path routed.** `secrets-engine` (msg +`15f0c0ca`) corrected two things and declined a third, all correctly. + +The KV field is `npm_token`. `NPM_AUTH_TOKEN` is the environment variable their +publication-scope policy injects, and ops-warden had copied the env var in as the +field name — so the catalog's `fetch_command` named a field that does not exist +and could only ever have failed. Corrected in `registry/routing/catalog.yaml` and +`wiki/playbooks/whynot-design-npm-publish.md` from the owner's statement rather +than re-derived here. This was `ADR-0001`'s failure mode rather than a typo: a +pointer layer restating an owner's procedure and getting a detail wrong. + +The endpoint claim already agreed; `delivery_config.npm.registry` has been the +Forgejo URL throughout. + +The **path** is routed to `railiance-platform` and the catalog is unchanged pending +their answer. Which location backs the lane for reads is custody state they own; +`secrets-engine` has no lane read authority to confirm it and refused to rewrite a +production lane pointer from a coordination message (`SECRETS-WP-0006-T06`). Their +reasoning is right, and asserting our own pointer is authoritative because it is +ours would route around it. The ask names a location only and flags that a +`bao kv get` answer would be the 2026-07-16 disclosure vector on a `risk: high` +lane. + +`ready: false` is not being read as path evidence: a source checkout with no +production authority reports not-ready regardless of which path the catalog names. + +T03 still waits on the human rotation of the dedicated Forgejo package token; the +path question does not block that, it determines whether the pointer is correct +once it rotates. diff --git a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md index bda2c13..5476108 100644 --- a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md +++ b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md @@ -74,3 +74,31 @@ authorized by this workplan alone. HFACT-WP-0001-T03 consumes this return. Validation: 429 tests passed (4 integration tests deselected by the repository default); Ruff passed for changed Python files. Full tests used the declared phase-memory source and an isolated temporary memory store. The focused policy/proxy suite passed 69 tests. The existing authenticated SSH policy probe still returns HTTP 200/ALLOW, decision:f3f7c88f9585582a; the credential-owner request returns 403. No CA issue or credential read was performed by these probes. Installed verification: source guard present after refreshed owner installation (`19bb75c`). `warden access forgejo-admin-api-token --exec --field API_TOKEN -- true` exits 4 with explicit HTTP 403 before credential transport or child execution. The previous fail-open warning is absent. The probe requested no output value; no credential was fetched. T01/T02 are complete; T03 owns the remaining exact policy-binding admission. + +**T03 routed 2026-09-09.** Asked `flex-auth` whether an admitted contract exists +for a delegated credential read where the caller (ops-warden) and the resource +owner differ, since that is exactly the shape drawing the live 403. Three outcomes +were named as equally acceptable answers, with no preference stated between the +first two: an existing contract to conform to; no contract yet, making this a +design question they should raise rather than one ops-warden invents a shape for; +or that there should be **no** such contract — the interim proxy transport is +itself the defect and the answer is to finish the native handoff +(`WARDEN-WP-0033` / `SECRETS-WP-0006`). + +The third is a real possibility and was not argued against. Eleven catalog lanes +are ops-warden proxies with a named intended owner precisely because no owner +front door exists; if delegated reads should not be made policy-admissible, that +shortens WP-0033 rather than blocking it. + +Two fixes were explicitly ruled out in the message rather than left unmentioned: +broadening the ops-warden caller binding so it may read other systems' resources, +and relabelling `resource.system` as ops-warden so the existing binding matches. +The second is worse — it would make the audit trail assert ops-warden owns +credentials it deliberately does not, which is the claim `ADR-0002` exists to +prevent, achieved by editing a field instead of making an argument. + +The refusal stays in place until an answer yields positive evidence plus +wrong-caller, wrong-owner and wrong-tenant negatives. No grant was requested. +`secrets-engine` was told this bears on SECRETS-WP-0007-T04 and on how many lanes +stay proxied; `gate-house` was told it may reach how the signing lane's +approval-consume is built (GH-DEC-2026-005). From d8791a929661dba62a8e57d9cd5e1d7c1fcf6413 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Wed, 9 Sep 2026 16:41:44 +0200 Subject: [PATCH 53/57] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-09-09: - update .custodian-brief.md for ops-warden Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- .custodian-brief.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index 1376e43..7e4d8af 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — ops-warden **Domain:** infotech -**Last synced:** 2026-09-08 18:23 UTC +**Last synced:** 2026-09-09 14:41 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -32,10 +32,6 @@ Progress: 2/3 done | workplan_id: `ae44a935-6fca-514c-a385-4550dd2b1fe8` - ! Resolve the credential proxy's admitted policy binding `8ca28b63` *(wait: The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership.)* -## Inbox Hygiene - -**Missing thread_id:** 4 unread message(s) lack supersession chains. - --- ## MCP Orientation (when available) From 9c01a8a212449f72c955118fa61cc98b6d493725 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 9 Sep 2026 16:42:01 +0200 Subject: [PATCH 54/57] chore: record hub ids for WARDEN-WP-0040 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- WORK-RECORDS.md | 5 +++++ .../WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index fc10fce..fa1ba40 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -37,6 +37,7 @@ | workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | | workplan | WARDEN-WP-0038 | proposed | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md | | workplan | WARDEN-WP-0039 | blocked | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | +| workplan | WARDEN-WP-0040 | proposed | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -157,5 +158,9 @@ | task | WARDEN-WP-0039-T01 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | | task | WARDEN-WP-0039-T02 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | | task | WARDEN-WP-0039-T03 | wait | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | +| task | WARDEN-WP-0040-T01 | todo | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | +| task | WARDEN-WP-0040-T02 | todo | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | +| task | WARDEN-WP-0040-T03 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | +| task | WARDEN-WP-0040-T04 | todo | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | | intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md | | intake | WARDEN-IN-0002 | open | — | intakes/intakes.md | diff --git a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md index c20c5e8..1216d23 100644 --- a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md +++ b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md @@ -13,6 +13,7 @@ depends_on_workplans: - WARDEN-WP-0034 created: "2026-09-09" updated: "2026-09-09" +state_hub_workstream_id: "c8ee441e-1be1-5219-910c-e79ff23cc9ec" --- # WARDEN-WP-0040 — `unknown` → `fail_closed`, in the right order @@ -40,6 +41,7 @@ So: coverage first, then the cell. That ordering is the whole holding of id: WARDEN-WP-0040-T01 status: todo priority: high +state_hub_task_id: "611f0901-9fb5-5954-8dd0-a860c5f0cbec" ``` **Classify the continuity path before anything else changes.** @@ -61,6 +63,7 @@ outcome this workplan exists to prevent. id: WARDEN-WP-0040-T02 status: todo priority: high +state_hub_task_id: "54ad4864-7bcf-592e-a187-9239a81a0b11" ``` **Raise signing-target coverage, by asking owners — never by inferring.** @@ -88,6 +91,7 @@ gate, and owners may legitimately decline. id: WARDEN-WP-0040-T03 status: wait priority: high +state_hub_task_id: "93eaf1f2-daaa-5e22-8804-85ca9433571c" ``` **Convert the cell and supersede `ADR-0009`'s unknown row.** @@ -108,6 +112,7 @@ or the test fails — which is the property that makes the map worth publishing. id: WARDEN-WP-0040-T04 status: todo priority: medium +state_hub_task_id: "222a8c5d-0c0f-5a1d-98d8-02be7dca3358" ``` **Return findings to gate-house and track the transition ask.** From 66db87e6aee932655da1df4fa60f3ebc1f881cfa Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 10 Sep 2026 08:02:10 +0200 Subject: [PATCH 55/57] feat: refuse to answer a write with a read (WARDEN-WP-0038) `warden plan` scored needs by keyword overlap with no notion of what the caller wanted to DO, so "generate a successor secret and CAS-write it to two custodians" matched the lane that READS that path and inherited its `autonomous` verdict -- answered with --out/--exec/--wrap. Two counterparties reported it in two days. key-cape distrusted the output on principle and was right to; railiance-platform, answering as the write authority being wrongly bypassed, said plainly that `founder_required` is the verdict it should have returned and that until it is fixed a plan result must not stand in for the owner's answer. A mutating need on a lane ops-warden does not permanently own can no longer reach any branch returning `autonomous`: it becomes `founder_required` with an approve act naming the write owner, or `unroutable` with a CCR stub when the lane admits no rotation route. Commands carry no read transport either way, which is the half that made the wrong verdict actionable. The ownership test does the work a verb list cannot. SSH certificate issuance is itself a mutating act, so `delegation.mode: permanent` -- not the absence of a verb -- separates ops-warden's own front door from someone else's custody. A regression asserts `warden sign` still proceeds; a guard that refused our own lane would be worse than the defect it fixes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- src/warden/plan.py | 117 ++++++++++++++++++ tests/test_plan.py | 84 +++++++++++++ .../WARDEN-WP-0038-plan-mutation-intent.md | 58 ++++++++- 3 files changed, 257 insertions(+), 2 deletions(-) diff --git a/src/warden/plan.py b/src/warden/plan.py index 730a43a..e709b9a 100644 --- a/src/warden/plan.py +++ b/src/warden/plan.py @@ -98,6 +98,48 @@ def _candidate_row(entry: RouteEntry, score: int) -> dict: } +#: Verbs that make a need a request to CHANGE custody rather than to read it +#: (WARDEN-WP-0038). Reported independently by key-cape (2026-09-08) and +#: railiance-platform (2026-09-09): a need saying "generate a successor secret and +#: CAS-write it to two custodians" scored well against the lane that READS that +#: path and inherited its `autonomous` verdict, answered with three read +#: transports. +#: +#: Deliberately not including "issue" or "sign". SSH certificate issuance is a +#: mutating act ops-warden owns outright, and the ownership test below is what +#: separates it from someone else's custody — not the absence of the verb. +MUTATE_TOKENS = frozenset({ + "rotate", "rotating", "rotation", "rerotate", + "generate", "regenerate", "mint", "reissue", + "write", "rewrite", "put", "patch", "cas", + "provision", "reprovision", "create", "install", + "revoke", "revoking", "disable", "delete", "remove", + "replace", "successor", "reset", "restart", "update", +}) + + +def _need_intent(need: str) -> str: + """``mutate`` when the need asks to change a credential, else ``read``. + + Token match, not substring: "update" must not fire on "updated docs" any + more than it already would, but "no-update" style hyphenation is normalised + the same way the scorer normalises it. + """ + tokens = {t.strip(".,;:()") for t in need.lower().replace("-", " ").split()} + return "mutate" if tokens & MUTATE_TOKENS else "read" + + +def _owns_write_authority(entry: RouteEntry) -> bool: + """Whether ops-warden may itself perform a mutating act on this lane. + + True only where ops-warden is the designed owner of the front door — the SSH + signing lane, `delegation.mode: permanent`. Everywhere else ops-warden is a + pointer or a caller-identity proxy (`ADR-0002`, `ADR-0005`), so a write is + another component's custody act and no plan verdict here can authorise it. + """ + return entry.effective_delegation.mode == "permanent" + + def _score_for(catalog: Catalog, entry: RouteEntry, need: str) -> int: if entry.id == need.strip(): return 100 @@ -294,6 +336,7 @@ def build_plan( ) entry, score = matches[0] + intent = _need_intent(need) # Draft-only top match without active alternatives → unroutable if entry.status == "draft" and not include_draft: @@ -378,6 +421,80 @@ def build_plan( domain=domain, ) + # --- mutating need on a lane ops-warden does not own (WARDEN-WP-0038) ------ + # + # Must run before any branch that can return `autonomous`. A write is a + # custody act belonging to the lane owner, and `autonomous` is documented as + # the signal to proceed without the founder — so returning it here would let + # this front door authorise a mutation on someone else's custody. It would + # also answer a write with `--out`/`--exec`/`--wrap`, which are reads. + if intent == "mutate" and not _owns_write_authority(entry): + rotation = entry.rotation + owner = (rotation.owner if rotation else None) or entry.owner_repo + reasons = [ + "need asks to change a credential, not read one", + f"write authority on this lane belongs to {owner}, not ops-warden", + ] + if rotation is not None: + act = FounderAct( + kind="approve", + summary=( + f"Attended owner act required to change {entry.id} — " + f"{owner} holds write authority" + ), + details={ + "lane_id": entry.id, + "write_owner": owner, + "rotation_method": rotation.method, + "rotation_automatable": rotation.automatable, + "wiki_ref": entry.wiki_ref, + "guidance_command": f"warden rotate-guide {entry.id}", + "desk_hint": f"warden desk --act approve --lane {entry.id}", + }, + ) + if not rotation.automatable: + reasons.append( + "lane records the rotation as not automatable — an executable " + "driver must not attempt it" + ) + return AccessPlan( + need=need, + verdict="founder_required", + organization_posture=org, + policy_gate=gate, + lane_id=entry.id, + lane_title=entry.title, + match_score=score, + # Deliberately empty: every command this lane offers is a READ, + # and offering one against a write need is the reported defect. + commands=[f"warden rotate-guide {entry.id} # guidance, not execution"], + founder_act=act, + catalog=freshness, + candidates=candidates, + reasons=reasons, + actor=actor, + domain=domain, + ) + stub = _ccr_stub(need) + stub["owner_hint"] = f"{owner} (write authority for {entry.id})" + reasons.append("lane records no rotation route — the act has no admitted transport") + return AccessPlan( + need=need, + verdict="unroutable", + organization_posture=org, + policy_gate=gate, + lane_id=entry.id, + lane_title=entry.title, + match_score=score, + commands=[], + ccr_stub=stub, + catalog=freshness, + candidates=candidates, + reasons=reasons, + actor=actor, + domain=domain, + ) + if _lane_is_autonomous(entry): return AccessPlan( need=need, diff --git a/tests/test_plan.py b/tests/test_plan.py index 73fc7e9..a3cf827 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -119,3 +119,87 @@ def test_cli_plan_json(): assert payload["verdict"] == "autonomous" assert payload["organization_posture"] == "build" assert payload["lane_id"] == "agent-harness-forgejo-deploy" + + +# --- mutate intent (WARDEN-WP-0038) ------------------------------------------- +# +# `warden plan` scored a need by keyword overlap with no notion of what the +# caller wanted to DO, so "generate a successor secret and CAS-write it to two +# custodians" matched the lane that READS that path and inherited its +# `autonomous` verdict, answered with --out/--exec/--wrap. Reported independently +# by key-cape (2026-09-08) and railiance-platform (2026-09-09), who added: "this +# is the same verdict your warden plan should have returned; until that is fixed, +# do not let a plan result stand in for this answer." + +QONTO_ROTATION_NEED = ( + "generate a successor client secret for rapp-qonto keycape client and " + "CAS-write it to OpenBao platform/workloads/rapp-qonto/keycape-client " + "and sso/keycape-rapp-qonto-client" +) + + +def test_reported_qonto_write_no_longer_returns_autonomous(): + """The exact need from the report. Regression, in the manner of WP-0033-T06.""" + plan = build_plan(QONTO_ROTATION_NEED) + assert plan.verdict == "founder_required" + assert plan.lane_id == "rapp-qonto-keycape-client" + assert plan.founder_act is not None + assert plan.founder_act.kind == "approve" + assert plan.founder_act.details["write_owner"] == "key-cape" + + +def test_a_write_need_is_never_answered_with_a_read_transport(): + """The half that made the wrong verdict actionable rather than merely wrong.""" + plan = build_plan(QONTO_ROTATION_NEED) + joined = " ".join(plan.commands) + for read_transport in ("--out", "--wrap", "--fetch", "bao kv get", "--exec"): + assert read_transport not in joined, read_transport + + +def test_mutating_need_names_why_it_escalated(): + """WP-0029's property: a verdict carries the reasons that produced it.""" + plan = build_plan(QONTO_ROTATION_NEED) + assert any("change a credential" in r for r in plan.reasons) + assert any("write authority" in r for r in plan.reasons) + # The lane records automatable: false; a driver must be told so. + assert any("not automatable" in r for r in plan.reasons) + + +def test_ops_warden_still_proceeds_autonomously_on_the_lane_it_owns(): + """Signing IS a mutating act. The test is ownership, not the absence of a verb. + + `delegation.mode: permanent` is the whole distinction — if this ever fails, + the guard has started refusing ops-warden's own front door. + """ + for need in ( + "sign an ssh certificate for agt-state-hub-bridge", + "issue a short-lived ssh cert for adm", + ): + plan = build_plan(need) + assert plan.verdict == "autonomous", need + assert plan.lane_id == "ssh-cert-host-access" + + +def test_reading_the_same_lane_is_unaffected(): + plan = build_plan("I need the npm token to publish whynot-design") + assert plan.verdict == "autonomous" + assert plan.lane_id == "whynot-design-npm-publish" + + +@pytest.mark.parametrize( + "verb", ["rotate", "revoke", "regenerate", "reset", "replace"] +) +def test_mutating_verbs_escalate_on_a_lane_ops_warden_does_not_own(verb): + plan = build_plan(f"{verb} the forgejo admin api token") + assert plan.verdict == "founder_required" + assert plan.founder_act.details["write_owner"] == "railiance-platform" + + +def test_intent_classifier_matches_tokens_not_substrings(): + from warden.plan import _need_intent + + assert _need_intent("rotate the forgejo token") == "mutate" + assert _need_intent("CAS-write to two custodians") == "mutate" + # "created" and "updated" are not the verbs; a need describing state is a read. + assert _need_intent("read the token created for whynot-design") == "read" + assert _need_intent("which subsystem owns the npm token") == "read" diff --git a/workplans/WARDEN-WP-0038-plan-mutation-intent.md b/workplans/WARDEN-WP-0038-plan-mutation-intent.md index a18a3f3..047148c 100644 --- a/workplans/WARDEN-WP-0038-plan-mutation-intent.md +++ b/workplans/WARDEN-WP-0038-plan-mutation-intent.md @@ -4,14 +4,14 @@ type: workplan title: "warden plan must distinguish reading a secret from mutating one" domain: infotech repo: ops-warden -status: proposed +status: finished owner: ops-warden topic_slug: netkingdom planning_priority: P1 depends_on_workplans: - WARDEN-WP-0029 created: "2026-09-08" -updated: "2026-09-08" +updated: "2026-09-09" state_hub_workstream_id: "751ad530-e44b-52c4-b70e-cb47568a0179" --- @@ -70,3 +70,57 @@ acts it admits (`read`, `rotate`, `provision`) and `plan` could refuse any act a lane does not declare — stricter, and it fails closed on lanes that say nothing, which is the majority today. That is a catalog schema change and needs its own argument. + +## Confirmed independently, then fixed — 2026-09-09 + +`railiance-platform` hit the same defect from the other side while answering the +Qonto custody routing question (msg `7c7228ac`). Having stated that the correct +verdict is `founder_required` — attended OIDC via `netkingdom` `role=platform-admin`, +through the governed `openbao-platform-admin-login` lane — they added: + +> Note this is the same verdict your `warden plan` should have returned; it +> returning `autonomous` and offering read transports for a write is a real +> defect and WARDEN-WP-0038 is the right place for it. Until that is fixed, do +> not let a plan result stand in for this answer. + +Two counterparties reporting the same wrong verdict in two days, one of whom is +the write authority being wrongly bypassed, moved this from proposed to shipped. + +### What shipped + +`_need_intent()` classifies a need as `mutate` or `read` by token match, and +`_owns_write_authority()` tests `delegation.mode == "permanent"`. A mutating need +on a lane ops-warden does not permanently own can no longer reach any branch that +returns `autonomous`: + +- lane declares a `rotation` → `founder_required`, with an `approve` act naming + the write owner, the rotation method, whether it is automatable, and + `warden rotate-guide` as guidance; +- lane declares none → `unroutable` with a CCR stub whose `owner_hint` names the + write authority. + +**Commands are empty of read transports either way.** That is the half that made +the wrong verdict actionable rather than merely wrong: `--out`, `--exec`, +`--wrap` and a bare `bao kv get` were being offered as the answer to a write, and +a test now asserts none of them appears. + +### The design question in the plan, answered by use + +The plan asked whether intent belongs in the matcher or in the lane. It went in +the matcher, with the *ownership* test doing the work that a verb list cannot: +SSH certificate issuance is itself a mutating act, and `warden sign` must stay +`autonomous`. `delegation.mode: permanent` is what separates ops-warden's own +front door from someone else's custody — not the absence of a verb from a list. +A regression asserts the SSH lane still proceeds, because a guard that refuses +our own lane would be a worse defect than the one it fixes. + +The stricter alternative — lanes declaring which acts they admit, `plan` refusing +anything undeclared — is not built. It is a catalog schema change that would fail +closed on the majority of lanes, which say nothing today, and it needs its own +argument rather than arriving as a side effect of this fix. + +### Not claimed + +This narrows what `warden plan` will assert; it does not make the tool an +authority on custody. railiance-platform's instruction stands and is the right +standing posture: a plan result does not substitute for the owner's answer. From a942ce805de7f58279b1adefe69568c799875e4e Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 10 Sep 2026 08:02:10 +0200 Subject: [PATCH 56/57] docs: record the answered custody authority on the qonto lane railiance-platform answered the routing question (msg 7c7228ac): steps 1-2 are executed by the platform operator attended, through the governed openbao-platform-admin-login lane with a unique metadata receipt path, under founder_required attended OIDC via netkingdom role=platform-admin. Nothing else in that repo carries write authority against platform/workloads/. The blocker narrows again -- the AUTHORITY is answered, the ARTIFACT is not. A two-custodian CAS rotation with a service restart is a distinct version-guarded operation needing its own reviewed CCR, which is theirs to write once an owner asks for the rotation. That question is now with key-cape; ops-warden connected the two and did not ask on their behalf. Their executable precedent for the identical two-custodian shape is cited so a rapp-qonto rotation script is not built from a bare `bao kv patch` -- the provider/consumer consistency reason key-cape gave when declining to ship a wrapper, which railiance-platform endorsed unprompted. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- registry/generated/high-risk-data-paths.yaml | 6 +++--- registry/routing/catalog.yaml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 2ad306a..8bc63da 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,10 +10,10 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-09-09T12:43:48Z" +generated_at: "2026-09-10T05:54:39Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "83fdd08f881ce6a5bd9c3fbcb4dc79d1ab62b4d5" -catalog_revision_date: "2026-09-09T14:43:44+02:00" +catalog_revision: "406446f7bb38efadf85849756c067a124902a2ec" +catalog_revision_date: "2026-09-10T07:54:39+02:00" catalog_dirty: false high_risk_lane_count: 24 concrete_path_count: 15 diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 7b9914a..8a0a867 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -772,8 +772,8 @@ entries: delegation: mode: interim intended_owner: key-cape - blocked_on: "Narrowed 2026-09-08 to rotation steps 1-2 only: successor generation and the CAS write to both custodians (platform/workloads/rapp-qonto/keycape-client field client_secret, and sso/keycape-rapp-qonto-client key client-secret) remain custody/deployment acts with no admitted execution and rollback contract, and no admitted ops-warden lane authorizes them. The key-cape-native exchange now exists (keycape service-token, 2026-09-05, KEY-WP-0014-T03) and step 3 verification exists as one command (keycape verify-client, 2026-09-08, including predecessor rejection and identical-secret detection); both are documented in key-cape/docs/native-authentication.md. The prior blocker recorded both as absent, which was accurate on 2026-08-28 and is not accurate now — corrected by key-cape (msg 08d42f47). Re-checked against key-cape source 2026-09-08." - reviewed: "2026-09-08" + blocked_on: "Narrowed again 2026-09-09: the AUTHORITY is answered, the ARTIFACT is not. railiance-platform (msg 7c7228ac) confirmed steps 1-2 are theirs: executed by the platform operator attended, never by ops-warden, secrets-engine autonomously, or any unattended agent; transport is the governed openbao-platform-admin-login lane invoked only through `warden access openbao-platform-admin-login --exec -- ` with a unique metadata receipt path (RPF-WP-0017 output containment); authority is founder_required attended OIDC via netkingdom role=platform-admin. What is still missing is a reviewed rotation CCR — a two-custodian CAS rotation with a service restart is a distinct version-guarded operation, not an implementation detail of this lane, and it must name the CAS precondition and expected version on both custodians, sibling-field preservation, the restart window, bounded predecessor retention, and the reconcile-to-same-version failure step. That CCR is railiance-platform to write under RPF-WP-0035 once an owner asks for the rotation; ops-warden has asked key-cape whether to schedule it. Executable precedent for the same two-custodian shape: railiance-platform scripts/keycape_approval_custody.py, with its dated receipt under docs/evidence/ and review packet under docs/credential-lane-designs/. Prior (2026-09-08) narrowing to rotation steps 1-2 only: successor generation and the CAS write to both custodians (platform/workloads/rapp-qonto/keycape-client field client_secret, and sso/keycape-rapp-qonto-client key client-secret) remain custody/deployment acts. The key-cape-native exchange now exists (keycape service-token, 2026-09-05, KEY-WP-0014-T03) and step 3 verification exists as one command (keycape verify-client, 2026-09-08, including predecessor rejection and identical-secret detection); both are documented in key-cape/docs/native-authentication.md. The prior blocker recorded both as absent, which was accurate on 2026-08-28 and is not accurate now — corrected by key-cape (msg 08d42f47). Re-checked against key-cape source 2026-09-08." + reviewed: "2026-09-09" verified: owner-confirmed risk: high workload_ref: From 5b1a5086108487e6a50e7ae8eca2a2a2870af71d Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 10 Sep 2026 08:02:10 +0200 Subject: [PATCH 57/57] docs: mark the unknown cell, measure the coverage we asked to publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gate-house ruled the v0.8 assent round (GH-DEC-2026-011, net-kingdom@64394e9): ask 1 declined, ask 2 adopted. Ask 1's refusal is accepted without reservation and the reason is better than the ask -- a sanctioned transitional fail_open is indistinguishable at runtime from the stance the rule forbids, and would make the rule optional at the only moment it costs anything. Ask 2 gave §13.1 a Coverage column with this repo's figures as its first entries. Since we asked for the column, we owe it accuracy: scripts/report_coverage.py measures both populations from the artifacts the runtime uses (reusing the workload-join build rather than re-deriving it), and a test asserts pep-stance.yaml's published block equals what it measures. A hand-counted number in a register that explicitly does not recompute it decays silently, and a stale figure beside a marked cell is worse than the blank the other four rows carry. pep-stance.yaml marks the unknown cell inline as a declared gap -- assent, the measured reason for not flipping, the declined ask, WARDEN-WP-0040 as route -- and a second test keeps it marked while it is fail_open, failing when it is flipped. standard_version stays 0.7 because that is what binds; v0.8 is proposed, so it gains standard_version_reviewed rather than pre-adopting. Separately, gate-house corrected GH-DEC-2026-008: the claim/decision digest comparison it originally required is unimplementable and a fail-closed consumer obeying it would have denied permanently. We had never copied the wording, so nothing to unwind -- but everything they have sent about this lane was living in an inbox thread, a bad home for a correction that only matters when someone finally wires the consume. Now wiki/ApprovalConsumption.md, leading with "nothing is wired", carrying the corrected target and the attribution gap that digest matching does not discharge. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C Assistant: claude-code Assistant-Model: opus Assistant-Process: 1276224@bnt-lap001 Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3 --- WORK-RECORDS.md | 2 +- history/2026-09-09-layer-model-v08-review.md | 56 ++++++++ pep-stance.yaml | 44 +++++- scripts/report_coverage.py | 129 ++++++++++++++++++ tests/test_layer_conformance.py | 49 +++++++ wiki/ApprovalConsumption.md | 77 +++++++++++ wiki/PolicyGatedSigning.md | 8 ++ ...-0040-unknown-zone-fail-closed-adoption.md | 25 ++++ 8 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 scripts/report_coverage.py create mode 100644 wiki/ApprovalConsumption.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index fa1ba40..6334c76 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -35,7 +35,7 @@ | workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md | | workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md | | workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md | -| workplan | WARDEN-WP-0038 | proposed | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md | +| workplan | WARDEN-WP-0038 | finished | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md | | workplan | WARDEN-WP-0039 | blocked | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md | | workplan | WARDEN-WP-0040 | proposed | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | diff --git a/history/2026-09-09-layer-model-v08-review.md b/history/2026-09-09-layer-model-v08-review.md index bc04aa6..ac4ddd8 100644 --- a/history/2026-09-09-layer-model-v08-review.md +++ b/history/2026-09-09-layer-model-v08-review.md @@ -117,3 +117,59 @@ catalog stage is an accurate statement about their control, and making them assert a zone they cannot know would trade a real fact for a conformant fiction. The unavailability of cross-axis aggregation is better recorded than papered over, and §13.1 now says so. + +--- + +## Ruling — GH-DEC-2026-011, 2026-09-09 + +gate-house closed the assent round the same day: **ask 1 declined, ask 2 +adopted**, standard amended at `net-kingdom@64394e9`, dispositioned in +`gate-house/docs/conformance/2026-09-06-v08-assent-round.md`. + +**Ask 1 (a dated transitional `unknown: fail_open`) — declined**, and the reason +is better than the ask. A sanctioned transitional `fail_open` is +*indistinguishable at runtime* from the stance the rule forbids, and it would make +the rule optional at the moment of adoption — the only moment it costs anything. +Dating an intention to stop being permissive does not make `unknown` any more +expensive for an attacker to induce while the transition runs. Accepted without +reservation; the argument defeats the ask on its own terms. + +**Ask 2 (coverage published beside stance) — adopted.** §13.1 now carries a +Coverage column and this repo's figures are its first entries: *signing targets +0/3 resolved; routing lanes 3/21 resolved (2026-09-09, self-measured)*. Three +guards came with it, all of which we would have wanted: coverage does not soften +the stance, does not gate it, and never makes a non-conformant cell conformant; a +blank means "not reported" and never "complete"; and the decision carries an +explicit reversal condition — if a row is ever argued to be conformant *because* +its coverage is low, the column is doing harm and comes out. + +That reversal condition is the right shape and it binds us first, since the column +exists because we asked. Our row reads non-conformant **and** low-coverage; those +are two facts, and the second is not a defence of the first. + +**The deadlock was recorded rather than routed around.** §6.4 obligation 3 now +states that a stricter stance is not a licence to manufacture the membership that +makes it survivable — where a scope is unknown because another repository has +published no workload-identity declaration, the consumer MUST NOT infer one. That +is `ADR-0009` rule 3 promoted into the standard at exactly the point where the +pressure lands, which is more than we asked for. + +### What we did in response + +- `pep-stance.yaml` marks the `unknown` cell as a **declared gap** inline, with + the assent, the measured reason for not flipping, the declined ask, and + `WARDEN-WP-0040` as its route. The stance value is unchanged — v0.8 is still + `proposed` and v0.7 remains in force, so `standard_version` stays `0.7` with a + new `standard_version_reviewed: 0.8`. +- The coverage figures are published in `pep-stance.yaml` and, because we asked + for the column, **measured rather than hand-counted**: + `scripts/report_coverage.py` derives both populations from the artifacts the + runtime uses, reusing `report_workload_join.build` rather than re-deriving the + join. `tests/test_layer_conformance.py` asserts the published block equals the + measured one, and asserts the cell stays marked while it is `fail_open`. + + A hand-counted figure in a register that explicitly does not recompute it would + decay silently, and a stale number beside a marked cell is worse than the blank + the other four rows carry — a blank at least reads as "not reported". +- `WARDEN-WP-0040` T04 is closed; T01–T03 stand unchanged. gate-house confirmed + they are not asking for the cell to be flipped before coverage exists. diff --git a/pep-stance.yaml b/pep-stance.yaml index 6364a80..6262211 100644 --- a/pep-stance.yaml +++ b/pep-stance.yaml @@ -18,7 +18,13 @@ schema_version: "0.1" framework: netkingdom-security-layer-model +# v0.7 is the accepted standard and the one in force. v0.8 is `proposed`: its +# assent round closed 2026-09-09 (GH-DEC-2026-011) with nine corrections applied, +# but the version is not yet accepted, so this declaration stays pinned to what +# binds. The one cell v0.8 would change is marked inline below rather than +# silently pre-adopted. standard_version: "0.7" +standard_version_reviewed: "0.8" # reviewed and assented; see history/2026-09-09-layer-model-v08-review.md repository: ops-warden pep_shape: true declared_by: docs/adr/ADR-0009 @@ -36,7 +42,26 @@ stance: z2-protected: fail_open z2-continuity: fail_open z3-critical: fail_closed - unknown: fail_open # versioned build profile (ADR-0009); explicit, never inferred + # DECLARED GAP under security-layer-model v0.8 §6.4 obligation 3 (GH-DEC-2026-011, + # net-kingdom@64394e9): unknown is not a zone and MUST resolve to fail_closed. + # ops-warden ASSENTED to that rule — we went looking for the §5.1 read-only + # diagnostic its reversal clause predicts and do not have one, because this map + # governs `warden sign`, a credential-issuing side effect. + # + # The cell has not been flipped, and the reason is measured rather than argued: + # 0 of 3 signing targets resolve to a zone, so converting today would fail closed + # on essentially every certificate whenever the engine is unreachable — including + # the certificate an operator needs to reach the host and repair it. That is + # ADR-0006's rejected configuration reached from another direction. + # + # We asked for a coverage-gated transitional fail_open and were DECLINED: a + # sanctioned transitional fail_open is indistinguishable at runtime from the + # stance the rule forbids. Our second preference was adopted instead — §13.1 now + # carries a Coverage column, and this repo's figures are its first entries. + # + # So this is tracked non-conformance with a route, not an exemption. + # Route: WARDEN-WP-0040. Register row: §13.1, marked. + unknown: fail_open # non-conformant at v0.8; see above not-applicable: fail_closed # What happens when the stance is applied. §6.4 obligation 1 requires a decision @@ -57,6 +82,23 @@ on_apply: # §6.4 obligation 2 — the verdict is never cached. Input claims (zone membership, # compiled from the flex-auth registry snapshot) are cached under their own # freshness rules; the answer is not. +# Classification coverage, published beside the stance because v0.8 §6.4 +# obligation 3 now requires it and because ops-warden asked for the column. +# Coverage is DISCLOSURE, never a transitional licence: it does not soften this +# map's stance, does not gate it, and never makes the marked cell conformant. +# Self-measured; regenerate with `python scripts/report_coverage.py`. +classification_coverage: + measured: "2026-09-09" + attribution: self-measured + signing_targets: + resolved: 0 + unknown: 3 + not_applicable: 1 + routing_lanes: + resolved: 3 + unknown: 18 + not_applicable: 12 + verdict_caching: none input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebuilt by scripts/build_flex_auth_registry.py" diff --git a/scripts/report_coverage.py b/scripts/report_coverage.py new file mode 100644 index 0000000..11955ea --- /dev/null +++ b/scripts/report_coverage.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +"""Measure the classification coverage published in `pep-stance.yaml`. + +Read-only. No network, no OpenBao, no secret material. + +Why this exists. security-layer-model v0.8 §6.4 obligation 3 requires a dated +classification-coverage figure beside each stance, and §13.1 carries a Coverage +column whose first entries are ops-warden's — because ops-warden asked for the +column after measuring that 0 of 3 signing targets resolve to a zone. + +Having asked for it, we own the figure's accuracy. A hand-counted number in a +published register decays silently: the register explicitly does not compute +anyone's coverage, and a stale figure beside a marked cell is worse than a blank, +which at least reads as "not reported". So the figure is measured from the same +two artifacts the runtime uses, and `tests/test_layer_conformance.py` asserts the +published block equals what this reports — the same property that makes the stance +map worth publishing (`pep-stance.yaml` equals `PolicyConfig.failure_modes` by +test), applied one level up. + +Two populations, deliberately not summed. They answer different questions and +share no denominator: + + signing targets — actor resources in the flex-auth registry snapshot. This is + the population the stance map actually governs: `warden sign` resolves a zone + per actor resource, and an unresolved one takes the `unknown` cell. + routing lanes — catalog entries with an explicit workload reference. Wider than + the stance map's reach, and the figure that shows *why* coverage is low: an + unknown lane is almost always another repository's undeclared workload + identity, which `ADR-0009` rule 3 forbids closing by inference here. + +Usage: + python scripts/report_coverage.py [--json] + +Exit: 0 always. This reports; it does not gate. Coverage is disclosure, never a +transitional licence, and a script that failed on low coverage would be arguing +the case v0.8 declined. +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +REPO = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(REPO / "src")) + +SNAPSHOT = REPO / "registry" / "flex-auth" / "production_registry_snapshot.json" +NOT_APPLICABLE = "not-applicable" + + +def signing_target_coverage(snapshot_path: Path = SNAPSHOT) -> dict[str, int]: + """Zone resolution across the actor resources `warden sign` can name.""" + registry = json.loads(snapshot_path.read_text()) + resolved = unknown = not_applicable = 0 + for manifest in registry.get("resource_manifests") or []: + for resource in manifest.get("resources") or []: + if str(resource.get("type")) != "ssh-certificate": + continue + attributes = resource.get("attributes") or {} + admission = str(attributes.get("security_zone_admission") or "unknown") + zone = str(attributes.get("security_zone") or "unknown") + if admission == NOT_APPLICABLE: + not_applicable += 1 + elif zone == "unknown": + unknown += 1 + else: + resolved += 1 + return {"resolved": resolved, "unknown": unknown, "not_applicable": not_applicable} + + +def routing_lane_coverage(estate_root: Path | None = None) -> dict[str, int]: + """Workload resolution across catalog lanes. + + Delegates to `report_workload_join.build` rather than re-deriving the join: two + implementations of "is this lane resolved" would drift, and the published figure + should be the one the join report shows. + """ + import importlib.util + + spec = importlib.util.spec_from_file_location( + "report_workload_join", Path(__file__).resolve().parent / "report_workload_join.py" + ) + join = importlib.util.module_from_spec(spec) + spec.loader.exec_module(join) + + report = join.build( + REPO / "registry" / "routing" / "catalog.yaml", + estate_root if estate_root is not None else Path.home(), + ) + return { + "resolved": len(report["resolved"]), + "unknown": len(report["unknown"]), + "not_applicable": len(report["not_applicable"]), + } + + +def measure(estate_root: Path | None = None) -> dict[str, Any]: + return { + "signing_targets": signing_target_coverage(), + "routing_lanes": routing_lane_coverage(estate_root), + } + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--json", action="store_true", dest="as_json") + args = parser.parse_args(argv) + + report = measure() + if args.as_json: + print(json.dumps(report, indent=2)) + return 0 + + for name, counts in report.items(): + total = sum(counts.values()) + scoped = counts["resolved"] + counts["unknown"] + print(f"{name.replace('_', ' ')}: {counts['resolved']}/{scoped} resolved " + f"({counts['unknown']} unknown, {counts['not_applicable']} not-applicable, " + f"{total} total)") + print() + print("Coverage is disclosure, not a transitional licence (v0.8 §6.4 obligation 3).") + print("Publish in pep-stance.yaml; §13.1 does not compute it for you.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py index b10b7f7..35e4a6f 100644 --- a/tests/test_layer_conformance.py +++ b/tests/test_layer_conformance.py @@ -150,3 +150,52 @@ class TestPepStanceMap: assert cadence["observed_window"]["signature_records"] == 3 assert cadence["observed_window"]["active_signature_days"] == 2 assert cadence["reason"] + + +# --- classification coverage (v0.8 §6.4 obligation 3) ------------------------- + +def test_published_coverage_equals_measured_coverage(): + """The published figure must equal what the repo actually measures. + + ops-warden asked gate-house for §13.1's Coverage column and its figures are + that column's first entries, so their accuracy is ours to hold. The register + explicitly does not compute anyone's coverage, and a stale number beside a + marked cell is worse than a blank -- a blank at least reads as "not reported". + + This is the same property that makes the stance map worth publishing (the map + equals PolicyConfig.failure_modes by test), applied one level up. + """ + import importlib.util + + repo = Path(__file__).resolve().parents[1] + spec = importlib.util.spec_from_file_location( + "report_coverage", repo / "scripts" / "report_coverage.py" + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + published = yaml.safe_load((repo / "pep-stance.yaml").read_text())[ + "classification_coverage" + ] + measured = module.measure() + + for population in ("signing_targets", "routing_lanes"): + assert published[population] == measured[population], population + + +def test_the_unknown_cell_is_marked_as_a_declared_gap(): + """A non-conformant cell must say so where it is declared, not only in a review. + + §11's marking obligation, which ops-warden argued for in the v0.6 round and + then acquired a marked cell under. If the cell is ever flipped to fail_closed + this test fails, which is the correct time to remove the marking. + """ + repo = Path(__file__).resolve().parents[1] + text = (repo / "pep-stance.yaml").read_text() + stance = yaml.safe_load(text)["stance"] + + if stance["unknown"] == "fail_open": + assert "DECLARED GAP" in text + assert "WARDEN-WP-0040" in text + else: + assert stance["unknown"] == "fail_closed" diff --git a/wiki/ApprovalConsumption.md b/wiki/ApprovalConsumption.md new file mode 100644 index 0000000..fb1c695 --- /dev/null +++ b/wiki/ApprovalConsumption.md @@ -0,0 +1,77 @@ +# Approval consumption — notes for the signing lane's future consume + +**Status:** notes only. **Nothing is wired.** ops-warden's pre-sign gate consults +flex-auth (`wiki/PolicyGatedSigning.md`); it does not yet consume an approval +artifact. This page exists so the corrections we have already been given survive +until it does, rather than sitting in an inbox thread. + +Owner of the contract: `gate-house`, `docs/contracts/approval-consumption.md`. +ops-warden is one of two PEP-shaped consumers named there (the SSH signing lane). + +## Validate across two artifacts, never one + +`GH-DEC-2026-005` settles the step-1 artifact: `GET /v1/approvals/{id}/claim` +serves the **approval-claim**. flex-auth's proposed `ActionAuthorization` is not +required there and will not be served from that endpoint. + +| Artifact | Carries | +| --- | --- | +| approval-claim (step 1) | the approval fact — binding digest, validity window, consumption state, freshness, issuer | +| `DecisionEnvelope` (step 2) | exact `CheckRequest` match, policy pin | + +v0.8 §6.4 obligation 5 hardened this: each artifact **must** be validated against +the layer that owns its data, and a PIP **must not** republish the PDP's decision. +Neither artifact may be taken from the other. + +## Three things that are easy to get wrong + +**1. Do not require `provenance.authority == 'state-hub'`.** + +State Hub is a read model with no runtime approval authority. This is easy to get +wrong precisely *because* the hub is where everything else about a workplan lives, +so a consumer reaches for it as the authority on approval too. + +Worth holding as a shape rather than an instance: a PEP requiring the hub to have +asserted an approval is the same boundary violated from the **consuming** side +that "never register workplans or tasks by hand" forbids from the **writing** +side. Same rule, two directions. + +**2. The digest comparison target was corrected. Use `binding.approval_binding_digest`.** + +`GH-DEC-2026-008` as originally issued required the claim's `pdp_digest` to equal +the decision's `request_digest`. That is unimplementable — a claim travelling +inside a hashed request cannot name the digest of the request containing it — and +a fail-closed consumer obeying it would have denied permanently. Corrected by +`FLEX-DEC-2026-007` to the PDP's published **exclusion-scoped** digest, +`binding.approval_binding_digest`. + +ops-warden never copied the superseded wording anywhere (checked 2026-09-09), so +there was nothing to unwind. Recorded here so it cannot be reintroduced from an +older reading of the contract. + +**3. Digest comparison does not discharge attribution, and attribution is not +available today.** + +`GH-DEC-2026-010` rules that a PEP must be able to **attribute** a decision to +access-engine, and that matching digests does not achieve that. flex-auth's +envelope is unsigned and its pins serve plain HTTP, so that half is a declared +§13 gap owned by access-engine until `FLEX-WP-0024` lands. + +Nothing for ops-warden to build. But when this lane is wired, its validation is +**complete-minus-attribution** and must be described that way: fail-closed +protects against a decision point that is absent, not against one that lies. + +## Open dependency + +`WARDEN-WP-0039-T03` — whether an admitted flex-auth contract exists for a +delegated credential read where caller and resource owner differ. If the answer +changes the caller shape it may reach how this lane's consume is built, and +`gate-house` has asked to hear that before rather than after: a contract admitted +for that shape would reach §6.4 obligation 5, and whether such a delegation +attenuates or widens is doctrine they own. + +## See also + +- `wiki/PolicyGatedSigning.md` — the pre-sign gate as it actually is today +- `pep-stance.yaml` — unreachable-engine stance, and the marked `unknown` cell +- `history/2026-09-09-layer-model-v08-review.md` — the v0.8 assent round diff --git a/wiki/PolicyGatedSigning.md b/wiki/PolicyGatedSigning.md index 07e89d2..a60542b 100644 --- a/wiki/PolicyGatedSigning.md +++ b/wiki/PolicyGatedSigning.md @@ -192,6 +192,14 @@ Rollback is a versioned profile or registry rollback. Do not reintroduce `policy.enabled: false`: that would erase per-zone evidence and recreate the global control ADR-0009 supersedes. +## Approval consumption (not wired) + +This lane does not yet consume an approval artifact. The corrections already +received about how it must validate when it does — two artifacts never taken from +each other, the corrected `binding.approval_binding_digest` comparison target, and +the attribution gap that digest matching does not discharge — are held in +`wiki/ApprovalConsumption.md` so they are not lost between now and then. + ## See also - `tenancy.yaml` diff --git a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md index 1216d23..d7b3601 100644 --- a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md +++ b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md @@ -125,3 +125,28 @@ records classification coverage alongside stance, so a register row cannot read If both are declined, that is an answer and this workplan proceeds unchanged — the cell stays a **declared gap** under §11 with this workplan as its route, which is what the `declared-gap` mark (our own v0.6 finding) exists to express. + +**Answered 2026-09-09 — GH-DEC-2026-011.** Ask 1 declined, ask 2 adopted; +standard amended at `net-kingdom@64394e9`. + +Ask 1's refusal is correct and we are not appealing it: a sanctioned transitional +`fail_open` is indistinguishable at runtime from the stance the rule forbids, and +would make the rule optional at the only moment it costs anything. Ask 2 gave us +what we actually needed — §13.1 carries a Coverage column with this repo's +figures as its first entries, guarded so that coverage never softens a stance, +never gates one, and never makes a non-conformant cell conformant. + +The reversal condition on that column binds ops-warden first, since the column +exists because we asked: if a row is ever argued conformant *because* its coverage +is low, the column comes out. Our row reads non-conformant and low-coverage, and +the second is not a defence of the first. + +gate-house confirmed explicitly that they are **not** asking for the cell to be +flipped before coverage exists, and that this workplan's order is right. The cell +stays a declared gap under §11 with WP-0040 recorded as its route — the outcome +this task named as acceptable if both asks were declined, reached with one of them +adopted. + +T01–T03 are unchanged and still gate the conversion. Coverage is now measured +rather than asserted (`scripts/report_coverage.py`), so T02's reporting obligation +has a tool behind it and the published figure cannot drift from the register's.