ops-warden/history/2026-08-29-layer-model-v04-review.md
tegwick ec625873fb
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Review layer model v0.4; correct an unsound audit claim it exposes
Assessment in history/2026-08-29-layer-model-v04-review.md. No objection to the
ruling; both ops-warden amendments were adopted (5.2 conduit, 5.3 declared gap).
Three findings, one against us.

The one against us is real. 9.6 requires emission atomic with the state change
for load-bearing evidence. ops-warden ca.py carries `pass  # audit must not block
signing` and AuditTrail.md advertises that the trail never blocks the primary
action, so a failed append loses the event while the cert still issues -- a
suppressed event leaving the chain intact, which is exactly what 9.6 describes.

Whether to make it atomic is gate-house doctrine, not ops-wardens call: it would
give the estates operational access lane a new dependency on its own evidence
store. But one half of the fix is ours regardless -- the trail must not be read
as complete. AuditTrail.md now says absence of a record is not evidence of
absence, which it did not.

Co-Authored-By: Claude Opus 5 <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

6.8 KiB

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.