Skip to content

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; use examples/firewall/demo.sh for a human-readable walkthrough driven by real CLI writes, explain, and verify. For the adoption path (init → first fact under 2 minutes once the binary is on PATH), use examples/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 (default source:local / High), creates a shell-loadable env file (DENT8_AUTHORITY, DENT8_REQUIRE_AUTHORITY=1, and either DENT8_LOG or DENT8_STORE_URL), and initializes the file dev log for the default file store (--agent profiles use the matching per-agent log name shown in examples/). By default it also wires the enforced PreToolUse native-memory guard into the agent’s project hook config (.claude/settings.json when no --agent, otherwise the selected agent’s hook file), so raw agent edits to CLAUDE.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 by command -v dent8 >/dev/null 2>&1 || exit 0, so a clone that has the hook wired but no dent8 on PATH allows 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 hecate and non-.dent8 dirs 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 --force is 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). --identity is retained (now the default); for --agent bundles the identity stays per-agent (.dent8/identity-<source>.env, sourced separately) rather than embedded in the shared env, and --agent selects the source id for a known agent. With --witness, it adds witness verification paths (DENT8_WITNESS_LOG and DENT8_WITNESS_PUBKEY) to the env file and creates the local signed-head log, but deliberately does not put DENT8_WITNESS_KEY in 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-daemon writes dent8 mcp proxy instead of dent8 mcp serve; --mcp-daemon-socket PATH writes dent8 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-up dent8 mcp install ... command instead of hiding the created .dent8 bundle. Supports --output json with structured paths, store/authority/identity/witness fields, and nested MCP install state. It refuses to rewrite the env file unless --force is 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 requires DENT8_STORE_URL in 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-up dent8 doctor --agent <profile> --dir <dir> --write-check command. If a bundle has multiple trusted issuers, pass --issuer; Hecate still needs --mcp-config because there is no stable project-local config path to infer. --mcp-use-daemon / --mcp-daemon-socket patch the config to run dent8 mcp proxy against a local daemon instead of launching a direct stdio server. Supports --output json with 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, and FAIL on tamper/rollback/corrupt heads. With --agent, it loads the generated .dent8/env plus 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 installed command + args + cwd + env with initialize + tools/list + runtime_status and 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 uses dent8 mcp proxy, doctor first probes the target daemon socket with the config’s own DENT8_GRANT / DENT8_IDENTITY_KEY, reports the authenticated source on success, and reports a concrete dent8 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 --agent doctor. Hecate has no default project-local config path, so use --agent hecate --mcp-config PATH for Hecate task configs. If --mcp-command is 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 references dent8 hook native-memory-guard with DENT8_HOOK_ENFORCE=1, WARN when the hook is missing/advisory, and WARN/unknown for profiles whose hook schema is host-specific. Since dent8 init now 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 as dent8 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, explain still returns ok, and verify passes. 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-run dent8.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, else high), so a healthy source whose ceiling is below high now 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 own ok fact 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 as SKIP rather than WARN; doctor --output json exposes stable ok / warn / fail / skip sections, and doctor --agent --output json also includes a structured mcp_runtime object with the MCP smoke result plus the live runtime_status payload when the server answers. doctor --all-agents --output json includes an agents[] array with each profile’s ok / failed / skipped status 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-to stamp the fact’s asserted validity interval (ADR 0016): reads treat an elapsed valid_to exactly like an elapsed TTL, and an inverted interval is rejected. Subjects are written as <kind>:<key> (for example, person:alice or repo:dent8); authority/source are provenance metadata, not part of the fact. They can be passed explicitly, or omitted when DENT8_GRANT is 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=0 opts out), and the opt-in DENT8_ENTRENCHMENT_GATE=1 additionally 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 to Contested, keeping both (paraconsistency, ADR 0009). This is dissentnot authority-gated, so a low-authority source can flag a wrong fact without overriding it; the exception is a Canonical incumbent, which hard-alarms (and records the rejected challenge on the incumbent, ADR 0015). Takes --valid-from/--valid-to for the opposing fact like assert.
  • 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 terminal Expired lifecycle. 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 a DerivedFrom dependency edge (ADR 0010). If the source is later retracted/expired, verify flags this derivative as tainted — the “poison does not survive in derivatives” differentiator, demonstrated by the poisoned_source_retraction eval.
  • The write commands above support --output json for agent wrappers/scripts. Every dent8 command emits its --output json result — success and error — to stdout as one object, so a machine consumer reads a single stream and branches on the object’s status (the nonzero exit code still signals failure); mcp serve, mcp proxy, daemon serve, and hook have no JSON result. Each payload carries a schema_version. The status string matches the MCP tool’s for the same operation: an admitted write is accepted (contradict is contested), a firewall refusal is rejected, malformed input is invalid; on the read side verify is ok/integrity_issues, runtime/snapshot probes may report degraded, and explain/conflicts surface contested.
  • 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-Active fact past its TTL or its asserted valid_to is headline-flagged [stale — no longer valid], and one whose valid_from is still in the future [not yet valid]; the receipt carries fresh + not_yet_valid + the valid_from/expires_at window plus the fact’s survived-challenge count (ADR 0015/0016). Time-travel (ADR 0016): --as-of folds only events recorded at or before an instant (the store as it stood then) and --valid-at judges freshness at an instant instead of now. Composes with assert/supersede/retract across processes (and the same receipt backs the MCP explain tool and resources/read). Supports --output json for 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-at time-travel like explain. 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 as dent8://{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 MCP list_facts / resources/list (which carry the same freshness); pass --include-diagnostics when auditing setup noise. Supports --output json (each fact gains a freshness field).
  • 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 or SessionStart-hook injection, or --output json for machines. Belief-state aware: terminal facts never appear, believed-but-stale / not-yet-valid facts are omitted by default (counted in a trailer; --include-stale shows them annotated), and a contested fact is flagged inline — with JSON status: "contested" — rather than silently picked. Each fact carries its authority, asserting source, and dent8:// receipt reference, so a generated block stays verifiable with dent8 native reconcile. With --record-retrieval, every fact the pack emits also gains a fact.retrieved audit event on its stream (stamped with --purpose, default context-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:agent at low), through the normal write boundary; the markdown stays a pure context block and --output json reports recorded_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 same op_* firewall path as the interactive writes: op is assert (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 on dent8 assert. A used_in_decision proposal takes a decision instead of a value and records a fact.used_in_decision audit 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, then DENT8_GRANT defaults, then the agent tier of the default profilesource:agent at low). Every line is attempted and reported; exit 2 on any malformed line, 1 on any firewall rejection (a safety signal for hook logs), else 0. --consume truncates 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 json with per-line results and kept_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 json it 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 with status: "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 is dent8 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; enable DENT8_ENTRENCHMENT_GATE to reject at write time), so verify stays OK. Supports --output json (with a structured advisories array); 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 a comparison object). 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 in dent8-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 a value_kind discriminator so redacted ≠ absent, and a stable authority name — the DerivedFrom dependency edges as a derived_from list column, and the full event retained in event_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_status reports the live server binary, cwd, selected store URL/path, event count, authority registry, signed identity, and witness configuration, while snapshot combines that runtime view with facts, verify, and conflicts for polling/debugger clients. The initialize response includes server instructions that tell MCP-aware agents to call snapshot (or runtime_status/list_facts for narrower checks), inspect dent8 before relying on durable project facts, and treat rejected writes as safety signals. Tool definitions advertise outputSchema for every structured result. Tool calls return human-readable content plus MCP 2025-11-25 structuredContent with 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), and rejection_reason when a firewall write is refused. The server prefers protocol 2025-11-25, also negotiates 2025-06-18, and includes a serialized JSON mirror as a second text content block for clients that ignore structuredContent. Malformed calls are status: "invalid" rather than status: "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 under examples/; they are client wiring only, not alternate semantics. Optional hook guard profiles under examples/agent-hooks/ call the built-in dent8 hook native-memory-guard to protect provider-native memory/rules files from bypassing dent8, while native_scan / native_reconcile let 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 same op_* 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 serves resources/list / resources/read — each fact stream is a readable resource at dent8://{kind}/{key}/{predicate} (read returns the integrity receipt, and a write-capable connection also appends a fact.retrieved audit event with purpose mcp:resources/read by default; set DENT8_MCP_RECORD_RETRIEVAL=0 to 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, 0700 dir + 0600 socket; $TMPDIR fallback where $XDG_RUNTIME_DIR is 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/hello presents the grant, the daemon verifies it and issues a single-use, 30-second, connection-scoped nonce, and dent8/prove returns an Ed25519 signature over the domain-separated dent8.session-challenge.v1 challenge (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). Setting DENT8_DAEMON_SOCKET routes the CLI’s own writes through such a daemon (the CLI does the handshake with its DENT8_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, then DENT8_DAEMON_SOCKET, then the default per-user daemon path. It first performs an unauthenticated read-only runtime_status probe so a user can see whether the daemon is reachable and which store it serves. If DENT8_GRANT and DENT8_IDENTITY_KEY are 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 for dent8 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 same dent8/hello / dent8/prove session-challenge handshake using the caller’s DENT8_GRANT and DENT8_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 on notifications/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 writes args = ["mcp", "serve"]; --use-daemon writes ["mcp", "proxy"], and --daemon-socket PATH writes ["mcp", "proxy", "--socket", PATH] so stdio-only clients can use a running local daemon. --dry-run renders the expected file without writing; --check does not write and exits 0 only when the existing file already matches. Supports --output json with structured config action, generated argv, rendered contents, and local-bin wrapper metadata. It reads the generated .dent8/env plus 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.json by default, or a side file / project .grok/config.toml when Claude already owns .mcp.json), Cursor (.cursor/mcp.json), Gemini (.gemini/settings.json), and Cascade (.windsurf/mcp_config.json); Hecate requires --config because 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 --dir is not literally named .dent8, pass --config (or --mcp-config through dent8 init) because the installer cannot infer the project root safely. The default command is dent8, 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-bin is the repo-local no-Cargo-startup path: it writes/validates .dent8/bin/dent8, a wrapper around a prebuilt .dent8/target-sqlite/debug/dent8 created with CARGO_TARGET_DIR=.dent8/target-sqlite cargo build -p dent8-cli --features sqlite. doctor --agent ... --mcp-local-bin verifies 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), runs dent8 verify on session/post-write audit modes, and exits 2 when DENT8_HOOK_ENFORCE=1 blocks 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 init installs 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 with dent8 init --no-native-memory-guard. It only covers agents whose PreToolUse hook 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 explicit dent8://<kind>/<key>/<predicate> references, and resolves each through the normal dent8 read path (explain semantics, including --as-of and --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/MCP op_* write layer (before the firewall). defaults seeds the registry with the out-of-the-box trust profile for a shared repository — source:humanhigh, source:cimedium, source:agentlow (human > CI > agent) — merge-only (an existing grant for those sources is kept, never downgraded), so arbitration works without inventing a trust taxonomy first; canonical stays reserved for explicit add. A source→authority ceiling registry: every write checks the requested/defaulted authority against its source’s registered ceiling and rejects (does not silently cap) a write above it — so a low-trust source cannot mint canonical, 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). Set DENT8_REQUIRE_AUTHORITY=1 to make a missing registry fail closed. With a registry it is deny-by-default — an unlisted source’s ceiling is Unknown, 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 reads DENT8_AUTHORITY from the local filesystem; sync it per instance). A grant’s issuer and scope are enforced at the same write gate: scope is 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. When issuer names 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 add refuses 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 remove is 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 --force cascades the revocation down the delegation chain, so orphaned delegates authorize nothing (deny-by-default) until an operator re-parents them with authority add. Remaining caveat: the ceiling is an op_*-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 json for list/add/remove.
  • The content-check hook (DENT8_CONTENT_CHECK) — the pluggable content layer (content-check.md), enforced at the same CLI/MCP op_* 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 answers allow, reject (the write is refused, nothing persists), or taint (admitted but marked with a content-check: evidence flag — detect-only like retraction taint; dent8 verify reports still-believed flagged facts as CONTENT-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=1 opts 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 in examples/scanners/ exists only to prove the seam and drive the eval hook mode. Same op_*-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; bootstrap remains 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>.env for one source. status checks the bundle/trust/active-grant/grant/source key/issuer key and reports expiry. All identity subcommands support --output json with structured paths, artifact metadata, and follow-up commands where applicable. repair-env rewrites generated .dent8/identity-<source>.env and, 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-source replaces 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/revoked records 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-log seeds records for grants that predate the log (stamped now, never backdated — pre-history entitlement stays honestly unknown). status/doctor gain a grant-log consistency line, and verify uses 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 binds source -> 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) that verify re-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 than low (medium, high, or canonical) — 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-in DENT8_REQUIRE_IDENTITY/registry. This closes the old bypass where --authority high --source source:human was 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 — CLI op_*, dent8 capture/import, the MCP tools, and the daemon — is covered. To keep the honest above-agent path working out of the box, dent8 init now 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: if DENT8_TRUST exists or DENT8_REQUIRE_IDENTITY=1, every write must have DENT8_GRANT and DENT8_IDENTITY_KEY; when DENT8_ACTIVE_GRANTS or the sibling active-grant registry exists, the presented grant must also be the current grant for that source. Migration: run dent8 init (or configure DENT8_TRUST/DENT8_GRANT/ DENT8_IDENTITY_KEY) — unsigned high-authority writes that “worked” before are now rejected; lower them to --authority low if they do not need above-agent trust. Signed identities are source:*-scoped, so an above-agent write must come from a source:* identity. Limits: source keys are local files (0600 required 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. keygen writes a keypair (private key 0600, with the warning to keep it off the log-writer’s machine); sign emits a signed tree head over the current log and appends it to a witness log (DENT8_WITNESS_LOG); verify re-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 internal verify_chain cannot, threat-model T6) as TAMPER and a truncation/reorder as ROLLBACK. serve [interval] [max-heads] is the cadence signer — it signs the head whenever the log grows, the loop a separate operator runs; head prints the latest signed head as JSON; and publish <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 reading DENT8_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/serve append signed grant-log heads (DENT8_WITNESS_GRANTS_LOG), verify detects a truncated revocation as ROLLBACK, and publish/verify-published take --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 (publish without --grants says so when a grants-witness log exists, instead of silently half-covering). dent8 init --witness configures verifier-side paths, dent8 doctor reports witness coverage, and doctor <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 checked examples/witness/ operator-split demo). All subcommands support --output json for CI/monitoring (keygen, sign, head, publish, verify, verify-published, doctor); serve streams NDJSON in JSON mode — signed heads on stdout (event: "head_signed", lane: "events" | "grants"), lifecycle on stderr. witness doctor --output json groups checks into stable ok / warn / fail sections. The operated deployment is packaged in examples/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 same clap command model as the parser. Visible aliases completion and autocomplete are accepted. Supports --output json with the generated script string.
  • dent8 schema postgres — prints the Postgres schema. Supports --output json with 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|json flag 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, and mcp 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:

  • FactEvent model, 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 via cargo 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, 0x00 domain separation. Not JCS (sorted-key serde_json form — see storage.md). The “logically-equal → identical bytes” invariant holds for all fields including embedded JSON: FactValue::Json is the CanonicalJson newtype, 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 that verify_chain cannot (threat-model T6).
  • Asymmetric anchor (sign_head / verify_signed_head / SignedTreeHead, behind the signed-anchor feature): 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 when float_roundtrip is removed), canonical_bytes/event_hash round-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 through apply_event is checked step-by-step against an independent reference model (accept/reject, reject reason, resulting lifecycle), plus terminal absorption / non-resurrection, value immutability, updated_at tracking, 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_chaincanonical_bytesapply_event) never panics on adversarial input, including values that bypass the constructors’ validation via derived Deserialize (out-of-range Confidence, extreme timestamps, u64::MAX TTL, 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_entity absorbs self-referential / cyclic / dangling supersessions and extreme freshness/TTL math without crashing.
  • Golden replay fixtures (tests/golden_replay.rs, fixtures in tests/golden/replay/): named event streams frozen on disk as canonical .events.jsonl (the DENT8_LOG format) + .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 with UPDATE_GOLDEN=1).
  • Scenario-family golden corpus (evals/, harness dent8-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; each evals/replay/<name>.expected.json freezes the whole-stream firewall outcome — admitted vs rejected writes (with a stable category), per-fact end-state, read-time freshness, and retraction taint — for beginner_to_senior, ttl_expiry, summary_drift, consistency_required, and low_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::append itself (via arbitrate): 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, and dent8 mcp serve.
  • InMemoryEventStore (test + file-backed CLI dev backend, not operational) + IntegrityReceipt / explain / explain_subject + global-chain verify_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 arbitrated append path.
  • 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::Never is out of scope), enforced via enforce_policy / apply_policy_defaults. Ships repo.database, repo.test_command, dependency.version, branch.status, user.preference.
  • EventStore / AsyncEventStore traits — 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_chain over the dent8_event_log table (migration 002). Transactional append serialized by an advisory lock for the global chain; the firewall reuses arbitrate_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 shared apply_event) into dent8_claim_projection and records the fact→fact relationship into dent8_claim_edge, in the same transaction. materialized_projection reads the believed state without re-folding; edges_from reads the supersession/contradiction/reinforcement graph; verify_projection re-folds and asserts projection == fold(log). Derived caches, not a second source of truth.
  • Event-id allocator (migration 004): dent8_id_allocator reserves CLI/MCP event:{n} suffixes before signing. IDs are unique but not gap-free; append order is global_sequence.
  • Status: verified against a live Postgres (postgres:16). The DATABASE_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 — via DATABASE_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). sqlx is 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 EXISTS is not race-safe on the pg_class/pg_type catalog), and connect() bounds its acquire timeout so an unreachable DB fails fast.

dent8-evals:

  • Demonstrative corpus (run_corpus, behind dent8 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 (or cargo 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 behind dent8 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.
  • Postgres remaining gaps — not the adapter itself. The v0 PostgresEventStore + its materialization (migration 003) are DB-verified (the gated integration tests pass against a live postgres:16, via compose.yml or the CI postgres job), and the runnable surface uses it: with DENT8_STORE_URL set and a --features postgres build, dent8 and mcp serve read/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 gated postgres job runs the adapter tests and a live assert → supersede → explain → verify end-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-agnostic AsyncEventStore trait (?Send, with atomic append_many) that both PostgresEventStore and SqliteEventStore implement; the CLI’s connect_backend dispatches by URL scheme into a Box<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 plain cargo test, no server), proving the boundary is genuinely backend-agnostic. What remains design-only: a richer per-column event table + uses_as_evidence edges (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 with DENT8_STORE_URL + a --features postgres build (selected in load_store/append_events via connect_backend; multi-event ops use the transactional append_many, and the Postgres load re-runs the same validate_unique_log integrity gate as the file path) — CI-verified end-to-end against live Postgres (the postgres job runs a live assert → supersede → explain → verify plus 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 reserve event:{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 remains global_sequence plus 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.sh drives 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 IMMEDIATE on a <db>-lease sidecar; 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, verify green; 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 keyskeychain:<account> accepted wherever a key path is, on macOS (Keychain), Linux (Secret Service via secret-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 / UsedInDecision close the read-audit loop on both CLI and MCP. The loop is runnable: dent8 context --record-retrieval emits fact.retrieved for every fact it packs; a dent8 capture proposal with "op": "used_in_decision" records fact.used_in_decision; and MCP resources/read auto-records fact.retrieved with purpose mcp:resources/read on every successful read from a write-capable connection (stdio MCP / authenticated daemon). Opt out with DENT8_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 rmcp SDK — the v0 server (read/audit tools, full belief surface as tools, resources/list/resources/read, resources/subscribe with notifications/resources/updated pushed on both transports (own writes immediately, cross-process writes within a poll tick; dent8 mcp proxy pumps 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, same dispatch). Prompts and streamable-HTTP push are not implemented; the official rmcp SDK is the eventual upgrade path.
  • Desktop debugger/control plane — the app is runnable: dent8 ui serves 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 same op_*/snapshot path as CLI/MCP; 127.0.0.1-bound; non-localhost Host refused; 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 as dent8 witness keygen | sign | verify | publish | verify-published | serve | doctor (Runnable, above), with init --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.

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.