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
156 lines
8.4 KiB
Markdown
156 lines
8.4 KiB
Markdown
# Security Layer Model v0.6 — ops-warden's review
|
||
|
||
**Date:** 2026-08-29
|
||
**Reviewed:** `security-layer-model_v0.6.md` (proposed), plus v0.5 and the companion
|
||
**Prior positions:** `ADR-0010` (v0.1 assent); `history/2026-08-29-layer-model-v04-review.md`
|
||
**Outcome:** no objection; one conformance action taken, two findings, one accepted SHOULD.
|
||
|
||
---
|
||
|
||
## Disposition of ops-warden's v0.4 findings
|
||
|
||
All three were acted on, two of them exactly as recommended.
|
||
|
||
| Finding | Outcome |
|
||
| --- | --- |
|
||
| §9.1 forces a false `pending` onto working capability | **Adopted** — v0.5 split it into `pending` and `declared-gap`, credited to ops-warden |
|
||
| §5 scope undefined for uncatalogued infrastructure | **Adopted** — "Tooling-layer system" now means a §4 Tooling row; the State Hub case is recorded, not policed |
|
||
| §9.6 atomicity lands on ops-warden's signing lane | **Ruled** — the load-bearing / attributive distinction, with ops-warden's `# audit must not block signing` named as the estate's live example |
|
||
|
||
The §9.6 ruling deserves a note, because it went in ops-warden's favour and that is
|
||
a reason to check it rather than accept it. The test is *"no control branches on
|
||
its presence"*. Verified: the only consumer of `audit.jsonl` is `warden activity`
|
||
(`cli.py`), which displays. Nothing gates on a signing record — not the agent
|
||
read-boundary, not `warden plan`, not the scorecard. The lane is genuinely
|
||
attributive and the trade is legitimate on the standard's own terms. The two
|
||
obligations that attach — declare it, never claim completeness — were already met
|
||
in `wiki/AuditTrail.md`, now updated to record the ruling rather than the open
|
||
question. **If a future ops-warden control ever gates on this trail, the trade has
|
||
to be revisited before that ships**, and that is recorded there.
|
||
|
||
`layer.yaml` is named in §11 as the estate's reference declaration form, including
|
||
the "record non-Tooling clients so the check is total" property. Offered again to
|
||
the repositories that have yet to declare.
|
||
|
||
---
|
||
|
||
## Conformance action taken — the stance map was not published
|
||
|
||
§6.4 obligation 3 requires a declared unreachable-engine stance that is total, per
|
||
zone, with no implicit default, *"published rather than held in code comments"* —
|
||
and §6.4 requires **every** PEP-shaped consumer to publish its map so the maps can
|
||
be inventoried. `ADR-0009` is named as the reference shape.
|
||
|
||
ops-warden was not doing this. The map lived in `PolicyConfig.failure_modes`, a
|
||
dataclass default in `src/warden/config.py`. That is not a code *comment*, but it
|
||
is not published either — it is merely written down, and a consumer of the estate
|
||
had no way to read ops-warden's stance without reading ops-warden's source.
|
||
|
||
Published as `pep-stance.yaml`, with the property that makes publishing worth
|
||
anything: `tests/test_layer_conformance.py` asserts the published map is **equal to
|
||
the shipped default**. A published map that may drift from the code is worse than
|
||
no map, because it invites reliance it cannot support. The file also records the
|
||
obligation-2 position (verdict never cached; input claims cached under their own
|
||
freshness rules) and the obligation-4 bound (§9.6 attributive).
|
||
|
||
---
|
||
|
||
## Finding 1 — §6.4 obligation 1 contradicts obligation 3, and ops-warden is the instance
|
||
|
||
> **1. No side effect without a decision record.** A PEP MUST NOT perform the
|
||
> protected action unless it holds a decision from `access-engine` identifying the
|
||
> request it was rendered for.
|
||
|
||
> **3. A declared unreachable-engine stance (§9.3):** total, per zone... `ops-warden`
|
||
> `ADR-0009` is the reference shape.
|
||
|
||
These cannot both be absolute. ops-warden's declared stance — blessed by §9.3 as
|
||
*"the only thing left"* when there is no engine to ask — is `fail_open` for `z0`–`z2`
|
||
and `unknown`. Applying it means issuing a certificate **without holding a
|
||
decision**, which obligation 1 forbids without qualification.
|
||
|
||
So the same section names ops-warden as the reference shape for obligation 3 while
|
||
obligation 1 makes ops-warden's shipped behaviour a violation. §9.3 settled the
|
||
substance; §6.4 restates it in a form that takes it back.
|
||
|
||
**Recommendation.** Bound obligation 1 by obligation 3:
|
||
|
||
> A PEP MUST NOT perform the protected action unless it holds a decision from
|
||
> `access-engine` identifying the request it was rendered for, **or its declared
|
||
> §9.3 stance for the applicable scope permits proceeding without one and the
|
||
> application of that stance is recorded in place of the decision**.
|
||
|
||
This is not a weakening. It is stricter than today's text in the case that matters:
|
||
it makes the *recorded application of the stance* mandatory, rather than leaving
|
||
"no decision record" as a silent state. ops-warden already does this — `ca.py`
|
||
writes `policy_zone`, `policy_failure_mode` and `policy_decision_id` (present only
|
||
where a decision was rendered) into both the signatures log and `audit.jsonl`, per
|
||
`ADR-0009` rule 4: *a fail-open signing result is metadata, not silence*.
|
||
|
||
This is the same shape as the v0.4 §9.1 finding: a rule written for the clean case,
|
||
correct there, producing a false result on the adjacent case the standard has
|
||
already sanctioned elsewhere.
|
||
|
||
---
|
||
|
||
## Finding 2 — §6.4 creates a register that §13 does not implement
|
||
|
||
§6.4: *"Every PEP-shaped consumer MUST publish its stance map, and those maps MUST
|
||
be inventoried — in `maturity-engine` once it exists, **in §13 until then**."*
|
||
|
||
§13 contains no stance-map rows. It records declared contacts and unowned
|
||
capabilities; there is no column, row, or section for a PEP stance. So the
|
||
obligation names a register that does not exist yet, and the failure mode §6.4
|
||
itself warns about — *"`z0`–`z2` and unknown fail open" becoming the estate's real
|
||
policy without anyone having compiled it* — is exactly what the missing register
|
||
permits.
|
||
|
||
**Recommendation.** Either add a stance-map table to §13 with the same
|
||
state/owner-status discipline the gap table has, or state that the inventory waits
|
||
for `maturity-engine` and mark the obligation pending under §9.1's own logic — a
|
||
requirement whose register does not exist is a capability catalogued without a
|
||
surface. ops-warden's row is ready to paste:
|
||
|
||
| PEP | Protected action | Scope | Stance | Published |
|
||
| --- | --- | --- | --- | --- |
|
||
| `ops-warden` | SSH certificate issuance | security-zone | open `z0`–`z2`+unknown, closed `z3`/n-a | `ops-warden/pep-stance.yaml` |
|
||
|
||
The second half matters more than the first: **ops-warden is currently the only
|
||
PEP that has published one**, so an inventory today would contain one row and that
|
||
is itself the finding. `ops-mason` is named PEP-shaped in the same paragraph.
|
||
|
||
---
|
||
|
||
## Accepted, not yet done — §9.6 emission cadence
|
||
|
||
§9.6: *"A source SHOULD declare an expected emission cadence, and a drop below it
|
||
SHOULD become a finding in its own right."*
|
||
|
||
ops-warden declares none. This is a genuine SHOULD and the reasoning behind it is
|
||
sound — it converts the suppression blind spot into something detectable without
|
||
any Tooling contact, because the source publishes its own stream. It is not done
|
||
here because a cadence asserted without evidence is worse than none: ops-warden's
|
||
signing volume is operator-driven and bursty, and a fabricated baseline would
|
||
generate findings that mean nothing. Deriving one from the existing trail is
|
||
tractable and is recorded as ops-warden's to do, not gate-house's to chase.
|
||
|
||
---
|
||
|
||
## On the pace
|
||
|
||
Six versions in two days, with four repositories' findings absorbed and credited,
|
||
is the §12 loop working at a rate the estate has not seen before. Two cautions,
|
||
offered as an interested consumer rather than as objections:
|
||
|
||
1. **§13 already says it should not be statute, and it is right.** The register has
|
||
grown every version. Moving it to `maturity-engine` is the stated plan; until
|
||
that exists, each version of the standard is also a snapshot of a backlog, and
|
||
the two have very different review intervals.
|
||
2. **The standard is `proposed` again at v0.6**, and the four repositories that
|
||
assented did so to v0.1. ops-warden's `ADR-0010` assent covers the three
|
||
boundary items, and nothing in v0.2–v0.6 has disturbed them — the layer, the
|
||
lane/rule demarcation, and doctrine-versus-runbook all stand. But the
|
||
`assented_by` list carries assent forward across five revisions, and a reader
|
||
could take it as assent to the current text. Worth distinguishing *assented to
|
||
the boundary* from *reviewed the current revision*; ops-warden has now done
|
||
both, and this note is the second.
|