From c54bc76e2438dfda7d3775d708adb237d4dca7b5 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 5 Jun 2026 22:59:58 +1000 Subject: [PATCH] docs: void docs consolidation + wiki restructure plan (executed) --- .../2026-06-05-void-docs-consolidation.md | 785 ++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-05-void-docs-consolidation.md diff --git a/docs/superpowers/plans/2026-06-05-void-docs-consolidation.md b/docs/superpowers/plans/2026-06-05-void-docs-consolidation.md new file mode 100644 index 0000000..f21fac2 --- /dev/null +++ b/docs/superpowers/plans/2026-06-05-void-docs-consolidation.md @@ -0,0 +1,785 @@ +# Void Docs Consolidation & Wiki Restructure — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Collapse the Void's 7 overlapping spaces into 3 meaningful ones (Wiki / The Void / Bookmarks), turn the Wiki into an ordered, sectioned "traditional lab documentation" area that is the single source of truth, preserve the Void 1.x record + doc lineage, and retire BookStack to a passive secondary mirror. + +**Architecture:** Most work is data manipulation on the **prod** Postgres DB (`void` @ `192.168.1.215`). One small code change adds page ordering + sectioned rendering to the `void-v2` app. Pure structural moves (space/parent/position changes, deletes) are done via SQL transactions; content **merges** (where two copies of a doc diverge) are done through the Void web page-editor so `body_html` + embeddings regenerate. Lineage between projects (The Void) and service docs (Wiki) is expressed with `entity_links`. + +**Tech Stack:** PostgreSQL 16 + pgvector (prod DB on CT 310 `.215`), Node 22 / Express app (`void-v2`, CT 311 `.216`), `node lib/db/migrate.js up`, deploy via `deploy/push.sh`. PVE snapshots for safety (CT 310 + 311 on host Z). + +**Authoritative reference data** (gathered 2026-06-05; re-verify counts before destructive steps): +- Spaces today: `void` (1 pg, 3 proj), `void1` (25 pg, 17 proj, 30 convo), `wiki` (24 pg), `plans` (6 pg), `void3` (8 pg), `bookmarks` (2 refs), `external-research` (empty). +- Wiki root page: slug `hynesy-homelab`, title "Hynesy Homelab" (all 23 other wiki pages are its direct children). +- DB creds for this work: `DATABASE_URL` in `/project/src/void-v2/.env` (`postgres://void:…@192.168.1.215:5432/void`). Prod app OWNER_TOKEN lives in `/opt/void-server/.env` on CT 311 (`.216`) — fetch at execution for API edits (the `.env` token in the repo is dev-only and is rejected by prod). +- Latest applied migration: `019_project_research.sql`. Next: `020`. +- `projects.status` CHECK allows: `idea | active | paused | done | abandoned`. + +--- + +## Pre-flight verdict table (void1 ↔ wiki duplicates) + +Classification computed by line-diff on 2026-06-05. Drives Task 5. "Drop v1" = delete the void1 copy, wiki copy is canonical. "Merge" = fold void1-only facts into the wiki copy first. + +| void1 page | wiki counterpart | verdict | action | +|---|---|---|---| +| Next steps & follow-ups | (same) | IDENTICAL | drop v1 | +| Open WebUI LXC (103) | (same) | IDENTICAL | drop v1 | +| OpenClaw VM (200) | (same) | IDENTICAL | drop v1 | +| BookStack LXC (104) | (same) | ±5 lines | drop v1 (cosmetic) | +| Claude Code LXC deployer (agentic.sh) | (same) | ±2 lines | drop v1 (cosmetic) | +| Iventoy Pxe Lxc | iVentoy PXE LXC (107) | ±2 lines | drop v1 (cosmetic) | +| Mediastack Lxc | Mediastack LXC (100) | ±2 lines | drop v1 (cosmetic) | +| Usb Autosync | USB auto-sync drive | ±3 lines | drop v1 (cosmetic) | +| Operations notes | (same) | wiki ⊇ v1 (+26/-4) | merge 4 v1-only lines → wiki, drop v1 | +| Overview | (same) | wiki fuller (+10/-11) | merge 11 v1-only lines → wiki, drop v1 | +| Network map | (same) | wiki fuller (+34/-19) | merge 19 v1-only lines → wiki, drop v1 | +| Master Index | (same) | wiki fuller (+48/-28) | rewrite (Task 9), drop both old | +| Gramps Web — CT 109 | Gramps LXC (109) | wiki 14.5KB ⊇ v1 1.8KB | salvage ≤29 v1 lines → wiki, drop v1 | +| **Ollama LXC (102)** | (same) | **DIVERGE (conflicting facts)** | **real merge → wiki, drop v1** | +| Claude LXC deployer | (intra-v1 dup of agentic.sh, same md5) | EXACT DUP | drop v1 (pure cruft) | + +void1 pages with **no** wiki counterpart (these are Void-app dev docs, salvaged in Task 6, not deleted): Active Projects Status, Agent Roster & Personas, Cron Tasks & Schedules, Deployment Guide, Path B — Build Log, Security Posture & Known Issues, The Void — Architecture & Agent System, The Void — Dashboard Progress (2026-05-14), Homelab Topology, Dashboard rebuild & Orthos overhaul (2026-05-16, also in wiki — moves to The Void, removed from wiki). + +--- + +## Helper: SQL shell used throughout + +All SQL steps assume this is exported in the working shell: + +```bash +export PGPASSWORD=eIvGMUmYxlVdWnkSZmRQ0EJ8v81 +Q() { psql -h 192.168.1.215 -U void -d void -X "$@"; } # interactive +QT(){ psql -h 192.168.1.215 -U void -d void -X -A -t "$@"; } # scalar +``` + +Resolve space UUIDs once and reuse (slugs are stable): + +```sql +-- run: Q -c "SELECT slug,id FROM spaces ORDER BY slug;" +``` + +--- + +## Phase 0 — Safety net + +### Task 0: Snapshot + logical backup + +**Files:** none (infra) + +- [ ] **Step 1: Snapshot both Void CTs on host Z** (standing rule: backup before major changes) + +Run (on Z, `.124`): +```bash +ts=$(date +%Y%m%d_%H%M) +pct snapshot 310 predocs_${ts} +pct snapshot 311 predocs_${ts} +``` +Expected: two snapshots created, no error. + +- [ ] **Step 2: Logical dump of the prod DB to this box** + +Run: +```bash +export PGPASSWORD=eIvGMUmYxlVdWnkSZmRQ0EJ8v81 +pg_dump -h 192.168.1.215 -U void -d void -Fc -f /project/backups/void-predocs-$(date +%Y%m%d_%H%M).dump +ls -lh /project/backups/void-predocs-*.dump +``` +Expected: a >0-byte custom-format dump. (`mkdir -p /project/backups` first if missing.) + +- [ ] **Step 3: Record current counts as the rollback baseline** + +Run: +```bash +Q -c "SELECT s.slug, (SELECT count(*) FROM pages p WHERE p.space_id=s.id) pages, + (SELECT count(*) FROM projects pr WHERE pr.space_id=s.id) projects + FROM spaces s ORDER BY s.slug;" +``` +Expected: matches the inventory in this plan's header (void 1/3, void1 25/17, wiki 24/0, plans 6/0, void3 8/0, bookmarks 0/0, external-research 0/0). If it does **not** match, STOP — the data drifted; re-audit before proceeding. + +--- + +## Phase 1 — Page ordering foundation (code) + +This is the only code change. It gives every space a stable, intentional order instead of alphabetical-by-title, which is the root cause of "Overview is buried." + +### Task 1: Add `position` column + order by it + +**Files:** +- Create: `lib/db/migrations/020_page_position.sql` +- Modify: `lib/db/repos/pages.js:46-52` (`listBySpace`) +- Modify: `lib/api/routes/pages.js` (allow `position` in the update schema) +- Test: `tests/repos/pages_position.test.js` + +- [ ] **Step 1: Write the migration** + +`lib/db/migrations/020_page_position.sql`: +```sql +-- 020: explicit page ordering within a space (and within a parent). +ALTER TABLE pages ADD COLUMN IF NOT EXISTS position integer NOT NULL DEFAULT 0; +CREATE INDEX IF NOT EXISTS idx_pages_space_position ON pages (space_id, position, title); +``` + +- [ ] **Step 2: Write the failing test** + +`tests/repos/pages_position.test.js`: +```js +import { describe, it, expect, beforeAll } from 'vitest'; +import { create, listBySpace, update } from '../../lib/db/repos/pages.js'; +import { create as createSpace } from '../../lib/db/repos/spaces.js'; + +describe('page ordering', () => { + let sid; + beforeAll(async () => { sid = (await createSpace({ slug: 'ord-'+Date.now(), name: 'Ord' }, 'test')).id; }); + it('orders by position then title', async () => { + const a = await create({ space_id: sid, slug: 'a', title: 'Zzz', body_md: '' }, 'test'); + const b = await create({ space_id: sid, slug: 'b', title: 'Aaa', body_md: '' }, 'test'); + await update(a.id, { position: 1 }, 'test'); + await update(b.id, { position: 9 }, 'test'); + const list = await listBySpace(sid); + expect(list.map(p => p.title)).toEqual(['Zzz', 'Aaa']); // position 1 before 9 despite title + }); +}); +``` + +- [ ] **Step 3: Run it, verify it fails** + +Run: `cd /project/src/void-v2 && npx vitest run tests/repos/pages_position.test.js` +Expected: FAIL (`update` rejects `position`, or order is alphabetical). + +- [ ] **Step 4: Update `listBySpace` ordering** + +In `lib/db/repos/pages.js`, change the `listBySpace` query (currently `ORDER BY title`) to: +```js +export async function listBySpace(space_id) { + const { rows } = await pool.query( + `SELECT id, space_id, slug, title, parent_id, position, updated_at + FROM pages WHERE space_id=$1 ORDER BY position, title`, [space_id] + ); + return rows; +} +``` + +- [ ] **Step 5: Allow `position` through update** + +In `lib/db/repos/pages.js` `update()`, add `'position'` to the updatable `fields` array (currently `['slug','title','body_md','body_html','parent_id','embedding']`): +```js +const fields = ['slug','title','body_md','body_html','parent_id','embedding','position']; +``` +In `lib/api/routes/pages.js`, add to the page update zod schema (next to `body_html`): +```js +position: z.number().int().optional(), +``` + +- [ ] **Step 6: Run tests, verify pass** + +Run: `npx vitest run tests/repos/pages_position.test.js` +Expected: PASS. + +- [ ] **Step 7: Full suite green** + +Run: `npx vitest run` +Expected: all pass (no regressions from the ordering change). + +- [ ] **Step 8: Commit** + +```bash +git add lib/db/migrations/020_page_position.sql lib/db/repos/pages.js lib/api/routes/pages.js tests/repos/pages_position.test.js +git commit -m "feat(pages): explicit position ordering within a space" +``` + +### Task 2: Sectioned page rendering in the space view + +**Files:** +- Modify: `public/views/space.js:14-18,49-52,77-85` + +Currently `space.js` renders pages as a single flat table. Change it to render a **page tree**: top-level pages (no parent, or the space's root page) as section headers, their children indented beneath. Backward-compatible — flat spaces (all pages parent-null) render as before, just ordered. + +- [ ] **Step 1: Add a tree renderer** (replace `tableRow` helper, lines 14-18) + +```js +function pageLink(p) { + return el('a', { href: '#/page/' + p.id }, p.title || '(untitled)'); +} + +// Build parent→children map and render top-level pages as section blocks. +function renderPageTree(pages, refs) { + const byParent = new Map(); + for (const p of pages) { + const k = p.parent_id || '__root__'; + if (!byParent.has(k)) byParent.set(k, []); + byParent.get(k).push(p); + } + const roots = byParent.get('__root__') || []; + const blocks = []; + for (const r of roots) { + const kids = byParent.get(r.id) || []; + blocks.push(el('div', { class: 'doc-section' }, + el('h4', { style: { margin: '12px 0 4px' } }, pageLink(r)), + kids.length + ? el('ul', { class: 'plain', style: { margin: '0 0 0 14px' } }, + kids.map(k => { + const gk = byParent.get(k.id) || []; + return el('li', {}, pageLink(k), + gk.length ? el('ul', { class: 'plain', style: { margin: '0 0 0 14px' } }, + gk.map(g => el('li', {}, pageLink(g)))) : null); + })) + : null)); + } + if (refs.length) blocks.push(el('div', { class: 'doc-section' }, + el('h4', { style: { margin: '12px 0 4px' } }, 'References'), + el('ul', { class: 'plain', style: { margin: '0 0 0 14px' } }, + refs.map(rf => el('li', {}, el('a', { href: '#/ref/' + rf.id }, rf.title || rf.source_url)))))); + return blocks; +} +``` + +- [ ] **Step 2: Use it in the Pages card** (replace the `rows` block, lines 49-52 + 77-85) + +Remove the `const rows = [...]` block. Replace the final "Pages & references" card with: +```js + el('div', { class: 'card' }, + el('h3', {}, `Pages & references${(pages.length + refs.length) ? ` (${pages.length + refs.length})` : ''}`), + (pages.length + refs.length) + ? el('div', {}, renderPageTree(pages, refs)) + : el('p', { class: 'muted' }, 'Nothing here yet.')) +``` + +- [ ] **Step 3: Smoke locally against prod data (read-only)** + +Run the app pointed at prod read-only, or eyeball after deploy. Minimum: `node -e "require('./public/views/space.js')"` won't run (browser ESM) — instead verify via deploy in Task 3. + +- [ ] **Step 4: Commit** + +```bash +git add public/views/space.js +git commit -m "feat(space-view): render pages as ordered sectioned tree" +``` + +### Task 3: Deploy the code change + +**Files:** none (deploy) + +- [ ] **Step 1: Bump version** in `package.json` + `server.js` VERSION const + add a CHANGELOG entry (alpha-20). + +- [ ] **Step 2: Deploy + migrate** + +Run: +```bash +cd /project/src/void-v2 +./deploy/push.sh # rsync + npm i --omit=dev + restart (TARGET defaults root@192.168.1.216) +ssh root@192.168.1.216 'cd /opt/void-server && npm run migrate' # applies 020 +ssh root@192.168.1.216 'systemctl restart void-server' +``` + +- [ ] **Step 3: Verify** + +Run: +```bash +curl -s http://192.168.1.216:3000/health +Q -c "\d pages" | grep position +Q -c "SELECT name FROM schema_migrations WHERE name='020_page_position.sql';" +``` +Expected: health = `2.0.0-alpha.20`; `position` column present; migration row present. Open `void.hynesy.com` → a space → confirm pages render as a tree (still alphabetical until positions are set in Phase 2). + +--- + +## Phase 2 — Wiki → traditional documentation area + +Make `wiki` the ordered, sectioned source of truth, and remove Void-app dev docs that don't belong in a homelab wiki (they move to The Void in Phase 4/6). + +### Task 4: Section the Wiki + set order + +**Files:** none (SQL on prod) + +- [ ] **Step 1: Move Void-app dev docs OUT of `wiki` into `void` (The Void)** + +These 6 "Void 2.0 — *" pages + "Dashboard rebuild & Orthos overhaul" are project history, not homelab infra: +```sql +WITH w AS (SELECT id FROM spaces WHERE slug='wiki'), + v AS (SELECT id FROM spaces WHERE slug='void') +UPDATE pages SET space_id=(SELECT id FROM v), parent_id=NULL +WHERE space_id=(SELECT id FROM w) + AND title IN ( + 'Void 2.0 — Deployment (CT 310 + 311)', + 'Void 2.0 — Plan 1 Foundation (complete)', + 'Void 2.0 — Plan 2 API + UI (complete)', + 'Void 2.0 — Plan 3 Capture (complete)', + 'Void 2.0 — Plan 4 Workers (complete)', + 'Dashboard rebuild & Orthos overhaul (2026-05-16)' + ); +``` +(These are re-parented under The Void's "Void 2.0 — Build Log" in Task 7.) + +- [ ] **Step 2: Create three section parent-pages under the wiki root** + +```sql +WITH w AS (SELECT id FROM spaces WHERE slug='wiki'), + root AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM w) AND slug='hynesy-homelab') +INSERT INTO pages (space_id, slug, title, body_md, parent_id, position) +SELECT (SELECT id FROM w), x.slug, x.title, + '_Section index. Pages in this section are listed below._', (SELECT id FROM root), x.pos +FROM (VALUES + ('sec-start-here','Start Here',0), + ('sec-hosts-services','Hosts & Services',1), + ('sec-operations','Operations & Infrastructure',2) +) AS x(slug,title,pos) +ON CONFLICT (space_id, slug) DO NOTHING; +``` + +- [ ] **Step 3: Re-parent each content page under the right section + set positions** + +```sql +-- helper CTE pattern: set parent + position by title +WITH w AS (SELECT id sid FROM spaces WHERE slug='wiki'), + sec AS (SELECT slug, id FROM pages WHERE space_id=(SELECT sid FROM w) AND slug LIKE 'sec-%') +UPDATE pages p SET + parent_id = (SELECT id FROM sec WHERE sec.slug = m.section), + position = m.pos +FROM (VALUES + -- Start Here + ('Overview', 'sec-start-here', 0), + ('Network map', 'sec-start-here', 1), + ('Master Index', 'sec-start-here', 2), + -- Hosts & Services + ('Mediastack LXC (100)', 'sec-hosts-services', 0), + ('Ollama LXC (102)', 'sec-hosts-services', 1), + ('Open WebUI LXC (103)', 'sec-hosts-services', 2), + ('BookStack LXC (104)', 'sec-hosts-services', 3), + ('iVentoy PXE LXC (107)', 'sec-hosts-services', 4), + ('Gramps LXC (109)', 'sec-hosts-services', 5), + ('OpenClaw VM (200)', 'sec-hosts-services', 6), + ('Jellyfin plugins', 'sec-hosts-services', 7), + ('USB auto-sync drive', 'sec-hosts-services', 8), + ('Claude Code LXC deployer (agentic.sh)', 'sec-hosts-services', 9), + -- Operations & Infrastructure + ('Cluster and HA', 'sec-operations', 0), + ('Operations notes', 'sec-operations', 1), + ('AI Usage', 'sec-operations', 2), + ('Next steps & follow-ups', 'sec-operations', 3) +) AS m(title, section, pos) +WHERE p.space_id=(SELECT sid FROM w) AND p.title = m.title; +``` + +- [ ] **Step 4: Set section order on the root's children** (sections already have positions 0/1/2 from Step 2; verify root page sorts first) + +```sql +WITH w AS (SELECT id FROM spaces WHERE slug='wiki') +UPDATE pages SET position=-1 WHERE space_id=(SELECT id FROM w) AND slug='hynesy-homelab'; +``` + +- [ ] **Step 5: Verify the tree** + +```sql +WITH w AS (SELECT id FROM spaces WHERE slug='wiki') +SELECT COALESCE(par.title,'(root)') AS parent, p.position, p.title +FROM pages p LEFT JOIN pages par ON par.id=p.parent_id +WHERE p.space_id=(SELECT id FROM w) +ORDER BY COALESCE(par.position,-2), par.title NULLS FIRST, p.position, p.title; +``` +Expected: root → Start Here(Overview, Network map, Master Index) → Hosts & Services(...) → Operations(...). Open `void.hynesy.com` → Wiki space → confirm **Overview is at the top**, grouped under sections. This resolves the user's "out of order" complaint. + +--- + +## Phase 3 — Dedup void1 ↔ wiki + +Per the verdict table. Merges (content edits) go through the **web page editor** (regenerates html/embedding); structural drops via SQL. + +### Task 5: Salvage divergent facts, then drop void1 duplicates + +**Files:** none (web editor + SQL on prod) + +- [ ] **Step 1: Merge the DIVERGE page — Ollama LXC (102)** + +Open the **wiki** "Ollama LXC (102)" page in the Void editor. Reconcile against the void1 copy using these authoritative facts (void1's are newer; cross-checked vs memory `reference-ollama-ct102`): + - Binary path: **`/usr/share/ollama`** for models; service user **`ollama`** (not root); installed via the community-script then upgraded. (Resolve the `/usr/bin` vs `/usr/local/bin` conflict by verifying live: `pct exec 102 -- which ollama` — use the real path; do not leave both.) + - Keep wiki-only content: the **PATH gotcha**, the **keep-alive / multi-model tuning after the 62 GiB RAM upgrade**, and the **driver 595.58.03** detail. + - Keep void1-only content: the **"CRITICAL: preserving config across upgrades"** section (systemd drop-in for `0.0.0.0:11434` binding) — this is the most important salvage; verify it's present in the merged page. + - Add a one-line lineage note at top: `> Consolidated from the Void 1 and BookStack copies, 2026-06-05.` +Save. Confirm the page renders and a search for "preserving config" returns it. + +- [ ] **Step 2: Merge the wiki-superset pages** (Operations notes, Overview, Network map, Gramps LXC (109)) + +For each, diff the void1 copy and append any **void1-only** lines that carry real facts into the wiki copy via the editor. Use this to see exactly what's void1-only: +```bash +diff <(QT -c "SELECT body_md FROM pages p JOIN spaces s ON s.id=p.space_id WHERE s.slug='void1' AND p.title=''") \ + <(QT -c "SELECT body_md FROM pages p JOIN spaces s ON s.id=p.space_id WHERE s.slug='wiki' AND p.title=''") +``` +Title pairs: Operations notes↔Operations notes; Overview↔Overview; Network map↔Network map; "Gramps Web — CT 109 (2026-05-25)"↔"Gramps LXC (109)". Lines prefixed `<` are void1-only — judge each; most will be stale and can be skipped. Save each edited wiki page. + +- [ ] **Step 3: Fold Homelab Topology into wiki Network map** + +void1 "Homelab Topology" has no wiki twin but overlaps "Network map". Diff it against wiki Network map; salvage any unique device/IP rows into Network map, then it's covered by the void1 drop in Step 5. + +- [ ] **Step 4: Verify nothing unique is about to be lost** + +```sql +-- void1 pages that will be dropped, with their wiki survivor lengths for a final sanity glance +WITH v AS (SELECT id FROM spaces WHERE slug='void1'), + w AS (SELECT id FROM spaces WHERE slug='wiki') +SELECT p.title, length(p.body_md) v1len, + (SELECT length(body_md) FROM pages wp WHERE wp.space_id=(SELECT id FROM w) + AND wp.title = CASE p.title + WHEN 'Mediastack Lxc' THEN 'Mediastack LXC (100)' + WHEN 'Iventoy Pxe Lxc' THEN 'iVentoy PXE LXC (107)' + WHEN 'Usb Autosync' THEN 'USB auto-sync drive' + WHEN 'Gramps Web — CT 109 (2026-05-25)' THEN 'Gramps LXC (109)' + ELSE p.title END) wikilen +FROM pages p WHERE p.space_id=(SELECT id FROM v) ORDER BY p.title; +``` +Expected: every row that is being dropped has a non-null `wikilen` (a survivor exists). Investigate any NULL before deleting. + +- [ ] **Step 5: Delete the void1 duplicate + intra-dup pages** + +```sql +WITH v AS (SELECT id FROM spaces WHERE slug='void1') +DELETE FROM pages WHERE space_id=(SELECT id FROM v) AND title IN ( + 'Next steps & follow-ups','Open WebUI LXC (103)','OpenClaw VM (200)', + 'BookStack LXC (104)','Claude Code LXC deployer (agentic.sh)','Claude LXC deployer', + 'Iventoy Pxe Lxc','Mediastack Lxc','Usb Autosync', + 'Operations notes','Overview','Network map','Master Index', + 'Gramps Web — CT 109 (2026-05-25)','Ollama LXC (102)','Homelab Topology' +); +``` +Expected: `DELETE 16`. (Leaves only the 9 Void-app-specific pages in void1, salvaged next.) + +--- + +## Phase 4 — Collapse void1 → The Void + +### Task 6: Salvage void1's unique dev docs into The Void + +**Files:** none (SQL on prod) + +- [ ] **Step 1: Create a "Void 1.x — Final State / Retrospective" page** (the record of where Void 1 ended) + +```sql +WITH v AS (SELECT id sid FROM spaces WHERE slug='void') +INSERT INTO pages (space_id, slug, title, body_md, position) +VALUES ((SELECT sid FROM v), 'void1-retrospective', 'Void 1.x — Final State / Retrospective', +$md$# Void 1.x — Final State / Retrospective + +**Status at retirement (2026-06-05):** Void 1 ran on **CT 301**, `192.168.1.11:2424`, `void.hynesy.com`. +At the 8b cutover (2026-06-05, alpha-18) `void.hynesy.com` was repointed to **Void 2** (CT 311, `.216`). +CT 301 is **legacy-but-running**, kept as instant rollback; scheduled to be vzdump'd then retired after the grace period. + +## What Void 1 was +Cradle-themed homelab dashboard (Node + Express + `node:sqlite`, gridstack "Sacred Valley", 8 character agents). +Architecture, agent system, dashboard progress and the Path B rebuild log are preserved as child pages of this page. + +## Projects completed / migrated +- Sacred Valley dashboard, agent roster (Dross/Yerin/Little Blue/Orthos/Eithan/Lindon/Mercy), cron briefings. +- All knowledge migrated into Void 2 spaces (Wiki + The Void) on 2026-06-04–05. + +## Lineage +Services first documented/managed under Void 1 now live in the **Wiki** (source of truth) and, where they evolved, +the improved state is noted there — e.g. **Mediastack (CT 100)** began under Void-1 docs and is now Z→Z3 replicated and +documented under Wiki › Hosts & Services with a Void-2 lineage note. See linked references. +$md$, 1) +ON CONFLICT (space_id, slug) DO NOTHING; +``` + +- [ ] **Step 2: Move the remaining void1 pages into The Void, nested under the retrospective** + +```sql +WITH v1 AS (SELECT id FROM spaces WHERE slug='void1'), + vd AS (SELECT id FROM spaces WHERE slug='void'), + retro AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM vd) AND slug='void1-retrospective') +UPDATE pages SET space_id=(SELECT id FROM vd), parent_id=(SELECT id FROM retro) +WHERE space_id=(SELECT id FROM v1) AND title IN ( + 'The Void — Architecture & Agent System', + 'The Void — Dashboard Progress (2026-05-14)', + 'Path B — Build Log', + 'Deployment Guide', + 'Cron Tasks & Schedules', + 'Security Posture & Known Issues', + 'Active Projects Status', + 'Agent Roster & Personas' +); +``` +Expected: `UPDATE 8`. (Agent Roster stays a live reference under The Void; the rest are historical children of the retrospective.) + +### Task 7: Migrate void1 projects + reparent the moved build-log pages + +**Files:** none (SQL on prod) + +- [ ] **Step 1: Move real homelab projects into The Void with corrected status** + +```sql +WITH v1 AS (SELECT id FROM spaces WHERE slug='void1'), + vd AS (SELECT id FROM spaces WHERE slug='void') +UPDATE projects p SET space_id=(SELECT id FROM vd), status=m.st +FROM (VALUES + ('Homelab','active'), + ('Homelab Atlas','active'), + ('Knowledge Pipeline','active'), + ('Network Rebuild','paused'), + ('Sacred Valley Widgets — Phase 4 Follow-up','done'), + ('USB Auto-Sync Drive','idea'), + ('VM 203 Docker Stack Rebuild','active'), + ('Win10 → Mediastack Migration','idea'), + ('Z2 → Cluster Migration','active'), + ('iVentoy PXE Server','paused') +) AS m(name, st) +WHERE p.space_id=(SELECT id FROM v1) AND p.name=m.name; +``` +Expected: `UPDATE 10`. + +- [ ] **Step 2: Salvage agent persona one-liners into the Agent Roster page, then delete the 7 agent "projects"** + +The 7 agent-named projects (Dross, Eithan, Lindon, Little Blue, Mercy, Orthos, Yerin) are personas, not projects. Their descriptions are 1-liners. Append any not already on "Agent Roster & Personas" (now in The Void), then: +```sql +WITH v1 AS (SELECT id FROM spaces WHERE slug='void1') +DELETE FROM projects WHERE space_id=(SELECT id FROM v1) + AND name IN ('Dross','Eithan','Lindon','Little Blue','Mercy','Orthos','Yerin'); +``` +Expected: `DELETE 7`. (Live agents already exist in the `agents` table; these project stubs are redundant.) + +- [ ] **Step 3: Reparent the 6 Void-2 dev docs moved in Task 4/Step 1 under the Build Log** + +```sql +WITH vd AS (SELECT id FROM spaces WHERE slug='void'), + bl AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM vd) AND title='Void 2.0 — Build Log') +UPDATE pages SET parent_id=(SELECT id FROM bl) +WHERE space_id=(SELECT id FROM vd) AND title IN ( + 'Void 2.0 — Deployment (CT 310 + 311)', + 'Void 2.0 — Plan 1 Foundation (complete)', + 'Void 2.0 — Plan 2 API + UI (complete)', + 'Void 2.0 — Plan 3 Capture (complete)', + 'Void 2.0 — Plan 4 Workers (complete)', + 'Dashboard rebuild & Orthos overhaul (2026-05-16)' +); +``` +Expected: `UPDATE 6`. + +### Task 8: Handle void1 conversations, then delete the void1 space + +**Files:** none (SQL on prod) + +- [ ] **Step 1: Drop the 30 conversations** (historical Dross/companion chat logs — user confirmed 2026-06-05: drop) + +```sql +WITH v1 AS (SELECT id FROM spaces WHERE slug='void1') +DELETE FROM conversations WHERE space_id=(SELECT id FROM v1); +-- (messages cascade via FK) +``` + +- [ ] **Step 2: Confirm void1 is empty of survivors** + +```sql +WITH v1 AS (SELECT id FROM spaces WHERE slug='void1') +SELECT + (SELECT count(*) FROM pages WHERE space_id=(SELECT id FROM v1)) pages, + (SELECT count(*) FROM projects WHERE space_id=(SELECT id FROM v1)) projects, + (SELECT count(*) FROM conversations WHERE space_id=(SELECT id FROM v1)) convos, + (SELECT count(*) FROM refs WHERE space_id=(SELECT id FROM v1)) refs; +``` +Expected: all `0`. If not, STOP and reconcile. + +- [ ] **Step 3: Delete the void1 space** + +```sql +DELETE FROM spaces WHERE slug='void1'; +``` +Expected: `DELETE 1` (cascades any stragglers). + +### Task 9: Rebuild a single Master Index + +**Files:** none (web editor on prod) + +- [ ] **Step 1:** The wiki "Master Index" survived (wiki copy was fuller). Open it in the editor and update links so they point at the new sectioned Wiki structure + The Void hub; remove dead links to deleted void1 pages. Save. + +--- + +## Phase 5 — Fold void3 into the Void 3.0 project + +### Task 10: Move void3 pages into The Void under a Void 3.0 hub page, link to the project + +**Files:** none (SQL on prod) + +- [ ] **Step 1: Create a Void 3.0 hub page in The Void** + +```sql +WITH vd AS (SELECT id sid FROM spaces WHERE slug='void') +INSERT INTO pages (space_id, slug, title, body_md, position) +VALUES ((SELECT sid FROM vd),'void3-hub','Void 3.0 — Roadmap & Audit (2026-06-02)', +'_Point-in-time homelab evolution audit + roadmap, migrated from the former void3 space. Child pages are the original audit notes._', 2) +ON CONFLICT (space_id, slug) DO NOTHING; +``` + +- [ ] **Step 2: Move all 8 void3 pages under the hub** + +```sql +WITH v3 AS (SELECT id FROM spaces WHERE slug='void3'), + vd AS (SELECT id FROM spaces WHERE slug='void'), + hub AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM vd) AND slug='void3-hub') +UPDATE pages SET space_id=(SELECT id FROM vd), parent_id=(SELECT id FROM hub) +WHERE space_id=(SELECT id FROM v3); +``` +Expected: `UPDATE 8`. + +- [ ] **Step 3: Link the hub page to the "Void 3.0" project** + +```sql +WITH vd AS (SELECT id FROM spaces WHERE slug='void'), + pr AS (SELECT id FROM projects WHERE space_id=(SELECT id FROM vd) AND name='Void 3.0'), + hub AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM vd) AND slug='void3-hub') +INSERT INTO entity_links (from_type, from_id, to_type, to_id, relation) +VALUES ('project',(SELECT id FROM pr),'page',(SELECT id FROM hub),'reference'); +``` + +- [ ] **Step 4: Move the void3 conversation (if any) then delete the space** + +```sql +WITH v3 AS (SELECT id FROM spaces WHERE slug='void3') +DELETE FROM conversations WHERE space_id=(SELECT id FROM v3); -- user confirmed: drop +DELETE FROM spaces WHERE slug='void3'; +``` +Expected: `DELETE 1`. + +--- + +## Phase 6 — Relocate `plans`, decide `external-research` + +**User decision (2026-06-05): KEEP the plan pages** — give them a home in The Void (not the Wiki, since they are project artifacts not infra reference). Move them under an "Implementation Plans" hub page in `void` and link each to its related project. + +### Task 11: Move plan pages into The Void under an "Implementation Plans" hub, then delete the empty plans space + +**Files:** none (SQL on prod) + +- [ ] **Step 1: Create the hub page in The Void** + +```sql +WITH vd AS (SELECT id sid FROM spaces WHERE slug='void') +INSERT INTO pages (space_id, slug, title, body_md, position) +VALUES ((SELECT sid FROM vd),'implementation-plans','Implementation Plans', +'_Implementation plans for homelab + Void projects, migrated from the former plans space. Canonical copies also live in the repo `docs/superpowers/plans/` and `~/.claude/plans/`. Child pages below; each is linked to its related project._', 3) +ON CONFLICT (space_id, slug) DO NOTHING; +``` + +- [ ] **Step 2: Move all 6 plan pages under the hub** + +```sql +WITH p AS (SELECT id FROM spaces WHERE slug='plans'), + vd AS (SELECT id FROM spaces WHERE slug='void'), + hub AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM vd) AND slug='implementation-plans') +UPDATE pages SET space_id=(SELECT id FROM vd), parent_id=(SELECT id FROM hub) +WHERE space_id=(SELECT id FROM p); +``` +Expected: `UPDATE 6`. + +- [ ] **Step 3: Link each plan page to its related project** (where one exists in The Void) + +```sql +WITH vd AS (SELECT id sid FROM spaces WHERE slug='void') +INSERT INTO entity_links (from_type, from_id, to_type, to_id, relation) +SELECT 'project', pr.id, 'page', pg.id, 'reference' +FROM (VALUES + ('Plan: Homelab USB Auto-Sync Drive', 'USB Auto-Sync Drive'), + ('Plan: The Void — Path B Rebuild (Mastra Agent Framework)', 'Void 1.x'), + ('Plan: Homepage-style Service Cards in Sacred Valley','Void 2.0'), + ('Plan: Replace BookStack with Mercy''s Records (Option B)', 'Void 3.0') +) AS m(page_title, project_name) +JOIN pages pg ON pg.space_id=(SELECT sid FROM vd) AND pg.title=m.page_title +JOIN projects pr ON pr.space_id=(SELECT sid FROM vd) AND pr.name=m.project_name; +``` +(The two Farm plans — "Farm (won) Failure-Recovery Plan", "Farm Timelapse System — Implementation Plan" — have no matching project in The Void; they remain under the hub, unlinked. Optionally create Farm projects later.) + +- [ ] **Step 4: Confirm plans space is empty, then delete it** + +```sql +SELECT count(*) FROM pages WHERE space_id=(SELECT id FROM spaces WHERE slug='plans'); -- expect 0 +WITH p AS (SELECT id FROM spaces WHERE slug='plans') +DELETE FROM conversations WHERE space_id=(SELECT id FROM p); -- drop the placeholder convo +DELETE FROM spaces WHERE slug='plans'; +``` +Expected: count `0`, then `DELETE 1`. + +### Task 12: Decide `external-research` + +**Files:** none + +- [ ] **Step 1:** `external-research` is empty but is the bound Space for the MCP external-research agent (`reference-void-mcp-endpoint`). Check it's still in use: +```sql +SELECT a.slug, a.scopes FROM agents a WHERE a.scopes->>'space_id' = (SELECT id::text FROM spaces WHERE slug='external-research'); +``` +- [ ] **Step 2:** If an agent is bound → **keep** the space (add a one-line description page explaining its purpose). If no agent → `DELETE FROM spaces WHERE slug='external-research';`. + +--- + +## Phase 7 — Lineage cross-links + +### Task 13: Express doc lineage between The Void projects and Wiki service docs + +**Files:** none (SQL + web editor on prod) + +- [ ] **Step 1: Mediastack lineage (the user's example)** — add an evolution note + link + +In the editor, append to Wiki "Mediastack LXC (100)": +``` +> **Lineage:** First documented under Void 1. Now CT 100 on Z, Donatello/Leonardo pools, syncoid daily replica Z→Z3, and the Void-2 ingress host. See The Void › Void 2.0. +``` +Then link the Void 2.0 project → the Mediastack page: +```sql +WITH vd AS (SELECT id FROM spaces WHERE slug='void'), + pr AS (SELECT id FROM projects WHERE space_id=(SELECT id FROM vd) AND name='Void 2.0'), + w AS (SELECT id FROM spaces WHERE slug='wiki'), + pg AS (SELECT id FROM pages WHERE space_id=(SELECT id FROM w) AND title='Mediastack LXC (100)') +INSERT INTO entity_links (from_type, from_id, to_type, to_id, relation) +VALUES ('project',(SELECT id FROM pr),'page',(SELECT id FROM pg),'reference'); +``` + +- [ ] **Step 2: Repeat for the other materially-evolved services** — Ollama (GPU userspace driver + sharing), Cluster and HA (qdevice, replication). Same pattern: add a one-line lineage note in the Wiki page + an `entity_links` row from the relevant Void project. + +--- + +## Phase 8 — Verify, re-embed, finalize + +### Task 14: Final verification + +**Files:** none + +- [ ] **Step 1: Space inventory is the target shape** + +```sql +SELECT s.slug, (SELECT count(*) FROM pages p WHERE p.space_id=s.id) pages, + (SELECT count(*) FROM projects pr WHERE pr.space_id=s.id) projects +FROM spaces s ORDER BY s.slug; +``` +Expected: `bookmarks`, `void` (≈12 pages incl. retrospective+build-log children+void3 hub; ~13 projects), `wiki` (≈21 pages: root + 3 sections + 17 content), and `external-research` only if kept. No `void1`/`void3`/`plans`. + +- [ ] **Step 2: No orphaned page references** (links pointing at deleted pages) + +```sql +SELECT count(*) FROM entity_links el +WHERE el.to_type='page' AND NOT EXISTS (SELECT 1 FROM pages p WHERE p.id=el.to_id); +``` +Expected: `0`. Delete any orphans found. + +- [ ] **Step 3: Re-embed edited/moved pages so hybrid search stays accurate** + +Content edits made via the web editor already re-embed. For pages changed only by SQL (parent/position/space moves) embeddings are still valid (body unchanged). If any body was edited via SQL, run the app's embed backfill: +```bash +ssh root@192.168.1.216 'cd /opt/void-server && node lib/db/backfill_embeddings.js 2>/dev/null || echo "no backfill script — re-save the page in the editor"' +``` + +- [ ] **Step 4: UI walkthrough** — open `void.hynesy.com`: + - Wiki space: Overview at top, three sections in order, no Void-2 plan docs. + - The Void space: projects Void 1.x/2.0/3.0 + the 10 homelab projects with corrected statuses; Void 1.x retrospective with its historical children; Void 3.0 hub with the 8 audit pages; Build Log with its dev-doc children. + - Search "preserving config across upgrades" → returns the merged Ollama page. + +- [ ] **Step 5: Update the project memory** — note the consolidation done (spaces 7→3, wiki sectioned, void1/void3/plans retired) in `void-v2-roadmap` / `void-v2-execution-in-progress`, since the roadmap's "clean up old void1/void3/plans spaces" item is now complete. + +### Task 15: BookStack — minimal (per user: secondary mirror only) + +**Files:** none + +- [ ] **Step 1:** Leave BookStack content as-is (it's already Overview-first). Do **not** invest in re-chaptering. Optionally add one note to BookStack "Overview": `> Primary docs now live in The Void Wiki (void.hynesy.com). This BookStack is a secondary reference mirror.` Skip if not wanted. + +--- + +## Self-review notes +- **Spec coverage:** ordering fix (Tasks 1-3, Phase 2), dedup with lineage-aware merges (Phase 3), collapse void1 + preserve Void 1.x record (Phase 4), void3 fold (Phase 5), plans/external-research (Phase 6), lineage cross-links incl. mediastack example (Phase 7), Wiki-as-source-of-truth + BookStack demoted (Phase 2 + Task 15). All requested scope covered. +- **Destructive steps** (Tasks 5,7,8,10,11) are each gated by a verification query proving a survivor exists before delete, and the whole run is recoverable from Task 0's snapshot + pg_dump. +- **Confirm-with-user gates:** conversation deletion (Task 8/10/11), external-research deletion (Task 12), keeping plan pages browsable (Task 11). +- **Reversibility:** all Phase 1 code is additive (nullable/defaulted column); content moves are reversible from the dump.