Domain Model
Core Primitive
Section titled “Core Primitive”The core primitive is FactEvent.
A fact event records something that happened to a fact: assertion, reinforcement, contradiction, supersession, expiration, retraction, retrieval, or use in a decision.
The platform may expose “memory” to agents, but internally memory is always a replayed projection over fact events.
Fact Shape
Section titled “Fact Shape”A fact is shaped as:
subject + predicate + valueExamples:
repo:dent8 + uses_database + postgresrepo:dent8 + cli_binary + dent8user:project_owner + prefers_eval_style + formal fixtures and invariantsEvent Types
Section titled “Event Types”fact.asserted: creates a new fact stream.fact.reinforced: adds compatible evidence without changing value.fact.contradicted: records a conflict from another fact.fact.superseded: points to a replacing fact.fact.expired: terminally closes a fact by explicit policy action; TTL staleness is a separate read-time predicate and does not mutate lifecycle.fact.retracted: removes trust in a fact because the source, policy, or evidence failed.fact.retrieved: audits that a fact was returned as context. Emitted bydent8 context --record-retrievalfor every fact the context pack emits, and by MCPresources/read(purposemcp:resources/read) on a write-capable connection.fact.used_in_decision: audits that a fact influenced an agent decision. Emitted by adent8 captureproposal with"op": "used_in_decision"— the channel agents already use to report back during a session. Audit events are deliberately not authority-gated in the fold (like dissent): a low-authority reader may record that it retrieved or used a high-authority fact; the write-boundary gate (source ceiling, grant scope, signed identity) still applies.fact.challenge_rejected: records that the firewall rejected a challenge against this fact on strength (ADR 0015) — written with the challenger’s provenance and effective authority, so surviving an attack is replayable, attributed entrenchment evidence.
Use past-tense event names because events are immutable facts.
Required Assertion Fields
Section titled “Required Assertion Fields”fact.asserted requires:
event_idfact_idsubjectpredicatevalueconfidenceauthorityttlprovenance(which carriesrecorded_at, the appender-supplied transaction time)- at least one
evidenceentry
event_hash is derived on append from the canonical bytes (it lives on
AppendReceipt and as a stored column), not a field the appender supplies.
observed_at, valid_from, and valid_to are optional FactEvent fields (the
valid-time anchors and the asserted end of validity — ADR 0016).
Provenance
Section titled “Provenance”Provenance should answer:
- Who or what produced this event?
- Which tool, run, or adapter recorded it?
- When was it recorded?
- Which input digest or source reference can reproduce it?
Initial fields:
sourceactortoolrun_idinput_digestrecorded_at
Evidence
Section titled “Evidence”Evidence links facts to observable support.
Initial evidence kinds:
- Direct observation.
- Tool output.
- File span.
- User statement.
- Derived summary.
- External document.
Derived summaries should never erase their source spans. They are facts with evidence, not privileged replacements for evidence.
Authority
Section titled “Authority”Authority answers how much weight a fact should receive for a specific scope.
Initial authority levels:
unknownlowmediumhighcanonical
Authority is not the same as confidence. A low-authority fact can be high-confidence about what a weak source said; a canonical source can still be superseded if it changes.
Formally, authority is an epistemic-entrenchment ordering and confidence is evidential strength — two distinct structures, which is the reason they are separate fields. Authority arbitrates conflict (higher entrenchment wins), and this is now enforced in the fold: apply_event rejects a supersession whose challenger authority is strictly below the incumbent’s, and hard-alarms a contradiction against a Canonical fact. Confidence is deliberately not consulted in arbitration. See belief-revision.md and ADR 0007.
TTL and Freshness
Section titled “TTL and Freshness”TTL answers when a fact should stop being returned as fresh context.
Initial TTL forms:
neverexpires_atduration
Expired facts remain in the event log. Explicit expiration changes read eligibility and projection state; it does not delete history, and it is authority-gated like retraction.
Status: the read-time freshness evaluator is implemented —
FactState::is_expired_at(now)(state.rs) evaluates TTL against the fact’sfreshness_anchor(valid_from, elseobserved_at, elserecorded_at) and is tested. It is deliberately a read-time predicate, kept separate from the event-driven lifecycle: a fact is not auto-mutated toExpiredby TTL;Expiredas a lifecycle state still comes only from an authority-gatedfact.expiredevent (ADR 0011). The CLI/MCP read surface applies freshness by flagging stale receipts.valid_tocloses the interval (ADR 0016): an asserted end of validity is treated by reads exactly like an elapsed TTL (expires_atis the earliest of the two bounds), and a futurevalid_fromreads not yet valid. The enumeration surfaces —facts list, MCPlist_facts, andresources/list— now flag each stream’s freshness too, so the read surface is complete.
Lifecycle State
Section titled “Lifecycle State”The first fact lifecycle is intentionally small:
none -> activeactive -> contestedactive -> supersededactive -> expiredactive -> retractedcontested -> supersededcontested -> expiredcontested -> retractedTerminal lifecycle states:
supersededexpiredretracted
Retrieval and decision-use events are audit events. They do not change lifecycle state.
Contradiction
Section titled “Contradiction”Contradiction records a relationship between facts. It should preserve both sides and a basis.
Initial contradiction bases:
- Same predicate, different value.
- Mutually exclusive predicate.
- Authority challenge.
- Freshness challenge.
A contradiction does not automatically delete either fact. It makes the conflict visible to read policy, replay, and debugger surfaces.
Supersession
Section titled “Supersession”Supersession records that one fact has been replaced by another.
Initial supersession reasons:
- Newer observation.
- Higher authority.
- User correction.
- Schema migration.
Supersession must be explainable: the old fact should point to the replacing fact, and the replacing fact should preserve evidence for why it is now preferred.
Formal grounding
Section titled “Formal grounding”dent8 is a belief base (Hansson), not a logically-closed AGM belief set, and it
deliberately does not satisfy the Recovery postulate: retracting a fact and later
re-asserting it must not resurrect everything that depended on the original, because
the new assertion carries different provenance and evidence. The contested state is
a paraconsistent design — a visible contradiction must not trivialize the store.
See belief-revision.md and
ADR 0005.
Invariants
Section titled “Invariants”- A fact stream starts with exactly one
fact.asserted. fact.assertedmust include value and evidence.fact.reinforcedmust not change value.- Terminal facts cannot be changed by lifecycle events.
- State replay must be deterministic.
- Projection state must be derivable from ordered events.
- Fresh reads must exclude expired facts unless explicitly requested (evaluator
FactState::is_expired_atbuilt and applied on reads:explainflags a stale fact and the receipt carriesfresh/expires_at;valid_tointervals are built (ADR 0016) — see threat-model.md T4). - Contradictions and supersessions must leave auditable edges, symmetric at query time.
- Higher-authority supersession requires an explicit basis: the replacing fact must out-rank or tie the incumbent (enforced in
apply_event). - Cross-stream lineage holds: if a fact is
superseded_byanother, the replacing fact exists and does not orphan the lineage. - Re-assertion after retraction does not restore prior dependents (Recovery deliberately not satisfied).