Keep fixed-candidate evidence outside the measured checkout
Why: recording checks in a frozen task card can change the candidate and invalidate current-head CI. Evidence: SELF fixed-candidate capture regression and all 33 evidence tests passed; actual output and object digests are preserved by make evidence. Impact: document preparation, external receipts and post-phase import using existing schema 1; no filesystem freeze or trust upgrade is introduced. Refs: U3; policy/governance.md; .task/feature-merge-queue-policy-draft.md
This commit is contained in:
+34
-2
@@ -20,10 +20,23 @@ make task-start NAME=feature/my-task PRECHECK_RECEIPT=/tmp/my-task-precheck.json
|
||||
同一收据不可覆盖。首次检查失败时处理原因,使用新路径重新启动。主干在两步间前进时,
|
||||
重新确认新的启动基线,不能把旧收据移花接木到新对象。
|
||||
|
||||
恢复已有任务仍先捕获本次启动检查,再在原任务工作区用
|
||||
`IMPORT_RECEIPT=/tmp/my-task-resume.json make evidence` 导入;不重新建分支。
|
||||
恢复已有登记任务先捕获本次启动检查,再在原任务 worktree 用
|
||||
`make task-resume PRECHECK_RECEIPT=/tmp/my-task-resume.json` 验证并原样导入;不重新建分支。
|
||||
默认 `TASK_WORK_MODE=delivery`,继续要求严格 READY 和当前目标一致。
|
||||
工作区忙或落后时先由 owner 保存现场并同步;远端不可达时只继续不依赖新鲜基线的阅读和分析。
|
||||
|
||||
局部恢复接口已具备,流程启用仍以 [合入调度](merge-queue.md#采纳顺序与停止条件) 的采纳条件为准。
|
||||
启用后,既有任务可以显式选择 local,保持原 R0,标记落后,继续已授权局部工作:
|
||||
|
||||
```bash
|
||||
TASK_WORK_MODE=local RECEIPT=/tmp/my-local-resume.json CMD="make precheck" make evidence
|
||||
TASK_WORK_MODE=local make task-resume PRECHECK_RECEIPT=/tmp/my-local-resume.json
|
||||
```
|
||||
|
||||
LOCAL_READY 只用于局部恢复;新任务入口始终校验严格 READY,正式验收与合入另行同步。
|
||||
恢复核验实际 worktree/卡片/分支、R0 祖先关系及源码外原始收据;脏现场、Git 操作、组件漂移和未知远端仍拒绝。
|
||||
旧收据允许作为历史证据原样导入,不自动成为当前恢复或交付准入;格式继续为 schema 1。
|
||||
|
||||
堆叠任务使用 `PARENT=feature/parent PARENT_PR=<父PR链接>`。父分支须包含本批主干 R0;
|
||||
卡片同时记录父分支、父 SHA 和父 PR。父先合入,子同步最新 main 并改回 main 为 PR base。
|
||||
|
||||
@@ -37,6 +50,9 @@ make task-start NAME=feature/my-task PRECHECK_RECEIPT=/tmp/my-task-precheck.json
|
||||
它是组合测试和构建的前置检查。父仓未提交改动可以局部测试,但证据必须记录实际对象状态;
|
||||
正式验收使用已提交的固定对象,不能仅写一个与工作树不同的 HEAD。
|
||||
|
||||
`task-sync` 对同一基线重复调用只核验,保留 HEAD、索引和卡片;实际合并新目标才追加一次记录。
|
||||
输出完整 sync_base、记录完成后的 tested_head、checked_at 及 changed;changed 表示是否实际纳入新目标,组件仍须核对。
|
||||
|
||||
## 本地记录与独立证据
|
||||
|
||||
本地 `make evidence` 一律生成 SELF。`SRC=GATE/REPLAY` 不可把本地执行升级为权威来源。
|
||||
@@ -51,6 +67,22 @@ PR 合入由智能体执行:先完成有效 Review,再同步并解决冲突
|
||||
命令含敏感值时展示脱敏形式,并记录原命令摘要。执行前后对象分别记录;命令改变 HEAD
|
||||
时不把执行前 SHA 冒充执行后对象。不能读取执行后状态时明确记录缺项。
|
||||
|
||||
### 候选固定前后
|
||||
|
||||
固定前,完成同步、必要检查、任务卡及本地收据提交,核对干净的组件组合,再读取和推送最终 HEAD。
|
||||
将这个 head、base、完整组件及相关配置引用到调度尝试;任务卡表内“记录前对象”不当作最终候选。
|
||||
|
||||
固定后,本地命令使用源码外唯一 RECEIPT 保存完整输出,平台 CI/Review/合入结果写调度记录:
|
||||
|
||||
```bash
|
||||
RECEIPT=/tmp/my-attempt-worktree-check.json CMD="make test-worktree" make evidence
|
||||
```
|
||||
|
||||
执行者选择位于当前 checkout 和 Git 元数据之外的未用绝对路径,工具保留 before/after、原命令退出码和 SELF 来源。
|
||||
这是 manual 流程,工具不提供工作树冻结或路径范围的 OS 强制隔离。命令改变源码、组件或相关配置时,
|
||||
保留实际记录,重新建立尝试及受影响验证;新增提交必须核验新 HEAD 的必需 CI。
|
||||
后续实际合入/清理后按授权闭合任务,再原样导入已保存收据;不向仍冻结的候选追加记录提交或补写未来结果。
|
||||
|
||||
凭据从环境或受保护文件引用,避免出现在命令字面量。脱敏是补充防护,不是允许输出秘密。
|
||||
执行脚本不通过 shell tracing 暴露环境;管道任何一段失败应传回非零退出码。
|
||||
工具写入失败本身也必须报错,不能只保留一个看似成功的退出状态。
|
||||
|
||||
@@ -109,6 +109,22 @@ class EvidenceTests(unittest.TestCase):
|
||||
VALIDATE_RECEIPT_PURPOSE="resume-local")
|
||||
self.assertEqual(data["output"], "unset")
|
||||
|
||||
def test_fixed_candidate_capture_preserves_checkout(self):
|
||||
self.git("add", ".task/main.md")
|
||||
self.git("commit", "-qm", "Save fixture card before validation")
|
||||
head = self.git("rev-parse", "HEAD")
|
||||
card = self.card.read_bytes()
|
||||
index = self.git("ls-files", "--stage")
|
||||
data = self.capture("printf 'fixed candidate check\\n'")
|
||||
self.assertEqual(data["before"]["head"], head)
|
||||
self.assertEqual(data["after"]["head"], head)
|
||||
self.assertEqual(data["output"], "fixed candidate check\n")
|
||||
self.assertEqual(self.git("rev-parse", "HEAD"), head)
|
||||
self.assertEqual(self.git("ls-files", "--stage"), index)
|
||||
self.assertEqual(self.card.read_bytes(), card)
|
||||
self.assertEqual(self.git("status", "--porcelain"), "")
|
||||
self.assertEqual(self.receipt.stat().st_mode & 0o777, 0o600)
|
||||
|
||||
def test_capture_without_card_and_import_without_rerun(self):
|
||||
self.card.unlink()
|
||||
data = self.capture("printf 'once\\n' >> invocation; cat invocation")
|
||||
|
||||
Reference in New Issue
Block a user