diff --git a/.hermes/plans/phase4-plan.md b/.hermes/plans/phase4-plan.md new file mode 100644 index 0000000..f3f02c8 --- /dev/null +++ b/.hermes/plans/phase4-plan.md @@ -0,0 +1,105 @@ +# MCPedia Phase 4 — Operability & Correctness Hardening + +> Reinterpretation: the PHASES.md "Scale-out" items (OpenSearch, object storage, +> multi-tenant, distributed workers) are YAGNI at KB scale (4 docs). Phase 4 = +> make the Phase 3 async + revision machinery **correct, secure, observable, and +> deployable** — not speculative infra. Each task below fixes a real gap found +> by reading the code, not a hypothetical need. + +## Tasks + +### T1 — `restoreRevision` must rebuild semantic chunks (CORRECTNESS BUG) +**Root cause:** `packages/core/src/revision.service.ts` `restoreRevision` writes +the old body back into `documents` but never calls `indexChunks(slug, body)`. +So after a restore, keyword search (FTS on `documents.body`) is correct but +`document_chunks`/embeddings stay on the *new* body → semantic + hybrid search +return stale/ghost chunks. + +**Fix:** +- Add `reindexChunks(slug)` to `@mcpedia/core` that re-runs `indexChunks(slug, body)` + using the live `documents.body` (the new body after the update). +- Call it inside `restoreRevision` after the `documents` update (wrap in try/catch + like `indexContentFile` so embed failure doesn't abort the restore). +- Add a unit-style assertion to the MCP smoke test or a small script: restore → + `document_chunks` count matches re-chunked body. + +**Files:** `packages/core/src/revision.service.ts`, `packages/core/src/index.ts`, +`packages/core/src/document.service.ts` (export existing `indexChunks` if needed). + +### T2 — Secure the git-sync webhook (SECURITY) +**Root cause:** `apps/api/src/index.ts` `/hooks/reindex` and `/hooks/index` accept +any request with no `WEBHOOK_SECRET` check — `.env.example` defines `WEBHOOK_SECRET` +but the router never reads it. + +**Fix:** +- In `apps/api/src/index.ts`, compare `c.req.header("x-webhook-secret")` (or + `?secret=`) against `WEBHOOK_SECRET` (from `@mcpedia/config`). If unset/mismatch → + `401`. If `WEBHOOK_SECRET` env is empty, reject at startup with a clear log + (fail-fast, don't run an open endpoint). +- Add `WEBHOOK_SECRET` to `packages/config/src/index.ts` export. +- Document the header in README + verify with curl (401 without secret, 200 with). + +**Files:** `apps/api/src/index.ts`, `packages/config/src/index.ts`, README. + +### T3 — Web UI revisions view (UX) +**Root cause:** Web UI (server components) calls `@mcpedia/core` directly; there is +no revisions surface even though `revisions`/`restoreRevision` tRPC + MCP resource +exist. + +**Fix (server-component only, no client JS):** +- On the doc page (`apps/web/app/[section]/[...slug]/page.tsx`), fetch + `listRevisions(fullSlug, 10)` and render a "History" panel: revision number, + reason, createdAt, body length, and a `/api/revisions/restore` link/POST that + calls the tRPC `restoreRevision` mutation via a server action or a form POST to + a small route handler. Simplest: a `