ops-warden/history/2026-08-29-layer-model-v06-review.md
tegwick 94f32bd160
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Review layer model v0.6; publish the PEP stance map §6.4 requires
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

156 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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