vault-provenance
Classifies claims in one vault note by epistemic status and back-fills its sources frontmatter. Use when the user wants claims and sources checked ("epistemic status", "provenance", "mark claims"). No
SKILL.mdplugins/mz-knowledge/skills/vault-provenance/SKILL.md
GitHubVault Provenance
Overview
Discipline skill that classifies every factual claim in a single vault note against a five-value epistemic vocabulary (first-hand | cited | inferred | received | unmarked) and back-fills the note's epistemic_status: and sources: [] frontmatter after explicit user approval. Scope is strictly per-note — never vault-wide. Body content is preserved verbatim unless the user opts into annotation mode.
When to Use
Classifying the factual claims of a single vault note by epistemic status and back-filling its sources, for example after drafting a research note or before promoting it to evergreen.
When NOT to use
- Vault-wide provenance audits — this skill is per-note by design.
- Whole-vault structure audits (orphans, broken links, stubs, tags) — use
vault-health. - Suggesting new links between notes — use
vault-connect. - Atomizing a note into smaller notes — use
process-notes. - Schema or frontmatter compliance checks — use
vault-schema.
Arguments
$ARGUMENTS is <note name or path>. If the argument is a bare name, resolve it against the vault by exact filename match first, then by case-insensitive basename match. If the argument is missing or matches zero / multiple notes, escalate via AskUserQuestion — never guess.
Constants
- PROJECT_ROOT: resolved once in setup with
echo "${MZ_MEMORY_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; expand<PROJECT_ROOT>to it in every command and agent prompt. - TASK_DIR:
<PROJECT_ROOT>/.mz/task/ - MAX_CLAIMS_PER_NOTE: 25 — hard cap delegated to
provenance-tracer; if more claims exist, the first 25 are classified and the cap is flagged in the summary. - EPISTEMIC_VOCAB:
[first-hand, cited, inferred, received, unmarked]— closed set; no other values.
Core Process
Phase Overview
| Phase | Goal | Details |
|---|---|---|
| 0 | Setup | Inline below |
| 1 | Scan claims | phases/scan_claims.md |
| 1.5 | User approval — classifications | Inline below |
| 2 | Back-fill sources | phases/backfill_sources.md |
Phase 0: Setup
- Resolve
PROJECT_ROOTwith the command in Constants, before anycdor worktree switch, and use it for every.mz/path below. - Parse
$ARGUMENTSas<note name or path>. - Resolve the note: if the argument is an absolute path, use it; if a relative path or bare name, resolve against the vault. If zero or multiple matches, escalate via AskUserQuestion with the candidate list. Never guess.
- Derive
task_name = <YYYY_MM_DD>_vault-provenance_<slug>where<YYYY_MM_DD>is today's date (underscores) and<slug>is the note basename normalised to[a-z0-9-]; on same-day collision append_v2,_v3. CreateTASK_DIR<task_name>/on disk. - Write
state.mdwithschema_version: 2,phase_complete: false,what_remains: [],Status: running,Phase: 0,Started: <ISO timestamp>,NotePath: <absolute path>,TaskName: <task_name>. - Print a visible setup block showing
task_name, resolved note path, and the output artifact path<PROJECT_ROOT>/.mz/task/<task_name>/claims_analysis.md.
Phase 1 — Scan claims
See phases/scan_claims.md.
Phase 1.5: User approval — classifications
This orchestrator (not a subagent) presents this gate. This step is interactive and must not be delegated.
Pre-read: Read <PROJECT_ROOT>/.mz/task/<task_name>/claims_analysis.md and capture its full contents into context.
State: write state.md with the artifact path and what_remains: [awaiting approval: claims gate]; Status stays running.
Surface 1 — emit the plan message. Output the artifact verbatim as a normal markdown chat message:
## Claims analysis ready for review — vault-provenance
<verbatim contents of <PROJECT_ROOT>/.mz/task/<task_name>/claims_analysis.md>
---
**Approve** → proceed to Phase 2 to back-fill frontmatter · **Reject** → abort task, no frontmatter written · reply with feedback to revise
Emit the full verbatim contents of <PROJECT_ROOT>/.mz/task/<task_name>/claims_analysis.md — do not substitute a path, summary, or placeholder.
Surface 2 — call AskUserQuestion. A short selector — do not re-embed the artifact in the question body:
- question:
The claims analysis above is ready for review. - header:
Claims - options: Approve — proceed to Phase 2 to back-fill frontmatter · Reject — abort task, no frontmatter written
Response handling:
- Approve → add
- classifications approved: .mz/task/<task_name>/claims_analysis.mdunder## Decisionsinstate.md, remove theawaiting approvalentry, setPhase: classifications_approved, and proceed to Phase 2. - Reject → update state to
aborted_by_userand stop. No frontmatter write occurs. - Any other reply (feedback) → incorporate edits (e.g., "reclassify claim 3 as received", "swap proposed source on claim 7", "enable annotation mode"), re-dispatch
provenance-tracerif a full re-scan is needed or edit the artifact in place for targeted changes, overwrite<PROJECT_ROOT>/.mz/task/<task_name>/claims_analysis.md, return to Surface 1, re-read the updated artifact, and re-emit the entire plan message from scratch — never diff-only, never summary-only. This is a loop — repeat until the user explicitly approves. Never proceed to Phase 2 without explicit approval. - No answer → set
Status: blocked, keepawaiting approval: claims gate, stop. A missing answer is never approval.
Phase 2 — Back-fill sources
See phases/backfill_sources.md.
Common Rationalizations
| Rationalization | Rebuttal |
|---|---|
| "This note is obviously first-hand — skip the analysis." | "First-hand classification requires tracing each claim to a direct experiential marker; skipping the analysis produces a false confidence marker across every sentence in the note." |
| "Classify all claims at the note level, not per-claim." | "Note-level classification loses provenance granularity; a single note routinely mixes first-hand observation, cited findings, and inferred conclusions. Every claim must carry its own attribution." |
| "Sources frontmatter is optional — the user probably won't use it." | "Provenance is the whole point of this skill; the frontmatter-only annotation contract is the default output. Treating it as optional silently reduces the skill to an advisory pass." |
| "Inline annotations are more visible — patch the body by default." | "Body annotations mutate author-authored prose and can break formatted notes; they require explicit opt-in during the Phase 1.5 feedback loop. Frontmatter-only is the default for a reason." |
State Management
State persists to .mz/task/<task_name>/state.md under the project root, ${MZ_MEMORY_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}, resolved once at the start of the run. Schema is v2: line 1 is schema_version: 2; line 2 is Status: with one of pending, running, blocked, complete, aborted_by_user or failed, lowercase and unquoted; then Phase, Started, phase_complete (boolean) and what_remains (YAML list of strings), with skill-specific keys below them. Milestones go in Phase; the reason for blocked or failed goes in what_remains. Set phase_complete: false on phase entry and true once the phase's artifacts are written and its gates pass. Refresh what_remains on every phase transition; it is [] when Status is complete. Write the file after every phase and before every gate, and re-read it after compaction. Record decisions worth recalling under a heading that reads exactly ## Decisions, placed before ## Notes; mz-memory harvests them only once Status is complete. Under a heading that reads exactly ## Lessons, add one line whenever a review finding is accepted and fixed, a step fails, gate feedback corrects a mistake, or an approved approach is reversed, in the form - (review|failure|correction|change) WHEN <situation> → <what to do> — WHY <cause>, concrete and under about 40 words; mz-memory harvests lessons whatever the Status. Treat a legacy completed as complete. On reading a schema_version: 1 or unversioned file, put schema_version: 2 on line 1 and Status on line 2, add the missing keys, and log the upgrade. Leave a file with a higher schema_version untouched: ask whether to Restart (archive it), start New (_v2) or Abort, and stop if nobody can answer.