0008: Retraction Authority (gate `fact.retracted` before shipping `dent8 retract`)
Date: 2026-06-27
Status
Section titled “Status”Accepted (option 1) — implemented. The authority gate is built in
dent8-core::apply_event’s Retracted arm and covered by the exhaustive lattice test
authority_monotone_retraction_and_non_resurrection plus a #[cfg(kani)] proof; dent8 retract exposes it. Options 2 (asserter-identity) and 3 (reason-scoping) remain future
refinements on top of this baseline.
Context
Section titled “Context”An adversarial review of the supersede verb surfaced a real, code-confirmed asymmetry:
a naively-built dent8 retract would let a low-authority actor terminally retract a
high-authority fact. Three gates that protect supersession do not apply to retraction:
apply_event’sRetractedarm (state.rs) setslifecycle = Retractedwith no authority comparison, unlike theSupersededarm (which rejectsevent.authority < state.authorityasInsufficientAuthority).arbitrate’s anti-laundering check (firewall.rs) fires only forSupersededevents; aRetractedevent is never authority-checked against the incumbent.- The registry’s authority floor and uniqueness (
registry.rs) gate onlyAsserted. ARetractedcandidate passes untouched.
Retracted is terminal (FactLifecycle::is_terminal), so a successful low-authority
retraction permanently kills a trusted fact — the same MINJA threat (T1) that authority-
weighted supersession exists to stop. Today this is latent: no CLI verb and nothing
outside #[cfg(test)] constructs a Retracted event, so it is a pre-emptive design gate,
not a live vulnerability.
Retraction is not the same as dissent. A low-authority Contradicted is deliberately
admitted (it moves a fact to Contested and preserves it, and a contradiction against a
Canonical fact trips the hard-alarm — see ADR 0007).
Retraction removes the belief. So it must not inherit the contradiction/dissent
exemption.
Decision
Section titled “Decision”Option 1 (symmetric with supersession), chosen. A Retracted event whose authority
strictly under-ranks the incumbent is rejected with the existing
TransitionError::InsufficientAuthority, enforced in apply_event’s Retracted arm —
the same gate the Superseded arm applies. Equal-or-higher authority is admitted (a
source can retract its own fact with its own authority).
One correction to the framing above: unlike supersession, retraction carries no backing
fact, so there is no laundering indirection (an over-stated event authority backed by
a weaker fact). The supersession-only arbitrate anti-laundering check therefore has no
retraction analogue, and the apply_event stated-authority gate is the complete check.
Retraction is not subjected to the registry’s Asserted-only floor (the
incumbent-relative gate is stronger), and from_trusted_events reload still treats the
log as already-arbitrated (the gate lives at write time, like the others).
Candidates not chosen, kept as future refinements:
- Asserter-only — only the source that made a fact (or a strictly higher one) may retract it. Needs the retractor’s identity checked against the incumbent’s provenance, not just authority levels.
- Reason-scoped —
RetractionReason::PoisoningDetected/SourceInvalidatedcould be privileged to a high authority regardless of the incumbent (a trust-and-safety action).
Consequences
Section titled “Consequences”Positive:
- Closes the T1 gap for retraction before it can be reached, keeping the firewall’s “low-privilege cannot destroy a trusted fact” guarantee total across all belief- removing events, not just supersession.
- Forces the asymmetry to be a conscious choice (dissent is cheap; removal is not).
Negative:
- Adds a
Retractedbranch toapply_eventplus property tests mirroring the supersession ones — retraction is no longer a trivial terminal transition. - Rule 2/3 require provenance/identity plumbing the current authority-level comparison does not need.
Residual risk:
- The gate stops a low-authority retraction. It does not stop a writer with
store/log access from injecting an equal-or-higher-authority
Retractedevent to drop a fact — the same class as the threat model’s “authority is asserted, not proven” and “operator with DB access” limits. Lineage validation does not flag this (a retracted successor is a legitimateSupersededByInvalidatedhistory); it is caught only by the hash chain plus a future external anchor (a signed/published head), not by replay alone.
Follow-Up
Section titled “Follow-Up”- [DONE] Authority gate in
apply_event’sRetractedarm (TransitionError::InsufficientAuthority), the exhaustive lattice testauthority_monotone_retraction_and_non_resurrection, and a#[cfg(kani)]proofretraction_is_authority_monotone_and_non_resurrecting. - [DONE]
dent8 retract <subject> <predicate> [--authority <level>] [--source <source>]removes every believed fact for the subject+predicate, each authority-gated; a low-authority retract of a high fact is rejected. - Future: option 2 (asserter-identity) / option 3 (reason-scoped trust-and-safety retraction) if needed; and the retraction-cascade to dependents (the second half of T8).
- Grounded in ADR 0007 (authority-as-entrenchment) and ADR 0005 (belief-base revision).