@owrede/vault-memory 2.0.0-rc.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,1314 @@
1
+ # Changelog
2
+
3
+ All notable changes to **vault-memory** are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ > **Release process** — every PR that ships a user-visible change MUST append an entry
9
+ > under `## [Unreleased]` below. On release, that section is renamed to the new version
10
+ > with today's date, and a fresh `## [Unreleased]` block is started above it. See the
11
+ > bottom of this file for the one-paragraph cut-a-release recipe.
12
+
13
+ ## [Unreleased]
14
+
15
+ _Nothing yet._
16
+
17
+ ## [2.0.0] — 2026-06-25
18
+
19
+ ### Fixed
20
+
21
+ - **Live indexer no longer crashes on duplicate-anchor sibling sections** (`ISSUE-indexer-duplicate-anchor.md`). A note containing two sibling sections with identical heading + body produces the same content-hash anchor and collided on `UNIQUE(note_id, anchor)`, aborting the entire `vault-memory index` / `index --full` run with `SQLITE_CONSTRAINT_UNIQUE`. `SectionsQueries` now uses `INSERT OR IGNORE` and a new `insertOneResolving()` that returns the surviving row's id on collision (so `parent_id` linkage still resolves); `buildSectionsForNote` uses it inside a per-note try/catch so one pathological note can never abort the whole vault's index. Mirrors the migration-010 backfill fix already shipped. Verified end-to-end: a full index of a vault with a colliding note exits 0.
22
+
23
+ ## [2.0.0-rc.1] — 2026-05-19
24
+
25
+
26
+ ### Added
27
+
28
+ - **Sources Registry — peer-MCP sources as first-class MCP Resources (ADR-025)** — three new MCP Resources expose the peer-MCP servers vault-memory connects to so the contract-editor palette can discover a peer's tools before any contract references them: `vault-memory://sources` (list known peers with `status` ∈ `connected`/`unavailable`/`unreachable`, `tool_count`, `last_refreshed`), `vault-memory://sources/{name}/tools` (cached `tools/list` for one peer), and `vault-memory://sources/{name}/tools/{tool}` (one tool's schema, inlined). `env` is never included in any response (secrets stay on the `set_mcp_client` path); `vault-memory` itself is not listed. MCP Resources: 10 → 13. Backed by an additive `PeerMcpRegistry` extension (`refresh`/`add`/`remove` + per-client tools cache). See `docs/v2/adr/025-sources-registry.md` + `.planning/specs/SOURCES-REGISTRY.md`.
29
+ - **2 new plugin-gated tools (ADR-025)** — `unset_mcp_client({name})` (dispose a live peer client; idempotent) and `refresh_source({name})` (re-poll `tools/list`, respawn if unavailable). Both live behind the existing `[plugin] enabled = true` gate, identical to the other Phase-7 plugin tools; the default-off MCP tool surface stays byte-identical to v1. `set_mcp_client` extended to cache `tools/list` on successful spawn and return the captured error inline on failure.
30
+ - **Task Contract DSL (Phase 6, ADR-006)** — declarative YAML contracts under `_contracts/<name>.yaml`, addressable by name, instantiable via MCP, with handle-based source/sink portability. Contracts use a closed assembly verb enum (11 baseline + `literal` + `mcp://<server>/<tool>` peer extension), `{{template}}` step composition, JSON-Schema-with-`$ref` inputs, MemorySink-only sinks (un-bypassable per D-A4c), and ChangeFeed hot reload (D-LOAD). See `docs/v2/PHASE-6-SIGN-OFF.md` + `docs/v2/adr/006-task-contract-dsl.md`.
31
+ - **3 new MCP tools (Phase 6)** — `describe_contract`, `instantiate_contract`, `register_contracts_as_tools`. Tool count: 34 → 37 (additive only; v1-baseline preserved byte-identical).
32
+ - **2 new MCP Resources (Phase 6)** — `vault-memory://contracts/{vault}` (CON-04) lists contracts available in a vault; `vault-memory://contract-verbs/{vault}` (D-A2b) lists baseline + custom `mcp://` verbs with invocation counts. Resources do NOT count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.
33
+ - **3 reference contracts (Phase 6)** under `evals/fixtures/v2-test-vault/_contracts/`: `meeting-prep`, `project-status`, `code-review-brief`. Plus `smoketest-trivial` for the CON-09 non-Claude smoketest path (literal-only assembly; no LLM needed in CI).
34
+ - **`contract_audit` table (Phase 6, migration 014)** — orchestration audit substrate; one row per assembly step (kind=`contract_step`) and one row per load failure (kind=`contract_load_error`). Payload-free per Invariant C-5 (peer-MCP outputs may carry sensitive data; we never capture them). Feeds `aggregateVerbUsage(vault)` for the D-A2b promotion signal.
35
+ - **`[contracts]` config block (Phase 6)** — `auto_register_tools: bool = false`, `tool_prefix: string = "vm_"`, `step_timeout_seconds: number = 30`, `defaults: Record<string, string> = {}`, `mcp_clients: Record<string, {command, args?, env?}> = {}`.
36
+ - **`instantiate_contract` write_back chokepoint (Phase 6)** — routes through `DeliveryAdapter.write()` so the MEM-05 invariant is un-bypassable by construction. Sink overrides MUST resolve through `MemorySinkRegistry.resolveMemorySink()` before write; otherwise `{ok:false, reason:"sink_override_not_a_memory_sink"}`.
37
+ - **CON-10 stub-parity proof (Phase 6)** — `src/adapters/source/conformance.test.ts` gains a `contracts stub-parity (CON-10)` describe block proving the same contract produces structurally identical bundles across obsidian-fs and stub source connectors.
38
+ - **CON-09 non-Claude smoketest extension (Phase 6)** — `scripts/smoketest-non-claude.mjs` spawns the server against a temp HOME with the fixture vault, asserts the three contract tools surface, calls `describe_contract` + `instantiate_contract` + reads the `list_contracts` Resource, and exits 0.
39
+ - **`ObsidianFsSource` widened to enumerate `_contracts/*.yaml` (Phase 6)** — non-recursive walk added via `scanContractFiles`; `scanVault` unchanged so the indexer's `.md`-only contract is preserved. `readDocument` handles YAML files by returning raw text as a single paragraph block (bypasses `parseNote`'s markdown assumptions).
40
+ - **`expand` MCP tool (Phase 4 plan 04-03 / GRA-01)** — typed-edge BFS retrieval primitive. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying `via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped at 2 (v2.0.0). Default direction `'both'`. Filterable by `edge_types: EdgeType[]` and by `filter_properties` (strict equality, no operators). Memory-sink documents (`_memory/...`) surface only when transitively reachable from a user-note seed via at least one in-result inbound edge — per ADR-004 memory-namespace opacity rule. Unknown `seed_doc_ids` do NOT throw — they are returned in a `warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins on dedup; ties broken by `(seed_doc_id, edge_type, direction)`.
41
+ - **`cluster` MCP tool (Phase 4 plan 04-05 / GRA-02)** — Louvain community detection over the typed-edge graph, deterministic across runs via `seedrandom`. Accepts mutually-exclusive `query | seed_doc_ids`. Returns `{ok: true, communities: Cluster[]}` or `{ok: false, reason: 'too_many_nodes' | 'missing_input' | 'mutually_exclusive_input'}`. 5000-node hard cap with `force: true` override. Per-community `cluster_id` is the smallest member DocId by lexicographic order — deterministic naming independent of internal community index assignment. Hybrid-search dependency injected via `ClusterDeps.hybridSearch` closure to avoid the `src/graph/cluster.ts → src/search/hybrid.ts` circular import.
42
+ - **`search_hybrid({expand})` additive nested param (Phase 4 plan 04-04 / GRA-03)** — pass `expand: {hops: 1|2, direction?, edge_types?}` to auto-attach 1–2 hop typed-edge neighbors as `SearchHit.expansions?: CitationPacketWithVia[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged. Guard-and-short-circuit composition: when `expand` is omitted, the code path is byte-identical to Phase 3 (zero new DB reads, zero new JSON fields).
43
+ - **Typed-edge storage substrate (Phase 4 plan 04-01 / GRA-04)** — `edges` table (migration 011) with four edge types (`wikilink`, `mention`, `frontmatter-ref`, `hyperlink`) and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE` for proper NULL-as-distinct dedup. Chunked backfill from v1 `wikilinks` table (10k-row chunks, `INSERT OR IGNORE`, idempotent). The v1 `wikilinks` table is preserved alongside `edges` for v1 invariance (D-01); v3 cleanup is tracked.
44
+ - **Unified edge extractor (Phase 4 plan 04-02 / GRA-04 indexer)** — `extractAllEdges(vault, parsed, resolver): EdgeInput[]` extracts all four edge types in a single per-note parse pass. `MIN_MENTION_LEN = 4` (empirically validated against the Atlas Robotics fixture: 0 raw FPs over 62 notes). `FRONTMATTER_REF_ALLOWLIST` is a sealed 8-key `ReadonlySet<string>` (`assignee`, `owner`, `project`, `related`, `parent`, `child`, `attendees`, `superseded_by`). Mention candidate set built from `note_aliases` only; word-boundary uses `(?<![\w-])`/`(?![\w-])` so aliases containing `-`/`_` are matched as whole tokens. Paragraph-scope masking blanks fenced code, ATX headings, inline backticks, and `[[wikilink]]` spans (byte length preserved so line-offset lookups stay valid).
45
+ - **`AliasesQueries.listAll()` (Phase 4 plan 04-02)** — full alias inventory, sorted by `alias_norm ASC` for deterministic mention-regex compilation.
46
+ - **Additive `type: EdgeType` field on graph result rows (Phase 4 plan 04-01)** — `list_backlinks`, `list_forward_links`, `find_broken_links` results widen with `type`. `BacklinkEntry.relation` / `ForwardLinkEntry.relation` widen from the v2.0.0-pinned `"wikilink"` literal to the full `EdgeType` union. `assemble_dossier.linked_documents[].relation` and `get_document_bundle.{backlinks,forward_links}[].relation` widen the same way — the `PHASE-4-WIDEN` marker comments in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` (a Phase 3 v2.0.0 limitation) are now retired.
47
+ - **3 new eval YAMLs (Phase 4 plan 04-06 / GRA-05):**
48
+ - `evals/fixtures/v2-test-vault/_queries/expand.yaml` — 8 hand-curated queries over the Atlas Robotics live fixture covering all four edge types, mixed-type traversal at hops 1 and 2, `_memory/` opacity, and the unknown-seed warning path. All eight clear `min_precision >= 0.8` / `min_recall >= 0.8`.
49
+ - `evals/fixtures/v2-test-vault/_queries/search-hybrid-with-expand.yaml` — 3 composition queries exercising `search_hybrid({expand: {hops}, ...})` end-to-end.
50
+ - `evals/fixtures/v2-test-vault/_queries/cluster.yaml` — D-12 byte-snapshot of Louvain partition (cluster_id + sorted member DocIds per community); pins determinism across library upgrades + Node-minor versions.
51
+ - **Cross-adapter conformance for graph tools (Phase 4 plan 04-06)** — 6 new parameterized cases in `src/adapters/source/conformance.test.ts` run `expand()` + `cluster()` against both `obsidian-fs` and `stub` adapters via `src/graph/__test_helpers__/atlas-live-fixture.ts`. New harness fields (`graphSeedDocId`, `occludedMemoryDocId`, `reachableMemoryDocId`) carry adapter-specific reference DocIds.
52
+ - **Adapter seams (Phase 1, plans 01-01..06)** — `SourceConnector` / `DeliveryAdapter` / `ChangeFeed` interfaces under `src/adapters/` per ADR-002. `obsidian-fs` is the v2 reference implementation for all three. The seam shape is preserved across vault-content read, vault-content write, and change-event paths so future connectors (Notion, Logseq, …) drop in without touching `src/server.ts` or any v1 tool. (ADP-01, ADP-02, ADP-03)
53
+ - **Canonical v2 types** in `src/types.ts`: `Document`, `BlockNode`, `Edge`, `ChangeEvent`, `SourceHandle`, `MemorySink`, branded `DocId`, `WikilinkRef`. `Document.properties: Record<string, unknown>` subsumes both YAML frontmatter and future Notion typed properties. (ADP-04, ADP-05)
54
+ - **`src/adapters/registry.ts`** — adapter registry + sole minting point for branded `DocId`s via `parseDocId` / `formatDocId`. Future adapters register under their canonical scheme (`obsidian-fs://`, `notion-api://`, …). (ADP-05)
55
+ - **Stub-adapter conformance suite** — parameterized over `obsidian-fs` and `stub`, three test files (`src/adapters/{source,delivery,change-feed}/conformance.test.ts`). Lets future adapters validate themselves against the v2 contract before any production code lands. (ADP-13)
56
+ - **`scripts/lint-adapters.sh`** — POSIX shell CI gate enforcing ADR-002 Invariants I-1..I-6 + C-1 (Claude-leak) + I-5b (`obsidian://` literal). Wired into `npm run lint:check` and `.github/workflows/ci.yml`. (ADP-12)
57
+ - **`scripts/smoketest-non-claude.mjs`** — end-to-end smoketest against the real MCP SDK Client (identifies as `non-claude-smoketest`); CI-gated. Asserts 23 tools listed, all descriptions non-empty, `tools/call list_vaults` succeeds, `tools/call <bogus>` surfaces as error. (ADP-10)
58
+ - **`docs/v2/AGENT_AGNOSTIC_AUDIT.md`** — per-leak inventory of every Claude / Obsidian assumption in `src/`, with explicit `fixed-v2` / `mixed` / `deferred-v3` status + rationale per row. Cross-references CONCERNS.md, ADR-002, and the resolving Phase-1 plans. (ADP-11)
59
+ - **`record_observation` MCP tool** — write a labeled memory observation through a configured `MemorySink` with mandatory provenance (Phase 2 plan 02-04 / MEM-02). Sugar args (`claim`, `evidence`, `confidence`, `type`, `sink?`) pre-fill the contract-required keys; optional `properties: Record<string, unknown>` escape hatch merges LAST so callers can populate any contract-allowed extra (D-02).
60
+ - **`recall` MCP tool** — retrieve memory documents filtered by `min_confidence`, `types`, `max_age_days`, and `sink`; returns Phase 3-shaped citation packets `{doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties}` (Phase 2 plan 02-05 / MEM-03 / D-01). Hides `status: superseded` by default; sorts `observed_at` DESC with `mtime` tiebreak; truncates AFTER filter+sort.
61
+ - **`supersede` MCP tool** — forward-only mark of a memory document as superseded by a replacement, with mandatory non-empty `superseded_reason` (Phase 2 plan 02-04 / MEM-04 / D-03). Single OCC `delivery.update()` call; the replacement document is never touched (back-edge derivation deferred to Phase 4 graph layer).
62
+ - **`vault-memory://memory/sinks` MCP Resource** — lists configured memory sinks per vault with their handle, contract name, and default-flag (Phase 2 plan 02-06 / MEM-09).
63
+ - **`vault-memory://memory/stats` MCP Resource** — per-sink doc counts, `by_type` / `by_status` breakdown, last-write timestamp aggregated from the indexed `notes` table + the `write_audit` partial index (Phase 2 plan 02-06 / MEM-09). Polled-only; no `notifyResourceUpdated` in v2.0.0.
64
+ - **`is_memory_sink_write` column on `write_audit`** (migration v9) + partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` + optional `is_memory_sink_write` filter on the `audit_log` tool input schema (Phase 2 plan 02-06 / MEM-08).
65
+ - **`decomposeDocId` helper** on `src/adapters/registry.ts` for splitting `DocId`s into `{scheme, authority, resource}` parts. Re-uses `DOC_ID_PATTERN`; no second regex (Phase 2 plan 02-02).
66
+ - **`pathInSink` + `joinVaultPath` helpers** in `src/adapters/delivery/obsidian-fs/path.ts` — the SOLE licensed sink/vault `path.join` sites for Phase 2+ per ADR-002 I-3 (Phase 2 plan 02-02).
67
+ - **MemorySink runtime** — `parseMemorySinkHandle` (IIFE-closed brand mint), `MemorySinkRegistry` (sole resolver per ADR-004 §Resolution), `.memory-sink` sentinel mechanics in `src/adapters/delivery/obsidian-fs/sentinel.ts`, hardcoded `DEFAULT_MEMORY_V1` contract + YAML loader at `src/memory/contract/` (Phase 2 plan 02-02 / MEM-01, MEM-05, MEM-06).
68
+ - **5 net-new memory fixture docs** under `evals/fixtures/v2-test-vault/_memory/` including the A→B→C Spire-budget supersede chain (2026-04-23 → 2026-04-24 → 2026-04-26), plus net-new `type: hypothesis`, `type: decision`, and `confidence: uncertain` dimensions (Phase 2 plan 02-07 / MEM-10). Fixture grows from 15 → 20 docs.
69
+ - **`tests/fixtures/malformed-memory/`** tree with 5 deliberately-broken fixtures (`missing-observed-at`, `missing-source`, `invalid-confidence`, `supersede-no-target`, `source-agent-no-evidence`) for validator failure-mode unit tests; each carries `expected_reason` + `expected_key` frontmatter (Phase 2 plan 02-07 / MEM-10).
70
+ - **`docs/tools/audit_log.md`** documenting the new optional `is_memory_sink_write` filter on the `audit_log` tool. Documentation lives here rather than in the MCP tool description text — the description is byte-identical to Phase 1 to honor the v1 backwards-compat invariant (Phase 2 plan 02-06).
71
+ - **Section identity substrate (Phase 3 plan 03-01 / ASM-01, ASM-02, ASM-03, ASM-05, ASM-08)** — `sections` table (migration 010) materializing per-document outline trees with content-hash anchors per ADR-003 H-7 (`sha256_hex(NFC(heading_text) || "\n" || render_blocks_to_plain_text(blocks))`). Parent-pointer reconstruction; per-section `chunk_id_first` / `chunk_id_last` ranges; denormalized `notes.status` column with partial index (`notes_status`) for the SQL-level superseded filter. Backfill from existing v1 vaults runs in lockstep with the migration so user vaults gain sections + status on first upgrade without re-indexing.
72
+ - **`get_outline` MCP tool** — return a nested `OutlineNode[]` tree for a `doc_id`. Each node carries `{anchor, heading_path, heading_text, level, chunk_ids: string[], children}` per D-02. Anchors are stable across re-indexings of unchanged content. Doc-level response envelope is the 8-field citation packet shape (Phase 3 plan 03-02 / ASM-02, ASM-05).
73
+ - **`search_sections` MCP tool** — section-level retrieval that COMPOSES the v1 chunk-level hybrid pipeline (`hybridSearch`) with a chunk-to-section promotion step. Accepts `{query, limit, vaults?, recency_weight?, authority_weight?, half_life_days?, include_superseded?}`. The promotion strategy: inflate `topK = limit × 5`, run hybrid once, promote each chunk hit to its enclosing section, dedupe by `(note_id, anchor)`, score each section as `MAX(constituent chunk scores)`, sort DESC tie-broken by `chunk_id_first` ASC, slice to `limit`, hydrate into `SectionHit` packets carrying the 8-field citation floor plus `{anchor, score, chunk_ids, snippet?}`. Preamble (level-0) sections are dropped — only sections with a non-empty `heading_path` surface. (Phase 3 plan 03-03 / ASM-03, ASM-05)
74
+ - **`get_document_bundle` MCP tool** — one-call composite read for a `doc_id`: `{anchor, outline, backlinks, forward_links, recent_edits}`. The anchor is a full 8-field citation packet with optional ASM-06 extras (`status`, `superseded_by`); `outline` re-uses `buildOutlineTree` from `get_outline` (composition, not duplication); `backlinks` + `forward_links` carry citation packets plus `{property_snippet, relation}` (relation is `"wikilink"` in v2.0.0 — see Phase 4 widening note below); `recent_edits` ≤10 entries from `audit_log`, with `is_memory_sink_write` surfaced only when truthy. (Phase 3 plan 03-04 / ASM-01, ASM-05, ASM-06)
75
+ - **`search_hybrid` rescore signals (Phase 3 plan 03-05 / ASM-06, ASM-07, ASM-08, ASM-11) — additive Zod params**, all defaulting to v1-no-op so legacy callers see byte-identical responses:
76
+ - `recency_weight: number` (default `0`) — adds `recency_weight × exp(-age_days / half_life_days)` to each candidate's RRF score.
77
+ - `authority_weight: number` (default `0`) — adds `authority_weight × 1` for docs with `properties.authoritative === true`.
78
+ - `half_life_days: number` (default `30`) — recency decay half-life.
79
+ - `include_superseded: boolean` (default `false`) — when `false`, excludes `status: "superseded"` chunks at SQL level via the `notes_status` partial index. Zero per-candidate frontmatter parses on the default-hide path.
80
+ - **9 new optional `SearchHit` fields** (D-08, ASM-06): `doc_id`, `source_handle`, `heading_path`, `mtime`, `hash`, `display_url`, `status`, `superseded_by`, `properties` — all snake_case to align with the Phase 2 `CitationPacket` shape; all optional and JSON-omitted on the v1-default path.
81
+ - **Display-URL resolver seam** — `HybridSearchOptions.displayUrlFor` optional closure keeps the `obsidian://` literal mint site in the obsidian-fs source adapter per ADR-002 §I-5b.
82
+ - **Clock-injection seam** — `HybridSearchOptions.clock` optional closure mirrors the recall convention; tests pass a fixed clock for deterministic age math.
83
+ - **`assemble_dossier` MCP tool** — resolve a `{type, key}` pair to an anchor `Document` and walk backlinks into a structured dossier `{anchor, linked_documents, property_rollups: {linked_count, linked_types, status_distribution}, error}` (Phase 3 plan 03-06 / ASM-04, ASM-05, ASM-10). Strict `properties.type` match (D-03); `key` matches the candidate's `title` OR any entry in `properties.aliases` (D-04). Every linked document carries an 8-field citation packet (D-01) plus the `relation` field. **v2.0.0 limitation:** `linked_documents[].relation` is always `"wikilink"` because the v1 `wikilinks` table only stores wikilink edges; Phase 4 (GRA-04 typed edges) will widen the field to the full `Edge.type` enum (mention, frontmatter-ref, hyperlink, …) as an additive change. Search for `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` for the widening sites.
84
+ - **Source-neutrality conformance suite for assembly tools (Phase 3 plan 03-07 / ASM-12)** — `src/adapters/source/conformance.test.ts` extends its `describe.each` parameterization with a new "Assembly tools — $name" section over `[obsidian-fs, stub-assembly]`. Five canonical assertions per adapter row (10 total) cover `get_outline`, `assemble_dossier`, `search_sections`, `get_document_bundle`, and citation-packet shape parity with recall's output. Backed by `src/adapters/stub/assembly-fixture.ts` — an 8-document purpose-built `Document[]` covering aliases, authoritative, superseded, all four edge types, and a multi-section doc. Per RESEARCH §7 P/R evals (ASM-10 dossier, ASM-11 recency) run against the obsidian-fs adapter only; the stub fixture is purpose-built for contract conformance.
85
+ - **Recency eval fixture** — `evals/fixtures/v2-test-vault/_queries/recency.yaml` ships ASM-11's stale-vs-fresh scenario: two near-duplicate Atlas-1 status notes (`status_updates/atlas-1-old-update.md` mtime ≈6 months back, `status_updates/atlas-1-new-update.md` mtime ≈1 day back) with neutral + recency-weighted query pair. Mtime contract is documented inline; eval harness `fs.utimesSync`-injects mtimes in a `beforeAll` block (git does not preserve mtimes on checkout).
86
+ - **Compiled brief layer (Phase 5, ADR-005)** — briefs are first-class `Document`s in `_memory/_briefs/` with chunk-level `source_hashes` provenance; compile-time wikilink emission (D-11), auto-supersede on target collision (D-12), and forward-only supersede chain. Writes route through `DeliveryAdapter` so the MEM-05 memory-namespace invariant is un-bypassable by construction. Closes the "agents rediscover 85% of context every run" failure mode that motivated the v2 program. See `docs/v2/PHASE-5-SIGN-OFF.md` + `docs/v2/adr/005-brief-compile-strategy.md`.
87
+ - **2 new MCP tools (Phase 5)** — `compile_brief`, `get_brief`. Tool count: 32 → 34 (additive only; the 32 Phase 4 entries verified byte-identical per-name in `evals/v1-baseline/tools-list.snapshot.json`; strict-equality snapshot test re-enabled in `baseline.test.ts`).
88
+ - **`vault-memory://briefs` MCP Resource (Phase 5 / BRF-09)** — pure-read discovery surface with optional `?target=<pattern>` substring filter. NOT a Tool; Resources do not count toward the REL-08 tool budget. `readListBriefs` is a closure over `MemorySinkRegistry` + `VaultManager` + `SourceConnector` and carries zero `fs` / `path` / `gray-matter` / `chokidar` imports (enforced by `scripts/lint-adapters.sh` + an explicit assertion in `src/brief/resources.test.ts` Test 10).
89
+ - **Brief staleness daemon (Phase 5 / BRF-05..08)** — `BriefStalenessDaemon` subscribes to `ChangeFeed.subscribe()`, runs a startup full scan against `brief_sources.listBriefDocIds()` (BRF-07; cursor in `daemon_state`), and preserves brief→source links across rename events via a 5-second pending-deletes grace window (BRF-08). Single-owner via `~/.vault-memory/locks/<vault>.lock` (`fs.open('wx')` + PID liveness); on lock contention emits `daemon_already_owned` and returns `{acquired: false}` without subscribing.
90
+ - **`default-brief-v1` MemoryContract (Phase 5)** — the provenance frame required by every brief `Document`. Required properties: `source: "agent"`, `confidence: "inferred"`, `evidence: parsedSourceDocIds`, `status: "active"`, `observed_at`, `superseded_by`, `type: "brief"`, `target`, `purpose`, `compiled_from`, `compiled_at`, chunk-level `source_hashes` map, and `model` audit attribution. The `_memory/_briefs/` sink binds to this contract at registration time.
91
+ - **ChunkId brand + content-stable fragment helpers (Phase 5)** — `parseChunkId`, `formatChunkId`, `decomposeChunkId`, `computeChunkHash`, `computeChunkIdFragment` in `src/chunker/chunk-id.ts`. D-04 content-derived 7-char fragments are essential for the v3 multi-user story (every user recomputes identical fragments over the same source text).
92
+ - **`OllamaClient.chat()` (Phase 5)** — `/api/chat` route alongside `/api/embed`; the first non-embedding LLM call in vault-memory history. Governed by ADR-005. No remote LLM SDK is bundled; the D-10 ladder probes MCP Sampling first (`getClientCapabilities().sampling`), then local Ollama via `[brief.ollama]` config, then falls back to `args.prepared_text` for deterministic CI / non-LLM contracts.
93
+ - **3 new eval YAMLs (Phase 5 / BRF-10, BRF-11):**
94
+ - `evals/fixtures/v2-test-vault/_queries/briefs-curated.yaml` — curated 20-document Atlas Robotics scenario; modifying one source flips the brief stale within one change-feed cycle.
95
+ - `evals/fixtures/v2-test-vault/_queries/briefs-staleness-stub.yaml` — BRF-11 cross-adapter parametric run (`adapters: [obsidian-fs, stub]`) proving brief-layer source-neutrality.
96
+ - `evals/fixtures/v2-test-vault/_queries/briefs-from-cluster.yaml` — cluster-fed compile scenario.
97
+ - **Cross-adapter conformance for brief tools (Phase 5)** — `src/adapters/source/conformance.test.ts` gains a `compile_brief + staleness daemon (BRF-11 source-neutrality)` describe block: 4 conformance test cases × 2 SourceConnector adapters = 8 test runs. Covers update flips stale, startup scan recovers missed events, delete past grace-window marks stale, and rename within grace-window preserves brief→source link.
98
+ - **Obsidian plugin (Phase 7, ADR-007)** — `plugin/` Obsidian community-plugin package (`manifest.json` v2.0.0, id=`vault-memory`, minAppVersion 1.5.0) with sideload-capable layout. `npm run build` emits a 1.9 MB `plugin/main.js`. Ships behind `[plugin] enabled = false` server-side; v1 baseline `tools/list` snapshot remains byte-identical when the flag is unset (FND-10 still passes). See `docs/v2/plugin/README.md` + `docs/v2/adr/007-contract-editor.md`.
99
+ - **Variant C three-pane contract editor (Phase 7 / CAN-01..CAN-07, CAN-10)** — palette + Svelte Flow canvas + Zod-derived inspector for editing Phase 6 task contracts. Canvas wires to `@xyflow/svelte` (MIT, license verified in ADR-007 — rescoped from the original jsoncanvas fork). `registerView('vault-memory-contract-editor')` + `registerExtensions(['contract'])` so opening a `.contract` file launches the editor.
100
+ - **`.contract` JSON envelope + lossless round-trip codec (Phase 7 / CAN-02, CAN-03, CAN-07)** — custom JSON format (`vmFormatVersion: 1`) with a pure-TS round-trip codec (`plugin/src/codec/contract-codec.ts`). Round-trip fixed-point pinned to 3rd/4th-emission byte identity across 4 fixtures (19/19 round-trip tests passing). Covers every ADR-006 field: `version`, `name`, `description`, `inputs`, `sources`, `sinks`, `assembly`, `output_shape`, `write_back`, `required`, `mcp_clients`.
101
+ - **3 reference `.contract` files (Phase 7 / CAN-06)** — `examples/contracts/meeting-prep.contract`, `project-status.contract`, `code-review-brief.contract`, each pinned to its Phase 6 YAML twin via 3 fixture round-trip tests.
102
+ - **6 new plugin-control MCP tools (Phase 7) — gated by `[plugin] enabled = true`, default OFF.** `set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`, `trigger_reindex`, `suppress_contract_write`. Default-OFF tool count: unchanged from Phase 6 (37); when the plugin flag is enabled the surface grows to 43. The v1-baseline tools-list snapshot remains byte-identical against the default-OFF surface — verified by `evals/v1-baseline/baseline.test.ts` "matches the pinned snapshot exactly" + "preserves the 23 v1 baseline tool names byte-identical".
103
+ - **Electron safeStorage-backed secrets (Phase 7 / PLG-02)** — `plugin/src/services/safe-storage.ts` wraps `window.electron.safeStorage` (`encryptString`/`decryptString`); only ciphertext lands in `data.json`. Secrets referenced by name from `[contracts.mcp_clients]` config via `${secret:name}` placeholders (regex `\$\{secret:([a-z][a-z0-9_-]{2,63})\}`). Connector-resolver test suite has 11 passing tests pinning the placeholder grammar + resolution path.
104
+ - **Plugin chrome — settings, reindex, stats, connectors panels (Phase 7 / PLG-01, PLG-03, PLG-04, PLG-05)** — `plugin/src/chrome/settings-tab.ts` (Ollama URL, model, indexer config; persisted via `loadData`/`saveData`; hot-swap via `set_runtime_config`), `reindex-panel.svelte` + controller (manual trigger with MCP `notifications/progress` feedback; SuppressionSet-aware), `stats-panel.svelte` + controller (read-only stats via `get_runtime_stats`), and `connectors-panel.svelte` (list/add/remove peer MCP clients, secrets-by-name). 42 chrome unit tests passing (7+8+7+12+8).
105
+ - **Hash-aware `SuppressionSet.consume(path, hash)` extension (Phase 7 / CAN-08)** — the Phase 1 `SuppressionSet` widens to accept a content hash so the contracts loader can short-circuit re-validation when the watcher fires on the plugin's own write. `src/contracts/loader.ts:242` calls `consume(resource, hash)` before reloading; `src/plugin-tools/suppress-contract-write.ts:109` registers the `(path, hash)` pair from the plugin BEFORE the write. Three dedicated loader tests cover the suppression path; regression watcher tests still pass (10/10 ObsidianFsChangeFeed + 6/6 VaultWatcher).
106
+ - **`vm-install` + `vm-update` Claude Code skills (Phase 7 / CAN-09 distribution half)** — one-liner curl-pipe install / upgrade for the plugin, with SHA-256 verification of the downloaded tarball. Idempotent. `bash skills/vm-install/setup.test.sh` 9/9 pass (fresh install + idempotent no-op); `bash skills/vm-update/update.test.sh` 12/12 pass (no-op + upgrade w/ SHA-256 + re-run). `RELEASE_URL_PLACEHOLDER` resolution is bookkept for the v2.0.0 GitHub Release publish (ROADMAP Phase 8 carryover).
107
+ - **6 plugin docs under `docs/v2/plugin/` (Phase 7 / CAN-09 docs half)** — `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section at the top of the repo README explains how to enable the flag and what the editor + chrome do.
108
+
109
+ ### Changed
110
+
111
+ - **Tool surface count: 30 → 32** (Phase 4, additive only). Two new graph tools added: `expand`, `cluster`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries + 7 Phase 3 entries remain content-identical (verified per-name; the strict-equality snapshot test `baseline.test.ts` "matches the pinned snapshot exactly" — which Plan 04-03 had `.skip`'d pending this regen — is re-enabled and green). The diff against the Phase 3 snapshot is purely additive: two new tool entries appended (before `assemble_dossier` to preserve insertion order at the end of `TOOLS`); one optional nested property added to `search_hybrid.inputSchema.properties` (`expand: {hops, direction?, edge_types?}`); `search_hybrid.description` widened additively to mention the new option.
112
+ - **`search_hybrid` accepts an additive nested `expand?: {hops: 1|2, direction?, edge_types?}` parameter (Phase 4 plan 04-04 / GRA-03).** Defaults to v1-no-op when omitted; legacy callers see byte-identical responses. When supplied, each `SearchHit` gains an `expansions?: CitationPacketWithVia[]` field carrying the per-hit typed-edge neighborhood; ranking is unchanged. Per-vault BFS isolation is enforced inside `expand()` (T-04-04-02 mitigation). Failures inside `expand()` are silently swallowed — same posture as the reranker fallback — so a corrupted graph traversal cannot break search.
113
+ - **Graph result rows widen from `relation: "wikilink"` to `relation: EdgeType` (Phase 4 plan 04-01).** `assemble_dossier.linked_documents[].relation`, `get_document_bundle.{backlinks,forward_links}[].relation`, and the v1 `BacklinkEntry`/`ForwardLinkEntry` `relation` fields now emit the actual edge type the indexer recorded. The Phase 3 known-limitation pinning these to `"wikilink"` is closed; the `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` have been retired. This is strictly an additive widening — old callers that only checked for `"wikilink"` still receive that value when the underlying edge is a wikilink.
114
+ - **Indexer dual-writes to `wikilinks` + `edges` tables (Phase 4 plans 04-01, 04-02).** Both indexer write paths (`src/indexer/single.ts` body-hash fast path + full re-embed branch; `src/indexer/indexer.ts` full index path) call `writeAllEdges` after parsing each note, threading the `WikilinkResolver` for cross-note target resolution. The v1 `wikilinks` table is preserved for D-01 v1 invariance; v3 cleanup will drop it once no v1 tool reads from it.
115
+ - **`@modelcontextprotocol/sdk` bumped to `^1.29.0`**; tool registration migrated from the v1 low-level `Server` + `setRequestHandler` pattern to `McpServer` + `server.registerTool()` × 23. Raw JSON Schema literals flow directly from `src/tool-registry.ts` (workaround for SDK#1143 / Zod-4 description-drop, although the issue is empirically MOOT in SDK 1.29). (ADP-08)
116
+ - **`zod` bumped to `^4.4.3`**. Refinements and `errorMap` swept per the Zod 4 migration guide; Standard Schema wiring intact. (ADP-09)
117
+ - **Default `client_id` for write / update / delete** is now captured from MCP `InitializeRequest.params.clientInfo.name` via a lazy closure (`getClientId()` in `src/server.ts`), falling back to `"unknown"`. Removes the v1 hardcoded `"claude-code"` default that misidentified every non-Claude write in the audit log. (D-02)
118
+ - **`obsidian://` display-URL minting** moved from `src/server.ts:obsidianUrl()` (deleted) into `SourceConnector.formatDisplayUrl(id)` on the adapter. The `obsidian://open?vault=…&file=…` URL is now adapter-published; future adapters mint their own scheme. (D-01)
119
+ - **README rewritten** to lead with "any MCP-aware agent" framing in the first 20 lines. Equal billing for Claude Code / Claude Desktop / ChatGPT Custom Connectors / MCP Inspector / generic clients. Obsidian framed as the v2 source connector, not the sole consumer. (ADP-14)
120
+ - **`src/cli.ts` user-facing strings** swept for Claude-leak — `"Open ${path} in Claude Code"` → `"Open ${path} in your MCP-aware client"`. (D-02 follow-through)
121
+ - **`WriteConflict.reason` discriminated union extended** (additively, backwards-compatible) with 7 new codes: `missing_provenance`, `invalid_provenance`, `supersede_mismatch`, `agent_write_outside_sink`, `non_agent_write_inside_sink`, `sentinel_missing`, `sink_write_blocked`. Optional envelope fields added: `sinkName`, `key`, `observedValue`, `suggestion`. The 3 Phase 1 reason codes (`hash_mismatch`, `permission_denied`, `not_found`) are unchanged (Phase 2 plan 02-03 / MEM-05, MEM-07, MEM-11).
122
+ - **v1 `write_note`, `update_frontmatter`, `delete_note` now refuse memory-sink-resolved targets** at the tool entry-point with `sink_write_blocked` + actionable `suggestion` text (`record_observation` for writes/updates, `supersede` for deletes). Defense-in-depth on top of the centralized `DeliveryAdapter.write()` chokepoint (Phase 2 plan 02-03b / MEM-07). When the `MemorySinkRegistry` is not wired (Phase 1 fixture-test path), v1 tool behavior is byte-identical to Phase 1.
123
+ - **`audit_log` tool gains optional `is_memory_sink_write` input filter** and adds `is_memory_sink_write: boolean` to each returned row. The MCP `description` text is byte-identical to Phase 1 — the new capability is documented in CHANGELOG and `docs/tools/audit_log.md` only (Phase 2 plan 02-06 / MEM-08).
124
+ - **Server bootstrap order** is now `loadConfig → manager.openAll → registerMemorySinks (writes sentinels) → server.connect → startCatchupAndWatchers (fire-and-forget)`. Sentinels are provisioned BEFORE the catch-up indexer walks the fixture, so the registry's `findSinkContaining` enclosure check is hot for the first incoming write (Phase 2 plan 02-03b). A new exported `BootstrapPhase` type + optional `onPhase` callback on `serve(options?)` make the bootstrap order observable for testing.
125
+ - **Assembly DocId minting derives scheme from `SourceConnector.handle`** (Phase 3 plan 03-07 fix). Pre-03-07 both `assembleDossier` and `getDocumentBundle` hardcoded `formatDocId("obsidian-fs", …)` when constructing linked-document / anchor DocIds. The 03-07 ASM-12 conformance suite (parameterized over obsidian-fs + stub adapters) surfaced this as a hard test failure on the stub-assembly row. The fix introduces `schemeFromSource(SourceConnector): string` and derives the scheme dynamically; `dossier.ts`'s sort-key helper is renamed `noteDocIdString → noteSortKey` and pinned to a fixed `vault://` prefix so sort order stays adapter-identical. No user-visible change for obsidian-fs callers (their scheme has always been `obsidian-fs://`); the fix unblocks future non-Obsidian sources.
126
+ - **Tool surface count: 26 → 30** (additive only). Four Phase 3 assembly tools added: `get_outline`, `search_sections`, `get_document_bundle`, `assemble_dossier`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries at slots 0–22 remain byte-identical (pinned by the existing `baseline.test.ts` "preserves the 23 v1 baseline tool names byte-identical" assertion). The diff against the Phase 2 snapshot is purely additive: four new tool entries appended; one optional field added to `search_hybrid.inputSchema.properties` (the four rescore params).
127
+ - **Tool surface count: 32 → 34** (Phase 5, additive only). Two new brief tools added: `compile_brief`, `get_brief`. The `list_briefs` discovery surface lives on MCP Resources (`vault-memory://briefs`) and does NOT count toward the tool budget — same precedent as the Phase 2 `vault-memory://memory/*` Resources. The 32 Phase 4 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (verified per-name); the diff is purely additive — two new tool entries appended.
128
+ - **Server bootstrap order extended (Phase 5)** — after `registerMemorySinks` and before `server.connect`, the bootstrap now (a) registers the `_memory/_briefs/` sink bound to the `default-brief-v1` contract and (b) starts `BriefStalenessDaemon` per vault. Daemon `start()` is fire-and-forget: a lock-contention failure does NOT block server startup — the server proceeds and `compile_brief` continues to work; only the staleness re-check is offline.
129
+ - **`SuppressionSet.consume()` accepts an optional `hash` argument (Phase 7 / CAN-08, additive).** Phase 1 callers that pass only a path see byte-identical behavior. When the plugin write path passes `(path, hash)`, the contracts loader at `src/contracts/loader.ts:242` matches both axes before short-circuiting — so a benign re-emit of the same canonical bytes is suppressed, but a divergent write still triggers reload.
130
+ - **Default-OFF plugin tool gating (Phase 7).** The 6 new plugin tools are wired through a config-time guard in `src/server.ts` so `tools/list` does NOT include them unless `[plugin] enabled = true`. The default-OFF surface keeps the v1 baseline snapshot byte-identical and keeps the REL-08 budget conversation focused on the user-facing v2 tools.
131
+
132
+ ### Dependencies
133
+
134
+ - **Phase 4 (plan 04-05 / GRA-02)** — pure-JS ESM, all MIT, all manually provenance-verified per Phase 4 RESEARCH §Package Legitimacy Audit:
135
+ - `graphology ^0.26.0` — graph data structure for the Louvain cluster wrapper.
136
+ - `graphology-communities-louvain ^2.0.2` — Louvain modularity-maximizing community detection.
137
+ - `seedrandom ^3.0.5` — deterministic seeded PRNG for the Louvain `rng` option.
138
+ - `@types/seedrandom ^3.0.8` (dev) — TypeScript types.
139
+ - No native bindings; no tsup `external` additions needed. ESM bundle grew from 376 KB to 392 KB (+13 KB).
140
+ - **Phase 7 plugin (ADR-007)** — added under `plugin/package.json` only; the server bundle is unaffected:
141
+ - `@xyflow/svelte` — Svelte Flow canvas renderer for the Variant C editor (MIT, license verified in ADR-007).
142
+ - `svelte`, `esbuild`, `vitest` — plugin-only build/test toolchain. None ship into the server `dist/`.
143
+
144
+ ### Migration
145
+
146
+ - **MIGRATION_011 — `edges` table + chunked backfill from `wikilinks` (Phase 4 plan 04-01).** Function-style migration mirroring `runMigration008`. Creates the `edges` table with `CHECK(type IN ('wikilink', 'mention', 'frontmatter-ref', 'hyperlink'))` and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE(anchor, '')` + `COALESCE(line_number, 0)` for proper NULL-as-distinct dedup. Backfills wikilink rows from the v1 `wikilinks` table in 10k-row chunks via `INSERT OR IGNORE` — idempotent, re-runnable, safe to interrupt. v1-shaped DBs migrate cleanly on first server start after upgrade; the v1 `wikilinks` table is preserved alongside the new `edges` table for D-01 v1 invariance. v3 cleanup will drop `wikilinks` once no v1 tool consumes it.
147
+ - **`notes.doc_uri` dual-column staging (Strategy A — additive, plan 01-02):**
148
+ - **MIGRATION_007** (additive) — adds nullable `doc_uri TEXT` column to `notes` + `idx_notes_doc_uri` index. Backwards-compatible; existing rows have `doc_uri IS NULL` post-migration.
149
+ - **MIGRATION_008** (function-style backfill) — sets `doc_uri = 'obsidian-fs://<vault>/' || path` for every row where `doc_uri IS NULL`. Idempotent; safe to re-run.
150
+ - Path stored **un-encoded** in the column; percent-encoding happens only at `formatDisplayUrl()` time (per ADR-002 §URL semantics).
151
+ - **No user action required.** Migrations run automatically on first server start after upgrade. SQLite transaction rollback is the safety net; no pre-migration backup is performed in v2 (deferred to Phase 8 per CONCERNS §"No Pre-Migration DB Backup"). (ADP-07)
152
+ - **v1 tool surface preserved byte-for-byte.** All 23 tool names, input schemas, output envelopes, and descriptions are unchanged. `evals/v1-baseline/tools-list.snapshot.json` regenerated under SDK 1.29 with zero diff against the Phase-0 baseline (W6 human-verify checkpoint passed in plan 01-06 Task 06).
153
+ - **MIGRATION_010 — sections table + notes.status column + section backfill (Phase 3 plan 03-01).** Three ordered steps in one transaction: (a) `CREATE TABLE sections` with 3 indexes, (b) `notes.status` column added + partial index `notes_status WHERE status IS NOT NULL`, (c) backfill `notes.status` from `json_extract(frontmatter, '$.status')` AND derive section rows for every indexed note via `markdownToSectionBlocks → extractSections`. Idempotent; re-applying is a no-op. Anchor-equivalence guarantee: backfilled section anchors match a fresh re-index byte-for-byte (the indexer + backfill share `src/chunker/headings.ts:extractHeadings` as the canonical heading source). v1-shaped DBs migrate cleanly without re-indexing.
154
+ - **`write_audit` migration v9** (function-style, idempotent) adds `is_memory_sink_write INTEGER NOT NULL DEFAULT 0` + a partial index `idx_write_audit_memory_sink ON (is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` (Phase 2 plan 02-06 / MEM-08). All Phase 1 audit rows backfill to `false`; new audit rows derive the flag from `WriteOptions.sink !== undefined` at the `ObsidianFsDelivery.write/update/delete` facade. The function-style migration runs `PRAGMA table_info` first so fixture tests that rewind `user_version` continue to pass.
155
+ - **v1 tool surface grows from 23 → 26** with the three Phase 2 memory tools (`record_observation`, `recall`, `supersede`). The 23 v1 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (pinned by a new `baseline.test.ts` assertion). The only diff on v1 tools is one optional input field added to `audit_log.inputSchema` (`is_memory_sink_write`); the v1 description text is byte-identical.
156
+ - **MIGRATION_013 — `chunks.chunk_id_fragment` + `brief_sources` + `daemon_state` (Phase 5 plan 05-01).** Three additive steps in one transaction: (a) `chunks.chunk_id_fragment` TEXT column added (`noUncheckedIndexedAccess` consumers see the new column as `T | undefined` until populated by the next index run); (b) `brief_sources` table (D-06 reverse-index — `(brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)`); (c) `daemon_state` table (per-vault change-feed cursor + `last_full_scan` timestamp). Idempotent; re-applying is a no-op. v1-shaped DBs migrate cleanly on first server start after upgrade.
157
+ - **Phase 5 brief artifacts are NOT a server schema migration.** Brief `Document`s land in `_memory/_briefs/` via the standard `DeliveryAdapter.write()` path; the `default-brief-v1` MemoryContract is registered at bootstrap (not persisted in SQLite). Users who never call `compile_brief` see zero new files on disk.
158
+ - **Phase 7 plugin is NOT a server schema migration.** No new SQLite migrations land for Phase 7. The plugin stores its own state in `data.json` (loadData/saveData) inside the Obsidian vault's plugin directory; secrets land as Electron safeStorage ciphertext only. Users who never enable the plugin flag see zero new server-side files.
159
+
160
+ ### Documentation
161
+
162
+ - Relocate ADRs 001–004 from `docs/dev/` (gitignored) to public `docs/v2/adr/`; amend each with Invariants + Examples sections covering both `obsidian-fs://` and `notion-api://` worked examples (FND-01, FND-04).
163
+ - ADR-003 amended with explicit hash-semantics pseudocode (RFC 8785 JCS, NFC normalisation, LF line endings, IEEE-754 number canonicalization) + chunk-level `source_hashes` schema (FND-02).
164
+ - ADR-004 amended: folder-default `MemorySink` is the only code path; separate-vault is config-only via `[memory] sink = "@…"` (FND-03). `.memory-sink` sentinel mandated.
165
+ - ADR-004 amended: underscored PropertyBag keys, confidence enum aligned to [direct, inferred, uncertain], superseded_reason field added (Phase 2 plan 02-01).
166
+ - Publish `docs/v2/ARCHITECTURE.md` (L0–L4 layer model + responsibility map), `docs/v2/MEMORY_CONTRACT.md` (provenance property contract on `Document.properties`), `docs/v2/AGENT_AGNOSTIC.md` (MCP-canonical client stance) (FND-05/06/07).
167
+ - Eval fixture vault `evals/fixtures/v2-test-vault/` — "Atlas Robotics" narrative; 56 notes across `projects/`, `meetings/`, `people/`, `decisions/`, `references/`; 15-document `_memory/` subset; 7 hand-labeled `_queries/*.yaml` (FND-08).
168
+ - v1-baseline regression suite `evals/v1-baseline/` — `tools-list.snapshot.json` pin for `tools/list` (23 tools), 11 per-tool semantic-floor YAMLs, `baseline.test.ts` vitest runner with `.todo` placeholders for Phase 1 precision/recall (FND-09/10).
169
+ - CI gates `scripts/check-fixture-privacy.sh` + `scripts/lint-no-telemetry.sh` + `.github/workflows/ci.yml` running `npm run lint:check && npm test` on every PR and push to `main` (FND-11/12 + D-21).
170
+ - ADR index `docs/v2/adr/README.md` listing 4 Accepted ADRs + 14 Open ADR stubs for v3 / Phase 10 follow-ups, including a Deferred-v3 section for adversarial-review findings (FND-13).
171
+ - Adversarial review `docs/v2/adr/ADVERSARIAL-REVIEW.md` — 10 findings against ADRs 001–004 raised by a fresh-context advisor; 6 Amended in Phase 0 (ADR-001 I-6, ADR-002 `DocumentRef.hash` contract + `hashProtected` enum, ADR-003 H-6), 4 Deferred-v3 to Phase 10 / Notion connector (FND-04 + FND-14 sign-off).
172
+ - Sign-off artifact `docs/v2/SIGN-OFF.md` — FND-01..14 checklist with resolving commit SHAs; PR approval is the FND-14 audit trail per D-17.
173
+ - Internal: extract `TOOLS` constant from `src/server.ts` to `src/tool-registry.ts` so `evals/v1-baseline/dump-tools.mjs` can produce the pinned snapshot without spinning the full MCP server (Phase 0 Assumption A5, no external behavior change).
174
+ - Phase 5 sign-off artifact `docs/v2/PHASE-5-SIGN-OFF.md` — BRF-01..BRF-11 traceability table + the five Phase 5 ROADMAP success criteria with disposition (`✅ MET` per criterion) + REL-08 hand-off plan to Phase 8.
175
+ - ADR-005 `docs/v2/adr/005-brief-compile-strategy.md` (Accepted) — governs the D-10 LLM strategy ladder and forbids bundling any remote LLM SDK in the server.
176
+ - Phase 7 plugin docs under `docs/v2/plugin/`: `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section added to the repo root README explaining how to flip `[plugin] enabled = true` and what the editor + chrome surfaces do.
177
+ - ADR-007 `docs/v2/adr/007-contract-editor.md` (Accepted, 2026-05-19) — rescopes the visual editor from a jsoncanvas fork to `@xyflow/svelte` (MIT, license verified inline in the ADR).
178
+
179
+ ### Deprecated (Phase 8 / REL-08)
180
+
181
+ The following five v1 tools are deprecated in v2.0.0 and promoted to MCP Resources. Each tool remains callable through the v2.x line for backwards compatibility; removal is scheduled for v3.0.0. The Resource URI is the canonical replacement.
182
+
183
+ - **`list_vaults`** → `vault-memory://vaults` (cross-vault discovery Resource). The tool remains callable through v2.x; removal scheduled for v3.0.0.
184
+ - **`list_models`** → `vault-memory://models/{vault}` (per-vault embedding-model inventory). The tool remains callable through v2.x; removal scheduled for v3.0.0.
185
+ - **`recent_notes`** → `vault-memory://recent/{vault}` (per-vault mtime-DESC recent notes). The tool remains callable through v2.x; removal scheduled for v3.0.0.
186
+ - **`vault_stats`** → `vault-memory://stats/{vault}` (per-vault note count + top tags + top frontmatter keys + last index run). The tool remains callable through v2.x; removal scheduled for v3.0.0.
187
+ - **`list_backlinks`** → `vault-memory://backlinks/{vault}/{+docId}` (per-document backlink listing). The URI uses RFC 6570 reserved expansion: `{+docId}` allows `/` in the variable value so multi-segment `docId`s like `obsidian-fs://my-vault/notes/sub/file.md` parse correctly. The tool remains callable through v2.x; removal scheduled for v3.0.0.
188
+
189
+ ## [2.0.0-rc.2] — 2026-05-19
190
+
191
+
192
+ ### Fixed
193
+
194
+ - **Migration 010 — `UNIQUE constraint failed: sections.note_id, sections.anchor`** crash blocking v0.9.x/v1.0.0 → v2.0.0-rc.1 upgrade for any user whose vault contains heading-only sibling sections (e.g. template scaffolds with repeated `## TODO` headings whose body slot is empty). `extractSections` produces identical content-hash anchors for sections with identical `(heading_text, body)` pairs, and the `INSERT INTO sections` was plain so the second sibling aborted the migration transaction — leaving every CLI command unreachable for every configured vault until the bad DB was removed from `config.toml`. `backfillSectionsFromChunks` now uses `INSERT OR IGNORE`; on collision the surviving row's id is looked up and reused so the `insertedIds` slot still resolves later children's `parent_id` correctly. See `ISSUE-migration-010-duplicate-anchor.md`.
195
+
196
+ ### Added
197
+
198
+ - **Task Contract DSL (Phase 6, ADR-006)** — declarative YAML contracts under `_contracts/<name>.yaml`, addressable by name, instantiable via MCP, with handle-based source/sink portability. Contracts use a closed assembly verb enum (11 baseline + `literal` + `mcp://<server>/<tool>` peer extension), `{{template}}` step composition, JSON-Schema-with-`$ref` inputs, MemorySink-only sinks (un-bypassable per D-A4c), and ChangeFeed hot reload (D-LOAD). See `docs/v2/PHASE-6-SIGN-OFF.md` + `docs/v2/adr/006-task-contract-dsl.md`.
199
+ - **3 new MCP tools (Phase 6)** — `describe_contract`, `instantiate_contract`, `register_contracts_as_tools`. Tool count: 34 → 37 (additive only; v1-baseline preserved byte-identical).
200
+ - **2 new MCP Resources (Phase 6)** — `vault-memory://contracts/{vault}` (CON-04) lists contracts available in a vault; `vault-memory://contract-verbs/{vault}` (D-A2b) lists baseline + custom `mcp://` verbs with invocation counts. Resources do NOT count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.
201
+ - **3 reference contracts (Phase 6)** under `evals/fixtures/v2-test-vault/_contracts/`: `meeting-prep`, `project-status`, `code-review-brief`. Plus `smoketest-trivial` for the CON-09 non-Claude smoketest path (literal-only assembly; no LLM needed in CI).
202
+ - **`contract_audit` table (Phase 6, migration 014)** — orchestration audit substrate; one row per assembly step (kind=`contract_step`) and one row per load failure (kind=`contract_load_error`). Payload-free per Invariant C-5 (peer-MCP outputs may carry sensitive data; we never capture them). Feeds `aggregateVerbUsage(vault)` for the D-A2b promotion signal.
203
+ - **`[contracts]` config block (Phase 6)** — `auto_register_tools: bool = false`, `tool_prefix: string = "vm_"`, `step_timeout_seconds: number = 30`, `defaults: Record<string, string> = {}`, `mcp_clients: Record<string, {command, args?, env?}> = {}`.
204
+ - **`instantiate_contract` write_back chokepoint (Phase 6)** — routes through `DeliveryAdapter.write()` so the MEM-05 invariant is un-bypassable by construction. Sink overrides MUST resolve through `MemorySinkRegistry.resolveMemorySink()` before write; otherwise `{ok:false, reason:"sink_override_not_a_memory_sink"}`.
205
+ - **CON-10 stub-parity proof (Phase 6)** — `src/adapters/source/conformance.test.ts` gains a `contracts stub-parity (CON-10)` describe block proving the same contract produces structurally identical bundles across obsidian-fs and stub source connectors.
206
+ - **CON-09 non-Claude smoketest extension (Phase 6)** — `scripts/smoketest-non-claude.mjs` spawns the server against a temp HOME with the fixture vault, asserts the three contract tools surface, calls `describe_contract` + `instantiate_contract` + reads the `list_contracts` Resource, and exits 0.
207
+ - **`ObsidianFsSource` widened to enumerate `_contracts/*.yaml` (Phase 6)** — non-recursive walk added via `scanContractFiles`; `scanVault` unchanged so the indexer's `.md`-only contract is preserved. `readDocument` handles YAML files by returning raw text as a single paragraph block (bypasses `parseNote`'s markdown assumptions).
208
+ - **`expand` MCP tool (Phase 4 plan 04-03 / GRA-01)** — typed-edge BFS retrieval primitive. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying `via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped at 2 (v2.0.0). Default direction `'both'`. Filterable by `edge_types: EdgeType[]` and by `filter_properties` (strict equality, no operators). Memory-sink documents (`_memory/...`) surface only when transitively reachable from a user-note seed via at least one in-result inbound edge — per ADR-004 memory-namespace opacity rule. Unknown `seed_doc_ids` do NOT throw — they are returned in a `warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins on dedup; ties broken by `(seed_doc_id, edge_type, direction)`.
209
+ - **`cluster` MCP tool (Phase 4 plan 04-05 / GRA-02)** — Louvain community detection over the typed-edge graph, deterministic across runs via `seedrandom`. Accepts mutually-exclusive `query | seed_doc_ids`. Returns `{ok: true, communities: Cluster[]}` or `{ok: false, reason: 'too_many_nodes' | 'missing_input' | 'mutually_exclusive_input'}`. 5000-node hard cap with `force: true` override. Per-community `cluster_id` is the smallest member DocId by lexicographic order — deterministic naming independent of internal community index assignment. Hybrid-search dependency injected via `ClusterDeps.hybridSearch` closure to avoid the `src/graph/cluster.ts → src/search/hybrid.ts` circular import.
210
+ - **`search_hybrid({expand})` additive nested param (Phase 4 plan 04-04 / GRA-03)** — pass `expand: {hops: 1|2, direction?, edge_types?}` to auto-attach 1–2 hop typed-edge neighbors as `SearchHit.expansions?: CitationPacketWithVia[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged. Guard-and-short-circuit composition: when `expand` is omitted, the code path is byte-identical to Phase 3 (zero new DB reads, zero new JSON fields).
211
+ - **Typed-edge storage substrate (Phase 4 plan 04-01 / GRA-04)** — `edges` table (migration 011) with four edge types (`wikilink`, `mention`, `frontmatter-ref`, `hyperlink`) and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE` for proper NULL-as-distinct dedup. Chunked backfill from v1 `wikilinks` table (10k-row chunks, `INSERT OR IGNORE`, idempotent). The v1 `wikilinks` table is preserved alongside `edges` for v1 invariance (D-01); v3 cleanup is tracked.
212
+ - **Unified edge extractor (Phase 4 plan 04-02 / GRA-04 indexer)** — `extractAllEdges(vault, parsed, resolver): EdgeInput[]` extracts all four edge types in a single per-note parse pass. `MIN_MENTION_LEN = 4` (empirically validated against the Atlas Robotics fixture: 0 raw FPs over 62 notes). `FRONTMATTER_REF_ALLOWLIST` is a sealed 8-key `ReadonlySet<string>` (`assignee`, `owner`, `project`, `related`, `parent`, `child`, `attendees`, `superseded_by`). Mention candidate set built from `note_aliases` only; word-boundary uses `(?<![\w-])`/`(?![\w-])` so aliases containing `-`/`_` are matched as whole tokens. Paragraph-scope masking blanks fenced code, ATX headings, inline backticks, and `[[wikilink]]` spans (byte length preserved so line-offset lookups stay valid).
213
+ - **`AliasesQueries.listAll()` (Phase 4 plan 04-02)** — full alias inventory, sorted by `alias_norm ASC` for deterministic mention-regex compilation.
214
+ - **Additive `type: EdgeType` field on graph result rows (Phase 4 plan 04-01)** — `list_backlinks`, `list_forward_links`, `find_broken_links` results widen with `type`. `BacklinkEntry.relation` / `ForwardLinkEntry.relation` widen from the v2.0.0-pinned `"wikilink"` literal to the full `EdgeType` union. `assemble_dossier.linked_documents[].relation` and `get_document_bundle.{backlinks,forward_links}[].relation` widen the same way — the `PHASE-4-WIDEN` marker comments in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` (a Phase 3 v2.0.0 limitation) are now retired.
215
+ - **3 new eval YAMLs (Phase 4 plan 04-06 / GRA-05):**
216
+ - `evals/fixtures/v2-test-vault/_queries/expand.yaml` — 8 hand-curated queries over the Atlas Robotics live fixture covering all four edge types, mixed-type traversal at hops 1 and 2, `_memory/` opacity, and the unknown-seed warning path. All eight clear `min_precision >= 0.8` / `min_recall >= 0.8`.
217
+ - `evals/fixtures/v2-test-vault/_queries/search-hybrid-with-expand.yaml` — 3 composition queries exercising `search_hybrid({expand: {hops}, ...})` end-to-end.
218
+ - `evals/fixtures/v2-test-vault/_queries/cluster.yaml` — D-12 byte-snapshot of Louvain partition (cluster_id + sorted member DocIds per community); pins determinism across library upgrades + Node-minor versions.
219
+ - **Cross-adapter conformance for graph tools (Phase 4 plan 04-06)** — 6 new parameterized cases in `src/adapters/source/conformance.test.ts` run `expand()` + `cluster()` against both `obsidian-fs` and `stub` adapters via `src/graph/__test_helpers__/atlas-live-fixture.ts`. New harness fields (`graphSeedDocId`, `occludedMemoryDocId`, `reachableMemoryDocId`) carry adapter-specific reference DocIds.
220
+ - **Adapter seams (Phase 1, plans 01-01..06)** — `SourceConnector` / `DeliveryAdapter` / `ChangeFeed` interfaces under `src/adapters/` per ADR-002. `obsidian-fs` is the v2 reference implementation for all three. The seam shape is preserved across vault-content read, vault-content write, and change-event paths so future connectors (Notion, Logseq, …) drop in without touching `src/server.ts` or any v1 tool. (ADP-01, ADP-02, ADP-03)
221
+ - **Canonical v2 types** in `src/types.ts`: `Document`, `BlockNode`, `Edge`, `ChangeEvent`, `SourceHandle`, `MemorySink`, branded `DocId`, `WikilinkRef`. `Document.properties: Record<string, unknown>` subsumes both YAML frontmatter and future Notion typed properties. (ADP-04, ADP-05)
222
+ - **`src/adapters/registry.ts`** — adapter registry + sole minting point for branded `DocId`s via `parseDocId` / `formatDocId`. Future adapters register under their canonical scheme (`obsidian-fs://`, `notion-api://`, …). (ADP-05)
223
+ - **Stub-adapter conformance suite** — parameterized over `obsidian-fs` and `stub`, three test files (`src/adapters/{source,delivery,change-feed}/conformance.test.ts`). Lets future adapters validate themselves against the v2 contract before any production code lands. (ADP-13)
224
+ - **`scripts/lint-adapters.sh`** — POSIX shell CI gate enforcing ADR-002 Invariants I-1..I-6 + C-1 (Claude-leak) + I-5b (`obsidian://` literal). Wired into `npm run lint:check` and `.github/workflows/ci.yml`. (ADP-12)
225
+ - **`scripts/smoketest-non-claude.mjs`** — end-to-end smoketest against the real MCP SDK Client (identifies as `non-claude-smoketest`); CI-gated. Asserts 23 tools listed, all descriptions non-empty, `tools/call list_vaults` succeeds, `tools/call <bogus>` surfaces as error. (ADP-10)
226
+ - **`docs/v2/AGENT_AGNOSTIC_AUDIT.md`** — per-leak inventory of every Claude / Obsidian assumption in `src/`, with explicit `fixed-v2` / `mixed` / `deferred-v3` status + rationale per row. Cross-references CONCERNS.md, ADR-002, and the resolving Phase-1 plans. (ADP-11)
227
+ - **`record_observation` MCP tool** — write a labeled memory observation through a configured `MemorySink` with mandatory provenance (Phase 2 plan 02-04 / MEM-02). Sugar args (`claim`, `evidence`, `confidence`, `type`, `sink?`) pre-fill the contract-required keys; optional `properties: Record<string, unknown>` escape hatch merges LAST so callers can populate any contract-allowed extra (D-02).
228
+ - **`recall` MCP tool** — retrieve memory documents filtered by `min_confidence`, `types`, `max_age_days`, and `sink`; returns Phase 3-shaped citation packets `{doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties}` (Phase 2 plan 02-05 / MEM-03 / D-01). Hides `status: superseded` by default; sorts `observed_at` DESC with `mtime` tiebreak; truncates AFTER filter+sort.
229
+ - **`supersede` MCP tool** — forward-only mark of a memory document as superseded by a replacement, with mandatory non-empty `superseded_reason` (Phase 2 plan 02-04 / MEM-04 / D-03). Single OCC `delivery.update()` call; the replacement document is never touched (back-edge derivation deferred to Phase 4 graph layer).
230
+ - **`vault-memory://memory/sinks` MCP Resource** — lists configured memory sinks per vault with their handle, contract name, and default-flag (Phase 2 plan 02-06 / MEM-09).
231
+ - **`vault-memory://memory/stats` MCP Resource** — per-sink doc counts, `by_type` / `by_status` breakdown, last-write timestamp aggregated from the indexed `notes` table + the `write_audit` partial index (Phase 2 plan 02-06 / MEM-09). Polled-only; no `notifyResourceUpdated` in v2.0.0.
232
+ - **`is_memory_sink_write` column on `write_audit`** (migration v9) + partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` + optional `is_memory_sink_write` filter on the `audit_log` tool input schema (Phase 2 plan 02-06 / MEM-08).
233
+ - **`decomposeDocId` helper** on `src/adapters/registry.ts` for splitting `DocId`s into `{scheme, authority, resource}` parts. Re-uses `DOC_ID_PATTERN`; no second regex (Phase 2 plan 02-02).
234
+ - **`pathInSink` + `joinVaultPath` helpers** in `src/adapters/delivery/obsidian-fs/path.ts` — the SOLE licensed sink/vault `path.join` sites for Phase 2+ per ADR-002 I-3 (Phase 2 plan 02-02).
235
+ - **MemorySink runtime** — `parseMemorySinkHandle` (IIFE-closed brand mint), `MemorySinkRegistry` (sole resolver per ADR-004 §Resolution), `.memory-sink` sentinel mechanics in `src/adapters/delivery/obsidian-fs/sentinel.ts`, hardcoded `DEFAULT_MEMORY_V1` contract + YAML loader at `src/memory/contract/` (Phase 2 plan 02-02 / MEM-01, MEM-05, MEM-06).
236
+ - **5 net-new memory fixture docs** under `evals/fixtures/v2-test-vault/_memory/` including the A→B→C Spire-budget supersede chain (2026-04-23 → 2026-04-24 → 2026-04-26), plus net-new `type: hypothesis`, `type: decision`, and `confidence: uncertain` dimensions (Phase 2 plan 02-07 / MEM-10). Fixture grows from 15 → 20 docs.
237
+ - **`tests/fixtures/malformed-memory/`** tree with 5 deliberately-broken fixtures (`missing-observed-at`, `missing-source`, `invalid-confidence`, `supersede-no-target`, `source-agent-no-evidence`) for validator failure-mode unit tests; each carries `expected_reason` + `expected_key` frontmatter (Phase 2 plan 02-07 / MEM-10).
238
+ - **`docs/tools/audit_log.md`** documenting the new optional `is_memory_sink_write` filter on the `audit_log` tool. Documentation lives here rather than in the MCP tool description text — the description is byte-identical to Phase 1 to honor the v1 backwards-compat invariant (Phase 2 plan 02-06).
239
+ - **Section identity substrate (Phase 3 plan 03-01 / ASM-01, ASM-02, ASM-03, ASM-05, ASM-08)** — `sections` table (migration 010) materializing per-document outline trees with content-hash anchors per ADR-003 H-7 (`sha256_hex(NFC(heading_text) || "\n" || render_blocks_to_plain_text(blocks))`). Parent-pointer reconstruction; per-section `chunk_id_first` / `chunk_id_last` ranges; denormalized `notes.status` column with partial index (`notes_status`) for the SQL-level superseded filter. Backfill from existing v1 vaults runs in lockstep with the migration so user vaults gain sections + status on first upgrade without re-indexing.
240
+ - **`get_outline` MCP tool** — return a nested `OutlineNode[]` tree for a `doc_id`. Each node carries `{anchor, heading_path, heading_text, level, chunk_ids: string[], children}` per D-02. Anchors are stable across re-indexings of unchanged content. Doc-level response envelope is the 8-field citation packet shape (Phase 3 plan 03-02 / ASM-02, ASM-05).
241
+ - **`search_sections` MCP tool** — section-level retrieval that COMPOSES the v1 chunk-level hybrid pipeline (`hybridSearch`) with a chunk-to-section promotion step. Accepts `{query, limit, vaults?, recency_weight?, authority_weight?, half_life_days?, include_superseded?}`. The promotion strategy: inflate `topK = limit × 5`, run hybrid once, promote each chunk hit to its enclosing section, dedupe by `(note_id, anchor)`, score each section as `MAX(constituent chunk scores)`, sort DESC tie-broken by `chunk_id_first` ASC, slice to `limit`, hydrate into `SectionHit` packets carrying the 8-field citation floor plus `{anchor, score, chunk_ids, snippet?}`. Preamble (level-0) sections are dropped — only sections with a non-empty `heading_path` surface. (Phase 3 plan 03-03 / ASM-03, ASM-05)
242
+ - **`get_document_bundle` MCP tool** — one-call composite read for a `doc_id`: `{anchor, outline, backlinks, forward_links, recent_edits}`. The anchor is a full 8-field citation packet with optional ASM-06 extras (`status`, `superseded_by`); `outline` re-uses `buildOutlineTree` from `get_outline` (composition, not duplication); `backlinks` + `forward_links` carry citation packets plus `{property_snippet, relation}` (relation is `"wikilink"` in v2.0.0 — see Phase 4 widening note below); `recent_edits` ≤10 entries from `audit_log`, with `is_memory_sink_write` surfaced only when truthy. (Phase 3 plan 03-04 / ASM-01, ASM-05, ASM-06)
243
+ - **`search_hybrid` rescore signals (Phase 3 plan 03-05 / ASM-06, ASM-07, ASM-08, ASM-11) — additive Zod params**, all defaulting to v1-no-op so legacy callers see byte-identical responses:
244
+ - `recency_weight: number` (default `0`) — adds `recency_weight × exp(-age_days / half_life_days)` to each candidate's RRF score.
245
+ - `authority_weight: number` (default `0`) — adds `authority_weight × 1` for docs with `properties.authoritative === true`.
246
+ - `half_life_days: number` (default `30`) — recency decay half-life.
247
+ - `include_superseded: boolean` (default `false`) — when `false`, excludes `status: "superseded"` chunks at SQL level via the `notes_status` partial index. Zero per-candidate frontmatter parses on the default-hide path.
248
+ - **9 new optional `SearchHit` fields** (D-08, ASM-06): `doc_id`, `source_handle`, `heading_path`, `mtime`, `hash`, `display_url`, `status`, `superseded_by`, `properties` — all snake_case to align with the Phase 2 `CitationPacket` shape; all optional and JSON-omitted on the v1-default path.
249
+ - **Display-URL resolver seam** — `HybridSearchOptions.displayUrlFor` optional closure keeps the `obsidian://` literal mint site in the obsidian-fs source adapter per ADR-002 §I-5b.
250
+ - **Clock-injection seam** — `HybridSearchOptions.clock` optional closure mirrors the recall convention; tests pass a fixed clock for deterministic age math.
251
+ - **`assemble_dossier` MCP tool** — resolve a `{type, key}` pair to an anchor `Document` and walk backlinks into a structured dossier `{anchor, linked_documents, property_rollups: {linked_count, linked_types, status_distribution}, error}` (Phase 3 plan 03-06 / ASM-04, ASM-05, ASM-10). Strict `properties.type` match (D-03); `key` matches the candidate's `title` OR any entry in `properties.aliases` (D-04). Every linked document carries an 8-field citation packet (D-01) plus the `relation` field. **v2.0.0 limitation:** `linked_documents[].relation` is always `"wikilink"` because the v1 `wikilinks` table only stores wikilink edges; Phase 4 (GRA-04 typed edges) will widen the field to the full `Edge.type` enum (mention, frontmatter-ref, hyperlink, …) as an additive change. Search for `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` for the widening sites.
252
+ - **Source-neutrality conformance suite for assembly tools (Phase 3 plan 03-07 / ASM-12)** — `src/adapters/source/conformance.test.ts` extends its `describe.each` parameterization with a new "Assembly tools — $name" section over `[obsidian-fs, stub-assembly]`. Five canonical assertions per adapter row (10 total) cover `get_outline`, `assemble_dossier`, `search_sections`, `get_document_bundle`, and citation-packet shape parity with recall's output. Backed by `src/adapters/stub/assembly-fixture.ts` — an 8-document purpose-built `Document[]` covering aliases, authoritative, superseded, all four edge types, and a multi-section doc. Per RESEARCH §7 P/R evals (ASM-10 dossier, ASM-11 recency) run against the obsidian-fs adapter only; the stub fixture is purpose-built for contract conformance.
253
+ - **Recency eval fixture** — `evals/fixtures/v2-test-vault/_queries/recency.yaml` ships ASM-11's stale-vs-fresh scenario: two near-duplicate Atlas-1 status notes (`status_updates/atlas-1-old-update.md` mtime ≈6 months back, `status_updates/atlas-1-new-update.md` mtime ≈1 day back) with neutral + recency-weighted query pair. Mtime contract is documented inline; eval harness `fs.utimesSync`-injects mtimes in a `beforeAll` block (git does not preserve mtimes on checkout).
254
+ - **Compiled brief layer (Phase 5, ADR-005)** — briefs are first-class `Document`s in `_memory/_briefs/` with chunk-level `source_hashes` provenance; compile-time wikilink emission (D-11), auto-supersede on target collision (D-12), and forward-only supersede chain. Writes route through `DeliveryAdapter` so the MEM-05 memory-namespace invariant is un-bypassable by construction. Closes the "agents rediscover 85% of context every run" failure mode that motivated the v2 program. See `docs/v2/PHASE-5-SIGN-OFF.md` + `docs/v2/adr/005-brief-compile-strategy.md`.
255
+ - **2 new MCP tools (Phase 5)** — `compile_brief`, `get_brief`. Tool count: 32 → 34 (additive only; the 32 Phase 4 entries verified byte-identical per-name in `evals/v1-baseline/tools-list.snapshot.json`; strict-equality snapshot test re-enabled in `baseline.test.ts`).
256
+ - **`vault-memory://briefs` MCP Resource (Phase 5 / BRF-09)** — pure-read discovery surface with optional `?target=<pattern>` substring filter. NOT a Tool; Resources do not count toward the REL-08 tool budget. `readListBriefs` is a closure over `MemorySinkRegistry` + `VaultManager` + `SourceConnector` and carries zero `fs` / `path` / `gray-matter` / `chokidar` imports (enforced by `scripts/lint-adapters.sh` + an explicit assertion in `src/brief/resources.test.ts` Test 10).
257
+ - **Brief staleness daemon (Phase 5 / BRF-05..08)** — `BriefStalenessDaemon` subscribes to `ChangeFeed.subscribe()`, runs a startup full scan against `brief_sources.listBriefDocIds()` (BRF-07; cursor in `daemon_state`), and preserves brief→source links across rename events via a 5-second pending-deletes grace window (BRF-08). Single-owner via `~/.vault-memory/locks/<vault>.lock` (`fs.open('wx')` + PID liveness); on lock contention emits `daemon_already_owned` and returns `{acquired: false}` without subscribing.
258
+ - **`default-brief-v1` MemoryContract (Phase 5)** — the provenance frame required by every brief `Document`. Required properties: `source: "agent"`, `confidence: "inferred"`, `evidence: parsedSourceDocIds`, `status: "active"`, `observed_at`, `superseded_by`, `type: "brief"`, `target`, `purpose`, `compiled_from`, `compiled_at`, chunk-level `source_hashes` map, and `model` audit attribution. The `_memory/_briefs/` sink binds to this contract at registration time.
259
+ - **ChunkId brand + content-stable fragment helpers (Phase 5)** — `parseChunkId`, `formatChunkId`, `decomposeChunkId`, `computeChunkHash`, `computeChunkIdFragment` in `src/chunker/chunk-id.ts`. D-04 content-derived 7-char fragments are essential for the v3 multi-user story (every user recomputes identical fragments over the same source text).
260
+ - **`OllamaClient.chat()` (Phase 5)** — `/api/chat` route alongside `/api/embed`; the first non-embedding LLM call in vault-memory history. Governed by ADR-005. No remote LLM SDK is bundled; the D-10 ladder probes MCP Sampling first (`getClientCapabilities().sampling`), then local Ollama via `[brief.ollama]` config, then falls back to `args.prepared_text` for deterministic CI / non-LLM contracts.
261
+ - **3 new eval YAMLs (Phase 5 / BRF-10, BRF-11):**
262
+ - `evals/fixtures/v2-test-vault/_queries/briefs-curated.yaml` — curated 20-document Atlas Robotics scenario; modifying one source flips the brief stale within one change-feed cycle.
263
+ - `evals/fixtures/v2-test-vault/_queries/briefs-staleness-stub.yaml` — BRF-11 cross-adapter parametric run (`adapters: [obsidian-fs, stub]`) proving brief-layer source-neutrality.
264
+ - `evals/fixtures/v2-test-vault/_queries/briefs-from-cluster.yaml` — cluster-fed compile scenario.
265
+ - **Cross-adapter conformance for brief tools (Phase 5)** — `src/adapters/source/conformance.test.ts` gains a `compile_brief + staleness daemon (BRF-11 source-neutrality)` describe block: 4 conformance test cases × 2 SourceConnector adapters = 8 test runs. Covers update flips stale, startup scan recovers missed events, delete past grace-window marks stale, and rename within grace-window preserves brief→source link.
266
+ - **Obsidian plugin (Phase 7, ADR-007)** — `plugin/` Obsidian community-plugin package (`manifest.json` v2.0.0, id=`vault-memory`, minAppVersion 1.5.0) with sideload-capable layout. `npm run build` emits a 1.9 MB `plugin/main.js`. Ships behind `[plugin] enabled = false` server-side; v1 baseline `tools/list` snapshot remains byte-identical when the flag is unset (FND-10 still passes). See `docs/v2/plugin/README.md` + `docs/v2/adr/007-contract-editor.md`.
267
+ - **Variant C three-pane contract editor (Phase 7 / CAN-01..CAN-07, CAN-10)** — palette + Svelte Flow canvas + Zod-derived inspector for editing Phase 6 task contracts. Canvas wires to `@xyflow/svelte` (MIT, license verified in ADR-007 — rescoped from the original jsoncanvas fork). `registerView('vault-memory-contract-editor')` + `registerExtensions(['contract'])` so opening a `.contract` file launches the editor.
268
+ - **`.contract` JSON envelope + lossless round-trip codec (Phase 7 / CAN-02, CAN-03, CAN-07)** — custom JSON format (`vmFormatVersion: 1`) with a pure-TS round-trip codec (`plugin/src/codec/contract-codec.ts`). Round-trip fixed-point pinned to 3rd/4th-emission byte identity across 4 fixtures (19/19 round-trip tests passing). Covers every ADR-006 field: `version`, `name`, `description`, `inputs`, `sources`, `sinks`, `assembly`, `output_shape`, `write_back`, `required`, `mcp_clients`.
269
+ - **3 reference `.contract` files (Phase 7 / CAN-06)** — `examples/contracts/meeting-prep.contract`, `project-status.contract`, `code-review-brief.contract`, each pinned to its Phase 6 YAML twin via 3 fixture round-trip tests.
270
+ - **6 new plugin-control MCP tools (Phase 7) — gated by `[plugin] enabled = true`, default OFF.** `set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`, `trigger_reindex`, `suppress_contract_write`. Default-OFF tool count: unchanged from Phase 6 (37); when the plugin flag is enabled the surface grows to 43. The v1-baseline tools-list snapshot remains byte-identical against the default-OFF surface — verified by `evals/v1-baseline/baseline.test.ts` "matches the pinned snapshot exactly" + "preserves the 23 v1 baseline tool names byte-identical".
271
+ - **Electron safeStorage-backed secrets (Phase 7 / PLG-02)** — `plugin/src/services/safe-storage.ts` wraps `window.electron.safeStorage` (`encryptString`/`decryptString`); only ciphertext lands in `data.json`. Secrets referenced by name from `[contracts.mcp_clients]` config via `${secret:name}` placeholders (regex `\$\{secret:([a-z][a-z0-9_-]{2,63})\}`). Connector-resolver test suite has 11 passing tests pinning the placeholder grammar + resolution path.
272
+ - **Plugin chrome — settings, reindex, stats, connectors panels (Phase 7 / PLG-01, PLG-03, PLG-04, PLG-05)** — `plugin/src/chrome/settings-tab.ts` (Ollama URL, model, indexer config; persisted via `loadData`/`saveData`; hot-swap via `set_runtime_config`), `reindex-panel.svelte` + controller (manual trigger with MCP `notifications/progress` feedback; SuppressionSet-aware), `stats-panel.svelte` + controller (read-only stats via `get_runtime_stats`), and `connectors-panel.svelte` (list/add/remove peer MCP clients, secrets-by-name). 42 chrome unit tests passing (7+8+7+12+8).
273
+ - **Hash-aware `SuppressionSet.consume(path, hash)` extension (Phase 7 / CAN-08)** — the Phase 1 `SuppressionSet` widens to accept a content hash so the contracts loader can short-circuit re-validation when the watcher fires on the plugin's own write. `src/contracts/loader.ts:242` calls `consume(resource, hash)` before reloading; `src/plugin-tools/suppress-contract-write.ts:109` registers the `(path, hash)` pair from the plugin BEFORE the write. Three dedicated loader tests cover the suppression path; regression watcher tests still pass (10/10 ObsidianFsChangeFeed + 6/6 VaultWatcher).
274
+ - **`vm-install` + `vm-update` Claude Code skills (Phase 7 / CAN-09 distribution half)** — one-liner curl-pipe install / upgrade for the plugin, with SHA-256 verification of the downloaded tarball. Idempotent. `bash skills/vm-install/setup.test.sh` 9/9 pass (fresh install + idempotent no-op); `bash skills/vm-update/update.test.sh` 12/12 pass (no-op + upgrade w/ SHA-256 + re-run). `RELEASE_URL_PLACEHOLDER` resolution is bookkept for the v2.0.0 GitHub Release publish (ROADMAP Phase 8 carryover).
275
+ - **6 plugin docs under `docs/v2/plugin/` (Phase 7 / CAN-09 docs half)** — `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section at the top of the repo README explains how to enable the flag and what the editor + chrome do.
276
+
277
+ ### Changed
278
+
279
+ - **Tool surface count: 30 → 32** (Phase 4, additive only). Two new graph tools added: `expand`, `cluster`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries + 7 Phase 3 entries remain content-identical (verified per-name; the strict-equality snapshot test `baseline.test.ts` "matches the pinned snapshot exactly" — which Plan 04-03 had `.skip`'d pending this regen — is re-enabled and green). The diff against the Phase 3 snapshot is purely additive: two new tool entries appended (before `assemble_dossier` to preserve insertion order at the end of `TOOLS`); one optional nested property added to `search_hybrid.inputSchema.properties` (`expand: {hops, direction?, edge_types?}`); `search_hybrid.description` widened additively to mention the new option.
280
+ - **`search_hybrid` accepts an additive nested `expand?: {hops: 1|2, direction?, edge_types?}` parameter (Phase 4 plan 04-04 / GRA-03).** Defaults to v1-no-op when omitted; legacy callers see byte-identical responses. When supplied, each `SearchHit` gains an `expansions?: CitationPacketWithVia[]` field carrying the per-hit typed-edge neighborhood; ranking is unchanged. Per-vault BFS isolation is enforced inside `expand()` (T-04-04-02 mitigation). Failures inside `expand()` are silently swallowed — same posture as the reranker fallback — so a corrupted graph traversal cannot break search.
281
+ - **Graph result rows widen from `relation: "wikilink"` to `relation: EdgeType` (Phase 4 plan 04-01).** `assemble_dossier.linked_documents[].relation`, `get_document_bundle.{backlinks,forward_links}[].relation`, and the v1 `BacklinkEntry`/`ForwardLinkEntry` `relation` fields now emit the actual edge type the indexer recorded. The Phase 3 known-limitation pinning these to `"wikilink"` is closed; the `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` have been retired. This is strictly an additive widening — old callers that only checked for `"wikilink"` still receive that value when the underlying edge is a wikilink.
282
+ - **Indexer dual-writes to `wikilinks` + `edges` tables (Phase 4 plans 04-01, 04-02).** Both indexer write paths (`src/indexer/single.ts` body-hash fast path + full re-embed branch; `src/indexer/indexer.ts` full index path) call `writeAllEdges` after parsing each note, threading the `WikilinkResolver` for cross-note target resolution. The v1 `wikilinks` table is preserved for D-01 v1 invariance; v3 cleanup will drop it once no v1 tool reads from it.
283
+ - **`@modelcontextprotocol/sdk` bumped to `^1.29.0`**; tool registration migrated from the v1 low-level `Server` + `setRequestHandler` pattern to `McpServer` + `server.registerTool()` × 23. Raw JSON Schema literals flow directly from `src/tool-registry.ts` (workaround for SDK#1143 / Zod-4 description-drop, although the issue is empirically MOOT in SDK 1.29). (ADP-08)
284
+ - **`zod` bumped to `^4.4.3`**. Refinements and `errorMap` swept per the Zod 4 migration guide; Standard Schema wiring intact. (ADP-09)
285
+ - **Default `client_id` for write / update / delete** is now captured from MCP `InitializeRequest.params.clientInfo.name` via a lazy closure (`getClientId()` in `src/server.ts`), falling back to `"unknown"`. Removes the v1 hardcoded `"claude-code"` default that misidentified every non-Claude write in the audit log. (D-02)
286
+ - **`obsidian://` display-URL minting** moved from `src/server.ts:obsidianUrl()` (deleted) into `SourceConnector.formatDisplayUrl(id)` on the adapter. The `obsidian://open?vault=…&file=…` URL is now adapter-published; future adapters mint their own scheme. (D-01)
287
+ - **README rewritten** to lead with "any MCP-aware agent" framing in the first 20 lines. Equal billing for Claude Code / Claude Desktop / ChatGPT Custom Connectors / MCP Inspector / generic clients. Obsidian framed as the v2 source connector, not the sole consumer. (ADP-14)
288
+ - **`src/cli.ts` user-facing strings** swept for Claude-leak — `"Open ${path} in Claude Code"` → `"Open ${path} in your MCP-aware client"`. (D-02 follow-through)
289
+ - **`WriteConflict.reason` discriminated union extended** (additively, backwards-compatible) with 7 new codes: `missing_provenance`, `invalid_provenance`, `supersede_mismatch`, `agent_write_outside_sink`, `non_agent_write_inside_sink`, `sentinel_missing`, `sink_write_blocked`. Optional envelope fields added: `sinkName`, `key`, `observedValue`, `suggestion`. The 3 Phase 1 reason codes (`hash_mismatch`, `permission_denied`, `not_found`) are unchanged (Phase 2 plan 02-03 / MEM-05, MEM-07, MEM-11).
290
+ - **v1 `write_note`, `update_frontmatter`, `delete_note` now refuse memory-sink-resolved targets** at the tool entry-point with `sink_write_blocked` + actionable `suggestion` text (`record_observation` for writes/updates, `supersede` for deletes). Defense-in-depth on top of the centralized `DeliveryAdapter.write()` chokepoint (Phase 2 plan 02-03b / MEM-07). When the `MemorySinkRegistry` is not wired (Phase 1 fixture-test path), v1 tool behavior is byte-identical to Phase 1.
291
+ - **`audit_log` tool gains optional `is_memory_sink_write` input filter** and adds `is_memory_sink_write: boolean` to each returned row. The MCP `description` text is byte-identical to Phase 1 — the new capability is documented in CHANGELOG and `docs/tools/audit_log.md` only (Phase 2 plan 02-06 / MEM-08).
292
+ - **Server bootstrap order** is now `loadConfig → manager.openAll → registerMemorySinks (writes sentinels) → server.connect → startCatchupAndWatchers (fire-and-forget)`. Sentinels are provisioned BEFORE the catch-up indexer walks the fixture, so the registry's `findSinkContaining` enclosure check is hot for the first incoming write (Phase 2 plan 02-03b). A new exported `BootstrapPhase` type + optional `onPhase` callback on `serve(options?)` make the bootstrap order observable for testing.
293
+ - **Assembly DocId minting derives scheme from `SourceConnector.handle`** (Phase 3 plan 03-07 fix). Pre-03-07 both `assembleDossier` and `getDocumentBundle` hardcoded `formatDocId("obsidian-fs", …)` when constructing linked-document / anchor DocIds. The 03-07 ASM-12 conformance suite (parameterized over obsidian-fs + stub adapters) surfaced this as a hard test failure on the stub-assembly row. The fix introduces `schemeFromSource(SourceConnector): string` and derives the scheme dynamically; `dossier.ts`'s sort-key helper is renamed `noteDocIdString → noteSortKey` and pinned to a fixed `vault://` prefix so sort order stays adapter-identical. No user-visible change for obsidian-fs callers (their scheme has always been `obsidian-fs://`); the fix unblocks future non-Obsidian sources.
294
+ - **Tool surface count: 26 → 30** (additive only). Four Phase 3 assembly tools added: `get_outline`, `search_sections`, `get_document_bundle`, `assemble_dossier`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries at slots 0–22 remain byte-identical (pinned by the existing `baseline.test.ts` "preserves the 23 v1 baseline tool names byte-identical" assertion). The diff against the Phase 2 snapshot is purely additive: four new tool entries appended; one optional field added to `search_hybrid.inputSchema.properties` (the four rescore params).
295
+ - **Tool surface count: 32 → 34** (Phase 5, additive only). Two new brief tools added: `compile_brief`, `get_brief`. The `list_briefs` discovery surface lives on MCP Resources (`vault-memory://briefs`) and does NOT count toward the tool budget — same precedent as the Phase 2 `vault-memory://memory/*` Resources. The 32 Phase 4 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (verified per-name); the diff is purely additive — two new tool entries appended.
296
+ - **Server bootstrap order extended (Phase 5)** — after `registerMemorySinks` and before `server.connect`, the bootstrap now (a) registers the `_memory/_briefs/` sink bound to the `default-brief-v1` contract and (b) starts `BriefStalenessDaemon` per vault. Daemon `start()` is fire-and-forget: a lock-contention failure does NOT block server startup — the server proceeds and `compile_brief` continues to work; only the staleness re-check is offline.
297
+ - **`SuppressionSet.consume()` accepts an optional `hash` argument (Phase 7 / CAN-08, additive).** Phase 1 callers that pass only a path see byte-identical behavior. When the plugin write path passes `(path, hash)`, the contracts loader at `src/contracts/loader.ts:242` matches both axes before short-circuiting — so a benign re-emit of the same canonical bytes is suppressed, but a divergent write still triggers reload.
298
+ - **Default-OFF plugin tool gating (Phase 7).** The 6 new plugin tools are wired through a config-time guard in `src/server.ts` so `tools/list` does NOT include them unless `[plugin] enabled = true`. The default-OFF surface keeps the v1 baseline snapshot byte-identical and keeps the REL-08 budget conversation focused on the user-facing v2 tools.
299
+
300
+ ### Dependencies
301
+
302
+ - **Phase 4 (plan 04-05 / GRA-02)** — pure-JS ESM, all MIT, all manually provenance-verified per Phase 4 RESEARCH §Package Legitimacy Audit:
303
+ - `graphology ^0.26.0` — graph data structure for the Louvain cluster wrapper.
304
+ - `graphology-communities-louvain ^2.0.2` — Louvain modularity-maximizing community detection.
305
+ - `seedrandom ^3.0.5` — deterministic seeded PRNG for the Louvain `rng` option.
306
+ - `@types/seedrandom ^3.0.8` (dev) — TypeScript types.
307
+ - No native bindings; no tsup `external` additions needed. ESM bundle grew from 376 KB to 392 KB (+13 KB).
308
+ - **Phase 7 plugin (ADR-007)** — added under `plugin/package.json` only; the server bundle is unaffected:
309
+ - `@xyflow/svelte` — Svelte Flow canvas renderer for the Variant C editor (MIT, license verified in ADR-007).
310
+ - `svelte`, `esbuild`, `vitest` — plugin-only build/test toolchain. None ship into the server `dist/`.
311
+
312
+ ### Migration
313
+
314
+ - **MIGRATION_011 — `edges` table + chunked backfill from `wikilinks` (Phase 4 plan 04-01).** Function-style migration mirroring `runMigration008`. Creates the `edges` table with `CHECK(type IN ('wikilink', 'mention', 'frontmatter-ref', 'hyperlink'))` and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE(anchor, '')` + `COALESCE(line_number, 0)` for proper NULL-as-distinct dedup. Backfills wikilink rows from the v1 `wikilinks` table in 10k-row chunks via `INSERT OR IGNORE` — idempotent, re-runnable, safe to interrupt. v1-shaped DBs migrate cleanly on first server start after upgrade; the v1 `wikilinks` table is preserved alongside the new `edges` table for D-01 v1 invariance. v3 cleanup will drop `wikilinks` once no v1 tool consumes it.
315
+ - **`notes.doc_uri` dual-column staging (Strategy A — additive, plan 01-02):**
316
+ - **MIGRATION_007** (additive) — adds nullable `doc_uri TEXT` column to `notes` + `idx_notes_doc_uri` index. Backwards-compatible; existing rows have `doc_uri IS NULL` post-migration.
317
+ - **MIGRATION_008** (function-style backfill) — sets `doc_uri = 'obsidian-fs://<vault>/' || path` for every row where `doc_uri IS NULL`. Idempotent; safe to re-run.
318
+ - Path stored **un-encoded** in the column; percent-encoding happens only at `formatDisplayUrl()` time (per ADR-002 §URL semantics).
319
+ - **No user action required.** Migrations run automatically on first server start after upgrade. SQLite transaction rollback is the safety net; no pre-migration backup is performed in v2 (deferred to Phase 8 per CONCERNS §"No Pre-Migration DB Backup"). (ADP-07)
320
+ - **v1 tool surface preserved byte-for-byte.** All 23 tool names, input schemas, output envelopes, and descriptions are unchanged. `evals/v1-baseline/tools-list.snapshot.json` regenerated under SDK 1.29 with zero diff against the Phase-0 baseline (W6 human-verify checkpoint passed in plan 01-06 Task 06).
321
+ - **MIGRATION_010 — sections table + notes.status column + section backfill (Phase 3 plan 03-01).** Three ordered steps in one transaction: (a) `CREATE TABLE sections` with 3 indexes, (b) `notes.status` column added + partial index `notes_status WHERE status IS NOT NULL`, (c) backfill `notes.status` from `json_extract(frontmatter, '$.status')` AND derive section rows for every indexed note via `markdownToSectionBlocks → extractSections`. Idempotent; re-applying is a no-op. Anchor-equivalence guarantee: backfilled section anchors match a fresh re-index byte-for-byte (the indexer + backfill share `src/chunker/headings.ts:extractHeadings` as the canonical heading source). v1-shaped DBs migrate cleanly without re-indexing.
322
+ - **`write_audit` migration v9** (function-style, idempotent) adds `is_memory_sink_write INTEGER NOT NULL DEFAULT 0` + a partial index `idx_write_audit_memory_sink ON (is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` (Phase 2 plan 02-06 / MEM-08). All Phase 1 audit rows backfill to `false`; new audit rows derive the flag from `WriteOptions.sink !== undefined` at the `ObsidianFsDelivery.write/update/delete` facade. The function-style migration runs `PRAGMA table_info` first so fixture tests that rewind `user_version` continue to pass.
323
+ - **v1 tool surface grows from 23 → 26** with the three Phase 2 memory tools (`record_observation`, `recall`, `supersede`). The 23 v1 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (pinned by a new `baseline.test.ts` assertion). The only diff on v1 tools is one optional input field added to `audit_log.inputSchema` (`is_memory_sink_write`); the v1 description text is byte-identical.
324
+ - **MIGRATION_013 — `chunks.chunk_id_fragment` + `brief_sources` + `daemon_state` (Phase 5 plan 05-01).** Three additive steps in one transaction: (a) `chunks.chunk_id_fragment` TEXT column added (`noUncheckedIndexedAccess` consumers see the new column as `T | undefined` until populated by the next index run); (b) `brief_sources` table (D-06 reverse-index — `(brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)`); (c) `daemon_state` table (per-vault change-feed cursor + `last_full_scan` timestamp). Idempotent; re-applying is a no-op. v1-shaped DBs migrate cleanly on first server start after upgrade.
325
+ - **Phase 5 brief artifacts are NOT a server schema migration.** Brief `Document`s land in `_memory/_briefs/` via the standard `DeliveryAdapter.write()` path; the `default-brief-v1` MemoryContract is registered at bootstrap (not persisted in SQLite). Users who never call `compile_brief` see zero new files on disk.
326
+ - **Phase 7 plugin is NOT a server schema migration.** No new SQLite migrations land for Phase 7. The plugin stores its own state in `data.json` (loadData/saveData) inside the Obsidian vault's plugin directory; secrets land as Electron safeStorage ciphertext only. Users who never enable the plugin flag see zero new server-side files.
327
+
328
+ ### Documentation
329
+
330
+ - Relocate ADRs 001–004 from `docs/dev/` (gitignored) to public `docs/v2/adr/`; amend each with Invariants + Examples sections covering both `obsidian-fs://` and `notion-api://` worked examples (FND-01, FND-04).
331
+ - ADR-003 amended with explicit hash-semantics pseudocode (RFC 8785 JCS, NFC normalisation, LF line endings, IEEE-754 number canonicalization) + chunk-level `source_hashes` schema (FND-02).
332
+ - ADR-004 amended: folder-default `MemorySink` is the only code path; separate-vault is config-only via `[memory] sink = "@…"` (FND-03). `.memory-sink` sentinel mandated.
333
+ - ADR-004 amended: underscored PropertyBag keys, confidence enum aligned to [direct, inferred, uncertain], superseded_reason field added (Phase 2 plan 02-01).
334
+ - Publish `docs/v2/ARCHITECTURE.md` (L0–L4 layer model + responsibility map), `docs/v2/MEMORY_CONTRACT.md` (provenance property contract on `Document.properties`), `docs/v2/AGENT_AGNOSTIC.md` (MCP-canonical client stance) (FND-05/06/07).
335
+ - Eval fixture vault `evals/fixtures/v2-test-vault/` — "Atlas Robotics" narrative; 56 notes across `projects/`, `meetings/`, `people/`, `decisions/`, `references/`; 15-document `_memory/` subset; 7 hand-labeled `_queries/*.yaml` (FND-08).
336
+ - v1-baseline regression suite `evals/v1-baseline/` — `tools-list.snapshot.json` pin for `tools/list` (23 tools), 11 per-tool semantic-floor YAMLs, `baseline.test.ts` vitest runner with `.todo` placeholders for Phase 1 precision/recall (FND-09/10).
337
+ - CI gates `scripts/check-fixture-privacy.sh` + `scripts/lint-no-telemetry.sh` + `.github/workflows/ci.yml` running `npm run lint:check && npm test` on every PR and push to `main` (FND-11/12 + D-21).
338
+ - ADR index `docs/v2/adr/README.md` listing 4 Accepted ADRs + 14 Open ADR stubs for v3 / Phase 10 follow-ups, including a Deferred-v3 section for adversarial-review findings (FND-13).
339
+ - Adversarial review `docs/v2/adr/ADVERSARIAL-REVIEW.md` — 10 findings against ADRs 001–004 raised by a fresh-context advisor; 6 Amended in Phase 0 (ADR-001 I-6, ADR-002 `DocumentRef.hash` contract + `hashProtected` enum, ADR-003 H-6), 4 Deferred-v3 to Phase 10 / Notion connector (FND-04 + FND-14 sign-off).
340
+ - Sign-off artifact `docs/v2/SIGN-OFF.md` — FND-01..14 checklist with resolving commit SHAs; PR approval is the FND-14 audit trail per D-17.
341
+ - Internal: extract `TOOLS` constant from `src/server.ts` to `src/tool-registry.ts` so `evals/v1-baseline/dump-tools.mjs` can produce the pinned snapshot without spinning the full MCP server (Phase 0 Assumption A5, no external behavior change).
342
+ - Phase 5 sign-off artifact `docs/v2/PHASE-5-SIGN-OFF.md` — BRF-01..BRF-11 traceability table + the five Phase 5 ROADMAP success criteria with disposition (`✅ MET` per criterion) + REL-08 hand-off plan to Phase 8.
343
+ - ADR-005 `docs/v2/adr/005-brief-compile-strategy.md` (Accepted) — governs the D-10 LLM strategy ladder and forbids bundling any remote LLM SDK in the server.
344
+ - Phase 7 plugin docs under `docs/v2/plugin/`: `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section added to the repo root README explaining how to flip `[plugin] enabled = true` and what the editor + chrome surfaces do.
345
+ - ADR-007 `docs/v2/adr/007-contract-editor.md` (Accepted, 2026-05-19) — rescopes the visual editor from a jsoncanvas fork to `@xyflow/svelte` (MIT, license verified inline in the ADR).
346
+
347
+ ### Deprecated (Phase 8 / REL-08)
348
+
349
+ The following five v1 tools are deprecated in v2.0.0 and promoted to MCP Resources. Each tool remains callable through the v2.x line for backwards compatibility; removal is scheduled for v3.0.0. The Resource URI is the canonical replacement.
350
+
351
+ - **`list_vaults`** → `vault-memory://vaults` (cross-vault discovery Resource). The tool remains callable through v2.x; removal scheduled for v3.0.0.
352
+ - **`list_models`** → `vault-memory://models/{vault}` (per-vault embedding-model inventory). The tool remains callable through v2.x; removal scheduled for v3.0.0.
353
+ - **`recent_notes`** → `vault-memory://recent/{vault}` (per-vault mtime-DESC recent notes). The tool remains callable through v2.x; removal scheduled for v3.0.0.
354
+ - **`vault_stats`** → `vault-memory://stats/{vault}` (per-vault note count + top tags + top frontmatter keys + last index run). The tool remains callable through v2.x; removal scheduled for v3.0.0.
355
+ - **`list_backlinks`** → `vault-memory://backlinks/{vault}/{+docId}` (per-document backlink listing). The URI uses RFC 6570 reserved expansion: `{+docId}` allows `/` in the variable value so multi-segment `docId`s like `obsidian-fs://my-vault/notes/sub/file.md` parse correctly. The tool remains callable through v2.x; removal scheduled for v3.0.0.
356
+
357
+ ## [2.0.0-rc.3] — 2026-05-19
358
+
359
+
360
+ ### Changed
361
+
362
+ - **Obsidian plugin error messages reference `/vmem:install`** (was: `vm-install skill` / `/vm-install`). Two strings updated in `plugin/src/services/mcp-client.ts` (the `CliNotFoundError` thrown in both the spawn-time and connect-time error paths) and one banner string in `plugin/src/chrome/settings-tab.ts` (the "CLI not found" banner in the settings tab). The new strings point users at the renamed marketplace plugin `vmem` (replacing the old `install-vault-memory` with its duplicated `/install-vault-memory:install-vault-memory` slug) and the verb-split surface: `/vmem:install`, `/vmem:health`, `/vmem:reindex`. See `ISSUE-marketplace-plugin-rename-vmem.md`.
363
+
364
+ ### Fixed
365
+
366
+ - **Migration 010 — `UNIQUE constraint failed: sections.note_id, sections.anchor`** crash blocking v0.9.x/v1.0.0 → v2.0.0-rc.1 upgrade for any user whose vault contains heading-only sibling sections (e.g. template scaffolds with repeated `## TODO` headings whose body slot is empty). `extractSections` produces identical content-hash anchors for sections with identical `(heading_text, body)` pairs, and the `INSERT INTO sections` was plain so the second sibling aborted the migration transaction — leaving every CLI command unreachable for every configured vault until the bad DB was removed from `config.toml`. `backfillSectionsFromChunks` now uses `INSERT OR IGNORE`; on collision the surviving row's id is looked up and reused so the `insertedIds` slot still resolves later children's `parent_id` correctly. See `ISSUE-migration-010-duplicate-anchor.md`.
367
+
368
+ ### Added
369
+
370
+ - **Task Contract DSL (Phase 6, ADR-006)** — declarative YAML contracts under `_contracts/<name>.yaml`, addressable by name, instantiable via MCP, with handle-based source/sink portability. Contracts use a closed assembly verb enum (11 baseline + `literal` + `mcp://<server>/<tool>` peer extension), `{{template}}` step composition, JSON-Schema-with-`$ref` inputs, MemorySink-only sinks (un-bypassable per D-A4c), and ChangeFeed hot reload (D-LOAD). See `docs/v2/PHASE-6-SIGN-OFF.md` + `docs/v2/adr/006-task-contract-dsl.md`.
371
+ - **3 new MCP tools (Phase 6)** — `describe_contract`, `instantiate_contract`, `register_contracts_as_tools`. Tool count: 34 → 37 (additive only; v1-baseline preserved byte-identical).
372
+ - **2 new MCP Resources (Phase 6)** — `vault-memory://contracts/{vault}` (CON-04) lists contracts available in a vault; `vault-memory://contract-verbs/{vault}` (D-A2b) lists baseline + custom `mcp://` verbs with invocation counts. Resources do NOT count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.
373
+ - **3 reference contracts (Phase 6)** under `evals/fixtures/v2-test-vault/_contracts/`: `meeting-prep`, `project-status`, `code-review-brief`. Plus `smoketest-trivial` for the CON-09 non-Claude smoketest path (literal-only assembly; no LLM needed in CI).
374
+ - **`contract_audit` table (Phase 6, migration 014)** — orchestration audit substrate; one row per assembly step (kind=`contract_step`) and one row per load failure (kind=`contract_load_error`). Payload-free per Invariant C-5 (peer-MCP outputs may carry sensitive data; we never capture them). Feeds `aggregateVerbUsage(vault)` for the D-A2b promotion signal.
375
+ - **`[contracts]` config block (Phase 6)** — `auto_register_tools: bool = false`, `tool_prefix: string = "vm_"`, `step_timeout_seconds: number = 30`, `defaults: Record<string, string> = {}`, `mcp_clients: Record<string, {command, args?, env?}> = {}`.
376
+ - **`instantiate_contract` write_back chokepoint (Phase 6)** — routes through `DeliveryAdapter.write()` so the MEM-05 invariant is un-bypassable by construction. Sink overrides MUST resolve through `MemorySinkRegistry.resolveMemorySink()` before write; otherwise `{ok:false, reason:"sink_override_not_a_memory_sink"}`.
377
+ - **CON-10 stub-parity proof (Phase 6)** — `src/adapters/source/conformance.test.ts` gains a `contracts stub-parity (CON-10)` describe block proving the same contract produces structurally identical bundles across obsidian-fs and stub source connectors.
378
+ - **CON-09 non-Claude smoketest extension (Phase 6)** — `scripts/smoketest-non-claude.mjs` spawns the server against a temp HOME with the fixture vault, asserts the three contract tools surface, calls `describe_contract` + `instantiate_contract` + reads the `list_contracts` Resource, and exits 0.
379
+ - **`ObsidianFsSource` widened to enumerate `_contracts/*.yaml` (Phase 6)** — non-recursive walk added via `scanContractFiles`; `scanVault` unchanged so the indexer's `.md`-only contract is preserved. `readDocument` handles YAML files by returning raw text as a single paragraph block (bypasses `parseNote`'s markdown assumptions).
380
+ - **`expand` MCP tool (Phase 4 plan 04-03 / GRA-01)** — typed-edge BFS retrieval primitive. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying `via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped at 2 (v2.0.0). Default direction `'both'`. Filterable by `edge_types: EdgeType[]` and by `filter_properties` (strict equality, no operators). Memory-sink documents (`_memory/...`) surface only when transitively reachable from a user-note seed via at least one in-result inbound edge — per ADR-004 memory-namespace opacity rule. Unknown `seed_doc_ids` do NOT throw — they are returned in a `warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins on dedup; ties broken by `(seed_doc_id, edge_type, direction)`.
381
+ - **`cluster` MCP tool (Phase 4 plan 04-05 / GRA-02)** — Louvain community detection over the typed-edge graph, deterministic across runs via `seedrandom`. Accepts mutually-exclusive `query | seed_doc_ids`. Returns `{ok: true, communities: Cluster[]}` or `{ok: false, reason: 'too_many_nodes' | 'missing_input' | 'mutually_exclusive_input'}`. 5000-node hard cap with `force: true` override. Per-community `cluster_id` is the smallest member DocId by lexicographic order — deterministic naming independent of internal community index assignment. Hybrid-search dependency injected via `ClusterDeps.hybridSearch` closure to avoid the `src/graph/cluster.ts → src/search/hybrid.ts` circular import.
382
+ - **`search_hybrid({expand})` additive nested param (Phase 4 plan 04-04 / GRA-03)** — pass `expand: {hops: 1|2, direction?, edge_types?}` to auto-attach 1–2 hop typed-edge neighbors as `SearchHit.expansions?: CitationPacketWithVia[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged. Guard-and-short-circuit composition: when `expand` is omitted, the code path is byte-identical to Phase 3 (zero new DB reads, zero new JSON fields).
383
+ - **Typed-edge storage substrate (Phase 4 plan 04-01 / GRA-04)** — `edges` table (migration 011) with four edge types (`wikilink`, `mention`, `frontmatter-ref`, `hyperlink`) and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE` for proper NULL-as-distinct dedup. Chunked backfill from v1 `wikilinks` table (10k-row chunks, `INSERT OR IGNORE`, idempotent). The v1 `wikilinks` table is preserved alongside `edges` for v1 invariance (D-01); v3 cleanup is tracked.
384
+ - **Unified edge extractor (Phase 4 plan 04-02 / GRA-04 indexer)** — `extractAllEdges(vault, parsed, resolver): EdgeInput[]` extracts all four edge types in a single per-note parse pass. `MIN_MENTION_LEN = 4` (empirically validated against the Atlas Robotics fixture: 0 raw FPs over 62 notes). `FRONTMATTER_REF_ALLOWLIST` is a sealed 8-key `ReadonlySet<string>` (`assignee`, `owner`, `project`, `related`, `parent`, `child`, `attendees`, `superseded_by`). Mention candidate set built from `note_aliases` only; word-boundary uses `(?<![\w-])`/`(?![\w-])` so aliases containing `-`/`_` are matched as whole tokens. Paragraph-scope masking blanks fenced code, ATX headings, inline backticks, and `[[wikilink]]` spans (byte length preserved so line-offset lookups stay valid).
385
+ - **`AliasesQueries.listAll()` (Phase 4 plan 04-02)** — full alias inventory, sorted by `alias_norm ASC` for deterministic mention-regex compilation.
386
+ - **Additive `type: EdgeType` field on graph result rows (Phase 4 plan 04-01)** — `list_backlinks`, `list_forward_links`, `find_broken_links` results widen with `type`. `BacklinkEntry.relation` / `ForwardLinkEntry.relation` widen from the v2.0.0-pinned `"wikilink"` literal to the full `EdgeType` union. `assemble_dossier.linked_documents[].relation` and `get_document_bundle.{backlinks,forward_links}[].relation` widen the same way — the `PHASE-4-WIDEN` marker comments in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` (a Phase 3 v2.0.0 limitation) are now retired.
387
+ - **3 new eval YAMLs (Phase 4 plan 04-06 / GRA-05):**
388
+ - `evals/fixtures/v2-test-vault/_queries/expand.yaml` — 8 hand-curated queries over the Atlas Robotics live fixture covering all four edge types, mixed-type traversal at hops 1 and 2, `_memory/` opacity, and the unknown-seed warning path. All eight clear `min_precision >= 0.8` / `min_recall >= 0.8`.
389
+ - `evals/fixtures/v2-test-vault/_queries/search-hybrid-with-expand.yaml` — 3 composition queries exercising `search_hybrid({expand: {hops}, ...})` end-to-end.
390
+ - `evals/fixtures/v2-test-vault/_queries/cluster.yaml` — D-12 byte-snapshot of Louvain partition (cluster_id + sorted member DocIds per community); pins determinism across library upgrades + Node-minor versions.
391
+ - **Cross-adapter conformance for graph tools (Phase 4 plan 04-06)** — 6 new parameterized cases in `src/adapters/source/conformance.test.ts` run `expand()` + `cluster()` against both `obsidian-fs` and `stub` adapters via `src/graph/__test_helpers__/atlas-live-fixture.ts`. New harness fields (`graphSeedDocId`, `occludedMemoryDocId`, `reachableMemoryDocId`) carry adapter-specific reference DocIds.
392
+ - **Adapter seams (Phase 1, plans 01-01..06)** — `SourceConnector` / `DeliveryAdapter` / `ChangeFeed` interfaces under `src/adapters/` per ADR-002. `obsidian-fs` is the v2 reference implementation for all three. The seam shape is preserved across vault-content read, vault-content write, and change-event paths so future connectors (Notion, Logseq, …) drop in without touching `src/server.ts` or any v1 tool. (ADP-01, ADP-02, ADP-03)
393
+ - **Canonical v2 types** in `src/types.ts`: `Document`, `BlockNode`, `Edge`, `ChangeEvent`, `SourceHandle`, `MemorySink`, branded `DocId`, `WikilinkRef`. `Document.properties: Record<string, unknown>` subsumes both YAML frontmatter and future Notion typed properties. (ADP-04, ADP-05)
394
+ - **`src/adapters/registry.ts`** — adapter registry + sole minting point for branded `DocId`s via `parseDocId` / `formatDocId`. Future adapters register under their canonical scheme (`obsidian-fs://`, `notion-api://`, …). (ADP-05)
395
+ - **Stub-adapter conformance suite** — parameterized over `obsidian-fs` and `stub`, three test files (`src/adapters/{source,delivery,change-feed}/conformance.test.ts`). Lets future adapters validate themselves against the v2 contract before any production code lands. (ADP-13)
396
+ - **`scripts/lint-adapters.sh`** — POSIX shell CI gate enforcing ADR-002 Invariants I-1..I-6 + C-1 (Claude-leak) + I-5b (`obsidian://` literal). Wired into `npm run lint:check` and `.github/workflows/ci.yml`. (ADP-12)
397
+ - **`scripts/smoketest-non-claude.mjs`** — end-to-end smoketest against the real MCP SDK Client (identifies as `non-claude-smoketest`); CI-gated. Asserts 23 tools listed, all descriptions non-empty, `tools/call list_vaults` succeeds, `tools/call <bogus>` surfaces as error. (ADP-10)
398
+ - **`docs/v2/AGENT_AGNOSTIC_AUDIT.md`** — per-leak inventory of every Claude / Obsidian assumption in `src/`, with explicit `fixed-v2` / `mixed` / `deferred-v3` status + rationale per row. Cross-references CONCERNS.md, ADR-002, and the resolving Phase-1 plans. (ADP-11)
399
+ - **`record_observation` MCP tool** — write a labeled memory observation through a configured `MemorySink` with mandatory provenance (Phase 2 plan 02-04 / MEM-02). Sugar args (`claim`, `evidence`, `confidence`, `type`, `sink?`) pre-fill the contract-required keys; optional `properties: Record<string, unknown>` escape hatch merges LAST so callers can populate any contract-allowed extra (D-02).
400
+ - **`recall` MCP tool** — retrieve memory documents filtered by `min_confidence`, `types`, `max_age_days`, and `sink`; returns Phase 3-shaped citation packets `{doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties}` (Phase 2 plan 02-05 / MEM-03 / D-01). Hides `status: superseded` by default; sorts `observed_at` DESC with `mtime` tiebreak; truncates AFTER filter+sort.
401
+ - **`supersede` MCP tool** — forward-only mark of a memory document as superseded by a replacement, with mandatory non-empty `superseded_reason` (Phase 2 plan 02-04 / MEM-04 / D-03). Single OCC `delivery.update()` call; the replacement document is never touched (back-edge derivation deferred to Phase 4 graph layer).
402
+ - **`vault-memory://memory/sinks` MCP Resource** — lists configured memory sinks per vault with their handle, contract name, and default-flag (Phase 2 plan 02-06 / MEM-09).
403
+ - **`vault-memory://memory/stats` MCP Resource** — per-sink doc counts, `by_type` / `by_status` breakdown, last-write timestamp aggregated from the indexed `notes` table + the `write_audit` partial index (Phase 2 plan 02-06 / MEM-09). Polled-only; no `notifyResourceUpdated` in v2.0.0.
404
+ - **`is_memory_sink_write` column on `write_audit`** (migration v9) + partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` + optional `is_memory_sink_write` filter on the `audit_log` tool input schema (Phase 2 plan 02-06 / MEM-08).
405
+ - **`decomposeDocId` helper** on `src/adapters/registry.ts` for splitting `DocId`s into `{scheme, authority, resource}` parts. Re-uses `DOC_ID_PATTERN`; no second regex (Phase 2 plan 02-02).
406
+ - **`pathInSink` + `joinVaultPath` helpers** in `src/adapters/delivery/obsidian-fs/path.ts` — the SOLE licensed sink/vault `path.join` sites for Phase 2+ per ADR-002 I-3 (Phase 2 plan 02-02).
407
+ - **MemorySink runtime** — `parseMemorySinkHandle` (IIFE-closed brand mint), `MemorySinkRegistry` (sole resolver per ADR-004 §Resolution), `.memory-sink` sentinel mechanics in `src/adapters/delivery/obsidian-fs/sentinel.ts`, hardcoded `DEFAULT_MEMORY_V1` contract + YAML loader at `src/memory/contract/` (Phase 2 plan 02-02 / MEM-01, MEM-05, MEM-06).
408
+ - **5 net-new memory fixture docs** under `evals/fixtures/v2-test-vault/_memory/` including the A→B→C Spire-budget supersede chain (2026-04-23 → 2026-04-24 → 2026-04-26), plus net-new `type: hypothesis`, `type: decision`, and `confidence: uncertain` dimensions (Phase 2 plan 02-07 / MEM-10). Fixture grows from 15 → 20 docs.
409
+ - **`tests/fixtures/malformed-memory/`** tree with 5 deliberately-broken fixtures (`missing-observed-at`, `missing-source`, `invalid-confidence`, `supersede-no-target`, `source-agent-no-evidence`) for validator failure-mode unit tests; each carries `expected_reason` + `expected_key` frontmatter (Phase 2 plan 02-07 / MEM-10).
410
+ - **`docs/tools/audit_log.md`** documenting the new optional `is_memory_sink_write` filter on the `audit_log` tool. Documentation lives here rather than in the MCP tool description text — the description is byte-identical to Phase 1 to honor the v1 backwards-compat invariant (Phase 2 plan 02-06).
411
+ - **Section identity substrate (Phase 3 plan 03-01 / ASM-01, ASM-02, ASM-03, ASM-05, ASM-08)** — `sections` table (migration 010) materializing per-document outline trees with content-hash anchors per ADR-003 H-7 (`sha256_hex(NFC(heading_text) || "\n" || render_blocks_to_plain_text(blocks))`). Parent-pointer reconstruction; per-section `chunk_id_first` / `chunk_id_last` ranges; denormalized `notes.status` column with partial index (`notes_status`) for the SQL-level superseded filter. Backfill from existing v1 vaults runs in lockstep with the migration so user vaults gain sections + status on first upgrade without re-indexing.
412
+ - **`get_outline` MCP tool** — return a nested `OutlineNode[]` tree for a `doc_id`. Each node carries `{anchor, heading_path, heading_text, level, chunk_ids: string[], children}` per D-02. Anchors are stable across re-indexings of unchanged content. Doc-level response envelope is the 8-field citation packet shape (Phase 3 plan 03-02 / ASM-02, ASM-05).
413
+ - **`search_sections` MCP tool** — section-level retrieval that COMPOSES the v1 chunk-level hybrid pipeline (`hybridSearch`) with a chunk-to-section promotion step. Accepts `{query, limit, vaults?, recency_weight?, authority_weight?, half_life_days?, include_superseded?}`. The promotion strategy: inflate `topK = limit × 5`, run hybrid once, promote each chunk hit to its enclosing section, dedupe by `(note_id, anchor)`, score each section as `MAX(constituent chunk scores)`, sort DESC tie-broken by `chunk_id_first` ASC, slice to `limit`, hydrate into `SectionHit` packets carrying the 8-field citation floor plus `{anchor, score, chunk_ids, snippet?}`. Preamble (level-0) sections are dropped — only sections with a non-empty `heading_path` surface. (Phase 3 plan 03-03 / ASM-03, ASM-05)
414
+ - **`get_document_bundle` MCP tool** — one-call composite read for a `doc_id`: `{anchor, outline, backlinks, forward_links, recent_edits}`. The anchor is a full 8-field citation packet with optional ASM-06 extras (`status`, `superseded_by`); `outline` re-uses `buildOutlineTree` from `get_outline` (composition, not duplication); `backlinks` + `forward_links` carry citation packets plus `{property_snippet, relation}` (relation is `"wikilink"` in v2.0.0 — see Phase 4 widening note below); `recent_edits` ≤10 entries from `audit_log`, with `is_memory_sink_write` surfaced only when truthy. (Phase 3 plan 03-04 / ASM-01, ASM-05, ASM-06)
415
+ - **`search_hybrid` rescore signals (Phase 3 plan 03-05 / ASM-06, ASM-07, ASM-08, ASM-11) — additive Zod params**, all defaulting to v1-no-op so legacy callers see byte-identical responses:
416
+ - `recency_weight: number` (default `0`) — adds `recency_weight × exp(-age_days / half_life_days)` to each candidate's RRF score.
417
+ - `authority_weight: number` (default `0`) — adds `authority_weight × 1` for docs with `properties.authoritative === true`.
418
+ - `half_life_days: number` (default `30`) — recency decay half-life.
419
+ - `include_superseded: boolean` (default `false`) — when `false`, excludes `status: "superseded"` chunks at SQL level via the `notes_status` partial index. Zero per-candidate frontmatter parses on the default-hide path.
420
+ - **9 new optional `SearchHit` fields** (D-08, ASM-06): `doc_id`, `source_handle`, `heading_path`, `mtime`, `hash`, `display_url`, `status`, `superseded_by`, `properties` — all snake_case to align with the Phase 2 `CitationPacket` shape; all optional and JSON-omitted on the v1-default path.
421
+ - **Display-URL resolver seam** — `HybridSearchOptions.displayUrlFor` optional closure keeps the `obsidian://` literal mint site in the obsidian-fs source adapter per ADR-002 §I-5b.
422
+ - **Clock-injection seam** — `HybridSearchOptions.clock` optional closure mirrors the recall convention; tests pass a fixed clock for deterministic age math.
423
+ - **`assemble_dossier` MCP tool** — resolve a `{type, key}` pair to an anchor `Document` and walk backlinks into a structured dossier `{anchor, linked_documents, property_rollups: {linked_count, linked_types, status_distribution}, error}` (Phase 3 plan 03-06 / ASM-04, ASM-05, ASM-10). Strict `properties.type` match (D-03); `key` matches the candidate's `title` OR any entry in `properties.aliases` (D-04). Every linked document carries an 8-field citation packet (D-01) plus the `relation` field. **v2.0.0 limitation:** `linked_documents[].relation` is always `"wikilink"` because the v1 `wikilinks` table only stores wikilink edges; Phase 4 (GRA-04 typed edges) will widen the field to the full `Edge.type` enum (mention, frontmatter-ref, hyperlink, …) as an additive change. Search for `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` for the widening sites.
424
+ - **Source-neutrality conformance suite for assembly tools (Phase 3 plan 03-07 / ASM-12)** — `src/adapters/source/conformance.test.ts` extends its `describe.each` parameterization with a new "Assembly tools — $name" section over `[obsidian-fs, stub-assembly]`. Five canonical assertions per adapter row (10 total) cover `get_outline`, `assemble_dossier`, `search_sections`, `get_document_bundle`, and citation-packet shape parity with recall's output. Backed by `src/adapters/stub/assembly-fixture.ts` — an 8-document purpose-built `Document[]` covering aliases, authoritative, superseded, all four edge types, and a multi-section doc. Per RESEARCH §7 P/R evals (ASM-10 dossier, ASM-11 recency) run against the obsidian-fs adapter only; the stub fixture is purpose-built for contract conformance.
425
+ - **Recency eval fixture** — `evals/fixtures/v2-test-vault/_queries/recency.yaml` ships ASM-11's stale-vs-fresh scenario: two near-duplicate Atlas-1 status notes (`status_updates/atlas-1-old-update.md` mtime ≈6 months back, `status_updates/atlas-1-new-update.md` mtime ≈1 day back) with neutral + recency-weighted query pair. Mtime contract is documented inline; eval harness `fs.utimesSync`-injects mtimes in a `beforeAll` block (git does not preserve mtimes on checkout).
426
+ - **Compiled brief layer (Phase 5, ADR-005)** — briefs are first-class `Document`s in `_memory/_briefs/` with chunk-level `source_hashes` provenance; compile-time wikilink emission (D-11), auto-supersede on target collision (D-12), and forward-only supersede chain. Writes route through `DeliveryAdapter` so the MEM-05 memory-namespace invariant is un-bypassable by construction. Closes the "agents rediscover 85% of context every run" failure mode that motivated the v2 program. See `docs/v2/PHASE-5-SIGN-OFF.md` + `docs/v2/adr/005-brief-compile-strategy.md`.
427
+ - **2 new MCP tools (Phase 5)** — `compile_brief`, `get_brief`. Tool count: 32 → 34 (additive only; the 32 Phase 4 entries verified byte-identical per-name in `evals/v1-baseline/tools-list.snapshot.json`; strict-equality snapshot test re-enabled in `baseline.test.ts`).
428
+ - **`vault-memory://briefs` MCP Resource (Phase 5 / BRF-09)** — pure-read discovery surface with optional `?target=<pattern>` substring filter. NOT a Tool; Resources do not count toward the REL-08 tool budget. `readListBriefs` is a closure over `MemorySinkRegistry` + `VaultManager` + `SourceConnector` and carries zero `fs` / `path` / `gray-matter` / `chokidar` imports (enforced by `scripts/lint-adapters.sh` + an explicit assertion in `src/brief/resources.test.ts` Test 10).
429
+ - **Brief staleness daemon (Phase 5 / BRF-05..08)** — `BriefStalenessDaemon` subscribes to `ChangeFeed.subscribe()`, runs a startup full scan against `brief_sources.listBriefDocIds()` (BRF-07; cursor in `daemon_state`), and preserves brief→source links across rename events via a 5-second pending-deletes grace window (BRF-08). Single-owner via `~/.vault-memory/locks/<vault>.lock` (`fs.open('wx')` + PID liveness); on lock contention emits `daemon_already_owned` and returns `{acquired: false}` without subscribing.
430
+ - **`default-brief-v1` MemoryContract (Phase 5)** — the provenance frame required by every brief `Document`. Required properties: `source: "agent"`, `confidence: "inferred"`, `evidence: parsedSourceDocIds`, `status: "active"`, `observed_at`, `superseded_by`, `type: "brief"`, `target`, `purpose`, `compiled_from`, `compiled_at`, chunk-level `source_hashes` map, and `model` audit attribution. The `_memory/_briefs/` sink binds to this contract at registration time.
431
+ - **ChunkId brand + content-stable fragment helpers (Phase 5)** — `parseChunkId`, `formatChunkId`, `decomposeChunkId`, `computeChunkHash`, `computeChunkIdFragment` in `src/chunker/chunk-id.ts`. D-04 content-derived 7-char fragments are essential for the v3 multi-user story (every user recomputes identical fragments over the same source text).
432
+ - **`OllamaClient.chat()` (Phase 5)** — `/api/chat` route alongside `/api/embed`; the first non-embedding LLM call in vault-memory history. Governed by ADR-005. No remote LLM SDK is bundled; the D-10 ladder probes MCP Sampling first (`getClientCapabilities().sampling`), then local Ollama via `[brief.ollama]` config, then falls back to `args.prepared_text` for deterministic CI / non-LLM contracts.
433
+ - **3 new eval YAMLs (Phase 5 / BRF-10, BRF-11):**
434
+ - `evals/fixtures/v2-test-vault/_queries/briefs-curated.yaml` — curated 20-document Atlas Robotics scenario; modifying one source flips the brief stale within one change-feed cycle.
435
+ - `evals/fixtures/v2-test-vault/_queries/briefs-staleness-stub.yaml` — BRF-11 cross-adapter parametric run (`adapters: [obsidian-fs, stub]`) proving brief-layer source-neutrality.
436
+ - `evals/fixtures/v2-test-vault/_queries/briefs-from-cluster.yaml` — cluster-fed compile scenario.
437
+ - **Cross-adapter conformance for brief tools (Phase 5)** — `src/adapters/source/conformance.test.ts` gains a `compile_brief + staleness daemon (BRF-11 source-neutrality)` describe block: 4 conformance test cases × 2 SourceConnector adapters = 8 test runs. Covers update flips stale, startup scan recovers missed events, delete past grace-window marks stale, and rename within grace-window preserves brief→source link.
438
+ - **Obsidian plugin (Phase 7, ADR-007)** — `plugin/` Obsidian community-plugin package (`manifest.json` v2.0.0, id=`vault-memory`, minAppVersion 1.5.0) with sideload-capable layout. `npm run build` emits a 1.9 MB `plugin/main.js`. Ships behind `[plugin] enabled = false` server-side; v1 baseline `tools/list` snapshot remains byte-identical when the flag is unset (FND-10 still passes). See `docs/v2/plugin/README.md` + `docs/v2/adr/007-contract-editor.md`.
439
+ - **Variant C three-pane contract editor (Phase 7 / CAN-01..CAN-07, CAN-10)** — palette + Svelte Flow canvas + Zod-derived inspector for editing Phase 6 task contracts. Canvas wires to `@xyflow/svelte` (MIT, license verified in ADR-007 — rescoped from the original jsoncanvas fork). `registerView('vault-memory-contract-editor')` + `registerExtensions(['contract'])` so opening a `.contract` file launches the editor.
440
+ - **`.contract` JSON envelope + lossless round-trip codec (Phase 7 / CAN-02, CAN-03, CAN-07)** — custom JSON format (`vmFormatVersion: 1`) with a pure-TS round-trip codec (`plugin/src/codec/contract-codec.ts`). Round-trip fixed-point pinned to 3rd/4th-emission byte identity across 4 fixtures (19/19 round-trip tests passing). Covers every ADR-006 field: `version`, `name`, `description`, `inputs`, `sources`, `sinks`, `assembly`, `output_shape`, `write_back`, `required`, `mcp_clients`.
441
+ - **3 reference `.contract` files (Phase 7 / CAN-06)** — `examples/contracts/meeting-prep.contract`, `project-status.contract`, `code-review-brief.contract`, each pinned to its Phase 6 YAML twin via 3 fixture round-trip tests.
442
+ - **6 new plugin-control MCP tools (Phase 7) — gated by `[plugin] enabled = true`, default OFF.** `set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`, `trigger_reindex`, `suppress_contract_write`. Default-OFF tool count: unchanged from Phase 6 (37); when the plugin flag is enabled the surface grows to 43. The v1-baseline tools-list snapshot remains byte-identical against the default-OFF surface — verified by `evals/v1-baseline/baseline.test.ts` "matches the pinned snapshot exactly" + "preserves the 23 v1 baseline tool names byte-identical".
443
+ - **Electron safeStorage-backed secrets (Phase 7 / PLG-02)** — `plugin/src/services/safe-storage.ts` wraps `window.electron.safeStorage` (`encryptString`/`decryptString`); only ciphertext lands in `data.json`. Secrets referenced by name from `[contracts.mcp_clients]` config via `${secret:name}` placeholders (regex `\$\{secret:([a-z][a-z0-9_-]{2,63})\}`). Connector-resolver test suite has 11 passing tests pinning the placeholder grammar + resolution path.
444
+ - **Plugin chrome — settings, reindex, stats, connectors panels (Phase 7 / PLG-01, PLG-03, PLG-04, PLG-05)** — `plugin/src/chrome/settings-tab.ts` (Ollama URL, model, indexer config; persisted via `loadData`/`saveData`; hot-swap via `set_runtime_config`), `reindex-panel.svelte` + controller (manual trigger with MCP `notifications/progress` feedback; SuppressionSet-aware), `stats-panel.svelte` + controller (read-only stats via `get_runtime_stats`), and `connectors-panel.svelte` (list/add/remove peer MCP clients, secrets-by-name). 42 chrome unit tests passing (7+8+7+12+8).
445
+ - **Hash-aware `SuppressionSet.consume(path, hash)` extension (Phase 7 / CAN-08)** — the Phase 1 `SuppressionSet` widens to accept a content hash so the contracts loader can short-circuit re-validation when the watcher fires on the plugin's own write. `src/contracts/loader.ts:242` calls `consume(resource, hash)` before reloading; `src/plugin-tools/suppress-contract-write.ts:109` registers the `(path, hash)` pair from the plugin BEFORE the write. Three dedicated loader tests cover the suppression path; regression watcher tests still pass (10/10 ObsidianFsChangeFeed + 6/6 VaultWatcher).
446
+ - **`vm-install` + `vm-update` Claude Code skills (Phase 7 / CAN-09 distribution half)** — one-liner curl-pipe install / upgrade for the plugin, with SHA-256 verification of the downloaded tarball. Idempotent. `bash skills/vm-install/setup.test.sh` 9/9 pass (fresh install + idempotent no-op); `bash skills/vm-update/update.test.sh` 12/12 pass (no-op + upgrade w/ SHA-256 + re-run). `RELEASE_URL_PLACEHOLDER` resolution is bookkept for the v2.0.0 GitHub Release publish (ROADMAP Phase 8 carryover).
447
+ - **6 plugin docs under `docs/v2/plugin/` (Phase 7 / CAN-09 docs half)** — `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section at the top of the repo README explains how to enable the flag and what the editor + chrome do.
448
+
449
+ ### Changed
450
+
451
+ - **Tool surface count: 30 → 32** (Phase 4, additive only). Two new graph tools added: `expand`, `cluster`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries + 7 Phase 3 entries remain content-identical (verified per-name; the strict-equality snapshot test `baseline.test.ts` "matches the pinned snapshot exactly" — which Plan 04-03 had `.skip`'d pending this regen — is re-enabled and green). The diff against the Phase 3 snapshot is purely additive: two new tool entries appended (before `assemble_dossier` to preserve insertion order at the end of `TOOLS`); one optional nested property added to `search_hybrid.inputSchema.properties` (`expand: {hops, direction?, edge_types?}`); `search_hybrid.description` widened additively to mention the new option.
452
+ - **`search_hybrid` accepts an additive nested `expand?: {hops: 1|2, direction?, edge_types?}` parameter (Phase 4 plan 04-04 / GRA-03).** Defaults to v1-no-op when omitted; legacy callers see byte-identical responses. When supplied, each `SearchHit` gains an `expansions?: CitationPacketWithVia[]` field carrying the per-hit typed-edge neighborhood; ranking is unchanged. Per-vault BFS isolation is enforced inside `expand()` (T-04-04-02 mitigation). Failures inside `expand()` are silently swallowed — same posture as the reranker fallback — so a corrupted graph traversal cannot break search.
453
+ - **Graph result rows widen from `relation: "wikilink"` to `relation: EdgeType` (Phase 4 plan 04-01).** `assemble_dossier.linked_documents[].relation`, `get_document_bundle.{backlinks,forward_links}[].relation`, and the v1 `BacklinkEntry`/`ForwardLinkEntry` `relation` fields now emit the actual edge type the indexer recorded. The Phase 3 known-limitation pinning these to `"wikilink"` is closed; the `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` have been retired. This is strictly an additive widening — old callers that only checked for `"wikilink"` still receive that value when the underlying edge is a wikilink.
454
+ - **Indexer dual-writes to `wikilinks` + `edges` tables (Phase 4 plans 04-01, 04-02).** Both indexer write paths (`src/indexer/single.ts` body-hash fast path + full re-embed branch; `src/indexer/indexer.ts` full index path) call `writeAllEdges` after parsing each note, threading the `WikilinkResolver` for cross-note target resolution. The v1 `wikilinks` table is preserved for D-01 v1 invariance; v3 cleanup will drop it once no v1 tool reads from it.
455
+ - **`@modelcontextprotocol/sdk` bumped to `^1.29.0`**; tool registration migrated from the v1 low-level `Server` + `setRequestHandler` pattern to `McpServer` + `server.registerTool()` × 23. Raw JSON Schema literals flow directly from `src/tool-registry.ts` (workaround for SDK#1143 / Zod-4 description-drop, although the issue is empirically MOOT in SDK 1.29). (ADP-08)
456
+ - **`zod` bumped to `^4.4.3`**. Refinements and `errorMap` swept per the Zod 4 migration guide; Standard Schema wiring intact. (ADP-09)
457
+ - **Default `client_id` for write / update / delete** is now captured from MCP `InitializeRequest.params.clientInfo.name` via a lazy closure (`getClientId()` in `src/server.ts`), falling back to `"unknown"`. Removes the v1 hardcoded `"claude-code"` default that misidentified every non-Claude write in the audit log. (D-02)
458
+ - **`obsidian://` display-URL minting** moved from `src/server.ts:obsidianUrl()` (deleted) into `SourceConnector.formatDisplayUrl(id)` on the adapter. The `obsidian://open?vault=…&file=…` URL is now adapter-published; future adapters mint their own scheme. (D-01)
459
+ - **README rewritten** to lead with "any MCP-aware agent" framing in the first 20 lines. Equal billing for Claude Code / Claude Desktop / ChatGPT Custom Connectors / MCP Inspector / generic clients. Obsidian framed as the v2 source connector, not the sole consumer. (ADP-14)
460
+ - **`src/cli.ts` user-facing strings** swept for Claude-leak — `"Open ${path} in Claude Code"` → `"Open ${path} in your MCP-aware client"`. (D-02 follow-through)
461
+ - **`WriteConflict.reason` discriminated union extended** (additively, backwards-compatible) with 7 new codes: `missing_provenance`, `invalid_provenance`, `supersede_mismatch`, `agent_write_outside_sink`, `non_agent_write_inside_sink`, `sentinel_missing`, `sink_write_blocked`. Optional envelope fields added: `sinkName`, `key`, `observedValue`, `suggestion`. The 3 Phase 1 reason codes (`hash_mismatch`, `permission_denied`, `not_found`) are unchanged (Phase 2 plan 02-03 / MEM-05, MEM-07, MEM-11).
462
+ - **v1 `write_note`, `update_frontmatter`, `delete_note` now refuse memory-sink-resolved targets** at the tool entry-point with `sink_write_blocked` + actionable `suggestion` text (`record_observation` for writes/updates, `supersede` for deletes). Defense-in-depth on top of the centralized `DeliveryAdapter.write()` chokepoint (Phase 2 plan 02-03b / MEM-07). When the `MemorySinkRegistry` is not wired (Phase 1 fixture-test path), v1 tool behavior is byte-identical to Phase 1.
463
+ - **`audit_log` tool gains optional `is_memory_sink_write` input filter** and adds `is_memory_sink_write: boolean` to each returned row. The MCP `description` text is byte-identical to Phase 1 — the new capability is documented in CHANGELOG and `docs/tools/audit_log.md` only (Phase 2 plan 02-06 / MEM-08).
464
+ - **Server bootstrap order** is now `loadConfig → manager.openAll → registerMemorySinks (writes sentinels) → server.connect → startCatchupAndWatchers (fire-and-forget)`. Sentinels are provisioned BEFORE the catch-up indexer walks the fixture, so the registry's `findSinkContaining` enclosure check is hot for the first incoming write (Phase 2 plan 02-03b). A new exported `BootstrapPhase` type + optional `onPhase` callback on `serve(options?)` make the bootstrap order observable for testing.
465
+ - **Assembly DocId minting derives scheme from `SourceConnector.handle`** (Phase 3 plan 03-07 fix). Pre-03-07 both `assembleDossier` and `getDocumentBundle` hardcoded `formatDocId("obsidian-fs", …)` when constructing linked-document / anchor DocIds. The 03-07 ASM-12 conformance suite (parameterized over obsidian-fs + stub adapters) surfaced this as a hard test failure on the stub-assembly row. The fix introduces `schemeFromSource(SourceConnector): string` and derives the scheme dynamically; `dossier.ts`'s sort-key helper is renamed `noteDocIdString → noteSortKey` and pinned to a fixed `vault://` prefix so sort order stays adapter-identical. No user-visible change for obsidian-fs callers (their scheme has always been `obsidian-fs://`); the fix unblocks future non-Obsidian sources.
466
+ - **Tool surface count: 26 → 30** (additive only). Four Phase 3 assembly tools added: `get_outline`, `search_sections`, `get_document_bundle`, `assemble_dossier`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries at slots 0–22 remain byte-identical (pinned by the existing `baseline.test.ts` "preserves the 23 v1 baseline tool names byte-identical" assertion). The diff against the Phase 2 snapshot is purely additive: four new tool entries appended; one optional field added to `search_hybrid.inputSchema.properties` (the four rescore params).
467
+ - **Tool surface count: 32 → 34** (Phase 5, additive only). Two new brief tools added: `compile_brief`, `get_brief`. The `list_briefs` discovery surface lives on MCP Resources (`vault-memory://briefs`) and does NOT count toward the tool budget — same precedent as the Phase 2 `vault-memory://memory/*` Resources. The 32 Phase 4 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (verified per-name); the diff is purely additive — two new tool entries appended.
468
+ - **Server bootstrap order extended (Phase 5)** — after `registerMemorySinks` and before `server.connect`, the bootstrap now (a) registers the `_memory/_briefs/` sink bound to the `default-brief-v1` contract and (b) starts `BriefStalenessDaemon` per vault. Daemon `start()` is fire-and-forget: a lock-contention failure does NOT block server startup — the server proceeds and `compile_brief` continues to work; only the staleness re-check is offline.
469
+ - **`SuppressionSet.consume()` accepts an optional `hash` argument (Phase 7 / CAN-08, additive).** Phase 1 callers that pass only a path see byte-identical behavior. When the plugin write path passes `(path, hash)`, the contracts loader at `src/contracts/loader.ts:242` matches both axes before short-circuiting — so a benign re-emit of the same canonical bytes is suppressed, but a divergent write still triggers reload.
470
+ - **Default-OFF plugin tool gating (Phase 7).** The 6 new plugin tools are wired through a config-time guard in `src/server.ts` so `tools/list` does NOT include them unless `[plugin] enabled = true`. The default-OFF surface keeps the v1 baseline snapshot byte-identical and keeps the REL-08 budget conversation focused on the user-facing v2 tools.
471
+
472
+ ### Dependencies
473
+
474
+ - **Phase 4 (plan 04-05 / GRA-02)** — pure-JS ESM, all MIT, all manually provenance-verified per Phase 4 RESEARCH §Package Legitimacy Audit:
475
+ - `graphology ^0.26.0` — graph data structure for the Louvain cluster wrapper.
476
+ - `graphology-communities-louvain ^2.0.2` — Louvain modularity-maximizing community detection.
477
+ - `seedrandom ^3.0.5` — deterministic seeded PRNG for the Louvain `rng` option.
478
+ - `@types/seedrandom ^3.0.8` (dev) — TypeScript types.
479
+ - No native bindings; no tsup `external` additions needed. ESM bundle grew from 376 KB to 392 KB (+13 KB).
480
+ - **Phase 7 plugin (ADR-007)** — added under `plugin/package.json` only; the server bundle is unaffected:
481
+ - `@xyflow/svelte` — Svelte Flow canvas renderer for the Variant C editor (MIT, license verified in ADR-007).
482
+ - `svelte`, `esbuild`, `vitest` — plugin-only build/test toolchain. None ship into the server `dist/`.
483
+
484
+ ### Migration
485
+
486
+ - **MIGRATION_011 — `edges` table + chunked backfill from `wikilinks` (Phase 4 plan 04-01).** Function-style migration mirroring `runMigration008`. Creates the `edges` table with `CHECK(type IN ('wikilink', 'mention', 'frontmatter-ref', 'hyperlink'))` and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE(anchor, '')` + `COALESCE(line_number, 0)` for proper NULL-as-distinct dedup. Backfills wikilink rows from the v1 `wikilinks` table in 10k-row chunks via `INSERT OR IGNORE` — idempotent, re-runnable, safe to interrupt. v1-shaped DBs migrate cleanly on first server start after upgrade; the v1 `wikilinks` table is preserved alongside the new `edges` table for D-01 v1 invariance. v3 cleanup will drop `wikilinks` once no v1 tool consumes it.
487
+ - **`notes.doc_uri` dual-column staging (Strategy A — additive, plan 01-02):**
488
+ - **MIGRATION_007** (additive) — adds nullable `doc_uri TEXT` column to `notes` + `idx_notes_doc_uri` index. Backwards-compatible; existing rows have `doc_uri IS NULL` post-migration.
489
+ - **MIGRATION_008** (function-style backfill) — sets `doc_uri = 'obsidian-fs://<vault>/' || path` for every row where `doc_uri IS NULL`. Idempotent; safe to re-run.
490
+ - Path stored **un-encoded** in the column; percent-encoding happens only at `formatDisplayUrl()` time (per ADR-002 §URL semantics).
491
+ - **No user action required.** Migrations run automatically on first server start after upgrade. SQLite transaction rollback is the safety net; no pre-migration backup is performed in v2 (deferred to Phase 8 per CONCERNS §"No Pre-Migration DB Backup"). (ADP-07)
492
+ - **v1 tool surface preserved byte-for-byte.** All 23 tool names, input schemas, output envelopes, and descriptions are unchanged. `evals/v1-baseline/tools-list.snapshot.json` regenerated under SDK 1.29 with zero diff against the Phase-0 baseline (W6 human-verify checkpoint passed in plan 01-06 Task 06).
493
+ - **MIGRATION_010 — sections table + notes.status column + section backfill (Phase 3 plan 03-01).** Three ordered steps in one transaction: (a) `CREATE TABLE sections` with 3 indexes, (b) `notes.status` column added + partial index `notes_status WHERE status IS NOT NULL`, (c) backfill `notes.status` from `json_extract(frontmatter, '$.status')` AND derive section rows for every indexed note via `markdownToSectionBlocks → extractSections`. Idempotent; re-applying is a no-op. Anchor-equivalence guarantee: backfilled section anchors match a fresh re-index byte-for-byte (the indexer + backfill share `src/chunker/headings.ts:extractHeadings` as the canonical heading source). v1-shaped DBs migrate cleanly without re-indexing.
494
+ - **`write_audit` migration v9** (function-style, idempotent) adds `is_memory_sink_write INTEGER NOT NULL DEFAULT 0` + a partial index `idx_write_audit_memory_sink ON (is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` (Phase 2 plan 02-06 / MEM-08). All Phase 1 audit rows backfill to `false`; new audit rows derive the flag from `WriteOptions.sink !== undefined` at the `ObsidianFsDelivery.write/update/delete` facade. The function-style migration runs `PRAGMA table_info` first so fixture tests that rewind `user_version` continue to pass.
495
+ - **v1 tool surface grows from 23 → 26** with the three Phase 2 memory tools (`record_observation`, `recall`, `supersede`). The 23 v1 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (pinned by a new `baseline.test.ts` assertion). The only diff on v1 tools is one optional input field added to `audit_log.inputSchema` (`is_memory_sink_write`); the v1 description text is byte-identical.
496
+ - **MIGRATION_013 — `chunks.chunk_id_fragment` + `brief_sources` + `daemon_state` (Phase 5 plan 05-01).** Three additive steps in one transaction: (a) `chunks.chunk_id_fragment` TEXT column added (`noUncheckedIndexedAccess` consumers see the new column as `T | undefined` until populated by the next index run); (b) `brief_sources` table (D-06 reverse-index — `(brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)`); (c) `daemon_state` table (per-vault change-feed cursor + `last_full_scan` timestamp). Idempotent; re-applying is a no-op. v1-shaped DBs migrate cleanly on first server start after upgrade.
497
+ - **Phase 5 brief artifacts are NOT a server schema migration.** Brief `Document`s land in `_memory/_briefs/` via the standard `DeliveryAdapter.write()` path; the `default-brief-v1` MemoryContract is registered at bootstrap (not persisted in SQLite). Users who never call `compile_brief` see zero new files on disk.
498
+ - **Phase 7 plugin is NOT a server schema migration.** No new SQLite migrations land for Phase 7. The plugin stores its own state in `data.json` (loadData/saveData) inside the Obsidian vault's plugin directory; secrets land as Electron safeStorage ciphertext only. Users who never enable the plugin flag see zero new server-side files.
499
+
500
+ ### Documentation
501
+
502
+ - Relocate ADRs 001–004 from `docs/dev/` (gitignored) to public `docs/v2/adr/`; amend each with Invariants + Examples sections covering both `obsidian-fs://` and `notion-api://` worked examples (FND-01, FND-04).
503
+ - ADR-003 amended with explicit hash-semantics pseudocode (RFC 8785 JCS, NFC normalisation, LF line endings, IEEE-754 number canonicalization) + chunk-level `source_hashes` schema (FND-02).
504
+ - ADR-004 amended: folder-default `MemorySink` is the only code path; separate-vault is config-only via `[memory] sink = "@…"` (FND-03). `.memory-sink` sentinel mandated.
505
+ - ADR-004 amended: underscored PropertyBag keys, confidence enum aligned to [direct, inferred, uncertain], superseded_reason field added (Phase 2 plan 02-01).
506
+ - Publish `docs/v2/ARCHITECTURE.md` (L0–L4 layer model + responsibility map), `docs/v2/MEMORY_CONTRACT.md` (provenance property contract on `Document.properties`), `docs/v2/AGENT_AGNOSTIC.md` (MCP-canonical client stance) (FND-05/06/07).
507
+ - Eval fixture vault `evals/fixtures/v2-test-vault/` — "Atlas Robotics" narrative; 56 notes across `projects/`, `meetings/`, `people/`, `decisions/`, `references/`; 15-document `_memory/` subset; 7 hand-labeled `_queries/*.yaml` (FND-08).
508
+ - v1-baseline regression suite `evals/v1-baseline/` — `tools-list.snapshot.json` pin for `tools/list` (23 tools), 11 per-tool semantic-floor YAMLs, `baseline.test.ts` vitest runner with `.todo` placeholders for Phase 1 precision/recall (FND-09/10).
509
+ - CI gates `scripts/check-fixture-privacy.sh` + `scripts/lint-no-telemetry.sh` + `.github/workflows/ci.yml` running `npm run lint:check && npm test` on every PR and push to `main` (FND-11/12 + D-21).
510
+ - ADR index `docs/v2/adr/README.md` listing 4 Accepted ADRs + 14 Open ADR stubs for v3 / Phase 10 follow-ups, including a Deferred-v3 section for adversarial-review findings (FND-13).
511
+ - Adversarial review `docs/v2/adr/ADVERSARIAL-REVIEW.md` — 10 findings against ADRs 001–004 raised by a fresh-context advisor; 6 Amended in Phase 0 (ADR-001 I-6, ADR-002 `DocumentRef.hash` contract + `hashProtected` enum, ADR-003 H-6), 4 Deferred-v3 to Phase 10 / Notion connector (FND-04 + FND-14 sign-off).
512
+ - Sign-off artifact `docs/v2/SIGN-OFF.md` — FND-01..14 checklist with resolving commit SHAs; PR approval is the FND-14 audit trail per D-17.
513
+ - Internal: extract `TOOLS` constant from `src/server.ts` to `src/tool-registry.ts` so `evals/v1-baseline/dump-tools.mjs` can produce the pinned snapshot without spinning the full MCP server (Phase 0 Assumption A5, no external behavior change).
514
+ - Phase 5 sign-off artifact `docs/v2/PHASE-5-SIGN-OFF.md` — BRF-01..BRF-11 traceability table + the five Phase 5 ROADMAP success criteria with disposition (`✅ MET` per criterion) + REL-08 hand-off plan to Phase 8.
515
+ - ADR-005 `docs/v2/adr/005-brief-compile-strategy.md` (Accepted) — governs the D-10 LLM strategy ladder and forbids bundling any remote LLM SDK in the server.
516
+ - Phase 7 plugin docs under `docs/v2/plugin/`: `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section added to the repo root README explaining how to flip `[plugin] enabled = true` and what the editor + chrome surfaces do.
517
+ - ADR-007 `docs/v2/adr/007-contract-editor.md` (Accepted, 2026-05-19) — rescopes the visual editor from a jsoncanvas fork to `@xyflow/svelte` (MIT, license verified inline in the ADR).
518
+
519
+ ### Deprecated (Phase 8 / REL-08)
520
+
521
+ The following five v1 tools are deprecated in v2.0.0 and promoted to MCP Resources. Each tool remains callable through the v2.x line for backwards compatibility; removal is scheduled for v3.0.0. The Resource URI is the canonical replacement.
522
+
523
+ - **`list_vaults`** → `vault-memory://vaults` (cross-vault discovery Resource). The tool remains callable through v2.x; removal scheduled for v3.0.0.
524
+ - **`list_models`** → `vault-memory://models/{vault}` (per-vault embedding-model inventory). The tool remains callable through v2.x; removal scheduled for v3.0.0.
525
+ - **`recent_notes`** → `vault-memory://recent/{vault}` (per-vault mtime-DESC recent notes). The tool remains callable through v2.x; removal scheduled for v3.0.0.
526
+ - **`vault_stats`** → `vault-memory://stats/{vault}` (per-vault note count + top tags + top frontmatter keys + last index run). The tool remains callable through v2.x; removal scheduled for v3.0.0.
527
+ - **`list_backlinks`** → `vault-memory://backlinks/{vault}/{+docId}` (per-document backlink listing). The URI uses RFC 6570 reserved expansion: `{+docId}` allows `/` in the variable value so multi-segment `docId`s like `obsidian-fs://my-vault/notes/sub/file.md` parse correctly. The tool remains callable through v2.x; removal scheduled for v3.0.0.
528
+
529
+ ## [2.0.0-rc.4] — 2026-05-20
530
+
531
+
532
+ ### Changed
533
+
534
+ - **Obsidian plugin error messages reference `/vmem:install`** (was: `vm-install skill` / `/vm-install`). Two strings updated in `plugin/src/services/mcp-client.ts` (the `CliNotFoundError` thrown in both the spawn-time and connect-time error paths) and one banner string in `plugin/src/chrome/settings-tab.ts` (the "CLI not found" banner in the settings tab). The new strings point users at the renamed marketplace plugin `vmem` (replacing the old `install-vault-memory` with its duplicated `/install-vault-memory:install-vault-memory` slug) and the verb-split surface: `/vmem:install`, `/vmem:health`, `/vmem:reindex`. See `ISSUE-marketplace-plugin-rename-vmem.md`.
535
+ - **`CHANGELOG.md` now ships in the npm tarball** — added to the `files` field in `package.json` so users running `npm view @owrede/vault-memory@<version>` or extracting the tarball can read per-release notes without leaving npm.
536
+ - **`publish.yml` adds an `NPM_DISABLE_PROVENANCE` escape hatch** — repo variable `NPM_DISABLE_PROVENANCE=1` skips the `--provenance` flag on `npm publish`. Recovery path for sigstore-attestation-orphan states. Default off; provenance stays on for all normal publishes.
537
+
538
+ ### Skipped: v2.0.0-rc.3
539
+
540
+ - **v2.0.0-rc.3 was tagged but never published to npm.** The first publish attempt for rc.3 wrote a sigstore log entry but the actual package PUT failed with `404 Not Found` from the npm registry; multiple subsequent retries with different tarball contents and provenance disabled all hit the same 404 (registry-internal poisoned state). rc.4 supersedes rc.3 and contains every change rc.3 would have shipped.
541
+
542
+ ### Fixed
543
+
544
+ - **Migration 010 — `UNIQUE constraint failed: sections.note_id, sections.anchor`** crash blocking v0.9.x/v1.0.0 → v2.0.0-rc.1 upgrade for any user whose vault contains heading-only sibling sections (e.g. template scaffolds with repeated `## TODO` headings whose body slot is empty). `extractSections` produces identical content-hash anchors for sections with identical `(heading_text, body)` pairs, and the `INSERT INTO sections` was plain so the second sibling aborted the migration transaction — leaving every CLI command unreachable for every configured vault until the bad DB was removed from `config.toml`. `backfillSectionsFromChunks` now uses `INSERT OR IGNORE`; on collision the surviving row's id is looked up and reused so the `insertedIds` slot still resolves later children's `parent_id` correctly. See `ISSUE-migration-010-duplicate-anchor.md`.
545
+
546
+ ### Added
547
+
548
+ - **Task Contract DSL (Phase 6, ADR-006)** — declarative YAML contracts under `_contracts/<name>.yaml`, addressable by name, instantiable via MCP, with handle-based source/sink portability. Contracts use a closed assembly verb enum (11 baseline + `literal` + `mcp://<server>/<tool>` peer extension), `{{template}}` step composition, JSON-Schema-with-`$ref` inputs, MemorySink-only sinks (un-bypassable per D-A4c), and ChangeFeed hot reload (D-LOAD). See `docs/v2/PHASE-6-SIGN-OFF.md` + `docs/v2/adr/006-task-contract-dsl.md`.
549
+ - **3 new MCP tools (Phase 6)** — `describe_contract`, `instantiate_contract`, `register_contracts_as_tools`. Tool count: 34 → 37 (additive only; v1-baseline preserved byte-identical).
550
+ - **2 new MCP Resources (Phase 6)** — `vault-memory://contracts/{vault}` (CON-04) lists contracts available in a vault; `vault-memory://contract-verbs/{vault}` (D-A2b) lists baseline + custom `mcp://` verbs with invocation counts. Resources do NOT count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.
551
+ - **3 reference contracts (Phase 6)** under `evals/fixtures/v2-test-vault/_contracts/`: `meeting-prep`, `project-status`, `code-review-brief`. Plus `smoketest-trivial` for the CON-09 non-Claude smoketest path (literal-only assembly; no LLM needed in CI).
552
+ - **`contract_audit` table (Phase 6, migration 014)** — orchestration audit substrate; one row per assembly step (kind=`contract_step`) and one row per load failure (kind=`contract_load_error`). Payload-free per Invariant C-5 (peer-MCP outputs may carry sensitive data; we never capture them). Feeds `aggregateVerbUsage(vault)` for the D-A2b promotion signal.
553
+ - **`[contracts]` config block (Phase 6)** — `auto_register_tools: bool = false`, `tool_prefix: string = "vm_"`, `step_timeout_seconds: number = 30`, `defaults: Record<string, string> = {}`, `mcp_clients: Record<string, {command, args?, env?}> = {}`.
554
+ - **`instantiate_contract` write_back chokepoint (Phase 6)** — routes through `DeliveryAdapter.write()` so the MEM-05 invariant is un-bypassable by construction. Sink overrides MUST resolve through `MemorySinkRegistry.resolveMemorySink()` before write; otherwise `{ok:false, reason:"sink_override_not_a_memory_sink"}`.
555
+ - **CON-10 stub-parity proof (Phase 6)** — `src/adapters/source/conformance.test.ts` gains a `contracts stub-parity (CON-10)` describe block proving the same contract produces structurally identical bundles across obsidian-fs and stub source connectors.
556
+ - **CON-09 non-Claude smoketest extension (Phase 6)** — `scripts/smoketest-non-claude.mjs` spawns the server against a temp HOME with the fixture vault, asserts the three contract tools surface, calls `describe_contract` + `instantiate_contract` + reads the `list_contracts` Resource, and exits 0.
557
+ - **`ObsidianFsSource` widened to enumerate `_contracts/*.yaml` (Phase 6)** — non-recursive walk added via `scanContractFiles`; `scanVault` unchanged so the indexer's `.md`-only contract is preserved. `readDocument` handles YAML files by returning raw text as a single paragraph block (bypasses `parseNote`'s markdown assumptions).
558
+ - **`expand` MCP tool (Phase 4 plan 04-03 / GRA-01)** — typed-edge BFS retrieval primitive. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying `via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped at 2 (v2.0.0). Default direction `'both'`. Filterable by `edge_types: EdgeType[]` and by `filter_properties` (strict equality, no operators). Memory-sink documents (`_memory/...`) surface only when transitively reachable from a user-note seed via at least one in-result inbound edge — per ADR-004 memory-namespace opacity rule. Unknown `seed_doc_ids` do NOT throw — they are returned in a `warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins on dedup; ties broken by `(seed_doc_id, edge_type, direction)`.
559
+ - **`cluster` MCP tool (Phase 4 plan 04-05 / GRA-02)** — Louvain community detection over the typed-edge graph, deterministic across runs via `seedrandom`. Accepts mutually-exclusive `query | seed_doc_ids`. Returns `{ok: true, communities: Cluster[]}` or `{ok: false, reason: 'too_many_nodes' | 'missing_input' | 'mutually_exclusive_input'}`. 5000-node hard cap with `force: true` override. Per-community `cluster_id` is the smallest member DocId by lexicographic order — deterministic naming independent of internal community index assignment. Hybrid-search dependency injected via `ClusterDeps.hybridSearch` closure to avoid the `src/graph/cluster.ts → src/search/hybrid.ts` circular import.
560
+ - **`search_hybrid({expand})` additive nested param (Phase 4 plan 04-04 / GRA-03)** — pass `expand: {hops: 1|2, direction?, edge_types?}` to auto-attach 1–2 hop typed-edge neighbors as `SearchHit.expansions?: CitationPacketWithVia[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged. Guard-and-short-circuit composition: when `expand` is omitted, the code path is byte-identical to Phase 3 (zero new DB reads, zero new JSON fields).
561
+ - **Typed-edge storage substrate (Phase 4 plan 04-01 / GRA-04)** — `edges` table (migration 011) with four edge types (`wikilink`, `mention`, `frontmatter-ref`, `hyperlink`) and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE` for proper NULL-as-distinct dedup. Chunked backfill from v1 `wikilinks` table (10k-row chunks, `INSERT OR IGNORE`, idempotent). The v1 `wikilinks` table is preserved alongside `edges` for v1 invariance (D-01); v3 cleanup is tracked.
562
+ - **Unified edge extractor (Phase 4 plan 04-02 / GRA-04 indexer)** — `extractAllEdges(vault, parsed, resolver): EdgeInput[]` extracts all four edge types in a single per-note parse pass. `MIN_MENTION_LEN = 4` (empirically validated against the Atlas Robotics fixture: 0 raw FPs over 62 notes). `FRONTMATTER_REF_ALLOWLIST` is a sealed 8-key `ReadonlySet<string>` (`assignee`, `owner`, `project`, `related`, `parent`, `child`, `attendees`, `superseded_by`). Mention candidate set built from `note_aliases` only; word-boundary uses `(?<![\w-])`/`(?![\w-])` so aliases containing `-`/`_` are matched as whole tokens. Paragraph-scope masking blanks fenced code, ATX headings, inline backticks, and `[[wikilink]]` spans (byte length preserved so line-offset lookups stay valid).
563
+ - **`AliasesQueries.listAll()` (Phase 4 plan 04-02)** — full alias inventory, sorted by `alias_norm ASC` for deterministic mention-regex compilation.
564
+ - **Additive `type: EdgeType` field on graph result rows (Phase 4 plan 04-01)** — `list_backlinks`, `list_forward_links`, `find_broken_links` results widen with `type`. `BacklinkEntry.relation` / `ForwardLinkEntry.relation` widen from the v2.0.0-pinned `"wikilink"` literal to the full `EdgeType` union. `assemble_dossier.linked_documents[].relation` and `get_document_bundle.{backlinks,forward_links}[].relation` widen the same way — the `PHASE-4-WIDEN` marker comments in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` (a Phase 3 v2.0.0 limitation) are now retired.
565
+ - **3 new eval YAMLs (Phase 4 plan 04-06 / GRA-05):**
566
+ - `evals/fixtures/v2-test-vault/_queries/expand.yaml` — 8 hand-curated queries over the Atlas Robotics live fixture covering all four edge types, mixed-type traversal at hops 1 and 2, `_memory/` opacity, and the unknown-seed warning path. All eight clear `min_precision >= 0.8` / `min_recall >= 0.8`.
567
+ - `evals/fixtures/v2-test-vault/_queries/search-hybrid-with-expand.yaml` — 3 composition queries exercising `search_hybrid({expand: {hops}, ...})` end-to-end.
568
+ - `evals/fixtures/v2-test-vault/_queries/cluster.yaml` — D-12 byte-snapshot of Louvain partition (cluster_id + sorted member DocIds per community); pins determinism across library upgrades + Node-minor versions.
569
+ - **Cross-adapter conformance for graph tools (Phase 4 plan 04-06)** — 6 new parameterized cases in `src/adapters/source/conformance.test.ts` run `expand()` + `cluster()` against both `obsidian-fs` and `stub` adapters via `src/graph/__test_helpers__/atlas-live-fixture.ts`. New harness fields (`graphSeedDocId`, `occludedMemoryDocId`, `reachableMemoryDocId`) carry adapter-specific reference DocIds.
570
+ - **Adapter seams (Phase 1, plans 01-01..06)** — `SourceConnector` / `DeliveryAdapter` / `ChangeFeed` interfaces under `src/adapters/` per ADR-002. `obsidian-fs` is the v2 reference implementation for all three. The seam shape is preserved across vault-content read, vault-content write, and change-event paths so future connectors (Notion, Logseq, …) drop in without touching `src/server.ts` or any v1 tool. (ADP-01, ADP-02, ADP-03)
571
+ - **Canonical v2 types** in `src/types.ts`: `Document`, `BlockNode`, `Edge`, `ChangeEvent`, `SourceHandle`, `MemorySink`, branded `DocId`, `WikilinkRef`. `Document.properties: Record<string, unknown>` subsumes both YAML frontmatter and future Notion typed properties. (ADP-04, ADP-05)
572
+ - **`src/adapters/registry.ts`** — adapter registry + sole minting point for branded `DocId`s via `parseDocId` / `formatDocId`. Future adapters register under their canonical scheme (`obsidian-fs://`, `notion-api://`, …). (ADP-05)
573
+ - **Stub-adapter conformance suite** — parameterized over `obsidian-fs` and `stub`, three test files (`src/adapters/{source,delivery,change-feed}/conformance.test.ts`). Lets future adapters validate themselves against the v2 contract before any production code lands. (ADP-13)
574
+ - **`scripts/lint-adapters.sh`** — POSIX shell CI gate enforcing ADR-002 Invariants I-1..I-6 + C-1 (Claude-leak) + I-5b (`obsidian://` literal). Wired into `npm run lint:check` and `.github/workflows/ci.yml`. (ADP-12)
575
+ - **`scripts/smoketest-non-claude.mjs`** — end-to-end smoketest against the real MCP SDK Client (identifies as `non-claude-smoketest`); CI-gated. Asserts 23 tools listed, all descriptions non-empty, `tools/call list_vaults` succeeds, `tools/call <bogus>` surfaces as error. (ADP-10)
576
+ - **`docs/v2/AGENT_AGNOSTIC_AUDIT.md`** — per-leak inventory of every Claude / Obsidian assumption in `src/`, with explicit `fixed-v2` / `mixed` / `deferred-v3` status + rationale per row. Cross-references CONCERNS.md, ADR-002, and the resolving Phase-1 plans. (ADP-11)
577
+ - **`record_observation` MCP tool** — write a labeled memory observation through a configured `MemorySink` with mandatory provenance (Phase 2 plan 02-04 / MEM-02). Sugar args (`claim`, `evidence`, `confidence`, `type`, `sink?`) pre-fill the contract-required keys; optional `properties: Record<string, unknown>` escape hatch merges LAST so callers can populate any contract-allowed extra (D-02).
578
+ - **`recall` MCP tool** — retrieve memory documents filtered by `min_confidence`, `types`, `max_age_days`, and `sink`; returns Phase 3-shaped citation packets `{doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties}` (Phase 2 plan 02-05 / MEM-03 / D-01). Hides `status: superseded` by default; sorts `observed_at` DESC with `mtime` tiebreak; truncates AFTER filter+sort.
579
+ - **`supersede` MCP tool** — forward-only mark of a memory document as superseded by a replacement, with mandatory non-empty `superseded_reason` (Phase 2 plan 02-04 / MEM-04 / D-03). Single OCC `delivery.update()` call; the replacement document is never touched (back-edge derivation deferred to Phase 4 graph layer).
580
+ - **`vault-memory://memory/sinks` MCP Resource** — lists configured memory sinks per vault with their handle, contract name, and default-flag (Phase 2 plan 02-06 / MEM-09).
581
+ - **`vault-memory://memory/stats` MCP Resource** — per-sink doc counts, `by_type` / `by_status` breakdown, last-write timestamp aggregated from the indexed `notes` table + the `write_audit` partial index (Phase 2 plan 02-06 / MEM-09). Polled-only; no `notifyResourceUpdated` in v2.0.0.
582
+ - **`is_memory_sink_write` column on `write_audit`** (migration v9) + partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` + optional `is_memory_sink_write` filter on the `audit_log` tool input schema (Phase 2 plan 02-06 / MEM-08).
583
+ - **`decomposeDocId` helper** on `src/adapters/registry.ts` for splitting `DocId`s into `{scheme, authority, resource}` parts. Re-uses `DOC_ID_PATTERN`; no second regex (Phase 2 plan 02-02).
584
+ - **`pathInSink` + `joinVaultPath` helpers** in `src/adapters/delivery/obsidian-fs/path.ts` — the SOLE licensed sink/vault `path.join` sites for Phase 2+ per ADR-002 I-3 (Phase 2 plan 02-02).
585
+ - **MemorySink runtime** — `parseMemorySinkHandle` (IIFE-closed brand mint), `MemorySinkRegistry` (sole resolver per ADR-004 §Resolution), `.memory-sink` sentinel mechanics in `src/adapters/delivery/obsidian-fs/sentinel.ts`, hardcoded `DEFAULT_MEMORY_V1` contract + YAML loader at `src/memory/contract/` (Phase 2 plan 02-02 / MEM-01, MEM-05, MEM-06).
586
+ - **5 net-new memory fixture docs** under `evals/fixtures/v2-test-vault/_memory/` including the A→B→C Spire-budget supersede chain (2026-04-23 → 2026-04-24 → 2026-04-26), plus net-new `type: hypothesis`, `type: decision`, and `confidence: uncertain` dimensions (Phase 2 plan 02-07 / MEM-10). Fixture grows from 15 → 20 docs.
587
+ - **`tests/fixtures/malformed-memory/`** tree with 5 deliberately-broken fixtures (`missing-observed-at`, `missing-source`, `invalid-confidence`, `supersede-no-target`, `source-agent-no-evidence`) for validator failure-mode unit tests; each carries `expected_reason` + `expected_key` frontmatter (Phase 2 plan 02-07 / MEM-10).
588
+ - **`docs/tools/audit_log.md`** documenting the new optional `is_memory_sink_write` filter on the `audit_log` tool. Documentation lives here rather than in the MCP tool description text — the description is byte-identical to Phase 1 to honor the v1 backwards-compat invariant (Phase 2 plan 02-06).
589
+ - **Section identity substrate (Phase 3 plan 03-01 / ASM-01, ASM-02, ASM-03, ASM-05, ASM-08)** — `sections` table (migration 010) materializing per-document outline trees with content-hash anchors per ADR-003 H-7 (`sha256_hex(NFC(heading_text) || "\n" || render_blocks_to_plain_text(blocks))`). Parent-pointer reconstruction; per-section `chunk_id_first` / `chunk_id_last` ranges; denormalized `notes.status` column with partial index (`notes_status`) for the SQL-level superseded filter. Backfill from existing v1 vaults runs in lockstep with the migration so user vaults gain sections + status on first upgrade without re-indexing.
590
+ - **`get_outline` MCP tool** — return a nested `OutlineNode[]` tree for a `doc_id`. Each node carries `{anchor, heading_path, heading_text, level, chunk_ids: string[], children}` per D-02. Anchors are stable across re-indexings of unchanged content. Doc-level response envelope is the 8-field citation packet shape (Phase 3 plan 03-02 / ASM-02, ASM-05).
591
+ - **`search_sections` MCP tool** — section-level retrieval that COMPOSES the v1 chunk-level hybrid pipeline (`hybridSearch`) with a chunk-to-section promotion step. Accepts `{query, limit, vaults?, recency_weight?, authority_weight?, half_life_days?, include_superseded?}`. The promotion strategy: inflate `topK = limit × 5`, run hybrid once, promote each chunk hit to its enclosing section, dedupe by `(note_id, anchor)`, score each section as `MAX(constituent chunk scores)`, sort DESC tie-broken by `chunk_id_first` ASC, slice to `limit`, hydrate into `SectionHit` packets carrying the 8-field citation floor plus `{anchor, score, chunk_ids, snippet?}`. Preamble (level-0) sections are dropped — only sections with a non-empty `heading_path` surface. (Phase 3 plan 03-03 / ASM-03, ASM-05)
592
+ - **`get_document_bundle` MCP tool** — one-call composite read for a `doc_id`: `{anchor, outline, backlinks, forward_links, recent_edits}`. The anchor is a full 8-field citation packet with optional ASM-06 extras (`status`, `superseded_by`); `outline` re-uses `buildOutlineTree` from `get_outline` (composition, not duplication); `backlinks` + `forward_links` carry citation packets plus `{property_snippet, relation}` (relation is `"wikilink"` in v2.0.0 — see Phase 4 widening note below); `recent_edits` ≤10 entries from `audit_log`, with `is_memory_sink_write` surfaced only when truthy. (Phase 3 plan 03-04 / ASM-01, ASM-05, ASM-06)
593
+ - **`search_hybrid` rescore signals (Phase 3 plan 03-05 / ASM-06, ASM-07, ASM-08, ASM-11) — additive Zod params**, all defaulting to v1-no-op so legacy callers see byte-identical responses:
594
+ - `recency_weight: number` (default `0`) — adds `recency_weight × exp(-age_days / half_life_days)` to each candidate's RRF score.
595
+ - `authority_weight: number` (default `0`) — adds `authority_weight × 1` for docs with `properties.authoritative === true`.
596
+ - `half_life_days: number` (default `30`) — recency decay half-life.
597
+ - `include_superseded: boolean` (default `false`) — when `false`, excludes `status: "superseded"` chunks at SQL level via the `notes_status` partial index. Zero per-candidate frontmatter parses on the default-hide path.
598
+ - **9 new optional `SearchHit` fields** (D-08, ASM-06): `doc_id`, `source_handle`, `heading_path`, `mtime`, `hash`, `display_url`, `status`, `superseded_by`, `properties` — all snake_case to align with the Phase 2 `CitationPacket` shape; all optional and JSON-omitted on the v1-default path.
599
+ - **Display-URL resolver seam** — `HybridSearchOptions.displayUrlFor` optional closure keeps the `obsidian://` literal mint site in the obsidian-fs source adapter per ADR-002 §I-5b.
600
+ - **Clock-injection seam** — `HybridSearchOptions.clock` optional closure mirrors the recall convention; tests pass a fixed clock for deterministic age math.
601
+ - **`assemble_dossier` MCP tool** — resolve a `{type, key}` pair to an anchor `Document` and walk backlinks into a structured dossier `{anchor, linked_documents, property_rollups: {linked_count, linked_types, status_distribution}, error}` (Phase 3 plan 03-06 / ASM-04, ASM-05, ASM-10). Strict `properties.type` match (D-03); `key` matches the candidate's `title` OR any entry in `properties.aliases` (D-04). Every linked document carries an 8-field citation packet (D-01) plus the `relation` field. **v2.0.0 limitation:** `linked_documents[].relation` is always `"wikilink"` because the v1 `wikilinks` table only stores wikilink edges; Phase 4 (GRA-04 typed edges) will widen the field to the full `Edge.type` enum (mention, frontmatter-ref, hyperlink, …) as an additive change. Search for `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` for the widening sites.
602
+ - **Source-neutrality conformance suite for assembly tools (Phase 3 plan 03-07 / ASM-12)** — `src/adapters/source/conformance.test.ts` extends its `describe.each` parameterization with a new "Assembly tools — $name" section over `[obsidian-fs, stub-assembly]`. Five canonical assertions per adapter row (10 total) cover `get_outline`, `assemble_dossier`, `search_sections`, `get_document_bundle`, and citation-packet shape parity with recall's output. Backed by `src/adapters/stub/assembly-fixture.ts` — an 8-document purpose-built `Document[]` covering aliases, authoritative, superseded, all four edge types, and a multi-section doc. Per RESEARCH §7 P/R evals (ASM-10 dossier, ASM-11 recency) run against the obsidian-fs adapter only; the stub fixture is purpose-built for contract conformance.
603
+ - **Recency eval fixture** — `evals/fixtures/v2-test-vault/_queries/recency.yaml` ships ASM-11's stale-vs-fresh scenario: two near-duplicate Atlas-1 status notes (`status_updates/atlas-1-old-update.md` mtime ≈6 months back, `status_updates/atlas-1-new-update.md` mtime ≈1 day back) with neutral + recency-weighted query pair. Mtime contract is documented inline; eval harness `fs.utimesSync`-injects mtimes in a `beforeAll` block (git does not preserve mtimes on checkout).
604
+ - **Compiled brief layer (Phase 5, ADR-005)** — briefs are first-class `Document`s in `_memory/_briefs/` with chunk-level `source_hashes` provenance; compile-time wikilink emission (D-11), auto-supersede on target collision (D-12), and forward-only supersede chain. Writes route through `DeliveryAdapter` so the MEM-05 memory-namespace invariant is un-bypassable by construction. Closes the "agents rediscover 85% of context every run" failure mode that motivated the v2 program. See `docs/v2/PHASE-5-SIGN-OFF.md` + `docs/v2/adr/005-brief-compile-strategy.md`.
605
+ - **2 new MCP tools (Phase 5)** — `compile_brief`, `get_brief`. Tool count: 32 → 34 (additive only; the 32 Phase 4 entries verified byte-identical per-name in `evals/v1-baseline/tools-list.snapshot.json`; strict-equality snapshot test re-enabled in `baseline.test.ts`).
606
+ - **`vault-memory://briefs` MCP Resource (Phase 5 / BRF-09)** — pure-read discovery surface with optional `?target=<pattern>` substring filter. NOT a Tool; Resources do not count toward the REL-08 tool budget. `readListBriefs` is a closure over `MemorySinkRegistry` + `VaultManager` + `SourceConnector` and carries zero `fs` / `path` / `gray-matter` / `chokidar` imports (enforced by `scripts/lint-adapters.sh` + an explicit assertion in `src/brief/resources.test.ts` Test 10).
607
+ - **Brief staleness daemon (Phase 5 / BRF-05..08)** — `BriefStalenessDaemon` subscribes to `ChangeFeed.subscribe()`, runs a startup full scan against `brief_sources.listBriefDocIds()` (BRF-07; cursor in `daemon_state`), and preserves brief→source links across rename events via a 5-second pending-deletes grace window (BRF-08). Single-owner via `~/.vault-memory/locks/<vault>.lock` (`fs.open('wx')` + PID liveness); on lock contention emits `daemon_already_owned` and returns `{acquired: false}` without subscribing.
608
+ - **`default-brief-v1` MemoryContract (Phase 5)** — the provenance frame required by every brief `Document`. Required properties: `source: "agent"`, `confidence: "inferred"`, `evidence: parsedSourceDocIds`, `status: "active"`, `observed_at`, `superseded_by`, `type: "brief"`, `target`, `purpose`, `compiled_from`, `compiled_at`, chunk-level `source_hashes` map, and `model` audit attribution. The `_memory/_briefs/` sink binds to this contract at registration time.
609
+ - **ChunkId brand + content-stable fragment helpers (Phase 5)** — `parseChunkId`, `formatChunkId`, `decomposeChunkId`, `computeChunkHash`, `computeChunkIdFragment` in `src/chunker/chunk-id.ts`. D-04 content-derived 7-char fragments are essential for the v3 multi-user story (every user recomputes identical fragments over the same source text).
610
+ - **`OllamaClient.chat()` (Phase 5)** — `/api/chat` route alongside `/api/embed`; the first non-embedding LLM call in vault-memory history. Governed by ADR-005. No remote LLM SDK is bundled; the D-10 ladder probes MCP Sampling first (`getClientCapabilities().sampling`), then local Ollama via `[brief.ollama]` config, then falls back to `args.prepared_text` for deterministic CI / non-LLM contracts.
611
+ - **3 new eval YAMLs (Phase 5 / BRF-10, BRF-11):**
612
+ - `evals/fixtures/v2-test-vault/_queries/briefs-curated.yaml` — curated 20-document Atlas Robotics scenario; modifying one source flips the brief stale within one change-feed cycle.
613
+ - `evals/fixtures/v2-test-vault/_queries/briefs-staleness-stub.yaml` — BRF-11 cross-adapter parametric run (`adapters: [obsidian-fs, stub]`) proving brief-layer source-neutrality.
614
+ - `evals/fixtures/v2-test-vault/_queries/briefs-from-cluster.yaml` — cluster-fed compile scenario.
615
+ - **Cross-adapter conformance for brief tools (Phase 5)** — `src/adapters/source/conformance.test.ts` gains a `compile_brief + staleness daemon (BRF-11 source-neutrality)` describe block: 4 conformance test cases × 2 SourceConnector adapters = 8 test runs. Covers update flips stale, startup scan recovers missed events, delete past grace-window marks stale, and rename within grace-window preserves brief→source link.
616
+ - **Obsidian plugin (Phase 7, ADR-007)** — `plugin/` Obsidian community-plugin package (`manifest.json` v2.0.0, id=`vault-memory`, minAppVersion 1.5.0) with sideload-capable layout. `npm run build` emits a 1.9 MB `plugin/main.js`. Ships behind `[plugin] enabled = false` server-side; v1 baseline `tools/list` snapshot remains byte-identical when the flag is unset (FND-10 still passes). See `docs/v2/plugin/README.md` + `docs/v2/adr/007-contract-editor.md`.
617
+ - **Variant C three-pane contract editor (Phase 7 / CAN-01..CAN-07, CAN-10)** — palette + Svelte Flow canvas + Zod-derived inspector for editing Phase 6 task contracts. Canvas wires to `@xyflow/svelte` (MIT, license verified in ADR-007 — rescoped from the original jsoncanvas fork). `registerView('vault-memory-contract-editor')` + `registerExtensions(['contract'])` so opening a `.contract` file launches the editor.
618
+ - **`.contract` JSON envelope + lossless round-trip codec (Phase 7 / CAN-02, CAN-03, CAN-07)** — custom JSON format (`vmFormatVersion: 1`) with a pure-TS round-trip codec (`plugin/src/codec/contract-codec.ts`). Round-trip fixed-point pinned to 3rd/4th-emission byte identity across 4 fixtures (19/19 round-trip tests passing). Covers every ADR-006 field: `version`, `name`, `description`, `inputs`, `sources`, `sinks`, `assembly`, `output_shape`, `write_back`, `required`, `mcp_clients`.
619
+ - **3 reference `.contract` files (Phase 7 / CAN-06)** — `examples/contracts/meeting-prep.contract`, `project-status.contract`, `code-review-brief.contract`, each pinned to its Phase 6 YAML twin via 3 fixture round-trip tests.
620
+ - **6 new plugin-control MCP tools (Phase 7) — gated by `[plugin] enabled = true`, default OFF.** `set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`, `trigger_reindex`, `suppress_contract_write`. Default-OFF tool count: unchanged from Phase 6 (37); when the plugin flag is enabled the surface grows to 43. The v1-baseline tools-list snapshot remains byte-identical against the default-OFF surface — verified by `evals/v1-baseline/baseline.test.ts` "matches the pinned snapshot exactly" + "preserves the 23 v1 baseline tool names byte-identical".
621
+ - **Electron safeStorage-backed secrets (Phase 7 / PLG-02)** — `plugin/src/services/safe-storage.ts` wraps `window.electron.safeStorage` (`encryptString`/`decryptString`); only ciphertext lands in `data.json`. Secrets referenced by name from `[contracts.mcp_clients]` config via `${secret:name}` placeholders (regex `\$\{secret:([a-z][a-z0-9_-]{2,63})\}`). Connector-resolver test suite has 11 passing tests pinning the placeholder grammar + resolution path.
622
+ - **Plugin chrome — settings, reindex, stats, connectors panels (Phase 7 / PLG-01, PLG-03, PLG-04, PLG-05)** — `plugin/src/chrome/settings-tab.ts` (Ollama URL, model, indexer config; persisted via `loadData`/`saveData`; hot-swap via `set_runtime_config`), `reindex-panel.svelte` + controller (manual trigger with MCP `notifications/progress` feedback; SuppressionSet-aware), `stats-panel.svelte` + controller (read-only stats via `get_runtime_stats`), and `connectors-panel.svelte` (list/add/remove peer MCP clients, secrets-by-name). 42 chrome unit tests passing (7+8+7+12+8).
623
+ - **Hash-aware `SuppressionSet.consume(path, hash)` extension (Phase 7 / CAN-08)** — the Phase 1 `SuppressionSet` widens to accept a content hash so the contracts loader can short-circuit re-validation when the watcher fires on the plugin's own write. `src/contracts/loader.ts:242` calls `consume(resource, hash)` before reloading; `src/plugin-tools/suppress-contract-write.ts:109` registers the `(path, hash)` pair from the plugin BEFORE the write. Three dedicated loader tests cover the suppression path; regression watcher tests still pass (10/10 ObsidianFsChangeFeed + 6/6 VaultWatcher).
624
+ - **`vm-install` + `vm-update` Claude Code skills (Phase 7 / CAN-09 distribution half)** — one-liner curl-pipe install / upgrade for the plugin, with SHA-256 verification of the downloaded tarball. Idempotent. `bash skills/vm-install/setup.test.sh` 9/9 pass (fresh install + idempotent no-op); `bash skills/vm-update/update.test.sh` 12/12 pass (no-op + upgrade w/ SHA-256 + re-run). `RELEASE_URL_PLACEHOLDER` resolution is bookkept for the v2.0.0 GitHub Release publish (ROADMAP Phase 8 carryover).
625
+ - **6 plugin docs under `docs/v2/plugin/` (Phase 7 / CAN-09 docs half)** — `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section at the top of the repo README explains how to enable the flag and what the editor + chrome do.
626
+
627
+ ### Changed
628
+
629
+ - **Tool surface count: 30 → 32** (Phase 4, additive only). Two new graph tools added: `expand`, `cluster`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries + 7 Phase 3 entries remain content-identical (verified per-name; the strict-equality snapshot test `baseline.test.ts` "matches the pinned snapshot exactly" — which Plan 04-03 had `.skip`'d pending this regen — is re-enabled and green). The diff against the Phase 3 snapshot is purely additive: two new tool entries appended (before `assemble_dossier` to preserve insertion order at the end of `TOOLS`); one optional nested property added to `search_hybrid.inputSchema.properties` (`expand: {hops, direction?, edge_types?}`); `search_hybrid.description` widened additively to mention the new option.
630
+ - **`search_hybrid` accepts an additive nested `expand?: {hops: 1|2, direction?, edge_types?}` parameter (Phase 4 plan 04-04 / GRA-03).** Defaults to v1-no-op when omitted; legacy callers see byte-identical responses. When supplied, each `SearchHit` gains an `expansions?: CitationPacketWithVia[]` field carrying the per-hit typed-edge neighborhood; ranking is unchanged. Per-vault BFS isolation is enforced inside `expand()` (T-04-04-02 mitigation). Failures inside `expand()` are silently swallowed — same posture as the reranker fallback — so a corrupted graph traversal cannot break search.
631
+ - **Graph result rows widen from `relation: "wikilink"` to `relation: EdgeType` (Phase 4 plan 04-01).** `assemble_dossier.linked_documents[].relation`, `get_document_bundle.{backlinks,forward_links}[].relation`, and the v1 `BacklinkEntry`/`ForwardLinkEntry` `relation` fields now emit the actual edge type the indexer recorded. The Phase 3 known-limitation pinning these to `"wikilink"` is closed; the `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` have been retired. This is strictly an additive widening — old callers that only checked for `"wikilink"` still receive that value when the underlying edge is a wikilink.
632
+ - **Indexer dual-writes to `wikilinks` + `edges` tables (Phase 4 plans 04-01, 04-02).** Both indexer write paths (`src/indexer/single.ts` body-hash fast path + full re-embed branch; `src/indexer/indexer.ts` full index path) call `writeAllEdges` after parsing each note, threading the `WikilinkResolver` for cross-note target resolution. The v1 `wikilinks` table is preserved for D-01 v1 invariance; v3 cleanup will drop it once no v1 tool reads from it.
633
+ - **`@modelcontextprotocol/sdk` bumped to `^1.29.0`**; tool registration migrated from the v1 low-level `Server` + `setRequestHandler` pattern to `McpServer` + `server.registerTool()` × 23. Raw JSON Schema literals flow directly from `src/tool-registry.ts` (workaround for SDK#1143 / Zod-4 description-drop, although the issue is empirically MOOT in SDK 1.29). (ADP-08)
634
+ - **`zod` bumped to `^4.4.3`**. Refinements and `errorMap` swept per the Zod 4 migration guide; Standard Schema wiring intact. (ADP-09)
635
+ - **Default `client_id` for write / update / delete** is now captured from MCP `InitializeRequest.params.clientInfo.name` via a lazy closure (`getClientId()` in `src/server.ts`), falling back to `"unknown"`. Removes the v1 hardcoded `"claude-code"` default that misidentified every non-Claude write in the audit log. (D-02)
636
+ - **`obsidian://` display-URL minting** moved from `src/server.ts:obsidianUrl()` (deleted) into `SourceConnector.formatDisplayUrl(id)` on the adapter. The `obsidian://open?vault=…&file=…` URL is now adapter-published; future adapters mint their own scheme. (D-01)
637
+ - **README rewritten** to lead with "any MCP-aware agent" framing in the first 20 lines. Equal billing for Claude Code / Claude Desktop / ChatGPT Custom Connectors / MCP Inspector / generic clients. Obsidian framed as the v2 source connector, not the sole consumer. (ADP-14)
638
+ - **`src/cli.ts` user-facing strings** swept for Claude-leak — `"Open ${path} in Claude Code"` → `"Open ${path} in your MCP-aware client"`. (D-02 follow-through)
639
+ - **`WriteConflict.reason` discriminated union extended** (additively, backwards-compatible) with 7 new codes: `missing_provenance`, `invalid_provenance`, `supersede_mismatch`, `agent_write_outside_sink`, `non_agent_write_inside_sink`, `sentinel_missing`, `sink_write_blocked`. Optional envelope fields added: `sinkName`, `key`, `observedValue`, `suggestion`. The 3 Phase 1 reason codes (`hash_mismatch`, `permission_denied`, `not_found`) are unchanged (Phase 2 plan 02-03 / MEM-05, MEM-07, MEM-11).
640
+ - **v1 `write_note`, `update_frontmatter`, `delete_note` now refuse memory-sink-resolved targets** at the tool entry-point with `sink_write_blocked` + actionable `suggestion` text (`record_observation` for writes/updates, `supersede` for deletes). Defense-in-depth on top of the centralized `DeliveryAdapter.write()` chokepoint (Phase 2 plan 02-03b / MEM-07). When the `MemorySinkRegistry` is not wired (Phase 1 fixture-test path), v1 tool behavior is byte-identical to Phase 1.
641
+ - **`audit_log` tool gains optional `is_memory_sink_write` input filter** and adds `is_memory_sink_write: boolean` to each returned row. The MCP `description` text is byte-identical to Phase 1 — the new capability is documented in CHANGELOG and `docs/tools/audit_log.md` only (Phase 2 plan 02-06 / MEM-08).
642
+ - **Server bootstrap order** is now `loadConfig → manager.openAll → registerMemorySinks (writes sentinels) → server.connect → startCatchupAndWatchers (fire-and-forget)`. Sentinels are provisioned BEFORE the catch-up indexer walks the fixture, so the registry's `findSinkContaining` enclosure check is hot for the first incoming write (Phase 2 plan 02-03b). A new exported `BootstrapPhase` type + optional `onPhase` callback on `serve(options?)` make the bootstrap order observable for testing.
643
+ - **Assembly DocId minting derives scheme from `SourceConnector.handle`** (Phase 3 plan 03-07 fix). Pre-03-07 both `assembleDossier` and `getDocumentBundle` hardcoded `formatDocId("obsidian-fs", …)` when constructing linked-document / anchor DocIds. The 03-07 ASM-12 conformance suite (parameterized over obsidian-fs + stub adapters) surfaced this as a hard test failure on the stub-assembly row. The fix introduces `schemeFromSource(SourceConnector): string` and derives the scheme dynamically; `dossier.ts`'s sort-key helper is renamed `noteDocIdString → noteSortKey` and pinned to a fixed `vault://` prefix so sort order stays adapter-identical. No user-visible change for obsidian-fs callers (their scheme has always been `obsidian-fs://`); the fix unblocks future non-Obsidian sources.
644
+ - **Tool surface count: 26 → 30** (additive only). Four Phase 3 assembly tools added: `get_outline`, `search_sections`, `get_document_bundle`, `assemble_dossier`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries at slots 0–22 remain byte-identical (pinned by the existing `baseline.test.ts` "preserves the 23 v1 baseline tool names byte-identical" assertion). The diff against the Phase 2 snapshot is purely additive: four new tool entries appended; one optional field added to `search_hybrid.inputSchema.properties` (the four rescore params).
645
+ - **Tool surface count: 32 → 34** (Phase 5, additive only). Two new brief tools added: `compile_brief`, `get_brief`. The `list_briefs` discovery surface lives on MCP Resources (`vault-memory://briefs`) and does NOT count toward the tool budget — same precedent as the Phase 2 `vault-memory://memory/*` Resources. The 32 Phase 4 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (verified per-name); the diff is purely additive — two new tool entries appended.
646
+ - **Server bootstrap order extended (Phase 5)** — after `registerMemorySinks` and before `server.connect`, the bootstrap now (a) registers the `_memory/_briefs/` sink bound to the `default-brief-v1` contract and (b) starts `BriefStalenessDaemon` per vault. Daemon `start()` is fire-and-forget: a lock-contention failure does NOT block server startup — the server proceeds and `compile_brief` continues to work; only the staleness re-check is offline.
647
+ - **`SuppressionSet.consume()` accepts an optional `hash` argument (Phase 7 / CAN-08, additive).** Phase 1 callers that pass only a path see byte-identical behavior. When the plugin write path passes `(path, hash)`, the contracts loader at `src/contracts/loader.ts:242` matches both axes before short-circuiting — so a benign re-emit of the same canonical bytes is suppressed, but a divergent write still triggers reload.
648
+ - **Default-OFF plugin tool gating (Phase 7).** The 6 new plugin tools are wired through a config-time guard in `src/server.ts` so `tools/list` does NOT include them unless `[plugin] enabled = true`. The default-OFF surface keeps the v1 baseline snapshot byte-identical and keeps the REL-08 budget conversation focused on the user-facing v2 tools.
649
+
650
+ ### Dependencies
651
+
652
+ - **Phase 4 (plan 04-05 / GRA-02)** — pure-JS ESM, all MIT, all manually provenance-verified per Phase 4 RESEARCH §Package Legitimacy Audit:
653
+ - `graphology ^0.26.0` — graph data structure for the Louvain cluster wrapper.
654
+ - `graphology-communities-louvain ^2.0.2` — Louvain modularity-maximizing community detection.
655
+ - `seedrandom ^3.0.5` — deterministic seeded PRNG for the Louvain `rng` option.
656
+ - `@types/seedrandom ^3.0.8` (dev) — TypeScript types.
657
+ - No native bindings; no tsup `external` additions needed. ESM bundle grew from 376 KB to 392 KB (+13 KB).
658
+ - **Phase 7 plugin (ADR-007)** — added under `plugin/package.json` only; the server bundle is unaffected:
659
+ - `@xyflow/svelte` — Svelte Flow canvas renderer for the Variant C editor (MIT, license verified in ADR-007).
660
+ - `svelte`, `esbuild`, `vitest` — plugin-only build/test toolchain. None ship into the server `dist/`.
661
+
662
+ ### Migration
663
+
664
+ - **MIGRATION_011 — `edges` table + chunked backfill from `wikilinks` (Phase 4 plan 04-01).** Function-style migration mirroring `runMigration008`. Creates the `edges` table with `CHECK(type IN ('wikilink', 'mention', 'frontmatter-ref', 'hyperlink'))` and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE(anchor, '')` + `COALESCE(line_number, 0)` for proper NULL-as-distinct dedup. Backfills wikilink rows from the v1 `wikilinks` table in 10k-row chunks via `INSERT OR IGNORE` — idempotent, re-runnable, safe to interrupt. v1-shaped DBs migrate cleanly on first server start after upgrade; the v1 `wikilinks` table is preserved alongside the new `edges` table for D-01 v1 invariance. v3 cleanup will drop `wikilinks` once no v1 tool consumes it.
665
+ - **`notes.doc_uri` dual-column staging (Strategy A — additive, plan 01-02):**
666
+ - **MIGRATION_007** (additive) — adds nullable `doc_uri TEXT` column to `notes` + `idx_notes_doc_uri` index. Backwards-compatible; existing rows have `doc_uri IS NULL` post-migration.
667
+ - **MIGRATION_008** (function-style backfill) — sets `doc_uri = 'obsidian-fs://<vault>/' || path` for every row where `doc_uri IS NULL`. Idempotent; safe to re-run.
668
+ - Path stored **un-encoded** in the column; percent-encoding happens only at `formatDisplayUrl()` time (per ADR-002 §URL semantics).
669
+ - **No user action required.** Migrations run automatically on first server start after upgrade. SQLite transaction rollback is the safety net; no pre-migration backup is performed in v2 (deferred to Phase 8 per CONCERNS §"No Pre-Migration DB Backup"). (ADP-07)
670
+ - **v1 tool surface preserved byte-for-byte.** All 23 tool names, input schemas, output envelopes, and descriptions are unchanged. `evals/v1-baseline/tools-list.snapshot.json` regenerated under SDK 1.29 with zero diff against the Phase-0 baseline (W6 human-verify checkpoint passed in plan 01-06 Task 06).
671
+ - **MIGRATION_010 — sections table + notes.status column + section backfill (Phase 3 plan 03-01).** Three ordered steps in one transaction: (a) `CREATE TABLE sections` with 3 indexes, (b) `notes.status` column added + partial index `notes_status WHERE status IS NOT NULL`, (c) backfill `notes.status` from `json_extract(frontmatter, '$.status')` AND derive section rows for every indexed note via `markdownToSectionBlocks → extractSections`. Idempotent; re-applying is a no-op. Anchor-equivalence guarantee: backfilled section anchors match a fresh re-index byte-for-byte (the indexer + backfill share `src/chunker/headings.ts:extractHeadings` as the canonical heading source). v1-shaped DBs migrate cleanly without re-indexing.
672
+ - **`write_audit` migration v9** (function-style, idempotent) adds `is_memory_sink_write INTEGER NOT NULL DEFAULT 0` + a partial index `idx_write_audit_memory_sink ON (is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` (Phase 2 plan 02-06 / MEM-08). All Phase 1 audit rows backfill to `false`; new audit rows derive the flag from `WriteOptions.sink !== undefined` at the `ObsidianFsDelivery.write/update/delete` facade. The function-style migration runs `PRAGMA table_info` first so fixture tests that rewind `user_version` continue to pass.
673
+ - **v1 tool surface grows from 23 → 26** with the three Phase 2 memory tools (`record_observation`, `recall`, `supersede`). The 23 v1 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (pinned by a new `baseline.test.ts` assertion). The only diff on v1 tools is one optional input field added to `audit_log.inputSchema` (`is_memory_sink_write`); the v1 description text is byte-identical.
674
+ - **MIGRATION_013 — `chunks.chunk_id_fragment` + `brief_sources` + `daemon_state` (Phase 5 plan 05-01).** Three additive steps in one transaction: (a) `chunks.chunk_id_fragment` TEXT column added (`noUncheckedIndexedAccess` consumers see the new column as `T | undefined` until populated by the next index run); (b) `brief_sources` table (D-06 reverse-index — `(brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)`); (c) `daemon_state` table (per-vault change-feed cursor + `last_full_scan` timestamp). Idempotent; re-applying is a no-op. v1-shaped DBs migrate cleanly on first server start after upgrade.
675
+ - **Phase 5 brief artifacts are NOT a server schema migration.** Brief `Document`s land in `_memory/_briefs/` via the standard `DeliveryAdapter.write()` path; the `default-brief-v1` MemoryContract is registered at bootstrap (not persisted in SQLite). Users who never call `compile_brief` see zero new files on disk.
676
+ - **Phase 7 plugin is NOT a server schema migration.** No new SQLite migrations land for Phase 7. The plugin stores its own state in `data.json` (loadData/saveData) inside the Obsidian vault's plugin directory; secrets land as Electron safeStorage ciphertext only. Users who never enable the plugin flag see zero new server-side files.
677
+
678
+ ### Documentation
679
+
680
+ - Relocate ADRs 001–004 from `docs/dev/` (gitignored) to public `docs/v2/adr/`; amend each with Invariants + Examples sections covering both `obsidian-fs://` and `notion-api://` worked examples (FND-01, FND-04).
681
+ - ADR-003 amended with explicit hash-semantics pseudocode (RFC 8785 JCS, NFC normalisation, LF line endings, IEEE-754 number canonicalization) + chunk-level `source_hashes` schema (FND-02).
682
+ - ADR-004 amended: folder-default `MemorySink` is the only code path; separate-vault is config-only via `[memory] sink = "@…"` (FND-03). `.memory-sink` sentinel mandated.
683
+ - ADR-004 amended: underscored PropertyBag keys, confidence enum aligned to [direct, inferred, uncertain], superseded_reason field added (Phase 2 plan 02-01).
684
+ - Publish `docs/v2/ARCHITECTURE.md` (L0–L4 layer model + responsibility map), `docs/v2/MEMORY_CONTRACT.md` (provenance property contract on `Document.properties`), `docs/v2/AGENT_AGNOSTIC.md` (MCP-canonical client stance) (FND-05/06/07).
685
+ - Eval fixture vault `evals/fixtures/v2-test-vault/` — "Atlas Robotics" narrative; 56 notes across `projects/`, `meetings/`, `people/`, `decisions/`, `references/`; 15-document `_memory/` subset; 7 hand-labeled `_queries/*.yaml` (FND-08).
686
+ - v1-baseline regression suite `evals/v1-baseline/` — `tools-list.snapshot.json` pin for `tools/list` (23 tools), 11 per-tool semantic-floor YAMLs, `baseline.test.ts` vitest runner with `.todo` placeholders for Phase 1 precision/recall (FND-09/10).
687
+ - CI gates `scripts/check-fixture-privacy.sh` + `scripts/lint-no-telemetry.sh` + `.github/workflows/ci.yml` running `npm run lint:check && npm test` on every PR and push to `main` (FND-11/12 + D-21).
688
+ - ADR index `docs/v2/adr/README.md` listing 4 Accepted ADRs + 14 Open ADR stubs for v3 / Phase 10 follow-ups, including a Deferred-v3 section for adversarial-review findings (FND-13).
689
+ - Adversarial review `docs/v2/adr/ADVERSARIAL-REVIEW.md` — 10 findings against ADRs 001–004 raised by a fresh-context advisor; 6 Amended in Phase 0 (ADR-001 I-6, ADR-002 `DocumentRef.hash` contract + `hashProtected` enum, ADR-003 H-6), 4 Deferred-v3 to Phase 10 / Notion connector (FND-04 + FND-14 sign-off).
690
+ - Sign-off artifact `docs/v2/SIGN-OFF.md` — FND-01..14 checklist with resolving commit SHAs; PR approval is the FND-14 audit trail per D-17.
691
+ - Internal: extract `TOOLS` constant from `src/server.ts` to `src/tool-registry.ts` so `evals/v1-baseline/dump-tools.mjs` can produce the pinned snapshot without spinning the full MCP server (Phase 0 Assumption A5, no external behavior change).
692
+ - Phase 5 sign-off artifact `docs/v2/PHASE-5-SIGN-OFF.md` — BRF-01..BRF-11 traceability table + the five Phase 5 ROADMAP success criteria with disposition (`✅ MET` per criterion) + REL-08 hand-off plan to Phase 8.
693
+ - ADR-005 `docs/v2/adr/005-brief-compile-strategy.md` (Accepted) — governs the D-10 LLM strategy ladder and forbids bundling any remote LLM SDK in the server.
694
+ - Phase 7 plugin docs under `docs/v2/plugin/`: `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section added to the repo root README explaining how to flip `[plugin] enabled = true` and what the editor + chrome surfaces do.
695
+ - ADR-007 `docs/v2/adr/007-contract-editor.md` (Accepted, 2026-05-19) — rescopes the visual editor from a jsoncanvas fork to `@xyflow/svelte` (MIT, license verified inline in the ADR).
696
+
697
+ ### Deprecated (Phase 8 / REL-08)
698
+
699
+ The following five v1 tools are deprecated in v2.0.0 and promoted to MCP Resources. Each tool remains callable through the v2.x line for backwards compatibility; removal is scheduled for v3.0.0. The Resource URI is the canonical replacement.
700
+
701
+ - **`list_vaults`** → `vault-memory://vaults` (cross-vault discovery Resource). The tool remains callable through v2.x; removal scheduled for v3.0.0.
702
+ - **`list_models`** → `vault-memory://models/{vault}` (per-vault embedding-model inventory). The tool remains callable through v2.x; removal scheduled for v3.0.0.
703
+ - **`recent_notes`** → `vault-memory://recent/{vault}` (per-vault mtime-DESC recent notes). The tool remains callable through v2.x; removal scheduled for v3.0.0.
704
+ - **`vault_stats`** → `vault-memory://stats/{vault}` (per-vault note count + top tags + top frontmatter keys + last index run). The tool remains callable through v2.x; removal scheduled for v3.0.0.
705
+ - **`list_backlinks`** → `vault-memory://backlinks/{vault}/{+docId}` (per-document backlink listing). The URI uses RFC 6570 reserved expansion: `{+docId}` allows `/` in the variable value so multi-segment `docId`s like `obsidian-fs://my-vault/notes/sub/file.md` parse correctly. The tool remains callable through v2.x; removal scheduled for v3.0.0.
706
+
707
+ ## [2.0.0-rc.5] — 2026-05-27
708
+
709
+
710
+ ### Added
711
+
712
+ - **`create-contract` agent skill** — `skills/create-contract/SKILL.md` lets a local agent turn a user's free-language description of a recurring search routine into a validated task-contract YAML written to `_contracts/`. Three modes: default authoring (free-text intake → design questions → verb selection → `doc_ids` chaining → memory-sink/brief-LLM setup → write + "test before you trust" handoff), **discovery mode** (read the gap log, cluster recurring unmet requests, propose contract candidates), and **artifact-schema mode** (ADR-030 precomputed-artifact field shape). References `docs/v2/plugin/AUTHORING-CONTRACTS.md` for the DSL rather than duplicating it; honest about Phase-8.5-not-yet-real items (ADR-027 reference picker, ADR-030 materialization, ADR-029 loops). Skill-only — zero `src/` change, no new MCP tool, no server LLM coupling.
713
+ - **Missing-contract discovery (skill + memory-sink layer)** — `use-contracts` now appends a structured entry to the memory sink (`_memory/_contract-gaps/`) whenever no contract matches a request (request + inferred intent shape + vault + timestamp). `create-contract --discovery` clusters those gaps and proposes the most frequent as new-contract candidates. Collect-and-suggest, not autonomous learning (true quality-signal loops await ADR-029 server instrumentation). The gap log is an allowed sink write under the sacrosanct memory-namespace invariant — never a silent write into user notes.
714
+ - **`/vmem:install` now installs content skills** — new Checkpoint 6.6 in the vmem marketplace plugin's `setup.sh` fetches the six content skills (`add-vault`, `audit-vault-health`, `find-stale-notes`, `triage-inbox`, `use-contracts`, `create-contract`) into `<vault>/.claude/skills/`. Previously the plugin installed only operational skills (install/health/reindex) and no content skills. Auto-applies in headless/AUTO mode; confirm prompt with a TTY; non-destructive. `scripts/install-skills.sh` gains `use-contracts` so both contract skills also ship via the curl path. vmem plugin bumped 0.3.10 → 0.3.11.
715
+
716
+ ### Changed
717
+
718
+ - **Obsidian plugin error messages reference `/vmem:install`** (was: `vm-install skill` / `/vm-install`). Two strings updated in `plugin/src/services/mcp-client.ts` (the `CliNotFoundError` thrown in both the spawn-time and connect-time error paths) and one banner string in `plugin/src/chrome/settings-tab.ts` (the "CLI not found" banner in the settings tab). The new strings point users at the renamed marketplace plugin `vmem` (replacing the old `install-vault-memory` with its duplicated `/install-vault-memory:install-vault-memory` slug) and the verb-split surface: `/vmem:install`, `/vmem:health`, `/vmem:reindex`. See `ISSUE-marketplace-plugin-rename-vmem.md`.
719
+ - **`CHANGELOG.md` now ships in the npm tarball** — added to the `files` field in `package.json` so users running `npm view @owrede/vault-memory@<version>` or extracting the tarball can read per-release notes without leaving npm.
720
+ - **`publish.yml` adds an `NPM_DISABLE_PROVENANCE` escape hatch** — repo variable `NPM_DISABLE_PROVENANCE=1` skips the `--provenance` flag on `npm publish`. Recovery path for sigstore-attestation-orphan states. Default off; provenance stays on for all normal publishes.
721
+
722
+ ### Skipped: v2.0.0-rc.3
723
+
724
+ - **v2.0.0-rc.3 was tagged but never published to npm.** The first publish attempt for rc.3 wrote a sigstore log entry but the actual package PUT failed with `404 Not Found` from the npm registry; multiple subsequent retries with different tarball contents and provenance disabled all hit the same 404 (registry-internal poisoned state). rc.4 supersedes rc.3 and contains every change rc.3 would have shipped.
725
+
726
+ ### Fixed
727
+
728
+ - **Migration 010 — `UNIQUE constraint failed: sections.note_id, sections.anchor`** crash blocking v0.9.x/v1.0.0 → v2.0.0-rc.1 upgrade for any user whose vault contains heading-only sibling sections (e.g. template scaffolds with repeated `## TODO` headings whose body slot is empty). `extractSections` produces identical content-hash anchors for sections with identical `(heading_text, body)` pairs, and the `INSERT INTO sections` was plain so the second sibling aborted the migration transaction — leaving every CLI command unreachable for every configured vault until the bad DB was removed from `config.toml`. `backfillSectionsFromChunks` now uses `INSERT OR IGNORE`; on collision the surviving row's id is looked up and reused so the `insertedIds` slot still resolves later children's `parent_id` correctly. See `ISSUE-migration-010-duplicate-anchor.md`.
729
+
730
+ ### Added
731
+
732
+ - **Task Contract DSL (Phase 6, ADR-006)** — declarative YAML contracts under `_contracts/<name>.yaml`, addressable by name, instantiable via MCP, with handle-based source/sink portability. Contracts use a closed assembly verb enum (11 baseline + `literal` + `mcp://<server>/<tool>` peer extension), `{{template}}` step composition, JSON-Schema-with-`$ref` inputs, MemorySink-only sinks (un-bypassable per D-A4c), and ChangeFeed hot reload (D-LOAD). See `docs/v2/PHASE-6-SIGN-OFF.md` + `docs/v2/adr/006-task-contract-dsl.md`.
733
+ - **3 new MCP tools (Phase 6)** — `describe_contract`, `instantiate_contract`, `register_contracts_as_tools`. Tool count: 34 → 37 (additive only; v1-baseline preserved byte-identical).
734
+ - **2 new MCP Resources (Phase 6)** — `vault-memory://contracts/{vault}` (CON-04) lists contracts available in a vault; `vault-memory://contract-verbs/{vault}` (D-A2b) lists baseline + custom `mcp://` verbs with invocation counts. Resources do NOT count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.
735
+ - **3 reference contracts (Phase 6)** under `evals/fixtures/v2-test-vault/_contracts/`: `meeting-prep`, `project-status`, `code-review-brief`. Plus `smoketest-trivial` for the CON-09 non-Claude smoketest path (literal-only assembly; no LLM needed in CI).
736
+ - **`contract_audit` table (Phase 6, migration 014)** — orchestration audit substrate; one row per assembly step (kind=`contract_step`) and one row per load failure (kind=`contract_load_error`). Payload-free per Invariant C-5 (peer-MCP outputs may carry sensitive data; we never capture them). Feeds `aggregateVerbUsage(vault)` for the D-A2b promotion signal.
737
+ - **`[contracts]` config block (Phase 6)** — `auto_register_tools: bool = false`, `tool_prefix: string = "vm_"`, `step_timeout_seconds: number = 30`, `defaults: Record<string, string> = {}`, `mcp_clients: Record<string, {command, args?, env?}> = {}`.
738
+ - **`instantiate_contract` write_back chokepoint (Phase 6)** — routes through `DeliveryAdapter.write()` so the MEM-05 invariant is un-bypassable by construction. Sink overrides MUST resolve through `MemorySinkRegistry.resolveMemorySink()` before write; otherwise `{ok:false, reason:"sink_override_not_a_memory_sink"}`.
739
+ - **CON-10 stub-parity proof (Phase 6)** — `src/adapters/source/conformance.test.ts` gains a `contracts stub-parity (CON-10)` describe block proving the same contract produces structurally identical bundles across obsidian-fs and stub source connectors.
740
+ - **CON-09 non-Claude smoketest extension (Phase 6)** — `scripts/smoketest-non-claude.mjs` spawns the server against a temp HOME with the fixture vault, asserts the three contract tools surface, calls `describe_contract` + `instantiate_contract` + reads the `list_contracts` Resource, and exits 0.
741
+ - **`ObsidianFsSource` widened to enumerate `_contracts/*.yaml` (Phase 6)** — non-recursive walk added via `scanContractFiles`; `scanVault` unchanged so the indexer's `.md`-only contract is preserved. `readDocument` handles YAML files by returning raw text as a single paragraph block (bypasses `parseNote`'s markdown assumptions).
742
+ - **`expand` MCP tool (Phase 4 plan 04-03 / GRA-01)** — typed-edge BFS retrieval primitive. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying `via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped at 2 (v2.0.0). Default direction `'both'`. Filterable by `edge_types: EdgeType[]` and by `filter_properties` (strict equality, no operators). Memory-sink documents (`_memory/...`) surface only when transitively reachable from a user-note seed via at least one in-result inbound edge — per ADR-004 memory-namespace opacity rule. Unknown `seed_doc_ids` do NOT throw — they are returned in a `warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins on dedup; ties broken by `(seed_doc_id, edge_type, direction)`.
743
+ - **`cluster` MCP tool (Phase 4 plan 04-05 / GRA-02)** — Louvain community detection over the typed-edge graph, deterministic across runs via `seedrandom`. Accepts mutually-exclusive `query | seed_doc_ids`. Returns `{ok: true, communities: Cluster[]}` or `{ok: false, reason: 'too_many_nodes' | 'missing_input' | 'mutually_exclusive_input'}`. 5000-node hard cap with `force: true` override. Per-community `cluster_id` is the smallest member DocId by lexicographic order — deterministic naming independent of internal community index assignment. Hybrid-search dependency injected via `ClusterDeps.hybridSearch` closure to avoid the `src/graph/cluster.ts → src/search/hybrid.ts` circular import.
744
+ - **`search_hybrid({expand})` additive nested param (Phase 4 plan 04-04 / GRA-03)** — pass `expand: {hops: 1|2, direction?, edge_types?}` to auto-attach 1–2 hop typed-edge neighbors as `SearchHit.expansions?: CitationPacketWithVia[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged. Guard-and-short-circuit composition: when `expand` is omitted, the code path is byte-identical to Phase 3 (zero new DB reads, zero new JSON fields).
745
+ - **Typed-edge storage substrate (Phase 4 plan 04-01 / GRA-04)** — `edges` table (migration 011) with four edge types (`wikilink`, `mention`, `frontmatter-ref`, `hyperlink`) and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE` for proper NULL-as-distinct dedup. Chunked backfill from v1 `wikilinks` table (10k-row chunks, `INSERT OR IGNORE`, idempotent). The v1 `wikilinks` table is preserved alongside `edges` for v1 invariance (D-01); v3 cleanup is tracked.
746
+ - **Unified edge extractor (Phase 4 plan 04-02 / GRA-04 indexer)** — `extractAllEdges(vault, parsed, resolver): EdgeInput[]` extracts all four edge types in a single per-note parse pass. `MIN_MENTION_LEN = 4` (empirically validated against the Atlas Robotics fixture: 0 raw FPs over 62 notes). `FRONTMATTER_REF_ALLOWLIST` is a sealed 8-key `ReadonlySet<string>` (`assignee`, `owner`, `project`, `related`, `parent`, `child`, `attendees`, `superseded_by`). Mention candidate set built from `note_aliases` only; word-boundary uses `(?<![\w-])`/`(?![\w-])` so aliases containing `-`/`_` are matched as whole tokens. Paragraph-scope masking blanks fenced code, ATX headings, inline backticks, and `[[wikilink]]` spans (byte length preserved so line-offset lookups stay valid).
747
+ - **`AliasesQueries.listAll()` (Phase 4 plan 04-02)** — full alias inventory, sorted by `alias_norm ASC` for deterministic mention-regex compilation.
748
+ - **Additive `type: EdgeType` field on graph result rows (Phase 4 plan 04-01)** — `list_backlinks`, `list_forward_links`, `find_broken_links` results widen with `type`. `BacklinkEntry.relation` / `ForwardLinkEntry.relation` widen from the v2.0.0-pinned `"wikilink"` literal to the full `EdgeType` union. `assemble_dossier.linked_documents[].relation` and `get_document_bundle.{backlinks,forward_links}[].relation` widen the same way — the `PHASE-4-WIDEN` marker comments in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` (a Phase 3 v2.0.0 limitation) are now retired.
749
+ - **3 new eval YAMLs (Phase 4 plan 04-06 / GRA-05):**
750
+ - `evals/fixtures/v2-test-vault/_queries/expand.yaml` — 8 hand-curated queries over the Atlas Robotics live fixture covering all four edge types, mixed-type traversal at hops 1 and 2, `_memory/` opacity, and the unknown-seed warning path. All eight clear `min_precision >= 0.8` / `min_recall >= 0.8`.
751
+ - `evals/fixtures/v2-test-vault/_queries/search-hybrid-with-expand.yaml` — 3 composition queries exercising `search_hybrid({expand: {hops}, ...})` end-to-end.
752
+ - `evals/fixtures/v2-test-vault/_queries/cluster.yaml` — D-12 byte-snapshot of Louvain partition (cluster_id + sorted member DocIds per community); pins determinism across library upgrades + Node-minor versions.
753
+ - **Cross-adapter conformance for graph tools (Phase 4 plan 04-06)** — 6 new parameterized cases in `src/adapters/source/conformance.test.ts` run `expand()` + `cluster()` against both `obsidian-fs` and `stub` adapters via `src/graph/__test_helpers__/atlas-live-fixture.ts`. New harness fields (`graphSeedDocId`, `occludedMemoryDocId`, `reachableMemoryDocId`) carry adapter-specific reference DocIds.
754
+ - **Adapter seams (Phase 1, plans 01-01..06)** — `SourceConnector` / `DeliveryAdapter` / `ChangeFeed` interfaces under `src/adapters/` per ADR-002. `obsidian-fs` is the v2 reference implementation for all three. The seam shape is preserved across vault-content read, vault-content write, and change-event paths so future connectors (Notion, Logseq, …) drop in without touching `src/server.ts` or any v1 tool. (ADP-01, ADP-02, ADP-03)
755
+ - **Canonical v2 types** in `src/types.ts`: `Document`, `BlockNode`, `Edge`, `ChangeEvent`, `SourceHandle`, `MemorySink`, branded `DocId`, `WikilinkRef`. `Document.properties: Record<string, unknown>` subsumes both YAML frontmatter and future Notion typed properties. (ADP-04, ADP-05)
756
+ - **`src/adapters/registry.ts`** — adapter registry + sole minting point for branded `DocId`s via `parseDocId` / `formatDocId`. Future adapters register under their canonical scheme (`obsidian-fs://`, `notion-api://`, …). (ADP-05)
757
+ - **Stub-adapter conformance suite** — parameterized over `obsidian-fs` and `stub`, three test files (`src/adapters/{source,delivery,change-feed}/conformance.test.ts`). Lets future adapters validate themselves against the v2 contract before any production code lands. (ADP-13)
758
+ - **`scripts/lint-adapters.sh`** — POSIX shell CI gate enforcing ADR-002 Invariants I-1..I-6 + C-1 (Claude-leak) + I-5b (`obsidian://` literal). Wired into `npm run lint:check` and `.github/workflows/ci.yml`. (ADP-12)
759
+ - **`scripts/smoketest-non-claude.mjs`** — end-to-end smoketest against the real MCP SDK Client (identifies as `non-claude-smoketest`); CI-gated. Asserts 23 tools listed, all descriptions non-empty, `tools/call list_vaults` succeeds, `tools/call <bogus>` surfaces as error. (ADP-10)
760
+ - **`docs/v2/AGENT_AGNOSTIC_AUDIT.md`** — per-leak inventory of every Claude / Obsidian assumption in `src/`, with explicit `fixed-v2` / `mixed` / `deferred-v3` status + rationale per row. Cross-references CONCERNS.md, ADR-002, and the resolving Phase-1 plans. (ADP-11)
761
+ - **`record_observation` MCP tool** — write a labeled memory observation through a configured `MemorySink` with mandatory provenance (Phase 2 plan 02-04 / MEM-02). Sugar args (`claim`, `evidence`, `confidence`, `type`, `sink?`) pre-fill the contract-required keys; optional `properties: Record<string, unknown>` escape hatch merges LAST so callers can populate any contract-allowed extra (D-02).
762
+ - **`recall` MCP tool** — retrieve memory documents filtered by `min_confidence`, `types`, `max_age_days`, and `sink`; returns Phase 3-shaped citation packets `{doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties}` (Phase 2 plan 02-05 / MEM-03 / D-01). Hides `status: superseded` by default; sorts `observed_at` DESC with `mtime` tiebreak; truncates AFTER filter+sort.
763
+ - **`supersede` MCP tool** — forward-only mark of a memory document as superseded by a replacement, with mandatory non-empty `superseded_reason` (Phase 2 plan 02-04 / MEM-04 / D-03). Single OCC `delivery.update()` call; the replacement document is never touched (back-edge derivation deferred to Phase 4 graph layer).
764
+ - **`vault-memory://memory/sinks` MCP Resource** — lists configured memory sinks per vault with their handle, contract name, and default-flag (Phase 2 plan 02-06 / MEM-09).
765
+ - **`vault-memory://memory/stats` MCP Resource** — per-sink doc counts, `by_type` / `by_status` breakdown, last-write timestamp aggregated from the indexed `notes` table + the `write_audit` partial index (Phase 2 plan 02-06 / MEM-09). Polled-only; no `notifyResourceUpdated` in v2.0.0.
766
+ - **`is_memory_sink_write` column on `write_audit`** (migration v9) + partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` + optional `is_memory_sink_write` filter on the `audit_log` tool input schema (Phase 2 plan 02-06 / MEM-08).
767
+ - **`decomposeDocId` helper** on `src/adapters/registry.ts` for splitting `DocId`s into `{scheme, authority, resource}` parts. Re-uses `DOC_ID_PATTERN`; no second regex (Phase 2 plan 02-02).
768
+ - **`pathInSink` + `joinVaultPath` helpers** in `src/adapters/delivery/obsidian-fs/path.ts` — the SOLE licensed sink/vault `path.join` sites for Phase 2+ per ADR-002 I-3 (Phase 2 plan 02-02).
769
+ - **MemorySink runtime** — `parseMemorySinkHandle` (IIFE-closed brand mint), `MemorySinkRegistry` (sole resolver per ADR-004 §Resolution), `.memory-sink` sentinel mechanics in `src/adapters/delivery/obsidian-fs/sentinel.ts`, hardcoded `DEFAULT_MEMORY_V1` contract + YAML loader at `src/memory/contract/` (Phase 2 plan 02-02 / MEM-01, MEM-05, MEM-06).
770
+ - **5 net-new memory fixture docs** under `evals/fixtures/v2-test-vault/_memory/` including the A→B→C Spire-budget supersede chain (2026-04-23 → 2026-04-24 → 2026-04-26), plus net-new `type: hypothesis`, `type: decision`, and `confidence: uncertain` dimensions (Phase 2 plan 02-07 / MEM-10). Fixture grows from 15 → 20 docs.
771
+ - **`tests/fixtures/malformed-memory/`** tree with 5 deliberately-broken fixtures (`missing-observed-at`, `missing-source`, `invalid-confidence`, `supersede-no-target`, `source-agent-no-evidence`) for validator failure-mode unit tests; each carries `expected_reason` + `expected_key` frontmatter (Phase 2 plan 02-07 / MEM-10).
772
+ - **`docs/tools/audit_log.md`** documenting the new optional `is_memory_sink_write` filter on the `audit_log` tool. Documentation lives here rather than in the MCP tool description text — the description is byte-identical to Phase 1 to honor the v1 backwards-compat invariant (Phase 2 plan 02-06).
773
+ - **Section identity substrate (Phase 3 plan 03-01 / ASM-01, ASM-02, ASM-03, ASM-05, ASM-08)** — `sections` table (migration 010) materializing per-document outline trees with content-hash anchors per ADR-003 H-7 (`sha256_hex(NFC(heading_text) || "\n" || render_blocks_to_plain_text(blocks))`). Parent-pointer reconstruction; per-section `chunk_id_first` / `chunk_id_last` ranges; denormalized `notes.status` column with partial index (`notes_status`) for the SQL-level superseded filter. Backfill from existing v1 vaults runs in lockstep with the migration so user vaults gain sections + status on first upgrade without re-indexing.
774
+ - **`get_outline` MCP tool** — return a nested `OutlineNode[]` tree for a `doc_id`. Each node carries `{anchor, heading_path, heading_text, level, chunk_ids: string[], children}` per D-02. Anchors are stable across re-indexings of unchanged content. Doc-level response envelope is the 8-field citation packet shape (Phase 3 plan 03-02 / ASM-02, ASM-05).
775
+ - **`search_sections` MCP tool** — section-level retrieval that COMPOSES the v1 chunk-level hybrid pipeline (`hybridSearch`) with a chunk-to-section promotion step. Accepts `{query, limit, vaults?, recency_weight?, authority_weight?, half_life_days?, include_superseded?}`. The promotion strategy: inflate `topK = limit × 5`, run hybrid once, promote each chunk hit to its enclosing section, dedupe by `(note_id, anchor)`, score each section as `MAX(constituent chunk scores)`, sort DESC tie-broken by `chunk_id_first` ASC, slice to `limit`, hydrate into `SectionHit` packets carrying the 8-field citation floor plus `{anchor, score, chunk_ids, snippet?}`. Preamble (level-0) sections are dropped — only sections with a non-empty `heading_path` surface. (Phase 3 plan 03-03 / ASM-03, ASM-05)
776
+ - **`get_document_bundle` MCP tool** — one-call composite read for a `doc_id`: `{anchor, outline, backlinks, forward_links, recent_edits}`. The anchor is a full 8-field citation packet with optional ASM-06 extras (`status`, `superseded_by`); `outline` re-uses `buildOutlineTree` from `get_outline` (composition, not duplication); `backlinks` + `forward_links` carry citation packets plus `{property_snippet, relation}` (relation is `"wikilink"` in v2.0.0 — see Phase 4 widening note below); `recent_edits` ≤10 entries from `audit_log`, with `is_memory_sink_write` surfaced only when truthy. (Phase 3 plan 03-04 / ASM-01, ASM-05, ASM-06)
777
+ - **`search_hybrid` rescore signals (Phase 3 plan 03-05 / ASM-06, ASM-07, ASM-08, ASM-11) — additive Zod params**, all defaulting to v1-no-op so legacy callers see byte-identical responses:
778
+ - `recency_weight: number` (default `0`) — adds `recency_weight × exp(-age_days / half_life_days)` to each candidate's RRF score.
779
+ - `authority_weight: number` (default `0`) — adds `authority_weight × 1` for docs with `properties.authoritative === true`.
780
+ - `half_life_days: number` (default `30`) — recency decay half-life.
781
+ - `include_superseded: boolean` (default `false`) — when `false`, excludes `status: "superseded"` chunks at SQL level via the `notes_status` partial index. Zero per-candidate frontmatter parses on the default-hide path.
782
+ - **9 new optional `SearchHit` fields** (D-08, ASM-06): `doc_id`, `source_handle`, `heading_path`, `mtime`, `hash`, `display_url`, `status`, `superseded_by`, `properties` — all snake_case to align with the Phase 2 `CitationPacket` shape; all optional and JSON-omitted on the v1-default path.
783
+ - **Display-URL resolver seam** — `HybridSearchOptions.displayUrlFor` optional closure keeps the `obsidian://` literal mint site in the obsidian-fs source adapter per ADR-002 §I-5b.
784
+ - **Clock-injection seam** — `HybridSearchOptions.clock` optional closure mirrors the recall convention; tests pass a fixed clock for deterministic age math.
785
+ - **`assemble_dossier` MCP tool** — resolve a `{type, key}` pair to an anchor `Document` and walk backlinks into a structured dossier `{anchor, linked_documents, property_rollups: {linked_count, linked_types, status_distribution}, error}` (Phase 3 plan 03-06 / ASM-04, ASM-05, ASM-10). Strict `properties.type` match (D-03); `key` matches the candidate's `title` OR any entry in `properties.aliases` (D-04). Every linked document carries an 8-field citation packet (D-01) plus the `relation` field. **v2.0.0 limitation:** `linked_documents[].relation` is always `"wikilink"` because the v1 `wikilinks` table only stores wikilink edges; Phase 4 (GRA-04 typed edges) will widen the field to the full `Edge.type` enum (mention, frontmatter-ref, hyperlink, …) as an additive change. Search for `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` for the widening sites.
786
+ - **Source-neutrality conformance suite for assembly tools (Phase 3 plan 03-07 / ASM-12)** — `src/adapters/source/conformance.test.ts` extends its `describe.each` parameterization with a new "Assembly tools — $name" section over `[obsidian-fs, stub-assembly]`. Five canonical assertions per adapter row (10 total) cover `get_outline`, `assemble_dossier`, `search_sections`, `get_document_bundle`, and citation-packet shape parity with recall's output. Backed by `src/adapters/stub/assembly-fixture.ts` — an 8-document purpose-built `Document[]` covering aliases, authoritative, superseded, all four edge types, and a multi-section doc. Per RESEARCH §7 P/R evals (ASM-10 dossier, ASM-11 recency) run against the obsidian-fs adapter only; the stub fixture is purpose-built for contract conformance.
787
+ - **Recency eval fixture** — `evals/fixtures/v2-test-vault/_queries/recency.yaml` ships ASM-11's stale-vs-fresh scenario: two near-duplicate Atlas-1 status notes (`status_updates/atlas-1-old-update.md` mtime ≈6 months back, `status_updates/atlas-1-new-update.md` mtime ≈1 day back) with neutral + recency-weighted query pair. Mtime contract is documented inline; eval harness `fs.utimesSync`-injects mtimes in a `beforeAll` block (git does not preserve mtimes on checkout).
788
+ - **Compiled brief layer (Phase 5, ADR-005)** — briefs are first-class `Document`s in `_memory/_briefs/` with chunk-level `source_hashes` provenance; compile-time wikilink emission (D-11), auto-supersede on target collision (D-12), and forward-only supersede chain. Writes route through `DeliveryAdapter` so the MEM-05 memory-namespace invariant is un-bypassable by construction. Closes the "agents rediscover 85% of context every run" failure mode that motivated the v2 program. See `docs/v2/PHASE-5-SIGN-OFF.md` + `docs/v2/adr/005-brief-compile-strategy.md`.
789
+ - **2 new MCP tools (Phase 5)** — `compile_brief`, `get_brief`. Tool count: 32 → 34 (additive only; the 32 Phase 4 entries verified byte-identical per-name in `evals/v1-baseline/tools-list.snapshot.json`; strict-equality snapshot test re-enabled in `baseline.test.ts`).
790
+ - **`vault-memory://briefs` MCP Resource (Phase 5 / BRF-09)** — pure-read discovery surface with optional `?target=<pattern>` substring filter. NOT a Tool; Resources do not count toward the REL-08 tool budget. `readListBriefs` is a closure over `MemorySinkRegistry` + `VaultManager` + `SourceConnector` and carries zero `fs` / `path` / `gray-matter` / `chokidar` imports (enforced by `scripts/lint-adapters.sh` + an explicit assertion in `src/brief/resources.test.ts` Test 10).
791
+ - **Brief staleness daemon (Phase 5 / BRF-05..08)** — `BriefStalenessDaemon` subscribes to `ChangeFeed.subscribe()`, runs a startup full scan against `brief_sources.listBriefDocIds()` (BRF-07; cursor in `daemon_state`), and preserves brief→source links across rename events via a 5-second pending-deletes grace window (BRF-08). Single-owner via `~/.vault-memory/locks/<vault>.lock` (`fs.open('wx')` + PID liveness); on lock contention emits `daemon_already_owned` and returns `{acquired: false}` without subscribing.
792
+ - **`default-brief-v1` MemoryContract (Phase 5)** — the provenance frame required by every brief `Document`. Required properties: `source: "agent"`, `confidence: "inferred"`, `evidence: parsedSourceDocIds`, `status: "active"`, `observed_at`, `superseded_by`, `type: "brief"`, `target`, `purpose`, `compiled_from`, `compiled_at`, chunk-level `source_hashes` map, and `model` audit attribution. The `_memory/_briefs/` sink binds to this contract at registration time.
793
+ - **ChunkId brand + content-stable fragment helpers (Phase 5)** — `parseChunkId`, `formatChunkId`, `decomposeChunkId`, `computeChunkHash`, `computeChunkIdFragment` in `src/chunker/chunk-id.ts`. D-04 content-derived 7-char fragments are essential for the v3 multi-user story (every user recomputes identical fragments over the same source text).
794
+ - **`OllamaClient.chat()` (Phase 5)** — `/api/chat` route alongside `/api/embed`; the first non-embedding LLM call in vault-memory history. Governed by ADR-005. No remote LLM SDK is bundled; the D-10 ladder probes MCP Sampling first (`getClientCapabilities().sampling`), then local Ollama via `[brief.ollama]` config, then falls back to `args.prepared_text` for deterministic CI / non-LLM contracts.
795
+ - **3 new eval YAMLs (Phase 5 / BRF-10, BRF-11):**
796
+ - `evals/fixtures/v2-test-vault/_queries/briefs-curated.yaml` — curated 20-document Atlas Robotics scenario; modifying one source flips the brief stale within one change-feed cycle.
797
+ - `evals/fixtures/v2-test-vault/_queries/briefs-staleness-stub.yaml` — BRF-11 cross-adapter parametric run (`adapters: [obsidian-fs, stub]`) proving brief-layer source-neutrality.
798
+ - `evals/fixtures/v2-test-vault/_queries/briefs-from-cluster.yaml` — cluster-fed compile scenario.
799
+ - **Cross-adapter conformance for brief tools (Phase 5)** — `src/adapters/source/conformance.test.ts` gains a `compile_brief + staleness daemon (BRF-11 source-neutrality)` describe block: 4 conformance test cases × 2 SourceConnector adapters = 8 test runs. Covers update flips stale, startup scan recovers missed events, delete past grace-window marks stale, and rename within grace-window preserves brief→source link.
800
+ - **Obsidian plugin (Phase 7, ADR-007)** — `plugin/` Obsidian community-plugin package (`manifest.json` v2.0.0, id=`vault-memory`, minAppVersion 1.5.0) with sideload-capable layout. `npm run build` emits a 1.9 MB `plugin/main.js`. Ships behind `[plugin] enabled = false` server-side; v1 baseline `tools/list` snapshot remains byte-identical when the flag is unset (FND-10 still passes). See `docs/v2/plugin/README.md` + `docs/v2/adr/007-contract-editor.md`.
801
+ - **Variant C three-pane contract editor (Phase 7 / CAN-01..CAN-07, CAN-10)** — palette + Svelte Flow canvas + Zod-derived inspector for editing Phase 6 task contracts. Canvas wires to `@xyflow/svelte` (MIT, license verified in ADR-007 — rescoped from the original jsoncanvas fork). `registerView('vault-memory-contract-editor')` + `registerExtensions(['contract'])` so opening a `.contract` file launches the editor.
802
+ - **`.contract` JSON envelope + lossless round-trip codec (Phase 7 / CAN-02, CAN-03, CAN-07)** — custom JSON format (`vmFormatVersion: 1`) with a pure-TS round-trip codec (`plugin/src/codec/contract-codec.ts`). Round-trip fixed-point pinned to 3rd/4th-emission byte identity across 4 fixtures (19/19 round-trip tests passing). Covers every ADR-006 field: `version`, `name`, `description`, `inputs`, `sources`, `sinks`, `assembly`, `output_shape`, `write_back`, `required`, `mcp_clients`.
803
+ - **3 reference `.contract` files (Phase 7 / CAN-06)** — `examples/contracts/meeting-prep.contract`, `project-status.contract`, `code-review-brief.contract`, each pinned to its Phase 6 YAML twin via 3 fixture round-trip tests.
804
+ - **6 new plugin-control MCP tools (Phase 7) — gated by `[plugin] enabled = true`, default OFF.** `set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`, `trigger_reindex`, `suppress_contract_write`. Default-OFF tool count: unchanged from Phase 6 (37); when the plugin flag is enabled the surface grows to 43. The v1-baseline tools-list snapshot remains byte-identical against the default-OFF surface — verified by `evals/v1-baseline/baseline.test.ts` "matches the pinned snapshot exactly" + "preserves the 23 v1 baseline tool names byte-identical".
805
+ - **Electron safeStorage-backed secrets (Phase 7 / PLG-02)** — `plugin/src/services/safe-storage.ts` wraps `window.electron.safeStorage` (`encryptString`/`decryptString`); only ciphertext lands in `data.json`. Secrets referenced by name from `[contracts.mcp_clients]` config via `${secret:name}` placeholders (regex `\$\{secret:([a-z][a-z0-9_-]{2,63})\}`). Connector-resolver test suite has 11 passing tests pinning the placeholder grammar + resolution path.
806
+ - **Plugin chrome — settings, reindex, stats, connectors panels (Phase 7 / PLG-01, PLG-03, PLG-04, PLG-05)** — `plugin/src/chrome/settings-tab.ts` (Ollama URL, model, indexer config; persisted via `loadData`/`saveData`; hot-swap via `set_runtime_config`), `reindex-panel.svelte` + controller (manual trigger with MCP `notifications/progress` feedback; SuppressionSet-aware), `stats-panel.svelte` + controller (read-only stats via `get_runtime_stats`), and `connectors-panel.svelte` (list/add/remove peer MCP clients, secrets-by-name). 42 chrome unit tests passing (7+8+7+12+8).
807
+ - **Hash-aware `SuppressionSet.consume(path, hash)` extension (Phase 7 / CAN-08)** — the Phase 1 `SuppressionSet` widens to accept a content hash so the contracts loader can short-circuit re-validation when the watcher fires on the plugin's own write. `src/contracts/loader.ts:242` calls `consume(resource, hash)` before reloading; `src/plugin-tools/suppress-contract-write.ts:109` registers the `(path, hash)` pair from the plugin BEFORE the write. Three dedicated loader tests cover the suppression path; regression watcher tests still pass (10/10 ObsidianFsChangeFeed + 6/6 VaultWatcher).
808
+ - **`vm-install` + `vm-update` Claude Code skills (Phase 7 / CAN-09 distribution half)** — one-liner curl-pipe install / upgrade for the plugin, with SHA-256 verification of the downloaded tarball. Idempotent. `bash skills/vm-install/setup.test.sh` 9/9 pass (fresh install + idempotent no-op); `bash skills/vm-update/update.test.sh` 12/12 pass (no-op + upgrade w/ SHA-256 + re-run). `RELEASE_URL_PLACEHOLDER` resolution is bookkept for the v2.0.0 GitHub Release publish (ROADMAP Phase 8 carryover).
809
+ - **6 plugin docs under `docs/v2/plugin/` (Phase 7 / CAN-09 docs half)** — `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section at the top of the repo README explains how to enable the flag and what the editor + chrome do.
810
+
811
+ ### Changed
812
+
813
+ - **Tool surface count: 30 → 32** (Phase 4, additive only). Two new graph tools added: `expand`, `cluster`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries + 7 Phase 3 entries remain content-identical (verified per-name; the strict-equality snapshot test `baseline.test.ts` "matches the pinned snapshot exactly" — which Plan 04-03 had `.skip`'d pending this regen — is re-enabled and green). The diff against the Phase 3 snapshot is purely additive: two new tool entries appended (before `assemble_dossier` to preserve insertion order at the end of `TOOLS`); one optional nested property added to `search_hybrid.inputSchema.properties` (`expand: {hops, direction?, edge_types?}`); `search_hybrid.description` widened additively to mention the new option.
814
+ - **`search_hybrid` accepts an additive nested `expand?: {hops: 1|2, direction?, edge_types?}` parameter (Phase 4 plan 04-04 / GRA-03).** Defaults to v1-no-op when omitted; legacy callers see byte-identical responses. When supplied, each `SearchHit` gains an `expansions?: CitationPacketWithVia[]` field carrying the per-hit typed-edge neighborhood; ranking is unchanged. Per-vault BFS isolation is enforced inside `expand()` (T-04-04-02 mitigation). Failures inside `expand()` are silently swallowed — same posture as the reranker fallback — so a corrupted graph traversal cannot break search.
815
+ - **Graph result rows widen from `relation: "wikilink"` to `relation: EdgeType` (Phase 4 plan 04-01).** `assemble_dossier.linked_documents[].relation`, `get_document_bundle.{backlinks,forward_links}[].relation`, and the v1 `BacklinkEntry`/`ForwardLinkEntry` `relation` fields now emit the actual edge type the indexer recorded. The Phase 3 known-limitation pinning these to `"wikilink"` is closed; the `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` have been retired. This is strictly an additive widening — old callers that only checked for `"wikilink"` still receive that value when the underlying edge is a wikilink.
816
+ - **Indexer dual-writes to `wikilinks` + `edges` tables (Phase 4 plans 04-01, 04-02).** Both indexer write paths (`src/indexer/single.ts` body-hash fast path + full re-embed branch; `src/indexer/indexer.ts` full index path) call `writeAllEdges` after parsing each note, threading the `WikilinkResolver` for cross-note target resolution. The v1 `wikilinks` table is preserved for D-01 v1 invariance; v3 cleanup will drop it once no v1 tool reads from it.
817
+ - **`@modelcontextprotocol/sdk` bumped to `^1.29.0`**; tool registration migrated from the v1 low-level `Server` + `setRequestHandler` pattern to `McpServer` + `server.registerTool()` × 23. Raw JSON Schema literals flow directly from `src/tool-registry.ts` (workaround for SDK#1143 / Zod-4 description-drop, although the issue is empirically MOOT in SDK 1.29). (ADP-08)
818
+ - **`zod` bumped to `^4.4.3`**. Refinements and `errorMap` swept per the Zod 4 migration guide; Standard Schema wiring intact. (ADP-09)
819
+ - **Default `client_id` for write / update / delete** is now captured from MCP `InitializeRequest.params.clientInfo.name` via a lazy closure (`getClientId()` in `src/server.ts`), falling back to `"unknown"`. Removes the v1 hardcoded `"claude-code"` default that misidentified every non-Claude write in the audit log. (D-02)
820
+ - **`obsidian://` display-URL minting** moved from `src/server.ts:obsidianUrl()` (deleted) into `SourceConnector.formatDisplayUrl(id)` on the adapter. The `obsidian://open?vault=…&file=…` URL is now adapter-published; future adapters mint their own scheme. (D-01)
821
+ - **README rewritten** to lead with "any MCP-aware agent" framing in the first 20 lines. Equal billing for Claude Code / Claude Desktop / ChatGPT Custom Connectors / MCP Inspector / generic clients. Obsidian framed as the v2 source connector, not the sole consumer. (ADP-14)
822
+ - **`src/cli.ts` user-facing strings** swept for Claude-leak — `"Open ${path} in Claude Code"` → `"Open ${path} in your MCP-aware client"`. (D-02 follow-through)
823
+ - **`WriteConflict.reason` discriminated union extended** (additively, backwards-compatible) with 7 new codes: `missing_provenance`, `invalid_provenance`, `supersede_mismatch`, `agent_write_outside_sink`, `non_agent_write_inside_sink`, `sentinel_missing`, `sink_write_blocked`. Optional envelope fields added: `sinkName`, `key`, `observedValue`, `suggestion`. The 3 Phase 1 reason codes (`hash_mismatch`, `permission_denied`, `not_found`) are unchanged (Phase 2 plan 02-03 / MEM-05, MEM-07, MEM-11).
824
+ - **v1 `write_note`, `update_frontmatter`, `delete_note` now refuse memory-sink-resolved targets** at the tool entry-point with `sink_write_blocked` + actionable `suggestion` text (`record_observation` for writes/updates, `supersede` for deletes). Defense-in-depth on top of the centralized `DeliveryAdapter.write()` chokepoint (Phase 2 plan 02-03b / MEM-07). When the `MemorySinkRegistry` is not wired (Phase 1 fixture-test path), v1 tool behavior is byte-identical to Phase 1.
825
+ - **`audit_log` tool gains optional `is_memory_sink_write` input filter** and adds `is_memory_sink_write: boolean` to each returned row. The MCP `description` text is byte-identical to Phase 1 — the new capability is documented in CHANGELOG and `docs/tools/audit_log.md` only (Phase 2 plan 02-06 / MEM-08).
826
+ - **Server bootstrap order** is now `loadConfig → manager.openAll → registerMemorySinks (writes sentinels) → server.connect → startCatchupAndWatchers (fire-and-forget)`. Sentinels are provisioned BEFORE the catch-up indexer walks the fixture, so the registry's `findSinkContaining` enclosure check is hot for the first incoming write (Phase 2 plan 02-03b). A new exported `BootstrapPhase` type + optional `onPhase` callback on `serve(options?)` make the bootstrap order observable for testing.
827
+ - **Assembly DocId minting derives scheme from `SourceConnector.handle`** (Phase 3 plan 03-07 fix). Pre-03-07 both `assembleDossier` and `getDocumentBundle` hardcoded `formatDocId("obsidian-fs", …)` when constructing linked-document / anchor DocIds. The 03-07 ASM-12 conformance suite (parameterized over obsidian-fs + stub adapters) surfaced this as a hard test failure on the stub-assembly row. The fix introduces `schemeFromSource(SourceConnector): string` and derives the scheme dynamically; `dossier.ts`'s sort-key helper is renamed `noteDocIdString → noteSortKey` and pinned to a fixed `vault://` prefix so sort order stays adapter-identical. No user-visible change for obsidian-fs callers (their scheme has always been `obsidian-fs://`); the fix unblocks future non-Obsidian sources.
828
+ - **Tool surface count: 26 → 30** (additive only). Four Phase 3 assembly tools added: `get_outline`, `search_sections`, `get_document_bundle`, `assemble_dossier`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries at slots 0–22 remain byte-identical (pinned by the existing `baseline.test.ts` "preserves the 23 v1 baseline tool names byte-identical" assertion). The diff against the Phase 2 snapshot is purely additive: four new tool entries appended; one optional field added to `search_hybrid.inputSchema.properties` (the four rescore params).
829
+ - **Tool surface count: 32 → 34** (Phase 5, additive only). Two new brief tools added: `compile_brief`, `get_brief`. The `list_briefs` discovery surface lives on MCP Resources (`vault-memory://briefs`) and does NOT count toward the tool budget — same precedent as the Phase 2 `vault-memory://memory/*` Resources. The 32 Phase 4 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (verified per-name); the diff is purely additive — two new tool entries appended.
830
+ - **Server bootstrap order extended (Phase 5)** — after `registerMemorySinks` and before `server.connect`, the bootstrap now (a) registers the `_memory/_briefs/` sink bound to the `default-brief-v1` contract and (b) starts `BriefStalenessDaemon` per vault. Daemon `start()` is fire-and-forget: a lock-contention failure does NOT block server startup — the server proceeds and `compile_brief` continues to work; only the staleness re-check is offline.
831
+ - **`SuppressionSet.consume()` accepts an optional `hash` argument (Phase 7 / CAN-08, additive).** Phase 1 callers that pass only a path see byte-identical behavior. When the plugin write path passes `(path, hash)`, the contracts loader at `src/contracts/loader.ts:242` matches both axes before short-circuiting — so a benign re-emit of the same canonical bytes is suppressed, but a divergent write still triggers reload.
832
+ - **Default-OFF plugin tool gating (Phase 7).** The 6 new plugin tools are wired through a config-time guard in `src/server.ts` so `tools/list` does NOT include them unless `[plugin] enabled = true`. The default-OFF surface keeps the v1 baseline snapshot byte-identical and keeps the REL-08 budget conversation focused on the user-facing v2 tools.
833
+
834
+ ### Dependencies
835
+
836
+ - **Phase 4 (plan 04-05 / GRA-02)** — pure-JS ESM, all MIT, all manually provenance-verified per Phase 4 RESEARCH §Package Legitimacy Audit:
837
+ - `graphology ^0.26.0` — graph data structure for the Louvain cluster wrapper.
838
+ - `graphology-communities-louvain ^2.0.2` — Louvain modularity-maximizing community detection.
839
+ - `seedrandom ^3.0.5` — deterministic seeded PRNG for the Louvain `rng` option.
840
+ - `@types/seedrandom ^3.0.8` (dev) — TypeScript types.
841
+ - No native bindings; no tsup `external` additions needed. ESM bundle grew from 376 KB to 392 KB (+13 KB).
842
+ - **Phase 7 plugin (ADR-007)** — added under `plugin/package.json` only; the server bundle is unaffected:
843
+ - `@xyflow/svelte` — Svelte Flow canvas renderer for the Variant C editor (MIT, license verified in ADR-007).
844
+ - `svelte`, `esbuild`, `vitest` — plugin-only build/test toolchain. None ship into the server `dist/`.
845
+
846
+ ### Migration
847
+
848
+ - **MIGRATION_011 — `edges` table + chunked backfill from `wikilinks` (Phase 4 plan 04-01).** Function-style migration mirroring `runMigration008`. Creates the `edges` table with `CHECK(type IN ('wikilink', 'mention', 'frontmatter-ref', 'hyperlink'))` and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE(anchor, '')` + `COALESCE(line_number, 0)` for proper NULL-as-distinct dedup. Backfills wikilink rows from the v1 `wikilinks` table in 10k-row chunks via `INSERT OR IGNORE` — idempotent, re-runnable, safe to interrupt. v1-shaped DBs migrate cleanly on first server start after upgrade; the v1 `wikilinks` table is preserved alongside the new `edges` table for D-01 v1 invariance. v3 cleanup will drop `wikilinks` once no v1 tool consumes it.
849
+ - **`notes.doc_uri` dual-column staging (Strategy A — additive, plan 01-02):**
850
+ - **MIGRATION_007** (additive) — adds nullable `doc_uri TEXT` column to `notes` + `idx_notes_doc_uri` index. Backwards-compatible; existing rows have `doc_uri IS NULL` post-migration.
851
+ - **MIGRATION_008** (function-style backfill) — sets `doc_uri = 'obsidian-fs://<vault>/' || path` for every row where `doc_uri IS NULL`. Idempotent; safe to re-run.
852
+ - Path stored **un-encoded** in the column; percent-encoding happens only at `formatDisplayUrl()` time (per ADR-002 §URL semantics).
853
+ - **No user action required.** Migrations run automatically on first server start after upgrade. SQLite transaction rollback is the safety net; no pre-migration backup is performed in v2 (deferred to Phase 8 per CONCERNS §"No Pre-Migration DB Backup"). (ADP-07)
854
+ - **v1 tool surface preserved byte-for-byte.** All 23 tool names, input schemas, output envelopes, and descriptions are unchanged. `evals/v1-baseline/tools-list.snapshot.json` regenerated under SDK 1.29 with zero diff against the Phase-0 baseline (W6 human-verify checkpoint passed in plan 01-06 Task 06).
855
+ - **MIGRATION_010 — sections table + notes.status column + section backfill (Phase 3 plan 03-01).** Three ordered steps in one transaction: (a) `CREATE TABLE sections` with 3 indexes, (b) `notes.status` column added + partial index `notes_status WHERE status IS NOT NULL`, (c) backfill `notes.status` from `json_extract(frontmatter, '$.status')` AND derive section rows for every indexed note via `markdownToSectionBlocks → extractSections`. Idempotent; re-applying is a no-op. Anchor-equivalence guarantee: backfilled section anchors match a fresh re-index byte-for-byte (the indexer + backfill share `src/chunker/headings.ts:extractHeadings` as the canonical heading source). v1-shaped DBs migrate cleanly without re-indexing.
856
+ - **`write_audit` migration v9** (function-style, idempotent) adds `is_memory_sink_write INTEGER NOT NULL DEFAULT 0` + a partial index `idx_write_audit_memory_sink ON (is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` (Phase 2 plan 02-06 / MEM-08). All Phase 1 audit rows backfill to `false`; new audit rows derive the flag from `WriteOptions.sink !== undefined` at the `ObsidianFsDelivery.write/update/delete` facade. The function-style migration runs `PRAGMA table_info` first so fixture tests that rewind `user_version` continue to pass.
857
+ - **v1 tool surface grows from 23 → 26** with the three Phase 2 memory tools (`record_observation`, `recall`, `supersede`). The 23 v1 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (pinned by a new `baseline.test.ts` assertion). The only diff on v1 tools is one optional input field added to `audit_log.inputSchema` (`is_memory_sink_write`); the v1 description text is byte-identical.
858
+ - **MIGRATION_013 — `chunks.chunk_id_fragment` + `brief_sources` + `daemon_state` (Phase 5 plan 05-01).** Three additive steps in one transaction: (a) `chunks.chunk_id_fragment` TEXT column added (`noUncheckedIndexedAccess` consumers see the new column as `T | undefined` until populated by the next index run); (b) `brief_sources` table (D-06 reverse-index — `(brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)`); (c) `daemon_state` table (per-vault change-feed cursor + `last_full_scan` timestamp). Idempotent; re-applying is a no-op. v1-shaped DBs migrate cleanly on first server start after upgrade.
859
+ - **Phase 5 brief artifacts are NOT a server schema migration.** Brief `Document`s land in `_memory/_briefs/` via the standard `DeliveryAdapter.write()` path; the `default-brief-v1` MemoryContract is registered at bootstrap (not persisted in SQLite). Users who never call `compile_brief` see zero new files on disk.
860
+ - **Phase 7 plugin is NOT a server schema migration.** No new SQLite migrations land for Phase 7. The plugin stores its own state in `data.json` (loadData/saveData) inside the Obsidian vault's plugin directory; secrets land as Electron safeStorage ciphertext only. Users who never enable the plugin flag see zero new server-side files.
861
+
862
+ ### Documentation
863
+
864
+ - Relocate ADRs 001–004 from `docs/dev/` (gitignored) to public `docs/v2/adr/`; amend each with Invariants + Examples sections covering both `obsidian-fs://` and `notion-api://` worked examples (FND-01, FND-04).
865
+ - ADR-003 amended with explicit hash-semantics pseudocode (RFC 8785 JCS, NFC normalisation, LF line endings, IEEE-754 number canonicalization) + chunk-level `source_hashes` schema (FND-02).
866
+ - ADR-004 amended: folder-default `MemorySink` is the only code path; separate-vault is config-only via `[memory] sink = "@…"` (FND-03). `.memory-sink` sentinel mandated.
867
+ - ADR-004 amended: underscored PropertyBag keys, confidence enum aligned to [direct, inferred, uncertain], superseded_reason field added (Phase 2 plan 02-01).
868
+ - Publish `docs/v2/ARCHITECTURE.md` (L0–L4 layer model + responsibility map), `docs/v2/MEMORY_CONTRACT.md` (provenance property contract on `Document.properties`), `docs/v2/AGENT_AGNOSTIC.md` (MCP-canonical client stance) (FND-05/06/07).
869
+ - Eval fixture vault `evals/fixtures/v2-test-vault/` — "Atlas Robotics" narrative; 56 notes across `projects/`, `meetings/`, `people/`, `decisions/`, `references/`; 15-document `_memory/` subset; 7 hand-labeled `_queries/*.yaml` (FND-08).
870
+ - v1-baseline regression suite `evals/v1-baseline/` — `tools-list.snapshot.json` pin for `tools/list` (23 tools), 11 per-tool semantic-floor YAMLs, `baseline.test.ts` vitest runner with `.todo` placeholders for Phase 1 precision/recall (FND-09/10).
871
+ - CI gates `scripts/check-fixture-privacy.sh` + `scripts/lint-no-telemetry.sh` + `.github/workflows/ci.yml` running `npm run lint:check && npm test` on every PR and push to `main` (FND-11/12 + D-21).
872
+ - ADR index `docs/v2/adr/README.md` listing 4 Accepted ADRs + 14 Open ADR stubs for v3 / Phase 10 follow-ups, including a Deferred-v3 section for adversarial-review findings (FND-13).
873
+ - Adversarial review `docs/v2/adr/ADVERSARIAL-REVIEW.md` — 10 findings against ADRs 001–004 raised by a fresh-context advisor; 6 Amended in Phase 0 (ADR-001 I-6, ADR-002 `DocumentRef.hash` contract + `hashProtected` enum, ADR-003 H-6), 4 Deferred-v3 to Phase 10 / Notion connector (FND-04 + FND-14 sign-off).
874
+ - Sign-off artifact `docs/v2/SIGN-OFF.md` — FND-01..14 checklist with resolving commit SHAs; PR approval is the FND-14 audit trail per D-17.
875
+ - Internal: extract `TOOLS` constant from `src/server.ts` to `src/tool-registry.ts` so `evals/v1-baseline/dump-tools.mjs` can produce the pinned snapshot without spinning the full MCP server (Phase 0 Assumption A5, no external behavior change).
876
+ - Phase 5 sign-off artifact `docs/v2/PHASE-5-SIGN-OFF.md` — BRF-01..BRF-11 traceability table + the five Phase 5 ROADMAP success criteria with disposition (`✅ MET` per criterion) + REL-08 hand-off plan to Phase 8.
877
+ - ADR-005 `docs/v2/adr/005-brief-compile-strategy.md` (Accepted) — governs the D-10 LLM strategy ladder and forbids bundling any remote LLM SDK in the server.
878
+ - Phase 7 plugin docs under `docs/v2/plugin/`: `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section added to the repo root README explaining how to flip `[plugin] enabled = true` and what the editor + chrome surfaces do.
879
+ - ADR-007 `docs/v2/adr/007-contract-editor.md` (Accepted, 2026-05-19) — rescopes the visual editor from a jsoncanvas fork to `@xyflow/svelte` (MIT, license verified inline in the ADR).
880
+
881
+ ### Deprecated (Phase 8 / REL-08)
882
+
883
+ The following five v1 tools are deprecated in v2.0.0 and promoted to MCP Resources. Each tool remains callable through the v2.x line for backwards compatibility; removal is scheduled for v3.0.0. The Resource URI is the canonical replacement.
884
+
885
+ - **`list_vaults`** → `vault-memory://vaults` (cross-vault discovery Resource). The tool remains callable through v2.x; removal scheduled for v3.0.0.
886
+ - **`list_models`** → `vault-memory://models/{vault}` (per-vault embedding-model inventory). The tool remains callable through v2.x; removal scheduled for v3.0.0.
887
+ - **`recent_notes`** → `vault-memory://recent/{vault}` (per-vault mtime-DESC recent notes). The tool remains callable through v2.x; removal scheduled for v3.0.0.
888
+ - **`vault_stats`** → `vault-memory://stats/{vault}` (per-vault note count + top tags + top frontmatter keys + last index run). The tool remains callable through v2.x; removal scheduled for v3.0.0.
889
+ - **`list_backlinks`** → `vault-memory://backlinks/{vault}/{+docId}` (per-document backlink listing). The URI uses RFC 6570 reserved expansion: `{+docId}` allows `/` in the variable value so multi-segment `docId`s like `obsidian-fs://my-vault/notes/sub/file.md` parse correctly. The tool remains callable through v2.x; removal scheduled for v3.0.0.
890
+
891
+ ## [Unreleased]
892
+
893
+ ### Added
894
+
895
+ - **`create-contract` agent skill** — `skills/create-contract/SKILL.md` lets a local agent turn a user's free-language description of a recurring search routine into a validated task-contract YAML written to `_contracts/`. Three modes: default authoring (free-text intake → design questions → verb selection → `doc_ids` chaining → memory-sink/brief-LLM setup → write + "test before you trust" handoff), **discovery mode** (read the gap log, cluster recurring unmet requests, propose contract candidates), and **artifact-schema mode** (ADR-030 precomputed-artifact field shape). References `docs/v2/plugin/AUTHORING-CONTRACTS.md` for the DSL rather than duplicating it; honest about Phase-8.5-not-yet-real items (ADR-027 reference picker, ADR-030 materialization, ADR-029 loops). Skill-only — zero `src/` change, no new MCP tool, no server LLM coupling.
896
+ - **Missing-contract discovery (skill + memory-sink layer)** — `use-contracts` now appends a structured entry to the memory sink (`_memory/_contract-gaps/`) whenever no contract matches a request (request + inferred intent shape + vault + timestamp). `create-contract --discovery` clusters those gaps and proposes the most frequent as new-contract candidates. Collect-and-suggest, not autonomous learning (true quality-signal loops await ADR-029 server instrumentation). The gap log is an allowed sink write under the sacrosanct memory-namespace invariant — never a silent write into user notes.
897
+ - **`/vmem:install` now installs content skills** — new Checkpoint 6.6 in the vmem marketplace plugin's `setup.sh` fetches the six content skills (`add-vault`, `audit-vault-health`, `find-stale-notes`, `triage-inbox`, `use-contracts`, `create-contract`) into `<vault>/.claude/skills/`. Previously the plugin installed only operational skills (install/health/reindex) and no content skills. Auto-applies in headless/AUTO mode; confirm prompt with a TTY; non-destructive. `scripts/install-skills.sh` gains `use-contracts` so both contract skills also ship via the curl path. vmem plugin bumped 0.3.10 → 0.3.11.
898
+
899
+ ### Changed
900
+
901
+ - **Obsidian plugin error messages reference `/vmem:install`** (was: `vm-install skill` / `/vm-install`). Two strings updated in `plugin/src/services/mcp-client.ts` (the `CliNotFoundError` thrown in both the spawn-time and connect-time error paths) and one banner string in `plugin/src/chrome/settings-tab.ts` (the "CLI not found" banner in the settings tab). The new strings point users at the renamed marketplace plugin `vmem` (replacing the old `install-vault-memory` with its duplicated `/install-vault-memory:install-vault-memory` slug) and the verb-split surface: `/vmem:install`, `/vmem:health`, `/vmem:reindex`. See `ISSUE-marketplace-plugin-rename-vmem.md`.
902
+ - **`CHANGELOG.md` now ships in the npm tarball** — added to the `files` field in `package.json` so users running `npm view @owrede/vault-memory@<version>` or extracting the tarball can read per-release notes without leaving npm.
903
+ - **`publish.yml` adds an `NPM_DISABLE_PROVENANCE` escape hatch** — repo variable `NPM_DISABLE_PROVENANCE=1` skips the `--provenance` flag on `npm publish`. Recovery path for sigstore-attestation-orphan states. Default off; provenance stays on for all normal publishes.
904
+
905
+ ### Skipped: v2.0.0-rc.3
906
+
907
+ - **v2.0.0-rc.3 was tagged but never published to npm.** The first publish attempt for rc.3 wrote a sigstore log entry but the actual package PUT failed with `404 Not Found` from the npm registry; multiple subsequent retries with different tarball contents and provenance disabled all hit the same 404 (registry-internal poisoned state). rc.4 supersedes rc.3 and contains every change rc.3 would have shipped.
908
+
909
+ ### Fixed
910
+
911
+ - **Migration 010 — `UNIQUE constraint failed: sections.note_id, sections.anchor`** crash blocking v0.9.x/v1.0.0 → v2.0.0-rc.1 upgrade for any user whose vault contains heading-only sibling sections (e.g. template scaffolds with repeated `## TODO` headings whose body slot is empty). `extractSections` produces identical content-hash anchors for sections with identical `(heading_text, body)` pairs, and the `INSERT INTO sections` was plain so the second sibling aborted the migration transaction — leaving every CLI command unreachable for every configured vault until the bad DB was removed from `config.toml`. `backfillSectionsFromChunks` now uses `INSERT OR IGNORE`; on collision the surviving row's id is looked up and reused so the `insertedIds` slot still resolves later children's `parent_id` correctly. See `ISSUE-migration-010-duplicate-anchor.md`.
912
+
913
+ ### Added
914
+
915
+ - **Task Contract DSL (Phase 6, ADR-006)** — declarative YAML contracts under `_contracts/<name>.yaml`, addressable by name, instantiable via MCP, with handle-based source/sink portability. Contracts use a closed assembly verb enum (11 baseline + `literal` + `mcp://<server>/<tool>` peer extension), `{{template}}` step composition, JSON-Schema-with-`$ref` inputs, MemorySink-only sinks (un-bypassable per D-A4c), and ChangeFeed hot reload (D-LOAD). See `docs/v2/PHASE-6-SIGN-OFF.md` + `docs/v2/adr/006-task-contract-dsl.md`.
916
+ - **3 new MCP tools (Phase 6)** — `describe_contract`, `instantiate_contract`, `register_contracts_as_tools`. Tool count: 34 → 37 (additive only; v1-baseline preserved byte-identical).
917
+ - **2 new MCP Resources (Phase 6)** — `vault-memory://contracts/{vault}` (CON-04) lists contracts available in a vault; `vault-memory://contract-verbs/{vault}` (D-A2b) lists baseline + custom `mcp://` verbs with invocation counts. Resources do NOT count toward the REL-08 tool budget per Phase 5 BRF-09 precedent.
918
+ - **3 reference contracts (Phase 6)** under `evals/fixtures/v2-test-vault/_contracts/`: `meeting-prep`, `project-status`, `code-review-brief`. Plus `smoketest-trivial` for the CON-09 non-Claude smoketest path (literal-only assembly; no LLM needed in CI).
919
+ - **`contract_audit` table (Phase 6, migration 014)** — orchestration audit substrate; one row per assembly step (kind=`contract_step`) and one row per load failure (kind=`contract_load_error`). Payload-free per Invariant C-5 (peer-MCP outputs may carry sensitive data; we never capture them). Feeds `aggregateVerbUsage(vault)` for the D-A2b promotion signal.
920
+ - **`[contracts]` config block (Phase 6)** — `auto_register_tools: bool = false`, `tool_prefix: string = "vm_"`, `step_timeout_seconds: number = 30`, `defaults: Record<string, string> = {}`, `mcp_clients: Record<string, {command, args?, env?}> = {}`.
921
+ - **`instantiate_contract` write_back chokepoint (Phase 6)** — routes through `DeliveryAdapter.write()` so the MEM-05 invariant is un-bypassable by construction. Sink overrides MUST resolve through `MemorySinkRegistry.resolveMemorySink()` before write; otherwise `{ok:false, reason:"sink_override_not_a_memory_sink"}`.
922
+ - **CON-10 stub-parity proof (Phase 6)** — `src/adapters/source/conformance.test.ts` gains a `contracts stub-parity (CON-10)` describe block proving the same contract produces structurally identical bundles across obsidian-fs and stub source connectors.
923
+ - **CON-09 non-Claude smoketest extension (Phase 6)** — `scripts/smoketest-non-claude.mjs` spawns the server against a temp HOME with the fixture vault, asserts the three contract tools surface, calls `describe_contract` + `instantiate_contract` + reads the `list_contracts` Resource, and exits 0.
924
+ - **`ObsidianFsSource` widened to enumerate `_contracts/*.yaml` (Phase 6)** — non-recursive walk added via `scanContractFiles`; `scanVault` unchanged so the indexer's `.md`-only contract is preserved. `readDocument` handles YAML files by returning raw text as a single paragraph block (bypasses `parseNote`'s markdown assumptions).
925
+ - **`expand` MCP tool (Phase 4 plan 04-03 / GRA-01)** — typed-edge BFS retrieval primitive. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying `via: {seed_doc_id, hop, edge_type, direction}` provenance. Hops hard-capped at 2 (v2.0.0). Default direction `'both'`. Filterable by `edge_types: EdgeType[]` and by `filter_properties` (strict equality, no operators). Memory-sink documents (`_memory/...`) surface only when transitively reachable from a user-note seed via at least one in-result inbound edge — per ADR-004 memory-namespace opacity rule. Unknown `seed_doc_ids` do NOT throw — they are returned in a `warnings: [{seed_doc_id, reason: 'unknown_doc'}]` array. Shortest path wins on dedup; ties broken by `(seed_doc_id, edge_type, direction)`.
926
+ - **`cluster` MCP tool (Phase 4 plan 04-05 / GRA-02)** — Louvain community detection over the typed-edge graph, deterministic across runs via `seedrandom`. Accepts mutually-exclusive `query | seed_doc_ids`. Returns `{ok: true, communities: Cluster[]}` or `{ok: false, reason: 'too_many_nodes' | 'missing_input' | 'mutually_exclusive_input'}`. 5000-node hard cap with `force: true` override. Per-community `cluster_id` is the smallest member DocId by lexicographic order — deterministic naming independent of internal community index assignment. Hybrid-search dependency injected via `ClusterDeps.hybridSearch` closure to avoid the `src/graph/cluster.ts → src/search/hybrid.ts` circular import.
927
+ - **`search_hybrid({expand})` additive nested param (Phase 4 plan 04-04 / GRA-03)** — pass `expand: {hops: 1|2, direction?, edge_types?}` to auto-attach 1–2 hop typed-edge neighbors as `SearchHit.expansions?: CitationPacketWithVia[]` per hit. Runs AFTER recency/authority rescore (D-16); never participates in score computation; top-K ranking unchanged. Guard-and-short-circuit composition: when `expand` is omitted, the code path is byte-identical to Phase 3 (zero new DB reads, zero new JSON fields).
928
+ - **Typed-edge storage substrate (Phase 4 plan 04-01 / GRA-04)** — `edges` table (migration 011) with four edge types (`wikilink`, `mention`, `frontmatter-ref`, `hyperlink`) and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE` for proper NULL-as-distinct dedup. Chunked backfill from v1 `wikilinks` table (10k-row chunks, `INSERT OR IGNORE`, idempotent). The v1 `wikilinks` table is preserved alongside `edges` for v1 invariance (D-01); v3 cleanup is tracked.
929
+ - **Unified edge extractor (Phase 4 plan 04-02 / GRA-04 indexer)** — `extractAllEdges(vault, parsed, resolver): EdgeInput[]` extracts all four edge types in a single per-note parse pass. `MIN_MENTION_LEN = 4` (empirically validated against the Atlas Robotics fixture: 0 raw FPs over 62 notes). `FRONTMATTER_REF_ALLOWLIST` is a sealed 8-key `ReadonlySet<string>` (`assignee`, `owner`, `project`, `related`, `parent`, `child`, `attendees`, `superseded_by`). Mention candidate set built from `note_aliases` only; word-boundary uses `(?<![\w-])`/`(?![\w-])` so aliases containing `-`/`_` are matched as whole tokens. Paragraph-scope masking blanks fenced code, ATX headings, inline backticks, and `[[wikilink]]` spans (byte length preserved so line-offset lookups stay valid).
930
+ - **`AliasesQueries.listAll()` (Phase 4 plan 04-02)** — full alias inventory, sorted by `alias_norm ASC` for deterministic mention-regex compilation.
931
+ - **Additive `type: EdgeType` field on graph result rows (Phase 4 plan 04-01)** — `list_backlinks`, `list_forward_links`, `find_broken_links` results widen with `type`. `BacklinkEntry.relation` / `ForwardLinkEntry.relation` widen from the v2.0.0-pinned `"wikilink"` literal to the full `EdgeType` union. `assemble_dossier.linked_documents[].relation` and `get_document_bundle.{backlinks,forward_links}[].relation` widen the same way — the `PHASE-4-WIDEN` marker comments in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` (a Phase 3 v2.0.0 limitation) are now retired.
932
+ - **3 new eval YAMLs (Phase 4 plan 04-06 / GRA-05):**
933
+ - `evals/fixtures/v2-test-vault/_queries/expand.yaml` — 8 hand-curated queries over the Atlas Robotics live fixture covering all four edge types, mixed-type traversal at hops 1 and 2, `_memory/` opacity, and the unknown-seed warning path. All eight clear `min_precision >= 0.8` / `min_recall >= 0.8`.
934
+ - `evals/fixtures/v2-test-vault/_queries/search-hybrid-with-expand.yaml` — 3 composition queries exercising `search_hybrid({expand: {hops}, ...})` end-to-end.
935
+ - `evals/fixtures/v2-test-vault/_queries/cluster.yaml` — D-12 byte-snapshot of Louvain partition (cluster_id + sorted member DocIds per community); pins determinism across library upgrades + Node-minor versions.
936
+ - **Cross-adapter conformance for graph tools (Phase 4 plan 04-06)** — 6 new parameterized cases in `src/adapters/source/conformance.test.ts` run `expand()` + `cluster()` against both `obsidian-fs` and `stub` adapters via `src/graph/__test_helpers__/atlas-live-fixture.ts`. New harness fields (`graphSeedDocId`, `occludedMemoryDocId`, `reachableMemoryDocId`) carry adapter-specific reference DocIds.
937
+ - **Adapter seams (Phase 1, plans 01-01..06)** — `SourceConnector` / `DeliveryAdapter` / `ChangeFeed` interfaces under `src/adapters/` per ADR-002. `obsidian-fs` is the v2 reference implementation for all three. The seam shape is preserved across vault-content read, vault-content write, and change-event paths so future connectors (Notion, Logseq, …) drop in without touching `src/server.ts` or any v1 tool. (ADP-01, ADP-02, ADP-03)
938
+ - **Canonical v2 types** in `src/types.ts`: `Document`, `BlockNode`, `Edge`, `ChangeEvent`, `SourceHandle`, `MemorySink`, branded `DocId`, `WikilinkRef`. `Document.properties: Record<string, unknown>` subsumes both YAML frontmatter and future Notion typed properties. (ADP-04, ADP-05)
939
+ - **`src/adapters/registry.ts`** — adapter registry + sole minting point for branded `DocId`s via `parseDocId` / `formatDocId`. Future adapters register under their canonical scheme (`obsidian-fs://`, `notion-api://`, …). (ADP-05)
940
+ - **Stub-adapter conformance suite** — parameterized over `obsidian-fs` and `stub`, three test files (`src/adapters/{source,delivery,change-feed}/conformance.test.ts`). Lets future adapters validate themselves against the v2 contract before any production code lands. (ADP-13)
941
+ - **`scripts/lint-adapters.sh`** — POSIX shell CI gate enforcing ADR-002 Invariants I-1..I-6 + C-1 (Claude-leak) + I-5b (`obsidian://` literal). Wired into `npm run lint:check` and `.github/workflows/ci.yml`. (ADP-12)
942
+ - **`scripts/smoketest-non-claude.mjs`** — end-to-end smoketest against the real MCP SDK Client (identifies as `non-claude-smoketest`); CI-gated. Asserts 23 tools listed, all descriptions non-empty, `tools/call list_vaults` succeeds, `tools/call <bogus>` surfaces as error. (ADP-10)
943
+ - **`docs/v2/AGENT_AGNOSTIC_AUDIT.md`** — per-leak inventory of every Claude / Obsidian assumption in `src/`, with explicit `fixed-v2` / `mixed` / `deferred-v3` status + rationale per row. Cross-references CONCERNS.md, ADR-002, and the resolving Phase-1 plans. (ADP-11)
944
+ - **`record_observation` MCP tool** — write a labeled memory observation through a configured `MemorySink` with mandatory provenance (Phase 2 plan 02-04 / MEM-02). Sugar args (`claim`, `evidence`, `confidence`, `type`, `sink?`) pre-fill the contract-required keys; optional `properties: Record<string, unknown>` escape hatch merges LAST so callers can populate any contract-allowed extra (D-02).
945
+ - **`recall` MCP tool** — retrieve memory documents filtered by `min_confidence`, `types`, `max_age_days`, and `sink`; returns Phase 3-shaped citation packets `{doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties}` (Phase 2 plan 02-05 / MEM-03 / D-01). Hides `status: superseded` by default; sorts `observed_at` DESC with `mtime` tiebreak; truncates AFTER filter+sort.
946
+ - **`supersede` MCP tool** — forward-only mark of a memory document as superseded by a replacement, with mandatory non-empty `superseded_reason` (Phase 2 plan 02-04 / MEM-04 / D-03). Single OCC `delivery.update()` call; the replacement document is never touched (back-edge derivation deferred to Phase 4 graph layer).
947
+ - **`vault-memory://memory/sinks` MCP Resource** — lists configured memory sinks per vault with their handle, contract name, and default-flag (Phase 2 plan 02-06 / MEM-09).
948
+ - **`vault-memory://memory/stats` MCP Resource** — per-sink doc counts, `by_type` / `by_status` breakdown, last-write timestamp aggregated from the indexed `notes` table + the `write_audit` partial index (Phase 2 plan 02-06 / MEM-09). Polled-only; no `notifyResourceUpdated` in v2.0.0.
949
+ - **`is_memory_sink_write` column on `write_audit`** (migration v9) + partial index on `(is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` + optional `is_memory_sink_write` filter on the `audit_log` tool input schema (Phase 2 plan 02-06 / MEM-08).
950
+ - **`decomposeDocId` helper** on `src/adapters/registry.ts` for splitting `DocId`s into `{scheme, authority, resource}` parts. Re-uses `DOC_ID_PATTERN`; no second regex (Phase 2 plan 02-02).
951
+ - **`pathInSink` + `joinVaultPath` helpers** in `src/adapters/delivery/obsidian-fs/path.ts` — the SOLE licensed sink/vault `path.join` sites for Phase 2+ per ADR-002 I-3 (Phase 2 plan 02-02).
952
+ - **MemorySink runtime** — `parseMemorySinkHandle` (IIFE-closed brand mint), `MemorySinkRegistry` (sole resolver per ADR-004 §Resolution), `.memory-sink` sentinel mechanics in `src/adapters/delivery/obsidian-fs/sentinel.ts`, hardcoded `DEFAULT_MEMORY_V1` contract + YAML loader at `src/memory/contract/` (Phase 2 plan 02-02 / MEM-01, MEM-05, MEM-06).
953
+ - **5 net-new memory fixture docs** under `evals/fixtures/v2-test-vault/_memory/` including the A→B→C Spire-budget supersede chain (2026-04-23 → 2026-04-24 → 2026-04-26), plus net-new `type: hypothesis`, `type: decision`, and `confidence: uncertain` dimensions (Phase 2 plan 02-07 / MEM-10). Fixture grows from 15 → 20 docs.
954
+ - **`tests/fixtures/malformed-memory/`** tree with 5 deliberately-broken fixtures (`missing-observed-at`, `missing-source`, `invalid-confidence`, `supersede-no-target`, `source-agent-no-evidence`) for validator failure-mode unit tests; each carries `expected_reason` + `expected_key` frontmatter (Phase 2 plan 02-07 / MEM-10).
955
+ - **`docs/tools/audit_log.md`** documenting the new optional `is_memory_sink_write` filter on the `audit_log` tool. Documentation lives here rather than in the MCP tool description text — the description is byte-identical to Phase 1 to honor the v1 backwards-compat invariant (Phase 2 plan 02-06).
956
+ - **Section identity substrate (Phase 3 plan 03-01 / ASM-01, ASM-02, ASM-03, ASM-05, ASM-08)** — `sections` table (migration 010) materializing per-document outline trees with content-hash anchors per ADR-003 H-7 (`sha256_hex(NFC(heading_text) || "\n" || render_blocks_to_plain_text(blocks))`). Parent-pointer reconstruction; per-section `chunk_id_first` / `chunk_id_last` ranges; denormalized `notes.status` column with partial index (`notes_status`) for the SQL-level superseded filter. Backfill from existing v1 vaults runs in lockstep with the migration so user vaults gain sections + status on first upgrade without re-indexing.
957
+ - **`get_outline` MCP tool** — return a nested `OutlineNode[]` tree for a `doc_id`. Each node carries `{anchor, heading_path, heading_text, level, chunk_ids: string[], children}` per D-02. Anchors are stable across re-indexings of unchanged content. Doc-level response envelope is the 8-field citation packet shape (Phase 3 plan 03-02 / ASM-02, ASM-05).
958
+ - **`search_sections` MCP tool** — section-level retrieval that COMPOSES the v1 chunk-level hybrid pipeline (`hybridSearch`) with a chunk-to-section promotion step. Accepts `{query, limit, vaults?, recency_weight?, authority_weight?, half_life_days?, include_superseded?}`. The promotion strategy: inflate `topK = limit × 5`, run hybrid once, promote each chunk hit to its enclosing section, dedupe by `(note_id, anchor)`, score each section as `MAX(constituent chunk scores)`, sort DESC tie-broken by `chunk_id_first` ASC, slice to `limit`, hydrate into `SectionHit` packets carrying the 8-field citation floor plus `{anchor, score, chunk_ids, snippet?}`. Preamble (level-0) sections are dropped — only sections with a non-empty `heading_path` surface. (Phase 3 plan 03-03 / ASM-03, ASM-05)
959
+ - **`get_document_bundle` MCP tool** — one-call composite read for a `doc_id`: `{anchor, outline, backlinks, forward_links, recent_edits}`. The anchor is a full 8-field citation packet with optional ASM-06 extras (`status`, `superseded_by`); `outline` re-uses `buildOutlineTree` from `get_outline` (composition, not duplication); `backlinks` + `forward_links` carry citation packets plus `{property_snippet, relation}` (relation is `"wikilink"` in v2.0.0 — see Phase 4 widening note below); `recent_edits` ≤10 entries from `audit_log`, with `is_memory_sink_write` surfaced only when truthy. (Phase 3 plan 03-04 / ASM-01, ASM-05, ASM-06)
960
+ - **`search_hybrid` rescore signals (Phase 3 plan 03-05 / ASM-06, ASM-07, ASM-08, ASM-11) — additive Zod params**, all defaulting to v1-no-op so legacy callers see byte-identical responses:
961
+ - `recency_weight: number` (default `0`) — adds `recency_weight × exp(-age_days / half_life_days)` to each candidate's RRF score.
962
+ - `authority_weight: number` (default `0`) — adds `authority_weight × 1` for docs with `properties.authoritative === true`.
963
+ - `half_life_days: number` (default `30`) — recency decay half-life.
964
+ - `include_superseded: boolean` (default `false`) — when `false`, excludes `status: "superseded"` chunks at SQL level via the `notes_status` partial index. Zero per-candidate frontmatter parses on the default-hide path.
965
+ - **9 new optional `SearchHit` fields** (D-08, ASM-06): `doc_id`, `source_handle`, `heading_path`, `mtime`, `hash`, `display_url`, `status`, `superseded_by`, `properties` — all snake_case to align with the Phase 2 `CitationPacket` shape; all optional and JSON-omitted on the v1-default path.
966
+ - **Display-URL resolver seam** — `HybridSearchOptions.displayUrlFor` optional closure keeps the `obsidian://` literal mint site in the obsidian-fs source adapter per ADR-002 §I-5b.
967
+ - **Clock-injection seam** — `HybridSearchOptions.clock` optional closure mirrors the recall convention; tests pass a fixed clock for deterministic age math.
968
+ - **`assemble_dossier` MCP tool** — resolve a `{type, key}` pair to an anchor `Document` and walk backlinks into a structured dossier `{anchor, linked_documents, property_rollups: {linked_count, linked_types, status_distribution}, error}` (Phase 3 plan 03-06 / ASM-04, ASM-05, ASM-10). Strict `properties.type` match (D-03); `key` matches the candidate's `title` OR any entry in `properties.aliases` (D-04). Every linked document carries an 8-field citation packet (D-01) plus the `relation` field. **v2.0.0 limitation:** `linked_documents[].relation` is always `"wikilink"` because the v1 `wikilinks` table only stores wikilink edges; Phase 4 (GRA-04 typed edges) will widen the field to the full `Edge.type` enum (mention, frontmatter-ref, hyperlink, …) as an additive change. Search for `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` for the widening sites.
969
+ - **Source-neutrality conformance suite for assembly tools (Phase 3 plan 03-07 / ASM-12)** — `src/adapters/source/conformance.test.ts` extends its `describe.each` parameterization with a new "Assembly tools — $name" section over `[obsidian-fs, stub-assembly]`. Five canonical assertions per adapter row (10 total) cover `get_outline`, `assemble_dossier`, `search_sections`, `get_document_bundle`, and citation-packet shape parity with recall's output. Backed by `src/adapters/stub/assembly-fixture.ts` — an 8-document purpose-built `Document[]` covering aliases, authoritative, superseded, all four edge types, and a multi-section doc. Per RESEARCH §7 P/R evals (ASM-10 dossier, ASM-11 recency) run against the obsidian-fs adapter only; the stub fixture is purpose-built for contract conformance.
970
+ - **Recency eval fixture** — `evals/fixtures/v2-test-vault/_queries/recency.yaml` ships ASM-11's stale-vs-fresh scenario: two near-duplicate Atlas-1 status notes (`status_updates/atlas-1-old-update.md` mtime ≈6 months back, `status_updates/atlas-1-new-update.md` mtime ≈1 day back) with neutral + recency-weighted query pair. Mtime contract is documented inline; eval harness `fs.utimesSync`-injects mtimes in a `beforeAll` block (git does not preserve mtimes on checkout).
971
+ - **Compiled brief layer (Phase 5, ADR-005)** — briefs are first-class `Document`s in `_memory/_briefs/` with chunk-level `source_hashes` provenance; compile-time wikilink emission (D-11), auto-supersede on target collision (D-12), and forward-only supersede chain. Writes route through `DeliveryAdapter` so the MEM-05 memory-namespace invariant is un-bypassable by construction. Closes the "agents rediscover 85% of context every run" failure mode that motivated the v2 program. See `docs/v2/PHASE-5-SIGN-OFF.md` + `docs/v2/adr/005-brief-compile-strategy.md`.
972
+ - **2 new MCP tools (Phase 5)** — `compile_brief`, `get_brief`. Tool count: 32 → 34 (additive only; the 32 Phase 4 entries verified byte-identical per-name in `evals/v1-baseline/tools-list.snapshot.json`; strict-equality snapshot test re-enabled in `baseline.test.ts`).
973
+ - **`vault-memory://briefs` MCP Resource (Phase 5 / BRF-09)** — pure-read discovery surface with optional `?target=<pattern>` substring filter. NOT a Tool; Resources do not count toward the REL-08 tool budget. `readListBriefs` is a closure over `MemorySinkRegistry` + `VaultManager` + `SourceConnector` and carries zero `fs` / `path` / `gray-matter` / `chokidar` imports (enforced by `scripts/lint-adapters.sh` + an explicit assertion in `src/brief/resources.test.ts` Test 10).
974
+ - **Brief staleness daemon (Phase 5 / BRF-05..08)** — `BriefStalenessDaemon` subscribes to `ChangeFeed.subscribe()`, runs a startup full scan against `brief_sources.listBriefDocIds()` (BRF-07; cursor in `daemon_state`), and preserves brief→source links across rename events via a 5-second pending-deletes grace window (BRF-08). Single-owner via `~/.vault-memory/locks/<vault>.lock` (`fs.open('wx')` + PID liveness); on lock contention emits `daemon_already_owned` and returns `{acquired: false}` without subscribing.
975
+ - **`default-brief-v1` MemoryContract (Phase 5)** — the provenance frame required by every brief `Document`. Required properties: `source: "agent"`, `confidence: "inferred"`, `evidence: parsedSourceDocIds`, `status: "active"`, `observed_at`, `superseded_by`, `type: "brief"`, `target`, `purpose`, `compiled_from`, `compiled_at`, chunk-level `source_hashes` map, and `model` audit attribution. The `_memory/_briefs/` sink binds to this contract at registration time.
976
+ - **ChunkId brand + content-stable fragment helpers (Phase 5)** — `parseChunkId`, `formatChunkId`, `decomposeChunkId`, `computeChunkHash`, `computeChunkIdFragment` in `src/chunker/chunk-id.ts`. D-04 content-derived 7-char fragments are essential for the v3 multi-user story (every user recomputes identical fragments over the same source text).
977
+ - **`OllamaClient.chat()` (Phase 5)** — `/api/chat` route alongside `/api/embed`; the first non-embedding LLM call in vault-memory history. Governed by ADR-005. No remote LLM SDK is bundled; the D-10 ladder probes MCP Sampling first (`getClientCapabilities().sampling`), then local Ollama via `[brief.ollama]` config, then falls back to `args.prepared_text` for deterministic CI / non-LLM contracts.
978
+ - **3 new eval YAMLs (Phase 5 / BRF-10, BRF-11):**
979
+ - `evals/fixtures/v2-test-vault/_queries/briefs-curated.yaml` — curated 20-document Atlas Robotics scenario; modifying one source flips the brief stale within one change-feed cycle.
980
+ - `evals/fixtures/v2-test-vault/_queries/briefs-staleness-stub.yaml` — BRF-11 cross-adapter parametric run (`adapters: [obsidian-fs, stub]`) proving brief-layer source-neutrality.
981
+ - `evals/fixtures/v2-test-vault/_queries/briefs-from-cluster.yaml` — cluster-fed compile scenario.
982
+ - **Cross-adapter conformance for brief tools (Phase 5)** — `src/adapters/source/conformance.test.ts` gains a `compile_brief + staleness daemon (BRF-11 source-neutrality)` describe block: 4 conformance test cases × 2 SourceConnector adapters = 8 test runs. Covers update flips stale, startup scan recovers missed events, delete past grace-window marks stale, and rename within grace-window preserves brief→source link.
983
+ - **Obsidian plugin (Phase 7, ADR-007)** — `plugin/` Obsidian community-plugin package (`manifest.json` v2.0.0, id=`vault-memory`, minAppVersion 1.5.0) with sideload-capable layout. `npm run build` emits a 1.9 MB `plugin/main.js`. Ships behind `[plugin] enabled = false` server-side; v1 baseline `tools/list` snapshot remains byte-identical when the flag is unset (FND-10 still passes). See `docs/v2/plugin/README.md` + `docs/v2/adr/007-contract-editor.md`.
984
+ - **Variant C three-pane contract editor (Phase 7 / CAN-01..CAN-07, CAN-10)** — palette + Svelte Flow canvas + Zod-derived inspector for editing Phase 6 task contracts. Canvas wires to `@xyflow/svelte` (MIT, license verified in ADR-007 — rescoped from the original jsoncanvas fork). `registerView('vault-memory-contract-editor')` + `registerExtensions(['contract'])` so opening a `.contract` file launches the editor.
985
+ - **`.contract` JSON envelope + lossless round-trip codec (Phase 7 / CAN-02, CAN-03, CAN-07)** — custom JSON format (`vmFormatVersion: 1`) with a pure-TS round-trip codec (`plugin/src/codec/contract-codec.ts`). Round-trip fixed-point pinned to 3rd/4th-emission byte identity across 4 fixtures (19/19 round-trip tests passing). Covers every ADR-006 field: `version`, `name`, `description`, `inputs`, `sources`, `sinks`, `assembly`, `output_shape`, `write_back`, `required`, `mcp_clients`.
986
+ - **3 reference `.contract` files (Phase 7 / CAN-06)** — `examples/contracts/meeting-prep.contract`, `project-status.contract`, `code-review-brief.contract`, each pinned to its Phase 6 YAML twin via 3 fixture round-trip tests.
987
+ - **6 new plugin-control MCP tools (Phase 7) — gated by `[plugin] enabled = true`, default OFF.** `set_runtime_config`, `resolve_secret`, `set_mcp_client`, `get_runtime_stats`, `trigger_reindex`, `suppress_contract_write`. Default-OFF tool count: unchanged from Phase 6 (37); when the plugin flag is enabled the surface grows to 43. The v1-baseline tools-list snapshot remains byte-identical against the default-OFF surface — verified by `evals/v1-baseline/baseline.test.ts` "matches the pinned snapshot exactly" + "preserves the 23 v1 baseline tool names byte-identical".
988
+ - **Electron safeStorage-backed secrets (Phase 7 / PLG-02)** — `plugin/src/services/safe-storage.ts` wraps `window.electron.safeStorage` (`encryptString`/`decryptString`); only ciphertext lands in `data.json`. Secrets referenced by name from `[contracts.mcp_clients]` config via `${secret:name}` placeholders (regex `\$\{secret:([a-z][a-z0-9_-]{2,63})\}`). Connector-resolver test suite has 11 passing tests pinning the placeholder grammar + resolution path.
989
+ - **Plugin chrome — settings, reindex, stats, connectors panels (Phase 7 / PLG-01, PLG-03, PLG-04, PLG-05)** — `plugin/src/chrome/settings-tab.ts` (Ollama URL, model, indexer config; persisted via `loadData`/`saveData`; hot-swap via `set_runtime_config`), `reindex-panel.svelte` + controller (manual trigger with MCP `notifications/progress` feedback; SuppressionSet-aware), `stats-panel.svelte` + controller (read-only stats via `get_runtime_stats`), and `connectors-panel.svelte` (list/add/remove peer MCP clients, secrets-by-name). 42 chrome unit tests passing (7+8+7+12+8).
990
+ - **Hash-aware `SuppressionSet.consume(path, hash)` extension (Phase 7 / CAN-08)** — the Phase 1 `SuppressionSet` widens to accept a content hash so the contracts loader can short-circuit re-validation when the watcher fires on the plugin's own write. `src/contracts/loader.ts:242` calls `consume(resource, hash)` before reloading; `src/plugin-tools/suppress-contract-write.ts:109` registers the `(path, hash)` pair from the plugin BEFORE the write. Three dedicated loader tests cover the suppression path; regression watcher tests still pass (10/10 ObsidianFsChangeFeed + 6/6 VaultWatcher).
991
+ - **`vm-install` + `vm-update` Claude Code skills (Phase 7 / CAN-09 distribution half)** — one-liner curl-pipe install / upgrade for the plugin, with SHA-256 verification of the downloaded tarball. Idempotent. `bash skills/vm-install/setup.test.sh` 9/9 pass (fresh install + idempotent no-op); `bash skills/vm-update/update.test.sh` 12/12 pass (no-op + upgrade w/ SHA-256 + re-run). `RELEASE_URL_PLACEHOLDER` resolution is bookkept for the v2.0.0 GitHub Release publish (ROADMAP Phase 8 carryover).
992
+ - **6 plugin docs under `docs/v2/plugin/` (Phase 7 / CAN-09 docs half)** — `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section at the top of the repo README explains how to enable the flag and what the editor + chrome do.
993
+
994
+ ### Changed
995
+
996
+ - **Tool surface count: 30 → 32** (Phase 4, additive only). Two new graph tools added: `expand`, `cluster`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries + 7 Phase 3 entries remain content-identical (verified per-name; the strict-equality snapshot test `baseline.test.ts` "matches the pinned snapshot exactly" — which Plan 04-03 had `.skip`'d pending this regen — is re-enabled and green). The diff against the Phase 3 snapshot is purely additive: two new tool entries appended (before `assemble_dossier` to preserve insertion order at the end of `TOOLS`); one optional nested property added to `search_hybrid.inputSchema.properties` (`expand: {hops, direction?, edge_types?}`); `search_hybrid.description` widened additively to mention the new option.
997
+ - **`search_hybrid` accepts an additive nested `expand?: {hops: 1|2, direction?, edge_types?}` parameter (Phase 4 plan 04-04 / GRA-03).** Defaults to v1-no-op when omitted; legacy callers see byte-identical responses. When supplied, each `SearchHit` gains an `expansions?: CitationPacketWithVia[]` field carrying the per-hit typed-edge neighborhood; ranking is unchanged. Per-vault BFS isolation is enforced inside `expand()` (T-04-04-02 mitigation). Failures inside `expand()` are silently swallowed — same posture as the reranker fallback — so a corrupted graph traversal cannot break search.
998
+ - **Graph result rows widen from `relation: "wikilink"` to `relation: EdgeType` (Phase 4 plan 04-01).** `assemble_dossier.linked_documents[].relation`, `get_document_bundle.{backlinks,forward_links}[].relation`, and the v1 `BacklinkEntry`/`ForwardLinkEntry` `relation` fields now emit the actual edge type the indexer recorded. The Phase 3 known-limitation pinning these to `"wikilink"` is closed; the `PHASE-4-WIDEN` markers in `src/assembly/dossier.ts` + `src/assembly/bundle.ts` have been retired. This is strictly an additive widening — old callers that only checked for `"wikilink"` still receive that value when the underlying edge is a wikilink.
999
+ - **Indexer dual-writes to `wikilinks` + `edges` tables (Phase 4 plans 04-01, 04-02).** Both indexer write paths (`src/indexer/single.ts` body-hash fast path + full re-embed branch; `src/indexer/indexer.ts` full index path) call `writeAllEdges` after parsing each note, threading the `WikilinkResolver` for cross-note target resolution. The v1 `wikilinks` table is preserved for D-01 v1 invariance; v3 cleanup will drop it once no v1 tool reads from it.
1000
+ - **`@modelcontextprotocol/sdk` bumped to `^1.29.0`**; tool registration migrated from the v1 low-level `Server` + `setRequestHandler` pattern to `McpServer` + `server.registerTool()` × 23. Raw JSON Schema literals flow directly from `src/tool-registry.ts` (workaround for SDK#1143 / Zod-4 description-drop, although the issue is empirically MOOT in SDK 1.29). (ADP-08)
1001
+ - **`zod` bumped to `^4.4.3`**. Refinements and `errorMap` swept per the Zod 4 migration guide; Standard Schema wiring intact. (ADP-09)
1002
+ - **Default `client_id` for write / update / delete** is now captured from MCP `InitializeRequest.params.clientInfo.name` via a lazy closure (`getClientId()` in `src/server.ts`), falling back to `"unknown"`. Removes the v1 hardcoded `"claude-code"` default that misidentified every non-Claude write in the audit log. (D-02)
1003
+ - **`obsidian://` display-URL minting** moved from `src/server.ts:obsidianUrl()` (deleted) into `SourceConnector.formatDisplayUrl(id)` on the adapter. The `obsidian://open?vault=…&file=…` URL is now adapter-published; future adapters mint their own scheme. (D-01)
1004
+ - **README rewritten** to lead with "any MCP-aware agent" framing in the first 20 lines. Equal billing for Claude Code / Claude Desktop / ChatGPT Custom Connectors / MCP Inspector / generic clients. Obsidian framed as the v2 source connector, not the sole consumer. (ADP-14)
1005
+ - **`src/cli.ts` user-facing strings** swept for Claude-leak — `"Open ${path} in Claude Code"` → `"Open ${path} in your MCP-aware client"`. (D-02 follow-through)
1006
+ - **`WriteConflict.reason` discriminated union extended** (additively, backwards-compatible) with 7 new codes: `missing_provenance`, `invalid_provenance`, `supersede_mismatch`, `agent_write_outside_sink`, `non_agent_write_inside_sink`, `sentinel_missing`, `sink_write_blocked`. Optional envelope fields added: `sinkName`, `key`, `observedValue`, `suggestion`. The 3 Phase 1 reason codes (`hash_mismatch`, `permission_denied`, `not_found`) are unchanged (Phase 2 plan 02-03 / MEM-05, MEM-07, MEM-11).
1007
+ - **v1 `write_note`, `update_frontmatter`, `delete_note` now refuse memory-sink-resolved targets** at the tool entry-point with `sink_write_blocked` + actionable `suggestion` text (`record_observation` for writes/updates, `supersede` for deletes). Defense-in-depth on top of the centralized `DeliveryAdapter.write()` chokepoint (Phase 2 plan 02-03b / MEM-07). When the `MemorySinkRegistry` is not wired (Phase 1 fixture-test path), v1 tool behavior is byte-identical to Phase 1.
1008
+ - **`audit_log` tool gains optional `is_memory_sink_write` input filter** and adds `is_memory_sink_write: boolean` to each returned row. The MCP `description` text is byte-identical to Phase 1 — the new capability is documented in CHANGELOG and `docs/tools/audit_log.md` only (Phase 2 plan 02-06 / MEM-08).
1009
+ - **Server bootstrap order** is now `loadConfig → manager.openAll → registerMemorySinks (writes sentinels) → server.connect → startCatchupAndWatchers (fire-and-forget)`. Sentinels are provisioned BEFORE the catch-up indexer walks the fixture, so the registry's `findSinkContaining` enclosure check is hot for the first incoming write (Phase 2 plan 02-03b). A new exported `BootstrapPhase` type + optional `onPhase` callback on `serve(options?)` make the bootstrap order observable for testing.
1010
+ - **Assembly DocId minting derives scheme from `SourceConnector.handle`** (Phase 3 plan 03-07 fix). Pre-03-07 both `assembleDossier` and `getDocumentBundle` hardcoded `formatDocId("obsidian-fs", …)` when constructing linked-document / anchor DocIds. The 03-07 ASM-12 conformance suite (parameterized over obsidian-fs + stub adapters) surfaced this as a hard test failure on the stub-assembly row. The fix introduces `schemeFromSource(SourceConnector): string` and derives the scheme dynamically; `dossier.ts`'s sort-key helper is renamed `noteDocIdString → noteSortKey` and pinned to a fixed `vault://` prefix so sort order stays adapter-identical. No user-visible change for obsidian-fs callers (their scheme has always been `obsidian-fs://`); the fix unblocks future non-Obsidian sources.
1011
+ - **Tool surface count: 26 → 30** (additive only). Four Phase 3 assembly tools added: `get_outline`, `search_sections`, `get_document_bundle`, `assemble_dossier`. `evals/v1-baseline/tools-list.snapshot.json` regenerated; the 23 v1 entries at slots 0–22 remain byte-identical (pinned by the existing `baseline.test.ts` "preserves the 23 v1 baseline tool names byte-identical" assertion). The diff against the Phase 2 snapshot is purely additive: four new tool entries appended; one optional field added to `search_hybrid.inputSchema.properties` (the four rescore params).
1012
+ - **Tool surface count: 32 → 34** (Phase 5, additive only). Two new brief tools added: `compile_brief`, `get_brief`. The `list_briefs` discovery surface lives on MCP Resources (`vault-memory://briefs`) and does NOT count toward the tool budget — same precedent as the Phase 2 `vault-memory://memory/*` Resources. The 32 Phase 4 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (verified per-name); the diff is purely additive — two new tool entries appended.
1013
+ - **Server bootstrap order extended (Phase 5)** — after `registerMemorySinks` and before `server.connect`, the bootstrap now (a) registers the `_memory/_briefs/` sink bound to the `default-brief-v1` contract and (b) starts `BriefStalenessDaemon` per vault. Daemon `start()` is fire-and-forget: a lock-contention failure does NOT block server startup — the server proceeds and `compile_brief` continues to work; only the staleness re-check is offline.
1014
+ - **`SuppressionSet.consume()` accepts an optional `hash` argument (Phase 7 / CAN-08, additive).** Phase 1 callers that pass only a path see byte-identical behavior. When the plugin write path passes `(path, hash)`, the contracts loader at `src/contracts/loader.ts:242` matches both axes before short-circuiting — so a benign re-emit of the same canonical bytes is suppressed, but a divergent write still triggers reload.
1015
+ - **Default-OFF plugin tool gating (Phase 7).** The 6 new plugin tools are wired through a config-time guard in `src/server.ts` so `tools/list` does NOT include them unless `[plugin] enabled = true`. The default-OFF surface keeps the v1 baseline snapshot byte-identical and keeps the REL-08 budget conversation focused on the user-facing v2 tools.
1016
+
1017
+ ### Dependencies
1018
+
1019
+ - **Phase 4 (plan 04-05 / GRA-02)** — pure-JS ESM, all MIT, all manually provenance-verified per Phase 4 RESEARCH §Package Legitimacy Audit:
1020
+ - `graphology ^0.26.0` — graph data structure for the Louvain cluster wrapper.
1021
+ - `graphology-communities-louvain ^2.0.2` — Louvain modularity-maximizing community detection.
1022
+ - `seedrandom ^3.0.5` — deterministic seeded PRNG for the Louvain `rng` option.
1023
+ - `@types/seedrandom ^3.0.8` (dev) — TypeScript types.
1024
+ - No native bindings; no tsup `external` additions needed. ESM bundle grew from 376 KB to 392 KB (+13 KB).
1025
+ - **Phase 7 plugin (ADR-007)** — added under `plugin/package.json` only; the server bundle is unaffected:
1026
+ - `@xyflow/svelte` — Svelte Flow canvas renderer for the Variant C editor (MIT, license verified in ADR-007).
1027
+ - `svelte`, `esbuild`, `vitest` — plugin-only build/test toolchain. None ship into the server `dist/`.
1028
+
1029
+ ### Migration
1030
+
1031
+ - **MIGRATION_011 — `edges` table + chunked backfill from `wikilinks` (Phase 4 plan 04-01).** Function-style migration mirroring `runMigration008`. Creates the `edges` table with `CHECK(type IN ('wikilink', 'mention', 'frontmatter-ref', 'hyperlink'))` and a UNIQUE INDEX on `(source_note_id, target_note_id, type, anchor, line_number)` using `COALESCE(anchor, '')` + `COALESCE(line_number, 0)` for proper NULL-as-distinct dedup. Backfills wikilink rows from the v1 `wikilinks` table in 10k-row chunks via `INSERT OR IGNORE` — idempotent, re-runnable, safe to interrupt. v1-shaped DBs migrate cleanly on first server start after upgrade; the v1 `wikilinks` table is preserved alongside the new `edges` table for D-01 v1 invariance. v3 cleanup will drop `wikilinks` once no v1 tool consumes it.
1032
+ - **`notes.doc_uri` dual-column staging (Strategy A — additive, plan 01-02):**
1033
+ - **MIGRATION_007** (additive) — adds nullable `doc_uri TEXT` column to `notes` + `idx_notes_doc_uri` index. Backwards-compatible; existing rows have `doc_uri IS NULL` post-migration.
1034
+ - **MIGRATION_008** (function-style backfill) — sets `doc_uri = 'obsidian-fs://<vault>/' || path` for every row where `doc_uri IS NULL`. Idempotent; safe to re-run.
1035
+ - Path stored **un-encoded** in the column; percent-encoding happens only at `formatDisplayUrl()` time (per ADR-002 §URL semantics).
1036
+ - **No user action required.** Migrations run automatically on first server start after upgrade. SQLite transaction rollback is the safety net; no pre-migration backup is performed in v2 (deferred to Phase 8 per CONCERNS §"No Pre-Migration DB Backup"). (ADP-07)
1037
+ - **v1 tool surface preserved byte-for-byte.** All 23 tool names, input schemas, output envelopes, and descriptions are unchanged. `evals/v1-baseline/tools-list.snapshot.json` regenerated under SDK 1.29 with zero diff against the Phase-0 baseline (W6 human-verify checkpoint passed in plan 01-06 Task 06).
1038
+ - **MIGRATION_010 — sections table + notes.status column + section backfill (Phase 3 plan 03-01).** Three ordered steps in one transaction: (a) `CREATE TABLE sections` with 3 indexes, (b) `notes.status` column added + partial index `notes_status WHERE status IS NOT NULL`, (c) backfill `notes.status` from `json_extract(frontmatter, '$.status')` AND derive section rows for every indexed note via `markdownToSectionBlocks → extractSections`. Idempotent; re-applying is a no-op. Anchor-equivalence guarantee: backfilled section anchors match a fresh re-index byte-for-byte (the indexer + backfill share `src/chunker/headings.ts:extractHeadings` as the canonical heading source). v1-shaped DBs migrate cleanly without re-indexing.
1039
+ - **`write_audit` migration v9** (function-style, idempotent) adds `is_memory_sink_write INTEGER NOT NULL DEFAULT 0` + a partial index `idx_write_audit_memory_sink ON (is_memory_sink_write, at DESC) WHERE is_memory_sink_write = 1` (Phase 2 plan 02-06 / MEM-08). All Phase 1 audit rows backfill to `false`; new audit rows derive the flag from `WriteOptions.sink !== undefined` at the `ObsidianFsDelivery.write/update/delete` facade. The function-style migration runs `PRAGMA table_info` first so fixture tests that rewind `user_version` continue to pass.
1040
+ - **v1 tool surface grows from 23 → 26** with the three Phase 2 memory tools (`record_observation`, `recall`, `supersede`). The 23 v1 entries in `evals/v1-baseline/tools-list.snapshot.json` are byte-identical (pinned by a new `baseline.test.ts` assertion). The only diff on v1 tools is one optional input field added to `audit_log.inputSchema` (`is_memory_sink_write`); the v1 description text is byte-identical.
1041
+ - **MIGRATION_013 — `chunks.chunk_id_fragment` + `brief_sources` + `daemon_state` (Phase 5 plan 05-01).** Three additive steps in one transaction: (a) `chunks.chunk_id_fragment` TEXT column added (`noUncheckedIndexedAccess` consumers see the new column as `T | undefined` until populated by the next index run); (b) `brief_sources` table (D-06 reverse-index — `(brief_doc_id, chunk_id_fragment, chunk_doc_id, recorded_hash)`); (c) `daemon_state` table (per-vault change-feed cursor + `last_full_scan` timestamp). Idempotent; re-applying is a no-op. v1-shaped DBs migrate cleanly on first server start after upgrade.
1042
+ - **Phase 5 brief artifacts are NOT a server schema migration.** Brief `Document`s land in `_memory/_briefs/` via the standard `DeliveryAdapter.write()` path; the `default-brief-v1` MemoryContract is registered at bootstrap (not persisted in SQLite). Users who never call `compile_brief` see zero new files on disk.
1043
+ - **Phase 7 plugin is NOT a server schema migration.** No new SQLite migrations land for Phase 7. The plugin stores its own state in `data.json` (loadData/saveData) inside the Obsidian vault's plugin directory; secrets land as Electron safeStorage ciphertext only. Users who never enable the plugin flag see zero new server-side files.
1044
+
1045
+ ### Documentation
1046
+
1047
+ - Relocate ADRs 001–004 from `docs/dev/` (gitignored) to public `docs/v2/adr/`; amend each with Invariants + Examples sections covering both `obsidian-fs://` and `notion-api://` worked examples (FND-01, FND-04).
1048
+ - ADR-003 amended with explicit hash-semantics pseudocode (RFC 8785 JCS, NFC normalisation, LF line endings, IEEE-754 number canonicalization) + chunk-level `source_hashes` schema (FND-02).
1049
+ - ADR-004 amended: folder-default `MemorySink` is the only code path; separate-vault is config-only via `[memory] sink = "@…"` (FND-03). `.memory-sink` sentinel mandated.
1050
+ - ADR-004 amended: underscored PropertyBag keys, confidence enum aligned to [direct, inferred, uncertain], superseded_reason field added (Phase 2 plan 02-01).
1051
+ - Publish `docs/v2/ARCHITECTURE.md` (L0–L4 layer model + responsibility map), `docs/v2/MEMORY_CONTRACT.md` (provenance property contract on `Document.properties`), `docs/v2/AGENT_AGNOSTIC.md` (MCP-canonical client stance) (FND-05/06/07).
1052
+ - Eval fixture vault `evals/fixtures/v2-test-vault/` — "Atlas Robotics" narrative; 56 notes across `projects/`, `meetings/`, `people/`, `decisions/`, `references/`; 15-document `_memory/` subset; 7 hand-labeled `_queries/*.yaml` (FND-08).
1053
+ - v1-baseline regression suite `evals/v1-baseline/` — `tools-list.snapshot.json` pin for `tools/list` (23 tools), 11 per-tool semantic-floor YAMLs, `baseline.test.ts` vitest runner with `.todo` placeholders for Phase 1 precision/recall (FND-09/10).
1054
+ - CI gates `scripts/check-fixture-privacy.sh` + `scripts/lint-no-telemetry.sh` + `.github/workflows/ci.yml` running `npm run lint:check && npm test` on every PR and push to `main` (FND-11/12 + D-21).
1055
+ - ADR index `docs/v2/adr/README.md` listing 4 Accepted ADRs + 14 Open ADR stubs for v3 / Phase 10 follow-ups, including a Deferred-v3 section for adversarial-review findings (FND-13).
1056
+ - Adversarial review `docs/v2/adr/ADVERSARIAL-REVIEW.md` — 10 findings against ADRs 001–004 raised by a fresh-context advisor; 6 Amended in Phase 0 (ADR-001 I-6, ADR-002 `DocumentRef.hash` contract + `hashProtected` enum, ADR-003 H-6), 4 Deferred-v3 to Phase 10 / Notion connector (FND-04 + FND-14 sign-off).
1057
+ - Sign-off artifact `docs/v2/SIGN-OFF.md` — FND-01..14 checklist with resolving commit SHAs; PR approval is the FND-14 audit trail per D-17.
1058
+ - Internal: extract `TOOLS` constant from `src/server.ts` to `src/tool-registry.ts` so `evals/v1-baseline/dump-tools.mjs` can produce the pinned snapshot without spinning the full MCP server (Phase 0 Assumption A5, no external behavior change).
1059
+ - Phase 5 sign-off artifact `docs/v2/PHASE-5-SIGN-OFF.md` — BRF-01..BRF-11 traceability table + the five Phase 5 ROADMAP success criteria with disposition (`✅ MET` per criterion) + REL-08 hand-off plan to Phase 8.
1060
+ - ADR-005 `docs/v2/adr/005-brief-compile-strategy.md` (Accepted) — governs the D-10 LLM strategy ladder and forbids bundling any remote LLM SDK in the server.
1061
+ - Phase 7 plugin docs under `docs/v2/plugin/`: `README.md`, `INSTALL.md`, `SETTINGS.md`, `SECRETS.md`, `CONTRACT-EDITOR.md`, `CONNECTORS.md`. README plugin section added to the repo root README explaining how to flip `[plugin] enabled = true` and what the editor + chrome surfaces do.
1062
+ - ADR-007 `docs/v2/adr/007-contract-editor.md` (Accepted, 2026-05-19) — rescopes the visual editor from a jsoncanvas fork to `@xyflow/svelte` (MIT, license verified inline in the ADR).
1063
+
1064
+ ### Deprecated (Phase 8 / REL-08)
1065
+
1066
+ The following five v1 tools are deprecated in v2.0.0 and promoted to MCP Resources. Each tool remains callable through the v2.x line for backwards compatibility; removal is scheduled for v3.0.0. The Resource URI is the canonical replacement.
1067
+
1068
+ - **`list_vaults`** → `vault-memory://vaults` (cross-vault discovery Resource). The tool remains callable through v2.x; removal scheduled for v3.0.0.
1069
+ - **`list_models`** → `vault-memory://models/{vault}` (per-vault embedding-model inventory). The tool remains callable through v2.x; removal scheduled for v3.0.0.
1070
+ - **`recent_notes`** → `vault-memory://recent/{vault}` (per-vault mtime-DESC recent notes). The tool remains callable through v2.x; removal scheduled for v3.0.0.
1071
+ - **`vault_stats`** → `vault-memory://stats/{vault}` (per-vault note count + top tags + top frontmatter keys + last index run). The tool remains callable through v2.x; removal scheduled for v3.0.0.
1072
+ - **`list_backlinks`** → `vault-memory://backlinks/{vault}/{+docId}` (per-document backlink listing). The URI uses RFC 6570 reserved expansion: `{+docId}` allows `/` in the variable value so multi-segment `docId`s like `obsidian-fs://my-vault/notes/sub/file.md` parse correctly. The tool remains callable through v2.x; removal scheduled for v3.0.0.
1073
+
1074
+ ## [1.0.0] — 2026-05-12
1075
+
1076
+ ### Stability declaration
1077
+
1078
+ The 0.x line is complete. All five items of the OB1 adoption plan shipped:
1079
+
1080
+ - v0.9.0 — Agent compatibility (`search`/`fetch`) + self-orientation (`vault_stats`/`recent_notes`)
1081
+ - v0.9.1 — Body-hash short-circuit (migration 006)
1082
+ - v0.9.2 — Vault-hygiene skill pack (`audit-vault-health`, `find-stale-notes`, `triage-inbox`)
1083
+ - v0.10.0 — Schema inference (`suggest_frontmatter`, three-layer combiner)
1084
+
1085
+ From v1.0.0 onward this project follows strict SemVer:
1086
+ - **MAJOR** bumps only for backwards-incompatible MCP-tool or CLI changes.
1087
+ - **MINOR** bumps for new tools, new skills, additive schema fields.
1088
+ - **PATCH** for bug fixes, internal refactors, doc-only updates.
1089
+
1090
+ ### What's stable
1091
+
1092
+ - **23 MCP tools** with their documented input/output shapes (see README).
1093
+ - Adding new fields to existing responses is non-breaking.
1094
+ - Renaming a field or removing one requires a MAJOR bump.
1095
+ - **CLI**: `vault-memory serve`, `add-vault`, `index`, `init` (no flag removals
1096
+ without MAJOR).
1097
+ - **SQLite schema**: migrations are forward-only and idempotent. Existing
1098
+ `user_version` ≤ 6 DBs migrate cleanly.
1099
+ - **`~/.vault-memory/config.toml` keys** — additive only.
1100
+ - **5 Claude Code skills** in `skills/` — adding workflow steps is non-breaking;
1101
+ renaming a skill is.
1102
+
1103
+ ### What's NOT under stability
1104
+
1105
+ Internal modules without an exported MCP-tool surface (`src/chunker/`,
1106
+ `src/rerank/`, `src/indexer/single.ts`, `src/schema/combiner.ts` internals)
1107
+ can refactor at any time. Only the public MCP-tool inputs/outputs and the CLI
1108
+ flags carry the v1 stability contract.
1109
+
1110
+ ### No code changes from 0.10.0
1111
+
1112
+ This release is a pure version bump + CHANGELOG marker. No new tools, no
1113
+ behavioural changes. The dist artefact (`dist/cli.js`) regenerates from
1114
+ the unchanged source.
1115
+
1116
+ ## [0.10.0] — 2026-05-12
1117
+
1118
+ ### Added
1119
+ - **`suggest_frontmatter` MCP tool** — composes three independent inference layers into
1120
+ a structured `{existing, suggestions, conflicts}` response with calibrated
1121
+ confidence-per-source. Closes the final item of the OB1 adoption plan.
1122
+ - **folder-conventions learner** (`src/schema/folder-conventions.ts`): per-key
1123
+ prevalence across sibling notes (same path prefix). Falls back to parent folders
1124
+ when sibling count is below 3. Dominant-value extraction when >50% agree on a value.
1125
+ - **neighbor-inference learner** (`src/schema/neighbor-inference.ts`): frontmatter
1126
+ aggregate across wikilink-linked neighbors (forward + backward, deduped).
1127
+ Confidence × 0.6 damping factor — indirect signal.
1128
+ - **content-heuristics learner** (`src/schema/content-heuristics.ts`): vault-agnostic
1129
+ title/body regex matchers — Email, Meeting, Person, Clipping, Fact, date-prefix.
1130
+ Fixed confidence per rule.
1131
+ - **Combiner** (`src/schema/combiner.ts`): max-across-sources fusion, conflict
1132
+ detection both within suggestions and against existing frontmatter. Below-threshold
1133
+ candidates dropped. Stable sort order.
1134
+ - 44 new unit tests across 4 files. 368/368 total (+44).
1135
+
1136
+ ### Changed
1137
+ - README: `MCP tools (23)` count bumped; new "Schema inference" section explains the
1138
+ three-layer architecture.
1139
+
1140
+ ## [0.9.2] — 2026-05-12
1141
+
1142
+ ### Added
1143
+ - **Vault-hygiene skill pack** — three new Claude Code skills that compose existing MCP
1144
+ tools into guided maintenance workflows:
1145
+ - `audit-vault-health` — read-only overview: stats, broken wikilinks, tag drift
1146
+ (case/separator variants), frontmatter schema drift, indexing freshness.
1147
+ - `find-stale-notes` — discovers notes >6 mo old with 0 backlinks, walks through each
1148
+ with per-note actions (Archive / Update / Delete / Skip / Keep). Hash-protected
1149
+ deletes; never bulk-acts.
1150
+ - `triage-inbox` — walks through recent inbox-stage notes (sparse frontmatter, few
1151
+ tags, recent mtime). Suggests target folder, tags, frontmatter, related wikilinks
1152
+ based on semantic search. User accepts / edits / skips per note.
1153
+
1154
+ ### Notes
1155
+ - Pure-Markdown release — **no code changes** in the server. Distributed via the
1156
+ existing `install-skills.sh` one-liner.
1157
+
1158
+ ## [0.9.1] — 2026-05-11
1159
+
1160
+ ### Added
1161
+ - **Body-hash short-circuit** (migration 006) — frontmatter-only edits no longer
1162
+ trigger chunk + embedding regeneration. Significantly reduces re-index cost for
1163
+ tag/metadata-only updates.
1164
+
1165
+ ## [0.9.0] — 2026-05-10
1166
+
1167
+ ### Added
1168
+ - **Agent-Compatibility adapters** — OB1-compatible flat-shape `search` and `fetch`
1169
+ MCP tools for ChatGPT Custom Connectors, Claude.ai, and Deep-Research modes. Backed
1170
+ by the hybrid (semantic + BM25 + RRF) pipeline so connector users get full search
1171
+ quality through the standardized shape.
1172
+ - **Agent self-orientation tools** — `vault_stats` (note count, top tags, top
1173
+ frontmatter keys, last index run) and `recent_notes` (mtime-DESC list). Use on
1174
+ first connect to brief an agent on what's in the vault and what the user has been
1175
+ working on.
1176
+
1177
+ ## [0.8.3] — 2026-05-12
1178
+
1179
+ ### Fixed
1180
+ - **Skills** — eliminated false-positive Ollama model re-pull caused by a SIGPIPE +
1181
+ `pipefail` race in `install-vault-memory/setup.sh`.
1182
+
1183
+ ### Changed
1184
+ - Merged `setup-memory-system` skill into `install-vault-memory`; the older name is
1185
+ retired.
1186
+
1187
+ ## [0.8.2] — 2026-05-12
1188
+
1189
+ ### Added
1190
+ - **`scripts/install-skills.sh`** — one-liner curl-pipe installer that drops the
1191
+ bundled Claude Code skills into any vault's `.claude/skills/` directory and is
1192
+ re-runnable to pull the latest skill versions from `main`.
1193
+ - **CI** — automated npm publish on tag push (npm-first distribution).
1194
+
1195
+ ### Fixed
1196
+ - Packaging — corrected `bin` path and `repository.url` format in `package.json`.
1197
+
1198
+ ## [0.8.1] — 2026-05-12
1199
+
1200
+ ### Added
1201
+ - **`vault-memory add-vault` CLI subcommand** — one-shot onboarding for a second/third
1202
+ vault: appends to `config.toml`, writes `.mcp.json` into the vault root, runs the
1203
+ initial index. Idempotent. Flags: `--name`, `--write`, `--no-index`.
1204
+ - **`VAULT_MEMORY_ACTIVE_VAULT` env var** — scopes implicit `search_*` calls to a
1205
+ single vault per consumer (set in `.mcp.json`'s `env` block). Explicit `vaults: [...]`
1206
+ still overrides.
1207
+ - **Mid-index skip** — vaults with an unfinished `index_runs` row are excluded from the
1208
+ implicit candidate set; skipped vaults surface on a `note` field in responses.
1209
+ - **Frontmatter wikilink extraction** — wikilinks declared in YAML frontmatter are now
1210
+ picked up as forward-links.
1211
+
1212
+ ### Fixed
1213
+ - **Reranker** — `Tokenizer` constructor needs `(tokenizerJson, config)` (Hugging
1214
+ Face API); config is derived from `added_tokens`. Also added a near-empty-chunk
1215
+ pre-filter so the rerank pool isn't diluted by degenerate inputs.
1216
+ - **Hybrid search** — widened reranker pool from `topK × 3` to `topK × 5`.
1217
+ - **Chunker** — drops whitespace-only and tiny chunks (#25).
1218
+ - **Indexer** — invalid YAML frontmatter on a single note is now logged-and-skipped,
1219
+ not fatal to the whole vault run. Count surfaces on `IndexRunResult.notesSkipped`.
1220
+
1221
+ ## [0.8.0] — 2026-05-11
1222
+
1223
+ ### Added
1224
+ - **Real ONNX cross-encoder reranker (Phase 8)** — replaces the v0.7.x L2-norm proxy
1225
+ with a true forward pass over **BAAI/bge-reranker-v2-m3** (ONNX INT8, ≈570 MB) via
1226
+ `onnxruntime-node` + `@huggingface/tokenizers`. Sigmoid-of-logit gives a real
1227
+ `[0, 1]` relevance score per `(query, chunk)` pair.
1228
+ - **`scripts/download-reranker.sh`** — one-time fetch of the ONNX model into
1229
+ `~/.vault-memory/models/bge-reranker-v2-m3/`.
1230
+ - Lazy session load — users who never set `rerank: true` pay zero startup cost.
1231
+ - Legacy `OllamaReranker` (L2-norm proxy) remains available via
1232
+ `reranker_backend = "ollama"` for back-compat, but is no longer recommended.
1233
+
1234
+ ## [0.7.3] — 2026-05-11
1235
+
1236
+ ### Added
1237
+ - **`vacuum_embeddings` MCP tool** — drops orphaned embedding rows whose `chunk_id`
1238
+ no longer exists.
1239
+ - **BGE-M3 as the default embedding model** — based on the v2 multilingual eval on a
1240
+ 187-note German+English vault. Materially better at concept-paraphrase queries than
1241
+ `qwen3-embedding:0.6b`.
1242
+
1243
+ ## [0.7.2] — 2026-05-11
1244
+
1245
+ ### Fixed
1246
+ - **Search** — use the active model recorded in the DB instead of the value in
1247
+ `config.toml` (which may lag a shadow-index promotion).
1248
+
1249
+ ## [0.7.1] — 2026-05-11
1250
+
1251
+ ### Fixed
1252
+ - **Schema** — per-model `embeddings_m<id>_d<dim>` vec0 tables so multiple models with
1253
+ the same dimensionality can coexist (e.g. `bge-m3` and `qwen3-embedding:0.6b` both
1254
+ at 1024-d).
1255
+
1256
+ ## [0.7.0] — 2026-05-11
1257
+
1258
+ ### Added
1259
+ - **Phase 7 complete:** path-exclude globs, variable embedding dimensions,
1260
+ cross-encoder reranker (Phase 7d), shadow-index for seamless model upgrades
1261
+ (Phase 7c).
1262
+ - **Model management tools** — `list_models`, `start_shadow_index`,
1263
+ `switch_active_model`.
1264
+
1265
+ ## [0.6.1] — 2026-05-11
1266
+
1267
+ ### Fixed
1268
+ - **FTS** — phrase-wrap tokens containing FTS5-meaningful punctuation so they don't
1269
+ blow up the parser.
1270
+
1271
+ ## [0.6.0] — 2026-05-11
1272
+
1273
+ ### Fixed
1274
+ - Codex review findings MEDIUM-1, MEDIUM-2 (canonical JSON hashing for stable
1275
+ frontmatter hashes), MEDIUM-3, MEDIUM-4 (wikilink-resolution caching per index run),
1276
+ MEDIUM-5 (resolve symlinks before vault-boundary check).
1277
+
1278
+ ### Added
1279
+ - npm distribution prep — pre-built `dist/` shipped in the package so users can
1280
+ install without devDependencies.
1281
+
1282
+ ## [0.5.x and earlier] — 2026-05-10 / 2026-05-11
1283
+
1284
+ Pre-0.6 development built the core stack in roughly this order:
1285
+
1286
+ - **Initial skeleton** — TypeScript MCP server stub.
1287
+ - **Ollama client** — HTTP embeddings with retry + batching.
1288
+ - **Vault reader** — scanner, markdown parser, wikilink extractor.
1289
+ - **Chunker** — heading-aware markdown chunking with overlap.
1290
+ - **Persistence** — SQLite + `sqlite-vec` with schema migrations.
1291
+ - **FTS5 BM25** — full-text search over `chunks_fts`.
1292
+ - **Graph layer** — high-level wikilink operations.
1293
+ - **Hybrid search** — Reciprocal Rank Fusion over semantic + BM25.
1294
+ - **Frontmatter query DSL** — `query_frontmatter` with a safe JSON-path subset.
1295
+ - **Phase 2 MCP tools** — wikilink resolution, Obsidian-style aliases.
1296
+ - **Audit + index_runs** — user-facing audit log API.
1297
+ - **Frontmatter merge DSL** — `updateFrontmatter`.
1298
+ - **Atomic writes** — `writeNote` / `deleteNote` with hash-based concurrency control.
1299
+ - **Phase 3 MCP tools** — write/delete/update + audit surfaces.
1300
+ - **File-watcher** — `DebouncedQueue` + `SuppressionSet` for incremental re-index.
1301
+ - **Single-note indexer** — for the file-watcher path.
1302
+ - **Catch-up** — reconcile DB with filesystem on server start.
1303
+
1304
+ ---
1305
+
1306
+ ## How releases are cut
1307
+
1308
+ 1. Move accumulated `## [Unreleased]` entries into a new `## [X.Y.Z] — YYYY-MM-DD` block.
1309
+ 2. Start a fresh `## [Unreleased]` block above it with `_Nothing yet._`.
1310
+ 3. Bump `version` in `package.json`.
1311
+ 4. Commit as `chore(release): vX.Y.Z` and tag `vX.Y.Z` — CI publishes to npm on tag push.
1312
+
1313
+ **Every PR that ships a user-visible change MUST update `## [Unreleased]` in the same commit.**
1314
+ Reviewers should block merges that change behavior but don't touch this file.