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 @@ -3269,6 +3269,35 @@ semantic corrections. A green unit suite, a canonical selector, or a new config
field alone cannot advance a package to default readiness. Planned integration,
soak, release, merge and live promotion retain their respective authorization.

### Reviewed cutover checkpoint

The saved-plan/recovery slice closes a concrete operator gap: execution can be
bound to the reviewed source/provider/policy, and a fenced cutover can be
completed or read back without reconstructing intent from legacy Markdown.
The TS owner shares durable qualification and exact receipt proof between both
paths. See [operation and acceptance](../../reference/reviewed-coordination-promotion.md).
This stage does not authorize an active Goal migration or flip a default.

For an existing claimed Goal, integrate the claim-preserving migration in #4870
with this slice, qualify the exact combined head and resolve its existing CI and
review holds. Preserve the registered owners, existing claims and leases; do not
clear ownership to make storage migration appear ready. The saved-plan carrier
must retain migration strategy, registered-agent facts and target digest when
that extension is integrated.

The remaining default-on program is still approximately **5–8 cohesive PR
packages**, with scope rather than line counts determining the split: caller /
external-effect fencing (1–2), consumer/projection closure (1), contributor-owned
SQLite D2 (#4224, 1–2), integrated capture/whole-Goal acceptance (1–2), then default
onboarding plus bounded Python retirement (1). This slice contributes to the
integrated migration package; it does not count an entire package complete.
Actual elapsed soak cannot be compressed into a promised number of PRs.
PostgreSQL service admission and operations remain a separate medium-term lane.

现有 Goal 的可审核晋升与恢复、所有新 Goal 默认选用 provider、删除全部 Python,
是三个不同完成条件。先交付一条能保留状态、能读回、能恢复的真实迁移路径,再按调用方
闭合程度删除旧实现。不要用已合入 PR 数量替代端到端验收。

### Parallel delivery plan

| Lane | May start | Scope and exit condition | Dependency |
Expand Down
14 changes: 14 additions & 0 deletions docs/architecture/rfcs/typescript-control-plane-migration-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -1830,3 +1830,17 @@ recovery and pinned-intent preservation use the existing journal-backed path;
no new RPC method, durable ACK or provider default. The stronger confirmation
costs one additional read on a stable delivery. Full L5/D1 qualification, D2 and
cutover remain open; see the [projection contract](../../reference/protocols/active-state-structured-projection-v0.md).

### Reviewed coordination cutover ownership

Saved-plan execution and fenced recovery now share the TypeScript promotion
owner. Fresh-source qualification wraps durable lineage qualification; recovery
uses that same lineage rule after exact fence verification. The Python CLI loads
a reviewed JSON carrier and transports fresh observations, without recreating
plan hashes, recovery decisions or receipt proof. Both commit paths share one
receipt/first-transaction readback contract.

This is a migration orchestration checkpoint, not completion of Stage 3 or a
default-provider flip. Integrate claim-preserving migration separately, retain
real-backend and captured-source qualification, and retire Python only where its
actual callers have moved. [Operator contract](../../reference/reviewed-coordination-promotion.md).
Original file line number Diff line number Diff line change
Expand Up @@ -1405,3 +1405,17 @@ transaction 只能靠削弱既有行为才能通过 invariant/recovery/performan

实测交付记录存于[逐条 ledger](ledger/typescript-control-plane-migration-v0/)。
每条记录说明已交付边界及剩余验收缺口;上方 T1–T4 检查点仍是当前迁移计划。

### Reviewed coordination cutover ownership

Saved-plan execution and fenced recovery now share the TypeScript promotion
owner. Fresh-source qualification wraps durable lineage qualification; recovery
uses that same lineage rule after exact fence verification. The Python CLI loads
a reviewed JSON carrier and transports fresh observations, without recreating
plan hashes, recovery decisions or receipt proof. Both commit paths share one
receipt/first-transaction readback contract.

This is a migration orchestration checkpoint, not completion of Stage 3 or a
default-provider flip. Integrate claim-preserving migration separately, retain
real-backend and captured-source qualification, and retire Python only where its
actual callers have moved. [Operator contract](../../reference/reviewed-coordination-promotion.md).
2 changes: 2 additions & 0 deletions docs/reference/local-authority-provider-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ selected-provider failure without fallback, PostgreSQL factory identity
fencing, and the factory's rejection of a different provider. File, SQLite,
and PostgreSQL continue to share the provider-neutral transaction conformance
contract; PostgreSQL's real-server qualification remains a separate gate.

See [reviewed promotion and recovery](reviewed-coordination-promotion.md) for the explicit saved-plan CLI journey.
2 changes: 2 additions & 0 deletions docs/reference/local-authority-provider-selection.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ provider selection matrix 使用 production-scale synthetic coordination fixture
PostgreSQL factory identity fencing,以及 factory 返回其他 provider 时的拒绝。
File、SQLite 和 PostgreSQL 继续共享 provider-neutral transaction conformance
contract;PostgreSQL 的真实服务器 qualification 仍是独立 gate。

保存计划、执行和断点恢复的操作见[审核后的晋升与恢复](reviewed-coordination-promotion.zh-CN.md)。
142 changes: 142 additions & 0 deletions docs/reference/reviewed-coordination-promotion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Reviewed coordination promotion and recovery

A promotion moves a Goal's Todo/lease coordination authority from the legacy
source to its selected canonical provider. Preview, writer fencing, provider
commit, and acknowledgement are distinct steps. A successful preview is neither
a grant nor evidence that cutover has happened.

The operator can now save the exact preview, execute that plan, and recover its
original transaction without reconstructing intent from a later Markdown view.
The TypeScript coordination boundary owns plan validation, qualification,
fencing and receipt proof; Python only loads the file and transports the request.

## Preview and execute

Use an explicitly enabled, bootstrapped and qualified runtime shadow. Its
qualification must cover real mutations and required event classes; an empty
shadow or a saved JSON file cannot substitute for that evidence. Existing v0
promotion still requires `hard_lease`. Provider selection and migration approval
remain separate from these commands.

```bash
loopx --format json coordination-shadow promote \
--goal-id example-goal \
--minimum-operations 3 \
--require-event-kind todo_update > reviewed-promotion.json

loopx --format json coordination-shadow promote \
--goal-id example-goal --reviewed-plan reviewed-promotion.json

loopx --format json coordination-shadow promote \
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
```

Inspect `ok`, `promotion.status`, the plan's target provider, source revision,
projection digest and qualification policy before execution. The saved file may
be the entire successful CLI preview or its `promotion.plan.reviewed_plan`
envelope. Keep it in operator-owned local storage: it carries a runtime path and
Goal identity, so it is not a public collaboration artifact.

`--reviewed-plan` owns the operation id and qualification policy. Combining it
with `--minimum-operations` or `--require-event-kind` is an error. A normal
`promote` command without a saved plan retains its existing defaults.

Execution captures and qualifies the source again under the existing locks. If
the computed plan digest differs, it returns
`local_authority_reviewed_plan_changed` before engaging a writer fence. Review a
new preview after legitimate source changes; do not edit the old digest to force
acceptance. The digest detects changed intent; the durable fence and provider
state establish whether that intent may proceed.

## Recover the original cutover

```bash
loopx --format json coordination-shadow recover-promotion \
--goal-id example-goal --reviewed-plan reviewed-promotion.json

loopx --format json coordination-shadow recover-promotion \
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
```

Recovery resolves the registered Goal and runtime but does not read legacy
Markdown or require the transient shadow opt-in. It requires the exact existing
writer fence. It never creates a missing fence, selects another provider, or
falls back to a legacy source.

| Durable state | Preview | With `--execute` |
| --- | --- | --- |
| No matching writer fence | Reject | Reject |
| Matching fence, no canonical commit, exact qualified shadow retained | `recovery_ready` | Commit and read back |
| Original promotion committed, including a later canonical head | `replayed` | `replayed`; no business write |
| Different canonical initialization or inconsistent receipt lineage | Reject | Reject |
| Provider unavailable | Report provider failure | Report provider failure |

For an uncommitted recovery, the original shadow revision, projection, capture
binding, complete transaction lineage, outbox settlement, operation count and
event coverage must still qualify. Recovery validates these durable facts under
the same maintenance guard used by canonical writers. It does not pretend to
observe fresh source parity after the source has ceased to be authority.

A thrown commit acknowledgement can mean that the provider already committed.
Both promotion paths therefore share one commit/readback implementation. It
attempts the business commit once, then checks the persisted receipt and first
transaction. The receipt body, operation id, cursor, provider revision and
initial projection must agree. A matching proof reports success/recovery even
if later work has advanced the head. A missing or conflicting proof remains a
failure; an unavailable proof read is not silently treated as absence.

The returned promotion revision and cursor identify the original cutover, not
the current head. `executed=false` on a replay means this invocation performed no
business write. Inspect `legacy_writer_fenced` and reconciliation evidence when
an execution fails; do not infer that a failure left legacy writers usable.

## Bounded capture proof transport

A long Goal can exceed the existing 2 MiB RPC response budget before promotion:
sequence recovery used to return a full head and full projections for retained
transactions. The `outbox_read` proof read model now keeps full lineage validation
inside TypeScript, while returning progress, receipts, projection digests and
partition markers. Sequence allocation requests no transaction rows; drain uses
the compact rows. Existing full diagnostic reads retain their default contract.
No transport limit, stored population or transaction validation is weakened.
A pending outbox still blocks promotion; use the existing bounded
`authority-shadow drain --goal-id example-goal --budget-seconds 60` operation
and inspect its result before retrying preview.

## Product and rollout boundary

This is an operator CLI administration journey. It adds no dashboard, Lark or
managed-Turn automatic migration trigger, settings editor, capability grant or
new provider selector. Those surfaces continue to consume canonical data through
the existing routing/projection contracts after a separately authorized cutover.

File and SQLite use their existing local stores. PostgreSQL follows the same
transaction/readback contract through its service-owned factory; a local CLI
selector alone does not provide a PostgreSQL connection or tenant authority.

The claim-preserving migration work in PR #4870 is a complementary prerequisite
for Goals that need explicit `preserve` or a claim-preserving `hard_lease`
transition. The two changes overlap the promotion orchestration and must be
integrated and tested together; this saved-plan feature alone does not enable
that policy conversion on a v0-only checkout.

Default-on promotion, SQLite long-duration qualification, post-promotion export
or rollback, and retirement of remaining Python callers retain their RFC gates.
Recovery is a forward completion/readback operation, not rollback. Do not remove
a live fence, reset canonical storage, or replace the source to make recovery
pass. Before execution, abandoning a saved preview needs no runtime mutation.

## Validation contract

Durable tests cover real File/SQLite CLI preview, saved-plan execution, source
drift, policy override rejection, later canonical writes and recovery after
legacy deletion. The provider conformance suite uses the shared production-scale
fixture in legacy and native record shapes, preserving the complete Todo/lease
population through File, SQLite and a real isolated PostgreSQL server.

Negative receipt tests independently corrupt the receipt index and first
transaction. Interrupted-commit tests distinguish failure before commit from a
lost acknowledgement after commit. These are synthetic fault injections, not a
claim of arbitrary process-death or elapsed-soak coverage. Real local rehearsals
must use read-only captured sources and disposable copies; never promote an
active Goal merely to validate this refactor.
99 changes: 99 additions & 0 deletions docs/reference/reviewed-coordination-promotion.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# 审核后的协调状态晋升与恢复

晋升会把 Goal 的 Todo/lease 权威来源从旧路径切换到选定的 canonical provider。
预览、封住旧写者、提交新存储、收到成功响应是不同的步骤。预览成功并不代表已经切换。

现在可以保存完整预览,执行这份计划,再用同一份计划恢复原事务。计划校验、准入、
fence 与 receipt 证明由 TypeScript 协调边界负责;Python 只读文件、传输请求。

## 操作

先显式启用并 bootstrap runtime shadow,让它捕获真实变更并通过资格校验。
现有 v0 晋升仍要求 Goal 已处于 `hard_lease`;保存 JSON 不会降低这个条件。

```bash
loopx --format json coordination-shadow promote \
--goal-id example-goal --minimum-operations 3 \
--require-event-kind todo_update > reviewed-promotion.json

loopx --format json coordination-shadow promote \
--goal-id example-goal --reviewed-plan reviewed-promotion.json

loopx --format json coordination-shadow promote \
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
```

执行前审核 `ok`、`promotion.status`、目标 provider、源 revision、projection digest
和资格策略。文件可以是完整 CLI 成功预览,也可以是其中的
`promotion.plan.reviewed_plan`。它包含 runtime 路径与 Goal 身份,应保存在本地,
不要贴到公开 PR。

保存的计划决定 operation id 和资格策略,不能再叠加 `--minimum-operations` 或
`--require-event-kind`。不传计划文件的旧命令继续沿用原默认值。

执行会在现有锁内重新捕获、校验源状态。计划变了,就在 fencing 前返回
`local_authority_reviewed_plan_changed`。此时重新预览并审核;不要修改旧 digest
来强行通过。digest 说明“执行的是哪份意图”,持久 fence 和 provider 状态说明
“这份意图现在能否执行”。

## 断点恢复

```bash
loopx --format json coordination-shadow recover-promotion \
--goal-id example-goal --reviewed-plan reviewed-promotion.json

loopx --format json coordination-shadow recover-promotion \
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
```

恢复仍要找到注册的 Goal 与 runtime,但不读取旧 Markdown,也不依赖临时 shadow
开关。它必须看到完全相同的持久 writer fence,不能创建缺失的 fence、换 provider,
也不能回退到旧来源。

| 状态 | 仅预览 | 加 `--execute` |
| --- | --- | --- |
| 没有匹配的 fence | 拒绝 | 拒绝 |
| 有 fence、尚未提交、保留的 shadow 仍完全合格 | `recovery_ready` | 提交并读回 |
| 原晋升已提交,甚至 canonical 已继续变更 | `replayed` | `replayed`,不重写 |
| 已被其他事务初始化,或 receipt 与事务链矛盾 | 拒绝 | 拒绝 |
| provider 不可用 | 报告 provider 错误 | 报告 provider 错误 |

尚未提交的恢复仍核对原 shadow revision、projection、capture binding、完整事务链、
outbox 是否结清、操作次数和事件覆盖;校验与提交共用 canonical writer 的维护锁。
它不会把已经失去权威地位的旧文件当作必须重新观察的来源。

提交调用抛错,也可能是“数据已落盘,但响应丢了”。两条晋升路径现在共用一次提交、
持久读回的实现,不盲目重做业务事务。receipt 与第一笔事务的 operation id、cursor、
provider revision、receipt 内容和初始 projection 必须全部一致。

返回的 revision 与 cursor 指向原晋升事务,不一定是当前最新 head。重放返回
`executed=false`,表示本次没有业务写入。失败时还要看 `legacy_writer_fenced` 和
恢复提示;失败不等于旧写者一定还能继续工作。

## 大 Goal 的捕获证明

序号恢复以前会返回完整 head 和历史事务的完整 projection,大 Goal 可能因此超过
现有 2 MiB RPC 响应上限,甚至不能产生晋升所需的 shadow 变更。
现在 `outbox_read` 的 proof read model 在 TS 内完整校验事务链,只传回进度、receipt、
projection digest 和 partition marker;分配序号不返回事务行,drain 使用紧凑行。
原有完整诊断读取保持默认合同,不提高传输上限、不减少持久数据,也不省略链校验。
若 outbox 尚未结清,晋升仍会拒绝。先执行并检查既有的有界 drain:
`authority-shadow drain --goal-id example-goal --budget-seconds 60`。

## 交付边界

这是运维 CLI 的显式管理操作,没有新增 Dashboard/飞书自动迁移按钮、设置项或
capability grant。它们在获授权的切换后继续使用既有 canonical 路由和展示合同。
PostgreSQL 仍需要服务持有的 factory 与租户权限,不能仅靠本地 selector 接通数据库。

PR #4870 提供保留 claim 的 `preserve`/`hard_lease` 转换,属于互补前置工作;
两者涉及同一个晋升编排,需要组合验证。本功能单独合入不会让 v0 checkout 自动获得
这些模式转换。默认切换、SQLite 长时资格、晋升后导出/回退、剩余 Python 删除,仍
遵守 RFC 的独立门槛。

恢复用于向前补齐或确认原切换,不是 rollback。不要删除活跃 fence、重置 canonical
存储或替换源文件来绕过拒绝。执行前放弃一份预览,只需停止使用该文件。

验证包括真实 File/SQLite CLI、完整合成状态的多 provider conformance、receipt
索引与事务链分别被破坏的负例,以及提交前中断、提交后丢响应的故障注入。故障注入
不等于任意进程崩溃或长时 soak 已验证。真实项目演练只在只读快照的可丢弃副本上执行。
Loading
Loading