diff --git a/PHASES.md b/PHASES.md index 1f55a07..0185ecd 100644 --- a/PHASES.md +++ b/PHASES.md @@ -257,6 +257,57 @@ mod: apps/api/src/index.ts (thin re-export), apps/api/src/router.ts (unchange renamed: apps/mcp/src/smoke.test.ts -> smoke.ts (fixed stale assertions) ``` +## Phase 10 — Audit + Bug fixes (live verification) + +> Full feature audit against live services. Found + fixed one real bug. + +### Bug: Frontmatter leaking into rendered doc pages + MCP doc body + +- **Symptom:** Doc pages showed raw YAML frontmatter (`id: websocket-contract`, + `title: WebSocket Contract`, etc.) as visible plain text between `
` elements in SSR HTML); MCP `mcpedia://` doc body resource
+ returns clean markdown (starts with `# WebSocket Contract`).
+
+### Audit findings (all phases verified live)
+
+| Phase | Feature | Live check | Status |
+|-------|---------|------------|--------|
+| P1 | Web UI `/docs//` | 200, renders markdown | ✅ |
+| P1 | Search page (`?q=` + `?mode=hybrid`) | 200, returns results | ✅ |
+| P1 | MCP stdio + HTTP (`/4021`) | 10 tools, 4 resources | ✅ |
+| P2 | Semantic/hybrid search | returns ranked chunks | ✅ |
+| P2 | tRPC API on domain (`/trpc/*`) | listDocuments → 4 docs | ✅ |
+| P3 | BullMQ worker drains jobs | queue completed 17→19 after enqueue | ✅ |
+| P3 | Revision system | listRevisions → rev #1 "phase4-final-clean" | ✅ |
+| P3 | Git webhook auth gate | 401 w/o secret, 200 w/ secret | ✅ |
+| P4 | Dashboard | `/dashboard` → 200 HTML | ✅ |
+| P6 | All 4 systemd services | web/api/mcp/worker all `active` | ✅ |
+| P6 | restoreRevision mutation locked | 401 w/o secret, executes w/ secret | ✅ |
+| P7 | 10 MCP tools (6 read + 4 write) | tools/list → 10 | ✅ |
+| P7 | Write-tool auth gate | reindex_all w/o secret → isError | ✅ |
+| P7 | Prometheus metrics | `/metrics` → 7 gauges, 200 | ✅ |
+| P8 | Dashboard live search | `fetch("/metrics")` + `hybrid_search` via `/mcp` | ✅ |
+| P9 | Test suite | 6/6 packages, 32 tests, 0 fail | ✅ |
+
+### Notes / non-bugs
+- Doc URLs follow `//` (e.g. `/docs/caddy/reverse-proxy`,
+ `/writeups/infra/cloudflare-525`, `/notes/postgres/full-text-search`).
+ The route is `[section]/[...slug]` — `/docs/websocket/contract` works because
+ the section IS `docs` for that doc; `/notes/postgres/fts` does not (the correct
+ slug is `notes/postgres/full-text-search`).
+- `restoreRevision` via tRPC needs the `x-webhook-secret` as an **HTTP header**
+ (not inside the JSON body) — the fetch adapter reads `c.req.raw.headers`.
+
## Decisions locked (from initial planning)