Compare commits

...
Sign in to create a new pull request.

57 commits

Author SHA1 Message Date
5b1a508610 docs: mark the unknown cell, measure the coverage we asked to publish
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>
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
2026-09-10 08:02:10 +02:00
a942ce805d 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 <noreply@anthropic.com>
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
2026-09-10 08:02:10 +02:00
66db87e6ae 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 <noreply@anthropic.com>
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
2026-09-10 08:02:10 +02:00
9c01a8a212 chore: record hub ids for WARDEN-WP-0040
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
2026-09-09 16:42:01 +02:00
custodian-sync
d8791a9296 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
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
2026-09-09 16:41:44 +02:00
3aedd8f844 docs: record the answers received and the questions routed
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
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 <noreply@anthropic.com>
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
2026-09-09 16:40:46 +02:00
1e3eb8df2a chore: refresh high-risk paths for committed catalog
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
2026-09-09 14:43:48 +02:00
83fdd08f88 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 <noreply@anthropic.com>
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
2026-09-09 14:43:44 +02:00
8e1b621116 docs: assent to v0.8 obligation 3, and price its adoption
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 <noreply@anthropic.com>
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
2026-09-09 14:42:20 +02:00
df48ee96e0 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/<name> 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 <noreply@anthropic.com>
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
2026-09-09 14:40:05 +02:00
3a01b8f1b5 chore(records): index the policy refusal workplan and residual
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 20:35:52 +02:00
custodian-sync
084bca783b chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-09-08 20:23:59 +02:00
repo-manager
589d9067da repo.work.assign_missing_identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 17:04:02 +02:00
541cd9043f docs(warden): record installed refusal proof and live binding residual
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 17:03:58 +02:00
19bb75c614 fix(install): refresh Warden source when reinstalling the CLI
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 16:58:22 +02:00
31d9b6671c Refuse explicit policy authentication and binding denials before side effects
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 16:46:00 +02:00
11e5e8be0f chore: record hub ids for 2026-09-08 workplans
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
2026-09-08 14:57:42 +02:00
custodian-sync
89a8dea2a1 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-09-08 14:57:31 +02:00
b9ca0cc3f5 chore: refresh high-risk paths for committed catalog
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
2026-09-08 14:55:03 +02:00
c1330049d9 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 <noreply@anthropic.com>
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
2026-09-08 14:55:02 +02:00
96dbb22aab docs: use canonical State Hub reads with caller attribution
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102
2026-09-05 10:24:19 +02:00
37b8f39bd0 chore: synchronize work-record index
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4
2026-09-05 01:26:57 +02:00
afc50354df chore: refresh high-risk paths for committed catalog
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4
2026-09-05 01:22:37 +02:00
00145d705e feat: complete local layer model v0.7 conformance work
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4
2026-09-05 01:19:48 +02:00
89b117f640 docs(workplan): persist Forgejo token rotation gate
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06d83-1cbc-71f2-b0dc-e0f48cedae43
2026-09-04 23:42:24 +02:00
81f28ff814 feat(routing): repoint whynot npm lane to Forgejo
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06d83-1cbc-71f2-b0dc-e0f48cedae43
2026-09-04 23:39:11 +02:00
custodian-sync
efabe526d8 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-09-04 23:31:05 +02:00
custodian-sync
60cf826c43 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
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
2026-09-04 23:19:11 +02:00
8afcc9c321 chore: refresh Warden work-record indexes
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 01:27:55 +02:00
repo-manager
529feeac49 repo.work.assign_missing_identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:51:59 +02:00
9770d6ad66 docs: close Warden credential workplans
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:51:52 +02:00
custodian-sync
31694ab64d chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-09-01 00:50:08 +02:00
eddb5d426b chore: refresh high-risk credential paths
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:47:25 +02:00
4fee839b11 feat: route Policy Nexus source credential
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:46:28 +02:00
8f01eefb1e Preserve Warden config in attended child
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:24:18 +02:00
b4c1d3900a Fix contained OpenBao login handoff
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:13:26 +02:00
repo-manager
4e267179db chore(registrar): assign State Hub identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4014535@bnt-lap001
Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c
2026-08-29 14:56:21 +02:00
custodian-sync
d18d18e313 chore(consistency): sync task status from DB [auto]
Some checks failed
CI Smoke / host-smoke (push) Has been cancelled
CI Smoke / container-smoke (push) Has been cancelled
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
2026-08-29 14:56:20 +02:00
2e3ff772f4 Refresh work-record index for WARDEN-WP-0034
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
2026-08-29 14:55:36 +02:00
fd08950231 Align INTENT and SCOPE to layer model v0.7; assess gaps; open WARDEN-WP-0034
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>
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
2026-08-29 14:50:55 +02:00
94f32bd160 Review layer model v0.6; publish the PEP stance map §6.4 requires
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>
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
2026-08-29 10:20:49 +02:00
ec625873fb Review layer model v0.4; correct an unsound audit claim it exposes
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>
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
2026-08-29 02:46:50 +02:00
f815bb35ca Implement §5.3 machine-readably — layer.yaml, checker, conformance tests
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 <noreply@anthropic.com>
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
2026-08-29 02:45:29 +02:00
57d39ede0f Refresh work-record index
Regenerated by fix-consistency; adds the inbound v0.3 review intake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2564823@bnt-lap001
Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9
2026-08-28 22:43:06 +02:00
repo-manager
f289465b90 repo.work.create_intake WARDEN-IN-0002
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-08-28 22:40:24 +02:00
custodian-sync
a45280f30d chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-08-28 22:02:58 +02:00
61c992923c WARDEN-WP-0027-T02: the owner gate closed five days ago
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>
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
2026-08-28 22:01:47 +02:00
ee94c18938 WARDEN-WP-0033 finished — key-cape accepted the issuance question five days ago
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 <noreply@anthropic.com>
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
2026-08-28 22:00:09 +02:00
d7f4ebcfe0 Re-emit the high-risk path artifact after the NetKingdom SSO lanes
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 <noreply@anthropic.com>
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
2026-08-28 21:55:33 +02:00
repo-manager
f766400563 chore(registrar): assign State Hub identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4014535@bnt-lap001
Assistant-Session: d0036016-73e8-4da1-8e47-563e3ab39a3c
2026-08-28 21:52:03 +02:00
70d8b503f0 Assent to the NetKingdom security layer model (WARDEN-IN-0001)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>
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
2026-08-28 21:47:44 +02:00
85d3078bae Refresh work-record index
Regenerated by fix-consistency; adds the inbound assent intake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2564823@bnt-lap001
Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9
2026-08-28 21:35:57 +02:00
repo-manager
467635e84b repo.work.create_intake WARDEN-IN-0001
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-08-28 21:30:28 +02:00
15730bb650 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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2564823@bnt-lap001
Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9
2026-08-28 21:21:08 +02:00
347e47ce8e 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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2564823@bnt-lap001
Assistant-Session: 2a7ed827-4928-4b9f-8613-9135c9cadfe9
2026-08-28 20:33:43 +02:00
37c387bd34 fix(workplans): qualify archived ad-hoc identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-28 00:34:02 +02:00
030362085a fix(workplans): qualify ad-hoc identifiers with the repository prefix
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
`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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-28 00:28:23 +02:00
59 changed files with 6599 additions and 146 deletions

View file

@ -16,6 +16,8 @@ distinction that matters here.
| `ADR-0006` | Enforcement is zone-scoped, never a global flag | | `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-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-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 ### Owned versus inherited — check `owner:` before changing anything

View file

@ -2,16 +2,16 @@
# Custodian Brief — ops-warden # Custodian Brief — ops-warden
**Domain:** infotech **Domain:** infotech
**Last synced:** 2026-08-25 18:17 UTC **Last synced:** 2026-09-09 14:41 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams ## Active Workstreams
### Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed ### Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule
Progress: 5/6 done | workplan_id: `4627d89b-4b00-562a-81e9-76e96f90fa7e` Progress: 4/5 done | workplan_id: `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef`
**Open tasks:** **Open tasks:**
- ! Tasks `5acac140` - ! Tasks `7d1b3c82`
### Tamper-resistant credential governance + mass rotation/lockdown (Strand B) ### Tamper-resistant credential governance + mass rotation/lockdown (Strand B)
Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf` Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf`
@ -19,11 +19,18 @@ Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf`
**Open tasks:** **Open tasks:**
- ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee` - ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee`
## Inbox Hygiene ### Repoint the whynot-design npm lane to Forgejo
Progress: 2/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e`
**Stale unread:** 9 message(s) older than 3 day(s) — triage at session start. **Open tasks:**
**Missing thread_id:** 7 unread message(s) lack supersession chains. - ! Prove routing and publication `a8b1b855`
- ! secrets-engine: Re: Need the warden-sign live apply/handoff (FLEX-WP-0007 T4) `80456912`
### 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.)*
--- ---
## MCP Orientation (when available) ## MCP Orientation (when available)

2360
.repo-manager/index.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -221,6 +221,19 @@ Install bundled memory: `make install-all` then `make check-memory` from
## Workplan Convention (ADR-001) ## Workplan Convention (ADR-001)
### State Hub reads and caller identity
Use `GET /workplans/{id}` and `GET /tasks/?workplan_id=<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=<workplan-uuid>'
```
Work items originate as files in this repo — not in the hub. The hub is a Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files. read/cache/index layer that rebuilds from files.

120
INTENT.md
View file

@ -1,5 +1,68 @@
---
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 # INTENT
> **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/`.
>
> **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`.
>
> **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 declarations are files, not this note** — prose cannot distinguish a
> declaration from a transcribed review (§11):
>
> | 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 > This file captures **why this repository exists**, the **direction it is
> moving toward**, and the **kind of system it is meant to become**. > moving toward**, and the **kind of system it is meant to become**.
> It is intentionally **aspirational and stable**, not a description of > It is intentionally **aspirational and stable**, not a description of
@ -77,30 +140,52 @@ 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 **This is a runbook, not doctrine.** Security doctrine, the authority model, and the
`net-kingdom` — especially: 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.
| Plane / component | Role in access | ops-warden relationship | The machine-readable form is `registry/routing/catalog.yaml`, and the executable form
| --- | --- | --- | is `warden plan "<need>"` / `warden route find`. Prefer either over this table — it is
| **key-cape / Keycloak** | Identity — who is the actor, MFA, IAM Profile claims | Instruct identity path; do not re-implement OIDC | orientation, and the catalog is the source of truth (`ADR-0001`).
| **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` | | Component | Layer | Owns | ops-warden relationship |
| **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 | | **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 |
| **tenant-engine** | Tenant/client secret custody and front door | Route tenant lanes once fronted; ops-warden's current tenant proxies are interim (§9) | | **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 |
| **user-engine** | End-user identity and account lifecycle | No ops-warden lane today; route rather than absorb | | **key-cape / Keycloak** | Tooling | Identity — who the actor is, MFA, IAM Profile claims | Instruct the identity path; do not re-implement OIDC |
| **ops-warden** | Operational SSH certificates — short-lived host access | **Own and issue** this lane | | **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 |
| **ops-bridge** | Tunnel transport — consumes certs via `cert_command` | Primary consumer; document integration | | **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** |
| **railiance-infra** | Host principals, force-command, SSH hardening | Instruct host-side deployment; do not own Ansible | | **tenant-engine** | Engine | Tenant/client secret custody and front door | Route tenant lanes once fronted; current tenant proxies are interim (section 9) |
| **railiance-platform** (deploy) | OpenBao/K8s/platform service deployment | Instruct production endpoints; do not deploy clusters | | **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.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.
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: Canonical references:
- `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/platform-identity-security-architecture.md`
- `net-kingdom/docs/responsibility-map.md` - `net-kingdom/docs/responsibility-map.md`
- `wiki/AccessManagementDirective.md` (ops SSH actor model) - `wiki/AccessManagementDirective.md` (ops SSH actor model)
- `.claude/rules/credential-routing.md` (agent-facing runbook — stays inline by design)
--- ---
@ -123,7 +208,8 @@ Canonical references:
| Need | Route to | | Need | Route to |
| --- | --- | | --- | --- |
| OIDC login, MFA, human identity claims | key-cape / Keycloak (NetKingdom IAM Profile) | | 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) | | 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 | | Inter-Hub operator keys, LLM provider credentials | OpenBao or approved operator secret store |
| Tunnel lifecycle, port forwarding | ops-bridge | | Tunnel lifecycle, port forwarding | ops-bridge |

View file

@ -24,12 +24,12 @@ sync:
uv sync uv sync
install-warden: install-warden:
uv tool install . --force uv tool install . --force --reinstall-package ops-warden
install-memory: 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 -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) @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 install: install-warden

View file

@ -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 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). 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 ### Issue vs route
ops-warden executes exactly one lane with its own authority and routes/assists the rest. ops-warden executes exactly one lane with its own authority and routes/assists the rest.
@ -156,6 +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-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-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-0009` | Adopt security-zones v0.1 and compile explicit workload membership; PEP failure mode is per zone |
| `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 Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention — are credential-management standard, the-custodian's ADR-001 workplan convention — are
@ -433,11 +456,12 @@ Downstream: `ops-bridge` (primary), kaizen agents, CI automations, human operato
| Repo | Relationship | | Repo | Relationship |
| --- | --- | | --- | --- |
| `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 | | `net-kingdom` | Canonical security architecture; ops-warden aligns to it |
| `ops-bridge` | Primary cert_command consumer | | `ops-bridge` | Primary cert_command consumer |
| `railiance-infra` | Host-side SSH principals and hardening | | `railiance-infra` | Host-side SSH principals and hardening |
| `railiance-platform` | OpenBao deployment and platform secrets | | `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 | | `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 | | `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) | | `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) |
@ -486,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/CredentialRouting.md` | Which subsystem for each credential need |
| `wiki/WorkloadSecurityPosture.md` | Secret-store posture, workload maturity, and blocker triage | | `wiki/WorkloadSecurityPosture.md` | Secret-store posture, workload maturity, and blocker triage |
| `registry/routing/catalog.yaml` | Machine-readable routing pointer catalog | | `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 | | `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 | | `wiki/NetKingdomSecurityMap.md` | Platform security component map |
| `examples/warden.production.example.yaml` | Production warden.yaml template | | `examples/warden.production.example.yaml` | Production warden.yaml template |

View file

@ -8,10 +8,11 @@
| Kind | ID | Status | Lane | Source | | Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| workplan | ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md | | workplan | WARDEN-WP-ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md |
| workplan | ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md | | workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md |
| workplan | ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md | | workplan | WARDEN-WP-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-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-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-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 | | workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
@ -29,16 +30,29 @@
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md | | 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-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-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 | ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | workplan | WARDEN-WP-0034 | active | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
| task | ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
| task | ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | | workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
| task | ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md | | workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
| task | ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md | | workplan | WARDEN-WP-0038 | finished | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
| task | ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md | | workplan | WARDEN-WP-0039 | blocked | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
| task | ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md | | workplan | WARDEN-WP-0040 | proposed | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
| task | ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md | | task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
| task | ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.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-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-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-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 | | task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
@ -126,6 +140,27 @@
| task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | 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-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-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-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-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.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 |
| 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 |

View file

@ -5,15 +5,15 @@ title: "ADR-0004 — High-risk lanes refuse raw value streaming to agent session
domain: infotech domain: infotech
repo: ops-warden repo: ops-warden
status: accepted status: accepted
version: "1.0" version: "1.1"
revision: "1" revision: "2"
owner: ops-warden owner: ops-warden
binds: "ops-warden; any agent runtime calling warden access" binds: "ops-warden; any agent runtime calling warden access"
created: "2026-07-20" created: "2026-07-20"
updated: "2026-08-18" updated: "2026-09-04"
last_reviewed: "2026-08-18" last_reviewed: "2026-09-04"
review_interval: 6m 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: "" supersedes: ""
successor: "" 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. 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*. 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 **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 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 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. **`risk: high` becomes a load-bearing catalog field** rather than documentation.
Classifying a lane now changes runtime behaviour, so it must be set deliberately. 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 **We accept that `--unsafe-stdout` still exists for humans.** The CLI recognizes the
`WARDEN_AGENT_ID`, so an agent that does not set it is not caught. That is a known issued subject when the runtime presents it and otherwise relies on
limit: this ADR raises the floor for cooperating runtimes and hardens the store `WARDEN_AGENT_ID`; both markers remain caller-visible and therefore advisory. This
behind them; it does not claim to stop a determined caller. 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 ## Related

View file

@ -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`

View file

@ -71,3 +71,4 @@ and the publication is a defect.
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure | ops-warden | | `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-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-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 |

View file

@ -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 <bao>/v1/<mount>/sign/<role>` | **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 "<need>"`,
`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`

View file

@ -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.

View file

@ -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.2v0.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.

View file

@ -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.2v0.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.

View file

@ -0,0 +1,175 @@
# 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.
---
## 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; T01T03 stand unchanged. gate-house confirmed
they are not asking for the cell to be flipped before coverage exists.

93
intakes/intakes.md Normal file
View file

@ -0,0 +1,93 @@
# 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: closed
outcome: assented
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.'
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-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'
state_hub_intake_id: "01a04d97-94cd-7b49-8019-a91c7fce8adb"
```

128
layer.yaml Normal file
View file

@ -0,0 +1,128 @@
# ops-warden — NetKingdom security layer declaration
#
# 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
#
# §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.7"
repository: ops-warden
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:
- id: ssh-ca-signing-write
shape: "5.3" # declared engine gap
module: src/warden/vault.py
symbol: VaultCA.sign
tooling: OpenBao
operation: "HTTP POST <addr>/v1/<mount>/sign/<role> 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 <path> <field>=- (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 <path>"
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.

146
pep-stance.yaml Normal file
View file

@ -0,0 +1,146 @@
# ops-warden — PEP unreachable-engine stance map
#
# 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
#
# §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
# 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
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
# 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
# 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.
# 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"
# §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
# §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"

View file

@ -10,19 +10,21 @@
# declares it, and is null where the field set has not been established -- # declares it, and is null where the field set has not been established --
# null means unknown, never 'one field'. # null means unknown, never 'one field'.
generated_at: "2026-08-22T23:31:10Z" generated_at: "2026-09-10T05:54:39Z"
source: ops-warden/registry/routing/catalog.yaml source: ops-warden/registry/routing/catalog.yaml
catalog_revision: "0fae0904ce8d8694338dd53a8a79abec5fec788d" catalog_revision: "406446f7bb38efadf85849756c067a124902a2ec"
catalog_revision_date: "2026-08-23T01:31:05+02:00" catalog_revision_date: "2026-09-10T07:54:39+02:00"
catalog_dirty: false catalog_dirty: false
high_risk_lane_count: 21 high_risk_lane_count: 24
concrete_path_count: 14 concrete_path_count: 15
# Graded high but not a single KV address -- a routing pattern, a broker # 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. # grant, or a non-KV lane. Nothing here for a policy to deny.
no_concrete_path: no_concrete_path:
- database-dynamic-credentials - database-dynamic-credentials
- inter-hub-bootstrap-ssh - inter-hub-bootstrap-ssh
- net-kingdom-lldap-bind-credential
- net-kingdom-privacyidea-admin-token
- object-storage-sts - object-storage-sts
- openbao-api-key - openbao-api-key
- openbao-platform-admin-login - openbao-platform-admin-login
@ -75,6 +77,11 @@ paths:
metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets
owner_repo: railiance-platform owner_repo: railiance-platform
fields: null # field set not established -- unknown, not one 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 - id: railiance-backup-offsite-lane
data_path: platform/data/workloads/railiance/backup/offsite-lane data_path: platform/data/workloads/railiance/backup/offsite-lane
metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane

View file

@ -40,6 +40,24 @@
version: 1 version: 1
entries: 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 - id: ssh-cert-host-access
title: Short-lived SSH certificate for host / ops reachability 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). # Emits a signed certificate — a public artifact. The private key never leaves the caller (WARDEN-WP-0032-T05).
@ -196,19 +214,19 @@ entries:
lane: ceremony lane: ceremony
- id: whynot-design-npm-publish - 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). # Publish rights to the package registry — a leaked token is a supply-chain write, not a read (WARDEN-WP-0032-T05).
risk: high risk: high
workload_ref: workload_ref:
applicability: applicable applicability: applicable
unknown_reason: "whynot-design has not published an authoritative workload identity declaration." 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 owner_repo: railiance-platform
subsystem: OpenBao subsystem: OpenBao
warden_executes: false warden_executes: false
wiki_ref: wiki/playbooks/whynot-design-npm-publish.md#worker-checklist wiki_ref: wiki/playbooks/whynot-design-npm-publish.md#worker-checklist
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-06-29" reviewed: "2026-09-04"
status: active status: active
delegation: delegation:
mode: native mode: native
@ -223,7 +241,15 @@ entries:
# form is superseded; do not reintroduce it. # form is superseded; do not reintroduce it.
auth_method: "bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read" auth_method: "bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read"
path_template: "platform/workloads/coulomb/whynot-design/npm-publish" 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" policy_ref: "flex-auth check secret.read:whynot-design"
exec_capable: true exec_capable: true
lane: secret lane: secret
@ -238,9 +264,45 @@ entries:
owner: railiance-platform owner: railiance-platform
automatable: false automatable: false
steps: 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)." - "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 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 <package>@<version>` through the governed execution lane (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 - id: flex-auth-policy-check
title: Authorization decision — may this actor perform this action title: Authorization decision — may this actor perform this action
@ -281,16 +343,16 @@ entries:
delegation: delegation:
mode: interim mode: interim
intended_owner: key-cape 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." 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=<domain> 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-08-21" reviewed: "2026-09-08"
verified: asked-and-waiting verified: owner-confirmed
# Login lane (WP-0014 T4) — interactive auth bootstrap, not a secret read. No # 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 # 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 # 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. # captures the resulting token — the owner tool writes it to the caller's store.
lane: login lane: login
auth_method: "browser OIDC via key-cape / Keycloak" auth_method: "browser OIDC via key-cape / Keycloak at auth/netkingdom"
fetch_command: "bao login -method=oidc role=<domain>" fetch_command: "bao login -no-print -method=oidc -path=netkingdom role=<domain>"
exec_capable: true exec_capable: true
- id: ops-bridge-tunnel - id: ops-bridge-tunnel
@ -710,9 +772,9 @@ entries:
delegation: delegation:
mode: interim mode: interim
intended_owner: key-cape 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)" 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 -- <command>` 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-08-17" reviewed: "2026-09-09"
verified: unverified verified: owner-confirmed
risk: high risk: high
workload_ref: workload_ref:
applicability: applicable applicability: applicable
@ -727,11 +789,15 @@ entries:
rotation: rotation:
method: rotate method: rotate
owner: key-cape 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: steps:
- "Generate a fresh newline-free secret through an approved execution transport; never print it." - "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)."
- "Write the same value to OpenBao and sso/keycape-rapp-qonto-client, then restart KeyCape." - "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 wrong-secret and excessive-scope denial without printing tokens." - "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 - id: net-kingdom-lldap-bind-credential
title: NetKingdom LLDAP bind credential for identity and privacyIDEA resolver title: NetKingdom LLDAP bind credential for identity and privacyIDEA resolver

View file

@ -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/<name>` 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())

View file

@ -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())

129
scripts/report_coverage.py Normal file
View file

@ -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())

View file

@ -12,13 +12,21 @@ reuses `expand_handoff` to build the command it runs as the caller.
""" """
from __future__ import annotations from __future__ import annotations
import os
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Mapping, Optional
from warden.config import ConfigError, load_config from warden.config import ConfigError, load_config
from warden.routing.models import RouteEntry 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 @dataclass
class ExpandedHandoff: class ExpandedHandoff:
"""Handoff templates with `<domain>` substituted when a domain is supplied. """Handoff templates with `<domain>` substituted when a domain is supplied.
@ -34,6 +42,25 @@ class ExpandedHandoff:
exec_capable: bool 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]: def _sub_domain(value: Optional[str], domain: Optional[str]) -> Optional[str]:
if value and domain: if value and domain:
return value.replace("<domain>", domain) return value.replace("<domain>", domain)

View file

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import json import json
import os
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from typing import Annotated, List, Optional 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. # T04 — agent identity on a high-risk lane: never stream raw secret data.
# Agents may use sanctioned transports (--out / --exec / --wrap / --fingerprint). # 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 = ( raw_value_stream = (
not is_login and not do_exec and not wrap and not out_path and not fingerprint not is_login and not do_exec and not wrap and not out_path and not fingerprint
) )

View file

@ -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: def _score_for(catalog: Catalog, entry: RouteEntry, need: str) -> int:
if entry.id == need.strip(): if entry.id == need.strip():
return 100 return 100
@ -294,6 +336,7 @@ def build_plan(
) )
entry, score = matches[0] entry, score = matches[0]
intent = _need_intent(need)
# Draft-only top match without active alternatives → unroutable # Draft-only top match without active alternatives → unroutable
if entry.status == "draft" and not include_draft: if entry.status == "draft" and not include_draft:
@ -378,6 +421,80 @@ def build_plan(
domain=domain, 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): if _lane_is_autonomous(entry):
return AccessPlan( return AccessPlan(
need=need, need=need,

View file

@ -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 = httpx.post(url, json=request, headers=headers, timeout=10.0)
response.raise_for_status() response.raise_for_status()
except httpx.HTTPStatusError as e: 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( _evaluator_failure(
f"flex-auth rejected sign policy check (HTTP {e.response.status_code}) " f"flex-auth rejected sign policy check (HTTP {e.response.status_code}) "
f"for security zone {zone!r}", 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 = httpx.post(url, json=request, headers=headers, timeout=10.0)
response.raise_for_status() response.raise_for_status()
except httpx.HTTPStatusError as e: 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( _evaluator_failure(
f"flex-auth rejected fetch policy check (HTTP {e.response.status_code})", f"flex-auth rejected fetch policy check (HTTP {e.response.status_code})",
fail_closed=fail_closed, fail_closed=fail_closed,

View file

@ -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; 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 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 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 an isolated helper session, never returns that output, requires successful
records are metadata only. 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 * **G3 policy gate before fetch.** The CLI runs ``check_fetch_policy`` before
calling anything here; this module refuses to run an unresolved command template. 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. """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, The default home is proven writable before the OIDC client starts. Login,
child, and revocation output are captured and discarded. Any non-empty output, child, and revocation output are captured and discarded. A successful login
persistence defect, or non-zero result fails closed; any possibly issued token may return client output only after the private helper has been populated;
is revoked before the isolated helper directory is removed. 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: if not child_argv:
raise ProxyError( raise ProxyError(
@ -400,6 +403,10 @@ def proxy_attended_login_exec(
root, session, root_created = _prepare_attended_login_home() root, session, root_created = _prepare_attended_login_home()
helper = session / _TOKEN_HELPER_NAME helper = session / _TOKEN_HELPER_NAME
env = _caller_env() 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["HOME"] = str(session)
env.pop("BAO_TOKEN", None) env.pop("BAO_TOKEN", None)
env.pop("VAULT_TOKEN", None) env.pop("VAULT_TOKEN", None)
@ -428,7 +435,7 @@ def proxy_attended_login_exec(
except (OSError, ProxyError): except (OSError, ProxyError):
helper_valid = False 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( revoked = _revoke_contained(
resolved.argv[0], env=env, possible_output=login_output resolved.argv[0], env=env, possible_output=login_output
) )

View file

@ -6,7 +6,7 @@ from pathlib import Path
from typer.testing import CliRunner 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.cli import app
from warden.routing.models import RouteEntry 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() 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 ------------------------------------------------------------------- # --- CLI -------------------------------------------------------------------
def test_access_advisory_output(monkeypatch): def test_access_advisory_output(monkeypatch):

View file

@ -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"] == "z2-continuity"
assert attrs["security_zone_admission"] == "satisfied" assert attrs["security_zone_admission"] == "satisfied"
assert attrs["security_zone_revision"] == "sha256:zone-revision" 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/<name> 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

View file

@ -0,0 +1,201 @@
"""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"
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"
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"
)
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"
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"]
# --- 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"

View file

@ -119,3 +119,87 @@ def test_cli_plan_json():
assert payload["verdict"] == "autonomous" assert payload["verdict"] == "autonomous"
assert payload["organization_posture"] == "build" assert payload["organization_posture"] == "build"
assert payload["lane_id"] == "agent-harness-forgejo-deploy" 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"

View file

@ -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 check_sign_policy(cfg, spec) == "decision:advisory"
assert spec.policy_zone == "unknown" assert spec.policy_zone == "unknown"
assert spec.policy_outcome == "audit_only" 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"}

View file

@ -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

View file

@ -234,6 +234,28 @@ def test_cli_proxy_requires_caller_auth(monkeypatch, tmp_path):
assert r.exit_code == 3 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): def test_cli_proxy_rejects_retired_no_policy_bypass(monkeypatch, tmp_path):
_proxy_env(monkeypatch, tmp_path) _proxy_env(monkeypatch, tmp_path)
monkeypatch.setenv("VAULT_TOKEN", "caller") monkeypatch.setenv("VAULT_TOKEN", "caller")
@ -391,6 +413,70 @@ def test_attended_login_unexpected_output_is_contained_revoked_and_cleaned(
assert not (tmp_path / ".warden-attended-login").exists() 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_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(): def test_real_catalog_login_entry_is_login_lane():
from warden.routing import load_catalog from warden.routing import load_catalog
e = load_catalog(_repo_catalog()).get("key-cape-oidc-login") e = load_catalog(_repo_catalog()).get("key-cape-oidc-login")
@ -526,6 +612,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() 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): def test_access_agent_high_risk_fingerprint_allowed(tmp_path, monkeypatch):
"""Agents may use --fingerprint on high-risk lanes (no raw value).""" """Agents may use --fingerprint on high-risk lanes (no raw value)."""
_proxy_env(monkeypatch, tmp_path) _proxy_env(monkeypatch, tmp_path)

View file

@ -143,21 +143,69 @@ def test_route_find_vault_token_ops_warden_prefers_broker_lane():
assert matches[0].id == "ops-warden-warden-sign-token" 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(): 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()) catalog = load_catalog(_repo_catalog())
e = catalog.get("whynot-design-npm-publish") e = catalog.get("whynot-design-npm-publish")
assert e is not None and e.is_active and e.exec_capable assert e is not None and e.is_active and e.exec_capable
assert e.resolvable is True 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 "<" not in e.fetch_command and ">" not in e.fetch_command
assert "platform/workloads/coulomb/whynot-design/npm-publish" 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 <package>@<version>`" in rotation
assert "Gitea" not in rotation
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(): def test_generic_and_template_lanes_not_resolvable():
catalog = load_catalog(_repo_catalog()) catalog = load_catalog(_repo_catalog())
# generic openbao lane has <FIELD>/<path_template>; login lane has <domain>. # generic openbao lane has <FIELD>/<path_template>; login lane has <domain>.
assert catalog.get("openbao-api-key").resolvable is False 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=<domain>"
)
def test_platform_admin_login_lane_is_exact_and_non_value_bearing(): def test_platform_admin_login_lane_is_exact_and_non_value_bearing():
@ -946,12 +994,31 @@ def test_asked_and_waiting_is_not_verification():
def test_stale_gaps_flags_unverified_even_when_the_date_is_today(): 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()) catalog = load_catalog(_repo_catalog())
stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=date(2026, 8, 21))} today = date(2026, 9, 8)
# Asked of key-cape on 2026-08-21 and unanswered -- zero days old, still stale. stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=today)}
assert "key-cape-oidc-login" in stale unverified = [
# Confirmed by the owner the same day -- fresh. e for e in catalog.gaps(include_draft=True)
assert "issue-core-ingestion-api-key" not in stale 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): def test_invalid_verification_method_rejected(tmp_path):
@ -979,10 +1046,10 @@ def test_cli_route_gaps_fail_on_stale_exits_3(repo_catalog_env):
assert result.exit_code == 3 assert result.exit_code == 3
rows = json.loads(result.stdout) rows = json.loads(result.stdout)
assert any(r["stale"] for r in rows) assert any(r["stale"] for r in rows)
# An asked-and-waiting lane stays stale until it is verified, regardless of # A lane whose blocker was never re-established stays stale regardless of
# how many calendar days have elapsed since the request. # how many calendar days have elapsed since it was reviewed. Asserted over
assert any( # the verification method rather than one named lane -- see
r["stale"] # test_stale_gaps_flags_unverified_even_when_the_date_is_today.
and r["verified"] == "asked-and-waiting" for row in rows:
for r in rows if row["verified"] in ("asked-and-waiting", "unverified"):
) assert row["stale"], f"{row['id']} is unverified but not stale"

View file

@ -13,8 +13,11 @@ def test_repo_catalog_uses_only_explicit_workload_references():
report = build(ROOT / "registry/routing/catalog.yaml", Path.home()) report = build(ROOT / "registry/routing/catalog.yaml", Path.home())
assert report["ok"] is True assert report["ok"] is True
assert len(report["resolved"]) == 3 assert len(report["resolved"]) == 3
assert len(report["unknown"]) == 17 assert len(report["unknown"]) == 18
assert len(report["not_applicable"]) == 9 # 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"]} == { assert {row["lane"] for row in report["resolved"]} == {
"ops-warden-warden-sign-token", "ops-warden-warden-sign-token",
"issue-core-ingestion-api-key", "issue-core-ingestion-api-key",

View file

@ -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

View file

@ -32,6 +32,20 @@ fresh file starts.
high-entropy runs). Signing and proxy paths swallow audit failures so gatekeeping high-entropy runs). Signing and proxy paths swallow audit failures so gatekeeping
never blocks the primary action — but tests prove values cannot be written. 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
> **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.
--- ---
## Query ## Query

View file

@ -134,6 +134,11 @@ truth.
## Caller identity ## 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 The production flex-auth pin authenticates ops-warden with Kubernetes
TokenReview and binds `resource.system: ops-warden` to TokenReview and binds `resource.system: ops-warden` to
`system:serviceaccount:ops-warden:ops-warden`. Supported token sources are: `system:serviceaccount:ops-warden:ops-warden`. Supported token sources are:
@ -187,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 `policy.enabled: false`: that would erase per-zone evidence and recreate the
global control ADR-0009 supersedes. 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 ## See also
- `tenancy.yaml` - `tenancy.yaml`

View file

@ -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.

View file

@ -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 -- <reviewed-source-read-command>
```
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.

View file

@ -1,11 +1,18 @@
# whynot-design npm publish token # whynot-design npm publish token
Date: 2026-06-29 Date: 2026-09-04
Catalog: `whynot-design-npm-publish` (status `active`, `resolvable: true`) Catalog: `whynot-design-npm-publish` (status `active`, `resolvable: true`)
Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f617fc) Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f617fc)
The `NPM_AUTH_TOKEN` that publishes `@whynot/design` to the coulomb Gitea npm registry > **Rotation required (2026-09-04).** The OIDC role and OpenBao read path are
(`https://gitea.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold > 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 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 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. **as the caller** and never persists, caches, or logs the value.
@ -16,7 +23,8 @@ this token** — it is the access front door: `warden access` proxies the read f
| Field | Value | | Field | Value |
| --- | --- | | --- | --- |
| OpenBao path | `platform/workloads/coulomb/whynot-design/npm-publish` | | 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` | | KV mount | `platform` |
| Read policy | `workload-kv-read-whynot-design-npm-publish` | | Read policy | `workload-kv-read-whynot-design-npm-publish` |
| OIDC login | `bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read` | | OIDC login | `bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read` |
@ -42,16 +50,23 @@ 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: for this lane (SECRETS-WP-0003, decision e6381a56); ops-warden routes to it:
```bash ```bash
secrets-engine route whynot-design-npm-publish --json # pointer / readiness secrets-engine route whynot-design-npm-publish --json # pointer / readiness
secrets-engine exec --catalog whynot-design-npm-publish -- \
npm view @whynot/design@<version> version \
--registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish secrets-engine exec --catalog whynot-design-npm-publish -- npm publish
``` ```
**ops-warden transparent fallback** — same lane via the `warden access` proxy (fetches as **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 ```bash
# --exec needs the env-var name. The zone-aware policy gate always runs first. # --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> version \
--registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/
warden access whynot-design-npm-publish --field npm_token \
--exec -- npm publish --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 On either path the value transits to you (or the child env) and never enters
ops-warden's memory, disk, or audit log. ops-warden's memory, disk, or audit log.
@ -63,9 +78,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 `resolvable: true` means the lane is concrete and `--fetch` will run; a template lane
reports `false`. reports `false`.
4. **Publish is outward-facing and immutable.** `npm publish` is irreversible and public. 4. **Publish is outward-facing and immutable.** Before publishing, confirm that
Even once the token resolves, hold for an explicit operator "yes, publish" — do not `package.json#publishConfig.registry` is exactly the Forgejo URL above, verify the
auto-run it from an agent. 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.
--- ---

View file

@ -1,5 +1,5 @@
--- ---
id: ADHOC-2026-06-27 id: WARDEN-WP-ADHOC-2026-06-27
type: workplan type: workplan
title: "Ad Hoc Tasks — 2026-06-27" title: "Ad Hoc Tasks — 2026-06-27"
domain: infotech domain: infotech
@ -9,7 +9,7 @@ owner: claude
topic_slug: custodian topic_slug: custodian
created: "2026-06-27" created: "2026-06-27"
updated: "2026-06-27" updated: "2026-06-27"
state_hub_workstream_id: "142b171b-c34b-4a45-91a5-c77e6d07ec6f" state_hub_workstream_id: "a222c91f-3bb5-58a4-b6b2-f0fb18cdd5c3"
--- ---
# Ad Hoc Tasks — 2026-06-27 # Ad Hoc Tasks — 2026-06-27
@ -19,10 +19,10 @@ Low-risk opportunistic fixes completed directly during the consolidation session
### T01 — Fix stale `warden` CLI install + make it usable outside the repo ### T01 — Fix stale `warden` CLI install + make it usable outside the repo
```task ```task
id: ADHOC-2026-06-27-T01 id: WARDEN-WP-ADHOC-2026-06-27-T01
status: done status: done
priority: medium priority: medium
state_hub_task_id: "867c72c9-9904-400f-8542-04264e5856c2" state_hub_task_id: "9176b560-8ca5-5143-888d-479857fe60f0"
``` ```
issue-core reported (msg `70bcf238`) that the `warden` CLI on `~/.local/bin` lacked issue-core reported (msg `70bcf238`) that the `warden` CLI on `~/.local/bin` lacked

View file

@ -1,5 +1,5 @@
--- ---
id: ADHOC-2026-06-29 id: WARDEN-WP-ADHOC-2026-06-29
type: workplan type: workplan
title: "Ad Hoc Tasks — 2026-06-29" title: "Ad Hoc Tasks — 2026-06-29"
domain: infotech domain: infotech
@ -9,7 +9,7 @@ owner: claude
topic_slug: custodian topic_slug: custodian
created: "2026-06-29" created: "2026-06-29"
updated: "2026-06-29" updated: "2026-06-29"
state_hub_workstream_id: "1c0460b7-bc8a-48db-96d4-681bce18ac91" state_hub_workstream_id: "13fa845f-852e-55ec-a2a5-2296996e0216"
--- ---
# Ad Hoc Tasks — 2026-06-29 # Ad Hoc Tasks — 2026-06-29
@ -17,10 +17,10 @@ state_hub_workstream_id: "1c0460b7-bc8a-48db-96d4-681bce18ac91"
### T01 — Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4) ### T01 — Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4)
```task ```task
id: ADHOC-2026-06-29-T01 id: WARDEN-WP-ADHOC-2026-06-29-T01
status: done status: done
priority: medium priority: medium
state_hub_task_id: "371235cc-b9d3-4103-b09f-e4e01cc83c5b" 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 flex-auth (msg `ea00620b`) asked ops-warden to help close FLEX-WP-0007 T4 (joint OpenBao

View file

@ -1,5 +1,5 @@
--- ---
id: ADHOC-2026-08-11 id: WARDEN-WP-ADHOC-2026-08-11
type: workplan type: workplan
title: "Ad Hoc Tasks — 2026-08-11" title: "Ad Hoc Tasks — 2026-08-11"
domain: infotech domain: infotech
@ -9,7 +9,7 @@ owner: claude
topic_slug: custodian topic_slug: custodian
created: "2026-08-11" created: "2026-08-11"
updated: "2026-08-15" updated: "2026-08-15"
state_hub_workstream_id: "bc6dd3e3-ab2b-567a-9574-30d2650f20a2" state_hub_workstream_id: "9f99cc64-4682-5f20-b13e-89af2b6f7c70"
--- ---
# Ad Hoc Tasks — 2026-08-11 # Ad Hoc Tasks — 2026-08-11
@ -17,10 +17,10 @@ state_hub_workstream_id: "bc6dd3e3-ab2b-567a-9574-30d2650f20a2"
### T01 — Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite) ### T01 — Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite)
```task ```task
id: ADHOC-2026-08-11-T01 id: WARDEN-WP-ADHOC-2026-08-11-T01
status: done status: done
priority: medium priority: medium
state_hub_task_id: "ddd0c95c-1196-5251-a4ca-6ef441c2f377" state_hub_task_id: "0771d121-278c-556e-9509-841cf6e657c3"
``` ```
rapp-postgres (msg `96907986`, residual from RAPP-POSTGRES-WP-0002-T04) reported the rapp-postgres (msg `96907986`, residual from RAPP-POSTGRES-WP-0002-T04) reported the
@ -39,10 +39,10 @@ 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) ### T02 — Triage the stale ops-warden inbox (11 unread, C-28/C-29)
```task ```task
id: ADHOC-2026-08-11-T02 id: WARDEN-WP-ADHOC-2026-08-11-T02
status: done status: done
priority: medium priority: medium
state_hub_task_id: "fda35ad4-f526-5012-a2be-ef6b5dcbc8af" state_hub_task_id: "0ed58145-732f-5102-b6a8-b931d9b6ba08"
``` ```
`fix-consistency` flagged 11 unread messages older than 3 days, two of them as possible `fix-consistency` flagged 11 unread messages older than 3 days, two of them as possible
@ -73,10 +73,10 @@ read. Inbox is now empty.
### T03 — warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover ### T03 — warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover
```task ```task
id: ADHOC-2026-08-11-T03 id: WARDEN-WP-ADHOC-2026-08-11-T03
status: done status: done
priority: medium priority: medium
state_hub_task_id: "7ef4b6b0-26bf-5456-8429-8cf8458c2ddc" state_hub_task_id: "337ae793-c6b0-59e9-8a07-3a7ccba237aa"
``` ```
secrets-engine (msg `80456912`, 2026-06-29) is holding a validated non-mutating dry-run secrets-engine (msg `80456912`, 2026-06-29) is holding a validated non-mutating dry-run

View file

@ -1,5 +1,5 @@
--- ---
id: ADHOC-2026-08-17 id: WARDEN-WP-ADHOC-2026-08-17
type: workplan type: workplan
title: "Ad Hoc Tasks — 2026-08-17" title: "Ad Hoc Tasks — 2026-08-17"
domain: infotech domain: infotech
@ -9,7 +9,7 @@ owner: claude
topic_slug: custodian topic_slug: custodian
created: "2026-08-17" created: "2026-08-17"
updated: "2026-08-17" updated: "2026-08-17"
state_hub_workstream_id: "8376eb89-36f2-52b0-a59a-2954b2e49349" state_hub_workstream_id: "5c6c2bbb-b944-5afd-b89c-20d865518849"
--- ---
# Ad Hoc Tasks — 2026-08-17 # Ad Hoc Tasks — 2026-08-17
@ -21,10 +21,10 @@ workload-identity owner.
### T01 — Answer flex-auth: how should `/v1/check` authenticate its callers? ### T01 — Answer flex-auth: how should `/v1/check` authenticate its callers?
```task ```task
id: ADHOC-2026-08-17-T01 id: WARDEN-WP-ADHOC-2026-08-17-T01
status: done status: done
priority: high priority: high
state_hub_task_id: "b980d1f7-2fcc-58dd-97d8-6e9b9ba57bc9" state_hub_task_id: "04a2f8f9-e70b-5eed-ad87-343c8f9ef501"
``` ```
flex-auth (msg `130a148c`, FLEX-WP-0015 T02) reported that `POST /v1/check` and flex-auth (msg `130a148c`, FLEX-WP-0015 T02) reported that `POST /v1/check` and
@ -69,10 +69,10 @@ Their four questions answered in `wiki/NetKingdomSecurityMap.md`
### T02 — user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only ### T02 — user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only
```task ```task
id: ADHOC-2026-08-17-T02 id: WARDEN-WP-ADHOC-2026-08-17-T02
status: done status: done
priority: medium priority: medium
state_hub_task_id: "e76b9b3e-189b-5a4f-8815-4ccf2057507c" state_hub_task_id: "0e815282-2fad-5c8d-be34-398e492737d0"
``` ```
user-engine (msg `2af4a124`) answered the open confirm question on user-engine (msg `2af4a124`) answered the open confirm question on
@ -95,10 +95,10 @@ consumes it and has no authority over custody, rotation, or issuance.
### T03 — key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker ### T03 — key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker
```task ```task
id: ADHOC-2026-08-17-T03 id: WARDEN-WP-ADHOC-2026-08-17-T03
status: done status: done
priority: medium priority: medium
state_hub_task_id: "edfb0626-5022-5f49-aaf9-08e0d1002b90" state_hub_task_id: "e21781d9-a35d-5916-b335-d12131f97a22"
``` ```
key-cape (msg `099b7cba`) acknowledged the lane staying interim on ops-warden. key-cape (msg `099b7cba`) acknowledged the lane staying interim on ops-warden.
@ -118,10 +118,10 @@ workplan.
### T04 — Session hygiene ### T04 — Session hygiene
```task ```task
id: ADHOC-2026-08-17-T04 id: WARDEN-WP-ADHOC-2026-08-17-T04
status: done status: done
priority: low priority: low
state_hub_task_id: "62109af6-f095-5e7b-9258-0fcec29d30e8" state_hub_task_id: "b15724e0-c27a-5260-a810-4dd25bff2228"
``` ```
- [x] `uv run pytest -q` → 338 passed, 4 deselected (routing anchor + no-double-source - [x] `uv run pytest -q` → 338 passed, 4 deselected (routing anchor + no-double-source

View file

@ -0,0 +1,179 @@
---
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"
state_hub_workstream_id: "182f3aff-8c8a-5766-8a68-5fe35f423f7a"
---
# 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
state_hub_task_id: "29cef28a-d93b-5318-bfdb-69dbe11fa3ba"
```
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
state_hub_task_id: "8efb8013-7b90-52ef-a17c-d63f5ce3752c"
```
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=<domain>` 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
state_hub_task_id: "bd621821-2116-51c9-a09a-a3660b5e5557"
```
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
state_hub_task_id: "9ada01d2-b14b-579e-921c-275820f255db"
```
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
state_hub_task_id: "06bf8508-d1a2-51b4-b951-02539524ebb9"
```
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
state_hub_task_id: "a0b88db9-2886-54dd-a925-9e0521044aa0"
```
`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.

View file

@ -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, that owner gate is open; acceptance will permit preparation of a new scenario,
not execution or reuse of the terminal one. 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: Tamper-evident policy governance + reconcile
```task ```task

View file

@ -4,14 +4,14 @@ type: workplan
title: "Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed" title: "Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed"
domain: infotech domain: infotech
repo: ops-warden repo: ops-warden
status: active status: finished
owner: ops-warden owner: ops-warden
topic_slug: netkingdom topic_slug: netkingdom
planning_priority: P1 planning_priority: P1
depends_on_workplans: depends_on_workplans:
- WARDEN-WP-0030 - WARDEN-WP-0030
created: "2026-08-21" created: "2026-08-21"
updated: "2026-08-23" updated: "2026-08-28"
state_hub_workstream_id: "4627d89b-4b00-562a-81e9-76e96f90fa7e" 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 ```task
id: WARDEN-WP-0033-T04 id: WARDEN-WP-0033-T04
status: wait status: done
priority: medium priority: medium
state_hub_task_id: "5acac140-a586-5db3-b231-bbf236710786" 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 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. 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 ```task
id: WARDEN-WP-0033-T05 id: WARDEN-WP-0033-T05
status: done status: done

View file

@ -0,0 +1,246 @@
---
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: active
owner: ops-warden
topic_slug: netkingdom
planning_priority: P1
depends_on_workplans:
- WARDEN-WP-0030
created: "2026-08-29"
updated: "2026-09-05"
state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef"
---
# 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: done
priority: high
state_hub_task_id: "8b3bdb9f-d2c2-5b3e-89e2-417bf3e37484"
```
**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.
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: done
priority: high
state_hub_task_id: "3318ee1a-b5d9-5d39-baf7-9c42a8bc7b55"
```
**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.
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: done
priority: medium
state_hub_task_id: "a891b32c-b0a7-59f6-a5cd-977be65c09ca"
```
**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.
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: done
priority: medium
state_hub_task_id: "94e73daa-f74d-51fd-8639-68896a4066ee"
```
**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`.
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: wait
priority: low
state_hub_task_id: "7d1b3c82-9b96-5087-a53a-496212909029"
```
**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.
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.
**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
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
- `net-kingdom/SECURITY-COMPANION.md` v0.2
- `ADR-0002`, `ADR-0003`, `ADR-0004`, `ADR-0005`, `ADR-0009`, `ADR-0010`

View file

@ -0,0 +1,46 @@
---
id: WARDEN-WP-0035
type: workplan
title: "Register the Policy Nexus Forgejo source-read route"
domain: infotech
repo: ops-warden
status: finished
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
```task
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
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: done
priority: high
state_hub_task_id: "1fa8f778-3e46-5f44-86c4-cab8628b7e60"
```
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.

View file

@ -0,0 +1,43 @@
---
id: WARDEN-WP-0036
type: workplan
title: "Accept contained OpenBao login output only after helper persistence"
domain: infotech
repo: ops-warden
status: finished
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
```task
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
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-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
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.

View file

@ -0,0 +1,111 @@
---
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
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"
```
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.
**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.

View file

@ -0,0 +1,126 @@
---
id: WARDEN-WP-0038
type: workplan
title: "warden plan must distinguish reading a secret from mutating one"
domain: infotech
repo: ops-warden
status: finished
owner: ops-warden
topic_slug: netkingdom
planning_priority: P1
depends_on_workplans:
- WARDEN-WP-0029
created: "2026-09-08"
updated: "2026-09-09"
state_hub_workstream_id: "751ad530-e44b-52c4-b70e-cb47568a0179"
---
# 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 <lane> --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.
## 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.

View file

@ -0,0 +1,104 @@
---
id: WARDEN-WP-0039
type: workplan
title: "Preserve explicit policy caller refusals before credential and CA effects"
domain: infotech
repo: ops-warden
status: blocked
owner: codex
topic_slug: custodian
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
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
state_hub_task_id: "eaad08d9-ae47-5f50-8278-ea402a0e2869"
```
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: 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,
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
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."
state_hub_task_id: "8ca28b63-2e04-5333-b635-011b124c1806"
```
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.
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).

View file

@ -0,0 +1,152 @@
---
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"
state_hub_workstream_id: "c8ee441e-1be1-5219-910c-e79ff23cc9ec"
---
# 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
state_hub_task_id: "611f0901-9fb5-5954-8dd0-a860c5f0cbec"
```
**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
state_hub_task_id: "54ad4864-7bcf-592e-a187-9239a81a0b11"
```
**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
state_hub_task_id: "93eaf1f2-daaa-5e22-8804-85ca9433571c"
```
**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
state_hub_task_id: "222a8c5d-0c0f-5a1d-98d8-02be7dca3358"
```
**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.
**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.
T01T03 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.

View file

@ -1,5 +1,5 @@
--- ---
id: ADHOC-2026-07-07 id: WARDEN-WP-ADHOC-2026-07-07
type: workplan type: workplan
title: "Ad Hoc Tasks — 2026-07-07" title: "Ad Hoc Tasks — 2026-07-07"
domain: infotech domain: infotech
@ -9,7 +9,7 @@ owner: grok
topic_slug: custodian topic_slug: custodian
created: "2026-07-07" created: "2026-07-07"
updated: "2026-07-07" updated: "2026-07-07"
state_hub_workstream_id: "2e8bc783-2e37-4b86-a691-177b18329783" 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`). > **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 +19,10 @@ state_hub_workstream_id: "2e8bc783-2e37-4b86-a691-177b18329783"
### T01 — Roll out proxy pipe fix (be3b4a2) ### T01 — Roll out proxy pipe fix (be3b4a2)
```task ```task
id: ADHOC-2026-07-07-T01 id: WARDEN-WP-ADHOC-2026-07-07-T01
status: done status: done
priority: high priority: high
state_hub_task_id: "55b6de52-b958-4b72-94eb-4ba1b8da2629" state_hub_task_id: "bf985c95-bea6-5057-94f9-9cfa7e1c9dd8"
``` ```
`warden access` failed on `reuse-surface-hub-write-token` because `shlex.split` `warden access` failed on `reuse-surface-hub-write-token` because `shlex.split`