Repository navigation
docs(rfc): confirm the C4.3 settlement vocabulary question #7617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
huangruiteng
merged 2 commits into
loopx-project:main
from
xiaoan17:codex/c43-settlement-vocabulary-confirmation
Oct 29, 2026
+230
−2
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
...cutor-unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
106 changes: 106 additions & 0 deletions
106
...unified-contract-v0/2026-10-29-c4-3-settlement-vocabulary-confirmation.zh-CN.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)。 | ||
| - 前序:本目录 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 修改,不构成运行时资格,也不 | ||
| 主张任何一条路径是冗余的。源码事实按上述基线读取,之后可能漂移。 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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。这是非阻断导航问题。