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:
parent
45a65c0748
commit
22915cf2a0
3 changed files with 187 additions and 6 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue