# 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; T01–T03 stand unchanged. gate-house confirmed they are not asking for the cell to be flipped before coverage exists.