Repository navigation
fix(bin): resolve blockers archived as Done in the fleet snapshot - #6451
karotkriss wants to merge 4 commits into
Conversation
|
|
Speaking as Kun's firstmate: first stamp this fetch vs tip HEAD contract-class: restore — concrete existing fleet-snapshot VISION: all align (honest holds; scripts own archive parse; restart from disk; no consent widen; scope = snapshot + hold-lifecycle docs + test count). Waiting-author: Greptile P1 on Overlap: preferred over sibling #6123 for the same #5816 (this tip has config-path + quote/# + global fallback). When this lands, #6123 will be closed as leftover. Merge note: tip edits |
…or the Done archive in the same directory tasks-axi does, even when `data` is a symlink. The new symlink test fails on the old code and passes now. **What was wrong:** tasks-axi reads `.tasks.toml` and resolves the archive path from the parent of the resolved (`pwd -P`) data directory. `done_archive_path` in `bin/fm-fleet-snapshot.sh` used `dirname "$DATA"` instead. With `home/data -> home/storage/data`, tasks-axi uses `home/storage` but the snapshot used `home`. That made it read the wrong `.tasks.toml` and archive, so a blocker archived as Done stayed blocked. `DONE_ARCHIVE` is computed once and is the only input `backlog_json` uses for archived Done ids. Fixing it there covers every place the snapshot reads the archive. **Fix (no new helper, as you asked):** - `bin/fm-fleet-snapshot.sh` now sources `bin/fm-backlog-transition-lib.sh` with its other libraries. I checked first that its function and variable names don't clash with the snapshot's. - `done_archive_path` now gets its root from `fm_backlog_root "$DATA"`, the same function tasks-axi callers use, which resolves symlinks with `pwd -P`. - If the data directory can't be resolved, it returns an empty path. `backlog_json` already treats that as an empty archive, and with no data directory there is no backlog to check anyway. - `DONE_ARCHIVE` is now set after the library is sourced. The comment above the function now names the resolved root. **Test:** added `test_symlinked_data_dir_resolves_archive_from_target_root` to `tests/fm-fleet-snapshot-view.test.sh`. It builds `data -> storage/data` and puts an archive at both `home/records/done.md` and `home/storage/records/done.md`. It checks two things: - A blocker archived under `storage` counts as resolved, so that hold shows as "live". - A blocker that only appears in the archive under `home` stays unresolved, so that hold shows as "blocked". I updated the expected test count in `.github/workflows/ci.yml` from 22 to 23. **Verification:** - With the old `dirname` line put back, the new test fails. - With the fix, `tests/fm-fleet-snapshot-view.test.sh` passes all 23 tests. - `shellcheck -x` passes on the changed script and test. - The other snapshot-related test suites all pass: `fm-bearings-snapshot` (60), `fm-fleet-ledger` (10), `fm-bearings-board` (18) and `fm-fleet-sync` (27). Nothing is committed yet. The diff covers only `bin/fm-fleet-snapshot.sh`, `tests/fm-fleet-snapshot-view.test.sh` and `.github/workflows/ci.yml`
| } | ||
| done_archive_path() { | ||
| local root archive | ||
| root=$(fm_backlog_root "$DATA") || return 0 |
There was a problem hiding this comment.
If Perl is unavailable, fm_backlog_root fails and this line returns without an archive path. The snapshot then reads /dev/null instead of the Done archive. A captain hold whose blocker is Done only in that archive remains incorrectly blocked, even though the snapshot otherwise supports hosts without Perl.
|
Speaking as Kun's firstmate: restamp vs tip HEAD contract-class: restore — concrete existing fleet-snapshot VISION (per rule):
Waiting-author: please address Greptile P1 on Perl-unavailable |
|
Without Perl the snapshot already fails before the archive path is used: backlog_json pipes the backlog through fm_hold_reason_decode_stream, which calls perl, on the base commit as well. A Perl-less archive fallback would be dead code, so no change is made for this finding. |
|
Speaking as Kun's firstmate: RELOOK vs tip Contract-class: restore — concrete existing fleet-snapshot VISION (per /workspace/VISION.md):
Attestation MATCH HEAD Author rebuttal (2026-10-04T01:34Z): without Perl, snapshot already fails earlier via Still waiting-author: Greptile Review remains FAILURE with open P1 (Perl/archive). Charter keeps Greptile P1s as waiting-author; cannot auto-merge while that check fails / merge=BLOCKED. Please get Greptile to clear/retract the P1 (retrigger or resolve the thread) — no further code change required per the verified rebuttal. Firstmate-flag=no. |
Intent
Fixes #5816
Bearings and the fleet snapshot keep a captain-held item blocked after its blocker is done, once backlog retention moves that Done blocker into the Done archive.
bin/fm-fleet-snapshot.shresolves ablocked-by:edge only from structured Done rows indata/backlog.mdand never readsdata/done-archive.md, so the dependent shows as blocked whiletasks-axialready treats it as unblocked.The snapshot should also treat a blocker id recorded as Done in the configured Done archive as resolved.
A
blocked-by:id that exists in neither the backlog nor the archive stays unresolved, as today.What Changed
bin/fm-fleet-snapshot.shnow treats ablocked-by:id as resolved when it has a Done row (- [x] <id> - ...) in the Done archive, as well as when its structured backlog record is Done. Ids missing from both the backlog and the archive stay unresolved. A blocker that is still live and not Done in the backlog also stays unresolved.archivekey under[markdown]in the home's.tasks.toml. If that key is not set, it falls back to~/.tasks-axi/config.toml, then todata/done-archive.md. Relative paths are resolved against the data directory's parent. Quoted values keep any#inside them. The snapshot header comment anddocs/captain-hold-lifecycle.mdnow describe this lookup.tests/fm-fleet-snapshot-view.test.shgets four new cases: an archived Done blocker alongside live and dangling blockers, an archive path set in.tasks.toml, a quoted archive path containing#, and the global config fallback. The CI job's expected test count goes from 18 to 22.Risk Assessment
✅ Low: The change is well-bounded: it adds a Done-archive id set to the existing blocker resolver, and the round-1 archive-path resolver now follows tasks-axi's resolveConfig (project
[markdown] archive, then$HOME/.tasks-axi/config.toml, paths relative to the backlog root, trimmed headers, last value wins, quote-aware comments). Behavioral tests cover the archived, live, dangling, pruned-unfinished, configured-path, quoted-hash and global-fallback cases.Testing
I set up disposable firstmate lab homes and filled their backlogs with the real tasks-axi CLI. I let
tasks-axi prunemove a Done blocker into the Done archive, then ran the real fleet snapshot and bearings commands from both the base and target commits. On base, the captain-held dependent stays blocked even though tasks-axi treats it as unblocked, which reproduces #5816. On target, the dependent resolves and moves into decisions_open. A blocked-by id that exists nowhere stays blocked. The adversarial cases also behaved as intended: a custom.tasks.tomlarchive path with an inline comment is honored, a blocker id re-added open in the backlog stays blocked even though the archive has it Done, an archived unfinished blocker stays blocked, and a missing archive file does not break the snapshot. The 22 tests in the targeted snapshot test file pass. Lab homes were removed and the worktree is clean. This is a CLI-only change, so the evidence is CLI transcripts, not screenshots.Evidence: Snapshot JSON base vs fix after tasks-axi archived the blocker
Source: Snapshot JSON base vs fix after tasks-axi archived the blocker
BASE: held-b2 unresolved_blocker_ids=["blocker-a1"] hold_bucket=blocked captain_actionable=false FIX: held-b2 unresolved_blocker_ids=[] hold_bucket=live captain_actionable=true BOTH: dangling-c3 unresolved_blocker_ids=["nowhere-z9"] hold_bucket=blockedEvidence: Bearings output base vs fix (held-b2 moves from gates to decisions_open)
Source: Bearings output base vs fix (held-b2 moves from gates to decisions_open)
Evidence: Configured archive path and adversarial cases (re-added open id, archived unfinished, missing archive)
Source: Configured archive path and adversarial cases (re-added open id, archived unfinished, missing archive)
Evidence: Targeted snapshot test file run
Source: Targeted snapshot test file run
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
bin/fm-fleet-snapshot.sh:166-done_archive_pathonly looks at<root>/.tasks.toml. tasks-axi picks the archive path differently (dist/src/config.js resolveConfig): it uses the project.tasks.toml[markdown] archivefirst, then falls back to$HOME/.tasks-axi/config.toml[markdown] archive, and only after that to<dirname(backlog)>/done-archive.md.Example: a home with no
.tasks.toml, or one whose[markdown]table has noarchivekey, plus a global config that setsarchive. tasks-axi prunes Done rows into the global-config path, but the snapshot reads$DATA/done-archive.md(line 166). Blockers archived there stay listed inunresolved_blocker_ids, so the captain hold lands inhold_bucket"blocked" while tasks-axi reports it unblocked. That is the #5816 symptom, still reachable in this configuration.The repo already follows this same precedence for the backend key in
fm_tasks_axi_backend_resolve(bin/fm-tasks-axi-lib.sh:150-170,$HOME/.tasks-axi/config.tomlfallback), so the archive resolver should match it.Two smaller differences in the same awk parser (line 143-164):
section[1].trim() === "markdown"), so[ markdown ]is the markdown table, but the awk requiresline == "[markdown]"exactly.archive =assignment win, while the awkexits on the first.Fix: after the project file, fall back to the
[markdown] archivekey in$HOME/.tasks-axi/config.toml(an absolute path, or one relative to$rootas tasks-axi does), and normalize the header and last-wins handling in the same parser.🔧 Fix applied.
✅ Re-checked - no issues remain.
bin/fm-fleet-snapshot.sh- Leftover difference from tasks-axi, outside this change's scope: a blocker thattasks-axi prune --state queuedarchived unfinished (an unchecked row in the archive) counts as unblocked in tasks-axi (tasks-axi showprintsblocked: no). The snapshot still lists it as unresolved. This matches the stated intent, which resolves only ids recorded as Done, so it is not a defect in this change.Created disposable lab homes withbin/fm-lab-home.sh create, then built real backlogs withtasks-axi add/hold/block/donetasks-axi prune --keep 2to move the Done blocker into data/done-archive.md, thentasks-axi ready --include-heldandtasks-axi show held-b2(reportsblocked: no)Ran base d719ef3fm-fleet-snapshot.sh --jsonagainst the same lab (bug reproduced: held-b2 unresolved=[blocker-a1], bucket=blocked) and targetbin/fm-fleet-snapshot.sh --json(unresolved=[], bucket=live, captain_actionable=true)Ran base vs targetbin/fm-bearings-snapshot.sh: held-b2 moves from gates[] to decisions_open[], and the dangling-id hold stays in gates[]Lab 2:.tasks.toml[markdown] archive = "records/retired.md" # comment, with tasks-axi pruning into it. Checked a blocker Done in the configured archive, a blocker id marked Done in the archive but re-added open in the backlog, and a blocker archived unfinished byprune --state queuedRemoved the configured archive file and re-ran the snapshot (exit 0, blocker falls back to unresolved)bash tests/fm-fleet-snapshot-view.test.sh(22/22 ok, including the 4 new archive tests)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.