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
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,15 @@ Every pull request that claims progress against this RFC follows the
It declares fixture impact, exercises every affected provider arm, and keeps
the read-only three-arm rehearsal as a separate promotion gate.

Legacy lifecycle field assembly now calls the single TS field planner described
in the [TS retirement checkpoint](typescript-control-plane-migration-v0.md#legacy-field-rule-retirement-checkpoint).
This removes Python decisions without changing the per-goal authority phase:
unpromoted goals still commit through the locked Markdown writer, while promoted
goals retain their existing provider transactions and unsupported-field fences.
The planner neither reads a provider nor grants a lease, CAS receipt, or write
permission. This checkpoint closes one rule owner, not the remaining mutation
inventory or local-store/promotion qualification.

### Next delivery and parallel provider work

Markdown is a **permanent first-class readable projection**. Retire its database
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,14 @@ fence。它不能替代只读三臂演练,因为所有 provider 共享新的 s
声明 fixture 影响、覆盖所有受影响的 provider arm,并把只读三臂演练保留为独立的
promotion gate。

Legacy lifecycle 的字段组装现在调用唯一 TS field planner,详见
[TS 退役检查点](typescript-control-plane-migration-v0.zh-CN.md#legacy-字段规则退役检查点)。
它删除 Python decision,但不改变逐 goal 的 authority 阶段:未 promotion 的 goal
仍由持锁 Markdown writer 提交,promoted goal 仍使用既有 provider transaction 与
unsupported-field fence。planner 不读取 provider,也不授予 lease、CAS receipt 或
写权限。该检查点闭合的是一个规则 owner,不是剩余 mutation inventory 或本地
store/promotion 资格化。

### 下一步交付与并行 provider 工作

Markdown 是**长期保留的一等可读投影**。退役的是它的数据库及业务 writer 权威,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- Status: Accepted, transaction-payoff phase in progress
- Proposed by: LoopX maintainers
- Date: 2026-08-15
- Last revised: 2026-09-07
- Last revised: 2026-09-09
- Scope: an incremental, replacement-first migration of the LoopX control-plane
core from Python to TypeScript without maintaining two semantic
implementations
Expand Down Expand Up @@ -195,6 +195,32 @@ marker/hint configuration with an explicit compatibility plan. Exact legacy
lifecycle classification codes and unrelated cadence policies are outside this
slice; they must not be reported as migrated or globally free of prose rules.

### Legacy field-rule retirement checkpoint

`todos/field_update.ts` now owns the complete metadata intent assembly used by
the legacy `update`, `claim`, `complete`, and `supersede` line writer: status and
completion timestamps, omission versus explicit clears, binding precedence,
removed-policy repair, resume-generation pairing, and completion metadata.
It composes the existing TS completion rule directly. The replaced Python
decision branches and the last-caller `todo.completion_state.metadata_updates`
RPC/facade are removed, not kept as a fallback.

This is a pure plan, not admission or a provider commit. Python still owns
Markdown lookup/encoding, byte-level no-op detection, locking and external
effects. Public role/binding admission and the event writer are not declared
migrated by this slice. Promoted update remains text/note-only; no unsupported
field gains authority, no goal is promoted, and no third storage path appears.
Rejected plans now leave even the caller's in-memory line buffer unchanged;
public rejected transactions were already non-committing.

There is one field-plan crossing per legacy line write. It replaces the former
metadata RPC on ordinary edits; already-finalized completions with an override
gain one planning crossing. Cached codec normalization calls remain. This is
semantic deletion, not a claim of fewer crossings on every command. Retire the
adapter with its final legacy lifecycle caller after full-goal cutover, or fold
it into that caller's coarse transaction when migrating the caller; do not grow
a series of field-level RPCs. Retain Markdown rendering permanently.

### Next delivery sequence

The destination retains Markdown as a **permanent readable projection**, not a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- Status:Accepted,transaction-payoff 阶段进行中
- Proposed by:LoopX maintainers
- Date:2026-08-15
- Last revised:2026-09-07
- Last revised:2026-09-09
- Scope:LoopX 控制面核心从 Python 到 TypeScript 的增量、replacement-first
迁移;不长期维护两份语义实现
- Tracking issue:[#3225](https://github.com/huangruiteng/loopx/issues/3225)
Expand Down Expand Up @@ -152,6 +152,28 @@ Python decision,保留独立审阅的 typed case,并通过真实 CLI 验证
字段的 writer,并用明确兼容计划退役旧 marker/hint 配置。本批不迁移精确的旧
lifecycle classification code 或其他 cadence policy,不能宣称全局已无文本规则。

### Legacy 字段规则退役检查点

`todos/field_update.ts` 现在持有 legacy `update`、`claim`、`complete`、`supersede`
line writer 共用的完整 metadata intent 组装:status 与 completion 时间、未传与显式
清空、binding 优先级、已移除 policy 的修复、resume-generation 配对及 completion
metadata。它直接组合已有 TS completion rule。被替代的 Python decision 分支,以及
失去最后调用者的 `todo.completion_state.metadata_updates` RPC/facade 一起删除,
不保留为 fallback。

这是一份纯 plan,不是 admission 或 provider commit。Python 仍保留 Markdown 定位/
编码、字节级 no-op 检查、锁与外部 effect;本批不宣称迁完公共 role/binding admission
或 event writer。Promoted update 仍只支持 text/note:不扩权、不 promotion goal、
不增加第三条存储路径。plan 拒绝时,现在连调用方的内存行缓冲也保持不变;公共
事务在拒绝时原本就不会提交。

每次 legacy line write 有一次 field-plan crossing:普通编辑替代原 metadata RPC;
已经 finalization、携带 override 的 completion 会增加一次 planning crossing。
带缓存的 codec normalization 调用仍在。这兑现的是语义代码删除,不宣称每个命令
都减少 round trip。完整 goal cutover 后随最后 legacy lifecycle caller 删除 adapter,
或者迁移该 caller 时将 plan 折叠进其粗粒度事务;不得继续扩张逐字段 RPC。
Markdown renderer 长期保留。

### 下一步交付顺序

终态长期保留 Markdown 作为**可读投影**,不是第二个数据库。本 RFC 负责唯一 typed
Expand Down
4 changes: 2 additions & 2 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ import {
evaluateTodoCompletionFence,
} from "./todos/completion_fence.ts";
import {
buildTodoCompletionMetadataUpdates,
normalizeTodoCompletionValue,
requireTodoCompletionMetadataValue,
selectTodoCompletionContinuation,
} from "./todos/completion_state.ts";
import { reduceTodoCompletionTransaction } from "./todos/completion_transaction.ts";
import { transitionTodoNextAction } from "./todos/next_action.ts";
import { planTodoFieldUpdate } from "./todos/field_update.ts";
import {
evaluateTodoResumeConditions,
normalizeTodoResumeWhen,
Expand Down Expand Up @@ -364,7 +364,7 @@ export function createEffectRuntimeHandlers(
["todo.completion_state.normalize", normalizeTodoCompletionValue],
["todo.completion_state.require_metadata", requireTodoCompletionMetadataValue],
["todo.completion_state.continuation_for_write", selectTodoCompletionContinuation],
["todo.completion_state.metadata_updates", buildTodoCompletionMetadataUpdates],
["todo.field_update.plan", planTodoFieldUpdate],
[
"todo.claim.decide",
(params) => evaluateCoordinationTodoClaimDecision(
Expand Down
49 changes: 0 additions & 49 deletions loopx/control_plane/todos/completion_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,6 @@ def _no_followup_value(value: Any) -> bool | str:
return value if isinstance(value, bool) else _string_value(value)


def _successor_values(value: Any) -> Any:
if not value:
return []
if isinstance(value, (list, tuple, set, str, Mapping)):
return list(value)
return value


def _result(method: str, params: dict[str, Any]) -> Mapping[str, Any]:
try:
result = effect_runtime_result(method, params)
Expand Down Expand Up @@ -135,44 +127,3 @@ def completion_continuation_for_write(*, no_followup: bool, has_successor: bool)
if continuation not in {item.value for item in TodoCompletionContinuation}:
raise RuntimeError("TypeScript completion continuation result shape mismatch")
return str(continuation)


def completion_metadata_updates(
block: Mapping[str, Any],
*,
target_status: str,
normalized_status: str | None,
completion_continuation: str | None,
completion_recovery: str | None,
no_followup: bool | None,
successor_todo_ids: list[str] | None,
) -> dict[str, Any]:
result = _result(
"todo.completion_state.metadata_updates",
{
"schema_version": TODO_COMPLETION_STATE_REQUEST_SCHEMA,
"block": {
"no_followup": _no_followup_value(block.get("no_followup")),
"completion_continuation": _string_value(
block.get("completion_continuation")
),
"successor_todo_ids": _successor_values(
block.get("successor_todo_ids")
),
},
"target_status": target_status,
"normalized_status": normalized_status,
"completion_continuation": completion_continuation,
"completion_recovery": completion_recovery,
"no_followup": no_followup,
"successor_todo_ids": successor_todo_ids,
},
)
updates = result.get("updates")
if not isinstance(updates, Mapping) or any(
key not in {"completion_continuation", "completion_recovery"}
or not isinstance(value, str)
for key, value in updates.items()
):
raise RuntimeError("TypeScript completion metadata updates shape mismatch")
return dict(updates)
Loading