# MuseHub Roadmap **Program:** Knowtation domain — vault-native VCS intelligence for agent knowledge systems. **Authority:** This document governs work in the **MUSE_HUB workspace** (`gabriel-muse`, `musehub`, and related repos). It is **separate** from Scooling's `docs/ROADMAP.md`. Scooling consumes Knowtation via REST/MCP adapters in parallel; it does not block this track. **Source spec:** [gabriel/muse issue #2](https://staging.musehub.ai/gabriel/muse/issues/2) (frozen phase order — each phase unlocks the next for *new* work; local code may span multiple phases). **Handover relay:** `docs/MUSEHUB-OVERSEER-HANDOVER.md` — paste the **NEXT SESSION** block into a fresh chat. --- ## Phase Model Key | Label | Meaning | | --- | --- | | **Thinking** | Contract, architecture, open-question resolution, test matrix — freeze spec before build | | **Auto** | Mechanical implementation against a frozen spec; run tests; Muse commit on feature branch | | **Thinking → Auto** | Split into `{step}a` (Thinking) then `{step}b` (Auto) in the handover doc | | **Operator + Auto** | Tier 3 human authorization for staging push / live gate; Auto for implementation | **Handover rule:** Every next-step table and paste block **must** include **`Model:`**. --- ## Next step at a glance (2026-07-10) | Item | Status | | --- | --- | | **Active track** | Knowtation domain — staging push + attestation verification | | **Plugin code (Phases 1–4, 7, KD-5b)** | **DONE locally** — `gabriel-muse/muse/plugins/knowtation/` + `muse/mcp/` (36 MCP tests, 1606 knowtation tests green) | | **Staging plugin repo** | **DONE** — `aaronrene/gabriel-muse` @ `sha256:660fcac1…` (2026-06-21) — **pre-KD-5b**; re-push needed | | **Marketplace registration** | **DONE** — `@aaronrene/knowtation` on staging (2026-07-05); `viewer_type: generic` (optional fix) | | **Vault activation smoke** | **DONE** — `knowtation-vault-smoke` (5 PASS / 3 PARTIAL — see handover matrix) | | **Open questions (KD-OQ)** | **DONE** (2026-07-10) — all 5 issue #2 questions resolved (see table below) | | **MCP surface (KD-5b)** | **DONE** (2026-07-10) — 9 tools / 8 resources / 4 prompts at `muse/mcp/`; activation gate; 7-tier + Claude Desktop stdio integration green on `knowtation-vault-smoke` | | **Vault UI contract (KD-6a)** | **DONE** (2026-07-10) — §KD-6a frozen: 4 page groups (landing, note detail, proposals, vault search), routes, MCP read-mapping, error enums, 7-tier matrix + Playwright E2E | | **Vault UI (KD-6b)** | **DONE** (2026-07-10) — `knowtation_view` adapter, `ui_vault.py`, templates/SCSS, harmony diff, 7-tier + Playwright E2E green locally | | **NEXT** | **KD-STAGING** — Tier 3 push gabriel-muse (KD-5b) + musehub (KD-6b) to staging; KD-7 attestation smoke on staging vault | | **Parked** | — | | **Parallel (Scooling)** | Phase 9A overseer gates — independent | --- ## Build status table | ID | Phase | Repo(s) | Model | Status | | --- | --- | --- | --- | --- | | **KD-0** | Recovery + rc14 re-baseline | gabriel-muse | Auto | **DONE** (2026-06-20) — corrupt rc3 history discarded; code intact; 1570 tests green | | **KD-SD8-PUSH** | Push plugin to staging | gabriel-muse | Auto (Tier 3) | **DONE** (2026-06-21) — `aaronrene/gabriel-muse` @ `660fcac1…` | | **KD-SD8-REG** | Marketplace registration | musehub (API) | Auto (Tier 3) | **DONE** (2026-07-05) — `@aaronrene/knowtation`; `viewer_type: generic` (fix optional) | | **KD-1** | Domain foundation (detector, parser, plugin, symbols, domain-info) | gabriel-muse | Auto | **DONE** — proven on vault smoke (domain-info PASS; symbols CLI PARTIAL) | | **KD-2** | Semantic diff & harmony merge | gabriel-muse | Auto | **DONE** — differ/merger API proven; CLI merge/diff PARTIAL (see handover) | | **KD-3** | Code intelligence (deps, impact, hotspots, dead, clones, entangle, velocity) | gabriel-muse | Auto | **DONE** — deps/impact/hotspots/dead PASS on test vault | | **KD-4** | Memory timeline (event schema, symbol-log, consolidation hook, prime) | gabriel-muse | Auto | **DONE** — log event_type PASS; prime_context API PASS (no CLI hook) | | **KD-7** | Attestation & ICP push hook | gabriel-muse | Auto | **DONE** (local; attestation smoke post-activation) | | **KD-ACTIVATE** | Vault activation + Phase 1–4 smoke | gabriel-muse + test vault | **Auto** | **DONE** (2026-07-10) — `knowtation-vault-smoke`; 5 PASS / 3 PARTIAL | | **KD-OQ** | Resolve issue #2 open questions (5 items) | gabriel-muse + musehub | **Thinking** | **DONE** (2026-07-10) — 5/5 resolved with source-backed rationale; gate cleared for KD-5a | | **KD-5a** | MCP domain surface — contract + test matrix | gabriel-muse + musehub | **Thinking** | **DONE** (2026-07-10) — contract frozen (§KD-5a); open items (a)–(e) resolved; 7-tier matrix + Claude Desktop integration test specified | | **KD-5b** | MCP domain surface — implementation (9 tools, 8 resources, 4 prompts) | gabriel-muse + musehub | Auto | **DONE** (2026-07-10) — `muse/mcp/` server + namespace; 36 MCP tests + stdio E2E green on `knowtation-vault-smoke` | | **KD-6a** | MuseHub vault UI — contract + test matrix | musehub | **Thinking** | **DONE** (2026-07-10) — contract frozen (§KD-6a); 4 page groups, routes, MCP read-mapping, error enums, 7-tier matrix + Playwright E2E specified | | **KD-6b** | MuseHub vault UI — implementation | musehub | Auto | **DONE** (2026-07-10) — `knowtation_view` adapter, `ui_vault.py`, vault templates/SCSS, harmony diff, 7-tier + Playwright E2E green locally | | **KD-STAGING** | Staging push (KD-5b plugin + KD-6b musehub) + KD-7 attestation smoke | gabriel-muse + musehub | **Operator + Auto** (Tier 3) | **NEXT** | --- ## Repo boundaries | Repo | Role in this program | VCS | | --- | --- | --- | | **gabriel-muse** | Canonical home of `muse/plugins/knowtation/` — detector, parser, merger, code intel, attestation | Muse; staging remote `aaronrene/gabriel-muse` | | **musehub** | Marketplace API, domain registration, Phase 6 vault-centric UI, MCP server mount | Muse / git per repo convention | | **knowtation** (product) | Vault store, Hub API, MCP — **not** the Muse domain plugin | Separate; Scooling adapter target | | **knowtation-vault-smoke** | KD-ACTIVATE test vault — **not** product repo | Local Muse repo; `domain=knowtation` | | **scooling** | Consumer UI — parallel track; wires domain features **after** KD-5/KD-6 land | See Scooling governance docs | **Important:** The Knowtation **product repo** (`.museattributes` → `domain = "code"`) is not a vault repo. Activation uses a **vault repo** — Markdown notes with Knowtation frontmatter (`source_type`, `source_id`, `project`, `tags`, `entity`). --- ## Phase detail (frozen from issue #2) ### KD-1 — Domain Foundation **Goal:** Muse recognises Knowtation vault files as a distinct domain and introspects them structurally. **Deliverables (verified in tree):** - `detector.py`, `parser.py`, `plugin.py`, `symbols.py`, `stats.py` - `defaults.museattributes` snippet - `muse domain-info --json` shape; `muse code symbols --file note.md` **Exit criteria:** KD-ACTIVATE smoke PASS on a real vault repo — **MET** (domain-info PASS; symbols CLI routes through code AST — gap recorded). --- ### KD-2 — Semantic Diff & Harmony Merge **Goal:** Section-aware diff; policy-based merge for tags, entities, source_id conflicts. **Deliverables (verified in tree):** - `differ.py`, `merger.py`, `policies.py`, `strategies.py`, `rerere.py` - Harmony policies for tag union, entity union, prefer-newer-date, §Summary escalation **Exit criteria:** `muse merge` resolves tag/entity conflicts without raw-text panics — **PARTIAL** (`merge_notes` union PASS; `muse merge` CLI file-conflict on tag-only divergence). --- ### KD-3 — Code Intelligence **Goal:** `muse code deps`, `impact`, `hotspots`, `gravity`, `dead`, `clones`, `entangle`, `velocity` on vault repos. **Deliverables (verified in tree):** - `link_index.py`, `code_intel.py`, `note_metrics.py`, `cli_hooks.py` **Exit criteria:** All six commands return meaningful JSON on test vault — **MET** (deps, impact, hotspots, dead verified). --- ### KD-4 — Memory Timeline **Goal:** Memory event types in commit metadata; symbol-log as memory replay; prime bootstrap from commit graph. **Deliverables (verified in tree):** - `events.py` (15 event kinds, schema locked v1.0.0) - `hooks.py` (consolidation pre-merge) - `prime_context.py` (`knowtation://prime` from commit graph) **Exit criteria:** `muse log --json` surfaces `event_type`; prime JSON smoke PASS — **MET** (log PASS; prime via `build_prime_context` API, no CLI). --- ### KD-5 — MCP Domain Surface (KD-5a contract frozen; KD-5b implementation DONE) **Status (KD-5b):** **DONE** (2026-07-10). Server at `muse/mcp/`; 9 tools / 8 resources / 4 prompts; activation gate on `repo.json domain==knowtation`; 36 MCP tests + stdio E2E green on `knowtation-vault-smoke`. **Goal:** Knowtation domain ops via Muse MCP (`knowtation/search`, `get-note`, `history`, `consolidate`, `impact`, `dead`, `prime`, `propose`, `attest` — 9 tools + 8 resources + 4 prompts per issue #2 Phase 5). **Entry criteria:** KD-ACTIVATE PASS; KD-OQ resolved. **Both MET (2026-07-10).** **Frozen inputs from KD-OQ:** search backend = Muse structural index + Knowtation live vector index (OQ2); `knowtation/consolidate` = async dispatch, non-blocking (OQ3); `knowtation/attest` = generic `MuseAttestationProvider` reusing the ICP canister (OQ4); no `agent.md` merge (OQ5); detection = frontmatter-primary with optional marker (OQ1). **Model:** Thinking → Auto (KD-5a Thinking **DONE** — contract frozen below; KD-5b Auto is NEXT). --- ## KD-5a Contract — Muse MCP Domain Surface (`knowtation` namespace) — FROZEN 2026-07-10 **Status:** DONE (Thinking). This section is the **frozen spec** KD-5b (Auto) implements against. Do not redesign during Build. Interfaces below are the contract; the seven-tier test matrix is in §KD-5a.F. Implementation-only (KD-5b) items are tagged **[KD-5b new work]**. **Authority chain:** issue #2 Phase 5 tables (9 tools / 8 resources / 4 prompts) + KD-OQ frozen decisions (OQ1–OQ5). The prompt count is **4** (issue #2 §5.3 lists 3 and adds `vault/knowledge-gaps` in its deliverables line; the corrected count is 4 — see change log 2026-07-10). ### KD-5a.0 — Transport, envelope & shared conventions - **Protocol:** JSON-RPC 2.0 per MCP (`tools/list`, `tools/call`, `resources/list`, `resources/read`, `prompts/list`, `prompts/get`). - **Tool names:** `knowtation/` (slash-namespaced). **Resource URIs:** `knowtation://…` scheme. **Prompt names:** `vault/`. - **Repo binding:** every call operates on the server's bound repo root (the process CWD's `.muse/`); no tool takes a filesystem root argument (prevents path-escape and cross-repo reads). - **`ref` grammar (shared):** `HEAD` | branch name | full/abbrev sha256 commit id. Resolved via `muse.core.store.resolve_commit_ref`; unknown ref → `REF_NOT_FOUND`. - **`path` grammar (shared):** vault-relative POSIX path. Reject absolute paths, `..` segments, and any resolved path escaping the repo root → `PATH_INVALID` (reuses the `link_index._normalise_rel_path` escape check). Non-`.md`/`.markdown`/`.mdx` → `NOT_A_NOTE`. - **Error envelope:** JSON-RPC error `{code, message, data:{reason, detail?}}` where `reason` is a stable UPPER_SNAKE token (enumerated per tool). Human `detail` is control-char sanitised (`sanitize_display`). **No stack traces, no secrets, no absolute host paths** in `message`/`data`. - **Determinism:** read tools are pure/idempotent; write/dispatch tools (`consolidate`, `propose`, `attest`) are the only side-effecting members and are individually gated (see below). - **Bounds (DoS):** every commit walk is capped (`max`/`max_commits`, clamp `1..100000`, default per tool); every note body read reuses the 16 MiB cap (`link_index._MAX_NOTE_BYTES`); every HTTP response is byte-capped (attestation 64 KiB; hub calls 256 KiB). ### KD-5a.A — Resolution of the five open contract items (a)–(e) **(a) `knowtation/search` fan-out — FROZEN.** Two backends, merged by the server: 1. **Muse lexical/structural** (owned by Muse, OQ2): live-vault content grep + `build_link_index` title/heading/frontmatter hits. **[KD-5b new work]** — no `content_grep` helper exists in the plugin today; KD-5b adds `muse/plugins/knowtation/search.py::lexical_search(root, query, *, top_k)` returning `[{path, score∈[0,1], section_address?, snippet}]`. 2. **Knowtation vector** (canonical for semantic at HEAD, OQ2): HTTP `GET` to the local hub (`http://localhost:/api/v1/search?q=…&k=…`) — Muse **never** rebuilds or versions `data/`. **[KD-5b new work]** — thin client, SSRF-guarded port validation reused verbatim from `hooks._resolve_port`. - **Merge/rank:** normalise each backend's score to `[0,1]`; combined `score = w_lex·lex + w_vec·vec` with **frozen weights `w_lex=0.4`, `w_vec=0.6`** (semantic favoured at HEAD, per OQ2). A result present in only one backend uses that backend's normalised score with the missing side treated as `0`. - **Dedupe key:** vault-relative `path` (case-insensitive basename collision resolved the same way `link_index` resolves it — first walk-order path wins; the other is dropped and counted in `deduped`). When both backends return the same `path`, scores are combined (not summed twice). - **Tie-breakers (stable):** higher combined `score` → then more recent HEAD mtime → then lexicographic `path`. - **Fallback / timeout:** vector call timeout **2.0 s**; on timeout/connection-refused/non-200 the server returns lexical-only results with `degraded=true`, `backends.vector="unavailable"`, and a populated `index_freshness` (or `null`) — **never an error**. If the lexical side raises, return vector-only with `backends.lexical="error"`. Both down → `results:[]`, `degraded=true` (still a success response so agents can proceed). **(b) `knowtation/consolidate` return schema — FROZEN.** The tool wraps `KnowtationMergeHook.pre_merge_hook` (OQ3, fire-and-forget). Output is the **dispatch receipt** exactly mirroring `hooks.hook_result_to_json`: `{hook_name:string, fired:bool, out_of_band:bool, message:string, error:string|null, dispatched_at:iso8601, hub_port:int}`. - **Async completion is NOT observable through this tool** (by design — merge never blocks). The agent observes completion out-of-band via `knowtation://memory/recent` (a later commit with `event_type∈{consolidation,consolidation_pass}`) or `knowtation/history`. This is documented in the tool description so agents do not poll. - Port-validation failure and dispatch failure are returned in `error` with `fired=false` — **the tool never raises** for hub-side problems (parity with the hook contract). **(c) `knowtation/attest` runtime wiring — FROZEN.** - **Who registers:** the **MCP server bootstrap** registers both providers when the bound repo's active domain is `knowtation` — it calls `attestation.register_knowtation_attestation_provider(cfg)` and `icp_push_hook.register_knowtation_icp_push_hook()` at server start. Today only the *merge* hook auto-registers in `plugins/knowtation/__init__.py`; the attest/ICP registration is **[KD-5b new work]** (a `bootstrap_knowtation_mcp(root)` call — it must **not** be moved into `__init__.py`, to keep import side-effect-free per OQ4). - **Required vs optional mode:** driven by `.muse/air.yaml` `air.required` (loaded via `attestation.load_air_config`). The tool accepts an optional `required?:bool` that overrides for this call only. `required=true` + endpoint unreachable → `AttestationRequiredError` surfaced as error `reason=ATTESTATION_REQUIRED`. `required=false` → placeholder record returned with `anchored=false` (never an error). - **Provider unregistered:** the tool returns a structured error `reason=PROVIDER_UNREGISTERED` (never a silent success, never a placeholder masquerading as real). ICP anchoring is out-of-band (`out_of_band=true`) and its failure is reported in `anchor` as `null` with `anchor_error`, never failing the tool. **(d) `.museignore data/` — FROZEN: YES (ignore for snapshots; keep readable on disk).** KD-5b adds a `[domain.knowtation]` section to `.museignore` with `patterns = ["data/", ".obsidian/"]` so Muse **never snapshots/versions Knowtation's vector/sqlite index** (consistent with OQ2 "not rebuilt/versioned by Muse"). This is a *snapshot-exclusion* rule only: `stats.collect_vault_stats` still `stat()`s `data/` on the live filesystem for `index_freshness` (a filesystem read, unaffected by `.museignore`), and `link_index`/`code_intel` already skip non-`.md` files so the vector store is never parsed as a note. `.knowtation` remains an always-ignored marker via `plugin._ALWAYS_IGNORE_DIRS`; `data/` is domain-scoped ignore (only when active domain is knowtation). **(e) MCP-scope activation semantics — FROZEN: explicit `repo.json` domain, detector is advisory.** The `knowtation/*` namespace mounts/advertises **only when `.muse/repo.json` `domain == "knowtation"`** (explicit activation via `muse init --domain knowtation`). `detector.classify_repo` is **advisory only**: the server MAY surface `detected_domain` + `confidence` as a capability hint (so a misconfigured repo can be diagnosed) but **never auto-mounts** vault tools on a repo whose active domain is `code`/`mist`. This prevents a code repo containing a few notes from exposing vault write/attest surfaces. When domain≠knowtation the server returns an empty `knowtation/*` tool list. ### KD-5a.B — Tool contracts (9) Legend: **Backing** = existing plugin/CLI API the tool wraps; **[KD-5b new work]** = no backing API, implement in KD-5b. All inputs validated against §KD-5a.0 grammars before dispatch. | # | Tool | Input (JSON-RPC params) | Output (result) | Backing API | Error reasons | | --- | --- | --- | --- | --- | --- | | 1 | `knowtation/search` | `{query:string!, top_k?:int=10 (1..100), project?:string, mode?:"hybrid"\|"lexical"\|"semantic"="hybrid", ref?:string="HEAD"}` | `{query, mode, results:[{path, score, source:"muse-lexical"\|"knowtation-vector"\|"both", snippet, section_address?, frontmatter:{project,tags,source_type}}], index_freshness:string\|null, degraded:bool, deduped:int, backends:{lexical:"ok"\|"error", vector:"ok"\|"unavailable"}}` | lexical **[KD-5b new work]** (`search.lexical_search`) + `link_index` + hub vector client **[KD-5b new work]**; `stats.collect_vault_stats` for freshness | `QUERY_INVALID`, `REF_NOT_FOUND` (never errors on backend outage — see A(a)) | | 2 | `knowtation/get-note` | `{path:string!, ref?:string="HEAD", format?:"json"\|"raw"="json"}` | `{path, ref, exists:bool, frontmatter:object, body:string, symbols:[symbol…], content_hash:string}` | `muse cat`@ref (`core.store` manifest+object read) + `symbols.extract_symbols` | `PATH_INVALID`, `NOT_A_NOTE`, `NOTE_NOT_FOUND`, `REF_NOT_FOUND` | | 3 | `knowtation/history` | `{address:string!, from_ref?:string="HEAD", max?:int=500 (1..100000)}` | `{address, start_ref, total_commits_scanned, truncated, events:[{event, commit_id, message, committed_at, address, detail, new_address, event_type, agent_id, model_id}]}` | `cli/commands/symbol_log.py` (`_find_events_in_commit` + `walk_commits_bfs`) — **existing** | `ADDRESS_INVALID`, `REF_NOT_FOUND` | | 4 | `knowtation/consolidate` | `{branch?:string, ours_ref?:string, theirs_ref?:string}` (metadata passthrough; hook ignores today) | dispatch receipt (see A(b)): `{hook_name, fired, out_of_band, message, error, dispatched_at, hub_port}` | `hooks.KnowtationMergeHook.pre_merge_hook` + `hook_result_to_json` — **existing** | (never raises; failures in `error`) | | 5 | `knowtation/impact` | `{path:string!, direction?:"reverse"\|"forward"="reverse", depth?:int=0 (>=0), include_attachments?:bool=true}` | `{note, direction, depth_limit, by_depth, total, include_attachments}` | `code_intel.impact_for_note`/`note_impact` — **existing** | `PATH_INVALID`, `NOTE_NOT_FOUND`, `DEPTH_NEGATIVE` | | 6 | `knowtation/dead` | `{use_mtime?:bool=true, mtime_days_threshold?:int=180 (>=0), project?:string}` | `{total_notes_indexed, dead_count, dead_notes:[{path, mtime?}]}` | `note_metrics.note_dead` (+ `build_link_index`) — **existing** | `NOT_KNOWTATION_REPO` | | 7 | `knowtation/prime` | `{max_commits?:int=100 (1..100000), hot_note_count?:int=10 (1..100)}` | `PrimeContext.to_dict`: `{schema_version, source, commits_scanned, last_consolidation, hot_notes}` (+ `active_projects`, `suggested_next` enrichment) | `prime_context.build_prime_context` — **existing**; `active_projects`/`suggested_next` via `stats` **[KD-5b new work]** | (never fatal; partial on graph-read error) | | 8 | `knowtation/propose` | `{path:string!, title:string!, proposed_body?:string, proposed_frontmatter?:object, base_ref?:string="HEAD", scope?:"personal"\|"project"\|"org"="personal", rationale?:string}` | `{proposal_id, status:"created", hub_url, path, base_ref, scope}` | `muse hub proposal create` → Knowtation Hub proposal endpoint — **[KD-5b new work]** (no proposal API in plugin today) | `PATH_INVALID`, `NOTE_NOT_FOUND`, `HUB_UNREACHABLE`, `SCOPE_UNAUTHORIZED`, `VALIDATION_FAILED` | | 9 | `knowtation/attest` | `{commit_id:string!, action?:string="write", required?:bool}` | `{record:{id, action, path, timestamp, content_hash, algorithm, provider_id, sig?}, anchored:bool, anchor:{tx_id, anchor_url, provider_id}\|null, anchor_error?:string, provider_registered:bool}` | `attestation.KnowtationHmacAttestationProvider` (compute+anchor) + `icp_push_hook` — **existing**; runtime registration **[KD-5b new work]** (see A(c)) | `PROVIDER_UNREGISTERED`, `COMMIT_NOT_FOUND`, `ATTESTATION_REQUIRED` | **Write/side-effect gating:** tools 4, 8, 9 are the only non-read members. `propose` is review-before-write (scope enforced server-side, per Scooling boundary rules); `consolidate`/`attest` are dispatch/opt-in. Read tools (1,2,3,5,6,7) have zero durable side effects. ### KD-5a.C — Resource contracts (8) | # | Resource URI | Content (result) | Backing API | Notes | | --- | --- | --- | --- | --- | | 1 | `knowtation://vault` | `{notes:[{path, title, project, tags, source_type}], stats:VaultStats}` | `stats.collect_vault_stats` + note-level walk (`parse_frontmatter`/`extract_symbols`) — listing walk **[KD-5b new work]** | Root listing + frontmatter summary | | 2 | `knowtation://vault/{path}` | note at HEAD — same body as `knowtation/get-note` (ref=HEAD) | `get-note` core | `{path}` obeys shared path grammar | | 3 | `knowtation://vault/{path}@{ref}` | note at `{ref}` — same body as `knowtation/get-note` | `get-note` core | URI-template `@{ref}` parsed then validated | | 4 | `knowtation://prime` | `PrimeContext.to_dict` | `prime_context.build_prime_context` — **existing** | Mirrors `knowtation/prime` (no args) | | 5 | `knowtation://graph` | `{notes_indexed, forward:{src:[edge…]}, backward:{tgt:[edge…]}, broken_links:[edge…], entities:{label:[path…]}}` | `link_index.build_link_index` + JSON serializer **[KD-5b new work]** (LinkIndex has no `to_json` today) | Edge = `describe_link` shape | | 6 | `knowtation://memory/recent` | `{events:[{commit_id, committed_at, message, event_type, agent_id, model_id}], count}` (last 20) | `walk_commits_bfs` + filter `metadata.event_type` present — composition **[KD-5b new work]** | Event kinds validated against `events.MemoryEventKind` | | 7 | `knowtation://projects` | `{projects:[{project, note_count, last_modified}]}` | `stats.collect_vault_stats.projects` + `_query.group_by_project` + mtime — composition **[KD-5b new work]** | | | 8 | `knowtation://hotspots` | `note_hotspots` JSON: `{commits_analysed, total_notes_touched, ranking:[{path, changes}]}` (top 10) | `note_metrics.note_hotspots` + `make_snapshot_loader` — **existing** | `top=10` fixed for the resource | ### KD-5a.D — Prompt contracts (4) MCP prompts return `messages[]`; each composes the tools/resources above (no new backend). | # | Prompt | Arguments | Composition (backing) | Output | | --- | --- | --- | --- | --- | | 1 | `vault/daily-brief` | `{since_hours?:int=24 (1..168), project?:string}` | `walk_commits_bfs` filtered to window + `knowtation://memory/recent` | Prompt messages summarising last-24h commits/projects | | 2 | `vault/search-synthesis` | `{query:string!, top_k?:int=5 (1..20)}` | `knowtation/search` (top_k) → format top-5 + synthesis instruction | Prompt messages: results + synthesis directive | | 3 | `vault/memory-session` | `{project?:string}` | `knowtation/prime` + `knowtation://memory/recent` | Prompt messages: prime + recent events to resume session | | 4 | `vault/knowledge-gaps` | `{}` | `knowtation/dead` × `link_index.entity_index` cross-reference | Prompt messages: dead notes joined with entity coverage gaps | ### KD-5a.E — Namespace mount point (gabriel-muse) & musehub hosting - **Server home [KD-5b new work]:** `gabriel-muse/muse/mcp/` — no MCP server exists today (confirmed: no `muse/mcp/` tree; `prime_context.py` only *anticipates* Phase 5). KD-5b scaffolds `muse/mcp/server.py` (a `MuseMCPServer` with a namespace registry) + `muse/mcp/namespaces/knowtation.py` (registers the 9 tools / 8 resources / 4 prompts under the `knowtation/` tool prefix and `knowtation://` resource scheme). - **Activation gate (A(e)):** on start, the server reads `.muse/repo.json`; it registers the `knowtation` namespace **iff `domain == "knowtation"`**. It then calls `bootstrap_knowtation_mcp(root)` which registers the attestation provider + ICP push hook (A(c)). When domain≠knowtation, `tools/list` returns no `knowtation/*` entries. - **musehub hosting:** `musehub` launches the Muse MCP server **per repo** — **stdio** transport for local MCP clients (Claude Desktop, Cursor), and a hosted **HTTP+SSE** transport for remote agents. The server process runs with the repo root as CWD so all binding is implicit (no path args cross the wire). The marketplace listing `@aaronrene/knowtation` (already registered, KD-SD8-REG) gains its MCP capability descriptor in KD-5b; the vault UI is KD-6. - **Secret hygiene at the mount:** the server never echoes `MUSE_AIR_HMAC_KEY`, `air.endpoint`, `MUSE_ICP_IDENTITY_PEM`, `KNOWTATION_HUB_PORT` internals, or absolute host paths in any tool/resource/prompt result, log line, or error `data`. Attestation `sig` (an HMAC *output*, not key material) MAY be returned; the HMAC *key* MUST NOT. ### KD-5a.F — Seven-tier test matrix (per group + Claude Desktop integration) All tiers run in `gabriel-muse` (`pytest tests/test_knowtation_mcp_*.py` — new files in KD-5b) against `~/MUSE_HUB/knowtation-vault-smoke`. Each row is the **required** minimum for KD-5b Definition of Done. | Tier | Tools (1–9) | Resources (1–8) | Prompts (1–4) | | --- | --- | --- | --- | | **1 Unit** | Each tool's input-schema validation (grammar A.0), output serialization shape, error-reason mapping; per-tool arg defaults/clamps | Each resource URI parse (incl. `@{ref}` template), output shape, empty-vault case | Argument schema + message assembly for each prompt (mocked tool outputs) | | **2 Integration** | Tool → backing API on the smoke vault (search fan-out both backends; get-note@ref; history events; consolidate receipt; impact/dead/prime/hotspots real JSON; propose→hub stub; attest→provider stub) | Resource → backing on smoke vault; `graph`/`memory/recent`/`projects` composition correctness | Prompt → real tool chain on smoke vault (search-synthesis, memory-session) | | **3 E2E** | `tools/list`+`tools/call` over JSON-RPC through the mounted server (domain=knowtation) | `resources/list`+`resources/read` round-trip | `prompts/list`+`prompts/get` round-trip | | **4 Stress** | 5k-note vault: search/impact/dead/hotspots within perf budget; `max` clamps honoured; vector-backend timeout path under load | `graph` on 5k notes; `vault` listing pagination bound | daily-brief over 500-commit window; knowledge-gaps over large entity index | | **5 Data-integrity** | `history`/`memory/recent` event_type parity vs `events.EVENTS_SCHEMA_HASH`; get-note `content_hash` matches store; attest `id` determinism (`compute_attestation_id`) | resource payloads round-trip; `graph` edges match `describe_link`; `index_freshness` == `data/` mtime | prompt outputs cite only real notes (no fabricated paths) | | **6 Performance** | search < 2 s hybrid (lexical budget < 1 s + vector timeout 2 s); impact/dead/hotspots < 2 s on 5k vault (reuse §3.3 budgets); prime < 1 s / 100 commits | `graph` build < 10 s / 5k (reuse `link_index` budget) | prompt assembly < 500 ms excluding model call | | **7 Security** | path-traversal reject (get-note/impact/propose); SSRF-guard on search-vector + consolidate port + attest endpoint + ICP canister-id (reuse existing regex guards); **no secret in any result/log/error**; `air.required` rejection; `PROVIDER_UNREGISTERED` never silent-success; sensitive-key scan (`events._has_sensitive_keys`) on surfaced note `data`; activation gate blocks `knowtation/*` on domain≠knowtation | resource path-escape reject; `memory/recent` strips sensitive metadata | prompt injection: note bodies are data-not-instructions; control-char sanitised | **Claude Desktop integration test (required, Tier 3/E2E):** register the KD-5b server in Claude Desktop's MCP config bound to `knowtation-vault-smoke`; verify `tools/list` shows the 9 tools, `resources/list` shows the 8 resources, `prompts/list` shows the 4 prompts, and one live `knowtation/search` + `knowtation://prime` round-trip returns valid payloads. Evidence captured in the KD-5b handover matrix. ### KD-5a.G — Security invariants (frozen) 1. **No secrets anywhere on the wire:** HMAC key, AIR endpoint, identity PEM path, hub port internals, absolute host paths are never in results, logs, or error `data` (§E). 2. **Reuse existing guards:** `_normalise_rel_path` (path escape), `hooks._resolve_port` + `attestation._validate_endpoint_url` + `icp_push_hook._is_valid_canister_id` (SSRF), body/response byte caps, `events._has_sensitive_keys` (payload scrub). 3. **Misuse-resistant verification:** attest never returns a placeholder as if real; `verify` partial-failures are hard errors (per `core.attestation` contract). 4. **Bounded everything:** commit walks, note reads, HTTP responses all capped (§0). 5. **Activation gate is a security control**, not a convenience: it prevents non-vault repos from exposing vault write/attest tools (A(e)). ### KD-5a.H — KD-5b new-work summary (no backing API today) - `knowtation/search` lexical helper (`search.lexical_search`) + hub vector client + fan-out merge. - `knowtation/propose` → `muse hub proposal create` + Knowtation Hub proposal endpoint wiring. - Attestation/ICP **runtime registration** (`bootstrap_knowtation_mcp`) — not in `__init__.py`. - `.museignore` `[domain.knowtation]` section (`data/`, `.obsidian/`). - Resource serializers: `knowtation://graph`, `://memory/recent`, `://projects`, `://vault` listing. - `prime` enrichment (`active_projects`, `suggested_next`). - The **MCP server scaffold** itself: `muse/mcp/server.py` + `muse/mcp/namespaces/knowtation.py`. Everything else (tools 2,3,5,6,7 and resources 2,3,4,8) maps to **existing, tested** plugin/CLI APIs. --- ### KD-6 — MuseHub UI for Vault Repos (KD-6a contract frozen; KD-6b implementation TODO) **Goal:** Domain-aware repo landing, note detail page, knowledge proposals UI, vault search. **Entry criteria:** KD-1 symbols + KD-2 diff available on staging vault; KD-5 MCP tools **landed** (KD-5b DONE). **All MET (2026-07-10).** **Repo:** `musehub/` primarily. **Model:** Thinking → Auto (KD-6a Thinking **DONE** — contract frozen in §KD-6a below; KD-6b Auto is NEXT). --- ## KD-6a Contract — MuseHub Vault UI (`musehub`) — FROZEN 2026-07-10 **Status:** DONE (Thinking). This section is the **frozen spec** KD-6b (Auto) implements against. Do not redesign during Build. Interfaces below are the contract; the seven-tier test matrix is in §KD-6a.F. Implementation-only (KD-6b) items are tagged **[KD-6b new work]**. The KD-5b MCP surface (9 tools / 8 resources / 4 prompts) is a **read dependency** consumed as-is — **do not redesign KD-5 MCP interfaces** (hard stop). **Authority chain:** issue #2 Phase 6 (vault-centric UI) + KD-5a frozen MCP contract (§KD-5a) + existing MuseHub UI conventions verified in tree (`api/routes/musehub/ui_*.py`, `templates/musehub/pages/`, `src/scss/`). Boundary rules from Scooling `CROSS-REPO-COORDINATION.md` apply: **MuseHub enriches (version/provenance/marketplace); it does not own note bodies**; Knowtation is the canonical vault store + permission authority; review-before-write; scope is authorization enforced **server-side**. ### KD-6a.0 — Surface conventions & shared contract - **URL scheme (reuse, do not fork):** vault pages are **domain-conditional branches of the existing repo route tree** `/{owner}/{repo_slug}/…` (`api/routes/musehub/ui_repo.py::repo_page` + siblings), **not** a new `/vault/…` tree. Ref selection reuses the existing `?ref=` query param. - **Domain activation gate (mirrors KD-5a.E — security control, not convenience):** a page renders its **vault variant iff the repo's active marketplace domain resolves to `knowtation`** (`_nav_ctx._resolve_repo_domain` / `_resolve_domain_viewer_type` → domain `@aaronrene/knowtation`). A `code`/`mist` repo that merely contains a few notes **never** shows vault tabs, the note-detail variant, or the knowledge-proposal write surface. `detector.classify_repo` is **advisory only** (may power a "looks like a vault — activate?" hint), never auto-mounts. - **Content negotiation (frozen, reuse verbatim):** `?format=json` **or** `Accept: application/json` → structured JSON (camelCase, `by_alias`); `HX-Request: true` → bare HTMX fragment (partial swap target); default → full SSR page. Canonical machine-readable REST lives under `/api/…`; UI routes are the same-URL negotiated surface (established pattern in `ui_repo.py`, `ui_domains.py`). - **Read surface split (frozen):** 1. **Note bodies, tree, commits, diffs, refs** are served from **MuseHub's own versioned snapshot store** (`services/musehub_repository`, `muse_cli/snapshot.py`) — exactly as code repos are today. This is *versioned enrichment*, not body ownership; the canonical body still lives in the Knowtation vault. 2. **Domain intelligence** (hybrid search, backlinks/impact, dead notes, link graph, prime, memory timeline, hotspots, history) is read from the **mounted Muse domain MCP `knowtation/*` surface (KD-5b)** — the 9 tools / 8 resources / 4 prompts, consumed as-is. - **Adapter boundary (frozen):** **no Jinja template or route handler calls an MCP tool / provider SDK inline.** All MCP reads go through a hub-side **`services/knowtation_view` adapter** **[KD-6b new work]** that (i) resolves the mounted per-repo Muse MCP server (stdio for local, hosted HTTP+SSE per KD-5a.E), (ii) issues `tools/call` + `resources/read`, (iii) normalises the JSON-RPC envelope into hub view-models, and (iv) maps MCP error `reason` tokens → UI states (§KD-6a.E). This is the KD-6 analog of the Scooling "no component calls a provider directly" rule. - **Write surface (frozen — review-before-write):** knowledge-proposal **creation** dispatches to `knowtation/propose` (→ Knowtation Hub proposal endpoint; scope `personal|project|org` enforced **server-side**). MuseHub **renders, lists, threads, and reviews** proposals via the existing proposals surface (`ui_proposals.py`, `MusehubProposal`); it **never writes note bodies directly** and never enforces scope in the UI layer. - **Resilience / degradation (frozen):** the MuseHub-owned data path (bodies/tree/history from the snapshot store) is **independent** of the MCP path. If the mounted MCP server is unavailable or a tool returns `degraded=true`, vault-intel panels render a **non-fatal** "domain intelligence unavailable" / "semantic search unavailable — showing lexical results" state; the page still renders bodies, tree, and history. A domain-intel outage is **never a 5xx**. - **Error envelope:** HTML → standard hub error pages (403/404/410/500) with domain-aware empty states; JSON → existing hub error shape `{error:{code,message}}`; MCP-sourced problems surface as the UI-state enum in §KD-6a.E, not as raw JSON-RPC errors. - **Bounds & determinism:** reuse hub cursor pagination for lists; note-body render reuses the 16 MiB cap; search/impact/history reuse the MCP tool clamps (KD-5a.0); no unbounded walks initiated from the UI. - **Secret hygiene:** never render `KNOWTATION_HUB_PORT` internals, `air.endpoint`, `MUSE_AIR_HMAC_KEY`, `MUSE_ICP_IDENTITY_PEM`, or absolute host paths in any HTML, JSON, `data-*` attribute, OG tag, or JSON-LD block. Attestation `sig` (an HMAC *output*) MAY render; key material MUST NOT. ### KD-6a.A — Resolution of the five open UI contract items (a)–(e) **(a) Note-body rendering & sanitisation — FROZEN: server-side render, sanitised, wikilinks resolved.** Markdown → HTML is rendered **server-side** reusing the hub's existing renderer path (the README/blob pipeline behind `_ui_helpers._fetch_readme` + `blob.html`), extended **[KD-6b new work]** with a Knowtation post-processor that (i) resolves `[[wikilinks]]` and `[[wikilink#section]]` to `/{owner}/{repo_slug}/note/{path}` (broken links get a `broken` class, never a raw link), and (ii) renders frontmatter as a **metadata panel**, never executed or reflected into script context. Raw inline HTML in note bodies is **sanitised** (allowlist; no `