Skip to content

feat(mcp): Step 2 governance — scope + quarantine-state + history audit trail#6

Closed
2233admin wants to merge 4 commits intofeat/ai-output-sedimentfrom
feat/step2-governance
Closed

feat(mcp): Step 2 governance — scope + quarantine-state + history audit trail#6
2233admin wants to merge 4 commits intofeat/ai-output-sedimentfrom
feat/step2-governance

Conversation

@2233admin
Copy link
Copy Markdown
Owner

Extends AI-Output frontmatter per Appendix D of the Step 2 governance plan (project_llm_wiki_step2_governance.md). Layers on top of feat/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.writeAIOutput accepts and serialises them with safe defaults (scope=project, quarantine-state=new). vault.sweepAIOutput still keys off status only — no behaviour change for existing Step 1 flows.

  • New enums: scope ∈ {project, global, cross-project, host-local}, quarantine-state ∈ {new, reviewed, promoted, discarded}
  • 8-field schema documented with the three-axis model (scope = namespace, status = content timeliness, quarantine-state = trust gate)
  • 4 new tests (defaults, explicit values, invalid enum rejection)

5749010 — history audit trail

vault.sweepAIOutput now appends a structured history entry on every draft→stale flip. YAML flow-style (- {ts: ..., trigger: ...}) keeps the Step-1 scalar-array parser byte-compatible — no parser rewrite needed until Step 3 wants structured reads.

  • New helper appendHistoryInYaml initialises the history: key if absent, otherwise inserts after the last flow-style item
  • Trigger enum, evidence_level, human_in_loop documented per the governance plan
  • 2 new tests (first-flip creates block; seeded history preserved and extended in order)

Verification

  • npm run build: 0 tsc errors
  • ai-output suite: 16/16 pass (was 10; +6 tests)
  • Full mcp-server suite: 104/104 pass (no regressions)
  • npm run generate-tools-doc: drift guard green, writeAIOutput entry updated to 8-field schema

Out of scope (Step 3+)

  • observation → candidate entry-gate (persona decides 'candidate vs observation')
  • durable layer + UserPromptSubmit injection gate (needs Claude Code hooks)
  • full structured-YAML parser for nested history reads

Stats

~230 insertions total across 2 commits — ~160 code/test, ~70 docs.

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.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/ai-output-convention.md Outdated
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a contradiction between this updated heading (which mentions defaults) and the statement on line 44: "None are optional". Since scope and quarantine-state now have defaults in the implementation, line 44 should be updated to reflect that they are optional for the tool caller.

Comment thread mcp-server/src/index.ts
Comment on lines +104 to +114
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");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.
@2233admin
Copy link
Copy Markdown
Owner Author

Collapsed into PR #4 for unified v2 merge (squash into main). feat/step2-6-tag-migration fast-forwarded into v2-staging; all commits in this PR are now part of #4's diff.

@2233admin 2233admin closed this Apr 21, 2026
2233admin added a commit that referenced this pull request Apr 21, 2026
…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.
@2233admin 2233admin deleted the feat/step2-governance branch April 21, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant