Skip to content
Open
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
18 changes: 8 additions & 10 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,6 @@ ORCA_TERMINAL=
ORCA_TERMINAL_PROOF=
ORCA_REPO_ID=
ORCA_EXPECTED_TASK=
ORCA_PROVIDER_TASK=
ID=
ACCOUNT_LEASE_CREATED=0
FM_ACCOUNT_MUTATION_ACQUIRED=0
Expand Down Expand Up @@ -1044,7 +1043,6 @@ parse_orca_worktree_result() {
rest=${rest#*$'\t'}
ORCA_REPO_ID=$rest
case "$ORCA_REPO_ID" in *$'\t'*) return 1 ;; esac
ORCA_PROVIDER_TASK=
}

persist_orca_cleanup_quarantine() {
Expand Down Expand Up @@ -1435,12 +1433,12 @@ spawn_return_created_worktree() {
return 1
fi
if [ -z "$WORKTREE_EXPECTED_TIP" ] \
|| ! "$SCRIPT_DIR/fm-checkout-refresh.sh" verify-returnable "$WT" "$PROJ_ABS" "$WORKTREE_EXPECTED_TIP"; then
|| ! "$SCRIPT_DIR/fm-checkout-refresh.sh" verify-returnable "$WT" "${PROJ_ABS_REAL:-$PROJ_ABS}" "$WORKTREE_EXPECTED_TIP"; then
echo "warning: retained acquired worktree $WT because repository identity and its expected detached tip could not be re-proven" >&2
return 1
fi
rm -f "$WT/.claude/settings.local.json" "$WT/.opencode/plugins/fm-turn-end.js" "$WT/.fm-grok-turnend"
if ! "$SCRIPT_DIR/fm-checkout-refresh.sh" verify-returnable "$WT" "$PROJ_ABS" "$WORKTREE_EXPECTED_TIP"; then
if ! "$SCRIPT_DIR/fm-checkout-refresh.sh" verify-returnable "$WT" "${PROJ_ABS_REAL:-$PROJ_ABS}" "$WORKTREE_EXPECTED_TIP"; then
echo "warning: retained acquired worktree $WT because post-cleanup repository safety could not be re-proven" >&2
return 1
fi
Expand Down Expand Up @@ -2751,7 +2749,7 @@ real_path_or_raw() { # <path>
# and resets the selected clean pool worktree from that remote-tracking ref.
# The post-acquisition verification below is the fail-closed freshness proof.
if [ "$KIND" != secondmate ] && [ "$BACKEND" != orca ] && [ "$RECOVERY_ACCOUNT" != 1 ]; then
if CHECKOUT_PREFLIGHT_OUT=$("$SCRIPT_DIR/fm-checkout-refresh.sh" preflight "$PROJ_ABS" 2>&1); then
if CHECKOUT_PREFLIGHT_OUT=$("$SCRIPT_DIR/fm-checkout-refresh.sh" preflight "$PROJ_ABS_REAL" 2>&1); then
CHECKOUT_PREFLIGHT_STATUS=0
else
CHECKOUT_PREFLIGHT_STATUS=$?
Expand Down Expand Up @@ -2839,12 +2837,12 @@ if [ "$DIRECT_ACCOUNT_RECOVERY" = 1 ]; then
fi

if [ "$KIND" != secondmate ] && [ "$BACKEND" != orca ] && [ "$RECOVERY_ACCOUNT" != 1 ]; then
"$SCRIPT_DIR/fm-checkout-refresh.sh" pool-preflight "$PROJ_ABS" || {
"$SCRIPT_DIR/fm-checkout-refresh.sh" pool-preflight "$PROJ_ABS_REAL" || {
echo "error: refusing Treehouse acquisition because pool safety could not be inspected for $PROJ_ABS" >&2
exit 1
}
acquire_status=0
WT=$("$SCRIPT_DIR/fm-checkout-refresh.sh" acquire-worktree "$PROJ_ABS" "firstmate-$ID") || acquire_status=$?
WT=$("$SCRIPT_DIR/fm-checkout-refresh.sh" acquire-worktree "$PROJ_ABS_REAL" "firstmate-$ID") || acquire_status=$?
if [ "$acquire_status" -ne 0 ]; then
if [ "$acquire_status" -eq 124 ]; then
echo "error: refusing to spawn $ID after the bounded Treehouse acquisition timed out" >&2
Expand All @@ -2861,7 +2859,7 @@ if [ "$KIND" != secondmate ] && [ "$BACKEND" != orca ] && [ "$RECOVERY_ACCOUNT"
WORKTREE_RETAIN_ON_ABORT=1
validate_spawn_worktree "treehouse get --lease" "$PROJ_ABS"
freshness_status=0
"$SCRIPT_DIR/fm-checkout-refresh.sh" verify-worktree "$WT" "$PROJ_ABS" || freshness_status=$?
"$SCRIPT_DIR/fm-checkout-refresh.sh" verify-worktree "$WT" "$PROJ_ABS_REAL" || freshness_status=$?
if [ "$freshness_status" -ne 0 ]; then
echo "error: refusing to launch fm-$ID from a leased worktree whose repository identity, cleanliness, or default-tip freshness could not be proved" >&2
exit 1
Expand Down Expand Up @@ -3417,8 +3415,8 @@ EOF
echo "error: cannot durably record Orca create authority for $ID" >&2
exit 1
}
if [ -z "$ORCA_WORKTREE_ID" ] || [ -z "$WT" ] || [ "$ORCA_PROVIDER_TASK" != "$ORCA_EXPECTED_TASK" ]; then
echo "error: orca did not return matching worktree id, path, and task authority for $W" >&2
if [ -z "$ORCA_WORKTREE_ID" ] || [ -z "$WT" ]; then
echo "error: orca did not return matching worktree id and path authority for $W" >&2
exit 1
fi
validate_spawn_worktree "orca worktree create" "$W"
Expand Down
77 changes: 70 additions & 7 deletions bin/fm-teardown.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
# teardown refuses rather than risk discarding unlanded work.
# Origin-backed content checks hold the shared checkout lock and require bounded
# remote HEAD probes before and after fetch to agree before comparing trees.
# If the default history was rewritten after task-branch creation, the comparison
# uses a base recorded by both the branch-creation and default-branch reflogs.
# Every authorized Treehouse return is process-tree bounded by
# FM_TREEHOUSE_RETURN_TIMEOUT while holding the same common checkout mutation
# lock across its retry and stale-index-lock recovery sequence.
Expand Down Expand Up @@ -313,6 +315,7 @@ managed_endpoint_is_gone() { # <backend> <target> <expected-label> [probe-home]
case "$state" in
absent) return 0 ;;
present)
[ "$backend" != zellij ] || return 1
if [ -n "$probe_home" ]; then
agent_state=$(unset FM_ROOT_OVERRIDE; FM_HOME="$probe_home" FM_ROOT="$probe_home" fm_backend_agent_alive "$backend" "$target" "$expected" "$recorded_scoped_target" 2>/dev/null)
else
Expand Down Expand Up @@ -797,20 +800,55 @@ pr_is_merged() {
unpushed_patches_are_in_pr_head "$head"
}

# Set TASK_BRANCH_CREATION_BASE only when the task branch's oldest surviving
# reflog entry is its creation record, remains an ancestor of HEAD, and also
# appears in the authoritative default ref's reflog.
task_branch_creation_base() {
local authoritative_ref=$1 branch_ref entry base message fork_point
TASK_BRANCH_CREATION_BASE=
branch_ref=$(git -C "$WT" symbolic-ref --quiet HEAD 2>/dev/null) || return 1
entry=$(LC_ALL=C git -C "$WT" reflog show \
--format='%H%x09%gs' "$branch_ref" 2>/dev/null | tail -1)
[ -n "$entry" ] || return 1
base=${entry%%$'\t'*}
message=${entry#*$'\t'}
[ "$message" != "$entry" ] || return 1
case "$message" in
"branch: Created from "*) ;;
*) return 1 ;;
esac
git -C "$WT" cat-file -e "$base^{commit}" 2>/dev/null || return 1
git -C "$WT" merge-base --is-ancestor "$base" HEAD 2>/dev/null || return 1
fork_point=$(git -C "$WT" merge-base \
--fork-point "$authoritative_ref" HEAD 2>/dev/null) || return 1
[ "$fork_point" = "$base" ] || return 1
TASK_BRANCH_CREATION_BASE=$base
}

# Is the branch's content already present in the up-to-date default branch?
# Origin-backed proof holds the common checkout lock across probe, fetch,
# unchanged branch-and-tip re-probe, and tree comparison.
# The ordinary merge finds the net task change from shared history.
# When the default was rewritten past the task branch's creation point, an
# explicit branch-creation base isolates the task change from its stale baseline.
content_matches_ref() {
local ref=$1 default_tree merged_tree
local ref=$1 default_tree merged_tree merge_output creation_base
default_tree=$(git -C "$WT" rev-parse --quiet --verify "$ref^{tree}" 2>/dev/null) || return 1
[ -n "$default_tree" ] || return 1
merged_tree=$(git -C "$WT" merge-tree --write-tree "$ref" HEAD 2>/dev/null) || return 1
merged_tree=$(printf '%s\n' "$merged_tree" | head -1)
if [ "$merged_tree" != "$default_tree" ]; then
echo "teardown: task content is not present in authoritative $ref; retaining $WT" >&2
return 1
if merge_output=$(git -C "$WT" merge-tree --write-tree "$ref" HEAD 2>/dev/null); then
merged_tree=$(printf '%s\n' "$merge_output" | head -1)
[ "$merged_tree" != "$default_tree" ] || return 0
fi
if task_branch_creation_base "$ref"; then
creation_base=$TASK_BRANCH_CREATION_BASE
if merge_output=$(git -C "$WT" merge-tree --write-tree \
--merge-base "$creation_base" "$ref" HEAD 2>/dev/null); then
merged_tree=$(printf '%s\n' "$merge_output" | head -1)
[ "$merged_tree" != "$default_tree" ] || return 0
fi
fi
return 0
echo "teardown: task content is not present in authoritative $ref; retaining $WT" >&2
return 1
}

content_in_origin_default() {
Expand Down Expand Up @@ -1059,6 +1097,10 @@ validate_teardown_target_identity() {
echo "error: teardown project metadata is not an exact inspectable repository root: ${PROJ:-<missing>}" >&2
return 1
}
if [ "$KIND" = scout ] && [ "$BACKEND" != orca ] &&
[ ! -e "$WT" ] && [ ! -L "$WT" ]; then
return 0
fi
worktree_root=$(exact_git_worktree_root "$WT") || {
echo "error: teardown worktree metadata is not an exact inspectable repository root: ${WT:-<missing>}" >&2
return 1
Expand Down Expand Up @@ -3074,6 +3116,25 @@ except OSError:
PY
}

secondmate_linked_worktree_is_recorded_child() {
local retiring_home=$1 repository=$2 linked_worktree=$3 child_metas child_meta child_kind child_project child_worktree
child_metas=$(secondmate_state_metadata "$retiring_home") || return 1
while IFS= read -r child_meta; do
[ -n "$child_meta" ] || continue
child_kind=$(meta_value "$child_meta" kind)
[ -n "$child_kind" ] || child_kind=ship
[ "$child_kind" != secondmate ] || continue
child_project=$(meta_value "$child_meta" project)
child_worktree=$(meta_value "$child_meta" worktree)
[ "$(fm_checkout_trusted_dir "$child_project" 2>/dev/null)" = "$repository" ] || continue
[ "$(fm_checkout_trusted_dir "$child_worktree" 2>/dev/null)" = "$linked_worktree" ] || continue
return 0
done <<EOF
$child_metas
EOF
return 1
}

validate_secondmate_repository_worktree_graph() {
local repository=$1 retiring_home=$2 repository_container=${3:-$1}
local common listed records path kind canonical count=0 bare_repository container_git submodule_admin=0
Expand Down Expand Up @@ -3144,6 +3205,8 @@ validate_secondmate_repository_worktree_graph() {
:
elif [ "$submodule_admin" -eq 1 ] && [ "$canonical" = "$common" ]; then
:
elif secondmate_linked_worktree_is_recorded_child "$retiring_home" "$repository" "$canonical"; then
count=$((count - 1))
else
echo "REFUSED: secondmate project common Git directory owns another linked worktree at $canonical" >&2
return 1
Expand Down
Loading
Loading