Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions bin/fm-backend.sh
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,10 @@ fm_backend_validate_task_endpoint() { # <meta-file> <task-id>
fi
;;
herdr)
[ "$binding" = "$id" ] || {
echo "REFUSED: legacy Herdr endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2
if [ -n "$binding" ] && [ "$binding" != "$id" ]; then
echo "REFUSED: Herdr endpoint metadata is bound to task $binding, not $id; preserving task state." >&2
return 1
}
fi
recorded_session=$(fm_backend_meta_exact_value "$meta" herdr_session) || recorded_session=
workspace=$(fm_backend_meta_exact_value "$meta" herdr_workspace_id) || workspace=
tab=$(fm_backend_meta_exact_value "$meta" herdr_tab_id) || tab=
Expand All @@ -471,10 +471,10 @@ fm_backend_validate_task_endpoint() { # <meta-file> <task-id>
fi
;;
zellij)
[ "$binding" = "$id" ] || {
echo "REFUSED: legacy Zellij endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2
if [ -n "$binding" ] && [ "$binding" != "$id" ]; then
echo "REFUSED: Zellij endpoint metadata is bound to task $binding, not $id; preserving task state." >&2
return 1
}
fi
recorded_session=$(fm_backend_meta_exact_value "$meta" zellij_session) || recorded_session=
tab=$(fm_backend_meta_exact_value "$meta" zellij_tab_id) || tab=
pane=$(fm_backend_meta_exact_value "$meta" zellij_pane_id) || pane=
Expand All @@ -487,10 +487,10 @@ fm_backend_validate_task_endpoint() { # <meta-file> <task-id>
fi
;;
orca)
[ "$binding" = "$id" ] || {
echo "REFUSED: legacy Orca endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2
if [ -n "$binding" ] && [ "$binding" != "$id" ]; then
echo "REFUSED: Orca endpoint metadata is bound to task $binding, not $id; preserving task state." >&2
return 1
}
fi
terminal=$(fm_backend_meta_exact_value "$meta" terminal) || terminal=
worktree_id=$(fm_backend_meta_exact_value "$meta" orca_worktree_id) || worktree_id=
[ -n "$terminal" ] || {
Expand All @@ -510,10 +510,10 @@ fm_backend_validate_task_endpoint() { # <meta-file> <task-id>
window=$terminal
;;
cmux)
[ "$binding" = "$id" ] || {
echo "REFUSED: legacy cmux endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2
if [ -n "$binding" ] && [ "$binding" != "$id" ]; then
echo "REFUSED: cmux endpoint metadata is bound to task $binding, not $id; preserving task state." >&2
return 1
}
fi
workspace=$(fm_backend_meta_exact_value "$meta" cmux_workspace_id) || workspace=
surface=$(fm_backend_meta_exact_value "$meta" cmux_surface_id) || surface=
if [ -z "$workspace" ] || [ -z "$surface" ] || [ "$window" != "$workspace:$surface" ] \
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Only metadata-routed task selectors carry secondmate-marker and Codex-harness co
These five sentences are the single owner of the task-selector vocabulary; backend guides and other documents point here instead of restating the resolution order.
`fm-teardown.sh <id>` takes a task id directly and validates the complete metadata-only endpoint identity before any runtime dispatch or cleanup mutation.
Missing, empty, duplicate, malformed, backend-inconsistent, or task-mismatched endpoint records are preserved and refused.
Legacy tmux metadata remains cleanup-compatible when its exact window name is `fm-<id>`; opaque non-tmux endpoints require their recorded `endpoint_task_id=` binding.
Legacy tmux metadata remains cleanup-compatible when its exact window name is `fm-<id>`; opaque non-tmux endpoints refuse only when a recorded `endpoint_task_id=` binding is present and mismatches the task id, so legacy metadata predating that field also stays cleanup-compatible.
`FM_HOME` determines Herdr's home label: the primary home uses `firstmate`, and a secondmate home marked by `.fm-secondmate-home` uses `2ndmate-<secondmate-id>`.
[`herdr-backend.md`](herdr-backend.md#watching-and-task-containers) owns launcher-bound workspace placement, the label-only fallback, collision handling, and recovery behavior.
The optional local `config/herdr-presentation-spaces` presence flag instead enables Herdr's default-off disposable single-task visual projection; [Optional presentation spaces](herdr-backend.md#optional-presentation-spaces) owns its behavior, safety limits, recovery contract, and narrow locked session-start cleanup of exact restored idle-shell children.
Expand Down
32 changes: 31 additions & 1 deletion tests/fm-teardown-endpoint-safety.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,44 @@ test_supported_backend_endpoint_records_validate() {
"backend=cmux" "cmux_workspace_id=workspace-1" "cmux_surface_id=surface-2"
fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "valid cmux endpoint refused"

id=herdr-legacy
fm_write_meta "$dir/home/state/$id.meta" \
"window=lab:w1:p2" "worktree=$dir/worktree" "project=$dir/project" \
"backend=herdr" "herdr_session=lab" "herdr_workspace_id=w1" "herdr_tab_id=w1:t2" "herdr_pane_id=w1:p2"
fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy Herdr endpoint (no binding) refused"
[ "$FM_BACKEND_VALIDATED_BACKEND" = herdr ] || fail "legacy Herdr did not select its backend"
[ "$FM_BACKEND_VALIDATED_TARGET" = "lab:w1:p2" ] || fail "legacy Herdr target mismatch"

id=zellij-legacy
fm_write_meta "$dir/home/state/$id.meta" \
"window=lab:7" "worktree=$dir/worktree" "project=$dir/project" \
"backend=zellij" "zellij_session=lab" "zellij_tab_id=3" "zellij_pane_id=7"
fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy Zellij endpoint (no binding) refused"
[ "$FM_BACKEND_VALIDATED_BACKEND" = zellij ] || fail "legacy Zellij did not select its backend"

id=orca-legacy
fm_write_meta "$dir/home/state/$id.meta" \
"window=fm-$id" "terminal=term-7" \
"worktree=$dir/worktree" "project=$dir/project" "backend=orca" "orca_worktree_id=worktree-9"
fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy Orca endpoint (no binding) refused"
[ "$FM_BACKEND_VALIDATED_BACKEND" = orca ] || fail "legacy Orca did not select its backend"
[ "$FM_BACKEND_VALIDATED_TARGET" = term-7 ] || fail "legacy Orca did not select its terminal"

id=cmux-legacy
fm_write_meta "$dir/home/state/$id.meta" \
"window=workspace-1:surface-2" "worktree=$dir/worktree" "project=$dir/project" \
"backend=cmux" "cmux_workspace_id=workspace-1" "cmux_surface_id=surface-2"
fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy cmux endpoint (no binding) refused"
[ "$FM_BACKEND_VALIDATED_BACKEND" = cmux ] || fail "legacy cmux did not select its backend"

for backend in tmux herdr zellij orca cmux; do
set +e
fm_backend_kill "$backend" "" >/dev/null 2>&1
target=$?
set -e
[ "$target" -ne 0 ] || fail "$backend generic kill accepted an empty target"
done
pass "cleanup identity: valid tmux, Herdr, Zellij, Orca, and cmux records validate while every empty backend target refuses"
pass "cleanup identity: valid and legacy tmux, Herdr, Zellij, Orca, and cmux records validate while every empty backend target refuses"
}

test_tmux_empty_target_refuses_without_invocation() {
Expand Down
Loading