From f81326bd0836caf2df53cbb73cb537acb8e7e9af Mon Sep 17 00:00:00 2001 From: agent-exec-transport Date: Sun, 18 Oct 2026 14:25:10 +0000 Subject: [PATCH] agent-exec transport 7da469a725ec41f651cf89a8616adaa7/78f40f5a0c57 --- policy/governance.md | 5 +++- scripts/task.sh | 54 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/policy/governance.md b/policy/governance.md index 93bbe34b..b744beab 100644 --- a/policy/governance.md +++ b/policy/governance.md @@ -42,7 +42,10 @@ make task-start NAME=feature/my-task TRIAGE_RECEIPT=/tmp/my-task-triage.json PRE 恢复已有登记任务先捕获本次启动检查,再在原任务 worktree 用 `make task-resume PRECHECK_RECEIPT=/tmp/my-task-resume.json` 验证并原样导入;不重新建分支。 默认 `TASK_WORK_MODE=delivery`,继续要求严格 READY 和当前目标一致。 -工作区忙或落后时先由 owner 保存现场并同步;远端不可达时只继续不依赖新鲜基线的阅读和分析。 +工作区忙或落后时先由 owner 保存现场并同步。远端不可达时,delivery 恢复仍以 REMOTE_UNVERIFIED 拒绝; +显式 `TASK_WORK_MODE=local` 可在先核验收据、活动任务、原 R0/HEAD/工作区范围后导入 LOCAL_READY 收据, +继续已授权局部工作(子任务以已登记的本地父分支为参考,且须包含记录的 `parent_head_sha`)。远端可达但目标缺失、 +回退/分叉或无法比较时仍拒绝。离线不得建任务、同步、PR 检查、Review/合入或交付,本地参考不视为最新或可交付。 局部恢复接口已具备,正式交付仍按[合入调度](delivery.md#合入调度)的单一最终窗口执行。 启用后,既有任务可以显式选择 local,保持原 R0,标记落后,继续已授权局部工作: diff --git a/scripts/task.sh b/scripts/task.sh index 16a3827e..caade4e3 100755 --- a/scripts/task.sh +++ b/scripts/task.sh @@ -18,9 +18,10 @@ fetch_target() { git rev-parse --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" >/dev/null \ || { echo "[verdict] REMOTE_UNVERIFIED — target commit missing" >&2; return 6; } } -target_history() { # 输出 ok(远端不可查询/目标 ref 缺失/无缓存目标/通告提交为缓存后代)、rewound(回退或分叉)或 uncomparable(通告提交本地缺失或无法比较) +target_history() { # $1=可选基线(无缓存目标时代替比较,如任务 R0)→ 输出 ok(远端不可查询/目标 ref 缺失/无缓存目标/通告提交为缓存后代)、rewound(回退或分叉)或 uncomparable(通告提交本地缺失或无法比较) local cached advertised rc=0 - cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || { echo ok; return 0; } + cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || cached=${1:-} + [ -n "$cached" ] || { echo ok; return 0; } advertised=$(git ls-remote origin "refs/heads/${TARGET_BRANCH}" 2>/dev/null \ | awk -v ref="refs/heads/${TARGET_BRANCH}" '$2 == ref && !seen { print $1; seen = 1 }') || { echo ok; return 0; } [ -n "$advertised" ] || { echo ok; return 0; } @@ -477,9 +478,9 @@ cmd_resume() { need_repo local mode mode=$(work_mode) - fetch_target + [ "$mode" = local ] || fetch_target [ -n "${PRECHECK_RECEIPT:-}" ] || die "PRECHECK_RECEIPT required: capture the current task precheck before resume" - local receipt + local receipt remote_fresh=1 receipt=$(realpath -- "$PRECHECK_RECEIPT") VALIDATE_RECEIPT="$receipt" VALIDATE_RECEIPT_PURPOSE="resume-${mode}" bash "${SCRIPT_DIR}/evidence.sh" # 立即目标:子任务为其父 initiative(delivery 下取远端头),其余为根目标。 @@ -487,14 +488,44 @@ cmd_resume() { branch=$(git rev-parse --abbrev-ref HEAD) card=$(branch_card "$branch") [ -n "$card" ] && immediate=$(card_immediate_target "$card") + if [ "$mode" = local ]; then + # local 先核验收据与活动任务再刷新:刷新成功按新目标复核;失败只在远端确实不可达时以登记 R0 继续, + # 远端可达而目标缺失、回退/分叉或无法比较时保持 REMOTE_UNVERIFIED,不导入收据。 + git check-ref-format "refs/heads/${TARGET_BRANCH}" >/dev/null || die "invalid target branch" + if fetch_target 2>/dev/null; then + VALIDATE_RECEIPT="$receipt" VALIDATE_RECEIPT_PURPOSE=resume-local bash "${SCRIPT_DIR}/evidence.sh" + else + remote_fresh=0 + local r0 ls_rc=0 + [ -n "$card" ] || die "no registered task card for ${branch}; receipt not imported" + r0=$(sed -n 's/^- 权威仓:.*;R0(initial_base_sha)= \([0-9a-f]\+\)[[:space:]]*$/\1/p' "$card" | head -1) + [ -n "$r0" ] || die "task card has no initial baseline; receipt not imported" + git ls-remote --exit-code --heads origin "refs/heads/${TARGET_BRANCH}" >/dev/null 2>&1 || ls_rc=$? + if [ "$ls_rc" -eq 2 ]; then + echo "[verdict] REMOTE_UNVERIFIED — origin is reachable but ${TARGET_BRANCH} is missing; preserve local work, receipt not imported" >&2 + return 6 + fi + case "$(target_history "$r0")" in + rewound) + echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} rewound or diverged from the known target; preserve local work, receipt not imported" >&2 + return 6 ;; + uncomparable) + echo "[verdict] REMOTE_UNVERIFIED — advertised origin/${TARGET_BRANCH} cannot be compared with the known target; preserve local work, receipt not imported" >&2 + return 6 ;; + esac + echo "[remote-freshness] unavailable — resuming on registered R0=${r0} for authorized local work only; delivery still requires a fresh target" + fi + fi if [ "$immediate" != "${TARGET_BRANCH}" ]; then observed=$(resolve_parent_ref "$immediate") || die "immediate target ${immediate} unavailable; receipt not imported" recorded=$(card_field "$card" parent_head_sha) if [ -n "$recorded" ] && ! git merge-base --is-ancestor "$recorded" "$observed"; then die "immediate target ${immediate} no longer contains recorded parent head ${recorded}; receipt not imported" fi - else + elif [ "$remote_fresh" -eq 1 ]; then observed=$(git rev-parse "origin/${TARGET_BRANCH}") + else + observed=unavailable fi IMPORT_RECEIPT="$receipt" bash "${SCRIPT_DIR}/evidence.sh" echo "[resumed] work_mode=${mode} source_head=$(git rev-parse HEAD) observed_target=${observed} immediate_target=${immediate}" @@ -630,7 +661,7 @@ except Stop as error: sys.exit("ERROR: " + error.message) sys.exit(0 if any(row["子任务分支"].strip("`").strip() == sys.argv[2] for row in rows) else 1)' "$SCRIPT_DIR" "$1" } -resolve_parent_ref() { # $1=父 initiative 分支 → 打印用于同步/核验的提交(delivery 仅远端;local 取远端与本地包含对方的一侧) +resolve_parent_ref() { # $1=父 initiative 分支 → 打印用于同步/核验的提交(delivery 仅远端;local 取远端与本地包含对方的一侧,远端不可查询时取本地) git check-ref-format "refs/heads/$1" >/dev/null || die "invalid parent branch $1" if [ "$(work_mode)" = delivery ]; then # delivery 只认 origin 上刚取回的父分支头:本地领先/分叉意味着未准入的提交,不得被同步或合入。 @@ -652,8 +683,15 @@ resolve_parent_ref() { # $1=父 initiative 分支 → 打印用于同步/核验 fi remote=$(git rev-parse --verify "refs/remotes/origin/$1^{commit}") elif [ "$rc" -ne 2 ]; then - echo "[verdict] REMOTE_UNVERIFIED — cannot query origin/$1; preserve local work and retry" >&2 - return 6 + # 远端不可查询时只把已登记的本地父分支作为局部续作参考;不声称其为最新或可交付,调用方仍核验 parent_head_sha。 + local_ref=$(git rev-parse -q --verify "refs/heads/$1^{commit}" 2>/dev/null || true) + if [ -z "$local_ref" ]; then + echo "[verdict] REMOTE_UNVERIFIED — cannot query origin/$1 and no local $1 exists; preserve local work and retry" >&2 + return 6 + fi + echo "[remote-freshness] origin/$1 unavailable — using local $1 for authorized local continuation only; not current or delivery-ready" >&2 + printf '%s\n' "$local_ref" + return 0 fi local_ref=$(git rev-parse -q --verify "refs/heads/$1^{commit}" 2>/dev/null || true) if [ -n "$remote" ] && [ -n "$local_ref" ]; then