Repository navigation
fix(todo): recover missing execution claims in one command #8310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,14 +85,45 @@ export async function executeCanonicalTaskLeaseAcquire(store: AuthorityStore, ra | |
| const decision = decideTaskLeaseAcquire({handoff_mode: mode!, registered_agents: input.registered_agents, | ||
| ...facts, command: input}); | ||
| if (decision.outcome === "rejected" || decision.outcome === "conflict") { | ||
| const conflictLeases = decision.conflict_indexes.map(i => facts.other_leases[i]); | ||
| const ownConflicts = conflictLeases.filter(lease => lease.owner === input.owner); | ||
| const foreignConflictCount = conflictLeases.length - ownConflicts.length; | ||
| const ownConflict = ownConflicts.length === 1 ? ownConflicts[0] : null; | ||
| const ownConflictLease = ownConflict === null ? null : canonicalTaskLease( | ||
| indexCoordinationProjection(head.head, input.goal_id).leases.get(String(ownConflict.todo_id))!, | ||
| input.goal_id, String(ownConflict.todo_id)); | ||
| const ownConflictRecovery = ownConflict === null || ownConflictLease === null ? {} : { | ||
| own_conflicting_lease: { | ||
| todo_id: ownConflict.todo_id, | ||
| owner: ownConflict.owner, | ||
| write_scopes: ownConflict.write_scopes, | ||
| version: leaseVersion(ownConflictLease), | ||
| }, | ||
| recovery: { | ||
| action: "inspect_own_conflicting_lease", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Render this recovery in the default CLI format 默认 task-lease acquire 遇到自己的重叠租约,旧版渲染 write_scope_conflict 和冲突 Todo;新回执缺少旧 renderer 读取的 cli_args,两个 backend 都报 KeyError,诊断未输出。 复现:隔离 File 或 SQLite 中,A 持有另一个重叠写 scope 的 lease,对目标运行默认格式的 task-lease acquire。请按 action 显式渲染 inspect/release 的结构,保留原冲突回执,并测默认 Markdown 和 JSON。 |
||
| execution_authority_granted: false, | ||
| reason: "Inspect the active lease for your other Todo. Release it only after confirming that execution has stopped; otherwise keep it and use an isolated worktree for independent code edits.", | ||
| inspect: {command: "loopx task-lease inspect", cli_args: ["task-lease", "inspect", | ||
| "--goal-id", input.goal_id, "--todo-id", String(ownConflict.todo_id)]}, | ||
| release: {command: "loopx task-lease release", cli_args: ["task-lease", "release", | ||
| "--goal-id", input.goal_id, "--todo-id", String(ownConflict.todo_id), | ||
| "--owner", input.owner, "--idempotency-key", String(ownConflictLease.idempotency_key), | ||
| "--expected-version", String(leaseVersion(ownConflictLease))]}, | ||
| }, | ||
| ...(foreignConflictCount > 0 ? {foreign_conflict_count: foreignConflictCount} : {}), | ||
| }; | ||
| return failed(decision.code, decision.code === "handoff_mode_retired" | ||
| ? "Legacy ownership cannot grant new execution; release retained leases and review handoff-mode plan-migration/migrate before acquiring again." | ||
| : `canonical task lease acquire rejected: ${decision.code}`, { | ||
| ...(decision.code === "write_scope_conflict" ? {recommended_action: | ||
| "Coordinate shared writes with the listed holders. For isolated code edits, use your own separate Git worktree and acquire with --write-worktree; file overlaps become integration advisories. A known same-checkout collision remains rejected. Never take over a foreign lease or use code-edit mode for shared runtime state."} : {}), | ||
| handoff_mode: mode, expected_version: input.expected_version, actual_version: leaseVersion(facts.current), | ||
| ...(facts.todo ? {todo_status: facts.todo.status, claimed_by: facts.todo.claimed_by, excluded_agents: [...facts.todo.excluded_agents]} : {}), | ||
| ...(decision.conflict_indexes.length ? {conflicts: decision.conflict_indexes.map(i => facts.other_leases[i])} : {})}); | ||
| ...ownConflictRecovery, | ||
| // Own conflicts get a bounded inspect/release route above. Foreign | ||
| // leases remain immutable and visible only when no own recovery can | ||
| // resolve the caller's collision. | ||
| ...(conflictLeases.length && ownConflict === null ? {conflicts: conflictLeases} : {})}); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Preserve other holders in a mixed write conflict 目标同时与自己和他人的租约重叠时,旧 JSON 列出两个持有者;当前只留 foreign_conflict_count=1,conflicts 消失。按建议释放自己的租约后,同一目标仍被另一位持有者阻挡。 复现:A 持有 src/owned/,B 持有 src/peer/,A 申请覆盖 src/** 的目标。请保留所有仍阻挡的冲突明细;只有自己的一条 lease 时也应保留既有消费者兼容。 |
||
| } | ||
| const objective = decision.outcome === "apply" ? goalObjectiveWorkGuard(head.head, input.owner) : null; | ||
| if (objective !== null) return failed(String(objective.reason_code), String(objective.reason), {goal_objective_guard: objective}); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Make the committed same-Turn completion recoverable
同 Turn 的 --recover-execution 首次完成并提交。模拟丢失响应后逐字重试,新版返回 coordination_operation_identity_mismatch 且没有恢复指引;拿到第一次内部 claim ID 再显式重试,才能读回已提交的原完成。File 与 SQLite 相同,未发生第二次写入或扣额。
复现:让 File 或 SQLite 的 hard-lease Todo 已准入,带同一 --turn-instance-id 和 --evidence 运行两次 todo complete --recover-execution,模拟第一次响应丢失。请用原 Turn 与已提交的 claim 找到原完成回执,或返回可执行的原证据读回;不得重新领取。