From 7e70952f71e8fc32458db6ccff54616295424f1b Mon Sep 17 00:00:00 2001
From: xiaobaifly7 <196511160+xiaobaifly7@users.noreply.github.com>
Date: Wed, 11 Nov 2026 15:25:38 +0800
Subject: [PATCH] docs(acceptance): show owner scope reconciliation before
supersession
Signed-off-by: xiaobaifly7 <196511160+xiaobaifly7@users.noreply.github.com>
---
.../reference/goal-acceptance-observations.md | 130 ++++++++++++++++++
loopx/cli_commands/goal_acceptance.py | 13 +-
.../references/outcome-refinement.md | 8 ++
.../test_owner_check_scope_example.py | 126 +++++++++++++++++
4 files changed, 276 insertions(+), 1 deletion(-)
create mode 100644 tests/control_plane/test_owner_check_scope_example.py
diff --git a/docs/reference/goal-acceptance-observations.md b/docs/reference/goal-acceptance-observations.md
index 32c5459537..c33930b6bd 100644
--- a/docs/reference/goal-acceptance-observations.md
+++ b/docs/reference/goal-acceptance-observations.md
@@ -569,6 +569,133 @@ Readback validation: Dashboard `npm run smoke:delivery-review`,
After the integrated Dashboard build, `npm run smoke:goal-acceptance-contract-packaged`
runs the same contract browser check against the shipped assets.
+
+
+### Reconcile obsolete work before supersession / 替代旧任务前协调检查范围
+
+Use this owner path when an authorized correction replaces a Todo's behavior,
+while the original objective and unaffected acceptance remain valid. For example,
+`todo_parse` is already accepted, `todo_transform_old` is obsolete,
+`todo_transform_current` is its existing replacement, and `todo_final` still checks
+the delivery. Inspect acceptance and Todos on the same registry/runtime route.
+Keep the original owner document and correction source with the operation receipt:
+public `inspect` deliberately omits executable argv, so it cannot reconstruct the
+complete configuration.
+
+With `all_advancement`, superseding bound work can return
+`goal_acceptance_validation_required`. Removing only its binding still leaves it
+in scope and can return `goal_acceptance_unbound`. Neither means that an Agent may
+disable checks. The authorized local owner must review the **whole replacement
+document**, including every retained criterion, command/pin/oracle and binding.
+Explicitly narrow coverage to the current accepted work; future Todos will no
+longer be included automatically and need owner review if they belong in scope.
+
+For this standalone contract, save the following as `acceptance-current.json`.
+Replace the illustrative IDs and bounded artifact checks with the real reviewed
+ones, including the available Python executable. Preserve any existing validator
+pins/oracles. The checks below assert actual output, not unconditional success.
+Do not add `outcome_id` to this expanded document: the compact outcome authoring
+form resolves its work links and supports only omitted scope or explicit `goal`
+scope; it cannot express this explicit work subset.
+
+```json
+{
+ "scope": {"kind": "selected_work", "todo_ids": ["todo_parse", "todo_transform_current", "todo_final"]},
+ "objective": "Deliver validated records",
+ "non_goals": ["Publish the records"],
+ "criteria": [
+ {
+ "id": "parse",
+ "description": "The accepted parsed records remain intact.",
+ "validation_argv": ["python3", "-c", "from pathlib import Path; assert Path('parsed.txt').read_text() == 'alpha\\nbeta\\n'"],
+ "validation_timeout_seconds": 5
+ },
+ {
+ "id": "transform-current",
+ "description": "The corrected transform preserves the requested order.",
+ "validation_argv": ["python3", "-c", "from pathlib import Path; assert Path('transformed.txt').read_text() == 'ALPHA\\nBETA\\n'"],
+ "validation_timeout_seconds": 5
+ },
+ {
+ "id": "final",
+ "description": "The final delivery contains the corrected records.",
+ "validation_argv": ["python3", "-c", "from pathlib import Path; assert Path('delivery.txt').read_text() == 'ALPHA\\nBETA\\n'"],
+ "validation_timeout_seconds": 5
+ }
+ ],
+ "bindings": [
+ {"todo_id": "todo_parse", "criterion_ids": ["parse"]},
+ {"todo_id": "todo_transform_current", "criterion_ids": ["transform-current"]},
+ {"todo_id": "todo_final", "criterion_ids": ["final"]}
+ ]
+}
+```
+
+Keep explicit `--registry` / `--runtime-root` arguments on **every** command when
+using a nondefault route. Configure without `--agent-id` only under existing local
+owner authorization; omission selects a trusted local role, not authentication.
+Use the exact current `provider_revision` from `inspect`, never the acceptance
+contract's numeric `revision`. Preview and apply the identical reviewed document
+with the same operation ID and provider revision:
+
+```bash
+loopx --format json goal-acceptance inspect --goal-id example-goal
+loopx --format json goal-acceptance configure --goal-id example-goal \
+ --document acceptance-current.json --expected-provider-revision '' \
+ --operation-id reconcile-transform-v2
+loopx --format json goal-acceptance configure --goal-id example-goal \
+ --document acceptance-current.json --expected-provider-revision '' \
+ --operation-id reconcile-transform-v2 --execute
+```
+
+Read the returned `operation_id`, `provider_revision`, scope, criteria and `tasks`
+associations (`todo_id`, `criterion_ids`, `state`);
+the accepted parser and final check must remain, and the obsolete Todo must be
+absent from both scope and bindings. Retain the reviewed document, correction
+source and receipt together. After a lost response retry that exact operation
+with its original revision. After a confirmed revision conflict, inspect and
+review current state before creating a new operation; do not silently rebase.
+
+Only then supersede the obsolete Todo. For canonical `hard_lease` work already
+claimed by `example-agent`, acquire its ordinary lease (or inspect its existing
+lease) and use the exact returned `lease.idempotency_key` and `lease.version`:
+
+```bash
+loopx --format json task-lease acquire --goal-id example-goal \
+ --todo-id todo_transform_old --owner example-agent --idempotency-key retire-transform-v1
+loopx --format json todo supersede --goal-id example-goal \
+ --todo-id todo_transform_old --agent-id example-agent \
+ --reason 'message:owner-correction: use the corrected transform' \
+ --successor-todo-id todo_transform_current \
+ --task-lease-idempotency-key '' \
+ --task-lease-expected-version ''
+loopx --format json todo list --goal-id example-goal
+loopx --format json goal-acceptance inspect --goal-id example-goal
+```
+
+This uses the raw valid lease pair; it does not require managed supersede claims.
+Do not reuse a released/expired lease or substitute a provider revision for its
+version. Read back the original Todo's successor link and the retained work/checks.
+Supersession records obsolete history; it does not verify the successor, rerun the
+accepted parser, or complete the Goal. Current/final work still needs its normal
+fresh checks, claims, leases and continuation handling.
+
+**保守边界:** 仅在已有明确授权且原目标与未受影响的验收仍有效时,由本地 owner 审阅
+完整替换文档。仅删 binding 不能让旧任务退出 `all_advancement`;改为上述
+`selected_work` 保留已验收 parser、当前替代任务和最终检查,同时明确以后新增任务
+不再自动纳入。每条命令沿用同一 registry/runtime,配置用最新 `provider_revision`,
+预览与执行复用同一 operation ID;丢响应原样重试,确认冲突后重新审阅并新建操作。
+保留原配置、用户更正来源、拟用文档和回执,再用实际 lease key/version 替代旧任务。
+配置与 supersede 都不证明新产物通过;后续仍须执行真实检查。
+
+This changes executable check coverage only. It does **not** retire or replace
+persisted additive `outcome_requirements`, erase their sources/work history, or
+resolve a contradictory immutable requirement. Report that conflict as unresolved
+unless a supported owner amendment path actually reconciles it; do not claim this
+recipe aligns it. 中文:本流程只协调命令检查覆盖,不撤销、覆盖或伪装满足累计的不可变
+成果要求;若已持久化要求互相冲突,在受支持的 owner 修订实际解决前,必须明确保留
+未解决状态。
+
## 中文
打开本机 Goal,选择 **概览 → 交付与依据 → 验收观察**。
@@ -612,6 +739,9 @@ status 同时在独立的 `run_history.goals[].artifact_lifecycle` 和 Markdown
### 默认验收中的命令检查
+旧任务被新任务替代时,使用上方[检查范围协调示例](#owner-check-scope-reconciliation):
+完整保留仍有效的检查与工作绑定,由 owner 明确排除旧任务后,再按真实租约执行 supersede。
+
已注册且**已提升到 canonical authority** 的 Goal 可为已有成果要求配置命令检查。
命令不会自动提升 provider,canonical 读取失败也不回退到旧 Markdown。
优先引用 `outcome_id` 和要求 ID,由类型化 owner 解析目标、条件描述和关联任务;
diff --git a/loopx/cli_commands/goal_acceptance.py b/loopx/cli_commands/goal_acceptance.py
index eebbe63923..3237e0c7de 100644
--- a/loopx/cli_commands/goal_acceptance.py
+++ b/loopx/cli_commands/goal_acceptance.py
@@ -45,7 +45,18 @@ def register_goal_acceptance_command(subparsers: Any, add_format: Any) -> None:
Own work review/adoption needs no human reply or unanimous peer vote.
All steps require --goal-id GOAL. Proposal preparation alone requires neither
owner input nor adoption; only an applied objective revision needs adoption.
-Authority, budgets, stops and existing gated actions remain binding.''',
+Authority, budgets, stops and existing gated actions remain binding.
+
+Owner check-scope reconciliation (configure without --agent-id):
+ --document is the full replacement: {"scope":{"kind":"selected_work","todo_ids":["todo_current"]},
+ "objective":"Retained objective","non_goals":[],"criteria":[{"id":"result","description":"Real check", "validation_argv":["python3","check.py"]}],
+ "bindings":[{"todo_id":"todo_current","criterion_ids":["result"]}]}
+ Retain accepted/current/final work and checks; exclude obsolete work from scope
+ AND bindings. Removing a binding alone leaves all_advancement work held.
+ Use inspect's provider_revision and one stable --operation-id for preview/apply
+ and unchanged retries. Configure changes checks, not additive outcome requirements.
+ Full example, owner authority and lease-fenced supersession:
+ docs/reference/goal-acceptance-observations.md#owner-check-scope-reconciliation''',
)
add_format(parser)
parser.add_argument("action", type=lambda value: "clear-checks" if value == "disable" else value, choices=("inspect", "prepare", "configure", "verify", "clear-checks", "refine", "prepare-objective", "inspect-proposal", "apply-proposal", "revise-objective", "adopt-objective"))
diff --git a/skills/loopx-project/references/outcome-refinement.md b/skills/loopx-project/references/outcome-refinement.md
index 0b72195967..2c0153b739 100644
--- a/skills/loopx-project/references/outcome-refinement.md
+++ b/skills/loopx-project/references/outcome-refinement.md
@@ -32,6 +32,14 @@ and mark both met, or silently edit an authoritative file. Ask only when a
consequential meaning or authority is genuinely missing, not merely because the
user gave a correction.
+For obsolete work covered by executable checks, follow the
+[owner check-scope reconciliation example](../../../docs/reference/goal-acceptance-observations.md#owner-check-scope-reconciliation).
+It preserves accepted work and current/final checks in an explicit
+`selected_work` replacement before lease-fenced supersession. Removing a binding
+alone does not remove work from `all_advancement`. This owner configuration path
+does not supersede persisted additive outcome requirements; keep any conflicting
+requirement explicit rather than claiming the check update retired it.
+
## Current operating objective
When recorded explicit user direction makes the Goal objective obsolete, prepare
diff --git a/tests/control_plane/test_owner_check_scope_example.py b/tests/control_plane/test_owner_check_scope_example.py
new file mode 100644
index 0000000000..0ea30a859d
--- /dev/null
+++ b/tests/control_plane/test_owner_check_scope_example.py
@@ -0,0 +1,126 @@
+"""Execute the documented owner reconciliation against disposable canonical stores."""
+from __future__ import annotations
+
+from copy import deepcopy
+import json
+from pathlib import Path
+import sys
+
+import pytest
+from canonical_authority_fixture import initialize_canonical_authority, isolate_sqlite_runtime
+
+from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection
+from loopx.control_plane.testing.canary_harness import run_json_cli_result, write_fixture_registry
+
+
+@pytest.mark.parametrize("provider", ["file", "sqlite"])
+def test_documented_owner_scope_reconciliation(tmp_path, monkeypatch, provider):
+ if provider == "sqlite":
+ isolate_sqlite_runtime(tmp_path, monkeypatch)
+ project = tmp_path / "project"
+ project.mkdir()
+ (project / "parsed.txt").write_text("alpha\nbeta\n")
+ state = project / "state.md"
+ state.write_text("---\nstatus: active\n---\n# Fixture\n## Agent Todo\n")
+ runtime, registry = tmp_path / "runtime", tmp_path / "registry.json"
+ write_fixture_registry(project=project, runtime_root=runtime, registry_path=registry,
+ goal_id="example-goal", domain="acceptance", adapter_kind="generic_project_goal_v0",
+ state_file=str(state), registered_agents=["example-agent"])
+ todos = [{"schema_version": "todo_item_v0", "todo_id": todo_id, "role": "agent",
+ "text": text, "status": "done" if index == 1 else "open", "done": index == 1,
+ "task_class": "advancement_task", "action_kind": "implement", "index": index,
+ "source_section": "Agent Todo", "archive_state": "active", "claimed_by": "example-agent"}
+ for index, (todo_id, text) in enumerate([
+ ("todo_parse", "Parse the records"),
+ ("todo_transform_old", "Transform using the obsolete ordering"),
+ ("todo_transform_current", "Transform preserving the requested order"),
+ ("todo_final", "Check the final delivery"),
+ ], 1)]
+ initialize_canonical_authority(runtime, "example-goal",
+ build_todo_runtime_shadow_projection(goal_id="example-goal", todos=todos, handoff_mode="hard_lease"),
+ state_path=state, provider=provider)
+
+ def run(*args, expected=0):
+ code, result = run_json_cli_result(*args, registry_path=registry, runtime_root=runtime)
+ assert code == expected, result
+ return result
+
+ def acceptance(action, *args, expected=0):
+ return run("goal-acceptance", action, "--goal-id", "example-goal", *args, expected=expected)
+
+ refinement = project / "outcome.json"
+ refinement.write_text(json.dumps({"outcome_id": "records", "owner_agent_id": "example-agent",
+ "objective": "Deliver validated records", "todo_ids": ["todo_parse"],
+ "requirements": [{"id": "parse", "description": "The accepted parsed records remain intact."}],
+ "source": {"ref": "message:initial", "summary": "The owner requested validated records"}}))
+ acceptance("refine", "--agent-id", "example-agent", "--document", str(refinement),
+ "--expected-provider-revision", acceptance("inspect")["provider_revision"],
+ "--operation-id", "declare-records", "--execute")
+ retained = acceptance("inspect")["goal_acceptance_contract"]["outcome_requirements"]["outcomes"]
+
+ docs = Path(__file__).resolve().parents[2] / "docs/reference/goal-acceptance-observations.md"
+ section = docs.read_text(encoding="utf-8").split('', 1)[1]
+ document = json.loads(section.split("```json\n", 1)[1].split("```", 1)[0])
+ # Use this test process's interpreter; all validator code is the documented code.
+ for criterion in document["criteria"]:
+ criterion["validation_argv"][0] = sys.executable
+ path = project / "acceptance-current.json"
+
+ def configure(value, operation):
+ path.write_text(json.dumps(value))
+ return ("--document", str(path), "--expected-provider-revision",
+ acceptance("inspect")["provider_revision"], "--operation-id", operation)
+
+ original = deepcopy(document)
+ original["scope"] = {"kind": "all_advancement"}
+ original["bindings"][1]["todo_id"] = "todo_transform_old"
+ original["criteria"][1].update(id="transform-old", description="The obsolete transform reverses the records.",
+ validation_argv=[sys.executable, "-c",
+ "from pathlib import Path; assert Path('transformed.txt').read_text() == 'BETA\\nALPHA\\n'"])
+ original["bindings"][1]["criterion_ids"] = ["transform-old"]
+ acceptance("configure", *configure(original, "configure-original"), "--execute")
+ lease = run("task-lease", "acquire", "--goal-id", "example-goal", "--todo-id", "todo_transform_old",
+ "--owner", "example-agent", "--idempotency-key", "retire-transform-v1")["lease"]
+ supersede = ("todo", "supersede", "--goal-id", "example-goal", "--todo-id", "todo_transform_old",
+ "--agent-id", "example-agent", "--reason", "message:owner-correction: use the corrected transform",
+ "--successor-todo-id", "todo_transform_current", "--task-lease-idempotency-key", lease["idempotency_key"],
+ "--task-lease-expected-version", str(lease["version"]))
+ assert run(*supersede, expected=1)["reason_code"] == "goal_acceptance_validation_required"
+
+ rebind_only = {**document, "scope": {"kind": "all_advancement"}}
+ acceptance("configure", *configure(rebind_only, "rebind-only"), "--execute")
+ assert run(*supersede, expected=1)["reason_code"] == "goal_acceptance_unbound"
+
+ args = configure(document, "reconcile-transform-v2")
+ assert acceptance("configure", *args)["status"] == "planned"
+ applied = acceptance("configure", *args, "--execute")
+ assert applied["status"] == "applied" and applied["operation_id"] == "reconcile-transform-v2"
+ assert applied["source_authority"] == f"{provider}_v0"
+ replay = acceptance("configure", *args, "--execute")
+ assert replay["status"] == "replayed" and replay["provider_revision"] == applied["provider_revision"]
+ assert replay["operation_id"] == applied["operation_id"]
+ contract = applied["goal_acceptance_contract"]
+ assert contract["scope"]["kind"] == "selected_work"
+ assert set(contract["scope"]["todo_ids"]) == set(document["scope"]["todo_ids"])
+ assert {item["todo_id"]: item["criterion_ids"] for item in contract["tasks"]} == {
+ binding["todo_id"]: binding["criterion_ids"] for binding in document["bindings"]}
+ assert all(item["state"] == "ready" for item in contract["tasks"])
+ assert {item["id"] for item in contract["criteria"]} == {"parse", "transform-current", "final"}
+ assert contract["outcome_requirements"]["outcomes"] == retained
+
+ retired = run(*supersede)
+ assert retired["superseded"] is True and retired["source_authority"] == f"{provider}_v0"
+ readback = run("todo", "list", "--goal-id", "example-goal")
+ records = {todo["todo_id"]: todo for todo in readback["todos"]}
+ assert records["todo_transform_old"]["superseded_by"] == "todo_transform_current"
+ assert records["todo_parse"]["done"] is True
+ assert records["todo_transform_current"]["done"] is False
+ assert records["todo_final"]["done"] is False
+ assert acceptance("inspect")["goal_acceptance_contract"]["outcome_requirements"]["outcomes"] == retained
+ assert acceptance("verify", "--agent-id", "example-agent", "--execute", expected=1)["checks_passed"] is False
+ for filename, content in [("parsed.txt", "alpha\nbeta\n"),
+ ("transformed.txt", "ALPHA\nBETA\n"), ("delivery.txt", "ALPHA\nBETA\n")]:
+ (project / filename).write_text(content)
+ verified = acceptance("verify", "--agent-id", "example-agent", "--execute")
+ assert verified["checks_passed"] is True
+ assert acceptance("inspect")["goal_acceptance_contract"]["status"] == "accepted"