go-cli-jsonl-topology-b-writes
Done. The verified solution is written to ~/solution.md (self-contained markdown: title, root-cause analysis, exact per-file fix, verification).
Diagnosis. boardctl (at github.com/coding-hermes/boardctl) auto-detects two topologies — A (header in board.jsonl) and B (header is line 1 of tasks.jsonl, no board.jsonl). Reads worked on B, but every mutating path treated B as read-only: hard gates in Create/UpdateTask/AppendEvent/SetHeader, plus a stale migration message in the header subcommand, plus validate/doctor downgrading B's header-counter checks to "not checked" warns (and counting the header as a task row). The missing abstraction: "which file carries the header" (A → board.jsonl, B → tasks.jsonl); HeaderRow() returned nil on B and SetHeader/validateHeader dereferenced an empty path.
Fix. Added headerPathFor() + skipTaskLine() (+ rowIsHeaderShape()) in board.go; removed the 4 write gates in write.go (SetHeader now rewrites only line 1 of tasks.jsonl on B with a whole-file byte-identical untouched-line assert before the atomic rewrite); added topology-aware TaskRows() used by list/show/stats/doctor; routed validateHeader/doctorHeaderVsEvents through headerPathFor() in both topologies (with the "extra lines past header" shape check kept topology-A-only); fixed header subcommand wording; init still refuses B but with writable-era wording (fresh boards only, migration optional); README/comments updated.
Verification (all run live, Go 1.26):
- Pre-fix source + BT-010 tests: go test fails (b.TaskRows undefined; 4 CLI tests fail). The installed buggy binary: create/header exit 1 with the stale message; validate shows header counters not checked (exit 0 even for junk).
- Fixed tree: go test -count=1 ./... → ok both packages, 71 test functions pass (16 new topology-B/BT-010 tests), go vet clean.
- Fixed binary full workflow on one shared topology-B board: create → header read/set → event → update --commit-hash → validate/doctor all exit 0; header counters are really checked (ticks_total -5 fails validate, tick drift fails doctor); tasks.jsonl final state shows only line 1 rewritten by header/commit-hash updates — all other lines byte-identical.
# Evidence - Problem class: go-cli-jsonl-topology-b-writes - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-09-04T23:16:33.401Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "boardctl: topology-B boards (header = line 1 of tasks.jsonl) were read-only in practice \u2014 4 write gates (Create/UpdateTask/AppendEvent/SetHeader) + header subcommand rejected them with a stale migration message. Fix: headerPathFor() helper (A\u2192board.jsonl, B\u2192tasksPath), skipTaskLine() so Create mirrors last TASK row schema not the header, UpdateTask preserves line 1, AppendEvent was headerless anyway (gate removed), header subcommand reads/writes line-1 header on B, validate/doctor upgraded from warn to real header-counter checks, README: both topologies writable, migration optional. Byte-identical untouched-line assert for the shared-file rewrite is the critical correctness invariant. 20 tests, judge PASS.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-cli-jsonl-topology-b-writes", "provider": "openrouter", "solved_at": "2026-09-04T23:16:33.402Z", "version": ""}Done. The verified solution is written to ~/solution.md (self-contained markdown: title, root-cause analysis, exact per-file fix, verification).
Diagnosis. boardctl (at github.com/coding-hermes/boardctl) auto-detects two topologies — A (header in board.jsonl) and B (header is line 1 of tasks.jsonl, no board.jsonl). Reads worked on B, but every mutating path treated B as read-only: hard gates in Create/UpdateTask/AppendEvent/SetHeader, plus a stale migration message in the header subcommand, plus validate/doctor downgrading B's header-counter checks to "not checked" warns (and counting the header as a task row). The missing abstraction: "which file carries the header" (A → board.jsonl, B → tasks.jsonl); HeaderRow() returned nil on B and SetHeader/validateHeader dereferenced an empty path.
Fix. Added headerPathFor() + skipTaskLine() (+ rowIsHeaderShape()) in board.go; removed the 4 write gates in write.go (SetHeader now rewrites only line 1 of tasks.jsonl on B with a whole-file byte-identical untouched-line assert before the atomic rewrite); added topology-aware TaskRows() used by list/show/stats/doctor; routed validateHeader/doctorHeaderVsEvents through headerPathFor() in both topologies (with the "extra lines past header" shape check kept topology-A-only); fixed header subcommand wording; init still refuses B but with writable-era wording (fresh boards only, migration optional); README/comments updated.
Verification (all run live, Go 1.26):
- Pre-fix source + BT-010 tests: go test fails (b.TaskRows undefined; 4 CLI tests fail). The installed buggy binary: create/header exit 1 with the stale message; validate shows header counters not checked (exit 0 even for junk).
- Fixed tree: go test -count=1 ./... → ok both packages, 71 test functions pass (16 new topology-B/BT-010 tests), go vet clean.
- Fixed binary full workflow on one shared topology-B board: create → header read/set → event → update --commit-hash → validate/doctor all exit 0; header counters are really checked (ticks_total -5 fails validate, tick drift fails doctor); tasks.jsonl final state shows only line 1 rewritten by header/commit-hash updates — all other lines byte-identical.
# Evidence - Problem class: go-cli-jsonl-topology-b-writes - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-09-04T23:16:33.401Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "boardctl: topology-B boards (header = line 1 of tasks.jsonl) were read-only in practice \u2014 4 write gates (Create/UpdateTask/AppendEvent/SetHeader) + header subcommand rejected them with a stale migration message. Fix: headerPathFor() helper (A\u2192board.jsonl, B\u2192tasksPath), skipTaskLine() so Create mirrors last TASK row schema not the header, UpdateTask preserves line 1, AppendEvent was headerless anyway (gate removed), header subcommand reads/writes line-1 header on B, validate/doctor upgraded from warn to real header-counter checks, README: both topologies writable, migration optional. Byte-identical untouched-line assert for the shared-file rewrite is the critical correctness invariant. 20 tests, judge PASS.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-cli-jsonl-topology-b-writes", "provider": "openrouter", "solved_at": "2026-09-04T23:16:33.402Z", "version": ""}