Skip to content

fix(herdr): bump min protocol floor to 16 post-0.7.2 upgrade#15

Merged
mielyemitchell merged 3 commits into
mainfrom
fm/herdr-proto-floor-16
Jul 11, 2026
Merged

fix(herdr): bump min protocol floor to 16 post-0.7.2 upgrade#15
mielyemitchell merged 3 commits into
mainfrom
fm/herdr-proto-floor-16

Conversation

@mielyemitchell

Copy link
Copy Markdown
Owner

Intent

Harden herdr backend by bumping minimum protocol floor from 14 to 16 post-0.7.2 upgrade. Handshake is exact-match per release; a floor of 14 would allow a stale 0.7.1 binary to pass the version check while being unable to communicate with the live protocol-16 server. Updated FM_BACKEND_HERDR_MIN_PROTOCOL constant and documented the new verified baseline (0.7.2, protocol 16) with full empirical evidence from the smoke test (spawn, peek, send, crew-state, teardown passes; task herdr-smoke-072, mpf worktree, 2026-07-11).

What Changed

  • Raised FM_BACKEND_HERDR_MIN_PROTOCOL from 14 to 16 in bin/fm-backend.sh and bin/backends/herdr.sh, since herdr's handshake is exact-match per release and a stale 14-floor would let an incompatible 0.7.1 client pass the version check against a live protocol-16 server.
  • Updated docs/herdr-backend.md to record the new verified baseline (herdr 0.7.2, protocol 16) in place of the old 0.7.1/protocol-14 baseline.
  • Updated the protocol-14 mock fixtures and the version-check comment in tests/fm-backend-herdr.test.sh, tests/fm-crew-state.test.sh, and tests/fm-send-popup-settle.test.sh to protocol 16, so the fakebin-driven tests (spawn/container-ensure paths) reflect the new floor instead of asserting the now-invalid protocol 14 as passing.

Risk Assessment

✅ Low: Both prior findings were fixed correctly and consistently, the diff is now self-consistent (constant, comment, and every test fixture agree on protocol 16 / 0.7.2), and no new issues were introduced.

Testing

Captain, ran the three test files touched by the protocol-floor bump plus the entire repo test suite; every assertion passed (58/58 in fm-backend-herdr, 40/40 in fm-crew-state, 8/8 in fm-send-popup-settle), including the one that specifically confirms the version check now accepts a 0.7.2/protocol-16 client and still refuses a genuinely old protocol — shipshape, no findings.

Evidence: fm-backend-herdr.test.sh full output (58 assertions, protocol-16 accept/reject cases)
ok - fm_backend_herdr_version_check: accepts the current protocol (16)
ok - fm_backend_herdr_version_check: refuses an old protocol loudly
ok - fm_backend_herdr_version_check: refuses loudly when herdr is not installed
ok - fm_backend_herdr_workspace_label: a primary home (no marker) resolves to 'firstmate'
ok - fm_backend_herdr_workspace_label: a secondmate home (.fm-secondmate-home) resolves to '2ndmate-<id>'
ok - fm_backend_herdr_workspace_label: trims whitespace around the marker's secondmate id
ok - fm_backend_herdr_workspace_label: an empty marker file falls back to the primary label 'firstmate'
ok - fm_backend_herdr_workspace_label: two different secondmate homes get two different, non-colliding labels
ok - fm_backend_herdr_cli: sets HERDR_SESSION AND appends a trailing --session flag on every call
ok - fm_backend_herdr_container_ensure: version-gates, starts the server, ensures the firstmate workspace, echoes session:workspace_id + the seeded default tab id
ok - fm_backend_herdr_container_ensure: reuses an existing firstmate workspace without recreating it, and reports no seeded default tab (adopted, not created)
ok - fm_backend_herdr_container_ensure: workspace create passes --no-focus
ok - fm_backend_herdr_container_ensure: creates the workspace under the SECONDMATE home's own label, not 'firstmate'
ok - fm_backend_herdr_create_task: prunes exactly the seeded default tab container_ensure identified, once the first real task tab exists
ok - herdr repeated spawn/teardown: one persistent firstmate workspace reused, zero orphans, default tab pruned, create ran once
ok - fm_backend_herdr_create_task: an ADOPTED workspace's pre-existing tab is never pruned (the created-vs-adopted gate)
ok - fm_backend_herdr_create_task: the label-collision startup-workspace scenario (2026-07-02 incident) leaves the captain's live tab untouched
ok - fm_backend_herdr_workspace_prune_seeded_default_tab: refuses to close the seeded default tab when its pane reports a working agent (defense in depth)
ok - no bin/ jq filter names a --arg/--argjson variable after a jq reserved keyword
ok - fm_backend_herdr_create_task: refuses a duplicate tab label (herdr's own tab create has no uniqueness check)
ok - fm_backend_herdr_create_task: a same-labeled tab with a live (even idle) registered agent still refuses exactly as before
ok - fm_backend_herdr_create_task: scans every same-labeled tab and refuses if any duplicate is live
ok - fm_backend_herdr_create_task: closes and replaces a same-labeled tab whose pane is dead (pane_not_found)
ok - fm_backend_herdr_create_task: closes and replaces a same-labeled tab whose pane is alive but hosts no registered agent (a restored plain shell)
ok - fm_backend_herdr_create_task: closes every confirmed same-labeled husk only after creating the replacement
ok - fm_backend_herdr_create_task: refuses success when a preexisting husk tab remains after replacement
ok - fm_backend_herdr_create_task: refuses (fail-safe) rather than guessing when the duplicate's agent state cannot be classified confidently
ok - fm_backend_herdr_create_task: creates the replacement tab BEFORE closing the husk tab, never the reverse
ok - fm_backend_herdr_create_task: creates a tab and parses tab_id/pane_id from the JSON response, prunes nothing when no seeded tab id is given
ok - fm_backend_herdr_create_task: tab create passes --no-focus
ok - fm_backend_herdr_workspace_find: matches only THIS home's own label among several coexisting workspaces
ok - fm_backend_herdr_list_live: scoped to this home's own workspace, never a sibling home's
ok - fm_backend_herdr_parse_target: splits '<session>:<pane_id>' on the FIRST colon (pane_id itself contains one)
ok - fm_backend_herdr_normalize_key: Enter/Escape/C-c map to herdr's verified enter/escape/ctrl+c
ok - fm_backend_herdr_capture: calls 'pane read <pane> --source recent --lines N' with the session set
ok - fm_backend_herdr_capture: works around the verified small-N '--lines' bug by over-fetching and trimming locally
ok - fm_backend_herdr_capture: ensures the session and preserves pane read failure
ok - fm_backend_herdr_send_key: normalizes the key and targets the right pane
ok - fm_backend_herdr_kill: calls pane close and stays best-effort on failure
ok - fm_backend_herdr_current_path: reads pane foreground_cwd (the live running process), not the frozen creation-time cwd
ok - fm_backend_herdr_busy_state: working -> busy
ok - fm_backend_herdr_busy_state: done -> idle, blocked -> idle (surfaced like a stale pane, not suppressed as busy)
ok - fm_backend_herdr_busy_state: unparseable/absent agent state reports unknown, the regex-fallback cue
ok - fm_backend_herdr_composer_state: a bare '❯' composer row reads empty
ok - fm_backend_herdr_composer_state: the ghost placeholder text reads empty, not pending
ok - fm_backend_herdr_composer_state: real composer text reads pending
ok - fm_backend_herdr_composer_state: a slash-command popup's argument-hint placeholder still reads pending (the incident fix)
ok - fm_backend_herdr_composer_state: reports unknown when the pane cannot be captured
ok - fm_backend_herdr_composer_state: reports unknown when no border-delimited composer row is found
ok - fm_backend_herdr_send_text_submit: reports 'empty' once the composer row reads empty after one Enter
ok - fm_backend_herdr_send_text_submit: reports 'pending' when the composer never clears after retried Enters (swallowed)
ok - fm_backend_herdr_send_text_submit: a slash-command popup's placeholder fill on Enter #1 does not short-circuit as submitted; Enter #2 is retried and lands it
ok - fm_backend_herdr_send_text_submit: reports 'send-failed' when the literal send-text call itself errors
ok - fm_backend_herdr_send_text_submit: reports 'unknown' when the post-Enter capture fails (never retries past an unreadable pane)
ok - fm_backend_validate: herdr is a known backend (P2)
ok - fm_backend_busy_state: tmux dispatches to its positive busy-footer detector
error: unknown backend 'bogus' (known: tmux herdr zellij orca cmux)
ok - fm_backend_composer_state dispatches tmux/herdr/orca to their named classifiers, unknown for zellij/unrecognized backends
ok - fm-peek/fm-send: explicit stale targets matching metadata use the recorded backend
Evidence: fm-crew-state.test.sh full output
ok - active run-step is authoritative
ok - stale needs-decision over active run is superseded
ok - stale blocked over active run is superseded
ok - genuine parked run is not flagged superseded
ok - scalar gate parked run is not flagged superseded
ok - gate block parked run is not flagged superseded
ok - ci-ready status log beats monitoring run
ok - stale checks-green status log does not mask a fresh early run
ok - ci-monitoring run with checks already green surfaces done
ok - top-level ci status uses ci log green marker
ok - terminal no-checks ci-monitor marker surfaces done
ok - base-advance rearm after green stays working
ok - pending no-checks ci-monitor marker stays working
ok - ci-monitoring run with checks not yet green stays working
ok - a fresh issue after an earlier green reading is not masked
ok - stale checks-green status log does not mask CI relapse
ok - ci fixing is not overridden by an earlier green marker
ok - top-level fixing is not overridden by a stale ci running row
ok - top-level fixing is not overridden by a stale done log
ok - terminal passed run is authoritative
ok - terminal failed run is authoritative
ok - cross-branch run is attributed via the real runs list
ok - cross-branch attribution picks the branch's most recent row
ok - coarse run does not probe another branch's ci log
ok - another branch's run is ignored, falls back
ok - no run + busy pane reads working from the pane
ok - herdr unknown native state falls back to backend capture busy regex
ok - herdr idle agent_status is corroborated by the pane text, not trusted outright
ok - herdr idle agent_status with a genuinely idle pane stays not-busy (no regression for a human-blocked agent)
ok - no run + idle pane uses the status-log verb
ok - dead window ignores stale status log
ok - closed pane still reports a terminal run-step
ok - closed pane still reports an active run-step
ok - no timeout command uses perl bound
ok - scout skips the run lookup
ok - torn-down worktree is handled gracefully
ok - missing meta is handled gracefully
ok - crew_is_provably_working absorbs a validating crew found only via the runs-list fallback
ok - crew_is_provably_working still surfaces a genuinely stopped crew (safety property preserved)
ok - usage error exits 2
all fm-crew-state tests passed
Evidence: fm-send-popup-settle.test.sh full output
ok - fm-send popup-settle: codex $skill -> long settle -> 1.2s
ok - fm-send popup-settle: claude $-message -> fast path -> 0.3s
ok - fm-send popup-settle: claude "$5/month" -> fast path -> 0.3s
ok - fm-send popup-settle: explicit target $message -> fast path (unknown harness) -> 0.3s
ok - fm-send popup-settle: claude /command -> long settle (slash unchanged) -> 1.2s
ok - fm-send popup-settle: codex /command -> long settle (slash unchanged) -> 1.2s
ok - fm-send popup-settle: codex plain text -> fast path -> 0.3s
ok - fm-send popup-settle: codex $skill over herdr fails loudly on unknown submit verification

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • 🚨 tests/fm-backend-herdr.test.sh:190 - FM_BACKEND_HERDR_MIN_PROTOCOL was bumped from 14 to 16, but the test fixtures that mock herdr status --json still report protocol 14 as the client protocol, and one test explicitly asserts that protocol 14 passes the gate. tests/fm-backend-herdr.test.sh:185-192 (test_version_check_accepts_current_protocol) sets "protocol":14 and asserts expect_code 0 with the message "version_check should accept protocol 14 (>= the verified minimum)" — this will now fail since 14 < 16 makes fm_backend_herdr_version_check return 1. The same stale protocol:14 mock also appears in tests/fm-crew-state.test.sh:99 and tests/fm-send-popup-settle.test.sh:82, and fm_backend_herdr_version_check is called as the first gate inside fm_backend_herdr_container_ensure (bin/backends/herdr.sh:312), so any test exercising spawn/container-ensure through these fakebins will now fail at the version gate before reaching the behavior under test.
  • ℹ️ bin/backends/herdr.sh:111 - The comment directly above fm_backend_herdr_version_check still says 'Verified locally: v0.7.1, protocol 14', which now reads as the enforced minimum right next to a function that gates on FM_BACKEND_HERDR_MIN_PROTOCOL=16. Given this repo's docs are supposed to record the exact-match verified baseline (per the commit's own intent), this comment should be updated to the new verified baseline (0.7.2 / protocol 16) to avoid misleading a future reader into thinking 14 is still accepted.

🔧 Fix: fix(herdr): update stale protocol-14 test fixtures and comment to 16
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
  • bash tests/fm-backend-herdr.test.sh
  • bash tests/fm-crew-state.test.sh
  • bash tests/fm-send-popup-settle.test.sh
  • full suite: for t in tests/*.test.sh; do bash "$t"; done (all green, rc=0)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

mielyemitchell and others added 3 commits July 11, 2026 10:13
Handshake is exact-match per release: the fleet's server and client both run protocol 16 now.
Floor of 14 would let a stale 0.7.1 binary pass the version check while unable to talk to the live server.
Verified baseline updated to herdr 0.7.2, protocol 16 with full smoke-test evidence.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012pyrm43Fc6525CL37qtjmU
@mielyemitchell
mielyemitchell merged commit 22f6dab into main Jul 11, 2026
4 checks passed
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.

1 participant