Files
mcpedia/.hermes/plans/phase4-plan.md
T
asepharyana b92f6f91fa feat(mcpedia): Phase 4 — operability + correctness hardening
Reinterpreted from the plan's YAGNI 'Scale-out' (OpenSearch/object-storage
/multi-tenant deferred at KB scale). Phase 4 = make the Phase 3 async +
revision system correct, secure, observable, deployable.

- T1 (correctness bug): restoreRevision now rebuilds semantic chunks via new
  @mcpedia/core reindexChunks(slug) so semantic/hybrid search stay consistent
  after a restore (previously document_chunks held the NEW body while
  documents.body held the restored OLD body -> stale search).
- T2 (security): /hooks/* git-sync webhooks now require x-webhook-secret header
  matching WEBHOOK_SECRET (401 otherwise); API fails fast at startup if unset.
  Added WEBHOOK_SECRET to @mcpedia/config + .env.example; set real secret in .env.
- T3 (UX): web doc page shows a History panel (revision no/reason/date/length)
  with per-revision Restore; app/api/revisions/restore/route.ts calls
  restoreRevision + revalidatePath (server-component only, no client JS).
- T4: listRevisions gains offset paging; summary never includes body.
- T5 (ops): deploy/mcpedia-api.service + deploy/mcpedia-worker.service systemd
  units (Restart=on-failure, EnvironmentFile=.env). Not auto-enabled on host.

Verified against live imrnes Redis + Postgres: turbo typecheck+build green;
restore-rebuilds-chunks (marker present -> gone after restore); webhook 401/200;
web restore route redirects to doc + reverts body; revisions API returns summary
(no body); systemd-analyze verify passes.
2026-08-19 21:54:54 +07:00

5.7 KiB

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 <form method="post" action="/api/revisions/restore"> with hidden id + a route handler in apps/web calling restoreRevision. Keep it read-mostly; restore is a deliberate action.
  • Add apps/web/app/api/revisions/restore/route.ts (POST) → restoreRevision(id) → revalidatePath the doc.

Files: apps/web/app/[section]/[...slug]/page.tsx, apps/web/app/api/revisions/restore/route.ts.

T4 — Paginate listRevisions / revisions API (PERF)

Root cause: revision.service.ts listRevisions does select length(body) (fine) but the tRPC revisions and MCP resource return full revision rows including the body in some callers; list endpoints should never carry bodies.

Fix:

  • Ensure listRevisions summary excludes body (it already does — bodyLength only). Add offset param for paging. Confirm MCP resource uses the summary.
  • No behavior change for the doc page (uses summary).

Files: packages/core/src/revision.service.ts (add offset), router unchanged.

T5 — Deployable as supervised services (OPS)

Root cause: apps/api and apps/worker run only ad-hoc; the host already runs zeavis via Nix/systemd. Phase-4 operability = provide a systemd unit (or Nix service) so mcpedia-api + mcpedia-worker start on boot and restart on failure.

Fix (Nix-first, per MEMORY):

  • Write mcpedia-api.service + mcpedia-worker.service systemd unit files under deploy/ (bun run api / bun run worker, WorkingDirectory, Restart=on-failure, EnvironmentFile pointing at .env, After=network-online.target).
  • README section "Run as a service" with cp deploy/*.service /etc/systemd/system && systemctl daemon-reload && systemctl enable --now mcpedia-api mcpedia-worker.
  • Do NOT systemctl on the host without user confirmation (changing live services). Provide the files + instructions only; user runs enable.

Files: deploy/mcpedia-api.service, deploy/mcpedia-worker.service, README.

Verification (all real, against imrnes Redis + Postgres)

  1. turbo run typecheck + turbo run build green.
  2. T1: script — edit a doc, reindex (new revision + new chunks), restore rev #1, assert document_chunks count for that slug now matches re-chunk of rev #1 body and semanticSearch on a term unique to rev #1 returns it.
  3. T2: curl -XPOST localhost:4020/hooks/reindex → 401; with -H "x-webhook-secret: $WEBHOOK_SECRET" → 200 + jobId.
  4. T3: next build includes the History panel; restore form rebuilds chunks (verified via T1 path through the route handler).
  5. T4: revisions API returns summaries without body; offset paging works.
  6. T5: systemd-analyze verify deploy/*.service passes (off-host safe check); README documents enable steps.

Out of scope (YAGNI, keep deferred per PHASES.md)

OpenSearch/Elasticsearch, object storage, multi-tenant, distributed workers, pgvector migration. Revisit only when corpus > ~10k docs or query latency bites.