Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

fix: isolate Herdr crew sessions from captain workspace - #96

Merged
JTInventory merged 5 commits into
mainfrom
fm/firstmate-herdr-isolated-session-0813
Aug 13, 2026
Merged

JTInventory merged 5 commits into
mainfrom
fm/firstmate-herdr-isolated-session-0813

Conversation

@JTInventory

Copy link
Copy Markdown
Owner

Intent

Make firstmate crews appear in Herdr again without touching the captain's live CAPTAIN workspace. Host Herdr is 0.8.0 / protocol 19 and has no pane.close_bound, so spawn crews into a dedicated Herdr session named firstmate (not default and not CAPTAIN/w1), keep config/backend as herdr without requiring FM_BACKEND=tmux, and close only after PID plus start-time match on the recorded pane. If identity is unproven, refuse and leave the pane; never bypass pane.close_bound inside the captain default session. Prove with focused tests that spawn records session=firstmate plus exact workspace/tab/pane, teardown refuses mismatched PID, and CAPTAIN/w1 is never a mutation target. Build on main including #95 f0899b8/bin/backends/herdr.sh diagnostics. Do not run the full 96-suite and do not merge; deliver through JTInventory/firstmate no-mistakes. Lint was attempted and failed only because the host daemon shut down under memory exhaustion; skip lint only for this recovery run.

What Changed

  • Route normal Herdr crew spawns through the dedicated firstmate session, record exact session/workspace/tab/pane metadata, and reject captain-owned default targets.
  • Add identity-checked legacy cleanup for hosts without pane.close_bound, matching pane.process-info PID and /proc start time while keeping non-dedicated targets fail-closed.
  • Expand focused Herdr presentation coverage and worker-isolation guidance for protected CAPTAIN/w1 state, mismatched-PID teardown, and uncertain cleanup.

Risk Assessment

⚠️ Medium: Captain, the change is bounded and fail-closed, but it modifies destructive Herdr teardown on a host without atomic close support, so risk is concentrated in the explicitly authorized identity-check-before-close containment path.

Testing

The four configured focused tests were rerun successfully, and the Herdr presentation E2E test provided direct CLI evidence from real Herdr 0.8.0/protocol-19 plus behavioral spawn/teardown fixtures. Evidence proves dedicated firstmate routing, exact workspace/tab/pane recording, protected CAPTAIN/w1, refusal on unproven identity and mismatched PID, and close only after PID/start-time proof. No UI artifact was applicable because this is a CLI/backend change.

Evidence: Herdr isolation E2E transcript

ok - installed Herdr 0.8.0 protocol-19 accepts the isolated firstmate session without mutation ok - real Herdr spawn records firstmate and exact workspace/tab/pane without touching CAPTAIN/w1 ok - Herdr teardown refuses a mismatched PID without closing the pane ok - Herdr teardown closes only the exact recorded firstmate pane after PID/start-time proof

ok - installed Herdr 0.8.0 protocol-19 accepts the isolated firstmate session without mutation
ok - real Herdr spawn records firstmate and exact workspace/tab/pane without touching CAPTAIN/w1
ok - normal Herdr dispatch refuses an explicit default-session target
ok - Herdr preflight permits the isolated firstmate session without bound-close methods
ok - Herdr refuses unbound teardown before mutating the captain-owned CAPTAIN/w1 target
ok - Herdr refuses an unverifiable task-tab inventory
ok - Herdr refuses workspace cleanup without process identity
ok - Herdr preserves an unidentified workspace mutation
ok - Herdr preserves workspace cleanup when identity cannot be proven
ok - Herdr preserves a zero-exit workspace identity mismatch
ok - launch records the exact Herdr workspace, tab, and pane returned by the provider
ok - Herdr launch submit uses one atomic pane.run against the recorded pane
ok - Herdr atomic submit failure reports a precise fail-closed reason
ok - Herdr literal launch failure reports a precise fail-closed reason
ok - Herdr launch submit-key failure reports a precise fail-closed reason
ok - Herdr teardown refuses a live target without process identity
ok - Herdr teardown refuses an unrecognized agent state
ok - Herdr teardown refuses a mismatched PID without closing the pane
ok - Herdr teardown closes only the exact recorded firstmate pane after PID/start-time proof
ok - Herdr refuses and retains a zero-exit task-create identity mismatch
ok - Herdr task-tab failure reports a precise fail-closed reason
ok - Herdr task-tab failure preserves a provider mutation without process identity
ok - Herdr retains stale task-create ids without process identity
ok - Herdr refuses legacy task-tab cleanup without process identity
ok - scoped Herdr presentation launch/teardown proof completed
Evidence: Configured focused-test transcript
ok - a crewmate declaration clears every operational-home variable and names its owner
ok - a secondmate declaration pins its own home and clears every inherited override
ok - an unbuildable declaration refuses instead of emitting a partial prefix
ok - incomplete worker identities fail closed at the shared guard
ok - markerless workers are refused before lock, send, and watcher state resolution
ok - markerless workers with worker ancestry are refused at the primary checkout root
ok - explicit primary roles require non-forgeable origin proof
ok - primary proof refuses unreadable process environments
ok - primary proof refuses any inherited worker declaration marker
ok - a reparented markerless worker cannot reuse a primary attestation
ok - primary startup reuses a state-bound launch attestation
ok - primary initialization requires an explicit bootstrap path
ok - invalid primary attestations are rejected before lock publication
ok - process identity proof is explicitly gated to Linux procfs
ok - newline-bearing process environments fail closed without forging markers
ok - unreadable task start proof remains contested
ok - the process index separates a proven empty scan from incomplete evidence
ok - the task process index ignores foreign uid environments
ok - task workers cannot mutate custom check trust state
ok - claude, codex, opencode, pi, and grok all launch with the crewmate home declaration
ok - a secondmate child receives its own home and no inherited override
ok - primary scope requires primary proof and excludes declared workers
ok - skip: this JT fork has no tracked session-start nudge adapter
ok - a declared task worker is refused lock acquisition but can inspect status
ok - a declared task worker is refused both dispatch and teardown
ok - a declared task worker is refused before bootstrap state mutation
ok - an agent's working directory is read from the live process, not a record
ok - the declared-agent lookup returns the agent itself, not one of its subprocesses
ok - providers with no verified per-pane process id report unknown instead of a pane value
ok - a tmux pane pid is read through the window's stable id, not its name
ok - a lost or renamed window reports unknown instead of firstmate's own pane
ok - one /proc walk answers every task in a sweep, and an empty index is a real answer
ok - tmux cwd fallback requires complete worker identity
ok - agent lookup scopes duplicate task ids by home and start time
ok - spawn settles on the process's own working directory, not a pane field that names another process
ok - spawn keeps an unproven pane path diagnostic-only
ok - slot ownership is stamped invisibly in a linked worktree and refused for a plain checkout
ok - a slot this task alone records and stamps disposes normally
ok - unexpected pooled-slot metadata scan failures retain the lease
ok - metadata record read failures retain the pooled lease
ok - malformed pooled-slot metadata retains the lease
ok - non-regular pooled-slot metadata retains the lease
ok - a missing ownership stamp retains the pooled slot
ok - relinquish refuses a retained slot without ownership evidence
ok - a missing recorded worktree retains the pooled slot
ok - a slot still recorded by another task - live, paused, or quarantined - retains its lease
ok - metadata pointing at a slot that was reissued is recognized as stale and retains the lease
ok - a slot occupied by another task's live agent retains its lease
ok - a retiring owner cannot grant unstamped disposal authority to a remaining holder
ok - a stamp naming another task survives a retain and still blocks that slot's disposal
ok - teardown retires a contested lease, leaves the slot untouched, and --force does not waive it
ok - the resume sweep re-asserts isolation and reports a worktree that collapsed onto the primary checkout
ok - the resume sweep stays silent for a worker that is genuinely in its worktree
ok - unproven process evidence blocks restore-time mutation without using a pane path
ok - the restore sweep blocks when its process identity scan is incomplete
ok - an unproven record whose endpoint is gone is a quiet fact, not a fleet-wide block
ok - the sweep reports foreign-owner processes independently of endpoint state
ok - an unreadable endpoint still blocks restore-time mutation
ok - the resume sweep reports an agent that declares another home as its owner
ok - the resume sweep stays silent for a secondmate that declares its own home
ok - the resume sweep still reports a secondmate whose declared home is not the one it owns
# all fm-worker-isolation tests passed
ok - watch-session bootstraps a missing primary attestation before its guard
ok - watch-session does not replay a persisted attestation without trusted entry proof
ok - watch-session start/status/stop are scoped to one FM_HOME
ok - watch-session stop waits through delayed watcher lock startup
ok - watch-session stop fails closed for an unpinned legacy watcher
ok - watch-session status reports runner-window liveness, not inner arm health
ok - watch-session refuses to steal the Grok primary follower slot
ok - watch-session restart refuses Grok overlap before stopping the fallback
ok - watch-session emergency override remains available for Grok fallback
ok - a readable empty environment is a complete no-marker result
ok - slot disposal scopes live-process proof to the exact backend endpoint
ok - a declared marker proves a live PID-less endpoint
ok - ambiguous duplicate task markers retain the durable lease
ok - an incomplete task PID index retains the durable lease
ok - a reused endpoint PID retains the durable lease
ok - a foreign endpoint-bound occupant retains the durable lease
ok - an incomplete self-owner home contract retains the durable lease
ok - a changed current home contract retains the durable lease
ok - missing exact endpoint proof retains the durable lease
ok - an undeclared process retains a closed endpoint lease
ok - a reparented worker retains a closed endpoint lease
ok - an unreadable process census retains uncertainty
ok - cross-home paused metadata retains a pooled slot
ok - registered ordinary task homes retain a pooled slot
ok - unterminated home registry records retain a pooled slot
ok - missing home registry retains a closed endpoint lease
ok - a closed endpoint disposes after a complete empty occupancy census
# all fm-slot-occupant-proof tests passed
ok - an incomplete durable return refuses with an exact, documented recovery
ok - an unresolved-lease record is retirable and reports its still-held lease
ok - a failed return stays retryable and only retires the task branch once the return is proven
ok - teardown retries a transient index lock without weakening landed-work checks
ok - endpoint recovery consumes the immutable tmux window id
ok - endpoint recovery retains metadata when tmux presence is unreadable
ok - pending endpoint recovery retires only after confirming no task window exists
ok - pending endpoint recovery preserves an unproven matching window

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • 🚨 bin/backends/herdr.sh:319 - Required: “spawn crews into a dedicated Herdr session named firstmate.” The changed local session=${HERDR_SESSION:-$FM_BACKEND_HERDR_DEDICATED_SESSION} still permits any non-default HERDR_SESSION, and fm-spawn.sh uses it directly, so an ambient override can spawn into another session. Force normal spawns to firstmate or explicitly authorize this override.
  • 🚨 bin/backends/herdr.sh:348 - Required: “close only after PID plus start-time match on the recorded pane. If identity is unproven, refuse and leave the pane.” When bound tab close is unavailable, this new fallback checks only workspace/tab/pane structure, then calls tab close; failed workspace/task-tab reconciliation can therefore close a pane without PID/start-time proof. Require identity proof at this shared fallback boundary or refuse and retain the pane.

🔧 Fix: Herdr session isolation and identity-gated teardown fixed
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-worker-isolation.test.sh && bash tests/fm-watch-session.test.sh && bash tests/fm-slot-occupant-proof.test.sh && FM_TEARDOWN_TEST_FOCUS=s1 bash tests/fm-teardown.test.sh
  • bash tests/fm-backend-herdr-presentation-e2e.test.sh
  • bash tests/fm-worker-isolation.test.sh
  • bash tests/fm-watch-session.test.sh
  • bash tests/fm-slot-occupant-proof.test.sh
  • FM_TEARDOWN_TEST_FOCUS=s1 bash tests/fm-teardown.test.sh
✅ **Document** - passed

✅ No issues found.

⏭️ **Lint** - skipped

Step was skipped.

✅ **Push** - passed

✅ No issues found.

@JTInventory
JTInventory merged commit b658819 into main Aug 13, 2026
5 checks passed
@JTInventory
JTInventory deleted the fm/firstmate-herdr-isolated-session-0813 branch August 13, 2026 20:18
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant