Repository navigation
fix(bin): resolve blockers archived as Done in the fleet snapshot - #6123
karotkriss wants to merge 6 commits into
Conversation
|
This comment has been minimized.
This comment has been minimized.
|
Speaking as Kun's firstmate: First stamp this fetch vs tip HEAD Tip vs main (own diff): Contract-class: restore — concrete existing fleet-snapshot / Bearings hold-bucket path was wrong vs tasks-axi after retention archived a Done blocker. Not a new always-on observer/wake/Bearings surface (type specimen #4627 N/A); fixes resolution of an already-specified edge. VISION.md per-rule
Security: bin/docs/tests/ci count. Clean. |
|
All checks are green on head cbaf113a87704411954981cff7f11028c25ddf83. |
kunchenguid
left a comment
There was a problem hiding this comment.
Speaking as Kun's firstmate: approving for ruleset require_extra_approval_for_unattributed_changes. contract-class restore: fleet snapshot already resolved blocked-by against Done backlog rows; tip only also treats checked Done rows in the configured archive as resolved (same semantics tasks-axi already uses). VISION: honesty under load (holds stop looking blocked when the blocker is Done); scripts own mechanics; no new default-on surface.
|
Speaking as Kun's firstmate: Restamp vs tip HEAD contract-class: restore — concrete existing blocked-by resolution path was incomplete vs Done archive; tip also reads configured archive (same semantics tasks-axi). Not a new Bearings surface. VISION: honesty under load (holds stop falsely looking blocked); scripts own mechanics; no new default-on / consent assumption. Merge gate: branch policy blocks squash (PR edits |
…solved The fleet snapshot resolved a blocked-by edge only from structured Done rows still in data/backlog.md, so a dependent stayed blocked after backlog retention moved its Done blocker into data/done-archive.md, even though tasks-axi already treats it as unblocked. Seed the snapshot's resolved-id set from the archive's structured Done ids before folding in the backlog's own rows, so a live backlog row for the same id still wins and a blocker id present in neither file stays unresolved as before.
…failed because its expected test count was out of date, not because any test failed. All 19 tests in tests/fm-fleet-snapshot-view.test.sh passed under stock /bin/bash 3.2.57. This PR adds one of them, the regression test for a blocker marked Done in the archive. The workflow still pinned the count at 18, and that count check is the rule that was broken: the number in the CI step must equal the number of `ok -` tests the suite runs. That count appears only in `.github/workflows/ci.yml`, in the test on line 449 and the error message on line 450. I changed both from 18 to 19. Running the suite locally now reports 19 `ok -` lines, and only the workflow file changed
…ss. Finding 1: the snapshot must read the same Done archive that tasks-axi writes. It only ever read `$DATA/done-archive.md`. The new `done_archive_path` in `bin/fm-fleet-snapshot.sh` reads the `archive` key from the `[markdown]` section of the home's `.tasks.toml`. That file sits in the data directory's parent, which is the directory tasks-axi runs from. A relative path resolves against that directory, and an absolute path is used as is. It falls back to `data/done-archive.md` only when `.tasks.toml` is missing or has no `archive` key. There is one shared `DONE_ARCHIVE`, and `backlog_json`, the snapshot's only consumer of the archive, reads it. Finding 2: the list of archived ids must never be passed to jq as a command-line argument. I removed the separate archived-id step and the `--argjson` argument. `backlog_json` now gives the archive file to jq with `--rawfile`, or `/dev/null` when the file is missing, and pulls the checked row ids out inside jq. Tests: - I added `test_configured_done_archive_path_resolves_blockers` to `tests/fm-fleet-snapshot-view.test.sh`. It points `.tasks.toml` at `records/done.md` and puts a decoy `data/done-archive.md` next to it. A hold whose blocker is in the configured archive resolves. A hold whose blocker is only in the decoy stays blocked. - The new test fails against the previous code and passes now. The suite passes 20 of 20. I raised the stock macOS Bash count check in `.github/workflows/ci.yml` from 19 to 20. - A 20 MB archive with 200,000 Done rows, far past Linux's 128 KB limit per argument, still resolves. - `shellcheck -x` is clean. - Six other suites that use the snapshot passed with no failures: fm-bearings-snapshot (60), fm-captain-hold-lifecycle (53), fm-crew-state (283), fm-home-summary-refresh (21), fm-pending-reply (41) and fm-contributions (44). Two more had not finished when I returned this result, so I have no result for them: fm-remote-secondmate-lifecycle-e2e and fm-secondmate-reconcile. I updated the matching sentence in `docs/captain-hold-lifecycle.md` and the script's header comment to name the configured archive path. Only `.github/workflows/ci.yml`, `bin/fm-fleet-snapshot.sh`, `docs/captain-hold-lifecycle.md` and `tests/fm-fleet-snapshot-view.test.sh` changed. Commit messages should be kept plain
… is that the `archive` path read from `.tasks.toml` has to be the whole quoted value. A `#` inside quotes is part of the path, not the start of a comment. That value is only parsed in one place: the awk block in `done_archive_path` in `bin/fm-fleet-snapshot.sh`. `backlog_json` gets the path through `DONE_ARCHIVE`, so fixing the parser fixes every reader. **The fix (`bin/fm-fleet-snapshot.sh`):** - Section headers still have trailing `# ...` comments removed before the `[markdown]` check, as you said they could. - For the `archive = ...` line, a value that starts with `"` or `'` is now read up to its matching closing quote. A comment after the closing quote is dropped. - An unquoted value still loses its trailing `# comment`. - The old code stripped quotes after cutting at the first `#`. That step is gone because nothing reaches it now. - A quoted value with no closing quote gives an empty result, so the snapshot uses `data/done-archive.md`, same as when no archive key is set. **Test (`tests/fm-fleet-snapshot-view.test.sh`):** I added `test_quoted_archive_path_keeps_hash` right after the configured-archive-path test. It sets `archive = "records/task#5/done.md" # retention target` under a `[markdown] # ...` header and puts a Done blocker in `records/task#5/done.md`. It then checks that the held item that depends on it has no unresolved blockers and shows as `live`. I raised the stock macOS Bash test count in `.github/workflows/ci.yml` from 20 to 21 to match. **Verification:** - The new test fails against the previous parser ("truncated a quoted .tasks.toml archive path at its #") and passes now. The suite passes 21 of 21. - `shellcheck -x` is clean on both changed shell files. - The quote handling gives `a#b/c.md` under both mawk and gawk. Called directly, the function also reads a single-quoted `'x#y.md'` whole and returns `plain.md` for an unquoted value followed by a `# c` comment. - I did not run the suite under stock Bash 3.2, because it isn't installed here. The CI job covers that. `git status --porcelain` shows only the three intended files changed
cbaf113 to
168a830
Compare
|
Speaking as Kun's firstmate: restamp vs tip HEAD contract-class: restore (same defect class as #6451). VISION: aligns as restore of archive Done resolution; superseded in completeness by preferred sibling. Waiting-author: (1) rebind attestation with Firstmate flag no this pass (attestation/NM red; not otherwise-ready). |
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 readsdata/done-archive.mdwhen it exists and collects the ids of checked ([x]) rows. It passes them tobacklog_json, which treats them as resolved when it evaluatesblocked-by:edges. A structured backlog record with the same id still overrides the archive, and a blocker id missing from both files stays unresolved. If the archive can't be read, the script exits withdone archive read failed.tests/fm-fleet-snapshot-view.test.shcovering four captain holds. The one blocked by an archived Done blocker and the one blocked by a backlog Done blocker both resolve to thelivebucket. The one blocked by an archived unchecked blocker and the one blocked by a missing id both stay in theblockedbucket.docs/captain-hold-lifecycle.mdto say that Done rows in the archive also resolve blockers.Risk Assessment
🚨 High: A reproduction with a real tasks-axi archive shows the claimed fix has no effect on real data, because it only parses
## Doneheadings and tasks-axi writes## Archived <date>, so #5816 stays broken while the test fixture hides it.Testing
I built a disposable lab home and used the real
tasks-axito create the #5816 situation: a captain hold depends onblocker-a, andblocker-afinishes and is moved into data/done-archive.md by done_keep=10 retention. I ran the real fleet-snapshot and bearings scripts against that home at the base and target commits. At base the hold stays blocked (a bearings gate), which reproduces the bug. At target it is resolved and shows as a live Captain's Call decision. In every run, the blocker still Done in the backlog resolves and the blocker in neither file stays blocked. Adversarial runs showed that a blocker archived unfinished, or re-filed under the same id as a live queued task, stays unresolved, and the script runs cleanly with no archive file. I ran the targeted snapshot view test file: it passes at target, and its new test fails against the base scripts. That unit-test check is not a live scenario, so it is recorded as untested. The lab was removed afterwards and the worktree is clean. This is a CLI and JSON change with no rendered UI, so the evidence is transcripts rather than screenshots.tasks-axi prune --state queued(a- [ ]archive row) is not treated as Donetasks-axi add blocker-aagain, the snapshot has held-archived unresolved [blocker-a] and bucket blocked, matching tasks-axi's blocked: yes (transcript)tests/fm-fleet-snapshot-view.test.shat base and target), not a live product run, so it does not show a live result. The live lab scenarios…Evidence: Snapshot before/after transcript with adversarial cases
Source: Snapshot before/after transcript with adversarial cases
Evidence: Bearings output at base c5f48e4 (held-archived listed as a blocked gate)
Source: Bearings output at base c5f48e4 (held-archived listed as a blocked gate)
Evidence: Bearings output at target a36fd40 (held-archived listed as an open captain decision)
Source: Bearings output at target a36fd40 (held-archived listed as an open captain decision)
Evidence: Lab backlog.md produced by tasks-axi
Source: Lab backlog.md produced by tasks-axi
Evidence: Lab done-archive.md produced by tasks-axi retention
Source: Lab done-archive.md produced by tasks-axi retention
Evidence: Before/after snapshot fields for the hold on the archived blocker
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-fleet-snapshot.sh:1979- The fix never fires against a real Done archive. tasks-axi writes every archive block under a## Archived <date>heading (markdown.js appendArchiveBlock:\n## Archived ${stamp}\n...). The live home archive has 71## Archivedheadings and no## Doneheading. When backlog_json parses that file, section_state mapsArchived <date>to null, so the reducer skips every row and DONE_ARCHIVE_IDS is always[].Reproduced end to end: I used tasks-axi add/block/hold/start/done/prune to archive blocker
blkin a temp home. The snapshot still reported depunresolved_blocker_ids:["blk"],hold_bucket:"blocked",captain_actionable:false. After rewriting the heading to## Doneit reported[]/live. So #5816 is not fixed.The new test only passes because its fixture (tests/fm-fleet-snapshot-view.test.sh:487-490) invents a
## Doneheading that the producer never writes. Rebuild that fixture with tasks-axi prune, or write the real## Archived <date>heading.Same invariant ("recorded as Done"), second gap:
tasks-axi prune --state queued|in_flightalso appends unchecked[ ]rows under## Archivedheadings. The filter at line 1979 (select(.structured)) does not check state or the[x]marker, so once headings parse, an archived non-Done id would wrongly resolve its dependents.Suggested remedy: replace the backlog_json call on the archive with a small dedicated extraction that takes only checked
- [x] <id> -rows, under any heading. This also avoids running the full per-row regex normalization over an archive that grows without bound on every snapshot.bin/fm-fleet-snapshot.sh:136- The intent says "configured Done archive", but the change hardcodes DONE_ARCHIVE="$DATA/done-archive.md" instead of reading[markdown] archivefrom .tasks.toml. It matches the tracked default and the existing hardcoded BACKLOG path, so this only matters for a home that overrides the archive path. Noting it for awareness only.🔧 Fix applied.
1 info still open:
bin/fm-fleet-snapshot.sh:136- The intent says "configured Done archive", but the change hardcodes DONE_ARCHIVE="$DATA/done-archive.md" instead of reading[markdown] archivefrom .tasks.toml. It matches the tracked default and the existing hardcoded BACKLOG path, so this only matters for a home that overrides the archive path. Noting it for awareness only.bin/fm-fleet-snapshot.sh- The snapshot and tasks-axi still disagree on two edge cases, both outside the scope the intent set. tasks-axi reports a task as not blocked when its blocker is missing entirely or was archived unfinished by prune. The snapshot keeps both cases unresolved, which is what the intent specifies ('stays unresolved, as today'). Also, the snapshot builds the archive path as $DATA/done-archive.md rather than reading the archive setting in .tasks.toml. That path matches the tracked config, so behaviour is correct today.tasks-axi prune --state queued(a- [ ]archive row) is not treated as Donetasks-axi add blocker-aagain, the snapshot has held-archived unresolved [blocker-a] and bucket blocked, matching tasks-axi's blocked: yes (transcript)tests/fm-fleet-snapshot-view.test.shat base and target), not a live product run, so it does not show a live result. The live lab scenarios…Minted a disposable lab home (bin/fm-lab-home.sh create) with the repo.tasks.toml(archive=data/done-archive.md, done_keep=10). Built a real backlog withtasks-axi add/hold/block/done, and 11 Done tasks made real retention archiveblocker-aFM_HOME=$LAB bin/fm-fleet-snapshot.sh --json, run at base c5f48e4 (extracted with git archive) and at target a36fd40, comparing unresolved_blocker_ids, hold_bucket and captain_actionable for each held taskFM_HOME=$LAB bin/fm-bearings-snapshot.sh, run at base and at target, comparing the decisions_open and gates sectionsAdversarial: re-filedblocker-aas a new queued task (tasks-axi shows the dependent as blocked) and checked that the snapshot keeps it unresolvedAdversarial:tasks-axi prune --state queued --keep 4archived an unfinished blocker as a- [ ]row, and the snapshot keeps it unresolvedAdversarial: blocker idghostthat is in neither the backlog nor the archive (row removed by hand after tasks-axi refused to delete it) stays unresolvedEdge case: snapshot with no done-archive.md file present exits 0bash tests/fm-fleet-snapshot-view.test.shat target (all ok), and the same test file run against the base scripts, where the new archived-blocker test reports not ok (regression proof)bin/fm-fleet-snapshot.sh:136- The snapshot reads the archive from the fixed path $DATA/done-archive.md rather than thearchivekey in .tasks.toml. That matches the tracked default, but a home that points its archive somewhere else would not be honored. The docs now name the fixed path so they match the code; whether the snapshot should read the configured path is a code decision outside the document phase.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.