From 68309e664069b565779246742f4e2f870e1fa48d Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Sat, 1 Aug 2026 14:53:26 -0700 Subject: [PATCH 1/4] Add beads-backend fleet-scoping to snapshot and Bearings (Stage 0+1) Stage 0 of the beads-authority migration (data/beads-authority-migration-scout/report.md section 4): define fm_beads_fleet_label() in bin/fm-tasks-axi-lib.sh as the single owner of the firstmate-fleet label ("fleet:firstmate" by default) that every bead firstmate creates for its own dispatched work should carry, so a `task list --label` call can scope to firstmate's fleet instead of the shared federated store's full cross-project set. Stage 1: bin/fm-fleet-snapshot.sh's backlog_json gains a beads-backed branch (backlog_json_beads) that reads this fleet's open/in_progress/blocked beads, scoped by that label, into the same fm-fleet-snapshot.v1 records[] shape the existing markdown parser (renamed to backlog_json_markdown) produces, when config/backlog-backend=beads. It falls back to the markdown parse on a beads read failure. Beads-sourced records are always structured:true with requires_child_metadata:false and captain_actionable:false, so main_inventory_json never flags them as orphaned or unstructured; dependency-graph fields and state/*.meta correlation are left for a later stage per the report. bin/fm-bearings-snapshot.sh needs no functional change: it only ever reads the canonical snapshot's .backlog.records[] and never re-parses data/backlog.md itself, so it inherits beads-backend awareness for free. Added a header note documenting that. Default-backend (tasks-axi/manual) output is unchanged: backlog_json_markdown's body is untouched, and the beads-only fields (source, fleet_label) only ever appear on the beads path. A regression test asserts the default path carries neither field. Documented the label convention and snapshot/Bearings beads-awareness in docs/configuration.md's "Backlog backend" section. --- bin/fm-bearings-snapshot.sh | 7 +++ bin/fm-fleet-snapshot.sh | 93 +++++++++++++++++++++++++++- bin/fm-tasks-axi-lib.sh | 11 ++++ docs/configuration.md | 4 ++ tests/fm-beads-backend.test.sh | 12 ++++ tests/fm-fleet-snapshot-view.test.sh | 65 +++++++++++++++++++ 6 files changed, 191 insertions(+), 1 deletion(-) diff --git a/bin/fm-bearings-snapshot.sh b/bin/fm-bearings-snapshot.sh index 7564f9ffac..8189357685 100755 --- a/bin/fm-bearings-snapshot.sh +++ b/bin/fm-bearings-snapshot.sh @@ -23,6 +23,13 @@ # backlog roles, unresolved blockers, and captain actionability. It never infers # decisions from report or visual-review prose or reimplements snapshot semantics. # +# Because this wrapper only ever reads the canonical snapshot's .backlog.records[] +# and never re-parses data/backlog.md itself, it inherits beads-backend awareness +# for free the moment fm-fleet-snapshot.sh's backlog_json gains a beads branch +# (beads-authority migration Stage 1): no code change is needed here to surface +# beads-sourced in-flight/queued records. See fm-fleet-snapshot.sh's backlog_json +# and backlog_json_beads, and docs/configuration.md "Backlog backend". +# # Main-home inventory validity comes from the canonical snapshot's main_inventory # object (orphan structured in-flight without meta, unstructured current rows). # Bearings never invents Underway rows from backlog-only ids; it discloses those diff --git a/bin/fm-fleet-snapshot.sh b/bin/fm-fleet-snapshot.sh index 1dee81bb84..e0ded2c9c7 100755 --- a/bin/fm-fleet-snapshot.sh +++ b/bin/fm-fleet-snapshot.sh @@ -94,6 +94,7 @@ FM_SNAPSHOT_REGISTRY_LINES=${FM_SNAPSHOT_REGISTRY_LINES:-256} FM_SNAPSHOT_REGISTRY_BYTES=${FM_SNAPSHOT_REGISTRY_BYTES:-65536} FM_SNAPSHOT_REGISTRY_RECORDS=${FM_SNAPSHOT_REGISTRY_RECORDS:-40} FM_SNAPSHOT_REGISTRY_TIMEOUT=${FM_SNAPSHOT_REGISTRY_TIMEOUT:-2} +FM_SNAPSHOT_BEADS_LIMIT=${FM_SNAPSHOT_BEADS_LIMIT:-200} validate_positive_bound() { # case "$2" in ''|*[!0-9]*|0) @@ -124,6 +125,7 @@ validate_positive_bound FM_SNAPSHOT_REGISTRY_LINES "$FM_SNAPSHOT_REGISTRY_LINES" validate_positive_bound FM_SNAPSHOT_REGISTRY_BYTES "$FM_SNAPSHOT_REGISTRY_BYTES" validate_positive_bound FM_SNAPSHOT_REGISTRY_RECORDS "$FM_SNAPSHOT_REGISTRY_RECORDS" validate_positive_bound FM_SNAPSHOT_REGISTRY_TIMEOUT "$FM_SNAPSHOT_REGISTRY_TIMEOUT" +validate_positive_bound FM_SNAPSHOT_BEADS_LIMIT "$FM_SNAPSHOT_BEADS_LIMIT" # shellcheck source=bin/fm-backend.sh # shellcheck disable=SC1091 @@ -134,6 +136,9 @@ validate_positive_bound FM_SNAPSHOT_REGISTRY_TIMEOUT "$FM_SNAPSHOT_REGISTRY_TIME # shellcheck source=bin/fm-ff-lib.sh # shellcheck disable=SC1091 . "$SCRIPT_DIR/fm-ff-lib.sh" # validate_secondmate_home: shared seeded-home boundary checks +# shellcheck source=bin/fm-tasks-axi-lib.sh +# shellcheck disable=SC1091 +. "$SCRIPT_DIR/fm-tasks-axi-lib.sh" # fm_beads_backend_available, fm_beads_fleet_label usage() { cat <<'EOF' @@ -247,7 +252,11 @@ first_pr_url_in_file() { # grep -Eo 'https?://[^[:space:])"]+/pull/[0-9]+' "$1" 2>/dev/null | head -1 } -backlog_json() { # [] - defaults to this home's $BACKLOG +# backlog_json_markdown [] - parses data/backlog.md into the +# fm-fleet-snapshot.v1 records[] schema. This is the default-backend body; +# its output must stay byte-identical, so beads-backend logic lives in the +# sibling backlog_json_beads instead of branching inline here. +backlog_json_markdown() { # [] - defaults to this home's $BACKLOG local backlog=${1:-$BACKLOG} if [ ! -f "$backlog" ]; then jq -n --arg path "$backlog" '{path:$path,present:false,records:[]}' @@ -397,6 +406,88 @@ backlog_json() { # [] - defaults to this home's $BACKLOG ' < "$backlog" } +# backlog_json_beads [] - beads-authority migration Stage 1 +# (data/beads-authority-migration-scout/report.md section 4). Reads this +# fleet's in-flight/queued beads, scoped by fm_beads_fleet_label, into the +# same fm-fleet-snapshot.v1 records[] shape backlog_json_markdown produces. +# Only status open/in_progress/blocked beads are fetched (closed beads are +# out of scope for an in-flight/queued view). Dependency-graph fields +# (blocked_by_ids, hold_reason/hold_kind) and local state/*.meta correlation +# are later-stage work per the report and are left empty/null here; a +# beads-sourced record is always structured:true with +# requires_child_metadata:false and captain_actionable:false so it can never +# be mistaken for an orphaned or unstructured row by main_inventory_json. +# is accepted for signature parity with backlog_json_markdown +# but only used as the markdown-fallback path when the beads read fails. +backlog_json_beads() { # [] - defaults to this home's $BACKLOG + local backlog=${1:-$BACKLOG} label out + label=$(fm_beads_fleet_label) + if ! out=$(task list --label "$label" --status open,in_progress,blocked \ + --limit "$FM_SNAPSHOT_BEADS_LIMIT" --json 2>/dev/null) \ + || ! printf '%s' "$out" | jq -e 'type == "array"' >/dev/null 2>&1; then + backlog_json_markdown "$backlog" + return 0 + fi + + printf '%s' "$out" | jq --arg path "$backlog" --arg label "$label" ' + def state_of: + if .status == "in_progress" then "in_flight" else "queued" end; + def body_excerpt: + (.description // "") as $d + | if ($d | length) == 0 then null else ($d[:240]) end; + {path:$path, + present:true, + source:"beads", + fleet_label:$label, + records:[ + to_entries[] | (.key + 1) as $order | .value | { + order:$order, + state:state_of, + structured:true, + id:.id, + checked:false, + title:.title, + repo:null, + kind:null, + priority:(.priority | tostring), + hold_reason:null, + hold_kind:null, + blocked_by:null, + blocked_by_ids:[], + blocked_reason:null, + since:null, + merged:null, + reported:null, + done:null, + completion:{verb:null,date:null}, + links:[], + pr_url:null, + report_path:null, + local_note:null, + raw:(.id + " - " + .title), + body_lines:[], + body_excerpt:body_excerpt, + unresolved_blocker_ids:[], + current_role:(if state_of == "in_flight" then "worker" else "queued" end), + requires_child_metadata:false, + captain_actionable:false + } + ]} + ' +} + +# backlog_json [] - dispatcher. Reads from the beads store when +# config/backlog-backend=beads (mirroring fm-session-start.sh's +# print_backlog_compact gate), otherwise parses data/backlog.md exactly as +# before so default-backend output stays byte-identical. +backlog_json() { # [] - defaults to this home's $BACKLOG + if fm_beads_backend_available "$CONFIG"; then + backlog_json_beads "${1:-$BACKLOG}" + else + backlog_json_markdown "${1:-$BACKLOG}" + fi +} + task_json_lines() { local meta id kind harness mode yolo project worktree home projects backend target status_log report_path local pr pr_source event_json current_json endpoint_exists agent_alive meta_json status_json report_json worktree_json home_json diff --git a/bin/fm-tasks-axi-lib.sh b/bin/fm-tasks-axi-lib.sh index 2ff9363dd5..f33f694e19 100644 --- a/bin/fm-tasks-axi-lib.sh +++ b/bin/fm-tasks-axi-lib.sh @@ -83,3 +83,14 @@ fm_beads_backend_available() { command -v task >/dev/null 2>&1 || return 1 task list --limit 1 >/dev/null 2>&1 } + +# fm_beads_fleet_label - the label firstmate applies to every bead it creates +# for its own dispatched work (beads-authority migration Stage 0; see +# data/beads-authority-migration-scout/report.md section 4 and +# docs/configuration.md "Backlog backend"). A `task list --label ` call +# scopes to firstmate's fleet instead of the shared federated store's full +# cross-project set. FM_BEADS_FLEET_LABEL is an override for test fixtures; +# production code should call this function rather than hardcoding the label. +fm_beads_fleet_label() { + printf '%s\n' "${FM_BEADS_FLEET_LABEL:-fleet:firstmate}" +} diff --git a/docs/configuration.md b/docs/configuration.md index 903112b97f..6e85667a7c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -50,6 +50,10 @@ Set the local, gitignored `config/backlog-backend` file to `manual` to force man Absent or `tasks-axi` selects the default tasks-axi backend. The file format is unchanged in tasks-axi and manual modes; both produce the same `## In flight`, `## Queued`, and `## Done` sections in `data/backlog.md`. The beads backend does not use `data/backlog.md`; all backlog state lives in the beads store and is queried dynamically at session start. +Under the beads backend, firstmate labels every bead it creates for its own dispatched work with a firstmate-fleet label, `fleet:firstmate` by default (overridable only for test fixtures via `FM_BEADS_FLEET_LABEL`), so a `task list --label ` call scopes to firstmate's fleet instead of surfacing the shared federated store's full cross-project set. +`bin/fm-tasks-axi-lib.sh`'s `fm_beads_fleet_label` is the single owner of that label; read it from there rather than hardcoding it. +The structured fleet snapshot (`bin/fm-fleet-snapshot.sh --json`) and Bearings (`bin/fm-bearings-snapshot.sh`) also read this fleet's in-flight/queued beads, scoped by that label, when the beads backend is selected; with any other backend their output is unchanged. +That beads-sourced view currently covers only status open/in_progress/blocked beads mapped to `records[]` state `queued`/`in_flight`; per-bead dependency graphs and correlation with local `state/*.meta` are not yet wired through, so `blocked_by_ids` is always empty and `requires_child_metadata`/`captain_actionable` are always false for a beads-sourced record. ## Runtime backend (config/backend / FM_BACKEND) diff --git a/tests/fm-beads-backend.test.sh b/tests/fm-beads-backend.test.sh index 6d14028867..4f576f22c0 100644 --- a/tests/fm-beads-backend.test.sh +++ b/tests/fm-beads-backend.test.sh @@ -83,10 +83,22 @@ test_backend_value_whitespace() { [ "$value" = "beads" ] || fail "backend value should strip whitespace, got: $value" } +# Test: fm_beads_fleet_label() defaults to fleet:firstmate and honors the +# test-only FM_BEADS_FLEET_LABEL override (beads-authority migration Stage 0). +test_beads_fleet_label() { + local value + value=$(unset FM_BEADS_FLEET_LABEL; fm_beads_fleet_label) + [ "$value" = "fleet:firstmate" ] || fail "default fleet label should be fleet:firstmate, got: $value" + + value=$(FM_BEADS_FLEET_LABEL="fleet:example" fm_beads_fleet_label) + [ "$value" = "fleet:example" ] || fail "fleet label override should win, got: $value" +} + # Run all tests test_beads_backend_value test_beads_backend_available test_tasks_axi_backend_false_for_beads test_backend_value_whitespace +test_beads_fleet_label echo "ok - all beads backend tests passed" diff --git a/tests/fm-fleet-snapshot-view.test.sh b/tests/fm-fleet-snapshot-view.test.sh index f47c70f2fa..ec3bfd4df9 100755 --- a/tests/fm-fleet-snapshot-view.test.sh +++ b/tests/fm-fleet-snapshot-view.test.sh @@ -779,6 +779,69 @@ test_parked_scout_decision_stays_pending() { pass "a scout still parked at a decision stays pending (terminal clear does not over-fire)" } +# beads-authority migration Stage 1 +# (data/beads-authority-migration-scout/report.md section 4): the snapshot +# reads this fleet's in-flight/queued state from the beads store, scoped by +# fm_beads_fleet_label, when config/backlog-backend=beads. +test_beads_backend_snapshot_scopes_by_fleet_label() { + local home fakebin out + home=$(make_home beads-backend) + printf 'beads\n' > "$home/config/backlog-backend" + fakebin=$(make_fakebin "$home") + cat > "$fakebin/task" <<'SH' +#!/usr/bin/env bash +set -u +case "$*" in + 'list --limit 1') + printf '[]\n' + exit 0 + ;; + 'list --label fleet:firstmate --status open,in_progress,blocked --limit 200 --json') + cat <<'JSON' +[ + {"id":"task-aaa","title":"Queued bead","description":"queued body","status":"open","priority":2}, + {"id":"task-bbb","title":"In flight bead","description":"","status":"in_progress","priority":0} +] +JSON + exit 0 + ;; + *) + printf 'unexpected task invocation: %s\n' "$*" >&2 + exit 1 + ;; +esac +SH + chmod +x "$fakebin/task" + out=$(PATH="$fakebin:$PATH" FM_HOME="$home" "$SNAPSHOT" --json) + printf '%s' "$out" | jq -e . >/dev/null || fail "beads-backed snapshot must be valid JSON: $out" + printf '%s' "$out" | jq -e ' + .backlog.source == "beads" + and .backlog.fleet_label == "fleet:firstmate" + and .backlog.present == true + ' >/dev/null || fail "beads-backed snapshot missing beads-source markers: $out" + printf '%s' "$out" | jq -e ' + (.backlog.records[] | select(.id == "task-aaa") | .state) == "queued" + and (.backlog.records[] | select(.id == "task-bbb") | .state) == "in_flight" + ' >/dev/null || fail "beads status open/in_progress was not mapped to record state queued/in_flight: $out" + printf '%s' "$out" | jq -e '.main_inventory.valid == true' >/dev/null \ + || fail "beads-sourced records must never trip the orphan/unstructured inventory checks: $out" + pass "beads backend snapshot reads fleet-scoped in-flight/queued beads" +} + +# Regression guard for the byte-identical default-backend requirement: adding +# the beads branch must not add fields to the markdown backlog_json path. +test_default_backend_backlog_json_has_no_beads_fields() { + local home fakebin out + home=$(make_home default-backend-beads-regression) + write_fixture "$home" + fakebin=$(make_fakebin "$home") + out=$(PATH="$fakebin:$PATH" FM_HOME="$home" "$SNAPSHOT" --json) + printf '%s' "$out" | jq -e ' + (.backlog | has("source") | not) and (.backlog | has("fleet_label") | not) + ' >/dev/null || fail "default-backend backlog_json must stay byte-identical: unexpected beads-only field: $out" + pass "default backend backlog_json carries no beads-only fields" +} + test_empty_fleet_json test_fixture_snapshot_json test_main_inventory_orphan_and_unstructured_disclosure @@ -794,3 +857,5 @@ test_scout_reports_include_teardown_reports test_backlog_tasks_axi_forms_and_overrides test_view_renders_snapshot test_view_renders_dead_secondmate_agent_status +test_beads_backend_snapshot_scopes_by_fleet_label +test_default_backend_backlog_json_has_no_beads_fields From a155fb59a61da03a4cbe77ab570d06fa72a3fd6b Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Sat, 1 Aug 2026 15:07:45 -0700 Subject: [PATCH 2/4] no-mistakes(review): Bound beads task list read with timeout, guard null id/title, disclose truncation --- bin/fm-fleet-snapshot.sh | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/bin/fm-fleet-snapshot.sh b/bin/fm-fleet-snapshot.sh index e0ded2c9c7..bbd95db680 100755 --- a/bin/fm-fleet-snapshot.sh +++ b/bin/fm-fleet-snapshot.sh @@ -95,6 +95,7 @@ FM_SNAPSHOT_REGISTRY_BYTES=${FM_SNAPSHOT_REGISTRY_BYTES:-65536} FM_SNAPSHOT_REGISTRY_RECORDS=${FM_SNAPSHOT_REGISTRY_RECORDS:-40} FM_SNAPSHOT_REGISTRY_TIMEOUT=${FM_SNAPSHOT_REGISTRY_TIMEOUT:-2} FM_SNAPSHOT_BEADS_LIMIT=${FM_SNAPSHOT_BEADS_LIMIT:-200} +FM_SNAPSHOT_BEADS_TIMEOUT=${FM_SNAPSHOT_BEADS_TIMEOUT:-4} validate_positive_bound() { # case "$2" in ''|*[!0-9]*|0) @@ -126,6 +127,7 @@ validate_positive_bound FM_SNAPSHOT_REGISTRY_BYTES "$FM_SNAPSHOT_REGISTRY_BYTES" validate_positive_bound FM_SNAPSHOT_REGISTRY_RECORDS "$FM_SNAPSHOT_REGISTRY_RECORDS" validate_positive_bound FM_SNAPSHOT_REGISTRY_TIMEOUT "$FM_SNAPSHOT_REGISTRY_TIMEOUT" validate_positive_bound FM_SNAPSHOT_BEADS_LIMIT "$FM_SNAPSHOT_BEADS_LIMIT" +validate_positive_bound FM_SNAPSHOT_BEADS_TIMEOUT "$FM_SNAPSHOT_BEADS_TIMEOUT" # shellcheck source=bin/fm-backend.sh # shellcheck disable=SC1091 @@ -420,16 +422,23 @@ backlog_json_markdown() { # [] - defaults to this home's $BACKLOG # is accepted for signature parity with backlog_json_markdown # but only used as the markdown-fallback path when the beads read fails. backlog_json_beads() { # [] - defaults to this home's $BACKLOG - local backlog=${1:-$BACKLOG} label out + local backlog=${1:-$BACKLOG} label out truncated label=$(fm_beads_fleet_label) - if ! out=$(task list --label "$label" --status open,in_progress,blocked \ - --limit "$FM_SNAPSHOT_BEADS_LIMIT" --json 2>/dev/null) \ + if ! out=$(run_timed "$FM_SNAPSHOT_BEADS_TIMEOUT" task list --label "$label" \ + --status open,in_progress,blocked --limit "$FM_SNAPSHOT_BEADS_LIMIT" \ + --json 2>/dev/null) \ || ! printf '%s' "$out" | jq -e 'type == "array"' >/dev/null 2>&1; then backlog_json_markdown "$backlog" return 0 fi - printf '%s' "$out" | jq --arg path "$backlog" --arg label "$label" ' + truncated=false + if [ "$(printf '%s' "$out" | jq 'length')" -ge "$FM_SNAPSHOT_BEADS_LIMIT" ]; then + truncated=true + fi + + printf '%s' "$out" | jq --arg path "$backlog" --arg label "$label" \ + --argjson truncated "$truncated" --argjson limit "$FM_SNAPSHOT_BEADS_LIMIT" ' def state_of: if .status == "in_progress" then "in_flight" else "queued" end; def body_excerpt: @@ -439,14 +448,16 @@ backlog_json_beads() { # [] - defaults to this home's $BACKLOG present:true, source:"beads", fleet_label:$label, + records_truncated:$truncated, + records_limit:$limit, records:[ - to_entries[] | (.key + 1) as $order | .value | { + to_entries[] | (.key + 1) as $order | .value | ((.id // "?") as $id | (.title // "(untitled)") as $title | { order:$order, state:state_of, structured:true, - id:.id, + id:$id, checked:false, - title:.title, + title:$title, repo:null, kind:null, priority:(.priority | tostring), @@ -464,24 +475,35 @@ backlog_json_beads() { # [] - defaults to this home's $BACKLOG pr_url:null, report_path:null, local_note:null, - raw:(.id + " - " + .title), + raw:($id + " - " + $title), body_lines:[], body_excerpt:body_excerpt, unresolved_blocker_ids:[], current_role:(if state_of == "in_flight" then "worker" else "queued" end), requires_child_metadata:false, captain_actionable:false - } + }) ]} ' } +# fm_snapshot_beads_backend_available - same check as +# fm_beads_backend_available (fm-tasks-axi-lib.sh), but bounds the probe's +# `task list` subprocess with FM_SNAPSHOT_BEADS_TIMEOUT so a hung or +# lock-contended federated store cannot stall the snapshot indefinitely. +fm_snapshot_beads_backend_available() { # + local config_dir=$1 + [ "$(fm_backlog_backend_value "$config_dir")" = beads ] || return 1 + command -v task >/dev/null 2>&1 || return 1 + run_timed "$FM_SNAPSHOT_BEADS_TIMEOUT" task list --limit 1 >/dev/null 2>&1 +} + # backlog_json [] - dispatcher. Reads from the beads store when # config/backlog-backend=beads (mirroring fm-session-start.sh's # print_backlog_compact gate), otherwise parses data/backlog.md exactly as # before so default-backend output stays byte-identical. backlog_json() { # [] - defaults to this home's $BACKLOG - if fm_beads_backend_available "$CONFIG"; then + if fm_snapshot_beads_backend_available "$CONFIG"; then backlog_json_beads "${1:-$BACKLOG}" else backlog_json_markdown "${1:-$BACKLOG}" From 5e7b02034019b8223f66fd623ac3de8d01257297 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Sat, 1 Aug 2026 15:19:18 -0700 Subject: [PATCH 3/4] no-mistakes(document): Sync docs: document beads backlog snapshot shape, env bounds, unwired fleet label --- bin/fm-fleet-snapshot.sh | 10 ++++++++++ bin/fm-tasks-axi-lib.sh | 12 +++++++----- docs/configuration.md | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bin/fm-fleet-snapshot.sh b/bin/fm-fleet-snapshot.sh index bbd95db680..b176d0dff5 100755 --- a/bin/fm-fleet-snapshot.sh +++ b/bin/fm-fleet-snapshot.sh @@ -20,6 +20,13 @@ # requires_child_metadata, blocked_by_ids, unresolved_blocker_ids, and # captain_actionable fields. Repeated blocker tokens remain ordered; a blocker # resolves only when its structured record is Done, and missing ids stay open. +# When config/backlog-backend=beads, backlog gains source:"beads", +# fleet_label (the queried fleet label), records_truncated, and +# records_limit, and records[] holds this fleet's open/in_progress/blocked +# beads (state queued/in_flight only, always structured:true with empty +# blocked_by_ids and requires_child_metadata/captain_actionable false); a +# failed or unavailable beads read falls back to the data/backlog.md shape +# above with no beads-only fields. Default-backend output is unaffected. # tasks[]: one row per state/.meta, sorted by id. # current_state is parsed from bin/fm-crew-state.sh and preserves # state, source, detail, and raw line separately. @@ -168,6 +175,9 @@ FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT, with truncation disclosed in the result. The registered secondmate table uses FM_SNAPSHOT_REGISTRY_LINES, FM_SNAPSHOT_REGISTRY_BYTES, FM_SNAPSHOT_REGISTRY_RECORDS, and FM_SNAPSHOT_REGISTRY_TIMEOUT, with unavailability and truncation disclosed. +When config/backlog-backend=beads, the beads-backed backlog read uses +FM_SNAPSHOT_BEADS_LIMIT (default 200) and FM_SNAPSHOT_BEADS_TIMEOUT (default +4), with truncation disclosed via records_truncated/records_limit. EOF } diff --git a/bin/fm-tasks-axi-lib.sh b/bin/fm-tasks-axi-lib.sh index f33f694e19..8ca6cfa13b 100644 --- a/bin/fm-tasks-axi-lib.sh +++ b/bin/fm-tasks-axi-lib.sh @@ -84,13 +84,15 @@ fm_beads_backend_available() { task list --limit 1 >/dev/null 2>&1 } -# fm_beads_fleet_label - the label firstmate applies to every bead it creates -# for its own dispatched work (beads-authority migration Stage 0; see -# data/beads-authority-migration-scout/report.md section 4 and +# fm_beads_fleet_label - the label firstmate's own dispatched-work beads are +# meant to carry once bead creation is wired to it (beads-authority migration +# Stage 0; see data/beads-authority-migration-scout/report.md section 4 and # docs/configuration.md "Backlog backend"). A `task list --label ` call # scopes to firstmate's fleet instead of the shared federated store's full -# cross-project set. FM_BEADS_FLEET_LABEL is an override for test fixtures; -# production code should call this function rather than hardcoding the label. +# cross-project set. As of Stage 1, only reads (fm-fleet-snapshot.sh) use this +# label; no code in bin/ creates a bead with it yet. FM_BEADS_FLEET_LABEL is +# an override for test fixtures; production code should call this function +# rather than hardcoding the label. fm_beads_fleet_label() { printf '%s\n' "${FM_BEADS_FLEET_LABEL:-fleet:firstmate}" } diff --git a/docs/configuration.md b/docs/configuration.md index 6e85667a7c..34698441ee 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -50,7 +50,7 @@ Set the local, gitignored `config/backlog-backend` file to `manual` to force man Absent or `tasks-axi` selects the default tasks-axi backend. The file format is unchanged in tasks-axi and manual modes; both produce the same `## In flight`, `## Queued`, and `## Done` sections in `data/backlog.md`. The beads backend does not use `data/backlog.md`; all backlog state lives in the beads store and is queried dynamically at session start. -Under the beads backend, firstmate labels every bead it creates for its own dispatched work with a firstmate-fleet label, `fleet:firstmate` by default (overridable only for test fixtures via `FM_BEADS_FLEET_LABEL`), so a `task list --label ` call scopes to firstmate's fleet instead of surfacing the shared federated store's full cross-project set. +Under the beads backend, firstmate's own dispatched-work beads are meant to carry a firstmate-fleet label, `fleet:firstmate` by default (overridable only for test fixtures via `FM_BEADS_FLEET_LABEL`), once bead creation is wired to it, so a `task list --label ` call scopes to firstmate's fleet instead of surfacing the shared federated store's full cross-project set; as of Stage 1 no `bin/` code creates a bead with that label yet, only the reads below query by it. `bin/fm-tasks-axi-lib.sh`'s `fm_beads_fleet_label` is the single owner of that label; read it from there rather than hardcoding it. The structured fleet snapshot (`bin/fm-fleet-snapshot.sh --json`) and Bearings (`bin/fm-bearings-snapshot.sh`) also read this fleet's in-flight/queued beads, scoped by that label, when the beads backend is selected; with any other backend their output is unchanged. That beads-sourced view currently covers only status open/in_progress/blocked beads mapped to `records[]` state `queued`/`in_flight`; per-bead dependency graphs and correlation with local `state/*.meta` are not yet wired through, so `blocked_by_ids` is always empty and `requires_child_metadata`/`captain_actionable` are always false for a beads-sourced record. From c5e44a0566a106613529a1dd73212524c163f0e1 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Sat, 1 Aug 2026 15:50:14 -0700 Subject: [PATCH 4/4] no-mistakes: apply CI fixes --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 407a29c88f..e33596f88f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -334,8 +334,8 @@ jobs: snapshot_output=$(/bin/bash tests/fm-fleet-snapshot-view.test.sh) printf '%s\n' "$snapshot_output" snapshot_count=$(printf '%s\n' "$snapshot_output" | grep -c '^ok - ') - [ "$snapshot_count" -eq 15 ] || { - echo "::error::expected 15 snapshot/fleet-view tests, got $snapshot_count" + [ "$snapshot_count" -eq 17 ] || { + echo "::error::expected 17 snapshot/fleet-view tests, got $snapshot_count" exit 1 }