dent8 Implementation Status
Single source of truth for what is built. If any other doc’s “what works” fact contradicts this file, this file wins. Three tiers, because the distinction that matters most is “a tested function exists” vs “a user can run it”:
- Runnable — a user can invoke it (a CLI command, a server).
- Library — implemented and tested in a crate, but exposed through no runnable surface (no persistence, no CLI, no MCP). It is correct code that nothing calls.
- Design-only — specified in docs, not implemented.
Runnable today (the entire user-facing surface)
Section titled “Runnable today (the entire user-facing surface)”dent8 eval— runs the adversarial corpus against the real firewall and a recency-only baseline, then an integrity-axis comparison against modeled Mem0 mutate-in-place and Zep/Graphiti recency semantics (not live peer APIs). It is the built-in proof that dent8 blocks the attack classes those baselines accept; useexamples/firewall/demo.shfor a human-readable walkthrough driven by real CLI writes,explain, andverify. For the adoption path (init → first fact under 2 minutes once the binary is onPATH), useexamples/on-ramp/demo.sh.dent8 init [--dir .dent8] [--store file|sqlite|postgres] [--store-url URL] [--identity] [--no-identity] [--agent codex|claude-code|cursor|grok-build|gemini|cascade|hecate] [--no-native-memory-guard] [--witness] [--witness-log PATH] [--witness-pubkey PATH] [--install-mcp] [--mcp-config PATH] [--mcp-command COMMAND|--mcp-local-bin] [--mcp-use-daemon] [--mcp-daemon-socket PATH] [--mcp-dry-run|--mcp-check]— bootstraps an adoptable local setup: creates the dent8 config directory, writes an authority registry granting a chosen source (defaultsource:local/ High), creates a shell-loadable env file (DENT8_AUTHORITY,DENT8_REQUIRE_AUTHORITY=1, and eitherDENT8_LOGorDENT8_STORE_URL), and initializes the file dev log for the default file store (--agentprofiles use the matching per-agent log name shown inexamples/). By default it also wires the enforcedPreToolUsenative-memory guard into the agent’s project hook config (.claude/settings.jsonwhen no--agent, otherwise the selected agent’s hook file), so raw agent edits toCLAUDE.md/AGENTS.md/… are blocked out of the box instead of shipping only a sample the user must copy in. The merge is idempotent (an existing guard entry is replaced, unrelated hooks preserved) and the wired command degrades gracefully — it is fronted bycommand -v dent8 >/dev/null 2>&1 || exit 0, so a clone that has the hook wired but nodent8onPATHallows the write (exit 0) rather than bricking every edit. Opt out with--no-native-memory-guard; the runtime soft-off (DENT8_HOOK_ENFORCE=0) and sanctioned bypass (DENT8_ALLOW_NATIVE_MEMORY_WRITE=1) still apply per write.--agent hecateand non-.dent8dirs skip the guard with a note (no stable project hook file). By default it now provisions a signed source identity (trust root + operator issuer key + source key + grant) and writes the identity env vars (DENT8_TRUST,DENT8_ACTIVE_GRANTS,DENT8_GRANT,DENT8_IDENTITY_KEY,DENT8_REQUIRE_IDENTITY=1) into the shared.dent8/env, so a freshly-init’d project can make signed above-agent (medium/high/canonical) writes with just. .dent8/env— the honest side of the signing-required-above-agent default. A re-init reuses the existing identity idempotently (it never clobbers key material, so--forceis safe). Opt out with--no-identity: no signing identity is provisioned and above-agent writes stay rejected until you configure signing (the secure default, not a bug).--identityis retained (now the default); for--agentbundles the identity stays per-agent (.dent8/identity-<source>.env, sourced separately) rather than embedded in the shared env, and--agentselects the source id for a known agent. With--witness, it adds witness verification paths (DENT8_WITNESS_LOGandDENT8_WITNESS_PUBKEY) to the env file and creates the local signed-head log, but deliberately does not putDENT8_WITNESS_KEYin the writer env. With--install-mcp, it also patches the selected agent’s MCP config and prints the resulting file; the MCP install step can be rendered (--mcp-dry-run) or checked without writing (--mcp-check).--mcp-use-daemonwritesdent8 mcp proxyinstead ofdent8 mcp serve;--mcp-daemon-socket PATHwritesdent8 mcp proxy --socket PATH. If the MCP patch fails after init (for example invalid TOML/JSON), init reports the partial success and prints the follow-updent8 mcp install ...command instead of hiding the created.dent8bundle. Supports--output jsonwith structured paths, store/authority/identity/witness fields, and nested MCP install state. It refuses to rewrite the env file unless--forceis passed and refuses to overwrite existing identity key/grant material.dent8 agent add --agent <profile> [--dir .dent8] [--authority <level>] [--issuer ISSUER] [--issuer-key PATH] [--mcp-config PATH] [--mcp-command COMMAND|--mcp-local-bin] [--mcp-use-daemon] [--mcp-daemon-socket PATH]— adds a known agent profile to an existing shared.dent8/bundle. It requiresDENT8_STORE_URLin the generated env, so a second agent cannot accidentally share another agent’s file-dev log. It creates or reuses/repairs the selected source’s signed identity material, adds/replaces that source’s authority ceiling in the bundle authority registry, patches the selected agent’s MCP config, prints the resulting file, and prints the follow-updent8 doctor --agent <profile> --dir <dir> --write-checkcommand. If a bundle has multiple trusted issuers, pass--issuer; Hecate still needs--mcp-configbecause there is no stable project-local config path to infer.--mcp-use-daemon/--mcp-daemon-socketpatch the config to rundent8 mcp proxyagainst a local daemon instead of launching a direct stdio server. Supports--output jsonwith structured identity, authority, store, MCP install, and follow-up doctor fields.dent8 doctor [--agent <profile>|--all-agents] [--dir .dent8] [--mcp-config PATH] [--mcp-command COMMAND|--mcp-local-bin] [--repair] [--write-check]— diagnoses the current setup: binary path, selected store, authority registry/grant, signed identity configuration when present, witness verification status when configured,verify, and MCP availability. Configured witness status includes the witness log/public key paths, signed-head count, latest witnessed event count, current event count, unwitnessed-tail warnings, andFAILon tamper/rollback/corrupt heads. With--agent, it loads the generated.dent8/envplus the selected source’s identity env (.dent8/identity-<source>.env) without requiring the shell to source it, parses the selected agent’s installed MCP config, checks that it is up to date, then smokes the exact installedcommand+args+cwd+envwithinitialize+tools/list+runtime_statusand a bounded timeout. The runtime-status smoke fails if the live MCP server starts against a different store backend/path or source identity than the installed agent bundle declares. On success, doctor prints the live MCP server version and binary path, and warns when that version differs from the doctor binary so stale global installs or repo-local wrappers are visible in the text report, not only in--output json. If the installed config usesdent8 mcp proxy, doctor first probes the target daemon socket with the config’s ownDENT8_GRANT/DENT8_IDENTITY_KEY, reports the authenticated source on success, and reports a concretedent8 daemon serve --socket ...hint when the daemon is unreachable. With--write-check, a failed MCP smoke skips the write probe because the same server would be reused. With--all-agents, it checks every known profile that has both a source-bound identity env and a default project-local MCP config, skips uninstalled profiles, and fails the aggregate command if any installed profile fails its normal--agentdoctor. Hecate has no default project-local config path, so use--agent hecate --mcp-config PATHfor Hecate task configs. If--mcp-commandis omitted, the expected command is read from the installed config; pass it only to assert a specific expected command. By default it is read-only; with--repair, it first repairs the generated identity env from the current signed grant and refreshes the selected agent’s installed MCP config from the generated bundle, then runs the normal checks. It also reports a best-effort bypass guard posture for known hook-capable profiles: OK when an installed native-memory hook config referencesdent8 hook native-memory-guardwithDENT8_HOOK_ENFORCE=1, WARN when the hook is missing/advisory, and WARN/unknown for profiles whose hook schema is host-specific. Sincedent8 initnow installs the enforced guard by default, this reads OK out of the box for the configured agent (doctor reads the actual hook file, so it reflects reality — including after--no-native-memory-guard). It also runs the same read-only native scan asdent8 native scan --agent <profile>and reports how many native memory/rules files exist and how many contain dent8 receipt markers. With--write-check, it runs an explicit acceptance probe through the installed MCP server against the configured store: an internal diagnostic fact (diagnostic:<run-id> dent8.write_check=ok) is accepted, a below-ceiling supersession to a tampered value from the same configured source is rejected,explainstill returnsok, andverifypasses. The probe is scope-aware: for a subject-scoped source (by its authority-registry grant chain or its signed identity grant) it targets the scoped subject itself under a per-rundent8.write_check.<run-id>predicate — a legitimately-authorized write through the unchanged write gate, never an out-of-scope one — so a correctly-scoped source passes doctor while an unauthorized source still fails. The probe asserts at the source’s own granted ceiling (its authority-registry grant if listed, else the signed identity’s max authority, elsehigh), so a healthy source whose ceiling is belowhighnow passes--write-check; the reject sub-check supersedes one level below that ceiling so it stays a genuine rejection (skipped, and noted, when the ceiling is already the minimum level). The log is append-only, so the probe retracts its ownokfact once the checks complete: the fact falls to a terminal, no-longer-believed state instead of lingering as live current state, so repeated runs leave no accumulating believed residue. Diagnostic streams (including scoped write-check probes) are hidden from normal MCP fact/resource browsing by default. When the optional write-check is not requested, doctor reports it asSKIPrather thanWARN;doctor --output jsonexposes stableok/warn/fail/skipsections, anddoctor --agent --output jsonalso includes a structuredmcp_runtimeobject with the MCP smoke result plus the liveruntime_statuspayload when the server answers.doctor --all-agents --output jsonincludes anagents[]array with each profile’sok/failed/skippedstatus and nested report.dent8 assert <subject> <predicate> <value> [--authority <level>] [--source <source>] [--valid-from TIME] [--valid-to TIME]— asserts a fact through the firewall + registry, persisted to a JSON-lines event log and composing across separate invocations. A below-floor or non-unique write is rejected and never reaches the log.--valid-from/--valid-tostamp the fact’s asserted validity interval (ADR 0016): reads treat an elapsedvalid_toexactly like an elapsed TTL, and an inverted interval is rejected. Subjects are written as<kind>:<key>(for example,person:aliceorrepo:dent8); authority/source are provenance metadata, not part of the fact. They can be passed explicitly, or omitted whenDENT8_GRANTis configured: the CLI and stdio MCP then default to the grant’s source and maximum authority before the normal authority-ceiling and signed-identity checks run. Authenticated daemon connections use the same defaults from their proven connection identity.dent8 supersede <subject> <predicate> <new-value> [--authority <level>] [--source <source>] [--valid-from TIME] [--valid-to TIME]— revises the believed fact via the sanctioned supersession path: it asserts a replacement (stamped with the validity interval when given) and marks every believed incumbent superseded by it, persisted as one write. The base firewall’s anti-laundering rejects a revision that cannot out-rank each incumbent; the end state is unique because all incumbents become terminal. A rejection lost on strength is recorded on the incumbent’s stream as a survived challenge (fact.challenge_rejected, ADR 0015;DENT8_RECORD_CHALLENGES=0opts out), and the opt-inDENT8_ENTRENCHMENT_GATE=1additionally rejects an equal-authority replacement with strictly weaker authority-weighted earned entrenchment — corroboration plus survived challenges (ADR 0017), so a fact that survived a challenge resists the next fresh equal-authority replacement — than its incumbent. Reload re-validates integrity: a torn write or external edit that leaves two fresh believed facts or a broken supersession lineage (dangling/cyclic) is rejected, not silently masked.dent8 retract <subject> <predicate> [--authority <level>] [--source <source>]— terminally removes every believed fact for the subject+predicate. Unlike a contradiction (dissent), it is authority-gated (ADR 0008): a retraction that under-ranks its incumbent is rejected — and recorded on the incumbent’s stream as a survived challenge (ADR 0015) — so a low-authority actor cannot delete a trusted fact, and the attempt itself becomes attributed evidence.dent8 contradict <subject> <predicate> <opposing-value> [--authority <level>] [--source <source>]— flags a conflict: asserts an opposing fact and moves the incumbent toContested, keeping both (paraconsistency, ADR 0009). This is dissent — not authority-gated, so a low-authority source can flag a wrong fact without overriding it; the exception is aCanonicalincumbent, which hard-alarms (and records the rejected challenge on the incumbent, ADR 0015). Takes--valid-from/--valid-tofor the opposing fact likeassert.dent8 reinforce <subject> <predicate> [--authority <level>] [--source <source>]— corroborates the believed fact: records an additional source/authority backing the same value, raising earned entrenchment without restating the value (no value-mismatch).dent8 expire <subject> <predicate> [--authority <level>] [--source <source>]— moves the believed fact(s) to the terminalExpiredlifecycle. This is an explicit policy close, not TTL staleness, and is authority-gated like retraction (ADR 0011): a lower-authority source cannot expire a higher-authority incumbent.dent8 derive <subject> <predicate> <value> --basis <basis-subject> <basis-predicate> [--authority <level>] [--source <source>] [--valid-from TIME] [--valid-to TIME]— asserts a fact derived from another (named by subject, resolved to its believed fact id), recording aDerivedFromdependency edge (ADR 0010). If the source is later retracted/expired,verifyflags this derivative as tainted — the “poison does not survive in derivatives” differentiator, demonstrated by thepoisoned_source_retractioneval.- The write commands above support
--output jsonfor agent wrappers/scripts. Every dent8 command emits its--output jsonresult — success and error — to stdout as one object, so a machine consumer reads a single stream and branches on the object’sstatus(the nonzero exit code still signals failure);mcp serve,mcp proxy,daemon serve, andhookhave no JSON result. Each payload carries aschema_version. Thestatusstring matches the MCP tool’s for the same operation: an admitted write isaccepted(contradictiscontested), a firewall refusal isrejected, malformed input isinvalid; on the read sideverifyisok/integrity_issues, runtime/snapshot probes may reportdegraded, andexplain/conflictssurfacecontested. dent8 explain <subject> <predicate> [--as-of TIME] [--valid-at TIME]— replays the persisted log and prints the believed (or, if removed, the terminal) fact’s integrity receipt. Freshness-aware (T4): a still-Activefact past its TTL or its assertedvalid_tois headline-flagged[stale — no longer valid], and one whosevalid_fromis still in the future[not yet valid]; the receipt carriesfresh+not_yet_valid+ thevalid_from/expires_atwindow plus the fact’s survived-challenge count (ADR 0015/0016). Time-travel (ADR 0016):--as-offolds only events recorded at or before an instant (the store as it stood then) and--valid-atjudges freshness at an instant instead of now. Composes withassert/supersede/retractacross processes (and the same receipt backs the MCPexplaintool andresources/read). Supports--output jsonfor the current-state receipt.dent8 replay <subject> <predicate> [--as-of TIME] [--valid-at TIME]— prints the full ordered event history (every assertion, supersession, retraction, contradiction, and survived challenge, with authority + source) and the current state — why the fact is what it is.--as-of/--valid-attime-travel likeexplain. Supports--output json.dent8 whatif <subject> <predicate> [--distrust SOURCE]... [--authority-floor LEVEL] [--confidence-floor MILLIS]— policy-counterfactual replay (research/novelty.md rank 2): re-folds the same immutable log under a swapped epistemic trust policy — “what would we believe if this source were distrusted / the authority floor were raised?” — and reports the believed set under the real fold vs the counterfactual, plus a per-fact structural diff (appeared / disappeared / lifecycle / value / supersession changes). Read-only, deterministic, zero model invocations; at least one policy knob is required. Also an MCP tool (whatif, same arguments). Supports--output json.dent8 facts list [--kind KIND] [--key KEY] [--predicate PREDICATE] [--include-diagnostics]— lists distinct fact streams known to dent8 asdent8://{kind}/{key}/{predicate}resources for human browsing, each flagged with its current freshness ([stale]/[not yet valid]/[no longer believed]; T4) so a stale fact is visible in the summary without reading each one. It hides internal doctor/write-check diagnostic streams by default, matching MCPlist_facts/resources/list(which carry the samefreshness); pass--include-diagnosticswhen auditing setup noise. Supports--output json(each fact gains afreshnessfield).dent8 context [--kind KIND] [--key KEY] [--predicate PREDICATE] [--include-stale] [--include-diagnostics] [--record-retrieval [--purpose TEXT]]— emits the currently-believed facts as an agent context pack: markdown ready for CLAUDE.md/AGENTS.md-style inclusion orSessionStart-hook injection, or--output jsonfor machines. Belief-state aware: terminal facts never appear, believed-but-stale / not-yet-valid facts are omitted by default (counted in a trailer;--include-staleshows them annotated), and a contested fact is flagged inline — with JSONstatus: "contested"— rather than silently picked. Each fact carries its authority, asserting source, anddent8://receipt reference, so a generated block stays verifiable withdent8 native reconcile. With--record-retrieval, every fact the pack emits also gains afact.retrievedaudit event on its stream (stamped with--purpose, defaultcontext-pack) — the read half of the read-audit loop. Recorded before the pack is emitted (an emitted pack is never un-audited), all-or-nothing, as the active signed grant’s source when configured, else the agent tier (source:agentatlow), through the normal write boundary; the markdown stays a pure context block and--output jsonreportsrecorded_retrievals. See context-capture.md.dent8 capture [FILE] [--consume [--keep-failed]] [--authority <level>] [--source <source>]— batches structured fact proposals (JSON lines from stdin or a file) through the sameop_*firewall path as the interactive writes:opisassert(default) /supersede/reinforce/contradict/retract/expire/used_in_decision, and a below-ceiling, laundered, or below-floor proposal is rejected exactly as it would be ondent8 assert. Aused_in_decisionproposal takes adecisioninstead of avalueand records afact.used_in_decisionaudit event on the believed fact(s) — how an agent reports which facts informed a decision (the report half of the read-audit loop; audit events never change lifecycle, value, or authority). Authority and source resolve per line (line fields, then flags, thenDENT8_GRANTdefaults, then the agent tier of the default profile —source:agentatlow). Every line is attempted and reported; exit2on any malformed line,1on any firewall rejection (a safety signal for hook logs), else0.--consumetruncates the proposals file after processing, so a session-end hook can flush an agent-written queue idempotently (a missing file is an empty session); with--keep-failed, rejected and malformed lines are written back instead of truncated away, so a failed proposal survives for inspection/retry rather than only in hook logs. Supports--output jsonwith per-line results andkept_failed. See context-capture.md for Claude Code hook wiring.dent8 snapshot [--include-diagnostics]— emits one debugger/control-plane read/audit payload over the same durable store: live runtime status, fact-stream browsing,verify,conflicts, and summary counts. In text mode it is a compact operator status; in--output jsonit is the stable polling shape for local dashboards and daemon/MCP clients. It exits nonzero for integrity issues or degraded runtime inspection, but a merely contested belief base stays a successful read withstatus: "contested".dent8 verify— on-demand integrity check. On Postgres it re-verifies the stored global hash chain (a mutated row →INTEGRITY FAILURE; CI-exercised); on the file dev store it checks structural integrity (uniqueness + lineage + canonicalization) and says plainly that content-edit tamper-detection there isdent8 witness verify’s job. On both it also reports retraction taint — a still-believed fact deriving from a retracted/expired source (TAINTED: X derives from Y). On identity builds it also re-verifies every persisted write attestation (ADR 0013) and, when a grant log is present, resolves each attested event’s entitlement at write time — entitled / unentitled (an integrity failure) / unknown, reported honestly (ADR 0014). It also surfaces unearned-supersession advisories (ADR 0017: a replacement that did not out-entrench what it displaced) — these are advisory, not failures (the base firewall admitted them; enableDENT8_ENTRENCHMENT_GATEto reject at write time), soverifystaysOK. Supports--output json(with a structuredadvisoriesarray); integrity findings still return a nonzero exit code, with the structured report on stdout.dent8 eval— runs the demonstrative corpus and prints the firewall-vs-recency-baseline contrast (5/5 attacks blocked by the firewall, 5/5 compromising a recency-only baseline), then the Mem0/Zep integrity-axis comparison (5/5 attack axes differentiate dent8; positive control admitted by all three models) — the self-demonstrating “why dent8” benchmark. Supports--output json(includes acomparisonobject). The larger, externally-grounded 47-case adversarial corpus (with its honest per-class block rates and out-of-scope analysis) is a library + test surface indent8-evals::adversarial, not this CLI subcommand; see evals.md.dent8 conflicts— lists every contested fact (in dispute) across all subjects, showing both rival facts (value + authority + lifecycle). Supports--output json.dent8 export [out.parquet]— the analytical/export lane (behind--features export). Writes the whole log — backend-aware, so the file or the Postgres log — to a flattened, columnar Parquet table (one row per event; the queryable scalars promoted to columns — including avalue_kinddiscriminator so redacted ≠ absent, and a stableauthorityname — theDerivedFromdependency edges as aderived_fromlist column, and the full event retained inevent_json). DuckDB reads the Parquet directly — no embedded engine in the binary — for offline forensics/audit/replay. Read-only export; the log stays the source of truth. (examples/duckdb/, storage.md).dent8 mcp serve— a stdio JSON-RPC 2.0 MCP server exposing read/audit tools (runtime_status/snapshot/list_facts/verify/conflicts/native_scan/native_reconcile) and the full belief surface as tools to agent clients —assert/supersede/retract/contradict/reinforce/expire/derive/explain/replay(initialize/tools/list/tools/call).runtime_statusreports the live server binary, cwd, selected store URL/path, event count, authority registry, signed identity, and witness configuration, whilesnapshotcombines that runtime view with facts,verify, andconflictsfor polling/debugger clients. The initialize response includes server instructions that tell MCP-aware agents to callsnapshot(orruntime_status/list_factsfor narrower checks), inspect dent8 before relying on durable project facts, and treat rejected writes as safety signals. Tool definitions advertiseoutputSchemafor every structured result. Tool calls return human-readablecontentplus MCP 2025-11-25structuredContentwith stable agent fields:status,accepted_events(one entry per committed event, including event hash), current-state receipt fields (fact_id,event_hash,replay_position,current_value,receipt_kind: "current_state",receipt), andrejection_reasonwhen a firewall write is refused. The server prefers protocol2025-11-25, also negotiates2025-06-18, and includes a serialized JSON mirror as a second text content block for clients that ignorestructuredContent. Malformed calls arestatus: "invalid"rather thanstatus: "rejected", so clients do not confuse usage errors with integrity decisions. Setup examples are checked in for Codex, Claude Code, Gemini CLI, Devin/Cascade, Cursor, Grok Build, and Hecate underexamples/; they are client wiring only, not alternate semantics. Optional hook guard profiles underexamples/agent-hooks/call the built-indent8 hook native-memory-guardto protect provider-native memory/rules files from bypassing dent8, whilenative_scan/native_reconcilelet MCP agents audit those native files and receipt references without writing state. They are examples around the MCP/CLI surfaces, not a separate dent8 runtime. Every tool dispatches to the sameop_*firewall path as the CLI, so a low-authority, laundered, or non-unique write is refused over MCP exactly as on the CLI (surfaced as a tool error, not a protocol error, so the agent sees the reason). It also servesresources/list/resources/read— each fact stream is a readable resource atdent8://{kind}/{key}/{predicate}(read returns the integrity receipt, and a write-capable connection also appends afact.retrievedaudit event with purposemcp:resources/readby default; setDENT8_MCP_RECORD_RETRIEVAL=0to opt out) — and accepts JSON-RPC 2.0 batches (an array of requests → an array of responses, notifications omitted; an empty batch is-32600). With--daemon [--socket <path>]the same surface is served over a per-user Unix-domain socket (default$XDG_RUNTIME_DIR/dent8/dent8.sock,0700dir +0600socket;$TMPDIRfallback where$XDG_RUNTIME_DIRis unset) so many agents share one belief base over one transport; connections are refused unless the peer runs as the same OS user. A connection proves its source identity before it may write (ADR 0018):dent8/hellopresents the grant, the daemon verifies it and issues a single-use, 30-second, connection-scoped nonce, anddent8/provereturns an Ed25519 signature over the domain-separateddent8.session-challenge.v1challenge (nonce + source + grant). Authenticated writes are attested server-side as that source, so they re-verify offline like a CLI write; an unauthenticated connection is read-only and a stray write fails closed. Handshake failures use server-defined codes (-32010…-32015). SettingDENT8_DAEMON_SOCKETroutes the CLI’s own writes through such a daemon (the CLI does the handshake with itsDENT8_GRANT/DENT8_IDENTITY_KEY); reads stay local and the output is identical to a local write — so the CLI and several agents can dogfood one shared belief base on a box.dent8 daemon status [--socket <path>]— a human-facing local daemon health check. It resolves the socket from--socket, thenDENT8_DAEMON_SOCKET, then the default per-user daemon path. It first performs an unauthenticated read-onlyruntime_statusprobe so a user can see whether the daemon is reachable and which store it serves. IfDENT8_GRANTandDENT8_IDENTITY_KEYare set, it also completes the session-challenge handshake and reports the authenticated source; if only one is set, it fails because writes would not authenticate. Supports--output json.dent8 daemon serve [--socket <path>]— foreground alias fordent8 mcp serve --daemon [--socket <path>], intended for humans and service managers. It has no single JSON result.dent8 mcp proxy [--socket <path>]— a stdio MCP bridge to a running local daemon. It completes the samedent8/hello/dent8/provesession-challenge handshake using the caller’sDENT8_GRANTandDENT8_IDENTITY_KEY, then forwards newline-delimited JSON-RPC between stdin/stdout and the authenticated daemon connection. JSON-RPC notifications are forwarded without waiting for a reply, so normal MCP clients do not deadlock onnotifications/initialized. This lets stdio-only clients use one long-lived daemon and one source identity; distinct per-agent provenance should still use separate stdio server subprocesses against the same backend, or separate daemon instances.dent8 mcp install --agent <profile> [--dir .dent8] [--config PATH] [--command COMMAND|--local-bin] [--use-daemon] [--daemon-socket PATH] [--dry-run|--check]— patches the selected agent’s MCP config with the local dent8 server entry, writes the file atomically, and prints the resulting file. By default it writesargs = ["mcp", "serve"];--use-daemonwrites["mcp", "proxy"], and--daemon-socket PATHwrites["mcp", "proxy", "--socket", PATH]so stdio-only clients can use a running local daemon.--dry-runrenders the expected file without writing;--checkdoes not write and exits0only when the existing file already matches. Supports--output jsonwith structured config action, generated argv, rendered contents, and local-bin wrapper metadata. It reads the generated.dent8/envplus the selected source’s identity env instead of asking the user to paste paths by hand. Built-in defaults cover Codex (.codex/config.toml), Claude Code (.mcp.json), Grok Build (.mcp.jsonby default, or a side file / project.grok/config.tomlwhen Claude already owns.mcp.json), Cursor (.cursor/mcp.json), Gemini (.gemini/settings.json), and Cascade (.windsurf/mcp_config.json); Hecate requires--configbecause its MCP servers live in a task/UI payload rather than a stable project config file. Prefer those project paths over user-global agent config for a single shared store. If--diris not literally named.dent8, pass--config(or--mcp-configthroughdent8 init) because the installer cannot infer the project root safely. The default command isdent8, which is intended for one globally installed binary used by many agents. Stdio MCP clients generally launch separate dent8 subprocesses; sharing memory across those agents means sharing the backend (DENT8_STORE_URL, preferably Postgres for operational concurrency) and registries while keeping per-agent identity env values distinct.--local-binis the repo-local no-Cargo-startup path: it writes/validates.dent8/bin/dent8, a wrapper around a prebuilt.dent8/target-sqlite/debug/dent8created withCARGO_TARGET_DIR=.dent8/target-sqlite cargo build -p dent8-cli --features sqlite.doctor --agent ... --mcp-local-binverifies that wrapper, checks the target can load the configured store, runs witness writer checks when witness env is present, and warns when the target is older than the workspace Rust sources. A single long-lived remote HTTP MCP server is design-only.dent8 hook native-memory-guard— a built-in provider hook helper. It reads hook JSON from stdin, recognizes common native memory/rules paths (AGENTS.md,CLAUDE.md,MEMORY.md,GEMINI.md,.cursor/rules,.devin/rules,.windsurf/rules,.windsurfrules), runsdent8 verifyon session/post-write audit modes, and exits2whenDENT8_HOOK_ENFORCE=1blocks a direct native-memory write that would bypass the fact-event firewall. It is a bypass guard around provider files, not an alternate dent8 store.dent8 initinstalls and enforces this guard by default (was opt-in — the guard used to ship only as a sample hook config the user copied in); opt out withdent8 init --no-native-memory-guard. It only covers agents whosePreToolUsehook init wires; a shell-capable agent that routes around its own hook system is still out of scope.dent8 native scan --agent <profile> [--dir .dent8] [--root PATH]— a read-only audit of provider-native memory/rules files. It scans known native surfaces (AGENTS.md,CLAUDE.md,CLAUDE.local.md,MEMORY.md,GEMINI.md,.cursor/rules,.devin/rules,.windsurf/rules,.windsurfrules), reports size, mtime, SHA-256, and whether a file contains dent8 receipt markers (dent8://,fact_id+event_hash, etc.), and includes the selected agent’s native-memory guard posture. This is audit only: it does not import native text as facts and does not write provider-native files. Supports--output json.dent8 native reconcile --agent <profile> [--dir .dent8] [--root PATH]— a read-only receipt check for provider-native files. It scans the same native surfaces, extracts explicitdent8://<kind>/<key>/<predicate>references, and resolves each through the normal dent8 read path (explainsemantics, including--as-ofand--valid-at). A reference is OK only when it resolves to a fresh, non-terminal, non-contested current receipt; stale, not-yet-valid, contested, no-longer-believed, missing, or malformed references are audit failures. Native files with no receipt references are reported but do not fail by default. This still does not infer facts from prose and does not write provider-native files. Supports--output json.dent8 authority list | add <source> <max> [issuer] [scope] | remove <source> [--force] | defaults— the authority layer (authz), enforced at the CLI/MCPop_*write layer (before the firewall).defaultsseeds the registry with the out-of-the-box trust profile for a shared repository —source:human→high,source:ci→medium,source:agent→low(human > CI > agent) — merge-only (an existing grant for those sources is kept, never downgraded), so arbitration works without inventing a trust taxonomy first;canonicalstays reserved for explicitadd. A source→authority ceiling registry: every write checks the requested/defaultedauthorityagainst itssource’s registered ceiling and rejects (does not silently cap) a write above it — so a low-trust source cannot mintcanonical, and the rejection names the source, ceiling, and request for debuggability. Opt-in by default: enforcement activates once a registry exists (DENT8_AUTHORITY, default./dent8-authority.json); without one the CLI is permissive (dev mode). SetDENT8_REQUIRE_AUTHORITY=1to make a missing registry fail closed. With a registry it is deny-by-default — an unlisted source’s ceiling isUnknown, below the lowest requestable level (Low), so it is blocked from writing until granted. The registry is host-local config, independent of the event backend (a Postgres deployment still readsDENT8_AUTHORITYfrom the local filesystem; sync it per instance). A grant’sissuerandscopeare enforced at the same write gate:scopeis a subject scope —"*"(or absent) covers every subject, any other value covers exactly the literal<kind>:<key>subject it names (a malformed scope covers nothing, fail closed; scope does not restrict predicates) — and a write about a subject outside the grant’s scope is rejected. Whenissuernames another registered source, the write must also satisfy that issuer’s own grant (ceiling and scope), transitively — an issuer cannot delegate authority it does not hold (no self-escalation), and a self-issued grant or an issuer cycle authorizes nothing (it never grounds out). An issuer that is not a registered source is an operator-level root recorded for audit; the registry has nothing to rank it against, so the chain grounds out there.authority addrefuses a self-escalating grant up front (above its registered issuer’s ceiling, broader than its scope, self-issued, or with a malformed scope), and the write gate re-checks the chain on every write so a hand-edited registry cannot smuggle an escalation past it. The add-time cycle refusal is order-independent: a grant that would complete an issuer cycle through existing grants is refused whichever side was added first.authority removeis fail-closed too: removing a grant that other grants chain their authority through is refused — the deleted issuer would become an unregistered name, which the write gate treats as an operator-level root, so revoking an issuer would silently loosen its delegates — and--forcecascades the revocation down the delegation chain, so orphaned delegates authorize nothing (deny-by-default) until an operator re-parents them withauthority add. Remaining caveat: the ceiling is anop_*-layer check, so a process calling the Postgres adapter directly (bypassing the CLI/MCP) is outside this trust boundary. The ceiling caps the authority a source may assert; use signed source identity below to prove who is holding that source’s key at the CLI/MCP boundary. Supports--output jsonforlist/add/remove.- The content-check hook (
DENT8_CONTENT_CHECK) — the pluggable content layer (content-check.md), enforced at the same CLI/MCPop_*write layer as the authority gate: after authority enforcement and before arbitration/attestation/ persistence, every value-carrying candidate fact from every write entry point (the CLI write commands,dent8 capture, the MCP write tools, daemon connections) is piped as JSON to the configured external scanner command, which answersallow,reject(the write is refused, nothing persists), ortaint(admitted but marked with acontent-check:evidence flag — detect-only like retraction taint;dent8 verifyreports still-believed flagged facts asCONTENT-FLAGGED). Scanner failures (crash/timeout/garbage) are fail-closed by default — a configured scanner going dark must not silently readmit unchecked content;DENT8_CONTENT_CHECK_FAIL_OPEN=1opts into admit-but-flag. Unconfigured = exact pass-through. This repositions dent8 as an authority layer + a composable content hook: dent8 deliberately ships no content classifier — the eval corpus’s content classes (A/F/G/H) are owned by whatever scanner (LLM Guard, Rebuff, Lakera/Azure Prompt Shields bridges) a deployment attaches; the demonstrative regex scanner inexamples/scanners/exists only to prove the seam and drive the eval hook mode. Sameop_*-layer caveat as the authority ceiling: a process calling a storage adapter directly bypasses it (threat-model T9). dent8 identity bootstrap | status | repair-env | rotate-source | revoke | backfill-grant-log | issuer-keygen | agent-keygen | trust-add | trust-list | grant-issue | grant-verify— the signed source identity layer (authn), included in the default CLI build.init --identity/init --agent <profile>are the happy path;bootstrapremains the manual creation path: it creates or reuses an operator issuer key outside the project/agent bundle, then creates a source key, trust registry, active-grant registry, grant, and shell-loadable.dent8/identity-<source>.envfor one source.statuschecks the bundle/trust/active-grant/grant/source key/issuer key and reports expiry. All identity subcommands support--output jsonwith structured paths, artifact metadata, and follow-up commands where applicable.repair-envrewrites generated.dent8/identity-<source>.envand, when missing, restores the active-grant entry from the current signed grant after verifying trust, grant, and source key consistency; it refuses to overwrite a different active grant.rotate-sourcereplaces the active source key and grant at the same stable paths, updates.dent8/active-grants.json, and removes the old private source-key backup after a successful rotation. The grant lifecycle also maintains an append-only, issuer-signed, hash-chained grant log (grant-log.jsonl/DENT8_GRANT_LOG, ADR 0014):issued/revokedrecords per action (a rotation lands both as one write), so rotation stops destroying evidence.revoke --source <s>ends trust in a source without a replacement — the compromise response; the write path then fails closed for that source.backfill-grant-logseeds records for grants that predate the log (stamped now, never backdated — pre-history entitlement stays honestly unknown).status/doctorgain a grant-log consistency line, andverifyuses the history to decide each attested event’s entitlement at write time. The old grant/env/public-key backups remain for audit, but the old grant+key pair is rejected at the write boundary once a bundle has an active-grant registry. The lower level commands remain available for custom paths, expiration, and exact subject scopes. This is the non-bearer-token form: an operator-held issuer key signs a grant that bindssource-> source public key + max authority + optional subject scope/expiration, and each write verifies the grant plus source-key possession before the candidate event reaches the firewall, and every accepted write persists a signed write attestation (provenance.attestation, ADR 0013) thatverifyre-checks offline — the file dev store thereby detects content edits to attested events. Above-agent authority is signing-required by default (BREAKING). A write whose effective authority is above the agent tier — i.e. strictly greater thanlow(medium,high, orcanonical) — MUST be backed by a valid signed identity attestation that proves key possession for the claimed source and authorizes that level. When signed identity is not configured (or the grant is missing/invalid/insufficient), such a write is now rejected (unsigned write claims authority '<level>' above the agent tier …), not trusted — regardless of the opt-inDENT8_REQUIRE_IDENTITY/registry. This closes the old bypass where--authority high --source source:humanwas an unauthenticated label. Writes at or below the agent tier (low/unknown) stay permissive with no signing required, so ordinary local/agent use is unchanged. The gate lives at the shared write boundary (enforce_write/enforce_write_authority), so every write path — CLIop_*,dent8 capture/import, the MCP tools, and the daemon — is covered. To keep the honest above-agent path working out of the box,dent8 initnow provisions a default signing identity by default (trust root + issuer key + source key + grant, with the identity env vars written into.dent8/env); opt out with--no-identity(above-agent writes are then rejected until you configure signing). The configured enforcement below is unchanged: ifDENT8_TRUSTexists orDENT8_REQUIRE_IDENTITY=1, every write must haveDENT8_GRANTandDENT8_IDENTITY_KEY; whenDENT8_ACTIVE_GRANTSor the sibling active-grant registry exists, the presented grant must also be the current grant for that source. Migration: rundent8 init(or configureDENT8_TRUST/DENT8_GRANT/DENT8_IDENTITY_KEY) — unsigned high-authority writes that “worked” before are now rejected; lower them to--authority lowif they do not need above-agent trust. Signed identities aresource:*-scoped, so an above-agent write must come from asource:*identity. Limits: source keys are local files (0600required on Unix), so this distinguishes honestly configured agents on one machine but is not a sandbox against malware or another process running as the same OS user; direct DB/adapter writes still bypass the CLI/MCP boundary. See ADR 0012.dent8 witness keygen | sign | verify | verify-published | head | publish | serve | doctor— the witness (a stock command), built on the Ed25519 signed tree head.keygenwrites a keypair (private key0600, with the warning to keep it off the log-writer’s machine);signemits a signed tree head over the current log and appends it to a witness log (DENT8_WITNESS_LOG);verifyre-checks every witnessed head against the current log’s matching prefix and that the counts never decrease — catching a history rewrite (a re-hashed-forward edit an internalverify_chaincannot, threat-model T6) asTAMPERand a truncation/reorder asROLLBACK.serve [interval] [max-heads]is the cadence signer — it signs the head whenever the log grows, the loop a separate operator runs;headprints the latest signed head as JSON; andpublish <heads.jsonl>idempotently appends that latest local head to an external JSONL sequence, refusing to publish behind a sequence that is already ahead.verify-published <heads.jsonl>verifies externally saved JSONL heads against the current log and public key without readingDENT8_WITNESS_LOG, so a local witness-log rollback cannot erase a later head retained by a monitor. It exits successfully but warns if the latest published count trails the current log. With signed identity in use, the witness also covers the grant log (ADR 0014):sign/serveappend signed grant-log heads (DENT8_WITNESS_GRANTS_LOG),verifydetects a truncated revocation asROLLBACK, andpublish/verify-publishedtake--grants <published-grants.jsonl>to retain and re-check those heads outside the writer’s control — a writer who scrubs a revocation and the local grants-witness file is still caught by the published copy (publishwithout--grantssays so when a grants-witness log exists, instead of silently half-covering).dent8 init --witnessconfigures verifier-side paths,dent8 doctorreports witness coverage, anddoctor <writer|signer|both>validates the operator split (writer/verifier env must have the log + public key and must not have the private key; signer env must have the private key and a matching public key). What is built is the mechanism (cadence signing + publishable heads + idempotent publication to an external JSONL file + verification of externally saved heads + setup/doctor visibility + role readiness checks + a checkedexamples/witness/operator-split demo). All subcommands support--output jsonfor CI/monitoring (keygen,sign,head,publish,verify,verify-published,doctor);servestreams NDJSON in JSON mode — signed heads on stdout (event: "head_signed",lane: "events" | "grants"), lifecycle on stderr.witness doctor --output jsongroups checks into stableok/warn/failsections. The operated deployment is packaged inexamples/witness-operated/(Docker Compose signer/publisher/monitor split + systemd units, event-head and grant-head publication, with key-rotation and publication-channel guidance); what remains is hosting it as a managed service. See witness.md.dent8 completions <bash|elvish|fish|powershell|zsh>— prints shell completion scripts generated from the sameclapcommand model as the parser. Visible aliasescompletionandautocompleteare accepted. Supports--output jsonwith the generated script string.dent8 schema postgres— prints the Postgres schema. Supports--output jsonwith the SQL string.dent8 --version,dent8 --help, and the global presentation flag--color auto|always|never(colored help/errors plus human-facing verdict words; adapter data stays plain). The global--output text|jsonflag currently supports the write commands,capture,context,explain,replay,facts list,verify,conflicts,eval,init,agent add,authority,identity <subcommand>,doctor,completions,export,witness <subcommand>,schema postgres, andmcp install; unsupported commands fail closed with a targeted usage error rather than falling back to prose.
assert/explain persist across invocations via a local file-backed log
(DENT8_LOG, default ./dent8-log.jsonl), rehydrated through the store’s trusted-reload
path. This is a dev store, not the operational backend: it is non-transactional and
single-user. Concurrent dent8 writers on the same file serialize through the firewall:
each write takes an exclusive OS file lock (advisory flock/LockFileEx, via fs4) on a
sibling <log>.lock, held across the whole load → arbitrate → append critical section, so
two processes appending at once queue rather than both loading the same snapshot and racing
past arbitration. The lock only serializes cooperating dent8 processes on the same
host/filesystem — a process that bypasses dent8 and edits the file directly is still out of
scope (that tamper is detected, not prevented — see the threat model). Reload is also
resilient: a single corrupt/garbage line is skipped and reported on stderr rather than
bricking the whole store, while a duplicated belief (validate_unique_log) or a duplicate
event_id is still surfaced loudly on the next load. The operational store with atomic
append + isolation is still Postgres; the file backend exists so the firewall loop is
usable and to prove a second EventStore backend behind the same contract.
explain exits 0 whenever a fact exists (believed or terminal — a retracted/superseded
fact still has an auditable receipt) and exits 1 only when no fact exists for the
subject+predicate.
Library — implemented and tested, not exposed
Section titled “Library — implemented and tested, not exposed”dent8-core:
FactEventmodel, lifecycle state machine, terminal immutability, replay fold.- Authority-weighted supersession, expiration, and retraction arbitration
(
InsufficientAuthority, ADR 0008, ADR 0011) + canonical contradiction hard-alarm (CanonicalContradiction); exhaustive 5×5-lattice non-resurrection tests (one per supersession/expiration/retraction) +#[cfg(kani)]harnesses (run manually viacargo kani; a green CI job is a tracked follow-up — Kani’s pinned nightly does not yet build this edition-2024 workspace). - Read-time freshness evaluator over the full validity window
[valid_from, expires_at)(FactState::is_fresh_at/is_not_yet_valid_at/is_expired_at, ADR 0016). - Earned-entrenchment (ADR 0017): authority-weighted
earned_entrenchment_at_or_above=corroboration_at_or_above+survived_challenges_at_or_above(both Sybil-resistant halves). - Canonicalization + hash chain (
canonical_bytes,event_hash,hash_chain): serde, SHA-256, injective length-framed leaf,0x00domain separation. Not JCS (sorted-keyserde_jsonform — see storage.md). The “logically-equal → identical bytes” invariant holds for all fields including embedded JSON:FactValue::Jsonis theCanonicalJsonnewtype, canonical by construction and re-canonicalized on deserialize (ADR 0004 item 6, resolved). - External anchor (
anchor_head/verify_anchor/ChainAnchor): an HMAC-SHA256 commitment to(count, head)under a witness key (zero new deps), giving tamper-resistance on top of the chain’s tamper-evidence — it catches a re-hashed-forward rewrite thatverify_chaincannot (threat-model T6). - Asymmetric anchor (
sign_head/verify_signed_head/SignedTreeHead, behind thesigned-anchorfeature): an Ed25519-signed tree head over the same domain-separated(count, head)message. Unlike the symmetric HMAC, the verifier needs only the public key, so a published head is publicly verifiable — the witness keeps the private key. Feature-gated so the default build and the CLI keep the HMAC anchor with no signature stack. Tested: public verification, tamper detection, and wrong-key rejection. - Property-based test suites (
proptest): universally-quantified complements to the example tests and Kani proofs.tests/proptest_invariants.rs— canonicalization is idempotent + reload-stable for arbitrary JSON (the property the float bug violated; the suite reproduces it whenfloat_roundtripis removed),canonical_bytes/event_hashround-trip through serde, the hash chain localizes tamper (a changed event flips its hash and every later one, never an earlier one), and the anchor accepts its own log while rejecting any change.tests/proptest_fold.rs— the stateful fold harness: a random coherent event stream folded throughapply_eventis checked step-by-step against an independent reference model (accept/reject, reject reason, resulting lifecycle), plus terminal absorption / non-resurrection, value immutability,updated_attracking, replay determinism, and fact isolation. The cross-check is verified to catch a deliberately wrong model gate.tests/proptest_robustness.rs— the robustness complement: the untrusted-input pipeline (parse →event_hash/hash_chain→canonical_bytes→apply_event) never panics on adversarial input, including values that bypass the constructors’ validation via derivedDeserialize(out-of-rangeConfidence, extreme timestamps,u64::MAXTTL, empty/oversized ids, deep JSON); a panic on hand-edited-log / MCP / JSONB input would be a DoS, not a wrong answer. The store firewall has the matching guard (dent8-store/tests/robustness.rs):replay_entityabsorbs self-referential / cyclic / dangling supersessions and extreme freshness/TTL math without crashing. - Golden replay fixtures (
tests/golden_replay.rs, fixtures intests/golden/replay/): named event streams frozen on disk as canonical.events.jsonl(theDENT8_LOGformat) +.expected.json(chain head + replayed-state summary). The test replays the on-disk events and asserts the current code reproduces them, locking the event encoding, the hash chain, and the fold against silent drift (regenerate withUPDATE_GOLDEN=1). - Scenario-family golden corpus (
evals/, harnessdent8-store/tests/evals_corpus.rs): the file-based fixture corpus from evals.md. Unlike the single-fact goldens above, these are often multi-fact streams that include writes the firewall is expected to reject; eachevals/replay/<name>.expected.jsonfreezes the whole-stream firewall outcome — admitted vs rejected writes (with a stable category), per-fact end-state, read-time freshness, and retraction taint — forbeginner_to_senior,ttl_expiry,summary_drift,consistency_required, andlow_authority_injection.
dent8-store:
replay_fact/replay_fact_with_policy+diff_states(policy-counterfactual replay).replay_entity/SubjectProjection(lineage_issues,unearned_supersessions).- The firewall is
EventStore::appenditself (viaarbitrate): every write is arbitrated and there is no un-arbitrated write path. It rejects a low-stated-authority supersession and a laundered one (over-stated event authority backed by a low-authority fact). Reachable via lifecycle CLI writes,dent8 doctor --write-check,dent8 eval, anddent8 mcp serve. InMemoryEventStore(test + file-backed CLI dev backend, not operational) +IntegrityReceipt/explain/explain_subject+ global-chainverify_chain(internally consistent) +anchor/verify_against_anchor(external tamper-resistance).InMemoryEventStore::from_trusted_events— the trusted-reload path (rehydrate an already-admitted log without re-arbitration), recomputing the global chain. Used by the file-backed CLI; the documented counterpart to the single arbitratedappendpath.PredicateRegistry(coding-agent fact policies): per-predicate authority floor, default TTL, uniqueness, and a retention ceiling (a caller-supplied bounded TTL reaching past the effective ceiling — a per-predicate override else a 90-day global default, both overridable — is rejected, not clamped;Ttl::Neveris out of scope), enforced viaenforce_policy/apply_policy_defaults. Shipsrepo.database,repo.test_command,dependency.version,branch.status,user.preference.EventStore/AsyncEventStoretraits — implemented by the in-memory/file path and the async SQLite/Postgres adapters. Postgres and SQLite are DB-backed adapters of the same firewall boundary, not separate architectures.arbitrate_events— the pure, I/O-free firewall decision over loaded event streams, shared by the in-memory backend and the Postgres adapter so they cannot diverge.
dent8-store-postgres (--features adapter):
PostgresEventStore(v0 async sqlx adapter) —connect/migrate/append/load_fact_events/scan_events/verify_chainover thedent8_event_logtable (migration 002). Transactional append serialized by an advisory lock for the global chain; the firewall reusesarbitrate_events; the canonical event is stored as JSONB.- Materialized projection + edge graph (migration 003): each accepted append also folds
the post-append
FactState(via the sharedapply_event) intodent8_claim_projectionand records the fact→fact relationship intodent8_claim_edge, in the same transaction.materialized_projectionreads the believed state without re-folding;edges_fromreads the supersession/contradiction/reinforcement graph;verify_projectionre-folds and assertsprojection == fold(log). Derived caches, not a second source of truth. - Event-id allocator (migration 004):
dent8_id_allocatorreserves CLI/MCPevent:{n}suffixes before signing. IDs are unique but not gap-free; append order isglobal_sequence. - Status: verified against a live Postgres (
postgres:16). TheDATABASE_URL-gated integration tests pass — the firewall over Postgres (incl. laundered-supersession rejection) and the projection/edge materialization +projection == fold(log)+ the scalar columns matching the fold — viaDATABASE_URL=… cargo test -p dent8-store-postgres --features adapter(the tests share one database but self-serialize and retry the initial connection, so no flags are needed).sqlxis feature-gated so the default build and the CLI stay free of it. The live run surfaced and fixed real bugs:migrate()now serializes concurrent schema creation under an advisory lock (CREATE TABLE IF NOT EXISTSis not race-safe on thepg_class/pg_typecatalog), andconnect()bounds its acquire timeout so an unreachable DB fails fast.
dent8-evals:
- Demonstrative corpus (
run_corpus, behinddent8 eval): MINJA injection, authority laundering, canonical contradiction, Sybil corroboration, and poisoned-source retraction run against the real firewall vs a recency-only baseline.dent8 eval(orcargo test -p dent8-evals) asserts the firewall blocks all five while the baseline is compromised by all five (plus a positive control admitting legitimate revision). - Integrity-axis comparison (
comparison::run_comparison, also behinddent8 eval): the same sequences judged against modeled Mem0 mutate-in-place and Zep/Graphiti recency semantics (documented peer resolution, not live API clients). Frozen tally: dent8 holds 6/6 axes; both peers fall on all 5 attack axes; all three admit legitimate supersession. See evals.md §Integrity-axis comparison. - Externally-grounded adversarial corpus (
adversarial::run_adversarial_corpus): 47 cases across 10 attack classes, patterns adapted (not copied) from public prompt-injection / memory-poisoning corpora (AgentDojo, InjecAgent, BIPIA, MINJA, AgentPoison, PoisonedRAG, HackAPrompt, garak, Lakera Gandalf, OWASP LLM/Agentic Top 10, Rehberger PoCs), each provenance-tagged. Verdicts are computed from each attacker’s goal predicate, never hardcoded, and reported honestly: blocked 16/47 by arbitration, detect-only 4/47 (freshness/taint flag but do not remove), out-of-model 27/47 (admitted by design — dent8 is an authority firewall, not a content scanner; a downstream content/registry/TTL layer owns them). The recency-only baseline falls to 46/47. The per-class tally is frozen as a regression guard. See evals.md for the full table, provenance/licensing, and the known-gaps / out-of-scope analysis. - Content-hook eval mode (
content_hook::run_adversarial_corpus_with_hook): re-runs the same 47 cases with a content-check scanner composed into the write boundary, exactly as the CLI/MCP hook applies it (reject drops the candidate before the firewall, taint admits it marked). With the repo’s demonstrative regex scanner attached: +9 blocked by the hook, +5 newly detect-only, 13 still admitted unflagged — reported in evals.md as a separate, clearly-labeled table (the core arbitration numbers are unchanged; the demo scanner’s deliberate misses, rot13/translation, are themselves frozen as a regression guard so the numbers stay honest). Run:cargo test -p dent8-evals --lib content_hook -- --nocapture.
Remaining Gaps
Section titled “Remaining Gaps”- Postgres remaining gaps — not the adapter itself. The v0
PostgresEventStore+ its materialization (migration 003) are DB-verified (the gated integration tests pass against a livepostgres:16, viacompose.ymlor the CIpostgresjob), and the runnable surface uses it: withDENT8_STORE_URLset and a--features postgresbuild,dent8andmcp serveread/write Postgres, with each multi-event operation (supersede/retract/contradict) committed as one transaction (append_many). Both the adapter and the CLI-over-Postgres path are CI-verified against live Postgres (the gatedpostgresjob runs the adapter tests and a liveassert → supersede → explain → verifyend-to-end). The stock binary still defaults to the file dev store but includes SQLite for no-server shared stores. The async side is a backend-agnosticAsyncEventStoretrait (?Send, with atomicappend_many) that bothPostgresEventStoreandSqliteEventStoreimplement; the CLI’sconnect_backenddispatches by URL scheme into aBox<dyn AsyncEventStore>. The embedded SQLite backend (dent8-store-sqlite,sqlite://URL, included in stock CLI builds) is runnable + tested — assert/supersede/explain/verify run end-to-end over it (it runs in-memory, so its tests run in plaincargo test, no server), proving the boundary is genuinely backend-agnostic. What remains design-only: a richer per-column event table +uses_as_evidenceedges (a possible later design) and operational tuning. Cryptographic caller identity is now a runnable feature-gated CLI/MCP boundary layer (dent8 identity, above); production still needs key distribution/rotation and stronger secret storage. - Persistent CLI/MCP remaining gaps — productization, not persistence. The full surface —
assert/supersede/retract/contradict/reinforce/expire/derive/explain/replay/facts list/verify/conflicts/eval— across invocations is Runnable (above) over the file dev store, and over Postgres withDENT8_STORE_URL+ a--features postgresbuild (selected inload_store/append_eventsviaconnect_backend; multi-event ops use the transactionalappend_many, and the Postgres load re-runs the samevalidate_unique_logintegrity gate as the file path) — CI-verified end-to-end against live Postgres (thepostgresjob runs a liveassert → supersede → explain → verifyplus concurrent CLI writers). Concurrency: the adapter is tested multi-writer-safe — a DB-gated test fires 12 genuinely concurrent appends and asserts they serialize (via a transaction-scoped advisory lock) into one gap-free, duplicate-free global chain that verifies, with every projection still== fold(log). The CLI/MCP reserveevent:{n}id ranges from async backends before signing, so concurrent processes do not mint the same id from the same snapshot; the CLI path has regression coverage for both embedded SQLite and live Postgres. Reserved ids are unique, not gap-free: a later rejected write can leave an unused suffix, while append order remainsglobal_sequenceplus the hash chain. Async adapters also recheck the final projection for touched unique predicates inside the append transaction, so two stale concurrent writers cannot silently land duplicate fresh beliefs. Integrity is unconditional — every committed log is a contiguous, corruption-free chain, and a writer that hits residual backend contention gets a clean retry/rejection, never a partial or corrupt write. Load-tested:scripts/load-test.shdrives N parallel CLI writers (a distinct-fact throughput phase plus a deliberate same-fact supersession herd) against SQLite or Postgres; each decide+commit attempt holds a cross-process write lease (SQLite:BEGIN IMMEDIATEon a<db>-leasesidecar; Postgres: a session advisory lock) so sustained same-fact contention queues fairly instead of livelocking — validated on both backends (every write eventually admitted, exactly one believed value,verifygreen; see the write-concurrency section of storage.md). Authz (source→authority ceilings) is built (dent8 authority, above), authn is a runnable boundary layer (dent8 identity, above) with OS-keychain-backed source keys —keychain:<account>accepted wherever a key path is, on macOS (Keychain), Linux (Secret Service viasecret-tool), and Windows (Credential Manager) — and the witness primitive is runnable (dent8 witness, above). The remaining product gap is operating those controls: team key distribution/rotation and the operated witness service. Retrieved/UsedInDecisionclose the read-audit loop on both CLI and MCP. The loop is runnable:dent8 context --record-retrievalemitsfact.retrievedfor every fact it packs; adent8 captureproposal with"op": "used_in_decision"recordsfact.used_in_decision; and MCPresources/readauto-recordsfact.retrievedwith purposemcp:resources/readon every successful read from a write-capable connection (stdio MCP / authenticated daemon). Opt out withDENT8_MCP_RECORD_RETRIEVAL=0; unauthenticated (read-only) daemon connections still return the receipt but skip the audit write. The MCP server also renders these events in replay/explain; the agent-report half (used_in_decision) remains a capture proposal (agents already use that queue for session-end reporting).- The official
rmcpSDK — the v0 server (read/audit tools, full belief surface as tools,resources/list/resources/read,resources/subscribewithnotifications/resources/updatedpushed on both transports (own writes immediately, cross-process writes within a poll tick;dent8 mcp proxypumps frames bidirectionally), and JSON-RPC batches, above) is a hand-rolled JSON-RPC loop, now over three transports: stdio, the local Unix-socket daemon, and HTTP (dent8 mcp serve --http, ADR 0019 — loopback + bearer token, samedispatch). Prompts and streamable-HTTP push are not implemented; the officialrmcpSDK is the eventual upgrade path. - Desktop debugger/control plane — the app is runnable:
dent8 uiserves a local, human-first read-only memory dashboard from the stock binary (ADR 0020 steps 2+3), organised around the belief base — four views: Memory (headline verdict + stat tiles + believed facts as value-first cards with freshness bars and contested pairs inline; click for the integrity receipt + replay drawer), Activity (event feed), What-if (counterfactual re-fold), and Health (a status-page rollup folding doctor OK/WARN/FAIL/SKIP, witness coverage + TAMPER/ROLLBACK, native scan/reconcile, and raw runtime detail behind one banner). Light/dark, deep-linkable tabs, click-to-pause poll. Read-only by construction (every endpoint is a GET over the sameop_*/snapshot path as CLI/MCP; 127.0.0.1-bound; non-localhostHostrefused; values HTML-escaped so agent-supplied text cannot execute). That completes the ADR’s read/audit-first shape. Remaining from the ADR: the Tauri desktop shell (step 4) and write actions over the signed identity path (step 5). It must not become a separate write path or memory provider. - A hosted / operated witness service. Both anchor primitives —
symmetric (
anchor_head) and asymmetric (sign_head, the publicly-verifiable signed tree head) — are built and tested (Library, above), and the signed-tree-head primitive is now runnable end-to-end asdent8 witness keygen | sign | verify | publish | verify-published | serve | doctor(Runnable, above), withinit --witness, coverage checks, writer/signer role readiness checks, idempotent JSONL publication (both lanes — event heads and, with--grants, grant-log heads), and verification of externally saved heads. The operated deployment is packaged (examples/witness-operated/: compose + systemd, with event-head and grant-head publication plus key-rotation and publication-channel guidance); what is still design-only is hosting it — a managed signer/publication service instead of your own second host.
How to keep this honest
Section titled “How to keep this honest”The README “what works” list must map 1:1 to the Runnable section above. Do not describe Library-tier mechanisms as things dent8 “does” for a user — they are things the code can compute, with no user-facing surface. Update this file in the same change that moves an item between tiers.