Skip to content

fix(bin): resolve blockers archived as Done in the fleet snapshot - #6123

Open
karotkriss wants to merge 6 commits into
kunchenguid:mainfrom
karotkriss:fm/fm-up-5816-snapshot-archived-blocker
Open

karotkriss wants to merge 6 commits into
kunchenguid:mainfrom
karotkriss:fm/fm-up-5816-snapshot-archived-blocker

Conversation

@karotkriss

@karotkriss karotkriss commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

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.sh resolves a blocked-by: edge only from structured Done rows in data/backlog.md and never reads data/done-archive.md, so the dependent shows as blocked while tasks-axi already 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.sh now reads data/done-archive.md when it exists and collects the ids of checked ([x]) rows. It passes them to backlog_json, which treats them as resolved when it evaluates blocked-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 with done archive read failed.
  • Added a regression test to tests/fm-fleet-snapshot-view.test.sh covering four captain holds. The one blocked by an archived Done blocker and the one blocked by a backlog Done blocker both resolve to the live bucket. The one blocked by an archived unchecked blocker and the one blocked by a missing id both stay in the blocked bucket.
  • Updated the header comment in the script and the "Fleet snapshot buckets" section of docs/captain-hold-lifecycle.md to 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 ## Done headings 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-axi to create the #5816 situation: a captain hold depends on blocker-a, and blocker-a finishes 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.

  • Live validation: ✅ go - 7 of 8 scenarios driven live against the product
Scenario Result Live Evidence
A captain hold whose blocker was finished and then archived by tasks-axi retention shows as unblocked (live, captain-actionable) in the fleet snapshot, where base showed it as blocked ✅ pass live snapshot-archived-blocker-transcript.txt: base shows unresolved [blocker-a], bucket blocked; target shows unresolved [], bucket live, captain_actionable true. tasks-axi show held-archived reports bloc…
Bearings moves the hold on an archived blocker from the gates list to the open captain decisions ✅ pass live bearings-base-c5f48e4.txt lists held-archived under gates[2]; bearings-target-a36fd40.txt lists it under decisions_open[2] and gates[1] has only held-dangling
A blocker that is still Done in the backlog keeps resolving its dependent (no regression) ✅ pass live held-live has unresolved [] and bucket live at both base and target (transcript)
Adversarial: a blocker id that is in neither the backlog nor the archive stays unresolved ✅ pass live held-dangling blocked-by ghost has unresolved [ghost] and bucket blocked at both base and target (transcript)
Adversarial: a blocker archived unfinished by tasks-axi prune --state queued (a - [ ] archive row) is not treated as Done ✅ pass live held-pruned has unresolved [pruned-blk] and bucket blocked after the prune (transcript, lab-done-archive.md)
Adversarial: an archived Done id re-filed as a live queued task keeps blocking; the live backlog record overrides the archive ✅ pass live After tasks-axi add blocker-a again, the snapshot has held-archived unresolved [blocker-a] and bucket blocked, matching tasks-axi's blocked: yes (transcript)
The snapshot still runs cleanly when no done-archive.md exists ✅ pass live Ran fm-fleet-snapshot.sh --json against the lab with the archive file moved away: exit 0, no stderr
The regression test fails before the fix and passes after it ⏸️ untested no The earlier payload backed this only with a unit-test run (tests/fm-fleet-snapshot-view.test.sh at 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

# Lab: disposable FM_HOME, backlog driven by real tasks-axi (.tasks.toml done_keep=10 archive=data/done-archive.md)
# blocker-a done then 10 more Done tasks -> tasks-axi retention archived blocker-a

## Snapshot BEFORE fix (c5f48e4) - held-archived still blocked
{"id":"held-archived","unresolved_blocker_ids":["blocker-a"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-dangling","unresolved_blocker_ids":["ghost"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-live","unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}

## Snapshot AFTER fix (a36fd40) - held-archived resolved; dangling 'ghost' still blocked
{"id":"held-archived","unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
{"id":"held-dangling","unresolved_blocker_ids":["ghost"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-live","unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}

## Adversarial: blocker-a re-filed as a queued task (tasks-axi: blocked yes) -> snapshot keeps it unresolved
{"id":"held-archived","blocked_by_ids":["blocker-a"],"unresolved_blocker_ids":["blocker-a"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-dangling","blocked_by_ids":["ghost"],"unresolved_blocker_ids":["ghost"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-live","blocked_by_ids":["blocker-live"],"unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
{"id":"held-pruned","blocked_by_ids":["pruned-blk"],"unresolved_blocker_ids":["pruned-blk"],"hold_bucket":"blocked","captain_actionable":false}

## Adversarial: tasks-axi prune --state queued archived unfinished pruned-blk as '- [ ]' -> stays unresolved
{"id":"held-archived","unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
{"id":"held-dangling","unresolved_blocker_ids":["ghost"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-live","unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
{"id":"held-pruned","unresolved_blocker_ids":["pruned-blk"],"hold_bucket":"blocked","captain_actionable":false}

## done-archive.md at end

## Archived 2026-09-29
- [x] blocker-a - Blocker that finishes and gets archived (kind: ship) (done 2026-09-29)

## Archived 2026-09-29
- [ ] pruned-blk - Blocker pruned unfinished (kind: ship) (since 2026-09-29)
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)

schema: fm-bearings.v1
home: tmp/fm-lab.y5RbCD
generated: "2026-09-29T12:45:56Z"
prs: "not_requested (run: /bearings include PRs)"
contributions: 
  scope: owned contributions per home
  known: 0
  checked: 0
  counts: 
    captain: 0
    fleet: 0
    maintainer: 0
    nobody: 0
  complete: true
  proven_clear: true
  unmeasured_homes: 0
  unreadable_records: 0
  unmeasured: 0
  stale_verdicts: 0
  missing_verdicts: 0
  captain_omitted: 0
  captain: []
in_flight: []
secondmates: []
secondmate_reconcile: []
decisions_open[1]{id,key,verb,summary,owner}:
  held-live,held-live,captain-hold,"Captain hold waiting on live Done blocker: captain decision pending",(main)
landed[6]{id,what,artifact,owner}:
  filler-9,Filler task 9,"-",(main)
  filler-8,Filler task 8,"-",(main)
  filler-7,Filler task 7,"-",(main)
  filler-6,Filler task 6,"-",(main)
  filler-5,Filler task 5,"-",(main)
  filler-4,Filler task 4,"-",(main)
gates[2]{id,title,blocked_by,reason,owner,filed}:
  held-archived,Captain hold waiting on archived blocker,blocker-a,"blocked-by blocker-a: captain decision p…",(main),2026-09-29
  held-dangling,Captain hold waiting on phantom,ghost,"blocked-by ghost: captain decision pendi…",(main),2026-09-29
reports: []
recorded_prs: []
omitted[8]{surface,reveal}:
  backlog item bodies,"--fields bodies"
  task paths,"--fields paths"
  watch/steer actions,"--fields actions"
  healthy endpoint detail,"--fields endpoints"
  full scout-report inventory,"--all-reports"
  landed per-home capped at 6 for 1 home(s),"--all-landed"
  "captain holds bucketed blocked, dated, or aged: 2","--all-decisions"
  live PR discovery + checks,"--include-prs"
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)

schema: fm-bearings.v1
home: tmp/fm-lab.y5RbCD
generated: "2026-09-29T12:45:56Z"
prs: "not_requested (run: /bearings include PRs)"
contributions: 
  scope: owned contributions per home
  known: 0
  checked: 0
  counts: 
    captain: 0
    fleet: 0
    maintainer: 0
    nobody: 0
  complete: true
  proven_clear: true
  unmeasured_homes: 0
  unreadable_records: 0
  unmeasured: 0
  stale_verdicts: 0
  missing_verdicts: 0
  captain_omitted: 0
  captain: []
in_flight: []
secondmates: []
secondmate_reconcile: []
decisions_open[2]{id,key,verb,summary,owner}:
  held-archived,held-archived,captain-hold,"Captain hold waiting on archived blocker: captain decision pending",(main)
  held-live,held-live,captain-hold,"Captain hold waiting on live Done blocker: captain decision pending",(main)
landed[6]{id,what,artifact,owner}:
  filler-9,Filler task 9,"-",(main)
  filler-8,Filler task 8,"-",(main)
  filler-7,Filler task 7,"-",(main)
  filler-6,Filler task 6,"-",(main)
  filler-5,Filler task 5,"-",(main)
  filler-4,Filler task 4,"-",(main)
gates[1]{id,title,blocked_by,reason,owner,filed}:
  held-dangling,Captain hold waiting on phantom,ghost,"blocked-by ghost: captain decision pendi…",(main),2026-09-29
reports: []
recorded_prs: []
omitted[8]{surface,reveal}:
  backlog item bodies,"--fields bodies"
  task paths,"--fields paths"
  watch/steer actions,"--fields actions"
  healthy endpoint detail,"--fields endpoints"
  full scout-report inventory,"--all-reports"
  landed per-home capped at 6 for 1 home(s),"--all-landed"
  "captain holds bucketed blocked, dated, or aged: 1","--all-decisions"
  live PR discovery + checks,"--include-prs"
Evidence: Lab backlog.md produced by tasks-axi

Source: Lab backlog.md produced by tasks-axi

# Backlog

## In flight
## Queued
- [ ] held-archived - Captain hold waiting on archived blocker blocked-by: blocker-a (kind: captain) (since 2026-09-29) (hold: captain decision pending) (hold-kind: captain)
- [ ] held-dangling - Captain hold waiting on phantom blocked-by: ghost (kind: captain) (since 2026-09-29) (hold: captain decision pending) (hold-kind: captain)
- [ ] held-live - Captain hold waiting on live Done blocker blocked-by: blocker-live (kind: captain) (since 2026-09-29) (hold: captain decision pending) (hold-kind: captain)
## Done
- [x] blocker-live - Blocker that finishes and stays in backlog (kind: ship) (done 2026-09-29)
- [x] filler-9 - Filler task 9 (kind: ship) (done 2026-09-29)
- [x] filler-8 - Filler task 8 (kind: ship) (done 2026-09-29)
- [x] filler-7 - Filler task 7 (kind: ship) (done 2026-09-29)
- [x] filler-6 - Filler task 6 (kind: ship) (done 2026-09-29)
- [x] filler-5 - Filler task 5 (kind: ship) (done 2026-09-29)
- [x] filler-4 - Filler task 4 (kind: ship) (done 2026-09-29)
- [x] filler-3 - Filler task 3 (kind: ship) (done 2026-09-29)
- [x] filler-2 - Filler task 2 (kind: ship) (done 2026-09-29)
- [x] filler-1 - Filler task 1 (kind: ship) (done 2026-09-29)
Evidence: Lab done-archive.md produced by tasks-axi retention

Source: Lab done-archive.md produced by tasks-axi retention


## Archived 2026-09-29
- [x] blocker-a - Blocker that finishes and gets archived (kind: ship) (done 2026-09-29)
Evidence: Before/after snapshot fields for the hold on the archived blocker
base c5f48e4: {"id":"held-archived","unresolved_blocker_ids":["blocker-a"],"hold_bucket":"blocked","captain_actionable":false}
target a36fd40: {"id":"held-archived","unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
both: {"id":"held-dangling","unresolved_blocker_ids":["ghost"],"hold_bucket":"blocked","captain_actionable":false}
- Outcome: ⚠️ 1 info across 1 run (3m1s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • 🚨 bin/fm-fleet-snapshot.sh:1979 - The fix never fires against a real Done archive. tasks-axi writes every archive block under a ## Archived &lt;date&gt; heading (markdown.js appendArchiveBlock: \n## Archived ${stamp}\n...). The live home archive has 71 ## Archived headings and no ## Done heading. When backlog_json parses that file, section_state maps Archived &lt;date&gt; 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 blk in a temp home. The snapshot still reported dep unresolved_blocker_ids:[&#34;blk&#34;], hold_bucket:&#34;blocked&#34;, captain_actionable:false. After rewriting the heading to ## Done it 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 ## Done heading that the producer never writes. Rebuild that fixture with tasks-axi prune, or write the real ## Archived &lt;date&gt; heading.

Same invariant ("recorded as Done"), second gap: tasks-axi prune --state queued|in_flight also appends unchecked [ ] rows under ## Archived headings. 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] &lt;id&gt; - 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] archive from .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] archive from .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.
⚠️ **Test** - 1 info
  • ℹ️ 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.
  • Live validation: ✅ go - 7 of 8 scenarios driven live against the product
Scenario Result Live Evidence
A captain hold whose blocker was finished and then archived by tasks-axi retention shows as unblocked (live, captain-actionable) in the fleet snapshot, where base showed it as blocked ✅ pass live snapshot-archived-blocker-transcript.txt: base shows unresolved [blocker-a], bucket blocked; target shows unresolved [], bucket live, captain_actionable true. tasks-axi show held-archived reports bloc…
Bearings moves the hold on an archived blocker from the gates list to the open captain decisions ✅ pass live bearings-base-c5f48e4.txt lists held-archived under gates[2]; bearings-target-a36fd40.txt lists it under decisions_open[2] and gates[1] has only held-dangling
A blocker that is still Done in the backlog keeps resolving its dependent (no regression) ✅ pass live held-live has unresolved [] and bucket live at both base and target (transcript)
Adversarial: a blocker id that is in neither the backlog nor the archive stays unresolved ✅ pass live held-dangling blocked-by ghost has unresolved [ghost] and bucket blocked at both base and target (transcript)
Adversarial: a blocker archived unfinished by tasks-axi prune --state queued (a - [ ] archive row) is not treated as Done ✅ pass live held-pruned has unresolved [pruned-blk] and bucket blocked after the prune (transcript, lab-done-archive.md)
Adversarial: an archived Done id re-filed as a live queued task keeps blocking; the live backlog record overrides the archive ✅ pass live After tasks-axi add blocker-a again, the snapshot has held-archived unresolved [blocker-a] and bucket blocked, matching tasks-axi's blocked: yes (transcript)
The snapshot still runs cleanly when no done-archive.md exists ✅ pass live Ran fm-fleet-snapshot.sh --json against the lab with the archive file moved away: exit 0, no stderr
The regression test fails before the fix and passes after it ⏸️ untested no The earlier payload backed this only with a unit-test run (tests/fm-fleet-snapshot-view.test.sh at 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 with tasks-axi add/hold/block/done, and 11 Done tasks made real retention archive blocker-a
  • FM_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 task
  • FM_HOME=$LAB bin/fm-bearings-snapshot.sh, run at base and at target, comparing the decisions_open and gates sections
  • Adversarial: re-filed blocker-a as a new queued task (tasks-axi shows the dependent as blocked) and checked that the snapshot keeps it unresolved
  • Adversarial: tasks-axi prune --state queued --keep 4 archived an unfinished blocker as a - [ ] row, and the snapshot keeps it unresolved
  • Adversarial: blocker id ghost that is in neither the backlog nor the archive (row removed by hand after tasks-axi refused to delete it) stays unresolved
  • Edge case: snapshot with no done-archive.md file present exits 0
  • bash tests/fm-fleet-snapshot-view.test.sh at target (all ok), and the same test file run against the base scripts, where the new archived-blocker test reports not ok (regression proof)
⚠️ **Document** - 1 info
  • ℹ️ bin/fm-fleet-snapshot.sh:136 - The snapshot reads the archive from the fixed path $DATA/done-archive.md rather than the archive key 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.

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Updates fleet snapshot logic to check archived tasks.

The PR appears safe to merge; no outstanding finding or new actionable issue was identified.

Reviews (5) · Last reviewed commit: "no-mistakes(ci): I fixed the Greptile fi..."

Comment thread bin/fm-fleet-snapshot.sh Outdated
Comment thread bin/fm-fleet-snapshot.sh Outdated
Comment thread bin/fm-fleet-snapshot.sh Outdated
@greptile-apps

This comment has been minimized.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: First stamp this fetch vs tip 260c4f089449c33f08c31df714ca9cfe2a25b50a.

HEAD 28bf256a8643d1278fa53fcfc99c5ed5c7ed790c. MERGEABLE/BLOCKED. Fixes #5816 verified (snapshot resolved blocked-by: only from live backlog Done; archived Done left dependents blocked while tasks-axi already unblocked). Attestation MATCH. NM 36581692514 SUCCESS. Behavior CI 36581692635 IN_PROGRESS (serial 1/5/9 + Lint 1). Greptile Review FAILURE — tip adds .tasks.toml archive path resolution + regressions (addresses "configured archives missed"); open Greptile notes remain on large-archive argv size and # inside quoted archive paths. Not escalating while CI unfinished.

Tip vs main (own diff): bin/fm-fleet-snapshot.sh seeds resolved ids from Done archive (configured [markdown] archive else data/done-archive.md); backlog structured rows still override; missing ids stay unresolved; docs + 2 regressions; CI expected snapshot test count 18→20.

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

  1. One captain, one interface — aligns (honest actionable vs blocked holds).
  2. Authority explicit — aligns (no consent widen).
  3. Scripts own mechanics — aligns (scripted archive parse).
  4. Restart non-event — aligns (disk archive is source of truth).
  5. Delegation with a spine — aligns (hold readiness).
  6. Fleet outlives vendor — aligns.
  7. Scope — aligns (fleet-snapshot + hold lifecycle docs + tests; ci.yml count only).

Security: bin/docs/tests/ci count. Clean.
Decision: waiting-ci. Auto-merge when CLEAN and Greptile no longer blocks (or author clears remaining P1s if they keep the check red). Firstmate flag no.

@karotkriss

Copy link
Copy Markdown
Contributor Author

All checks are green on head cbaf113a87704411954981cff7f11028c25ddf83.

@kunchenguid kunchenguid left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: Restamp vs tip 1f2c9548f4680efeda76fabe9f057b4021b97272 (merged #6125/#6126 this pass).

HEAD cbaf113a87704411954981cff7f11028c25ddf83. MERGEABLE/BLOCKED. Fixes #5816 verified (snapshot resolved blocked-by: only from Done rows still in data/backlog.md; archive Done left dependents blocked while tasks-axi already treats them unblocked). Attestation MATCH. CI 36585799109 SUCCESS. NM 36585799096 SUCCESS. Greptile SUCCESS.

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 .github/workflows/ci.yml expected-count 18→21; OAuth lacks workflow scope — same class as prior ci.yml merges). Approving review posted; gh pr merge --squash still refused without --admin. Otherwise auto-merge-ready. Needs captain/workflow-capable squash. Not waiting on author/CI.

…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
@karotkriss
karotkriss force-pushed the fm/fm-up-5816-snapshot-archived-blocker branch from cbaf113 to 168a830 Compare October 3, 2026 00:11
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: restamp vs tip e31bc6e6.

HEAD 168a8304326379752561f9b0ae859d2456e610f3. Attestation MISMATCH (body cbaf113a87704411954981cff7f11028c25ddf83 ≠ HEAD). NM/body-compliance FAILURE for that reason. Behavior CI otherwise SUCCESS; Greptile SUCCESS. MERGEABLE/UNSTABLE. Fixes #5816 still accurate in intent.

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 git push no-mistakes to current HEAD, or (2) prefer concentrating on #6451 (same author, same #5816, newer tip with configured archive path + quote/# + global fallback + live evidence). When #6451 lands this will be closed as leftover with thank-you. Do not treat prior waiting-captain workflow-scope hold as decline of #6451.

Firstmate flag no this pass (attestation/NM red; not otherwise-ready).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bearings reads a blocked-by edge as unresolved once done_keep archives the Done blocker

2 participants