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>
This commit is contained in:
88
docs/plan-2-complete.md
Normal file
88
docs/plan-2-complete.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Plan 2 — Complete
|
||||
|
||||
**Date:** 2026-06-01
|
||||
**Version:** 2.0.0-alpha.2
|
||||
**Tests:** 185 passing across 43 files
|
||||
**Commits on `main`:** 10 (`5aa6fe7` … `8ae9bce`)
|
||||
|
||||
## Scope delivered
|
||||
|
||||
### Phase A — REST routes (T1–T8)
|
||||
- `/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 (T9–T11)
|
||||
- `agentOrOwner` bearer middleware replaces the old owner-only `/api` gate.
|
||||
- `requireWrite` middleware tiers writes through `canAct`: `allow` → through, `suggest` → `divertToPending` (202 + pending row), else 403.
|
||||
- `/api/agents` (owner-only) + agent token mint (plaintext returned once) + revoke.
|
||||
|
||||
### Phase C — Cross-cutting (T12–T15)
|
||||
- `/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 (T17–T22)
|
||||
- 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
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in New Issue
Block a user