Skip to content

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

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

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

Conversation

@karotkriss

@karotkriss karotkriss commented Oct 3, 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 treats a blocked-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.
  • The archive path comes from the archive key under [markdown] in the home's .tasks.toml. If that key is not set, it falls back to ~/.tasks-axi/config.toml, then to data/done-archive.md. Relative paths are resolved against the data directory's parent. Quoted values keep any # inside them. The snapshot header comment and docs/captain-hold-lifecycle.md now describe this lookup.
  • tests/fm-fleet-snapshot-view.test.sh gets 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 prune move 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.toml archive 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.

  • Live validation: ✅ go - 7 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Captain-held item whose blocker tasks-axi pruned into data/done-archive.md shows as resolved (live, captain-actionable) in the fleet snapshot ✅ pass live snapshot-base-vs-fix.txt: base keeps held-b2 blocked on blocker-a1 (bug reproduced); target gives unresolved_blocker_ids=[] hold_bucket=live
Bearings lists that item under decisions_open, not gates, once the blocker is archived Done ✅ pass live bearings-base-vs-fix.txt: base gates[2] includes held-b2; fix decisions_open[1] held-b2, gates[1] only dangling-c3
Adversarial: a blocked-by id that exists in neither the backlog nor the archive stays unresolved ✅ pass live snapshot-base-vs-fix.txt and bearings-base-vs-fix.txt: dangling-c3 on nowhere-z9 stays blocked on both base and fix
Blocker archived Done at a custom .tasks.toml [markdown] archive path (with inline comment) resolves ✅ pass live configured-archive-adversarial.txt: held-p1 on blk-x1 in records/retired.md -> unresolved=[] live
Adversarial: a blocker id marked Done in the archive but re-added open in the backlog stays unresolved (the backlog wins, matching tasks-axi) ✅ pass live configured-archive-adversarial.txt: held-q2 on blk-y2 unresolved, bucket blocked; tasks-axi show held-q2 prints blocked: yes
Adversarial: a blocker archived unfinished (unchecked row) is not treated as resolved ✅ pass live configured-archive-adversarial.txt: held-r3 on blk-z3 unresolved, bucket blocked
Adversarial: a missing archive file does not break the snapshot ✅ pass live configured-archive-adversarial.txt: exit=0, held-p1 falls back to unresolved
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=blocked

\### BASE d719ef3: bin/fm-fleet-snapshot.sh --json (after tasks-axi prune archived blocker-a1)
{"id":"held-b2","blocked_by_ids":["blocker-a1"],"unresolved_blocker_ids":["blocker-a1"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"dangling-c3","blocked_by_ids":["nowhere-z9"],"unresolved_blocker_ids":["nowhere-z9"],"hold_bucket":"blocked","captain_actionable":false}

\### FIX 673b2c7: bin/fm-fleet-snapshot.sh --json
{"id":"held-b2","blocked_by_ids":["blocker-a1"],"unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
{"id":"dangling-c3","blocked_by_ids":["nowhere-z9"],"unresolved_blocker_ids":["nowhere-z9"],"hold_bucket":"blocked","captain_actionable":false}
Evidence: 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)

\### BASE d719ef3: bin/fm-bearings-snapshot.sh
schema: fm-bearings.v1
home: tmp/fm-lab.UwRrsh
generated: "2026-10-03T00:39:49Z"
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: []
landed[2]{id,what,artifact,owner}:
  filler-3,filler 3,"-",(main)
  filler-2,filler 2,"-",(main)
gates[2]{id,title,blocked_by,reason,owner,filed}:
  held-b2,captain decision after blocker,blocker-a1,"blocked-by blocker-a1: captain decision …",(main),2026-10-02
  dangling-c3,waits on unknown blocker,nowhere-z9,"blocked-by nowhere-z9: waits on phantom",(main),2026-10-02
reports: []
recorded_prs: []
omitted[7]{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"
  "captain holds bucketed blocked, dated, or aged: 2","--all-decisions"
  live PR discovery + checks,"--include-prs"

\### FIX 673b2c7: bin/fm-bearings-snapshot.sh
schema: fm-bearings.v1
home: tmp/fm-lab.UwRrsh
generated: "2026-10-03T00:39:49Z"
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-b2,held-b2,captain-hold,"captain decision after blocker: captain decision pending",(main)
landed[2]{id,what,artifact,owner}:
  filler-3,filler 3,"-",(main)
  filler-2,filler 2,"-",(main)
gates[1]{id,title,blocked_by,reason,owner,filed}:
  dangling-c3,waits on unknown blocker,nowhere-z9,"blocked-by nowhere-z9: waits on phantom",(main),2026-10-02
reports: []
recorded_prs: []
omitted[7]{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"
  "captain holds bucketed blocked, dated, or aged: 1","--all-decisions"
  live PR discovery + checks,"--include-prs"
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)

\### Lab 2: .tasks.toml [markdown] archive = "records/retired.md"; tasks-axi prune wrote archive there
# held-p1: blocker blk-x1 Done in configured archive -> expect resolved/live
# held-q2: blk-y2 [x] in archive but re-added open in backlog -> expect unresolved (backlog wins)
# held-r3: blk-z3 archived unfinished ([ ]) -> expect unresolved
{"id":"held-p1","blocked_by_ids":["blk-x1"],"unresolved_blocker_ids":[],"hold_bucket":"live","captain_actionable":true}
{"id":"held-q2","blocked_by_ids":["blk-y2"],"unresolved_blocker_ids":["blk-y2"],"hold_bucket":"blocked","captain_actionable":false}
{"id":"held-r3","blocked_by_ids":["blk-z3"],"unresolved_blocker_ids":["blk-z3"],"hold_bucket":"blocked","captain_actionable":false}

\### bearings
decisions_open[1]{id,key,verb,summary,owner}:
  held-p1,held-p1,captain-hold,"held on archived blocker: decide held-p1",(main)
landed: []
gates[3]{id,title,blocked_by,reason,owner,filed}:
  held-q2,held on reopened blocker,blk-y2,"blocked-by blk-y2: decide held-q2",(main),2026-10-02
  held-r3,held on queued-archived blocker,blk-z3,"blocked-by blk-z3: decide held-r3",(main),2026-10-02
  blk-y2,blocker reopened later,"-","-",(main),2026-10-02
reports: []

\### Lab 2 with configured archive file removed (missing archive must not break snapshot)
exit=0
{"id":"held-p1","unresolved_blocker_ids":["blk-x1"],"hold_bucket":"blocked"}
Evidence: Targeted snapshot test file run

Source: Targeted snapshot test file run

ok - empty fleet snapshot and view use explicit absence markers
ok - fixture snapshot covers task rows, backlog rows, pointers, stable ordering, and emission-time event age
ok - home-summary excludes kind=secondmate from unowned_current and terminal_in_flight
ok - undated captain holds age after a configurable threshold, decided only from structured fields
ok - captain-hold buckets are total, mutually exclusive, and never decided by prose
ok - main_inventory discloses orphan/unstructured and clears when inventory is consistent
ok - backlog normalization preserves strict roles and resolves every blocker compatibly
ok - a blocker recorded Done in the archive resolves like one still Done in the backlog, while an archived unfinished or truly dangling blocker stays open
ok - a blocker archived Done at the .tasks.toml [markdown] archive path resolves, and the unconfigured default archive is ignored
ok - a quoted .tasks.toml archive path containing # is read whole
ok - without a project archive key, the last [markdown] archive key in the global tasks-axi config decides the archive
ok - snapshot event hints follow reconciled current state
ok - durable fold keeps an open decision past a later unrelated event
ok - a live secondmate endpoint preserves unrelated open decisions
ok - durable captain-held transfer closes the duplicate live status decision
ok - durable fold clears a decision only on a keyed resolution
ok - a completed scout's stale decision surfaces as a report pointer, not pending
ok - a scout still parked at a decision stays pending (terminal clear does not over-fire)
ok - snapshot includes durable scout reports after teardown
ok - snapshot parses tasks-axi rows and respects operational overrides
ok - fleet view renders the snapshot without secondmate peek guidance
ok - fleet view renders secondmate agent liveness
- Outcome: ⚠️ 1 info across 1 run (2m5s)

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_path only looks at &lt;root&gt;/.tasks.toml. tasks-axi picks the archive path differently (dist/src/config.js resolveConfig): it uses the project .tasks.toml [markdown] archive first, then falls back to $HOME/.tasks-axi/config.toml [markdown] archive, and only after that to &lt;dirname(backlog)&gt;/done-archive.md.

Example: a home with no .tasks.toml, or one whose [markdown] table has no archive key, plus a global config that sets archive. 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 in unresolved_blocker_ids, so the captain hold lands in hold_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.toml fallback), so the archive resolver should match it.

Two smaller differences in the same awk parser (line 143-164):

  • tasks-axi trims inside the header (section[1].trim() === &#34;markdown&#34;), so [ markdown ] is the markdown table, but the awk requires line == &#34;[markdown]&#34; exactly.
  • tasks-axi lets the last archive = assignment win, while the awk exits on the first.

Fix: after the project file, fall back to the [markdown] archive key in $HOME/.tasks-axi/config.toml (an absolute path, or one relative to $root as tasks-axi does), and normalize the header and last-wins handling in the same parser.

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 info
  • ℹ️ bin/fm-fleet-snapshot.sh - Leftover difference from tasks-axi, outside this change's scope: a blocker that tasks-axi prune --state queued archived unfinished (an unchecked row in the archive) counts as unblocked in tasks-axi (tasks-axi show prints blocked: 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.
  • Live validation: ✅ go - 7 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Captain-held item whose blocker tasks-axi pruned into data/done-archive.md shows as resolved (live, captain-actionable) in the fleet snapshot ✅ pass live snapshot-base-vs-fix.txt: base keeps held-b2 blocked on blocker-a1 (bug reproduced); target gives unresolved_blocker_ids=[] hold_bucket=live
Bearings lists that item under decisions_open, not gates, once the blocker is archived Done ✅ pass live bearings-base-vs-fix.txt: base gates[2] includes held-b2; fix decisions_open[1] held-b2, gates[1] only dangling-c3
Adversarial: a blocked-by id that exists in neither the backlog nor the archive stays unresolved ✅ pass live snapshot-base-vs-fix.txt and bearings-base-vs-fix.txt: dangling-c3 on nowhere-z9 stays blocked on both base and fix
Blocker archived Done at a custom .tasks.toml [markdown] archive path (with inline comment) resolves ✅ pass live configured-archive-adversarial.txt: held-p1 on blk-x1 in records/retired.md -> unresolved=[] live
Adversarial: a blocker id marked Done in the archive but re-added open in the backlog stays unresolved (the backlog wins, matching tasks-axi) ✅ pass live configured-archive-adversarial.txt: held-q2 on blk-y2 unresolved, bucket blocked; tasks-axi show held-q2 prints blocked: yes
Adversarial: a blocker archived unfinished (unchecked row) is not treated as resolved ✅ pass live configured-archive-adversarial.txt: held-r3 on blk-z3 unresolved, bucket blocked
Adversarial: a missing archive file does not break the snapshot ✅ pass live configured-archive-adversarial.txt: exit=0, held-p1 falls back to unresolved
  • Created disposable lab homes with bin/fm-lab-home.sh create, then built real backlogs with tasks-axi add/hold/block/done
  • tasks-axi prune --keep 2 to move the Done blocker into data/done-archive.md, then tasks-axi ready --include-held and tasks-axi show held-b2 (reports blocked: no)
  • Ran base d719ef3 fm-fleet-snapshot.sh --json against the same lab (bug reproduced: held-b2 unresolved=[blocker-a1], bucket=blocked) and target bin/fm-fleet-snapshot.sh --json (unresolved=[], bucket=live, captain_actionable=true)
  • Ran base vs target bin/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 = &#34;records/retired.md&#34; # 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 by prune --state queued
  • Removed 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.

@greptile-apps

greptile-apps Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Updates fleet snapshot logic to recognize archived blockers.

The PR is not yet safe to merge because archive-only Done blockers remain blocked on hosts without Perl.

Reviews (2) · Last reviewed commit: "no-mistakes(ci): I fixed the finding (ci..."

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

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: first stamp this fetch vs tip e31bc6e6 (just merged #5489).

HEAD a2087695b6a0d6ae0c2e8042d73509f71be7896c. Attestation MATCH. NM SUCCESS. CI SUCCESS. Greptile FAILURE (open P1). MERGEABLE/UNSTABLE. Fixes #5816 verified.

contract-class: restore — concrete existing fleet-snapshot blocked-by: resolution vs Done archive was incomplete after retention; tip also reads configured [markdown] archive (project → global → default). Not a new Bearings/observer surface.

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 done_archive_path — when data is a symlink, resolve the data directory (e.g. pwd -P / fm_backlog_root) before choosing the archive root so symlink homes match tasks-axi. Please address and re-push / rebind attestation if HEAD moves.

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 .github/workflows/ci.yml expected count 18→22 — squash may need workflow-capable token when otherwise green (same class as prior ci.yml bumps). Not otherwise-ready this pass (Greptile P1). Firstmate flag no.

…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`
Comment thread bin/fm-fleet-snapshot.sh
}
done_archive_path() {
local root archive
root=$(fm_backlog_root "$DATA") || return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Archive lookup silently fails

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.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: restamp vs tip 1f3e7696 (just merged #5863).

HEAD 884c4484b4d03268228165336b9b32d4e6208759. Attestation MATCH. NM SUCCESS. CI SUCCESS. Greptile FAILURE (open P1). MERGEABLE/BLOCKED. Fixes #5816 verified.

contract-class: restore — concrete existing fleet-snapshot blocked-by: resolution vs Done archive was incomplete after retention; tip also reads configured [markdown] archive (project → global → default via fm_backlog_root). Not a new Bearings/observer surface. Tip moved a2087695 → 884c4484 to prefer fm_backlog_root (addresses prior symlink P1); Greptile now has a new open P1: if Perl is unavailable, fm_backlog_root fails and archive lookup silently falls through to /dev/null.

VISION (per rule):

  1. One captain, one interface — aligns (honest hold/gate presentation once archived Done blockers resolve).
  2. Authority explicit — aligns (no new autonomy; snapshot read-only).
  3. Scripts vs agents — aligns (scripts own archive parse).
  4. Restart non-event — aligns (disk archive + backlog).
  5. Delegation with spine — n/a.
  6. Fleet outlives vendor — n/a.
  7. Scope — aligns (fleet snapshot + hold-lifecycle docs + tests).

Waiting-author: please address Greptile P1 on Perl-unavailable fm_backlog_root failure so Done-archive resolution still works (or fails loudly) without Perl. Do not merge until Greptile green.

@karotkriss

Copy link
Copy Markdown
Contributor Author

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.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: RELOOK vs tip 1f3e7696. Diff reviewed (done_archive_path via fm_backlog_root; archive Done ids in backlog_json; symlink + config-path tests; ci count 18→23).

Contract-class: restore — concrete existing fleet-snapshot blocked-by: resolution vs Done archive was incomplete after done_keep retention (#5816). Tip also honors configured [markdown] archive (project → global → default) via fm_backlog_root. Not a new Bearings/observer/wake surface.

VISION (per /workspace/VISION.md):

  1. One captain, one interface — aligns (honest hold/gate presentation once archived Done blockers resolve).
  2. Authority is explicit and never inferred — aligns (read-only snapshot; no new autonomy grant).
  3. Scripts own the mechanics, agents own the judgment — aligns (scripts own archive parse + blocker resolve).
  4. A restart is a non-event — aligns (disk archive + backlog).
  5. Delegation with a spine — n/a.
  6. The fleet outlives any vendor — n/a.
  7. Scope — aligns (fleet snapshot + hold-lifecycle docs + test count; not workshop).

Attestation MATCH HEAD 884c4484. NM SUCCESS. CI SUCCESS. Greptile FAILURE (open P1). MERGEABLE/BLOCKED. Closes #5816: yes (Fixes #5816; body matches issue).

Author rebuttal (2026-10-04T01:34Z): without Perl, snapshot already fails earlier via fm_hold_reason_decode_stream (and fm_backlog_root itself uses perl for control-byte checks). Verified on tip: backlog_json pipes through fm_hold_reason_decode_stream json. Firstmate accepts the technical claim that a Perl-less archive fallback is dead code on this path.

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.

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