# 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.