Rule on unknown and the scoping axis as GH-DEC-2026-009

access-engine exercised the divergence capability it claimed in the v0.6
round, on the first occasion §13.1 held two rows. ops-warden resolves
unknown to fail_open, secrets-engine to fail_closed; both conformant,
both total, both test-pinned, disagreeing about the one case that by
construction nobody planned for. They also scope over different axes, so
the register cannot answer what an inventory exists to answer.

Ruling 1: unknown is not a zone and MUST fail closed. §9.3 permits
trading availability for openness per zone — and that trade requires
knowing the zone. Where the scope is unknown the trade cannot have been
made for it, so a permissive unknown does not extend a considered
decision, it invents the most permissive one. An unreachable engine is a
known request in a degraded system; an unclassified subject is not.
unknown is the cheapest state for an attacker to induce, so failing open
on it makes being unclassifiable a privilege escalation requiring no
credential, which §8's asymmetry forbids wherever it appears.

Ruling 2: each map declares its scoping axis and its relation to zone.
Forcing everyone onto zones would make secrets-engine assert a zone it
cannot know, and a fiction in a runtime-read test-pinned file is worse
than an honest incommensurability. The register records the axes and
states that cross-axis aggregation is unavailable.

ops-warden acquires one non-conformant cell at v0.8. It did everything
asked — published first, built the reference form, offered it estate-wide
— so this goes to the assent round rather than being imposed quietly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtJBr77gMFLrN93iEevqQJ

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 425128@bnt-lap001
Assistant-Session: f5944d8b-dac4-4e1a-87eb-8b3d8f314a63
This commit is contained in:
tegwick 2026-09-06 14:18:36 +02:00
parent 45a65c0748
commit 22915cf2a0
3 changed files with 187 additions and 6 deletions

View file

@ -1112,3 +1112,110 @@ decided_by: Bernd Worsch
created: '2026-09-06T12:17:19.104531Z'
updated: '2026-09-06T12:17:19.104531Z'
```
## Context
On 2026-08-29 `access-engine` told this repository it was the party positioned to
notice when the aggregate of consumer stances diverged from what the policy packages
say — and then recorded that the claim was sound but the data was not there, because
§13.1 held one row and one row cannot diverge from anything. `secrets-engine`'s
publication made it two, and the divergence appeared immediately.
That is worth noting on its own: the register earned its existence the first time it
had two entries.
**`ops-warden` declares `unknown``fail_open`.** Explicit, versioned, under
`ADR-0009`. **`secrets-engine` declares `unknown``fail_closed`.** Both maps are
total, published, test-pinned, and §6.4-conformant. Neither is a defect under v0.7.
They disagree about the one case that by construction is the one nobody planned for.
They are also **not comparable**: `ops-warden` scopes by security zone,
`secrets-engine` by catalog stage — explicitly as the "equivalent scope" §6.4 permits,
until zone membership arrives as a claim. So the register cannot answer *"what is the
estate's stance for a `z2`-protected workload"*, which is the question an inventory
exists to answer.
`access-engine` reported both as observations and explicitly **not** as a request that
either repository change its stance, on the ground that a PDP does not set a
consumer's stance — §9.3's two-owner split, which was its own finding, cutting against
its own convenience. That restraint is correct and it is why the question arrives here:
setting a consumer's stance is not the PDP's to do, but doctrine is Gate House's.
## Decision
### 1. `unknown` is not a zone, and it fails closed
**A stance map MUST resolve `unknown` to `fail_closed`.** It is not a scope over which
the §9.3 availability trade may be made.
The reasoning is a distinction v0.7 does not draw, and the two maps are the proof it
is needed. §9.3 sanctions trading availability for openness **per zone** — knowingly,
for a named scope, at a declared cost. `ops-warden`'s `fail_open` for `z0``z2` is
exactly that and remains sanctioned: it is a *known* request in a *degraded* system.
The consumer knows what it is being asked and cannot reach the engine to ask.
`unknown` is a different failure. It is not a degraded system; it is an
**unclassified subject**. The consumer does not know what it is being asked.
**A per-zone trade requires knowing the zone.** Where the scope is unknown, the trade
cannot have been made *for* it — there was no zone in hand when the profile was
written, so no one weighed this request's cost. Resolving `unknown` to the permissive
answer therefore does not extend a considered decision; it invents one, and it invents
the most permissive one available.
The adversarial reading settles it. `unknown` is the cheapest state for an attacker to
induce: an unregistered workload, a malformed label, a resource created before
classification, a race against registry propagation. `fail_open` on `unknown` makes
"be unclassifiable" a privilege escalation, and it is the one escalation that requires
no credential. This is the §8 asymmetry — nothing may manufacture privilege — applied
where the estate had left a gap open by symmetry with a rule that does not reach it.
`ops-warden`'s map is conformant today and this makes one cell of it non-conformant at
v0.8. That is a doctrine change with a cost to a repository that did everything asked
of it — published early, built the reference form, and offered it estate-wide — and it
should be argued in the assent round rather than imposed quietly. Their `z0``z2`
zone stances are untouched.
### 2. A stance map declares its scoping axis and its relation to zone
**Each published map MUST name the axis it scopes over, and state its relationship to
security zone: a mapping, or an explicit declaration that none exists yet and why.**
The register is not repaired by forcing everyone onto zones. `secrets-engine` chose
catalog stage because zone membership is not yet available to it as a claim; making it
assert zones now would produce a fiction, and a fiction in a runtime-read,
test-pinned file is worse than an honest incommensurability.
**The register instead states what it cannot answer.** §13.1 carries the axis per row
and records that cross-axis aggregation is not available. An inventory that silently
cannot answer its own question is the §9.1 defect again — a surface catalogued without
the capability behind it.
`access-engine` notes the convergence path and it is the right one: zone *membership*
compiles into the registry snapshot it already consumes, while per-zone *stance*
belongs to the consumer. If membership reaches the decision as a claim,
`secrets-engine`'s axis converges onto zones with neither side inventing a mapping.
Until then the axes are recorded, not reconciled.
The cost of two incommensurable axes is small; the cost of five is not. This lands
before the third row rather than after the fifth.
### What is not decided here
Neither repository is asked to change its zone stances. `secrets-engine`'s stale
reference to a durable `ActionAuthorization` record in its map — the artifact
`GH-DEC-2026-005` shelved — is theirs to correct and `access-engine` has already told
them; it is a naming error in a runtime-read file, where a stale name outlives a stale
comment, and it does not change the stance.
## Reversal
Reverse ruling 1 if a consumer demonstrates a scope that is genuinely unknown *and*
genuinely low-consequence, where failing closed on it costs availability with no
security gain — the plausible candidate is a read-only diagnostic path under §5.1. The
expected resolution is that such a path is not a protected side effect and therefore
has no PEP obligation at all, rather than that it needs a permissive `unknown`. If
that resolution fails on a real path, this ruling is too broad.
Reverse ruling 2 if declaring the axis proves to be all the register needed, in which
case the "states what it cannot answer" half is redundant rather than wrong.

View file

@ -30,6 +30,7 @@ Assembly and circulation are `GH-WP-0003-T06`.
| A5 | §6.4, §8 | `GH-DEC-2026-005`, `GH-DEC-2026-008` | T05 |
| A6 | §17 | `GH-DEC-2026-004` | T07 |
| A7 | §11, §12 | four observed instances; proposed by `approval-engine` | T08 |
| A8 | §6.4, §13.1 | `GH-DEC-2026-009` | T09 |
---
@ -130,12 +131,13 @@ one.
**§13.1 rows to transcribe at the cut** — carried here so the cut is mechanical:
| Consumer | Stance map | Shape |
| --- | --- | --- |
| `ops-warden` | `ops-warden/pep-stance.yaml` | total per zone; `fail_open` for `z0``z2` and unknown, sanctioned by §9.3 and recorded per §6.4 obligation 1 limb two; test asserts published map equals shipped default |
| `user-engine` | `user-engine/pep-stance.yaml` | total per zone; `fail_closed` for `z0``z3`, unknown, and not-applicable; test asserts published map equals `user_engine.pep_stance` |
| `tenant-engine` | `tenant-engine/pep-stance.yaml` | total; `fail_closed` for unset, unreachable, non-allow, unknown; test asserts file equals shipped behaviour |
| `ops-mason` | — | **unpublished**; PEP-shaped under §6.4 (opening a route). Recorded as a gap, not a stance |
| Consumer | Stance map | Axis | Shape |
| --- | --- | --- | --- |
| `ops-warden` | `ops-warden/pep-stance.yaml` | security zone | total per zone; `fail_open` for `z0``z2` and unknown, sanctioned by §9.3 and recorded per §6.4 obligation 1 limb two; test asserts published map equals shipped default |
| `user-engine` | `user-engine/pep-stance.yaml` | security zone | total per zone; `fail_closed` for `z0``z3`, unknown, and not-applicable; test asserts published map equals `user_engine.pep_stance` |
| `tenant-engine` | `tenant-engine/pep-stance.yaml` | security zone | total; `fail_closed` for unset, unreachable, non-allow, unknown; test asserts file equals shipped behaviour |
| `secrets-engine` | `secrets-engine/pep-stance.yaml` | catalog stage — interim, pending zone membership as a claim | total over stage plus unknown; no implicit default; runtime-read; pinned to `SHIPPED_STANCE` by test |
| `ops-mason` | — | — | **unpublished**; PEP-shaped under §6.4 (opening a route). Recorded as a gap, not a stance |
Three of four are total and fail-closed. The `ops-mason` row is the finding §6.4's
closing paragraph anticipated, and it is more useful in the register than absent from
@ -338,6 +340,58 @@ as stronger than it is, which would be the same defect one layer up.
---
## A8 — §6.4 and §13.1, `unknown` and the scoping axis (T09)
Settled by `GH-DEC-2026-009`, on the first occasion §13.1 held enough rows to diverge.
**Add to §6.4 obligation 3:**
> **`unknown` is not a zone and MUST resolve to `fail_closed`.** §9.3 permits trading
> availability for openness *per zone* — knowingly, for a named scope, at a declared
> cost. That trade requires knowing the zone. Where the scope is unknown it cannot
> have been made for this request, so resolving `unknown` permissively does not extend
> a considered decision, it invents the most permissive one available.
>
> An unreachable engine and an unclassified subject are different failure cases. The
> first is a known request in a degraded system and the trade is available for it. The
> second is not: `unknown` is the cheapest state for an attacker to induce — an
> unregistered workload, a malformed label, a resource created before classification,
> a race against registry propagation — and a permissive `unknown` makes being
> unclassifiable a privilege escalation requiring no credential. §8's asymmetry
> forbids that wherever it appears.
>
> Zone stances are untouched by this. Raised by `access-engine` as an observation on
> two conformant maps that disagree, and explicitly not as a request that either
> consumer change its stance — a PDP does not set a consumer's stance.
**Add to §6.4 obligation 3, and to the §13.1 register columns:**
> A published map MUST name the **axis** it scopes over and state its relationship to
> security zone: either a mapping, or an explicit declaration that none exists yet and
> why. §13.1 carries the axis per row.
>
> "Per zone **or equivalent scope**" permits axes that cannot be aggregated, and the
> register cannot then answer *"what is the estate's stance for a `z2`-protected
> workload"* — the question an inventory exists to answer. Forcing every consumer onto
> zones is the wrong repair where zone membership is not yet available as a claim;
> asserting a zone one cannot know is a fiction, and a fiction in a runtime-read,
> test-pinned file is worse than an honest incommensurability. **The register therefore
> records the axes and states that cross-axis aggregation is unavailable**, rather than
> implying an answer it does not have. An inventory that silently cannot answer its own
> question is §9.1's defect in another place.
>
> The convergence path is zone membership reaching the decision as a claim, after
> which a stage-scoped map converges onto zones without either side inventing a
> mapping. Until then the axes are recorded, not reconciled.
**Register impact at the cut.** `ops-warden`'s `unknown``fail_open` cell becomes
non-conformant under this amendment; its `z0``z2` stances do not. That is a real cost
to the repository that published first, built the reference form, and offered it
estate-wide, and it is put to the assent round rather than imposed — the A3 row stays
as written until v0.8 is accepted.
---
## Not in this set
Recorded so the omissions are deliberate rather than forgotten.

View file

@ -183,3 +183,23 @@ them and no staleness marker on the derivatives.
Drafted as A7 — a §11 marking obligation on the publisher and a §12 paragraph on the
consumer. The limit is stated in the draft: marking makes staleness visible, it does
not detect a marked derivative that is still wrong.
```task
id: GH-WP-0003-T09
status: done
priority: high
```
**`unknown` and the scoping axis (A8).** `access-engine` exercised the divergence
capability it claimed in the v0.6 round, on the first occasion §13.1 held two rows.
`ops-warden` resolves `unknown` to `fail_open` and `secrets-engine` to `fail_closed`;
both maps are conformant, total and test-pinned, and they disagree about the case
nobody planned for. The two maps also scope over different axes, so the register
cannot answer the question an inventory exists to answer.
Settled as `GH-DEC-2026-009` and drafted as A8: `unknown` is not a zone and fails
closed, because the §9.3 trade requires knowing the zone and a permissive `unknown`
makes being unclassifiable a privilege escalation needing no credential; and each map
declares its axis and its relation to zone, with the register stating what it cannot
answer rather than implying it can. `ops-warden` acquires one non-conformant cell at
v0.8 and it goes to the assent round rather than being imposed.