Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/) |
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/STATUS.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/) |
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SettlementStepKind, "validation">`;
`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.
Original file line number Diff line number Diff line change
@@ -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)。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] 使用中文目标的实际章节锚点

实际 strict build 的中文目标章节 ID 是 c43-adapter,这里仍使用英文 fragment #c43--settlement-adapters-over-existing-primitives。页面能打开,但不会跳到对应章节。请将 fragment 改为 #c43-adapter,并核对生成 HTML 的 heading 与 href。这是非阻断导航问题。

- 前序:本目录 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<SettlementStepKind, "validation">`;
`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 修改,不构成运行时资格,也不
主张任何一条路径是冗余的。源码事实按上述基线读取,之后可能漂移。
Loading