Files
Void-Homelab/docs/plan-2-complete.md
root fa47419cbd docs: Plan 2 completion summary
22/22 tasks landed; 185 tests; 10 commits; SPA renders end-to-end
including the agent suggest -> owner approve flow. Captures the UI
smoke matrix, security findings handled, and what's deferred to
Plans 3-6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 02:27:58 +10:00

6.3 KiB
Raw Blame History

Plan 2 — Complete

Date: 2026-06-01 Version: 2.0.0-alpha.2 Tests: 185 passing across 43 files Commits on main: 10 (5aa6fe78ae9bce)

Scope delivered

Phase A — REST routes (T1T8)

  • /api/spaces, /api/projects, /api/tasks (with space + project scoping)
  • /api/pages + revisions + /api/pages/:id/backlinks
  • /api/refs + /api/refs/upsert
  • /api/resources + dependencies + change history
  • /api/resources/:id/source-docs + /api/source-docs/:id/resync (gated by ENABLE_RESYNC)

Phase B — Auth + capability (T9T11)

  • agentOrOwner bearer middleware replaces the old owner-only /api gate.
  • requireWrite middleware tiers writes through canAct: allow → through, suggestdivertToPending (202 + pending row), else 403.
  • /api/agents (owner-only) + agent token mint (plaintext returned once) + revoke.

Phase C — Cross-cutting (T12T15)

  • /api/conversations + nested /messages.
  • /api/tags + entity-scoped attach/detach mounted at /api/:entity_type/:entity_id/tags. Tag upsert is idempotent.
  • /api/links (POST/GET from|to/DELETE) for polymorphic entity links. Idempotent POST.
  • /api/pending-changes (owner-only) with applyPendingChange dispatch table covering page/project/task/ref/resource/source_doc × create/update/delete. Approve and reject emit explicit approve/reject audit entries with the original agent id preserved in the diff.
  • /api/audit/entity/:type/:id + /api/audit/actor (owner-only).

Phase D — Search (T16)

  • /api/search?q=&space_id=&kinds=&limit=&offset= — FTS UNION across pages, refs, source docs, messages with a kind discriminator. ts_rank order. Messages branch excluded when a space filter is set (messages have no space_id).
  • Vector + RRF deferred to Plan 3 (TODO in lib/db/repos/search.js).

Phase E — Void UI shell (T17T22)

  • Static SPA served from public/ with three-column Cradle aesthetic: blackflame palette, Cinzel display headings, Cormorant Garamond body, system UI chrome.
  • Hash-based router (public/router.js) with views for home / space / project / page / reference / resource / search / inbox / sacred valley.
  • public/dom.js safe DOM builders. Invariant: no component sets innerHTML from API data; the explicit, scary-named html: opt-in exists only for sanitized output (markdown preview using marked + DOMPurify).
  • public/state.js tiny event bus shares pending-change count between sidebar item and topbar bell.
  • Sidebar: Spaces tree with lazy project expansion, bottom Navigate section with pending-count badge.
  • Topbar: brand + capture modal stub + global search (Enter → /search?q=) + pending bell + owner toggle.
  • Right rail: collapsible companion placeholder, state in localStorage.void_rail_collapsed.
  • Page editor: split-pane Markdown via marked + DOMPurify; save PATCHes /api/pages/:id; backlinks card.
  • Reference detail: media block (image / YouTube embed via youtube-nocookie / link fallback), summary, metadata table, tag attach/detach, linked-from list.
  • Resource detail: status + runtime + host + clickable URL header, dependencies + source docs + runbook pages columns, change history.
  • Inbox: pending changes grouped by agent, approve → navigate to resulting entity.

UI smoke results

Captured via Playwright (/tmp/void-shots/):

# Scenario Outcome
1 Owner token persists in localStorage verified — modal builds via safe DOM, sets localStorage.void_token.
2 Create space via API → appears in sidebar t18-tree-expanded.png / t19-home.png
3 Create project → shows in space view t19-space.png (not captured) / t19-project.png shows project header
4 Edit page in editor + save t20-page-editing.png — live preview via DOMPurify-sanitized marked output. Save button calls PATCH /api/pages/:id.
5 #/search?q=blackflame → results t22-search.png — 2 hits grouped by Pages / References.
6 Agent suggest → bell badge → approve via Inbox t21-inbox.png — Inbox badge shows 3, approve clears it; t21-after-approve.png confirms count drops.

Security findings handled

  • HIGH — javascript: URLs in href/src flagged on reference.js. Fix: safeHref() in dom.js enforces http(s) / mailto. Applied to reference media block, reference source_url anchor, and resource URL anchor. (8ae9bce)
  • HIGH — innerHTML in api.js modal flagged repeatedly during the build. Mitigated upstream by introducing dom.js el() / mount() safe builders; the modal was refactored to use them. The only remaining innerHTML write is in markdown_editor.js and is guarded by DOMPurify.sanitize(marked.parse(...)).
  • HIGH — cross-tenant disclosure on /api/search and similar polymorphic IDOR findings on /api/links / /api/tags: same defer as Plan 1's polymorphic shape — owner-only API today, no membership model exists yet. Documented in docs/security-followups.md.

Known follow-ups (deferred)

  • pending_changes.action CHECK blocks 'upsert' / 'add_dependency' / 'remove_dependency' from divertToPending in refs/resources routes. Latent — only fires for an agent at suggest tier on those specific endpoints. Three mitigation options documented in docs/security-followups.md.
  • Polymorphic IDOR concerns on entity_links, entity_tags, attachments, and unscoped search — defensible at single-tenant alpha; revisit when membership lands.
  • Universal capture button is a modal stub; capture workers land in Plan 3.
  • Right rail companion is empty; chat lands in Plan 5.
  • Sacred Valley is a placeholder card; widgets port from Void 1.x in Plan 6.

Verification commands

cd /project/src/void-v2
npm test                                                       # 185/185 green
OWNER_TOKEN=void-dev-token PORT=3010 node server.js &
curl -s localhost:3010/health                                  # { db_ok: true, version: '2.0.0-alpha.2' }
# browser: http://localhost:3010/  →  paste OWNER_TOKEN, navigate

What's left after Plan 2

  • Plan 3: capture pipeline (pg-boss, Karakeep webhook, URL/YouTube/PDF/image workers, embeddings).
  • Plan 4: heavy ingest (Whisper, Tesseract OCR) via void-workers Python service.
  • Plan 5: companion chat in right rail.
  • Plan 6: Sacred Valley widgets ported from Void 1.x.