repo.work.resolve_decision GH-DEC-2026-002

correlation_id: 75014e3f-ab3f-4038-9e2a-719f2da53abc
reason: GH-WP-0002-T03: record the revocation failure mode
source: repo-manager

Assistant: grok
Assistant-Session: 01a04d89-aaa5-7443-945e-b3055cd4b7e4
This commit is contained in:
repo-manager 2026-08-29 14:50:58 +02:00
parent 6a5d2170a4
commit 23cbefcb7b

View file

@ -167,7 +167,7 @@ the estate declining to adopt the authority vocabulary.
id: GH-DEC-2026-002
kind: decision
title: Revocation fails closed only when approval-engine's own store is down
status: open
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
@ -182,9 +182,75 @@ affects:
- audit-core
- flex-auth
created: '2026-08-29T12:50:04.747439Z'
updated: '2026-08-29T12:50:04.747439Z'
updated: '2026-08-29T12:50:58.873684Z'
rationale: 'Approved as GH-WP-0002-T03. Local-outbox fail-closed is the only remaining
closed path: if approval-engine cannot insert the outbox row, the revocation does
not commit. An audit-core outage must not block a revocation. Synchronous emission
inside the mutation is forbidden even though it is atomic. Proceed-with-gap is rejected
for load-bearing approval evidence.'
decided_by: Bernd Worsch
decided_at: '2026-08-29T12:50:58.873684Z'
```
## Context
`GH-IN-0001` (from `audit-core`) required that the revocation failure mode be a
recorded decision rather than an implementation accident. Both sides are
defensible on their own: fail-closed makes an audit dependency into an
availability risk on the revocation path; proceed-with-gap needs a detectable
marker so the gap is visible rather than silent.
v0.5's local-outbox rule already narrowed this. With the queue in
`approval-engine`'s own store, fail-closed triggers only when that store is
down — where the change could not have been recorded anyway — and an
`audit-core` outage does not block a revocation. Satisfying atomicity by
emitting synchronously to `audit-core` inside the mutation is also atomic, and
turns an audit outage into an inability to revoke: the operation least
tolerable to block during an incident.
The statute states the locality rule at §9.4. This record is the decision the
workplan still owed, so an implementer cannot pick the other side by accident.
Wire: `docs/contracts/approval-outbox.md`.
## Decision
**If the local outbox insert cannot commit, the revocation does not commit.**
The same rule applies to issuance, use, and supersession.
**An `audit-core` outage MUST NOT block a revocation.** Drain is asynchronous
and at-least-once. `audit-core` dedupes on `event_id`.
**Synchronous emission to `audit-core` inside the state-change transaction is
forbidden**, even though it is atomic.
Proceed-with-gap is rejected for load-bearing approval evidence. A revocation
that succeeds while its event is lost is the exact case `AUDIT-IN-0001`
conditioned assent on.
## What this authorizes
- `approval-engine` failing a revoke (and any other mutation) when its own
store cannot insert the outbox row.
- Continuing to revoke while `audit-core` is down, with the row draining later.
- Treating emit-after-commit, a second non-local queue, or a sync `audit-core`
call inside `BEGIN``COMMIT` as out of contract.
## What this does not authorize
- Claiming the outbox closes adversarial omission. Atomicity prevents crash;
cadence and reconciliation detect suppression after the fact (§9.6).
- A validity query on `audit-core`.
- Any change to another repository's workplans.
## Reversal
Revert this record and the outbox contract. The falsifier is operational: if
failing closed on the local store removes the ability to revoke during the
incidents this engine exists for, the trade needs revisiting — but the
alternative is a silent evidence gap on the event an attacker most wants
missing, which is worse.
## GH-DEC-2026-003 — The PEP consumes an approval before the protected side effect
```yaml
@ -209,3 +275,67 @@ affects:
created: '2026-08-29T12:50:06.944092Z'
updated: '2026-08-29T12:50:06.944092Z'
```
## Context
Statute §16 left open who marks an approval consumed, and at what point
relative to the decision. `flex-auth` named three failure modes neither engine
closes alone: an ALLOW never consumed; a double consumption by racing callers;
consumption after a failed action. `approval-engine` performs the mutation
because `access-engine` never mutates, and refused to wire a public consume by
guessing the contract. `APPROVAL-WP-0001-T05` and `FLEX-WP-0017-T05` are wait
on this record.
§9.7.3 forbids inferring consumption from a decision record. That stands. The
same paragraph currently reads as if the action must precede the consume call.
That reading cannot enforce single use: two racing PEPs can both act, and CAS
then prevents only the second record.
## Decision
**The PEP-shaped consumer consumes, by compare-and-swap, before the protected
side effect.** The PDP never mutates. Staff never calls consume.
**There is no unconsume and no reserve/release.** An approval authorizes one
attempt, not one success. A consume followed by a failed action spends the
object; retry is a new approval.
**Same `request_digest` against an already-consumed object is idempotent
success** (a retry of one logical request). A different digest is conflict,
and the PEP MUST NOT act.
The three failure modes have owners:
1. *Allow never consumed* — PEP duty to consume; PDP lifetime bounds the
window; a stale ALLOW without a matching `use` is a finding, not a consume.
2. *Double consumption*`approval-engine` CAS; the PEP that receives
conflict does not act.
3. *Consumed then the action fails* — accepted as the cost of closing (2).
Protocol: `docs/contracts/approval-consumption.md`. Statute v0.8 will replace
the §9.7.3 implication that action precedes the consume call; until then this
record governs the blocked implementers.
## What this authorizes
- `approval-engine` exposing `POST /v1/approvals/{id}/consume` against this
contract, including the digest column the internal CAS does not yet store.
- `FLEX-WP-0017-T05` requiring the PEP (secrets-engine, for that task) to
consume before the OpenBao call.
- Canon `T-06` consume-side replay (a second, different digest against a
consumed object) becoming in-scope once the endpoint exists.
## What this does not authorize
- The PDP consuming, or inferring consumption from a decision record.
- Unconsume, reserve, or any path that returns a consumed object to `approved`.
- A consume that is a decision ("may this actor do X").
- Any change to another repository's workplans. Work structure stays with the
repository doing the work.
## Reversal
Revert this record and the consumption contract. The falsifier is operational:
if spending an approval on a failed attempt makes the estate unable to
complete the actions this object exists for, a reserve/commit protocol can be
raised then. Unconsume is not the alternative — it reopens replay.