diff --git a/docs/architecture/rfcs/STATUS.md b/docs/architecture/rfcs/STATUS.md index 24f907b374..6fac57113c 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 | [2 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 | [3 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 039db58cdb..51a81ad3e6 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) | 已接受 | 无 | [2 条](ledger/controller-executor-unified-contract-v0/) | +| [RFC:Controller / Executor 会话与统一受治理工作契约(v0)](controller-executor-unified-contract-v0.zh-CN.md) | 已接受 | 无 | [3 条](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-settlement-vocabulary-confirmation.md b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md new file mode 100644 index 0000000000..02620ff0d9 --- /dev/null +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md @@ -0,0 +1,122 @@ +# C4.3 settlement vocabulary confirmation + +- Parent: [controller/executor RFC, C4.3](2026-10-27-refactor-handoff.md#c43--settlement-adapters-over-existing-primitives). +- Follows: the [C4.3 settlement obligation comparison entry](2026-10-29-c4-3-settlement-obligation-comparison.md) + of 2026-10-29 in this directory. It settles that entry's first candidate slice. +- Baseline: `0a9a3a388`, 2026-10-29. The settlement owners are unchanged since + `544aa0fee`. +- Status: read-only confirmation. No code, schema, wire, registry or default change. +- [Chinese mirror](2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md). + +## Why this entry exists + +The C4.3 settlement obligation comparison listed a candidate next slice: +*vocabulary unification (small). If any caller adopts both paths, give `guard`, +`lifecycle_completion` and `validation` one typed owner instead of three names in +two files. Do not unify names that describe different obligations.* + +That candidate was conditional on a question this entry answers: do the three +names describe one obligation? The comparison already hinted that they do not +all match, but it did not settle it, and a guess here would have produced either +a wrong merge or a wrong refusal to merge. + +## The three names + +| Name | Owner | Position | What it does | +| --- | --- | --- | --- | +| `guard` | `turn_driver/host_todo_completion.ts` `STEP_KINDS` | first step of the host provider plan | runs `quota should-run` with the turn instance id; its `continue_when` decides whether the rest of the plan runs | +| `lifecycle_completion` | same | second step | runs `todo complete` with the completion arguments | +| `validation` | `effect_program.ts` `SETTLEMENT_STEP_KINDS`, and a phase of `turn_transaction_contract.json` | a transaction phase the caller must already have completed | asserts the admission already happened; it is never executed | + +## Evidence + +1. **`validation` is not an executable effect in either runtime.** + `effect_program.ts` defines + `TurnProviderStepKind = Exclude`; + `effect_program.py` mirrors that exclusion and + `require_turn_provider_step_kind` raises `validation is not a provider effect + step`. `settlement.ts` throws `unsupported base settlement step validation` + if the reducer ever asks to execute it, and it receives + `committed_payloads.validation = {}` together with `require_validation: true`, + so the caller discharges the phase and B never runs it. The registry already + says the same in its own words: the `validation` value note in + `loopx/semantics/vocabulary_v0.json` records that it "is the one step the + dispatcher never executes", that it "carries no command template", and that it + "is seeded, so its receipt is emitted directly when the completed phases + already contain it". + +2. **`guard` and `lifecycle_completion` are executed steps.** + `host_adapter_settlement.py` decodes an `ordered_cli_sequence` of four or five + steps in the fixed order `guard → lifecycle_completion → durable_writeback → + quota_spend → [terminal_closeout]`, then calls `run_cli(step.args)` for each + one. Every non-final step carries a `continue_when` that decides whether the + following steps run. + +3. **The two value sets differ exactly where the question is asked.** A emits + `guard`, `lifecycle_completion`, `durable_writeback`, `quota_spend`, + `terminal_closeout`. B's `SettlementStepKind` is `validation`, + `durable_writeback`, `quota_spend`, `terminal_closeout`. `guard` and + `lifecycle_completion` occur only in A; `validation` only in B. The + repository's own advisory (`scripts/generate_semantic_inventory.py --report`) + does not list the two sets as a merge candidate, because their value sets + differ. + +4. **`lifecycle_completion` has no counterpart in B.** B carries the completion + obligation inside `durable_writeback` — the writeback payload has to satisfy + the completion condition when the result is `validated_completion` — and in + `terminal_closeout_requirement`. + +5. **One shipped caller already uses both vocabularies for one admission + decision.** `extensions/governed_capability_execution.py` classifies the + caller's admission snapshot with A's `classify_host_guard_snapshot` (the + `classify_guard` phase of `host_todo_completion.ts`), asserts `should_run` + and the typed settlement identity, and then calls + `execute_turn_driver_settlement` with `completed_phases` that already + contain `validation`. The MCP host path (`goal_mode_mcp.py` → + `settle_host_todo_completion`) instead executes the `guard` step itself. + +## Verdict + +- `guard` and `validation` name one obligation seen from two positions: the + producer that runs the admission, and the consumer that refuses to run it and + requires it to have been discharged. They are not two spellings of one value. + Renaming them would hide that asymmetry and would break B's "a phase is never + executed" invariant. +- `lifecycle_completion` is a different obligation and keeps its own name. +- The candidate *one typed owner for the three names* is therefore **withdrawn**. + The names are already unambiguous once read at their owners. + +## What the confirmation leaves open + +The vocabulary that does exist has no typed owner, and its literal is written +twice: + +- `loopx/control_plane/turn_driver/host_todo_completion.ts` defines a local + `STEP_KINDS`; +- `loopx/control_plane/host_adapter_settlement.py` defines a private + `_STEP_KINDS` and validates the TypeScript-produced plan order against it. + +Neither symbol is exported, neither is registered in +`loopx/semantics/vocabulary_v0.json` — 28 vocabularies are registered, and +`settlement_step_kind` covers only B's set — and the repository's inventory does +not see either carrier. The TypeScript scan matches +`export const NAME = [...] as const;` and the Python scan matches module-level +`UPPER_NAME` assignments, so a private, unexported pair stays invisible to the +drift smoke that the registry policy relies on. A hand-kept Python mirror of a +TypeScript literal can drift, and the failure would surface at runtime as +`TypeScript host provider step order mismatch`. + +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 +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. + +## What this does not establish + +No equivalence proof, no deletion authorization, no registry edit, no runtime +qualification and no claim that either path is redundant. Source facts are read +at the baseline above and may drift. 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 new file mode 100644 index 0000000000..b137757052 --- /dev/null +++ b/docs/architecture/rfcs/ledger/controller-executor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md @@ -0,0 +1,106 @@ +# C4.3 结算词汇确认 + +- 上级:[controller/executor RFC,C4.3](2026-10-27-refactor-handoff.zh-CN.md#c43--settlement-adapters-over-existing-primitives)。 +- 前序:本目录 2026-10-29 的 + [《C4.3 结算义务对照》条目](2026-10-29-c4-3-settlement-obligation-comparison.zh-CN.md)。 + 本条目回答该条目列出的第一个候选切片。 +- 基线:`0a9a3a388`,2026-10-29。结算属主自 `544aa0fee` 起未变。 +- 状态:只读确认。不改代码、schema、wire、registry 或默认值。 +- [英文版](2026-10-29-c4-3-settlement-vocabulary-confirmation.md)。 + +## 为什么有这条记录 + +C4.3 结算义务对照列出一个候选后续切片:*词汇统一(小)。若确有调用方同时采用 +两条路径,为 `guard`、`lifecycle_completion` 与 `validation` 指定一个类型属主, +而不是让三个名字分散在两个文件中。不要合并描述不同义务的名字。* + +该候选以一个问题为前提,本条目回答它:这三个名字描述的是不是同一个义务? +对照条目已经暗示它们并不全等价,但没有定论;在这里靠猜,要么会错误合并, +要么会错误拒绝合并。 + +## 三个名字 + +| 名字 | 属主 | 位置 | 作用 | +| --- | --- | --- | --- | +| `guard` | `turn_driver/host_todo_completion.ts` 的 `STEP_KINDS` | host provider plan 的第一步 | 带 turn instance id 执行 `quota should-run`;其 `continue_when` 决定后续步骤是否继续 | +| `lifecycle_completion` | 同上 | 第二步 | 带 completion 参数执行 `todo complete` | +| `validation` | `effect_program.ts` 的 `SETTLEMENT_STEP_KINDS`,以及 `turn_transaction_contract.json` 的一个阶段 | 调用方必须已经完成的事务阶段 | 断言准入已经发生;它从不被执行 | + +## 证据 + +1. **`validation` 在两个运行时都不是可执行效果。** + `effect_program.ts` 定义 + `TurnProviderStepKind = Exclude`; + `effect_program.py` 镜像了同一个排除, + `require_turn_provider_step_kind` 会抛出 `validation is not a provider effect + step`。`settlement.ts` 在 reducer 试图执行它时抛出 + `unsupported base settlement step validation`,并且它收到的是 + `committed_payloads.validation = {}` 与 `require_validation: true`, + 即该阶段由调用方履行,B 从不执行它。registry 本身已经用同样的话记录了这点: + `loopx/semantics/vocabulary_v0.json` 里 `validation` 的 value note 写明它是 + "dispatcher 从不执行的那一步"、"不携带命令模板",并且"被直接种下——已完成阶段 + 已包含它时直接发出回执"。 + +2. **`guard` 与 `lifecycle_completion` 是被执行的步骤。** + `host_adapter_settlement.py` 解码一个 `ordered_cli_sequence`:四或五步, + 固定顺序为 `guard → lifecycle_completion → durable_writeback → quota_spend + → [terminal_closeout]`,随后对每一步调用 `run_cli(step.args)`。 + 每个非末步都带一个 `continue_when`,决定后续步骤是否继续。 + +3. **两套取值恰好在问题所在处不同。** A 产出 `guard`、`lifecycle_completion`、 + `durable_writeback`、`quota_spend`、`terminal_closeout`;B 的 + `SettlementStepKind` 是 `validation`、`durable_writeback`、`quota_spend`、 + `terminal_closeout`。`guard` 与 `lifecycle_completion` 只出现在 A, + `validation` 只出现在 B。仓库自带的顾问工具 + (`scripts/generate_semantic_inventory.py --report`)没有把这两套列为合并 + 候选,因为它们的取值集合不同。 + +4. **`lifecycle_completion` 在 B 无对应项。** B 把完成义务放在 + `durable_writeback` 里——当结果为 `validated_completion` 时,写回载荷必须满足 + 完成条件——以及 `terminal_closeout_requirement` 里。 + +5. **已发布的一个调用点已经用两套词汇表达同一个准入决定。** + `extensions/governed_capability_execution.py` 用 A 的 + `classify_host_guard_snapshot`(`host_todo_completion.ts` 的 `classify_guard` + 阶段)判定调用方给出的准入快照,断言 `should_run` 与类型化结算身份, + 随后调用 `execute_turn_driver_settlement`,其 `completed_phases` 已经包含 + `validation`。而 MCP host 路径(`goal_mode_mcp.py` → + `settle_host_todo_completion`)是自己执行 `guard` 步骤的。 + +## 结论 + +- `guard` 与 `validation` 描述的是同一个义务的两个位置:执行准入的一方,与拒绝 + 执行、只要求准入已被履行的一方。它们不是同一个值的两种拼写。改名会掩盖这个 + 不对称,也会破坏 B "阶段从不被执行" 的不变式。 +- `lifecycle_completion` 是不同的义务,保留自己的名字。 +- 因此,候选切片 *为三个名字指定一个类型属主* **撤回**。只要回到各自属主阅读, + 这些名字本身已经不含糊。 + +## 这次确认留下什么 + +真正存在的那个词汇没有类型属主,而且字面量写了两遍: + +- `loopx/control_plane/turn_driver/host_todo_completion.ts` 定义了本地的 + `STEP_KINDS`; +- `loopx/control_plane/host_adapter_settlement.py` 定义了私有的 `_STEP_KINDS`, + 并用它校验 TypeScript 产出的 plan 顺序。 + +两个符号都没有导出,都没有登记进 `loopx/semantics/vocabulary_v0.json` +(已登记 28 个词汇,`settlement_step_kind` 只覆盖 B 那一套),仓库的清单工具 +也看不到其中任何一个载体。TypeScript 扫描匹配的是 +`export const NAME = [...] as const;`,Python 扫描匹配的是模块级 `UPPER_NAME` +赋值,所以一对私有且未导出的字面量对 registry 策略所依赖的漂移 smoke 始终 +不可见。手工维护的 Python 副本可能与 TypeScript 字面量漂移,届时会以 +`TypeScript host provider step order mismatch` 的形式在运行时暴露。 + +这是一个独立切片:为 host-adapter 的有序步骤类型给出同一个共享属主,并登记进 +词汇 registry,让 guard/validation 的区别从散文变成机器可读。它必须先调和在途的 +结算工作:#3412、#5677、#7580、#7523。该切片已在 +[#7619](https://github.com/loopx-project/loopx/pull/7619) 中提出:它把 host-adapter +的有序步骤类型登记为独立词汇,并由两个运行时共同作为属主,于是两个载体在每次 +漂移扫描中都会被比较,而不再靠手工维护。 + +## 本记录不成立的结论 + +不构成等价证明,不构成删除授权,不构成 registry 修改,不构成运行时资格,也不 +主张任何一条路径是冗余的。源码事实按上述基线读取,之后可能漂移。