docs: void docs consolidation + wiki restructure plan (executed)
This commit is contained in:
785
docs/superpowers/plans/2026-06-05-void-docs-consolidation.md
Normal file
785
docs/superpowers/plans/2026-06-05-void-docs-consolidation.md
Normal file
@@ -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='<VOID1 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='<WIKI 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.
|
||||
Reference in New Issue
Block a user