From af008d216edce58278d3392d01d2d112c7285a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E7=91=9E=E5=8D=9A?= <912906590@qq.com> Date: Mon, 17 Aug 2026 22:59:49 +0800 Subject: [PATCH 1/2] docs(todos): state the user-role update validation gate in the contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address the two non-blocking P2 notes from the #3291 review: - project-agent-todo-contract.md now says explicitly that a user-role `todo update --status done` on a todo with a declared validation command runs the same completion validation gate as `todo complete` and fails closed with a typed receipt, so the earlier non-terminal-update framing cannot be misread as a gate bypass. - the pre-lock gate in update_goal_todo drops the dead try/except around normalize_todo_status: it returns None for an invalid status rather than raising, so the except branch was unreachable and the None comparison already skips the gate unchanged. Signed-off-by: 牛瑞博 <912906590@qq.com> --- docs/project-agent-todo-contract.md | 7 +++++++ loopx/todos.py | 9 ++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/project-agent-todo-contract.md b/docs/project-agent-todo-contract.md index d78487d7c..4dcdb0059 100644 --- a/docs/project-agent-todo-contract.md +++ b/docs/project-agent-todo-contract.md @@ -580,6 +580,13 @@ be bypassed. An evidence-backed peer transition. This closeout records `self_merged=false` and does not create a successor review todo for observation-only work. +User-role todos may still write `done` through `todo update --status done`; +when the todo declares `validation_command` (or `validation_command_json`), +that update runs the same completion validation gate as `todo complete` and +fails closed with a typed `validation_blocked_completion` receipt instead of +committing `done`. Todos without a declared command keep the unchanged fast +path. + Use `--resume-when` when deferring a successor that should wake up after a machine-readable condition instead of living only in prose: diff --git a/loopx/todos.py b/loopx/todos.py index c7c0beb47..a4f55aa36 100644 --- a/loopx/todos.py +++ b/loopx/todos.py @@ -1262,11 +1262,10 @@ def update_goal_todo( # validation command). Returns a typed failure payload (ok=False) when # validation blocks; otherwise None. if status: - try: - update_completes_todo = normalize_todo_status(status) == TODO_STATUS_DONE - except ValueError: - update_completes_todo = False # invalid status surfaces in-lock, unchanged - if update_completes_todo: + # normalize_todo_status returns None (never raises) for an invalid + # status, which simply skips this gate; the in-lock write path then + # surfaces the same invalid-status error as before. + if normalize_todo_status(status) == TODO_STATUS_DONE: update_block_match = find_todo_block( resolved_state_file.read_text(encoding="utf-8").splitlines(), todo_id=todo_id, From b22c27062df5690f97959fd1d39262b567233d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E7=91=9E=E5=8D=9A?= <912906590@qq.com> Date: Mon, 17 Aug 2026 23:07:46 +0800 Subject: [PATCH 2/2] docs(todos): use the canonical validation_command_argv field name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the #3293 review note: the contract sentence now references the stored field `validation_command_argv` (declared via the `--validation-command-json` flag) instead of the flag-derived alias `validation_command_json`, matching the todo schema exactly. Signed-off-by: 牛瑞博 <912906590@qq.com> --- docs/project-agent-todo-contract.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/project-agent-todo-contract.md b/docs/project-agent-todo-contract.md index 4dcdb0059..45868c6be 100644 --- a/docs/project-agent-todo-contract.md +++ b/docs/project-agent-todo-contract.md @@ -581,7 +581,8 @@ transition. This closeout records `self_merged=false` and does not create a successor review todo for observation-only work. User-role todos may still write `done` through `todo update --status done`; -when the todo declares `validation_command` (or `validation_command_json`), +when the todo declares `validation_command` (or the `validation_command_argv` +declared via `--validation-command-json`), that update runs the same completion validation gate as `todo complete` and fails closed with a typed `validation_blocked_completion` receipt instead of committing `done`. Todos without a declared command keep the unchanged fast