Skip to content

Skill reference: mif-provenance

New to this? Start with the how-to guide for the walk-through, or the explanation for the reasoning behind it. This page is the deep reference for drilling into specifics.

The mif-provenance skill is the suite’s witnessed-provenance helper — the fifth authoring helper. It stamps hook-observed session facts into a MIF document’s provenance block and verifies existing blocks against those observations. The distinction it adds to the suite is the witness itself: mif-frontmatter writes provenance asserted from drafting context, mif-validate passes any schema-valid block regardless of who actually authored the document, and only this skill proves the named session touched the file — the model being described is never the source of the facts describing it.

Property Value
Authors A witnessed provenance block; a deterministic match/drift verdict
Purpose group Authoring helpers
MIF conceptType substrate
Target MIF level 1 (operates on documents at any level, preserving it)
Primary source The capture hooks’ session ledger

This skill (stamp/verify/status) is the piece you invoke. The capture hooks (hooks/provenance-session-start.mjs, hooks/provenance-post-tool-use.mjs, hooks/provenance-session-end.mjs) are the piece that runs silently in the background and is never invoked directly — they only build the ledger this skill reads. The skill never observes a session itself; it only turns what the hooks already recorded into a document’s provenance block, or checks a block against it.

Capture is implemented entirely on Claude Code’s own plugin hook events (SessionStart, PostToolUse, SessionEnd — see hooks/hooks.json), so witnessed provenance is only available for sessions run through Claude Code today. The ledger contract itself is a generic, append-only “session touched file” log with no Claude-Code-specific assumption in its shape; extending capture to another coding agent or tool would mean that tool appending to the same kind of ledger, which is real, unstarted work, not something this design rejects.

The ledger is a local, unsigned file written by the plugin’s own hooks. That is a real witness — the hooks observe tool events; they do not ask the model what happened — but it is not a cryptographic attestation. The stamped trustLevel is therefore fixed at user_stated and is not configurable: never verified, which MIF reserves for externally verifiable attestations. confidence is never written under any configuration — a witness proves presence, never extent. There is no signing; anyone with filesystem access could edit the ledger, which is exactly why the ceiling is what it is.

Reads the ledger scoped to the selected session and writes only witnessed fields into the document’s provenance block:

  • agentclaude-code/<model>, the model witnessed at the latest touch (mid-session model switches are real), else the session line’s; bare claude-code when none was witnessed.
  • agentVersion — the CLI version witnessed from its exec path; omitted rather than invented when unwitnessed.
  • wasGeneratedBy — the session activity URN urn:mif:activity:claude-code-session:<session-id>, which doubles as the stamp marker the corpus report keys on.
  • trustLeveluser_stated, from the fixed policy above.

It modifies only the provenance block and the modified timestamp (set to the latest witnessed touch, making the stamp byte-idempotent), and it preserves every provenance field it does not own (sourceType, wasAttributedTo, wasDerivedFrom, …). It declines — exit 3, file untouched — when the ledger records no touch of the document by the selected session, when the document is not MIF-conformant to begin with, or when stamping would drop the document below the MIF level it already satisfies.

Re-derives the expected block from the same ledger and reports match or per-field drift (a hand-edited agent, a document the ledger never witnessed). It never restamps — reconciliation is an explicit stamp. The verdict is deterministic, with no model in the conformance path: identical document, ledger, and resolved config yield an identical verdict, the same discipline mif-validate established for schema conformance.

Reports whether capture is actually active for the current session, from inside the session, without touching any document: the resolved mifProvenance.capture/stamp config, and — when capture is on — whether the session ledger has a session_start line for the current session id. Enabling capture mid-session, or updating this plugin mid-session, is not guaranteed to wire hooks into an already-running session’s dispatch (issue #90, confirmed by direct repro: Claude Code snapshots the set of hook commands per matcher at session/plugin-load time and does not re-read hooks.json for that set on later dispatches), and both provenance-config.mjs (fail-closed) and the capture hooks themselves (fail-open) are deliberately silent either way — status is the one surface built to say so plainly. A missing session_start line means: restart the Claude Code session, don’t keep authoring and hoping.

status also hashes this plugin’s own hooks.json at every session_start and compares it against the current on-disk copy. A mismatch means this plugin was updated after this session started — the running session may still be dispatching the stale hook set it loaded at start — same fix, restart the session.

--session <id> wins; $CLAUDE_CODE_SESSION_ID (the same variable the capture hooks record from) is next; otherwise the verbs use the ledger’s own evidence — but only when exactly one session ever touched the file. Several witnessing sessions is an error demanding --session, never a guess. Selection can only ever land on a session the ledger witnessed touching the document, so cross-session attribution is structurally impossible, not merely discouraged.

Everything is opt-in and fail-closed, per ADR-0005: the mifProvenance settings key (capture, stamp) defaults to off at every scope, an explicit disable at any scope defeats an enable at every other, and a malformed settings file disables rather than enables. The hook-mediated stamp: "ask" mode surfaces the exact command and writes nothing until it is run on the user’s explicit approval; in CI or headless contexts "ask" behaves as "off".

Terminal window
node scripts/mif-provenance.mjs stamp <file> [--session <id>] [--ledger <path>]
node scripts/mif-provenance.mjs verify <file> [--session <id>] [--ledger <path>]
node scripts/mif-provenance.mjs status [--session <id>] [--ledger <path>]
npm run provenance-corpus-check # witnessed-vs-asserted coverage report

Exit codes: 0 stamped / match / status-healthy, 1 verify drift (including unwitnessed) or status found no session_start line yet, 2 usage or environment error, 3 stamp declined.