feat(goals): gate Todo completion on owner-configured acceptance checks - #4683
Conversation
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Signed-off-by: song <22676124+songoow@users.noreply.github.com>
The typed authority owned the acceptance contract but nothing could read a basis or run its checks from a real entry point. Add the host adapter and `loopx goal-acceptance inspect|configure|verify|disable`, so the owner configures criteria against an exact provider revision and verification executes only the argv that authority stored. No caller-supplied pass/fail crosses the boundary: `verify` runs the commands through the existing completion-validation runner and commits receipts back under the revision it read. Registered Agents may inspect and verify; only the local owner may change the contract. Declared verifier files carry a sha256 pin that is checked before and after execution, so editing a verifier mid-run yields `validation_basis_changed` rather than reusing the configured basis. Pins cover declared assets, not inferred dependencies. Passing the configured checks is reported separately from acceptance: a contract whose task bindings are unsatisfied returns `checks_passed: true` with `acceptance_ready: false`. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Require current owner-confirmed acceptance for canonical claims, lease acquisition and completion. Plan fresh validation effects and bind receipts to the exact provider, contract and Todo before committing completion with its evidence. Carry native acceptance decisions into status and quota without adding canonical Todo fields. Preserve absent-acceptance wire and receipt behavior; validate with real file/PostgreSQL providers and public CLI reads. Signed-off-by: song <22676124+songoow@users.noreply.github.com> (cherry picked from commit 79d6f265d3361f9855b362cd67c7cfba3e913514)
The terminal authority now returns `goal_acceptance_validation_effects` with the basis it bound them to, but the host still executed only the single declared completion validator, so the plan named checks that never ran. Execute each demanded criterion through the pinned acceptance runner and return the receipts with the source binding the authority issued. The host never invents that binding: a plan that omits it raises rather than letting a completion carry unbound receipts, and authority rejects receipts whose binding does not match what it planned. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
The panel and its schema landed in dashboard source, but the observation never carried `goal_acceptance_contract` and the served bundle predates the panel, so the owner's configured basis reached no reader. Carry the enabled contract from whichever canonical summary holds it, and rebuild the shipped chat bundle so the panel is actually served. Absent or disabled acceptance leaves the observation shape unchanged. `acceptance_assessed` stays false. Executed checks are narrower than an independent judgment that the Goal is met; the panel shows their basis, not a verdict. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
The authority planned acceptance criteria and the host executed them, but no test crossed that seam: `terminal_canonical_todo_if_promoted` had no coverage at all, so a host that returned the plan unexecuted would have passed. Drive `loopx todo complete` twice with identical configuration, binding and command, changing only whether the artifact exists. Only real execution separates a refusal carrying `goal_acceptance_validation_rejected` from a completion carrying the criterion's receipt. Stubbing out the host's execution branch fails this test; without it the same branch is unobserved. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
The smoke passed but nothing could reach it: no package script, no workflow and no importing smoke named it, so only prose in the reference doc did. It also resolved `.venv/bin/python`, a path that exists neither in CI nor in a plain checkout, so the documented invocation could not have run either. Name it alongside `smoke:goal-acceptance-browser`, with the packaged variant its siblings have, and default to `python3` on PATH as every other smoke does. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
Both mirrors described the browser check by file path, which no longer matches how it is invoked. Point them at the named scripts, and say that the packaged variant is a script rather than an environment variable the reader sets by hand. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
…t run `node-minimum-compatibility` runs every `tests/control_plane_ts/*.test.ts` outside an exclusion list of SQLite-dedicated suites, on Node 22.18.0. This file is not SQLite-dedicated — it carries file, sqlite and postgresql rows — so its 11 sqlite rows failed there with "SQLite authority runtime is not qualified (SQLite 3.50.2)". Excluding the whole file in the workflow would also drop its file rows, which are exactly what that job exists to qualify. Skip only the sqlite rows when `sqliteRuntimeIdentity()` reports the driver unqualified, mirroring how the postgresql rows already skip without `LOOPX_TEST_POSTGRES_URL`. Coverage is unchanged where it can run: `kernel-static-checks` pins Node 22.22.3 and executes every row there. Forcing the predicate true makes all 11 fail again, so the guard is conditioned on the runtime rather than disabling them. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
这套设计为什么是现在这个样子补充一份设计依据。下面每一条都引用仓库里已有的陈述,而不是我的转述;仓库没有给出理由的地方,我明确标注 未陈述,不代为编造。 1. 为什么 TypeScript 持有权威,Python 只做解析与执行不是分层习惯,是仓库的一条硬规则——禁止第二个事实来源。
CAS 归属同一个所有者也有出处, 值得一提的是 2. 为什么不接受调用方给出的 pass/fail
而且通过与否不是被「声明」的,是被推导的—— acceptanceRequire(item.passed === (item.exit_code === 0),
"criterion pass/fail must agree with exit code zero");
背后的原则在 3. 为什么是 plan → execute → commit 两阶段,而不是权威自己去跑命令
关键不在「谁方便执行」,而在于结果必须与执行时读到的那个 revision 绑在同一次 CAS 里。执行期间契约若发生变化, 需要说明的是:未陈述——仓库没有任何一行说「权威不得产生副作用/不得起进程」。它表达的是归属与 CAS,不是 reducer 的纯函数约束。 4. 为什么公共投影里绝不出现 argv、stdout、stderr 和本地路径
这条规则在四处独立落地: 5. 为什么验证预算是 29 秒这个奇怪的数字
一次完成里同时要跑声明的 caller validator 和全部绑定 criteria,所以 6. 为什么
|
songoow
left a comment
There was a problem hiding this comment.
Review of 4683@35b58b7ba per pull_request_review_execution_contract_v2 (policy_revision 6), action review_pull_request_exact_head, depth runtime_behavior_review.
结论:请求修改。 PR 的核心承诺——「绑定的 Todo 在其配置的验收条件真正运行并通过之前无法完成」——按字面并不成立:该门禁只覆盖 todo complete 这一条路径。已复现三条 HIGH 级绕过,最短一条只需一条命令。
动机
这个切片要解决的是 Goal 漂移治理的第三个判定点:完成声明必须由作者状态字段以外的东西支撑。
改动前的状态不是「没有设计」,而是「设计到不了真实入口」:类型化权威已经拥有验收契约,Dashboard 源码里也已经有面板,但没有任何 host adapter 或 CLI 能读到基准或运行其检查;终端权威已经返回 goal_acceptance_validation_effects,而 host 只执行了那一个声明的 completion validator,于是完成计划点名的条件从未被执行;观测从不携带 goal_acceptance_contract;已发布的 chat bundle 早于面板。此外 terminal_canonical_todo_if_promoted 完全没有测试覆盖。
交付判定:这不是一个完整的切片。 它在 todo complete 上是真实可用的,但同一个权威模块把 supersede 也当作终端命令并写入 done: true,而验收门禁没有跟上。按 AGENTS.md:15-17——「Small diffs, fields, receipts, test counts and merged PR counts do not establish progress」——新增 1207 行测试不能替代这个缺口的闭合。
改动思路
- 入口:
loopx goal-acceptance inspect|configure|verify|disable(新增),以及既有的loopx todo complete。 - 权威状态:TS 契约内核
acceptance_contract.ts/acceptance_authority.ts,置于 provider CAS 之下。 - 判定边界:TS 判定,Python 只解析已注册 Goal 并执行 owner 存下的 argv。这不是分层偏好,是
AGENTS.md:326-329的硬规则——Python「must not silently recreate a second source of truth」。 - 正向路径:
todo complete→ 权威返回status: execute_validation并附goal_acceptance_validation_effects+source_binding→ host 执行 → 携收据重新提交 → 权威比对 binding 后 commit。两阶段的理由在acceptance_contract.ts:293-294:结果必须与读到的那个 revision 绑定在同一次 CAS 中。 - 既有实现复用:验收检查走既有的
run_declared_completion_validation_effect,没有新建第二个 runner;通过与否是推导的而非声明的(acceptance_contract.ts:250断言passed === (exit_code === 0))。
具体改动
| 分类 | 文件 | 增/删 |
|---|---|---|
runtime (loopx/) |
21 | +1286 / −16 |
| tests | 5 | +1207 |
| docs | 7 | +230 / −9 |
| dashboard src + smoke | 11 | +376 / −1 |
| 生成产物 bundle | 6 | +144 / −142 |
关键代码讲解
1. acceptanceApplies / advancement(loopx/control_plane/goals/acceptance_contract.ts:160-166) — 决定一个 Todo 是否受验收约束:
function advancement(todo: JsonObject): boolean {
return todo.role === "agent" && (todo.task_class == null || todo.task_class === "advancement_task");
}
export function acceptanceApplies(todo: JsonObject): boolean {
return advancement(todo) && todo.archive_state === "active" &&
(todo.status === "open" || todo.status === "blocked") && todo.done === false;
}适用性完全由 task_class 与 status 两个可变字段推导。被门禁约束的一方可以修改它们——这是下面第 2、3 条绕过的根因。
2. 终端门禁(loopx/control_plane/coordination/todo_terminal_lifecycle.ts:805, 811)
const acceptance = input.command === "complete" ? acceptanceWorkGuard(...) : null;
const acceptanceRequirements = input.command === "complete" ? acceptanceCompletionRequirements(...) : null;而同文件 :60 定义 TERMINAL_COMMANDS = ["complete", "supersede"],terminalTarget(:648-651)对两者都写入 status: "done", done: true。门禁锚在两条终端命令中的一条上。
3. projectGoalAcceptance(acceptance_contract.ts:259-278) — 状态推导是严谨的:accepted 要求 Goal 级收据同时匹配契约 revision、digest 与当前 work digest,无失败项,todo_id === null,且无 applicable 任务处于非 ready。契约或工作内容任一漂移即转 stale,逐 Todo 完成只会得到 partial。这部分没有问题。
4. run_goal_acceptance_validation_effect(loopx/control_plane/goals/acceptance.py:143-225) — 在执行前后各校验一次声明校验器的 sha256,中途改写校验器得到 validation_basis_changed 而非沿用原基准。符号链接、绝对路径、.. 穿越均被拒(:182-191),配置期在 acceptance_contract.ts:102-104 另有一道。
对主干的风险
阻塞项(已复现,均为 file provider,本地实跑)
B1 — todo supersede 完全绕过验收门禁(HIGH,一条命令)。
触发状态:Goal 启用验收,todo_export 已绑定且 ready,artifact.txt 不存在。
观察结果:todo supersede 将该 Todo 写为 status: done, done: true,零条件被执行;随后契约报告 held_todo_ids: []、status: "unverified"、verification: null,而摘要报告 advancement_done_count: 1。即:门禁被跳过后,聚合视图看起来像正常进展——这正是本功能要防的漂移。
最小修复:验收门禁应锚定在「任何写入 done: true 的终端命令」上,而非 command === "complete"。supersede 的语义是否应要求条件通过、还是仅要求关联确认(work guard)而不要求执行,属于设计决策,应由 owner 判定;但当前「完全不评估」不是可辩护的第三种选项。
B2 — todo update --task-class blocker → todo complete(HIGH,两条命令)。
advancement() 要求 task_class 为空或 advancement_task;而 todo_update.ts:370 仅在 input.planning_intent?.claimed_by != null 时才咨询验收 guard,因此 Agent 可自由改写 task_class,随后完成时 requirements 为 null,无证据提交。
最小修复:适用性不应由被门禁方可改写的字段推导,应以 owner 确认的 binding 为准;或将 task_class/status 纳入受验收保护的字段集。
B3 — todo update --status deferred --resume-when <future> → todo complete(HIGH)。
同一形状:acceptanceApplies 将适用范围限定在 open/blocked,deferred 直接脱离白名单。
三条共享同一根因:门禁键取自可变的 Todo 字段与两条终端命令中的一条,而这些字段的写入路径未被同一道门禁覆盖。
非阻塞
- 默认契约没有校验器完整性(MEDIUM):
validation_files可选且默认[],acceptance.py:174-175将空 pin 视为恒匹配。一个自然写法validation_argv: [python, "verify.py"]允许 Agent 把verify.py改成pass后以passed: true完成。文档已声明 pin 覆盖范围有界(acceptance.py:153-154),但默认路径缺少提示。 --dry-run预览与真实调用矛盾(LOW):对已绑定 ready 的 Todo,todo complete --dry-run返回status: planned, completed: true且不含任何goal_acceptance*字段,而真实调用被拒。无写入,但预览在此处会误导。- 一处未解释的不对称:
todo_terminal_lifecycle.ts:720-721在验收 guard 之前返回重放收据,而todo_claim.ts:413-424有显式的重放后复检。未能构造出可利用路径(收据仅存在于已提交操作),但不对称本身值得说明。
已证伪的攻击(门禁确实成立)
Agent 角色的 configure/disable 均被拒(acceptance_authority.ts:29);todo update --status done 被 native_update_plan.ts:79-81 挡下;--status blocked → complete 仍在门禁内;符号链接校验器、四种路径穿越变体、校验器改写另一声明文件,均被执行前后双重 hash 拦截;收据重放无法构造——source binding 每次从活动 head 重算并做 digest 比对,operation_id 每次调用新生成,provider_revision 每次提交递增。
边界与隔离
- 公共边界干净:
loopx status、todo list、quota context、goal-acceptance inspect|verify、完成载荷与失败载荷中均无validation_argv、无 stdout、无验收相关本地路径。 - default_off_isolation:实测而非推断。 同一个未启用验收的 fixture goal,分别用
origin/main与本分支运行真实loopx status:输出均为 27764 字节,差异仅为 4 处时间戳与public boundary scan clean: 1136 → 1138 files。后者是两个新增.py模块进入扫描计数(loopx/contract.py:159的DEFAULT_SCAN_SUFFIXES不含.ts),非行为差异。 - authority_semantics:PR 描述原先写「only the local owner may change the contract」,与
docs/reference/goal-acceptance-observations.md:113-116的「a trusted local invocation role, not an authentication boundary」冲突,已按AGENTS.md:280-282更正描述。契约状态accepted的含义在:162已有明确限定(「this does not approve or complete the Goal」),此处无发现。
验证矩阵
| 项 | 结果 |
|---|---|
canary premerge --from-git-diff |
ok: true(direct 5/5,catalog 10,0 failures) |
| 验收 pytest 套件(file provider) | 41 passed |
受影响 tests/control_plane/ |
145 passed |
| 验收 TS(file provider) | 25 passed / 21 skipped |
smoke:delivery-review / smoke:goal-acceptance-contract-browser |
passed |
docs-governance-smoke |
ok |
CI on 35b58b7ba |
DCO / Dependency Review / Desktop Release Artifacts / Frontstage Pages / PostgreSQL Integration / Release Artifacts 均 success;Python Tests 仍在队列中 |
未在本机验证:全部 sqlite provider 行(本机 Node 22.22.2 / SQLite 3.51.2 不满足资格门槛,在未修改的 origin/main 上同样失败),以及全部 postgresql 行(LOOPX_TEST_POSTGRES_URL 未设置;PostgreSQL Integration workflow 只跑 authority-service 与 store 集成测试,不含本套件)。按 AGENTS.md:311-313,跳过的测试不满足该门。
我的整体评价
方向与归属是对的,机制的大部分是扎实的:TS 持权威、Python 只执行、通过与否由退出码推导、收据必须新鲜、公共投影经四处独立白名单收紧、校验器 pin 前后双检、acceptance_assessed 保持 false 且与 checks_passed / acceptance_ready 三者分离。证伪阶段里绝大多数攻击都被真实挡住了,包括我认为最有希望的收据重放。
但按 reconcile_verdict——「Approve only when positive value, architecture fit, and applicable evidence are established」——这里缺的是第三项中最关键的一条:PR 陈述的那条材料承诺在一条命令下即不成立。B1 不需要竞态、不需要特权、不需要构造,只需要换一个终端命令。
因此请求修改,缺失的观察已具体化为 B1/B2/B3 的最小修复。我的建议是把适用性判定从可变 Todo 字段迁移到 owner 确认的 binding,并把门禁锚定到写入 done: true 的终端命令集合——这三条会被同一处修复一并关闭。supersede 在验收下究竟应当要求条件通过还是仅要求关联确认,需要 owner 定,不应由实现默认。
另需说明:Python Tests 尚未完成,按契约 final CI 是出具结论的必要条件之一;上述结论基于已复现的阻塞项,与该 workflow 的结果无关,但完整的 CI 判定仍需等它落地。
🤖 Generated with Claude Code
…elds the guarded party can edit Review of this PR found three ways to close a bound Todo with its criteria never executed. They are not three defects; they are two wrong premises. **The gate keyed off the Todo's current shape.** `acceptanceApplies` derives applicability from `task_class` and `status` — fields the Agent under the gate may rewrite. `todo update --task-class blocker` or `--status deferred`, then `todo complete`, and `acceptanceCompletionRequirements` returns null. Acceptance is not a property of how a Todo currently looks; it is a commitment the owner made about a piece of work, already recorded as an owner-confirmed binding. So applicability now follows that binding. A Todo's shape can still make the binding stale — the work digest catches that, and `task_class` is a work field, so flipping it refuses as `goal_acceptance_stale` rather than passing. It can no longer make the binding absent. `acceptanceApplies` keeps its other job, which it does correctly: deciding which *unbound* work must be held. One predicate answering two different questions was the error. **The gate keyed off a verb.** Both checks were `input.command === "complete"`, but `TERMINAL_COMMANDS` is `["complete", "supersede"]` and `terminalTarget` writes `status: "done", done: true` for both. `todo supersede` closed bound work with zero criteria executed, and left no trace: the contract reported `held_todo_ids: []` while the summary counted an advancement task done. The invariant was never about a command name — nothing may reach `done: true` without its bound criteria satisfied. The guard now follows that transition, so a future terminal command inherits it instead of silently bypassing it. Superseding bound work whose criteria have not run is refused rather than redefined. Whether `supersede` should run the criteria or only require a confirmed association is the Goal owner's decision, not this patch's; the refusal names both ways out. Fail-closed until that is decided. Each row of the new test fails against the code before this change and passes after. The honest path is unaffected: a bound Todo with its artifact present still completes and still records the criterion's receipt. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
这是 Goal 漂移治理那条线的第三个决策点:完成声明必须由作者状态字段之外的东西背书。本 head 把它做成按 Goal opt-in 的验收依据:owner 配置一份带 revision/digest 的 contract,被绑定到这个 Goal 的 Todo 在配置的判据真正跑过之前不能关。
上一版的缺口是真实且具体的:typed authority 早就拥有 acceptance contract,dashboard 也早有面板,但两者都到不了任何入口——没有 host adapter、没有 CLI 能读依据或执行判据;终端 authority 返回 goal_acceptance_validation_effects,而 host 只跑那一个声明的 completion validator,于是"completion plan 里写着的判据从未执行";observation 里从不带 goal_acceptance_contract;terminal_canonical_todo_if_promoted 完全没有测试;连浏览器 smoke 都指向一个 CI 与普通 checkout 里都不存在的 .venv/bin/python。所以这不是"补一个面板",而是把已有所有权接上真实入口和真实读者。
改动思路
- 所有权不变:TypeScript 拥有 revision / binding / admission / CAS,Python 只解析注册的 Goal 并执行 owner 存下的 argv。没有任何调用方提供的 pass/fail 能跨过这条边界,
validation_argv也不进入任何公开投影。 - 完成路径的判据必须真跑:
provider_terminal_lifecycle.py现在把goal_acceptance_validation_effects逐条执行并带上goal_acceptance_source_binding;TS 侧acceptanceCompletionEvidence要求 receipts 是同一次绑定的新鲜回执、command_label与判据 id 一致、stdout_captured/stderr_captured/local_path_captured全为 false,再经validateAcceptanceCompletion才允许提交。 - 判据文件带 sha256 钉:执行前和执行后各校验一次,中途被改返回
validation_basis_changed;拒绝符号链接与..路径穿越,只跑 owner 存下的 argv。 - 角色只到"可信本机调用":带
--agent-id的调用可以 inspect/verify,不能 configure/attest(acceptance_authority.ts:29要求actor_agent_id === null);文档与 PR 描述都写明这不是认证边界。 - 默认关闭:未配置 contract 的 Goal 完全按原样完成,这是被断言的 off-parity 而不是口头承诺。
具体改动
关键代码讲解
loopx/control_plane/goals/acceptance.py(新,297 行):_routing强制"已注册 Goal + 已注册 Agent + 已存在 canonical authority(激活永不自行升级 provider)";run_goal_acceptance_validation_effect复用既有 declared-validation runner,只加钉校验与逐判据扇出。loopx/control_plane/todos/provider_terminal_lifecycle.py:补上"plan 必须执行"这条——收集全部验收回执并携带 source binding,缺失即RuntimeError,不再静默只跑一个 validator。todo_terminal_lifecycle.ts:新增acceptanceWorkGuard/acceptanceCompletionRequirements/acceptanceCompletionEvidence,把"被绑定的 Todo 必须满足判据"变成提交前不可绕过的准入。acceptance_authority.ts/acceptance_contract.ts:revision、digest、CAS 与角色检查;loopx/web/chat的打包产物同步重建(旧 bundle 删除、index.html指向新哈希、asset-retention.json收录)。- 测试:
test_goal_acceptance_cli.py(286)、test_goal_acceptance_runtime.py(99)、goal_acceptance_authority.test.ts(447)、goal_acceptance_runtime.test.ts(279)、test_goal_acceptance_contract_rendering.py(96)。
我实际跑出来的结果
python -m pytest tests/control_plane/test_goal_acceptance_cli.py \
tests/control_plane/test_goal_acceptance_runtime.py \
tests/test_goal_acceptance_contract_rendering.py -q → 28 passed
node --test tests/control_plane_ts/goal_acceptance_{authority,runtime}.test.ts
→ 36 passed, 10 skipped
npm run smoke:delivery-review → passed(含 tsc 构建)
node smoke/goal-acceptance-contract-smoke.mjs → passed
打包一致性:index.html → index-Ca-NQZt1.js / index-TYklSAhI.css 均在且在 asset-retention.json 内;
大包里含 acceptance 文案(151 处),旧 bundle 已删除
TS 侧那 10 个 skip 是 sqlite-provider 行:sqlite_authority_qualified 要求 synchronous_statement_finalization === true && hasSqliteWalResetFix(version),本机 node v24.21.0 不满足;Python 侧同一语义的 sqlite 行是实跑的(fixture 参数化 file/sqlite,无 skip)。所以残余缺口是 TS 的 sqlite 本地导出路径。
对主干的风险
没有阻塞发现,但有必须写明的证据缺口和两条非阻塞项:
- [P3] 这 10 个 sqlite 行在本机与(按最后一个 commit 自己的说法)public minimum 上都 skip,而 diff 里没有别处跑它们;Python 侧覆盖了 sqlite provider,但 TS 的本地导出路径在公开 CI 上无人验证。建议放进 runtime 合格的 job,或在参考文档里直接写明这一缺口。
- [P3] 我没有执行浏览器与打包版浏览器 smoke(
smoke:goal-acceptance-contract-browser与其 packaged 变体需要 Playwright)。我只跑了文档里列出的两条非浏览器命令,并核对了打包产物确实引用了新资源、且包含面板文案——面板的渲染验证仍只有作者声明。 - [P3] 必需检查
kernel-static-checks又在 15 分钟上限被取消(本 head 15m15s:02:52:58Z→03:08:13Z),checks聚合器随之报红,另有一个 test shard 当时仍在跑。今天第五次(#4663/#4680/#4664/#4651/本 head),而 #4682 的同一作业通过——CI 容量问题,不是本 diff 的属性。
默认关闭这一条我确认是被断言的:TS 侧 off-parity 行 + 浏览器 fixture smoke 都断言"contract 缺失或 disabled 时,即使残留上次成功数据也保持基线导出"。
我的整体评价
这是把"完成声明不可自证"落到真实入口的一次完整接线,而且接线的方向是对的:类型与所有权留在 TS,Python 只做解析与执行;回执只允许来自可信适配器,且必须是这次绑定的新鲜证据;判据文件的前后钉把"跑完再改"堵住;角色限制写在代码里而不是靠文档提醒;默认关闭被测试而不是被宣称。我特别看重它补上了"terminal plan 从未被执行"和"那个函数零覆盖"这两条——正是前者让上一版的契约形同虚设。
我没能找到可阻塞的问题,但也不打算把没跑的东西说成跑过:浏览器/打包渲染 smoke 与 sqlite 的 TS 行是我这份证据里的空白,另有那条今天第五次出现的 CI 超时。建议合并前把浏览器 smoke 跑一遍(或明确接受该缺口),并把 15 分钟上限当独立问题修掉。
English verdict: APPROVE - head 35b58b7 wires the pre-existing Goal acceptance contract to real entry points and enforces the boundary it claims: an unconfigured Goal keeps ordinary completion (asserted in the TypeScript parity rows and the browser-fixture rendering smoke), a bound Todo cannot close until the configured criteria run against the exact basis, only owner-stored argv is executed, declared verifier files are sha256-pinned before and after execution with symlinks and traversal rejected, receipts must be fresh and public-safe for the same source binding, and --agent-id callers may inspect and verify but never configure or attest; I ran the CLI/runtime/rendering tests (28 passed), the two TypeScript acceptance suites (36 passed, 10 sqlite rows skipped because sqlite_authority_qualified is false on this runtime while the Python side does run the sqlite provider), and both documented non-browser smokes in sequence (passed), and confirmed the packaged bundle references the new hashed assets and carries the acceptance copy - with three P3s recorded: those skipped TypeScript sqlite rows are not run elsewhere on the public minimum, I did not execute the browser or packaged browser smokes, and the required kernel-static-checks job was cancelled at its 15-minute cap for the fifth time today.
`todo complete --dry-run` on bound work reported `status: planned` with no acceptance field at all, while the real call on the same state was refused. A preview whose only difference from the real call is that it omits the gate is not a preview; acting on it is how an operator learns the gate exists. Name the criteria the real call must run — contract revision, digest and criterion ids. Not their argv: a preview is a projection like any other, and `validation_argv` stays out of every projection. This reports the obligation rather than predicting its outcome. The preview still writes nothing and still says `planned`; it no longer implies the work would close unconditionally. Removing the disclosure fails the new test. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
三条阻塞项已修复 —
|
| 路径 | 结果 |
|---|---|
B1 todo supersede(已绑定、条件未满足) |
拒绝 · goal_acceptance_validation_required |
B2 --task-class blocker → complete |
拒绝 · goal_acceptance_stale |
B3 --status deferred → complete |
拒绝 · goal_acceptance_stale |
| 对照:artifact 存在时正常完成 | changed=true,记录 {criterion_id: export, passed: true, exit_code: 0} |
三条用例在本次改动之前的代码上全部失败,之后全部通过;诚实路径不受影响。
一并修掉的 LOW 项
todo complete --dry-run 原先对已绑定工作返回 status: planned 且不含任何验收字段,而真实调用被拒。预览与真实调用的唯一差别是它省略了门禁——那不是预览。现在预览披露 goal_acceptance_pending: {contract_revision, contract_digest, criterion_ids},不含 argv(预览和其它投影一样受同一条公共边界约束)。它报告义务,不预测结果;仍然不写入、仍然是 planned。
我刻意没有决定的两件事
supersede在验收下应当意味着什么。 当前是 fail-closed 拒绝,并在错误信息里给出两条出路(改用 complete 让条件运行,或请 owner 重新绑定/停用)。它究竟应该要求条件通过、还是只要求关联确认,是 Goal owner 的语义决定,不该由这个补丁默认。更深一层:terminalTarget让 supersede 也写done: true,于是被取代的工作计入advancement_done_count——「离开活动集」与「被验收」混为一谈,正是绕过在聚合视图里看不见的原因。这值得单独决定。- 校验器 pin 的默认值。
validation_files可选且默认[],acceptance.py:174-175把空 pin 当作恒匹配,于是默认契约没有校验器完整性,安全配置反而是要额外动手的那个。把「validation_argv指向工作区内文件时自动 pin、允许显式退出」作为默认更合理,但这会改变既有契约在 configure 时的行为。
本地验证(704242a4b)
canary premerge ok:true(direct 5/5,catalog 10 项 0 失败,risk profile ok)· 受影响 tests/control_plane/ 138 passed · 验收 TS 58 tests / 0 fail / 21 skipped(skipped 为 sqlite 与 postgresql provider)· docs-governance-smoke ok。
唯一的 Python 失败是 test_canonical_lease_acquire.py::…[sqlite],在未修改的 origin/main 上以相同的 SQLite 资格错误失败,与本改动无关。
另:35b58b7ba 上的 CI 中 kernel-static-checks 为 cancelled——该 job 跑满 15 分钟超时,被取消的步骤是 "Qualify agent-facing CLI output";"Qualify the TypeScript Effect core" 是 success,即验收 TS 套件(含全部 sqlite 行)已在 Node 22.22.3 上真实执行并通过。同一 job 在未修改的 main 上同样 cancelled,非本改动引入。
🤖 Generated with Claude Code
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
这是我在 35b58b7b 上批准之后的复审,而这轮要先把一件事说清楚:那两条绕过路径确实存在,而我上一轮没有发现它们。
作者在批准后自查(commit message 写得很直白:"Review of this PR found three ways to close a bound Todo with its criteria never executed")——追根到底只有两个错误前提:
- 门禁以 Todo 当前的形状为准:
acceptanceApplies读task_class与status,而这两个字段正是被门禁约束的一方可以改写的。于是todo update --task-class blocker(或--status deferred)之后再todo complete,acceptanceCompletionRequirements直接返回 null,门禁消失。 - 门禁以动词为准:两处检查都写成
input.command === "complete",而TERMINAL_COMMANDS = ["complete", "supersede"],supersede同样写status: "done", done: true。用todo supersede关掉被绑定的工作,既不跑判据,又在投影里不留痕迹——contract 报held_todo_ids: [],而摘要把一条 advancement task 记成完成。
我上一轮检查了边界(钉、回执、角色、默认关闭),却没有问"还有哪些动词/字段能到达被守护的状态"。这是我这边的漏检,这次的复审就是纠正。
改动思路
- 适用性跟随绑定,而不是跟随形状:新增
acceptanceBound(state, todoId);acceptanceWorkGuard与acceptanceCompletionRequirements改为"有 owner 确认绑定"即适用,acceptanceApplies只保留它原本擅长的另一件事——判断哪些未被绑定的工作需要被 hold。形状变化仍能通过 work digest 让绑定变陈旧,但那会以goal_acceptance_stale拒绝,而不是让门禁消失。 - 门禁跟随"转移"而不是"动词":两处
input.command === "complete"判断删除,改为对每个 TERMINAL_COMMANDS 都生效——因为它们都经terminalTarget写done: true。这样将来新增的终局动词会自动继承门禁,而不是悄悄绕过。 - fail-closed 而不擅自定义语义:
supersede被绑定且判据未跑时拒绝,拒绝文案同时给出两条出路(补跑判据,或请 owner 重新绑定/关闭验收)。"supersede 到底该跑判据还是只要求已确认关联"被明确交回 Goal owner,而不是由这个补丁决定。 - 预览不再撒谎:dry-run 现在带
goal_acceptance_pending(仅 contract revision/digest 与 criterion ids,不含 argv),避免预览显示一次真实调用其实会拦下的关闭。
具体改动
关键代码讲解
acceptance_contract.ts:新增acceptanceBound;两个入口改为if (!acceptanceApplies(todo) && !acceptanceBound(state, todoId)) return null;,并在注释里写明"可变字段可以让绑定陈旧,但不得让绑定失效"。todo_terminal_lifecycle.ts:802:门禁与 requirements 不再看命令名;非complete的终局命令失败文案改为"${command}would close this work as done without running the owner-configured acceptance criteria…"。todo_terminal_lifecycle.ts:1099:新增goal_acceptance_pending预览字段。tests/control_plane/test_goal_acceptance_cli.py:新增test_bound_work_cannot_be_closed_by_editing_its_way_out_of_the_gate与test_preview_discloses_the_criteria_the_real_call_will_run。
我实跑与"写入者枚举"
python -m pytest tests/control_plane/test_goal_acceptance_cli.py \
tests/control_plane/test_goal_acceptance_runtime.py -q → 26 passed
node --test tests/control_plane_ts/goal_acceptance_{authority,runtime}.test.ts
→ 36 passed, 10 skipped(sqlite 行,同上一轮)
因为这次的教训是"别再只信测试",我额外把写入 done: true 的路径枚举了一遍:
todo_terminal_lifecycle.ts:651的terminalTarget—— 两个终局动词共用,现在都被门禁覆盖;todo_update.ts:299的next.done = status === "done" || status === "deferred"—— 该路径在todo_update.ts:371同样调用acceptanceWorkGuard,且planNativeTodoUpdate对target_status === "done"直接抛错;deferred需要--resume-when且被摘要单独计入deferred_count(不是完成)。
也就是说"任何东西都不能在绑定判据未跑的情况下到达 done: true"这条不变量,在我能找到的写入者上是成立的。
对主干的风险
无阻塞发现。一条 P3(与上一轮相同):TS 侧 10 个 sqlite 验收行在 sqlite_authority_qualified=false 的运行时上仍然 skip,而 diff 里没有别处跑它们;Python 侧覆盖了 sqlite provider,残余缺口是 TS 的 sqlite 本地导出路径。
另外仍要承认:这个绕过类别可以复发——只要将来有人新增一个写 done 标志的路径而不问门禁。门禁改为跟随"转移"已消除最常见的两种,但"新写入者"这一类别要靠新增测试或共享不变量来兜。
我的整体评价
这次的修复方式我完全认可:不是把绕过路径一个个打补丁,而是把两个错误前提拆掉——适用性跟绑定、门禁跟转移;并且对 supersede 的语义拒绝而不擅自定义,把决定交回 owner。预览字段也顺手把"dry-run 显示会成功、真实调用会拦下"这种不一致关掉了。
我上一轮的批准漏掉了这两条绕过,这是我该记的账:审门禁类改动时,除了看它声称防住什么,还要枚举所有能到达被守护状态的路径(动词、字段、写入者),而不是只看被展示的那一条命令。这次我用写入者枚举加实跑补上了;代码侧我没有其它阻塞项。
English verdict: APPROVE (author-owned PR; published as a COMMENTED review because GitHub blocks formal self-approval) - head 704242a closes two real bypasses that were live at the version I previously approved: applicability now follows the owner-confirmed binding instead of task_class/status (fields the guarded party can rewrite) and the guard follows the terminal transition instead of the complete verb, so todo supersede no longer closes bound work and a shape change refuses as goal_acceptance_stale rather than disabling the gate; the refusal for a non-complete terminal command names both ways out and leaves the supersede semantics decision to the Goal owner, and a dry run now discloses the pending criterion ids without argv; I ran 26 Python acceptance tests (including the two new bypass tests) and the TypeScript acceptance suites (36 passed, 10 sqlite rows skipped), and enumerated the writers of the done flag to check the invariant rather than the suite - terminalTarget is shared by every terminal command and now guarded, and the update path already consults acceptanceWorkGuard while refusing a target status of done; one P3 remains (the TypeScript sqlite rows skip on an unqualified runtime), and my earlier approval did not catch these bypasses, which this re-review corrects.
Two conflicts, both from d068c25 (leased observations / recoverable quota settlement), which landed after this branch last synced. `local_authority_runtime.ts` is an import-line collision only: this branch added the acceptance import, main added COORDINATION_LEASED_MONITOR_POLL_REQUEST_SCHEMA to the adjacent line. Both retained. `active_state_todos.py` is not textual. Main *removed* the `monitor_writeback_contract_writer(fields, supported=False, ...)` call for the canonical branch, because canonical observation and successor transactions now carry current lease proof. Keeping both sides would have silently reverted that. Main's removal and its explanation are kept; this branch's `goal_acceptance_contract` / `goal_acceptance_work_guards` arguments to `canonical_todo_summary_fields` are retained alongside it. The writer is still used on the two legacy branches, so the binding is not dead. Acceptance, status and projection suites pass on the merged tree. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
…e-check Reviewing this PR flagged an unexplained asymmetry: `todo_claim` re-checks the acceptance work guard after reading a replay receipt, and the terminal path returns one immediately. Left unstated, the next reader either re-derives the reason or closes it with a redundant check. The two receipts differ in kind. A claim receipt grants work going forward, so replaying one after its binding changed would resume work acceptance now holds. A terminal receipt only reports a transition that already committed. Verified rather than argued: a closed Todo cannot be reopened (`unsupported_todo_update_target`), so no replay can meet work that never closed, and re-running a committed completion returns `changed: false` even with the artifact deleted. Re-checking here would cost a load per replay and protect nothing. No behavior change. Signed-off-by: song <22676124+songoow@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: song <22676124+songoow@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
这是 Goal-drift 治理这条线的第三个决策点:完成声明不能只由被约束方自己的状态字段背书。交付形态是"每个 Goal 可选开启的验收依据(acceptance basis)"。
上一轮我在 35b58b7b 上批准之后,作者自查发现两条真实绕过(commit message 写得很直白:review found three ways to close a bound Todo with its criteria never executed),根因只有两个错误前提:
- 门禁以 Todo 当前的形状为准:
acceptanceApplies读task_class与status,而这两个字段正是被约束方可以改写的。todo update --task-class blocker或--status deferred之后再todo complete,acceptanceCompletionRequirements直接返回 null,门禁消失。 - 门禁以动词为准:两处检查写成
input.command === "complete",而TERMINAL_COMMANDS = ["complete", "supersede"],supersede同样经terminalTarget写done: true。用todo supersede关掉被绑定的工作,既不跑判据,也不在投影里留痕。
我上一轮的批准漏掉了这两条,这是我该记的账。
改动思路
- 适用性跟随绑定,而不是跟随形状:新增
acceptanceBound(state, todoId);acceptanceWorkGuard与acceptanceCompletionRequirements改为"存在 owner 确认的绑定"即适用。形状变化仍能通过 work digest 让绑定陈旧,但那会以goal_acceptance_stale拒绝,而不是让门禁消失。 - 门禁跟随"转移"而不是"动词":删除两处
input.command === "complete"判断,改为对每个TERMINAL_COMMANDS都生效——因为它们都经terminalTarget写done: true,将来新增终局动词会自动继承门禁。 - fail-closed 而不擅自定义语义:
supersede被绑定且判据未跑时拒绝,拒绝文案同时给出两条出路(补跑判据,或请 owner 重新绑定/关闭验收),把"supersede 该不该跑判据"交回 Goal owner。 - 预览不再撒谎:dry-run 带
goal_acceptance_pending(仅 contract revision/digest 与 criterion ids,不含 argv),避免预览显示一次真实调用其实会拦下的关闭。
具体改动
关键代码讲解
loopx/control_plane/goals/acceptance_contract.ts:新增acceptanceBound;两个入口改为if (todo && !acceptanceApplies(todo) && !acceptanceBound(state, todoId)) return null;,注释写明"可变字段可以让绑定陈旧,但不得让绑定失效"。loopx/control_plane/coordination/todo_terminal_lifecycle.ts:TERMINAL_COMMANDS共用terminalTarget,门禁与 requirements 不再看命令名;非complete的终局命令失败文案改为${command} would close this work as done without running the owner-configured acceptance criteria…;预览新增goal_acceptance_pending。tests/control_plane/test_goal_acceptance_cli.py:新增test_bound_work_cannot_be_closed_by_editing_its_way_out_of_the_gate与test_preview_discloses_the_criteria_the_real_call_will_run。
在 5c7541c 上的复核
先说清 head 为什么动:git log --oneline --no-merges 704242a4..HEAD 全部是 main 侧提交(#4654、#4672 的监控租约、#4676、#4681 等),这个 head 是 Merge origin/main into codex/goal-acceptance-governance;合并后我把 PR 的三处标记逐一读回确认仍在:acceptance_contract.ts 的 acceptanceBound、todo_terminal_lifecycle.ts 的"门禁跟随转移"注释与守卫、以及 goal_acceptance_pending 预览字段。
实跑(exact head 5c7541ca,均为我自己执行、非引用 PR 描述):
pytest tests/control_plane/test_goal_acceptance_cli.py \
tests/control_plane/test_goal_acceptance_runtime.py \
tests/test_goal_acceptance_contract_rendering.py -q → 36 passed in 180.93s
node --test tests/control_plane_ts/goal_acceptance_authority.test.ts \
tests/control_plane_ts/goal_acceptance_runtime.test.ts
→ 36 passed, 10 skipped
LOOPX_PYTHON=.venv/bin/python node \
apps/presentation/dashboard/smoke/goal-acceptance-contract-browser-smoke.mjs
→ off parity, states, keyboard, locales, mobile, revision refresh and read-only export passed
浏览器 smoke 这一条是这一轮新增的读回:它跑的是真正发货的看板面(关闭态对齐、状态、键盘、语言、移动端、revision 刷新、只读导出),而上一轮我没有跑它。
写入者枚举(为什么我相信的是不变量,而不是测试)
我上一轮的教训是"别只看被展示的那一条命令",所以这次仍把写 done: true 的路径列出来核对:
todo_terminal_lifecycle.ts的terminalTarget—— 两个终局动词共用,现在都被门禁覆盖;todo_update.ts的next.done = status === "done" || status === "deferred"—— 该路径同样调用acceptanceWorkGuard,且planNativeTodoUpdate对target_status === "done"直接抛错;deferred需要--resume-when且摘要单独计入deferred_count(不是完成)。
也就是说"任何东西都不能在绑定判据未跑的情况下到达 done: true"这条不变量,在我能找到的写入者上成立。
对主干的风险
无阻塞发现。两点记录:
- P3(与上一轮相同):TS 侧 10 个 sqlite 验收行在
sqlite_authority_qualified=false的运行时上仍然 skip,diff 里没有别处跑它们;Python 侧覆盖了 sqlite provider,残余缺口是 TS 的 sqlite 本地导出路径。 - 记录更正:我上一轮在
704242a4上的记录被发布成COMMENTED,标题写成"author-owned PR;GitHub blocks formal self-approval"——这是错的,这个 PR 的作者不是评审账号,正式APPROVE是可用的。本轮改为正式批准,正文与结论一致。
另外仍要承认:这个绕过类别会复发——只要将来有人新增一个写 done 标志的路径而不问门禁。门禁改为跟随"转移"已消除最常见的两种;"新写入者"这一类要靠新增测试或共享不变量来兜。
我的整体评价
我完全认可这次的修复方式:不是给绕过路径逐个打补丁,而是把两个错误前提拆掉——适用性跟绑定、门禁跟转移;对 supersede 的语义拒绝而不擅自定义,把决定交回 owner;预览字段顺手关掉了"dry-run 显示会成功、真实调用会拦下"的不一致。这一轮我还补上了发货看板的端到端 smoke 读回,并确认合并 main 后 PR 自有 hunk 未被冲掉。代码侧我没有其它阻塞项。
English verdict: APPROVE - head 5c7541c differs from the head I previously reviewed (704242a) only by main's merge, and it keeps the two fixes that close real bypasses which were live at the version I first approved: applicability now follows the owner-confirmed binding instead of task_class/status (fields the guarded party can rewrite), and the guard follows the terminal transition instead of the complete verb, so todo supersede no longer closes bound work and a shape change refuses as goal_acceptance_stale rather than disabling the gate; the refusal for a non-complete terminal command names both ways out and leaves the supersede semantics decision to the Goal owner, and a dry run now discloses the pending criterion ids without argv; I verified it by running 36 Python acceptance tests including the two bypass tests, the TypeScript acceptance suites (36 passed, 10 sqlite rows skipped), and the shipped dashboard acceptance-contract browser smoke (off parity, states, keyboard, locales, mobile, revision refresh, read-only export), re-reading the PR's three markers after the merge, and enumerating the writers of the done flag rather than trusting the suite - terminalTarget is shared by every terminal command and now guarded, and the update path already consults acceptanceWorkGuard while refusing a target status of done; one P3 remains (the TypeScript sqlite rows skip on an unqualified runtime), the supersede question is deliberately left to the owner, and this review also replaces my earlier comment, which had wrongly labelled this author's PR as author-owned.
huangruiteng
left a comment
There was a problem hiding this comment.
动机
这是同一个 PR 的第四次结论(35b58b7b → 704242a4 → 5c7541ca → 014459f4)。相对我上一轮批准的 5c7541ca,唯一的 PR 自有改动是一条七行注释(docs(coordination): say why the terminal replay needs no acceptance re-check),其余全是 origin/main 的合并。既然我批准的是精确 head,就重新取证——但这一轮要判断的是"这条注释说的是不是真的"。
改动思路
PR 本身(前几轮已确认)做的是两件事:适用性跟随 owner 确认的绑定而不是 Todo 的可变形状,门禁跟随终局转移而不是 complete 这个动词;supersede 被绑定时 fail-closed 拒绝,dry-run 用 goal_acceptance_pending 披露将要运行的判据 id。
新注释要解释的是"为什么 todo 终局命令的 replay 不需要再做一次验收复查"(对照 todo_claim 的 replay 路径,那里必须复查)。作者给了三条理由。
具体改动
// Unlike `todo_claim`, this replay needs no post-replay acceptance re-check.
// A claim receipt grants work going forward, so replaying one after its
// binding changed would resume work acceptance now holds. A terminal receipt
// only reports a transition that already committed: it cannot exist for work
// that never closed, a closed Todo cannot be reopened
// (`unsupported_todo_update_target`), and a replay returns `changed: false`.
// Re-checking here would add a load per replay and protect nothing.
const replay = await terminalReceipt(input, requestSha).read(store);
if (replay !== null) return replay;我逐条核对了这三条理由(读代码,不是读注释)
- "replay 返回
changed: false" —— 成立。loopx/control_plane/coordination/command_receipt.ts:49:changed: phase !== "replayed" && payload.changed,replay 阶段被强制为false。 - "已关闭的 Todo 不能被重新打开(
unsupported_todo_update_target)" —— 成立。todo_update.ts:171:if (todo.status === "done") return failure("unsupported_todo_update_target", …)。 - "终局 receipt 只报告已经提交的转移" —— 方向成立,但措辞比代码略强:
commitTerminalResult在mutations为空(changed: false)时也会写 receipt,所以断言"它不可能为从未关闭的工作而存在"应读作"它只可能由终局提交路径产生",而不是"凡是 receipt 必然意味着发生过关闭"。这是 P3 措辞精度,不是缺陷——因为两种情形下 replay 都不能把工作交回给某个 agent。
实跑(exact head 014459f4)
pytest tests/control_plane/test_goal_acceptance_cli.py \
tests/control_plane/test_goal_acceptance_runtime.py \
tests/test_goal_acceptance_contract_rendering.py -q → 36 passed in 188.09s
node --test tests/control_plane_ts/goal_acceptance_authority.test.ts \
tests/control_plane_ts/goal_acceptance_runtime.test.ts
→ 36 passed, 10 skipped
git diff 5c7541ca..014459f4 -- <PR files> → 仅 todo_terminal_lifecycle.ts +7(即上述注释)
三处标记(acceptanceBound、终局转移守卫、goal_acceptance_pending 预览)在新 head 上读回仍在。
对主干的风险
无阻塞发现。三点记录:
- P3:注释措辞略强(见上第 3 条),建议改成"终局 receipt 只由终局提交路径写入",因为 no-change 的终局调用同样会留下 receipt。
- P3(沿用):TS 侧 10 个 sqlite 验收行在
sqlite_authority_qualified=false的运行时上仍 skip;本轮我没有重跑上一轮通过的发货看板浏览器 smoke(head 只多了注释,但这是我这次的证据边界,明确写出来)。 - 记录:
supersede该不该跑判据仍按设计留回 Goal owner。
我的整体评价
用注释解释"为什么这里不需要检查",比默默省略检查要好得多,而且作者给出的三条理由都能在代码里找到对应(我逐条核过)。它也是这次审阅里少见的"补的是理解而不是逻辑"的改动:行为零变化,可读性提升。只有第三条的措辞建议收紧。可以接受。
English verdict: APPROVE - head 014459f differs from the head I approved (5c7541c) by one seven-line comment plus main's merge, and I checked the comment's three claims against the code instead of taking them on trust: a replayed terminal receipt is forced to changed: false (command_receipt.ts:49), a closed Todo cannot be reopened because the update path rejects a done status with unsupported_todo_update_target (todo_update.ts:171), and the receipt is written only from the terminal commit path - with the one wording nit that commitTerminalResult also writes a receipt for a no-change outcome, so "it cannot exist for work that never closed" reads slightly stronger than the code (P3, no behavioural consequence since a replay cannot hand work back to an agent either way); I re-ran the PR's own suites at this head (36 Python acceptance tests, TypeScript 36 passed with 10 sqlite rows skipped), read the three markers back, and confirmed the only PR-authored delta is the comment; the standing notes are that the TypeScript sqlite rows still skip on an unqualified runtime, that the supersede semantics stay with the Goal owner, and that I did not re-run the dashboard browser smoke for this comment-only head.
|
Integration assessment against 这项工作与多 Agent 主线共享同一目标:#4688 解决本地/云端有界执行与产物验证,本 PR 提供可复用的 owner-configured 绑定、漂移门禁和 canonical 完成事务。应复用这里的 TS Goal/Todo owner,Python 保留 SDK/进程适配和领域验证器;不应把研究示例的 本次补充验证:Python CLI/runtime/rendering 36 passed;TS authority/runtime 52 passed、零 skipped,覆盖真实 File、SQLite 和新建的隔离 PostgreSQL 16。另将 #4688 已有真实运行的合成产物副本交给本 PR 的执行/完成入口,在 File、SQLite 上验证五个预先绑定任务:子任务可在总报告缺失时完成;过期依赖哈希拒绝 lead 完成;旧验证通过后修改产物仍被新一次完成检查拒绝;改写 Todo 工作内容使绑定 stale,owner 重新确认后才能继续;最后五个 Todo 完成,但 Goal 保持 active。这是产物/合同组合验证,没有重新调用模型,也不代表 autonomous launcher 已接通。 集成需要保留两个边界:
可复现的文档修正点: reference JSON 的第 94 行 使用 此外当前 main 合并冲突位于 packaged chat assets/index/retention;应合并源码后重建并跑 packaged acceptance smoke,而不是手选旧 bundle。本次没有重跑浏览器或完整产品 CI,也没有执行合并。 English integration note: reuse this TypeScript acceptance authority for managed-team work; keep child checks separate from aggregate acceptance and add scoped work derivation through the existing owner. File/SQLite composition and all three authority backends passed the focused checks. Fix the reference example's 29-second timeout to fit the 25-second contract budget. |
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
评审 head:0435a711407cb08576ef88c5b0621e2138f15baf;基线:71cf1649e7691693d872c8e077933222138b86fe。本次在原 fork 分支合并最新 main,重建 packaged chat 资源,并修复操作文档中 29 秒超时与 25 秒合同预算不一致的问题,增加文档 JSON 经过实际 TS normalizer 的回归检查。
整体 PR 解决的是:已启用治理的任务,不能仅凭 Agent 自己声称完成或选择自己的检查就关闭,必须满足所有者确认的验收条件和当前工作依据。这是 #4574 R4 的有用增量;完整共享意图修订、动态任务派生授权和多层级自主协作仍未交付。验收绑定不能从 Agent 产物中自动推断,因而显式 owner 配置具有必要性。
改动思路
复用现有 AuthorityStore、CAS、CoordinationCommandReceipt、Todo 生命周期及验证执行器。TS 持有合同版本、工作语义绑定、适用性、准入和提交;Python 解析本地环境并执行 owner 存储的 argv,不接受公共 CLI 提交的 pass/fail。Dashboard 只消费脱敏投影。
正常路径是 inspect → owner configure → 当前任务/claim/lease 准入 → 执行绑定条件 → 在同一 provider basis 校验并提交 Todo 完成 → 独立读回。缺失产物、陈旧任务绑定、错误 actor、伪造/不完整回执或验证期间的并发提交都会阻止相应操作。历史 verify 通过不能替代新一次完成验证;Todo 完成和整个 Goal 验收保持区别。
具体改动
关键代码讲解
acceptance_authority.ts::configureGoalAcceptance(第 87 行)验证 owner 调用和 provider revision,将条件绑定到现有任务的语义摘要,通过既有 CAS/journal 更新合同;配置本身不提升 provider。acceptance_contract.ts::acceptanceCompletionRequirements(第 302 行)给完成事务提供当前任务范围内的条件。绑定跟随已确认工作,不会因被约束一方修改 task_class/status 就消失。todo_terminal_lifecycle.ts::executeCoordinationTodoTerminalLifecycle(第 706 行)约束到done的迁移;完成前规划真实检查,并将结果与 source binding 一起纳入提交。supersede 不能绕过条件。已提交 terminal 操作的 replay 读取历史结果,包括 no-change 结果,不再次授予执行权。GoalAcceptanceContractSection(第 5 行)展示合同/验证各自的版本、任务关联及结果,默认折叠且没有写入控件;刷新失败时标明保留快照并禁止把它导出为当前结果。
本次修复将 reference 示例超时改为 5 秒,并说明单项 1–25 秒、全部条件总和不超过 25 秒。没有放宽运行时预算。冲突资源由合并后源码构建,不手工选择旧 bundle。
对主干的风险
主要风险是终态旁路、旧依据被误用,以及可选功能影响未启用的调用者。验证覆盖:
- Python acceptance CLI/runtime/rendering:36 passed,包含真实 File/SQLite 与实际 argv 执行。
- TS authority/runtime:53 passed,零 skipped,覆盖 File、SQLite 和新建的隔离 PostgreSQL;TS 类型检查通过。
- Dashboard build、delivery/export 检查,以及 development / packaged 浏览器检查通过:中英文、窄屏、键盘、版本刷新、错误快照和只读导出。
- 同一默认关闭 fixture 在基线和 head 上,claim/lease/edit/clear/complete/replay/独立读回的完整规范化输出逐字相同;Python quota/summary/observation/Markdown 也逐字相同。只替换临时目录,保留诊断、游标、provider revision 和回执。两份输出摘要分别为
6bcb9d5fda6a40eede3e884b48219365732beb64d4f4e41f41466b26e825a35d和a8d5b25a147a161dfe13f8f7324f3c5fa3d56f8f6ce44b0a4410a103360e1652。 - 独立临时副本故意让
acceptanceCompletionRequirements返回 null 后,真实 CLI 缺产物完成测试按预期失败(错误地返回 0、changed:true);未修改 head 的测试通过,证明 oracle 对绕过实际完成链路敏感。 - 复用 #4688 已有合成产物副本,在 File/SQLite 上检查子任务先于总报告完成、过期依赖拒绝、验证后改产物拒绝、工作绑定过期及五个 Todo 完成而 Goal 保持 active。没有重新调用模型,也不宣称自主 launcher 已融合。
canary premerge --from-git-diff:19 项选中检查全部通过,直接检查通过,零失败、零警告、零 manual hold;包含 public boundary scan。- GitHub CI:本地验收完成后,仓库所有者明确要求跳过剩余 CI、直接合并;已停止轮询。远端 CI 尚未完成,不把它记为通过。本次采用显式不等待 CI 的评审/合并策略,保留全部本地验证要求。
语义与 CI 对齐
新合同扩展既有 Goal/Todo 词汇,不建立管家专属权威或 Python 决策镜像。未启用时不增加 Todo 必填字段或自动 Agent 指令;启用后的 held/stale 是强制门禁,文档没有称其为可忽略建议。配置、公开读回与 Goal 完成授权分开,规则保持领域中立。实际新增/重建内容按 runtime、docs、tests 和 generated 分类,未包含私有日志、凭据或本机路径。
我的整体评价
没有阻塞性发现,建议批准并按本次明确授权合并。 49 个文件、+3235/-47 的整体范围包含完整配置、执行、提交和用户读回,已有 owner/runner 的复用使其成为可独立使用、验证和停用的阶段交付。后续重构检查确认,不应另造 acceptance ledger;动态拆分授权应继续进入现有 TS work-graph,而不是让 coordinator 每次以 owner 身份重配合同。
边界:可信本地调用角色不是远端身份认证;声明的文件 pin 不证明传递依赖;配置检查全部通过不等于自动批准或完成 Goal。Lark 编辑、完整意图 amendment 和动态派生仍属后续合同,本次没有推广 provider、修改活跃 Goal 或扩大这些权限。
English verdict: APPROVE - 0435a711407cb08576ef88c5b0621e2138f15baf. Reuses the TypeScript Goal/Todo authority for owner-bound acceptance and atomic completion, with default-off parity. Reference timeout repaired and packaged assets rebuilt. 36 Python checks, 53 real-backend TS checks, source/packaged browser validation, mutation sensitivity, artifact composition and risk canaries passed; remaining remote CI explicitly waived by the repository owner and not claimed green. No blocking findings; remote authentication and autonomous work derivation remain outside scope.
|
Owner-authorized merge exception for exact head The repository owner explicitly requested direct merge after fork repair and then explicitly instructed skipping remaining CI. The exact-head approval is published above; all named local validation passed, and no unresolved review threads remain. Remote CI is not claimed green. The read-only readiness gate reports |
Goal / source
Continues the Goal-drift governance thread on this repo's three decision
points. This slice delivers the third — a completion claim must be backed by
something other than the author's own status field — as an opt-in, per-Goal
acceptance basis the Goal owner configures.
Gap this closes
The typed authority already owned an acceptance contract, and dashboard source
already had a panel for it, but neither reached a real entry point:
goal_acceptance_validation_effects, and thehost executed only the single declared completion validator, so a completion
plan named criteria that never ran;
goal_acceptance_contract, and the shippedchat bundle predated the panel, so the owner's basis reached no reader;
terminal_canonical_todo_if_promotedhad no test coverage at all, so ahost that returned the plan unexecuted would have passed;
.venv/bin/pythonthat exists neither in CI nor in a plain checkout.Observable result
loopx goal-acceptance inspect|configure|verify|disableagainst an exactprovider revision, and bound work that cannot reach
done: trueuntil itsconfigured criteria actually run:
Configuration, binding and command are identical across those two attempts;
only the artifact differs, so only real execution separates them.
The guard follows that transition, not the verb. Review of this PR found
three ways to close bound work with its criteria never executed, all fixed in
f94ea5f7c:todo supersedereaches the samedone: truewrite ascomplete, andacceptanceAppliesderived applicability fromtask_classand
status— fields the guarded Agent may rewrite. Applicability now followsthe owner-confirmed binding, and the guard sits on the terminal transition, so
a future terminal command inherits it instead of bypassing it.
todo supersedeon bound workgoal_acceptance_validation_required--task-class blockerthen completegoal_acceptance_stale--status deferredthen completegoal_acceptance_staleEach row fails against the code before that commit and passes after.
todo complete --dry-runnow reportsgoal_acceptance_pendingwith thecriterion ids the real call must run, instead of previewing an unconditional
close the real call gates. It names the obligation, never the argv.
Owning boundary
TypeScript owns revisions, bindings, admission and CAS. Python resolves the
registered Goal and executes only argv the owner stored. No caller-supplied
pass/fail crosses the boundary, and
validation_argvnever enters a publicprojection. An invocation passing
--agent-idmay inspect and verify but notconfigure or disable.
That role is a trusted local invocation role, not an authentication
boundary, exactly as
docs/reference/goal-acceptance-observations.md:113states: omitting
--agent-idis not authentication, and processes holding thesame private-runtime permissions are not isolated from owner operations. This
PR does not add an authorization boundary; it relies on the existing
local-process and private-runtime filesystem permissions.
Declared verifier files carry a sha256 pin checked before and after
execution, so editing a verifier mid-run yields
validation_basis_changedrather than reusing the configured basis. Pins cover declared assets, not
inferred dependencies; the local execution environment remains the existing
host trust boundary.
What this deliberately does not claim
acceptance_assessedstays false. Executed checks are narrower than anindependent judgment that the Goal is met, and the two are reported
separately: a contract whose task bindings are unsatisfied returns
checks_passed: truewithacceptance_ready: false, and a bound Todocompleting with passing criteria leaves the contract at
unverified.Acceptance is off by default. An absent or disabled contract leaves the
observation shape, summary shape and executability unchanged.
Acceptance evidence
canary premerge --from-git-diffpytestacceptance suitestests/control_plane/filesnode --testacceptance TSnpm run smoke:delivery-reviewnpm run smoke:goal-acceptance-contract-browserdocs-governance-smokenpm run build:chatleavesloopx/web/chatcleanMutation check. Stubbing out the host's execution branch fails the new
end-to-end test; without that branch the same code path is unobserved.
Local limitation, stated rather than hidden. This machine has Node 22.22.2
/ SQLite 3.51.2; the repo requires 22.22.3 / 3.51.3+. Every
sqliterow herefails at store construction with "SQLite authority runtime is not qualified" —
including
test_canonical_lease_acquire.pyon unmodifiedorigin/main, whichI ran to confirm it is the environment and not this branch. So the SQLite rows
are unverified locally and depend on CI's qualified runtime; the file
provider rows above all pass.
Follow-up this does not close
Decision point 1 (does this task serve a named acceptance condition?) is
confirmed by explicit owner configuration here, not inferred — the system
still does not judge relevance from a task's title, and should not. Decision
point 2 (which in-flight tasks must be re-judged when the objective changes)
remains open: the basis digest covers Todos, leases and events, not a full
objective/non-goals/acceptance intent version.
🤖 Generated with Claude Code