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:
parent
6a5d2170a4
commit
23cbefcb7b
1 changed files with 132 additions and 2 deletions
|
|
@ -167,7 +167,7 @@ the estate declining to adopt the authority vocabulary.
|
||||||
id: GH-DEC-2026-002
|
id: GH-DEC-2026-002
|
||||||
kind: decision
|
kind: decision
|
||||||
title: Revocation fails closed only when approval-engine's own store is down
|
title: Revocation fails closed only when approval-engine's own store is down
|
||||||
status: open
|
status: resolved
|
||||||
owner: Bernd Worsch
|
owner: Bernd Worsch
|
||||||
repo: gate-house
|
repo: gate-house
|
||||||
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
|
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
|
||||||
|
|
@ -182,9 +182,75 @@ affects:
|
||||||
- audit-core
|
- audit-core
|
||||||
- flex-auth
|
- flex-auth
|
||||||
created: '2026-08-29T12:50:04.747439Z'
|
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
|
## GH-DEC-2026-003 — The PEP consumes an approval before the protected side effect
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
@ -209,3 +275,67 @@ affects:
|
||||||
created: '2026-08-29T12:50:06.944092Z'
|
created: '2026-08-29T12:50:06.944092Z'
|
||||||
updated: '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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue