feat(mcp): Step 2 governance — scope + quarantine-state + history audit trail#6
feat(mcp): Step 2 governance — scope + quarantine-state + history audit trail#62233admin wants to merge 4 commits intofeat/ai-output-sedimentfrom
Conversation
Step 2 of AI-Output provenance (Appendix D of governance plan). - vault.writeAIOutput now accepts optional scope (project|global| cross-project|host-local) and quarantineState (new|reviewed| promoted|discarded), written to frontmatter. Defaults preserve Step 1 behaviour (scope=project, quarantine-state=new). - Zero logic change in vault.sweepAIOutput: it still keys off status only; governance fields are passthrough for this commit. - 8-field schema documented in docs/ai-output-convention.md with the three-axis model (scope = namespace, status = content timeliness, quarantine-state = trust gate). - 4 new tests: default values, explicit values, invalid scope rejected, invalid quarantineState rejected. 14/14 pass. - mcp-tools-reference.md regenerated (+4 lines).
Completes Appendix D of the Step 2 governance extension. - vault.sweepAIOutput now appends a structured history entry whenever it flips draft -> stale. Entry is YAML flow-style so the Step-1 scalar-array frontmatter parser round-trips it as an opaque string, avoiding a parser rewrite until Step 3 needs structured reads. - New helper appendHistoryInYaml in index.ts: initialises the history: key if absent, otherwise inserts a new item after the last existing flow-style entry. - Schema doc explains the flow-style choice, lists required fields (ts / from / to / trigger / evidence_level / human_in_loop / note) with the trigger enum from the governance plan, and notes that human-driven transitions must also append. - 2 new tests: first-flip appends a new history: block; a file seeded with an older history item keeps both entries in order. 16/16 ai-output pass; full mcp-server suite 104/104.
There was a problem hiding this comment.
Code Review
This pull request introduces Step 2 governance for AI-generated output by adding "scope" and "quarantine-state" fields to the frontmatter and implementing a structured "history" audit trail. The changes include updates to documentation, tool definitions, and the gardener sweep logic to record status transitions. Review feedback identifies a contradiction in the documentation regarding field optionality and points out technical risks in the manual YAML manipulation logic, such as handling empty history arrays and inconsistent line endings.
| Per-persona subdirs keep lint/stale policy local to each persona for MVP; if per-content-type differentiation matters more than per-persona later, the layout can flatten without a schema change (all required info is in frontmatter). | ||
|
|
||
| ## Schema (6 fields, all required) | ||
| ## Schema (8 fields — 6 required, 2 governance with defaults) |
There was a problem hiding this comment.
| if (historyKeyRe.test(yamlBlock)) { | ||
| // Insert after the last ` - ` item following history: | ||
| const lines = yamlBlock.split("\n"); | ||
| let hIdx = -1; | ||
| for (let i = 0; i < lines.length; i++) { | ||
| if (/^history:\s*$/.test(lines[i])) { hIdx = i; break; } | ||
| } | ||
| let insertAt = hIdx + 1; | ||
| while (insertAt < lines.length && /^ {2}- /.test(lines[insertAt])) insertAt++; | ||
| lines.splice(insertAt, 0, ` - ${flowItem}`); | ||
| newBlock = lines.join("\n"); |
There was a problem hiding this comment.
The logic for appending to an existing history block is fragile. It specifically looks for history: on its own line (line 109) and assumes items are indented with exactly two spaces (line 112). If the frontmatter contains an inline empty array (e.g., history: []), the historyKeyRe might match but the line-by-line search will fail to find the correct index, potentially leading to duplicate history: keys or incorrect insertion at the top of the YAML block. Additionally, if the source file uses CRLF line endings, this function will introduce mixed line endings (LF for the new lines, CRLF for the rest).
Follow-up to the external governance review. Adds a 9th frontmatter field that caches the human-vs-machine signal, informed by agent-memory-runtime's split of quarantine_state (machine) and review_status (human). review-status enum: none | user-confirmed Design choice: the enum deliberately omits the value "reviewed" that quarantine-state already uses. Two clean axes: - quarantine-state: new | reviewed | promoted | discarded (gardener's model of candidate maturity) - review-status: none | user-confirmed (Curry's explicit signature — never auto-advanced by the sweep) Why cache at all instead of deriving from history: - history is source of truth (flow-style items survive parser) - but flow-style means searchByFrontmatter can't index it - frontmatter field exists purely as the indexable cache over latest history entry with trigger=manual-user-confirmed-write - sweep never writes user-confirmed; only explicit reviewStatus param or a manual history append can (Step 2.6 gardener skill) Changes: - vault.writeAIOutput accepts optional reviewStatus param - frontmatter serialises review-status: none by default - schema tool description bumped to "9-field" in operations.ts - convention doc adds the 4-axis explanation + cache semantics - mcp-tools-reference.md regenerated (+1 line) - 3 new tests (default, explicit user-confirmed, invalid enum) — 19/19 ai-output pass; no regression in 104 full-suite run
…w handoff Records two-PR (#6 governance + #7 Step 2.5 input gate/metrics) outcome, external-review absorption from agent-memory-runtime, and an explicit self-audited backlog for next session: P1: review-status cache has no sync code (drift lurking); history from/to axis is ambiguous (needs axis: sub-key) P2: review-status should be an Obsidian tag not a fm field (adapt-over-build rule violation); input gate thresholds are guesses (should downgrade to warnings); metrics is snapshot without trend log; no e2e MCP smoke test P3: MUST-append-history undocumented enforcement; borrow-3/skip-3 was inference not measurement; PR should have been single Next-session priority: migrate review-status frontmatter -> Obsidian tag (~-30 lines, kills two gaps at once), then downgrade input gate to warnings (~10 lines), then history axis sub-key (~20 lines), then metrics trend log (~15 lines). Total ~70 lines closes all P1 + half of P2.
…dless MCP) Consolidates PRs #4 → #5 → #6 → #7 → #8 into one commit. ## Headline - 7 vault-* persona skills (architect/curator/gardener/historian/janitor/librarian/teacher) - AI-Output sediment pipeline (writeAIOutput + sweepAIOutput + review-status + scope + quarantine-state + history audit trail) - Step 2.5 input gate with warning emission - Step 2.6-2.8: tag migration + axis sub-key + sweep metrics trend log - Bilingual user guide (EN + CN) - Auto-generated tools reference with drift guard - End-to-end stdio smoke test - Paste-install UX (setup / setup.ps1) - Graph viewer (static HTML) - Brand: LLM Wiki Bridge (display) / obsidian-llm-wiki (slug) ## Merge-prep (c8651f5) - loadConfig precedence flipped to env > ./yaml > ../yaml (fixes silent vault redirect) - pglite vector.tar.gz bundle path fix via esbuild externalize (5ee746a) - compiler ruff clean (unblocks lint-python CI) - docs/ICEBOX.md: 2026-04-20 persona+MCP audit 12 items deferred to v3 - CHANGELOG.md v2.0.0 entry 121/121 tests green.
Extends AI-Output frontmatter per Appendix D of the Step 2 governance plan (
project_llm_wiki_step2_governance.md). Layers on top offeat/ai-output-sediment; merges after that branch lands.Two commits, logical split
d1862b1 — scope + quarantine-state (zero sweep-logic change)
Adds the two new optional fields as pure passthrough.
vault.writeAIOutputaccepts and serialises them with safe defaults (scope=project,quarantine-state=new).vault.sweepAIOutputstill keys offstatusonly — no behaviour change for existing Step 1 flows.scope ∈ {project, global, cross-project, host-local},quarantine-state ∈ {new, reviewed, promoted, discarded}5749010 — history audit trail
vault.sweepAIOutputnow appends a structured history entry on everydraft→staleflip. YAML flow-style (- {ts: ..., trigger: ...}) keeps the Step-1 scalar-array parser byte-compatible — no parser rewrite needed until Step 3 wants structured reads.appendHistoryInYamlinitialises thehistory:key if absent, otherwise inserts after the last flow-style itemVerification
npm run build: 0 tsc errorsnpm run generate-tools-doc: drift guard green, writeAIOutput entry updated to 8-field schemaOut of scope (Step 3+)
Stats
~230 insertions total across 2 commits — ~160 code/test, ~70 docs.