git rm --cached --ignore-unmatch board.db '.parquet'
Design decision (per Bane directive 2026-08-07): the four tables (board, events, tasks, fixtures) are exported to .jsonl files that are tracked and authoritative; board.db/*.parquet become a local cache that any consumer can rebuild with read_json_auto. Full runnable implementation: ~/board-migration-demo/migrate_board.py (fixture generator: build_board.py).
Step 1 — duckdb COPY export (deterministic, ORDER BY id):
COPY (SELECT * FROM board ORDER BY id) TO 'board.jsonl' (FORMAT JSON);
COPY (SELECT * FROM events ORDER BY id) TO 'events.jsonl' (FORMAT JSON);
COPY (SELECT * FROM tasks ORDER BY id) TO 'tasks.jsonl' (FORMAT JSON);
COPY (SELECT * FROM fixtures ORDER BY id) TO 'fixtures.jsonl' (FORMAT JSON);
FORMAT JSON produces compact NDJSON (one record/line — ideal for line-based git diffs). --pretty re-emits records via json.dumps(indent=2); both are readable by read_json_auto (auto-detect handles NDJSON, JSON arrays, and multi-line pretty objects — verified).
Step 2 — .gitignore so the cache can never sneak back into git:
# Bane directive 2026-08-07: boards are canonical JSONL.
board.db
*.parquet
*.duckdb
.board-cache/
__pycache__/
Step 3 — untrack legacy binaries:
git rm --cached --ignore-unmatch board.db '*.parquet'
Step 4 — parity probe (count + max-id must MATCH). Empty-table safe: COUNT(*) binds no columns, so a 0-byte JSONL still returns (0,0); the full MAX(id) bind is attempted first and falls back to COUNT(*) on BinderException:
(n_src, m_src) = con.execute(f"SELECT COUNT(*), COALESCE(MAX(id),0) FROM {t}").fetchone()
try:
(n_json, m_json) = con.execute(
f"SELECT COUNT(*), COALESCE(MAX(id),0) FROM read_json_auto('{t}.jsonl')").fetchone()
except duckdb.BinderException: # empty table -> column-less JSONL
n_json = con.execute(f"SELECT COUNT(*) FROM read_json_auto('{t}.jsonl')").fetchone()[0]
m_json = 0
match = (n_src == n_json) and (m_src == m_json)
Step 5 — commit with co-author trailer (--coauthor "Name <email>", or BOARD_COAUTHOR/git config):
migrate board to canonical JSONL (helios tick 186)
board/events/tasks/fixtures exported via duckdb COPY (FORMAT JSON);
board.db and *.parquet dropped from tracking. JSONL is tracked and
authoritative; duckdb is a local cache rebuilt with read_json_auto.
Co-authored-by: Bane <<email>>
Consumer rebuild (fresh clone, JSONL only): CREATE TABLE board AS SELECT * FROM read_json_auto('board.jsonl').
Verified in `~/board-migration-demo` (duckdb 1.5.5, git repo) against a real `board.db` containing nested `JSON` columns (`meta.settings.columns`), `TIMESTAMP`, `DATE`, and `VARCHAR`: - **Migration run:** all 4 tables exported; `git rm --cached board.db '*.parquet'` removed both legacy files; final `git ls-files` shows only `.gitignore`, `*.jsonl`, and sources; `git status` clean; `git log` shows the trailer `Co-authored-by: Bane <<email>>` on the migration commit. - **Parity probe:** `count 3/5/5/4` and `max_id 3/5/5/4` → **MATCH** on every run (initial, idempotent rerun, `--check-only`, pretty-printed, and restore runs — 24 probe assertions). - **read_json_auto format matrix:** compact NDJSON ✓, pretty JSON array (`ARRAY true`) ✓, pretty multi-line JSONL (auto-detect) ✓; explicit `format='newline_delimited'` on pretty multi-line objects correctly fails (expected — that's why auto-detect is the probe default). - **Edge cases:** empty table → 0-byte JSONL → probe still `MATCH (0 vs 0)` via `COUNT(*)` fallback; nested JSON values survive round-trip (`SELECT meta.settings.columns ... WHERE id=1` → `(3,)`); type fidelity confirmed (`updated_at` re-inferred as `TIMESTAMP`). - **Clone simulation:** fresh `git clone` has no `board.db`; local cache rebuilt from JSONL gives 3 boards / 5 tasks / 5 events / 4 fixtures; `git status` stays clean afterward (cache is ignored).
{"model": "deepseek-v4-flash", "problem_class": "duckdb-board-jsonl-canonical-migration", "result": "passed", "tests": 38}Design decision (per Bane directive 2026-08-07): the four tables (board, events, tasks, fixtures) are exported to .jsonl files that are tracked and authoritative; board.db/*.parquet become a local cache that any consumer can rebuild with read_json_auto. Full runnable implementation: ~/board-migration-demo/migrate_board.py (fixture generator: build_board.py).
Step 1 — duckdb COPY export (deterministic, ORDER BY id):
COPY (SELECT * FROM board ORDER BY id) TO 'board.jsonl' (FORMAT JSON);
COPY (SELECT * FROM events ORDER BY id) TO 'events.jsonl' (FORMAT JSON);
COPY (SELECT * FROM tasks ORDER BY id) TO 'tasks.jsonl' (FORMAT JSON);
COPY (SELECT * FROM fixtures ORDER BY id) TO 'fixtures.jsonl' (FORMAT JSON);
FORMAT JSON produces compact NDJSON (one record/line — ideal for line-based git diffs). --pretty re-emits records via json.dumps(indent=2); both are readable by read_json_auto (auto-detect handles NDJSON, JSON arrays, and multi-line pretty objects — verified).
Step 2 — .gitignore so the cache can never sneak back into git:
# Bane directive 2026-08-07: boards are canonical JSONL.
board.db
*.parquet
*.duckdb
.board-cache/
__pycache__/
Step 3 — untrack legacy binaries:
git rm --cached --ignore-unmatch board.db '*.parquet'
Step 4 — parity probe (count + max-id must MATCH). Empty-table safe: COUNT(*) binds no columns, so a 0-byte JSONL still returns (0,0); the full MAX(id) bind is attempted first and falls back to COUNT(*) on BinderException:
(n_src, m_src) = con.execute(f"SELECT COUNT(*), COALESCE(MAX(id),0) FROM {t}").fetchone()
try:
(n_json, m_json) = con.execute(
f"SELECT COUNT(*), COALESCE(MAX(id),0) FROM read_json_auto('{t}.jsonl')").fetchone()
except duckdb.BinderException: # empty table -> column-less JSONL
n_json = con.execute(f"SELECT COUNT(*) FROM read_json_auto('{t}.jsonl')").fetchone()[0]
m_json = 0
match = (n_src == n_json) and (m_src == m_json)
Step 5 — commit with co-author trailer (--coauthor "Name <email>", or BOARD_COAUTHOR/git config):
migrate board to canonical JSONL (helios tick 186)
board/events/tasks/fixtures exported via duckdb COPY (FORMAT JSON);
board.db and *.parquet dropped from tracking. JSONL is tracked and
authoritative; duckdb is a local cache rebuilt with read_json_auto.
Co-authored-by: Bane <<email>>
Consumer rebuild (fresh clone, JSONL only): CREATE TABLE board AS SELECT * FROM read_json_auto('board.jsonl').
Verified in `~/board-migration-demo` (duckdb 1.5.5, git repo) against a real `board.db` containing nested `JSON` columns (`meta.settings.columns`), `TIMESTAMP`, `DATE`, and `VARCHAR`: - **Migration run:** all 4 tables exported; `git rm --cached board.db '*.parquet'` removed both legacy files; final `git ls-files` shows only `.gitignore`, `*.jsonl`, and sources; `git status` clean; `git log` shows the trailer `Co-authored-by: Bane <<email>>` on the migration commit. - **Parity probe:** `count 3/5/5/4` and `max_id 3/5/5/4` → **MATCH** on every run (initial, idempotent rerun, `--check-only`, pretty-printed, and restore runs — 24 probe assertions). - **read_json_auto format matrix:** compact NDJSON ✓, pretty JSON array (`ARRAY true`) ✓, pretty multi-line JSONL (auto-detect) ✓; explicit `format='newline_delimited'` on pretty multi-line objects correctly fails (expected — that's why auto-detect is the probe default). - **Edge cases:** empty table → 0-byte JSONL → probe still `MATCH (0 vs 0)` via `COUNT(*)` fallback; nested JSON values survive round-trip (`SELECT meta.settings.columns ... WHERE id=1` → `(3,)`); type fidelity confirmed (`updated_at` re-inferred as `TIMESTAMP`). - **Clone simulation:** fresh `git clone` has no `board.db`; local cache rebuilt from JSONL gives 3 boards / 5 tasks / 5 events / 4 fixtures; `git status` stays clean afterward (cache is ignored).
{"model": "deepseek-v4-flash", "problem_class": "duckdb-board-jsonl-canonical-migration", "result": "passed", "tests": 38}