From bec92f6f0bea81a5d9b896dd5ffc4bb4cb27272f Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Thu, 29 Oct 2026 15:53:05 +0800 Subject: [PATCH 1/5] test(settlement): characterize the host adapter's partial-step boundary C4.3's obligation comparison recorded that the host Todo adapter keeps no journal of its own, and left the adapter's own behavior when a step succeeds and a later step fails uncharacterized, together with the place where the recovery boundary between the adapter and the CLI receipts sits. A slice that moved this path would otherwise have had to assume both. Four cases now pin that behavior with the real TypeScript owner and the real CLI: - an unreadable step output stops the plan, and the owner classifies the stop (blocked, failed_stage guard, malformed-JSON reason); - a raising step ends the call before the owner is asked to finalize, so the transaction is never classified, and a retry re-enters the plan at its first step because the adapter keeps no record of the step that already succeeded; - a raise after a durable commit leaves the committed receipt in the CLI's canonical store: the readback reports settled, and the retry replays that receipt, spends no second slot and reports the same settlement identity; - a raise before the lifecycle completion commits no settlement at all. The adapter makes no classification decision in either case, and the recovery boundary is the CLI receipt rather than adapter state. The existing lost response case stays where it was; this adds the raised transport surface. The bilingual ledger entry records the characterization and what it does not establish. No runtime, schema, wire, default or recovery behavior changes. Validation: the four new cases pass; the settlement, MCP settlement and host vision recovery modules pass (29 tests); docs governance smoke passes; the RFC bilingual check passes; canary premerge --from-git-diff is green; the public boundary scan is clean. Signed-off-by: xiaoan17 --- ...3-adapter-partial-step-characterization.md | 72 ++++ ...ter-partial-step-characterization.zh-CN.md | 64 ++++ ...t_host_adapter_settlement_partial_steps.py | 333 ++++++++++++++++++ 3 files changed, 469 insertions(+) create mode 100644 docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md create mode 100644 docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md create mode 100644 tests/control_plane/test_host_adapter_settlement_partial_steps.py diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md new file mode 100644 index 0000000000..33b1cbdee9 --- /dev/null +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md @@ -0,0 +1,72 @@ +# C4.3 adapter partial-step characterization + +- Parent: [controller/executor RFC, C4.3](2026-10-27-refactor-handoff.md#c43--settlement-adapters-over-existing-primitives). +- Baseline: `2a2323756`, 2026-10-29. The settlement owners are unchanged at `fb331f45`. +- Status: characterization only. No runtime, schema, wire or default change. +- Follows: [C4.3 settlement obligation comparison](2026-10-29-c4-3-settlement-obligation-comparison.md). +- [Chinese mirror](2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md). + +## Why this entry exists + +The comparison entry recorded the adapter's lack of a local journal as a fact +about the adapter, and left the adapter's own behavior when a step succeeds and +a later step fails uncharacterized, together with the place where the recovery +boundary between the adapter and the CLI receipts sits. A consolidation slice +that moved this path would otherwise have had to assume both. + +This entry pins that behavior with executable cases, so a later recovery change +starts from measured behavior rather than from the comparison's wording. + +## What is characterized now + +| Surface | Adapter behavior | Owner behavior | +| --- | --- | --- | +| A step returns output the adapter cannot read | `_provider_condition_matches` treats a non-JSON output as a condition that did not match, so the plan stops after that step | The finalize phase still runs and the owner classifies the stop: `blocked`, `failed_stage: guard`, reason `quota guard returned malformed JSON` | +| A step raises | The exception propagates out of the adapter. The finalize phase never runs, so the transaction is never classified. The adapter keeps no record of the step that already succeeded | Nothing is classified, because the owner is never asked | +| A retry after a raise | The adapter re-enters the plan at its first step and never resumes mid-plan | The identity is re-derived from the same intent, so the same settlement identity is used | +| A step raises after a durable commit | The adapter leaves nothing behind; the CLI's canonical receipt store holds the committed receipt | The readback reports `settled`; the retry replays that receipt (`idempotent_replay`), spends no second slot and reports the same settlement identity | +| A step raises before the lifecycle completion | Nothing is spent and the Todo stays open | No settlement receipt exists to replay | + +Two findings follow from these cases. + +1. **The adapter makes no classification decision.** A stopped plan is + classified by the TypeScript owner; a raised step is not classified at all, + because the owner is never asked. The adapter is transport in both cases. +2. **The recovery boundary is the CLI receipt, not adapter state.** After a + raise, the only durable evidence is what the CLI already committed. The + retry succeeds because that receipt replays, and the adapter contributes + nothing but a re-execution of the plan. + +## Evidence + +`tests/control_plane/test_host_adapter_settlement_partial_steps.py`: + +- `test_an_unreadable_step_output_stops_the_plan_and_the_owner_classifies_it` +- `test_a_raising_step_leaves_no_finalize_and_no_adapter_local_resume` +- `test_a_raising_step_after_a_durable_commit_recovers_through_the_cli_receipt` + (real CLI, real quota store) +- `test_a_raised_step_before_completion_commits_no_settlement` (real CLI) + +The existing end-to-end recovery case stays where it was: +`tests/test_goal_mode_mcp_settlement.py::test_real_mcp_completion_recovers_a_lost_mutation_response` +covers a lost response after a commit; the new case covers the same recovery +through a raised transport instead. + +## What this does not establish + +- No equivalence proof, no deletion authorization, no runtime qualification, no + benefit measurement, and no claim that either settlement path is redundant. +- The characterization covers the shipped MCP host path through + `settle_host_todo_completion`. It does not characterize other callers of the + adapter, and it does not describe the TypeScript-only Turn settlement path, + which recovers per provider step inside its own transaction. +- It adds no journal, retry policy or resume capability to the adapter. Adding + one is a new design decision with its own acceptance, not a characterization. +- The observed recovery depends on the CLI's canonical receipt store. Source + facts were read at the baseline above and may drift. + +## Next slices + +Unchanged by this entry. A deletion of the adapter-side interpreter still +needs the preconditions recorded in the comparison entry, and vocabulary +unification still needs a caller that adopts both paths. diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md new file mode 100644 index 0000000000..b9a2fbb3eb --- /dev/null +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md @@ -0,0 +1,64 @@ +# C4.3 适配器部分步骤行为刻画 + +- 上级:[controller/executor RFC,C4.3](2026-10-27-refactor-handoff.zh-CN.md#c43--settlement-adapters-over-existing-primitives)。 +- 基线:`2a2323756`,2026-10-29。结算属主在 `fb331f45` 未变。 +- 状态:只做刻画。不改变运行时、schema、wire 或默认值。 +- 上游条目:[C4.3 结算义务对照](2026-10-29-c4-3-settlement-obligation-comparison.zh-CN.md)。 +- [英文版](2026-10-29-c4-3-adapter-partial-step-characterization.md)。 + +## 为什么有这条记录 + +对照条目把"适配器没有本地 journal"记成关于适配器自身的事实,但适配器在 +"某步成功、后续步失败"时的自身行为,以及适配器与 CLI 回执之间的恢复边界落在 +哪里,当时仍没有被刻画。任何会改动这条路径的整合切片,都只能对这两点做假设。 + +本条目用可执行用例把该行为钉住,让后续的恢复改动从实测行为出发,而不是从对照 +条目的措辞出发。 + +## 现在刻画了什么 + +| 场景 | 适配器行为 | 属主行为 | +| --- | --- | --- | +| 某一步返回适配器读不懂的输出 | `_provider_condition_matches` 把非 JSON 输出当作"条件不匹配",计划在该步之后停止 | finalize 阶段仍然运行,由属主判定这次停止:`blocked`、`failed_stage: guard`、原因 `quota guard returned malformed JSON` | +| 某一步抛错 | 异常直接冒出适配器。finalize 阶段从未运行,这笔事务从未被分类。适配器不保留已成功那一步的任何记录 | 没有分类,因为属主从未被询问 | +| 抛错后的重试 | 适配器从计划第一步重新进入,绝不在中途续跑 | 身份由同一意图重新推导,因此使用同一个 settlement identity | +| 某一步在持久提交之后抛错 | 适配器什么也不留下;已提交的凭据由 CLI 的规范凭据库持有 | 读回报告 `settled`;重试重放该凭据(`idempotent_replay`),不会再扣一个槽位,并报告同一个 settlement identity | +| 某一步在生命周期完成之前抛错 | 没有花费,Todo 保持 open | 不存在可重放的结算凭据 | + +由此得到两条结论。 + +1. **适配器不做任何分类判定。** 计划停止由 TypeScript 属主分类;某一步抛错则 + 完全没有分类,因为属主从未被询问。两种情形下适配器都只是传输。 +2. **恢复边界是 CLI 凭据,不是适配器状态。** 抛错之后,唯一持久存在的证据是 + CLI 已经提交的东西。重试之所以成功,是因为那份凭据可以重放;适配器除了重跑 + 计划之外没有贡献任何东西。 + +## 证据 + +`tests/control_plane/test_host_adapter_settlement_partial_steps.py`: + +- `test_an_unreadable_step_output_stops_the_plan_and_the_owner_classifies_it` +- `test_a_raising_step_leaves_no_finalize_and_no_adapter_local_resume` +- `test_a_raising_step_after_a_durable_commit_recovers_through_the_cli_receipt` + (真实 CLI、真实配额存储) +- `test_a_raised_step_before_completion_commits_no_settlement`(真实 CLI) + +既有的端到端恢复用例仍在原处: +`tests/test_goal_mode_mcp_settlement.py::test_real_mcp_completion_recovers_a_lost_mutation_response` +覆盖的是提交之后响应丢失;新增用例覆盖同一条恢复路径在传输抛错时的形态。 + +## 本条目没有建立什么 + +- 没有等价性证明,没有删除授权,没有运行时资格,没有收益测量,也没有断言任何 + 一条结算路径是多余的。 +- 刻画覆盖的是经 `settle_host_todo_completion` 的已交付 MCP host 路径。它不覆盖 + 适配器的其他调用方,也不描述纯 TypeScript 的 Turn settlement 路径——后者在 + 自己的事务内按 provider 步骤恢复。 +- 它没有给适配器增加 journal、重试策略或续跑能力。增加这些是新的设计决策, + 有各自的验收,不是刻画。 +- 观察到的恢复依赖 CLI 的规范凭据库。源码事实基于上述基线读取,可能漂移。 + +## 后续切片 + +本条目不改变结论。删除适配器侧解释器仍需要对照条目记录的前提条件;词汇统一 +仍需要一个同时采用两条路径的调用方。 diff --git a/tests/control_plane/test_host_adapter_settlement_partial_steps.py b/tests/control_plane/test_host_adapter_settlement_partial_steps.py new file mode 100644 index 0000000000..95205aff35 --- /dev/null +++ b/tests/control_plane/test_host_adapter_settlement_partial_steps.py @@ -0,0 +1,333 @@ +"""Characterize the host settlement adapter's own partial-step behavior. + +C4.3's obligation comparison recorded that the host Todo adapter keeps no +journal of its own: the durable settlement receipts live in the CLI, and the +TypeScript owner classifies a stopped plan. What was still uncharacterized is +the adapter's own behavior when a step succeeds and a later step fails, and +where the recovery boundary between the adapter and the CLI receipts sits. + +These cases pin that behavior before any recovery change touches this path. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import pytest + +from loopx.control_plane import host_adapter_settlement +from loopx.control_plane.coordination.goal_storage_initialization import ( + initialize_goal_storage_target, +) +from loopx.control_plane.host_adapter_settlement import ( + HostTodoSettlementRequest, + settle_host_todo_completion, +) +from loopx.control_plane.quota.settlement import read_heartbeat_settlement +from loopx.goal_mode_mcp import GoalModeMCPConfig, GoalModeMCPControlPlane +from loopx.status import parse_active_state_todos +from loopx.todos import add_goal_todo + + +GOAL_ID = "adapter-partial-steps-fixture" +AGENT_ID = "claude" +TODO_ID = "todo_abc123" + + +def _request() -> HostTodoSettlementRequest: + return HostTodoSettlementRequest( + goal_id="goal", + agent_id="agent", + todo_id=TODO_ID, + runtime_profile="claude_code", + legacy_host_surface="claude_code", + scheduler_owner="agent_cli_loop", + execution_mode="interactive", + completion_args=("todo", "complete", TODO_ID), + ) + + +def _guard_payload() -> str: + """The guard shape the TypeScript owner accepts as a Todo selection.""" + return json.dumps({"ok": True, "selected_todo": {"todo_id": TODO_ID}}) + + +def test_an_unreadable_step_output_stops_the_plan_and_the_owner_classifies_it() -> None: + """The adapter stops; the TypeScript owner decides what the stop means.""" + calls: list[list[str]] = [] + + def provider(args: list[str], *, legacy_args: list[str] | None = None) -> str: + del legacy_args + calls.append(args) + # A CLI failure returns its stderr, which is not JSON at all. + return "quota should-run failed: synthetic CLI failure" + + result = json.loads(settle_host_todo_completion(_request(), run_cli=provider)) + + assert [call[:2] for call in calls] == [["quota", "should-run"]] + assert result["ok"] is False + assert result["settlement_blocked_completion"] is True + assert result["settlement"]["failed_stage"] == "guard" + assert result["settlement"]["reason"] == "quota guard returned malformed JSON" + + +def test_a_raising_step_leaves_no_finalize_and_no_adapter_local_resume( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A failed step ends the call before the owner is asked to finalize.""" + phases: list[str] = [] + runtime_result = host_adapter_settlement.effect_runtime_result + + def spy(method: str, params: dict[str, object]) -> object: + phases.append(str(params.get("phase"))) + return runtime_result(method, params) + + monkeypatch.setattr(host_adapter_settlement, "effect_runtime_result", spy) + + calls: list[list[str]] = [] + + def provider(args: list[str], *, legacy_args: list[str] | None = None) -> str: + del legacy_args + calls.append(args) + if len(calls) == 2: + raise RuntimeError("synthetic CLI step failure") + return _guard_payload() + + with pytest.raises(RuntimeError, match="synthetic CLI step failure"): + settle_host_todo_completion(_request(), run_cli=provider) + + # The finalize phase never ran, so the owner never classified the failure. + assert phases == ["prepare"] + # The failing step was attempted; the earlier step had already succeeded. + assert [call[:2] for call in calls] == [ + ["quota", "should-run"], + ["todo", "complete"], + ] + + calls.clear() + phases.clear() + with pytest.raises(RuntimeError, match="synthetic CLI step failure"): + settle_host_todo_completion(_request(), run_cli=provider) + + # A retry re-enters the plan at its first step. The adapter holds no + # record of the step that already succeeded and never resumes mid-plan. + assert phases == ["prepare"] + assert [call[:2] for call in calls] == [ + ["quota", "should-run"], + ["todo", "complete"], + ] + + +def _write_fixture(tmp_path: Path) -> tuple[Path, Path]: + project = tmp_path / "project" + project.mkdir() + state_file = project / "ACTIVE_GOAL_STATE.md" + state_file.write_text( + "\n".join( + [ + "---", + f"goal_id: {GOAL_ID}", + "status: active", + "updated_at: 2026-10-29T00:00:00+00:00", + "---", + "", + "## Agent Todo", + "", + ] + ) + + "\n", + encoding="utf-8", + ) + registry = tmp_path / "registry.global.json" + registry.write_text( + json.dumps( + { + "common_runtime_root": str(tmp_path / "runtime"), + "goals": [ + { + "id": GOAL_ID, + "creation_operation_id": f"goal-create:{GOAL_ID}", + "domain": "harness_self_improvement", + "status": "active", + "repo": str(project), + "state_file": state_file.name, + "adapter": { + "kind": "harness_self_improvement", + "status": "connected-read-only", + }, + "quota": { + "compute": 1.0, + "window_hours": 24, + "slot_minutes": 1, + }, + "coordination": { + "agent_model": "peer_v1", + "registered_agents": [AGENT_ID], + "storage_target": { + "schema_version": "loopx_new_goal_storage_target_v1", + "provider": "file", + "handoff_mode": "soft_claim", + }, + }, + } + ], + } + ), + encoding="utf-8", + ) + goal = json.loads(registry.read_text(encoding="utf-8"))["goals"][0] + initialized = initialize_goal_storage_target( + tmp_path / "runtime", + goal, + registry_path=registry, + ) + assert initialized["authority_initialized"] is True + assert initialized["legacy_fallback_used"] is False + return registry, state_file + + +def _control(registry: Path) -> GoalModeMCPControlPlane: + control = GoalModeMCPControlPlane( + GoalModeMCPConfig( + server_name="loopx-test", + runtime_profile="claude_code", + legacy_host_surface="claude_code", + ), + lambda: { + "goal_id": GOAL_ID, + "registry": str(registry), + "agent_id": AGENT_ID, + }, + ) + control.command_prefix = lambda: [sys.executable, "-m", "loopx.cli"] + return control + + +def test_a_raising_step_after_a_durable_commit_recovers_through_the_cli_receipt( + tmp_path: Path, +) -> None: + """The recovery boundary: the CLI owns the receipt, not the adapter.""" + registry, _ = _write_fixture(tmp_path) + added = add_goal_todo( + registry_path=registry, + goal_id=GOAL_ID, + role="agent", + text="Characterize the partial-step recovery boundary.", + task_class="advancement_task", + claimed_by=AGENT_ID, + ) + todo_id = str(added["todo_id"]) + control = _control(registry) + original = control.run_cli + injected = False + bound_identity: dict[str, object] = {} + bound_turn_instance_id = "" + bound_effect_id = "" + calls: list[list[str]] = [] + + def raise_after_commit(args: list[str], **kwargs: object) -> str: + nonlocal injected, bound_identity, bound_turn_instance_id, bound_effect_id + calls.append(list(args)) + output = original(args, **kwargs) # type: ignore[arg-type] + if not injected and args[:2] == ["quota", "spend-slot"]: + committed = json.loads(output) + if committed.get("appended") is True: + injected = True + bound_identity = dict(committed["settlement_identity"]) + bound_turn_instance_id = str(bound_identity["turn_instance_id"]) + bound_effect_id = str(bound_identity["effect_id"]) + raise RuntimeError("synthetic CLI transport failure after commit") + return output + + control.run_cli = raise_after_commit # type: ignore[assignment] + + with pytest.raises(RuntimeError, match="synthetic CLI transport failure"): + control.complete_task( + todo_id, + AGENT_ID, + "partial-step fixture check passed", + no_follow_up=True, + ) + + assert injected + readback = read_heartbeat_settlement( + tmp_path / "runtime", + goal_id=GOAL_ID, + agent_id=AGENT_ID, + todo_id=todo_id, + turn_instance_id=bound_turn_instance_id, + ) + # The spend receipt was committed before the failure, and the adapter left + # nothing of its own behind it. + assert readback.replay_phase == "settled" + + calls.clear() + replay = json.loads( + control.complete_task( + todo_id, + AGENT_ID, + "partial-step fixture check passed", + no_follow_up=True, + ) + ) + + assert replay["ok"] is True, json.dumps(replay, indent=2, sort_keys=True) + # The retry reuses the same settlement identity: the identity is derived + # from the same intent, not from adapter-local state. + assert replay["settlement_identity"]["turn_instance_id"] == bound_turn_instance_id + assert replay["settlement_identity"]["effect_id"] == bound_effect_id + assert replay["settlement_identity"]["todo_id"] == bound_identity["todo_id"] + # The retry replays the CLI receipt instead of spending a second slot. + assert replay["settlement"]["quota_spend"]["appended"] is False + assert replay["settlement"]["quota_spend"]["idempotent_replay"] is True + # The retry re-entered the plan at its first step: no adapter-local resume. + assert calls[0][:2] == ["quota", "should-run"] + + status = json.loads(control.should_run()) + assert status["quota"]["spent_slots"] == 1 # type: ignore[index] + + +def test_a_raised_step_before_completion_commits_no_settlement( + tmp_path: Path, +) -> None: + """A failed transport never becomes a settlement the CLI would replay.""" + registry, state_file = _write_fixture(tmp_path) + added = add_goal_todo( + registry_path=registry, + goal_id=GOAL_ID, + role="agent", + text="Prove that a raised step commits no settlement receipt.", + task_class="advancement_task", + claimed_by=AGENT_ID, + ) + todo_id = str(added["todo_id"]) + control = _control(registry) + original = control.run_cli + + def raise_on_completion(args: list[str], **kwargs: object) -> str: + if args[:2] == ["todo", "complete"]: + raise RuntimeError("synthetic CLI transport failure before completion") + return original(args, **kwargs) # type: ignore[arg-type] + + control.run_cli = raise_on_completion # type: ignore[assignment] + + with pytest.raises(RuntimeError, match="synthetic CLI transport failure"): + control.complete_task( + todo_id, + AGENT_ID, + "raised step fixture check passed", + ) + + # Nothing was completed and nothing was spent: the failure happened + # before the CLI committed the lifecycle completion. + status = json.loads(control.should_run()) + assert status["quota"]["spent_slots"] == 0 # type: ignore[index] + + todos = parse_active_state_todos(state_file.read_text(encoding="utf-8")) + by_id = { + str(item["todo_id"]): item + for item in todos["agent_todos"]["items"] + } + assert by_id[todo_id]["status"] == "open" From e8bfc415d4858f9690cea3133c403bec656ecaca Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Thu, 29 Oct 2026 22:26:25 +0800 Subject: [PATCH 2/5] test(settlement): pin the pending phase of each durable stage The characterization claimed that any durable commit left a settled readback, but the case it exercised only committed the quota spend. A raise after the lifecycle completion or the durable writeback leaves a settlement pending, and the owner names the stage that is still owed. Measure the real dispatch for each stage and read back the committed receipt kinds, the replay phase and the settlement progress, so the documented stage table is executable rather than prose. Signed-off-by: xiaoan17 --- ...t_host_adapter_settlement_partial_steps.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/tests/control_plane/test_host_adapter_settlement_partial_steps.py b/tests/control_plane/test_host_adapter_settlement_partial_steps.py index 95205aff35..cb82f26208 100644 --- a/tests/control_plane/test_host_adapter_settlement_partial_steps.py +++ b/tests/control_plane/test_host_adapter_settlement_partial_steps.py @@ -262,6 +262,15 @@ def raise_after_commit(args: list[str], **kwargs: object) -> str: # The spend receipt was committed before the failure, and the adapter left # nothing of its own behind it. assert readback.replay_phase == "settled" + assert readback.progress is not None + assert readback.progress["state"] == "settled" + # Only the spend stage was reached, so only its receipts exist. The + # validation receipt is committed even when the plan stops early. + assert _committed_receipt_kinds(readback) == [ + "validation", + "durable_writeback", + "quota_spend", + ] calls.clear() replay = json.loads( @@ -305,8 +314,12 @@ def test_a_raised_step_before_completion_commits_no_settlement( todo_id = str(added["todo_id"]) control = _control(registry) original = control.run_cli + turn_instance_id = "" def raise_on_completion(args: list[str], **kwargs: object) -> str: + nonlocal turn_instance_id + if "--turn-instance-id" in args: + turn_instance_id = str(args[args.index("--turn-instance-id") + 1]) if args[:2] == ["todo", "complete"]: raise RuntimeError("synthetic CLI transport failure before completion") return original(args, **kwargs) # type: ignore[arg-type] @@ -331,3 +344,120 @@ def raise_on_completion(args: list[str], **kwargs: object) -> str: for item in todos["agent_todos"]["items"] } assert by_id[todo_id]["status"] == "open" + + # The guard already ran, so the validation receipt is committed. No + # lifecycle completion, writeback or spend receipt exists, and the + # settlement is still open rather than pending. + readback = read_heartbeat_settlement( + tmp_path / "runtime", + goal_id=GOAL_ID, + agent_id=AGENT_ID, + todo_id=todo_id, + turn_instance_id=turn_instance_id, + ) + assert readback is not None + assert readback.replay_phase == "open" + assert readback.progress is not None + assert readback.progress["state"] == "writeback_required" + assert _committed_receipt_kinds(readback) == ["validation"] + + +def _raise_after_step( + tmp_path: Path, + step_prefix: tuple[str, ...], +) -> tuple[Path, str, str]: + """Complete a Todo whose transport fails just after ``step_prefix`` commits. + + Returns the runtime root, the Todo id and the turn instance id the plan used, + so the caller can read back the receipts the CLI already committed. + """ + registry, _ = _write_fixture(tmp_path) + added = add_goal_todo( + registry_path=registry, + goal_id=GOAL_ID, + role="agent", + text="Characterize the partial-step recovery boundary.", + task_class="advancement_task", + claimed_by=AGENT_ID, + ) + todo_id = str(added["todo_id"]) + control = _control(registry) + original = control.run_cli + turn_instance_id = "" + injected = False + + def raise_after_commit(args: list[str], **kwargs: object) -> str: + nonlocal turn_instance_id, injected + if "--turn-instance-id" in args: + turn_instance_id = str(args[args.index("--turn-instance-id") + 1]) + output = original(args, **kwargs) # type: ignore[arg-type] + if not injected and tuple(args[: len(step_prefix)]) == step_prefix: + injected = True + raise RuntimeError("synthetic CLI transport failure after commit") + return output + + control.run_cli = raise_after_commit # type: ignore[assignment] + with pytest.raises(RuntimeError, match="synthetic CLI transport failure"): + control.complete_task( + todo_id, + AGENT_ID, + "partial-step fixture check passed", + no_follow_up=True, + ) + return tmp_path / "runtime", todo_id, turn_instance_id + + +def _committed_receipt_kinds(readback: object) -> list[str]: + """The receipt kinds the CLI committed, in settlement order.""" + return [ + receipt.step_kind.value + for result in ( + readback.identity, # type: ignore[attr-defined] + readback.writeback, # type: ignore[attr-defined] + readback.spend, # type: ignore[attr-defined] + ) + for receipt in result.receipts + ] + + +@pytest.mark.parametrize( + ("step_prefix", "expected_replay_phase", "expected_state", "expected_receipt_kinds"), + [ + (("quota", "should-run"), "open", "writeback_required", ["validation"]), + (("todo", "complete"), "settlement_pending", "writeback_required", ["validation"]), + ( + ("refresh-state",), + "settlement_pending", + "spend_required", + ["validation", "durable_writeback"], + ), + ( + ("quota", "spend-slot"), + "settled", + "settled", + ["validation", "durable_writeback", "quota_spend"], + ), + ], +) +def test_a_raised_step_after_a_durable_commit_leaves_only_that_stage_pending( + tmp_path: Path, + step_prefix: tuple[str, ...], + expected_replay_phase: str, + expected_state: str, + expected_receipt_kinds: list[str], +) -> None: + """Each durable stage leaves exactly its own receipt behind.""" + runtime_root, todo_id, turn_instance_id = _raise_after_step(tmp_path, step_prefix) + + readback = read_heartbeat_settlement( + runtime_root, + goal_id=GOAL_ID, + agent_id=AGENT_ID, + todo_id=todo_id, + turn_instance_id=turn_instance_id, + ) + assert readback is not None + assert readback.replay_phase == expected_replay_phase + assert readback.progress is not None + assert readback.progress["state"] == expected_state + assert _committed_receipt_kinds(readback) == expected_receipt_kinds From 3f8a4c3b2c828eb38a73a94e45d53b4a943afc65 Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Thu, 29 Oct 2026 22:26:32 +0800 Subject: [PATCH 3/5] docs(rfc): correct the settlement stage claims and the remaining slices The characterization described any durable commit as settled and the before-completion case as having no settlement receipt, but the guard has already committed the validation receipt and only the quota spend settles the transaction. Spell the stage table out from the measured readbacks, and drop the claim that a settlement receipt does not exist before the lifecycle completion. The next slices also asked for a caller that adopts both settlement paths, which the parent confirmation entry already found and recorded as a withdrawn candidate. Keep the deletion preconditions, point the open slice at the host-adapter step registry in #7619, and fix the Chinese parent anchor to the id the strict build generates. Signed-off-by: xiaoan17 --- docs/architecture/rfcs/STATUS.md | 2 +- docs/architecture/rfcs/STATUS.zh-CN.md | 2 +- ...3-adapter-partial-step-characterization.md | 35 ++++++++++++++----- ...ter-partial-step-characterization.zh-CN.md | 28 +++++++++++---- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/architecture/rfcs/STATUS.md b/docs/architecture/rfcs/STATUS.md index 035ecd07c1..f67af4274c 100644 --- a/docs/architecture/rfcs/STATUS.md +++ b/docs/architecture/rfcs/STATUS.md @@ -31,7 +31,7 @@ appendix may keep dated history, but no dated log heading may precede it. | [RFC: Capable Agent Manager and Semantic Work Handoff (v0)](capable-manager-semantic-handoff-v0.md) | Accepted | [Personal Follow-through independent proposal](personal-follow-through-v0.md) | [2 entries](ledger/capable-manager-semantic-handoff-v0/) | | [RFC: Complete State Recovery and Controlled Reactivation (v0)](complete-state-recovery-v0.md) | Accepted | none | — | | [RFC: Composable State Machines and Recovery Verification (v0)](composable-state-machines-recovery-verification-v0.md) | Accepted | none | — | -| [RFC: Controller / Executor Sessions and One Governed Work Contract (v0)](controller-executor-unified-contract-v0.md) | Accepted | none | [4 entries](ledger/controller-executor-unified-contract-v0/) | +| [RFC: Controller / Executor Sessions and One Governed Work Contract (v0)](controller-executor-unified-contract-v0.md) | Accepted | none | [5 entries](ledger/controller-executor-unified-contract-v0/) | | [Explicit Todo continuation: Stage A](cross-session-memory-substrate-v0.md) | Accepted | none | — | | [RFC: LoopX Desktop Execution Frontends v0](desktop-execution-frontends-v0.md) | Accepted | none | — | | [RFC: External Evidence Research Capability v0](external-evidence-research-capability-v0.md) | Accepted | none | [3 entries](ledger/external-evidence-research-capability-v0/) | diff --git a/docs/architecture/rfcs/STATUS.zh-CN.md b/docs/architecture/rfcs/STATUS.zh-CN.md index a0b1ad1601..373aa3ef3c 100644 --- a/docs/architecture/rfcs/STATUS.zh-CN.md +++ b/docs/architecture/rfcs/STATUS.zh-CN.md @@ -28,7 +28,7 @@ | [RFC:强能力 Agent 管家与语义工作交接(v0)](capable-manager-semantic-handoff-v0.zh-CN.md) | 已接受 | [个人事项持续跟进独立提案](personal-follow-through-v0.zh-CN.md) | [2 条](ledger/capable-manager-semantic-handoff-v0/) | | [RFC:完整状态恢复与受控重新激活(v0)](complete-state-recovery-v0.zh-CN.md) | 已接受 | 无 | — | | [RFC:可组合状态机与恢复验证(v0)](composable-state-machines-recovery-verification-v0.zh-CN.md) | 已接受 | 无 | — | -| [RFC:Controller / Executor 会话与统一受治理工作契约(v0)](controller-executor-unified-contract-v0.zh-CN.md) | 已接受 | 无 | [4 条](ledger/controller-executor-unified-contract-v0/) | +| [RFC:Controller / Executor 会话与统一受治理工作契约(v0)](controller-executor-unified-contract-v0.zh-CN.md) | 已接受 | 无 | [5 条](ledger/controller-executor-unified-contract-v0/) | | [显式 Todo 接续:阶段 A](cross-session-memory-substrate-v0.zh-CN.md) | 已接受 | 无 | — | | [RFC:LoopX 桌面执行前端 v0](desktop-execution-frontends-v0.zh-CN.md) | 已接受 | 无 | — | | [RFC:外部证据研究能力 v0](external-evidence-research-capability-v0.zh-CN.md) | 已接受 | 无 | [3 条](ledger/external-evidence-research-capability-v0/) | diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md index 33b1cbdee9..77f1c0dea2 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md @@ -1,7 +1,7 @@ # C4.3 adapter partial-step characterization -- Parent: [controller/executor RFC, C4.3](2026-10-27-refactor-handoff.md#c43--settlement-adapters-over-existing-primitives). -- Baseline: `2a2323756`, 2026-10-29. The settlement owners are unchanged at `fb331f45`. +- Parent: [controller/executor RFC, C4.3](2026-10-27-refactor-handoff.md#c43-settlement-adapters-over-existing-primitives). +- Baseline: `f2990e3de`, 2026-10-29. The settlement owners are unchanged at `3a350c871`. - Status: characterization only. No runtime, schema, wire or default change. - Follows: [C4.3 settlement obligation comparison](2026-10-29-c4-3-settlement-obligation-comparison.md). - [Chinese mirror](2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md). @@ -24,10 +24,12 @@ starts from measured behavior rather than from the comparison's wording. | A step returns output the adapter cannot read | `_provider_condition_matches` treats a non-JSON output as a condition that did not match, so the plan stops after that step | The finalize phase still runs and the owner classifies the stop: `blocked`, `failed_stage: guard`, reason `quota guard returned malformed JSON` | | A step raises | The exception propagates out of the adapter. The finalize phase never runs, so the transaction is never classified. The adapter keeps no record of the step that already succeeded | Nothing is classified, because the owner is never asked | | A retry after a raise | The adapter re-enters the plan at its first step and never resumes mid-plan | The identity is re-derived from the same intent, so the same settlement identity is used | -| A step raises after a durable commit | The adapter leaves nothing behind; the CLI's canonical receipt store holds the committed receipt | The readback reports `settled`; the retry replays that receipt (`idempotent_replay`), spends no second slot and reports the same settlement identity | -| A step raises before the lifecycle completion | Nothing is spent and the Todo stays open | No settlement receipt exists to replay | +| A step raises before the lifecycle completion | Nothing is spent and the Todo stays open | The guard already ran, so the `validation` receipt is committed. The readback is `open` with progress `writeback_required`; there is no lifecycle completion, writeback or spend receipt to replay | +| A step raises after the lifecycle completion | The adapter leaves nothing behind; the CLI's canonical receipt store holds the committed receipt | The readback is `settlement_pending` with progress `writeback_required`: `validation` is committed, the writeback and spend receipts are missing | +| A step raises after the durable writeback | The adapter leaves nothing behind; the CLI's canonical receipt store holds the committed receipt | The readback is `settlement_pending` with progress `spend_required`: `validation` and `durable_writeback` are committed, the spend receipt is missing | +| A step raises after the quota spend | The adapter leaves nothing behind; the CLI's canonical receipt store holds the committed receipt | The readback is `settled`; the retry replays that receipt (`idempotent_replay`), spends no second slot and reports the same settlement identity | -Two findings follow from these cases. +Three findings follow from these cases. 1. **The adapter makes no classification decision.** A stopped plan is classified by the TypeScript owner; a raised step is not classified at all, @@ -36,6 +38,12 @@ Two findings follow from these cases. raise, the only durable evidence is what the CLI already committed. The retry succeeds because that receipt replays, and the adapter contributes nothing but a re-execution of the plan. +3. **The outstanding work is named by the owner, stage by stage.** The + readback distinguishes an open settlement from a pending one and names the + step still owed — `writeback_required` after the lifecycle completion, + `spend_required` after the durable writeback, `settled` after the spend. + A raise is therefore not a single undifferentiated failure state, and the + committed receipt kinds tell which stage a retry would resume from. ## Evidence @@ -43,6 +51,10 @@ Two findings follow from these cases. - `test_an_unreadable_step_output_stops_the_plan_and_the_owner_classifies_it` - `test_a_raising_step_leaves_no_finalize_and_no_adapter_local_resume` +- `test_a_raised_step_after_a_durable_commit_leaves_only_that_stage_pending` + (real CLI, real quota store; parametrized over the guard, lifecycle + completion, durable writeback and quota spend stages, reading back the + committed receipt kinds, the replay phase and the settlement progress) - `test_a_raising_step_after_a_durable_commit_recovers_through_the_cli_receipt` (real CLI, real quota store) - `test_a_raised_step_before_completion_commits_no_settlement` (real CLI) @@ -67,6 +79,13 @@ through a raised transport instead. ## Next slices -Unchanged by this entry. A deletion of the adapter-side interpreter still -needs the preconditions recorded in the comparison entry, and vocabulary -unification still needs a caller that adopts both paths. +Unchanged by this entry, with one correction to how the remaining work is +described. A deletion of the adapter-side interpreter still needs the +preconditions recorded in the comparison entry. The vocabulary candidate that +the confirmation entry withdrew — one typed owner for `guard`, +`lifecycle_completion` and `validation` — is not reopened here: that entry +already identified the shipped caller that adopts both paths and recorded the +candidate as withdrawn. The slice that remains open is the shared owner for +the ordered host-adapter step kinds, proposed in +[#7619](https://github.com/loopx-project/loopx/pull/7619). That is a registry +slice, not a continuation of the withdrawn unification. diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md index b9a2fbb3eb..b449fc2d61 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md @@ -1,7 +1,7 @@ # C4.3 适配器部分步骤行为刻画 -- 上级:[controller/executor RFC,C4.3](2026-10-27-refactor-handoff.zh-CN.md#c43--settlement-adapters-over-existing-primitives)。 -- 基线:`2a2323756`,2026-10-29。结算属主在 `fb331f45` 未变。 +- 上级:[controller/executor RFC,C4.3](2026-10-27-refactor-handoff.zh-CN.md#c43-adapter)。 +- 基线:`f2990e3de`,2026-10-29。结算属主自 `3a350c871` 起未变。 - 状态:只做刻画。不改变运行时、schema、wire 或默认值。 - 上游条目:[C4.3 结算义务对照](2026-10-29-c4-3-settlement-obligation-comparison.zh-CN.md)。 - [英文版](2026-10-29-c4-3-adapter-partial-step-characterization.md)。 @@ -22,16 +22,22 @@ | 某一步返回适配器读不懂的输出 | `_provider_condition_matches` 把非 JSON 输出当作"条件不匹配",计划在该步之后停止 | finalize 阶段仍然运行,由属主判定这次停止:`blocked`、`failed_stage: guard`、原因 `quota guard returned malformed JSON` | | 某一步抛错 | 异常直接冒出适配器。finalize 阶段从未运行,这笔事务从未被分类。适配器不保留已成功那一步的任何记录 | 没有分类,因为属主从未被询问 | | 抛错后的重试 | 适配器从计划第一步重新进入,绝不在中途续跑 | 身份由同一意图重新推导,因此使用同一个 settlement identity | -| 某一步在持久提交之后抛错 | 适配器什么也不留下;已提交的凭据由 CLI 的规范凭据库持有 | 读回报告 `settled`;重试重放该凭据(`idempotent_replay`),不会再扣一个槽位,并报告同一个 settlement identity | -| 某一步在生命周期完成之前抛错 | 没有花费,Todo 保持 open | 不存在可重放的结算凭据 | +| 某一步在生命周期完成之前抛错 | 没有花费,Todo 保持 open | guard 已经跑过,因此 `validation` 凭据已提交。读回为 `open`,进度为 `writeback_required`;不存在可重放的生命周期完成、写回或花费凭据 | +| 某一步在生命周期完成提交之后抛错 | 适配器什么也不留下;已提交的凭据由 CLI 的规范凭据库持有 | 读回为 `settlement_pending`,进度为 `writeback_required`:`validation` 已提交,写回与花费凭据缺失 | +| 某一步在持久写回提交之后抛错 | 适配器什么也不留下;已提交的凭据由 CLI 的规范凭据库持有 | 读回为 `settlement_pending`,进度为 `spend_required`:`validation` 与 `durable_writeback` 已提交,花费凭据缺失 | +| 某一步在配额花费提交之后抛错 | 适配器什么也不留下;已提交的凭据由 CLI 的规范凭据库持有 | 读回报告 `settled`;重试重放该凭据(`idempotent_replay`),不会再扣一个槽位,并报告同一个 settlement identity | -由此得到两条结论。 +由此得到三条结论。 1. **适配器不做任何分类判定。** 计划停止由 TypeScript 属主分类;某一步抛错则 完全没有分类,因为属主从未被询问。两种情形下适配器都只是传输。 2. **恢复边界是 CLI 凭据,不是适配器状态。** 抛错之后,唯一持久存在的证据是 CLI 已经提交的东西。重试之所以成功,是因为那份凭据可以重放;适配器除了重跑 计划之外没有贡献任何东西。 +3. **欠下的工作由属主按阶段点名。** 读回区分"结算尚未开始"与"结算待补",并指出 + 还欠哪一步:生命周期完成之后是 `writeback_required`,持久写回之后是 + `spend_required`,配额花费之后是 `settled`。因此抛错并不是一种不加区分的 + 失败状态;已提交的凭据类型说明重试会从哪个阶段继续。 ## 证据 @@ -39,6 +45,9 @@ - `test_an_unreadable_step_output_stops_the_plan_and_the_owner_classifies_it` - `test_a_raising_step_leaves_no_finalize_and_no_adapter_local_resume` +- `test_a_raised_step_after_a_durable_commit_leaves_only_that_stage_pending` + (真实 CLI、真实配额存储;按 guard、生命周期完成、持久写回与配额花费四个阶段 + 参数化,读回已提交的凭据类型、replay phase 与结算进度) - `test_a_raising_step_after_a_durable_commit_recovers_through_the_cli_receipt` (真实 CLI、真实配额存储) - `test_a_raised_step_before_completion_commits_no_settlement`(真实 CLI) @@ -60,5 +69,10 @@ ## 后续切片 -本条目不改变结论。删除适配器侧解释器仍需要对照条目记录的前提条件;词汇统一 -仍需要一个同时采用两条路径的调用方。 +本条目不改变结论,但修正了剩余工作的表述方式。删除适配器侧解释器仍需要对照条目 +记录的前提条件。词汇对照条目已经撤回的候选——把 `guard`、`lifecycle_completion` +与 `validation` 归到一个 typed 属主——在此不重新打开:那条目已经指出了同时采用 +两条路径的已交付调用方,并把该候选记为 withdrawn。仍然开放的切片是为 host-adapter +有序步骤类型设立共享属主,已在 +[#7619](https://github.com/loopx-project/loopx/pull/7619) 中提出;那是登记切片, +不是被撤回的统一化工作的延续。 From 14ba8b7040667bf11f481907e42148f7015dfa18 Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Thu, 29 Oct 2026 22:31:35 +0800 Subject: [PATCH 4/5] docs(rfc): record the merged state of the host-adapter step registry PR #7619 registered the ordered host-adapter step kinds and is merged, so the parent confirmation entry and this entry must stop describing it as a proposal. Update both mirrors and rebase this entry's baseline onto the main that carries it. Signed-off-by: xiaoan17 --- .../2026-10-29-c4-3-adapter-partial-step-characterization.md | 4 ++-- ...-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md | 4 ++-- .../2026-10-29-c4-3-settlement-vocabulary-confirmation.md | 2 +- ...026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md index 77f1c0dea2..08ee326efa 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md @@ -1,7 +1,7 @@ # C4.3 adapter partial-step characterization - Parent: [controller/executor RFC, C4.3](2026-10-27-refactor-handoff.md#c43-settlement-adapters-over-existing-primitives). -- Baseline: `f2990e3de`, 2026-10-29. The settlement owners are unchanged at `3a350c871`. +- Baseline: `8caaf678e`, 2026-10-29. The settlement owners are unchanged at `1e3401591`. - Status: characterization only. No runtime, schema, wire or default change. - Follows: [C4.3 settlement obligation comparison](2026-10-29-c4-3-settlement-obligation-comparison.md). - [Chinese mirror](2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md). @@ -86,6 +86,6 @@ the confirmation entry withdrew — one typed owner for `guard`, `lifecycle_completion` and `validation` — is not reopened here: that entry already identified the shipped caller that adopts both paths and recorded the candidate as withdrawn. The slice that remains open is the shared owner for -the ordered host-adapter step kinds, proposed in +the ordered host-adapter step kinds, merged in [#7619](https://github.com/loopx-project/loopx/pull/7619). That is a registry slice, not a continuation of the withdrawn unification. diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md index b449fc2d61..b981f5e9cc 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md @@ -1,7 +1,7 @@ # C4.3 适配器部分步骤行为刻画 - 上级:[controller/executor RFC,C4.3](2026-10-27-refactor-handoff.zh-CN.md#c43-adapter)。 -- 基线:`f2990e3de`,2026-10-29。结算属主自 `3a350c871` 起未变。 +- 基线:`8caaf678e`,2026-10-29。结算属主自 `1e3401591` 起未变。 - 状态:只做刻画。不改变运行时、schema、wire 或默认值。 - 上游条目:[C4.3 结算义务对照](2026-10-29-c4-3-settlement-obligation-comparison.zh-CN.md)。 - [英文版](2026-10-29-c4-3-adapter-partial-step-characterization.md)。 @@ -74,5 +74,5 @@ 与 `validation` 归到一个 typed 属主——在此不重新打开:那条目已经指出了同时采用 两条路径的已交付调用方,并把该候选记为 withdrawn。仍然开放的切片是为 host-adapter 有序步骤类型设立共享属主,已在 -[#7619](https://github.com/loopx-project/loopx/pull/7619) 中提出;那是登记切片, +[#7619](https://github.com/loopx-project/loopx/pull/7619) 中合入;那是登记切片, 不是被撤回的统一化工作的延续。 diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md index 02620ff0d9..5d6f2181f6 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md @@ -110,7 +110,7 @@ That gap is a separate slice: one shared owner for the ordered host-adapter step kinds, registered in the vocabulary registry so that the guard/validation distinction becomes machine-readable instead of prose. It must first reconcile the in-flight settlement work: #3412, #5677, #7580, #7523. That slice is -proposed in [#7619](https://github.com/loopx-project/loopx/pull/7619): it +merged in [#7619](https://github.com/loopx-project/loopx/pull/7619): it registers the ordered host-adapter step kinds as their own vocabulary with both runtimes as owners, so the two carriers are compared on every drift scan instead of hand-kept. diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md index b137757052..77f3ef0619 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md @@ -96,7 +96,7 @@ C4.3 结算义务对照列出一个候选后续切片:*词汇统一(小) 这是一个独立切片:为 host-adapter 的有序步骤类型给出同一个共享属主,并登记进 词汇 registry,让 guard/validation 的区别从散文变成机器可读。它必须先调和在途的 结算工作:#3412、#5677、#7580、#7523。该切片已在 -[#7619](https://github.com/loopx-project/loopx/pull/7619) 中提出:它把 host-adapter +[#7619](https://github.com/loopx-project/loopx/pull/7619) 中合入:它把 host-adapter 的有序步骤类型登记为独立词汇,并由两个运行时共同作为属主,于是两个载体在每次 漂移扫描中都会被比较,而不再靠手工维护。 From 00f730d11ec55234d33b4d638b3b8a84338dc329 Mon Sep 17 00:00:00 2001 From: xiaoan17 Date: Fri, 30 Oct 2026 00:53:28 +0800 Subject: [PATCH 5/5] docs(rfc): name the merged step-registry slice as completed The review noted that one sentence called the host-adapter step registry slice both still open and already merged. It is completed in #7619. Keep the open work on the deletion and adoption preconditions the comparison entry records. Signed-off-by: xiaoan17 --- ...10-29-c4-3-adapter-partial-step-characterization.md | 10 ++++++---- ...c4-3-adapter-partial-step-characterization.zh-CN.md | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md index 08ee326efa..43a8c43207 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.md @@ -85,7 +85,9 @@ preconditions recorded in the comparison entry. The vocabulary candidate that the confirmation entry withdrew — one typed owner for `guard`, `lifecycle_completion` and `validation` — is not reopened here: that entry already identified the shipped caller that adopts both paths and recorded the -candidate as withdrawn. The slice that remains open is the shared owner for -the ordered host-adapter step kinds, merged in -[#7619](https://github.com/loopx-project/loopx/pull/7619). That is a registry -slice, not a continuation of the withdrawn unification. +candidate as withdrawn. The independent registry slice — the shared owner for +the ordered host-adapter step kinds — is completed in +[#7619](https://github.com/loopx-project/loopx/pull/7619). That slice was a +registry change, not a continuation of the withdrawn unification. The work +that remains open is the deletion and adoption preconditions recorded in the +comparison entry. diff --git a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md index b981f5e9cc..6d59912f0d 100644 --- a/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-adapter-partial-step-characterization.zh-CN.md @@ -72,7 +72,7 @@ 本条目不改变结论,但修正了剩余工作的表述方式。删除适配器侧解释器仍需要对照条目 记录的前提条件。词汇对照条目已经撤回的候选——把 `guard`、`lifecycle_completion` 与 `validation` 归到一个 typed 属主——在此不重新打开:那条目已经指出了同时采用 -两条路径的已交付调用方,并把该候选记为 withdrawn。仍然开放的切片是为 host-adapter -有序步骤类型设立共享属主,已在 -[#7619](https://github.com/loopx-project/loopx/pull/7619) 中合入;那是登记切片, -不是被撤回的统一化工作的延续。 +两条路径的已交付调用方,并把该候选记为 withdrawn。独立的登记切片——为 host-adapter +有序步骤类型设立共享属主——已在 +[#7619](https://github.com/loopx-project/loopx/pull/7619) 中完成;那是登记切片, +不是被撤回的统一化工作的延续。仍然开放的是对照条目记录的删除与采用前提。