diff --git a/bin/fm-fleet-snapshot.sh b/bin/fm-fleet-snapshot.sh index 4f67b9be00f..90423d7a6ee 100755 --- a/bin/fm-fleet-snapshot.sh +++ b/bin/fm-fleet-snapshot.sh @@ -23,8 +23,12 @@ # normalized current_role, requires_child_metadata, blocked_by_ids, # unresolved_blocker_ids, captain_actionable, hold_set, hold_age_days, # and hold_bucket fields. -# Repeated blocker tokens remain ordered; a blocker resolves only when its -# structured record is Done, and missing ids stay open. +# Repeated blocker tokens remain ordered; a blocker resolves when its +# structured record is Done, or when no record for that id exists at all +# in this snapshot (already torn down or aged out of retention) - it +# stays open only when a record for it exists and is not Done, matching +# tasks-axi's own dependency resolution so the two readers of one backlog +# cannot disagree on whether a since-archived blocker still blocks. # There is no separate decision type: any captain-held task is the same # primitive, whatever kind its row carries. # hold_bucket is the single classification for every captain hold, decided @@ -528,7 +532,7 @@ backlog_json() { # [] - defaults to this home's $BACKLOG . as $record | .unresolved_blocker_ids = [ $record.blocked_by_ids[] as $blocker - | select($resolved_ids[$blocker] != true) + | select(($resolved_ids | has($blocker)) and ($resolved_ids[$blocker] != true)) | $blocker ] | .current_role = diff --git a/tests/fm-bearings-snapshot.test.sh b/tests/fm-bearings-snapshot.test.sh index aae68b78510..9c39241ecde 100755 --- a/tests/fm-bearings-snapshot.test.sh +++ b/tests/fm-bearings-snapshot.test.sh @@ -360,6 +360,7 @@ write_domain_alpha_fixture() { # ## Queued - [ ] legal-release - Release approval blocked-by: external-legal - external legal dependency (repo: sample) (kind: ship) +- [ ] external-legal - External legal review (repo: sample) (kind: ship) (since 2026-07-13) ## Done EOF @@ -803,6 +804,7 @@ test_parent_evidence_reconciles_by_verb_and_key() { ## Queued - [ ] legal-release - Legal release blocked-by: external-legal - legal review (repo: sample) (kind: ship) +- [ ] external-legal - External legal review (repo: sample) (kind: ship) (since 2026-07-11) ## Done EOF @@ -811,6 +813,7 @@ EOF ## Queued - [ ] vendor-release - Vendor release blocked-by: external-vendor - vendor review (repo: sample) (kind: ship) +- [ ] external-vendor - External vendor review (repo: sample) (kind: ship) (since 2026-07-11) ## Done EOF @@ -1228,6 +1231,7 @@ test_undated_hold_phrasing_and_aging_projection() { NOT REQUIRED - the remote decision is moot. - [ ] mate-aged - Remote aged call (repo: firstmate) (kind: captain) (since 2026-06-01) (hold: choose a remote route) (hold-kind: captain) Captain hold set: 2026-06-01T00:00:00Z +- [ ] missing-remote-blocker - Real still-open remote blocker (repo: firstmate) (kind: ship) ## Done EOF @@ -1237,6 +1241,7 @@ EOF ## Queued - [ ] parked-hold - Parked style call (repo: firstmate) (kind: ship) (since 2026-07-10) (hold: not urgent) (hold-kind: captain) - [ ] blocked-parked - Blocked parked call blocked-by: missing-blocker (repo: firstmate) (kind: captain) (hold: parked) (hold-kind: captain) +- [ ] missing-blocker - Real still-open blocker (repo: firstmate) (kind: ship) - [ ] future-parked - Parked style call for later (repo: firstmate) (kind: captain) (hold: parked) (hold-kind: captain) (hold-until: 2026-08-01) - [ ] due-parked - Parked style call now due (repo: firstmate) (kind: captain) (hold: parked) (hold-kind: captain) (hold-until: 2026-07-11) - [ ] due-superseded - Superseded call now due (repo: firstmate) (kind: captain) (hold: SUPERSEDED) (hold-kind: captain) (hold-until: 2026-07-11) @@ -1358,6 +1363,7 @@ test_blocked_deferred_hold_has_concrete_disclosure() { ## Queued - [ ] only-blocked-parked - Blocked parked call blocked-by: missing-blocker (repo: firstmate) (kind: captain) (hold: parked) (hold-kind: captain) (hold-until: 2026-07-11) +- [ ] missing-blocker - Real still-open blocker (repo: firstmate) (kind: ship) ## Done EOF @@ -1388,6 +1394,7 @@ test_revealed_deferred_holds_show_their_deferral_reason() { - [ ] reveal-aged - Aged undated call (repo: firstmate) (kind: captain) (since 2026-06-01) (hold: choose a route) (hold-kind: captain) Captain hold set: 2026-06-01T00:00:00Z - [ ] reveal-live - 123456789012345678901234567890123456789012345678901234567890 (repo: firstmate) (kind: captain) (hold: choose A) (hold-kind: captain) +- [ ] missing-blocker-1234567890123456789012345678901234567890 - Real still-open blocker (repo: firstmate) (kind: ship) ## Done EOF @@ -2317,6 +2324,8 @@ test_working_captain_holds_keep_their_bucket_surfaces() { Captain hold set: 2026-06-01T00:00:00Z ## Queued +- [ ] blocker-alpha-123456789012345678901234567890 - Real still-open blocker alpha (repo: sample) (kind: ship) +- [ ] blocker-beta-123456789012345678901234567890 - Real still-open blocker beta (repo: sample) (kind: ship) ## Done EOF @@ -2830,9 +2839,9 @@ EOF mv "$ha/data/backlog.next" "$ha/data/backlog.md" json=$(run "$home" "$fakebin" --json) printf '%s' "$json" | jq -e ' - (.decisions_open | any(.id == "home-assistant/captain-run") | not) - and (.gates | any(.id == "captain-run" and .owner == "home-assistant" and .blocked_by == "missing")) - ' >/dev/null || fail "a missing Home Assistant blocker was treated as Done: $json" + ([.decisions_open[] | select(.id == "home-assistant/captain-run")] | length) == 1 + and (.gates | any(.id == "captain-run" and .owner == "home-assistant") | not) + ' >/dev/null || fail "a blocker id with no backlog row (already torn down) was not treated as resolved: $json" sed 's/(kind: program)/(kind: mystery)/' "$hibit/data/backlog.md" > "$hibit/data/backlog.next" mv "$hibit/data/backlog.next" "$hibit/data/backlog.md" diff --git a/tests/fm-fleet-snapshot-view.test.sh b/tests/fm-fleet-snapshot-view.test.sh index 71b2acbdc18..623a75d9d6b 100755 --- a/tests/fm-fleet-snapshot-view.test.sh +++ b/tests/fm-fleet-snapshot-view.test.sh @@ -406,9 +406,9 @@ EOF printf '%s' "$out" | jq -e ' .backlog.records[] | select(.id == "captain-run") | .blocked_by_ids == ["worker", "missing"] - and .unresolved_blocker_ids == ["missing"] - and .captain_actionable == false - ' >/dev/null || fail "a missing blocker was incorrectly treated as resolved: $out" + and .unresolved_blocker_ids == [] + and .captain_actionable == true + ' >/dev/null || fail "a blocker id with no backlog row (already torn down) was not treated as resolved: $out" pass "backlog normalization preserves strict roles and resolves every blocker compatibly" }