duckdb.connect().execute("SELECT COUNT() FROM readparquet('board.parquet')")
The board-migration-duckdb fix is a self-contained migration (/tmp/board-migration-duckdb/migrate.py) addressing all five tick-27 learnings:
L1 — .gitignore must ignore the db (parquet is the committed artifact). The db showed untracked because .gitignore had no entry. Append the ignore lines (idempotent):
IGNORE_LINES = ["board.db", "board.db-wal", "board.db-shm"]
def append_ignore_lines() -> None:
if ignore_lines_present(): # no-op on re-run
return
with open(GITIGNORE, "a") as fh:
fh.write("\n# DuckDB board artifacts (parquet is the committed artifact)\n")
for line in IGNORE_LINES:
fh.write(line + "\n")
L2 — namespace basename bug (home→totalstack) fixed via UPDATE board. The legacy matrix carries home; fleet.toml is authoritative. One statement rewrites the namespace in the migrated table (no per-row re-parse, no re-import):
desired = namespace_from_fleet(rows) # reads fleet.toml, NOT basename(os.getcwd())
con.execute("UPDATE board SET namespace = ? WHERE namespace = 'home'", [desired])
assert con.execute("SELECT COUNT(*) FROM board WHERE namespace = 'home'").fetchone()[0] == 0
L3 — verify with read_parquet, not the db. board.db is a local, gitignored build artifact; board.parquet is what's committed. All post-migration checks read the parquet:
con.execute("COPY board TO 'board.parquet' (FORMAT PARQUET)") # committed artifact
# verification path:
duckdb.connect().execute("SELECT COUNT(*) FROM read_parquet('board.parquet')")
L4 — two-commit pattern for commit-hash backfill. A commit's hash can't be known before it exists, so commit #1 ships rows with NULL hashes; after git rev-parse HEAD yields the hash, commit #2 backfills it into both the db and the parquet artifact:
# commit #1: migrate() -> board.parquet with empty commit_hash
# ... git commit ... ; H1 = git rev-parse HEAD
def backfill_commit_hashes(commit_hash, md, db=DB_PATH, parquet=PARQUET_PATH):
con = duckdb.connect(str(db))
con.execute("UPDATE board SET commit_hash = ? WHERE commit_hash IS NULL OR commit_hash = ''", [commit_hash])
con.execute(f"COPY board TO '{parquet}' (FORMAT PARQUET)")
con.close()
# keep tasks.md in sync so re-migration is idempotent
for r in read_legacy_matrix(md):
if not r["commit_hash"]:
r["commit_hash"] = commit_hash
write_legacy_matrix(md, rows)
Applied in the repo: commit e51eb93 (migration, empty hashes) → backfilled with e51eb938... → commit 020b972.
L5 — fleet.toml pins cooldown_s=900; never PUT despite API showing 43200. The API's reported effective value is not a bug in the migration — fleet.toml is the source of truth. The fix is a guard that refuses to mutate the fleet and reports drift instead:
def sync_fleet_from_toml(api_cooldown_s: int) -> int:
pinned = fleet_cooldown_s() # toml -> 900
assert pinned == 900
if api_cooldown_s != pinned: # e.g. API says 43200
print(f"fleet drift: api={api_cooldown_s}s vs toml={pinned}s — no PUT issued")
return pinned # no HTTP client is even imported
Verified end-to-end in a fresh `git init` repo (DuckDB 1.5.5), via `verify.py` — **15/15 checks pass** (`exit=0`), covering every learning:
- **L1:** `git check-ignore board.db board.db-wal board.db-shm` returns 0; `git status --porcelain` shows no `board.db` after creating one; final `git ls-files` lists `board.parquet` but never `board.db`.
- **L2:** `SELECT DISTINCT namespace FROM board` → only `totalstack`; zero `home` rows remain after the UPDATE.
- **L3:** `read_parquet('board.parquet')` row count == db row count (4 == 4); parquet exists and is non-empty.
- **L4:** phase-1 artifact has 4/4 empty hashes; after backfill 0 empty and all 4 rows carry the commit-1 hash; tasks.md resync matches.
- **L5:** `fleet_cooldown_s() == 900`; `sync_fleet_from_toml(api_cooldown_s=43200)` returns 900 and prints the drift warning; `migrate.py` imports no HTTP client, so a PUT is structurally impossible.
**Edge cases tested:**
1. **NULL vs `''` commit hashes** — both backfilled; a pre-set hash (`'k'`) is left untouched.
2. **Idempotency** — `append_ignore_lines()` and `backfill_commit_hashes()` re-run cleanly (no duplicated ignore lines, no double-write); `migrate()` re-runs via `DROP TABLE IF EXISTS`.
3. **Stale/backfilled repo state** — the L4 suite is hermetic (scratch dir + pristine matrix), so a pre-backfilled checkout cannot produce false failures.
4. **Namespace drift** — UPDATE only rewrites `home` rows; any future fleet-rename still converges to the toml-derived value.
5. **Clean final state** — `git status` empty; the committed artifact self-references its own migration commit (`e51eb938`) via `read_parquet`:
```
[('T-0001','totalstack','e51eb938'), ('T-0002','totalstack','e51eb938'),
('T-0003','totalstack','e51eb938'), ('T-0004','totalstack','e51eb938')]
```
Git log: `d360d0a chore: ignore test scratch dir` → `020b972 tick27: backfill commit hash (commit #2)` → `e51eb93 tick27: migrate tasks.md -> DuckDB board v2.1 (commit #1)`.{"model": "deepseek-v4-flash", "problem_class": "board-migration-duckdb", "result": "passed", "tests": 15}