Skip to content

Commit c4314d9

Browse files
authored
Merge pull request #4879 from loopx-project/codex/authority-closure-0922
feat(coordination): make reviewed Goal promotion recoverable at full-state scale
2 parents 4bed6ed + 9970bb1 commit c4314d9

27 files changed

Lines changed: 1807 additions & 302 deletions

‎docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3269,6 +3269,35 @@ semantic corrections. A green unit suite, a canonical selector, or a new config
32693269
field alone cannot advance a package to default readiness. Planned integration,
32703270
soak, release, merge and live promotion retain their respective authorization.
32713271

3272+
### Reviewed cutover checkpoint
3273+
3274+
The saved-plan/recovery slice closes a concrete operator gap: execution can be
3275+
bound to the reviewed source/provider/policy, and a fenced cutover can be
3276+
completed or read back without reconstructing intent from legacy Markdown.
3277+
The TS owner shares durable qualification and exact receipt proof between both
3278+
paths. See [operation and acceptance](../../reference/reviewed-coordination-promotion.md).
3279+
This stage does not authorize an active Goal migration or flip a default.
3280+
3281+
For an existing claimed Goal, integrate the claim-preserving migration in #4870
3282+
with this slice, qualify the exact combined head and resolve its existing CI and
3283+
review holds. Preserve the registered owners, existing claims and leases; do not
3284+
clear ownership to make storage migration appear ready. The saved-plan carrier
3285+
must retain migration strategy, registered-agent facts and target digest when
3286+
that extension is integrated.
3287+
3288+
The remaining default-on program is still approximately **5–8 cohesive PR
3289+
packages**, with scope rather than line counts determining the split: caller /
3290+
external-effect fencing (1–2), consumer/projection closure (1), contributor-owned
3291+
SQLite D2 (#4224, 1–2), integrated capture/whole-Goal acceptance (1–2), then default
3292+
onboarding plus bounded Python retirement (1). This slice contributes to the
3293+
integrated migration package; it does not count an entire package complete.
3294+
Actual elapsed soak cannot be compressed into a promised number of PRs.
3295+
PostgreSQL service admission and operations remain a separate medium-term lane.
3296+
3297+
现有 Goal 的可审核晋升与恢复、所有新 Goal 默认选用 provider、删除全部 Python,
3298+
是三个不同完成条件。先交付一条能保留状态、能读回、能恢复的真实迁移路径,再按调用方
3299+
闭合程度删除旧实现。不要用已合入 PR 数量替代端到端验收。
3300+
32723301
### Parallel delivery plan
32733302

32743303
| Lane | May start | Scope and exit condition | Dependency |

‎docs/architecture/rfcs/typescript-control-plane-migration-v0.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,3 +1830,17 @@ recovery and pinned-intent preservation use the existing journal-backed path;
18301830
no new RPC method, durable ACK or provider default. The stronger confirmation
18311831
costs one additional read on a stable delivery. Full L5/D1 qualification, D2 and
18321832
cutover remain open; see the [projection contract](../../reference/protocols/active-state-structured-projection-v0.md).
1833+
1834+
### Reviewed coordination cutover ownership
1835+
1836+
Saved-plan execution and fenced recovery now share the TypeScript promotion
1837+
owner. Fresh-source qualification wraps durable lineage qualification; recovery
1838+
uses that same lineage rule after exact fence verification. The Python CLI loads
1839+
a reviewed JSON carrier and transports fresh observations, without recreating
1840+
plan hashes, recovery decisions or receipt proof. Both commit paths share one
1841+
receipt/first-transaction readback contract.
1842+
1843+
This is a migration orchestration checkpoint, not completion of Stage 3 or a
1844+
default-provider flip. Integrate claim-preserving migration separately, retain
1845+
real-backend and captured-source qualification, and retire Python only where its
1846+
actual callers have moved. [Operator contract](../../reference/reviewed-coordination-promotion.md).

‎docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,3 +1405,17 @@ transaction 只能靠削弱既有行为才能通过 invariant/recovery/performan
14051405

14061406
实测交付记录存于[逐条 ledger](ledger/typescript-control-plane-migration-v0/)。
14071407
每条记录说明已交付边界及剩余验收缺口;上方 T1–T4 检查点仍是当前迁移计划。
1408+
1409+
### Reviewed coordination cutover ownership
1410+
1411+
Saved-plan execution and fenced recovery now share the TypeScript promotion
1412+
owner. Fresh-source qualification wraps durable lineage qualification; recovery
1413+
uses that same lineage rule after exact fence verification. The Python CLI loads
1414+
a reviewed JSON carrier and transports fresh observations, without recreating
1415+
plan hashes, recovery decisions or receipt proof. Both commit paths share one
1416+
receipt/first-transaction readback contract.
1417+
1418+
This is a migration orchestration checkpoint, not completion of Stage 3 or a
1419+
default-provider flip. Integrate claim-preserving migration separately, retain
1420+
real-backend and captured-source qualification, and retire Python only where its
1421+
actual callers have moved. [Operator contract](../../reference/reviewed-coordination-promotion.md).

‎docs/reference/local-authority-provider-selection.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ selected-provider failure without fallback, PostgreSQL factory identity
5656
fencing, and the factory's rejection of a different provider. File, SQLite,
5757
and PostgreSQL continue to share the provider-neutral transaction conformance
5858
contract; PostgreSQL's real-server qualification remains a separate gate.
59+
60+
See [reviewed promotion and recovery](reviewed-coordination-promotion.md) for the explicit saved-plan CLI journey.

‎docs/reference/local-authority-provider-selection.zh-CN.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ provider selection matrix 使用 production-scale synthetic coordination fixture
5252
PostgreSQL factory identity fencing,以及 factory 返回其他 provider 时的拒绝。
5353
File、SQLite 和 PostgreSQL 继续共享 provider-neutral transaction conformance
5454
contract;PostgreSQL 的真实服务器 qualification 仍是独立 gate。
55+
56+
保存计划、执行和断点恢复的操作见[审核后的晋升与恢复](reviewed-coordination-promotion.zh-CN.md)。
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Reviewed coordination promotion and recovery
2+
3+
A promotion moves a Goal's Todo/lease coordination authority from the legacy
4+
source to its selected canonical provider. Preview, writer fencing, provider
5+
commit, and acknowledgement are distinct steps. A successful preview is neither
6+
a grant nor evidence that cutover has happened.
7+
8+
The operator can now save the exact preview, execute that plan, and recover its
9+
original transaction without reconstructing intent from a later Markdown view.
10+
The TypeScript coordination boundary owns plan validation, qualification,
11+
fencing and receipt proof; Python only loads the file and transports the request.
12+
13+
## Preview and execute
14+
15+
Use an explicitly enabled, bootstrapped and qualified runtime shadow. Its
16+
qualification must cover real mutations and required event classes; an empty
17+
shadow or a saved JSON file cannot substitute for that evidence. Existing v0
18+
promotion still requires `hard_lease`. Provider selection and migration approval
19+
remain separate from these commands.
20+
21+
```bash
22+
loopx --format json coordination-shadow promote \
23+
--goal-id example-goal \
24+
--minimum-operations 3 \
25+
--require-event-kind todo_update > reviewed-promotion.json
26+
27+
loopx --format json coordination-shadow promote \
28+
--goal-id example-goal --reviewed-plan reviewed-promotion.json
29+
30+
loopx --format json coordination-shadow promote \
31+
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
32+
```
33+
34+
Inspect `ok`, `promotion.status`, the plan's target provider, source revision,
35+
projection digest and qualification policy before execution. The saved file may
36+
be the entire successful CLI preview or its `promotion.plan.reviewed_plan`
37+
envelope. Keep it in operator-owned local storage: it carries a runtime path and
38+
Goal identity, so it is not a public collaboration artifact.
39+
40+
`--reviewed-plan` owns the operation id and qualification policy. Combining it
41+
with `--minimum-operations` or `--require-event-kind` is an error. A normal
42+
`promote` command without a saved plan retains its existing defaults.
43+
44+
Execution captures and qualifies the source again under the existing locks. If
45+
the computed plan digest differs, it returns
46+
`local_authority_reviewed_plan_changed` before engaging a writer fence. Review a
47+
new preview after legitimate source changes; do not edit the old digest to force
48+
acceptance. The digest detects changed intent; the durable fence and provider
49+
state establish whether that intent may proceed.
50+
51+
## Recover the original cutover
52+
53+
```bash
54+
loopx --format json coordination-shadow recover-promotion \
55+
--goal-id example-goal --reviewed-plan reviewed-promotion.json
56+
57+
loopx --format json coordination-shadow recover-promotion \
58+
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
59+
```
60+
61+
Recovery resolves the registered Goal and runtime but does not read legacy
62+
Markdown or require the transient shadow opt-in. It requires the exact existing
63+
writer fence. It never creates a missing fence, selects another provider, or
64+
falls back to a legacy source.
65+
66+
| Durable state | Preview | With `--execute` |
67+
| --- | --- | --- |
68+
| No matching writer fence | Reject | Reject |
69+
| Matching fence, no canonical commit, exact qualified shadow retained | `recovery_ready` | Commit and read back |
70+
| Original promotion committed, including a later canonical head | `replayed` | `replayed`; no business write |
71+
| Different canonical initialization or inconsistent receipt lineage | Reject | Reject |
72+
| Provider unavailable | Report provider failure | Report provider failure |
73+
74+
For an uncommitted recovery, the original shadow revision, projection, capture
75+
binding, complete transaction lineage, outbox settlement, operation count and
76+
event coverage must still qualify. Recovery validates these durable facts under
77+
the same maintenance guard used by canonical writers. It does not pretend to
78+
observe fresh source parity after the source has ceased to be authority.
79+
80+
A thrown commit acknowledgement can mean that the provider already committed.
81+
Both promotion paths therefore share one commit/readback implementation. It
82+
attempts the business commit once, then checks the persisted receipt and first
83+
transaction. The receipt body, operation id, cursor, provider revision and
84+
initial projection must agree. A matching proof reports success/recovery even
85+
if later work has advanced the head. A missing or conflicting proof remains a
86+
failure; an unavailable proof read is not silently treated as absence.
87+
88+
The returned promotion revision and cursor identify the original cutover, not
89+
the current head. `executed=false` on a replay means this invocation performed no
90+
business write. Inspect `legacy_writer_fenced` and reconciliation evidence when
91+
an execution fails; do not infer that a failure left legacy writers usable.
92+
93+
## Bounded capture proof transport
94+
95+
A long Goal can exceed the existing 2 MiB RPC response budget before promotion:
96+
sequence recovery used to return a full head and full projections for retained
97+
transactions. The `outbox_read` proof read model now keeps full lineage validation
98+
inside TypeScript, while returning progress, receipts, projection digests and
99+
partition markers. Sequence allocation requests no transaction rows; drain uses
100+
the compact rows. Existing full diagnostic reads retain their default contract.
101+
No transport limit, stored population or transaction validation is weakened.
102+
A pending outbox still blocks promotion; use the existing bounded
103+
`authority-shadow drain --goal-id example-goal --budget-seconds 60` operation
104+
and inspect its result before retrying preview.
105+
106+
## Product and rollout boundary
107+
108+
This is an operator CLI administration journey. It adds no dashboard, Lark or
109+
managed-Turn automatic migration trigger, settings editor, capability grant or
110+
new provider selector. Those surfaces continue to consume canonical data through
111+
the existing routing/projection contracts after a separately authorized cutover.
112+
113+
File and SQLite use their existing local stores. PostgreSQL follows the same
114+
transaction/readback contract through its service-owned factory; a local CLI
115+
selector alone does not provide a PostgreSQL connection or tenant authority.
116+
117+
The claim-preserving migration work in PR #4870 is a complementary prerequisite
118+
for Goals that need explicit `preserve` or a claim-preserving `hard_lease`
119+
transition. The two changes overlap the promotion orchestration and must be
120+
integrated and tested together; this saved-plan feature alone does not enable
121+
that policy conversion on a v0-only checkout.
122+
123+
Default-on promotion, SQLite long-duration qualification, post-promotion export
124+
or rollback, and retirement of remaining Python callers retain their RFC gates.
125+
Recovery is a forward completion/readback operation, not rollback. Do not remove
126+
a live fence, reset canonical storage, or replace the source to make recovery
127+
pass. Before execution, abandoning a saved preview needs no runtime mutation.
128+
129+
## Validation contract
130+
131+
Durable tests cover real File/SQLite CLI preview, saved-plan execution, source
132+
drift, policy override rejection, later canonical writes and recovery after
133+
legacy deletion. The provider conformance suite uses the shared production-scale
134+
fixture in legacy and native record shapes, preserving the complete Todo/lease
135+
population through File, SQLite and a real isolated PostgreSQL server.
136+
137+
Negative receipt tests independently corrupt the receipt index and first
138+
transaction. Interrupted-commit tests distinguish failure before commit from a
139+
lost acknowledgement after commit. These are synthetic fault injections, not a
140+
claim of arbitrary process-death or elapsed-soak coverage. Real local rehearsals
141+
must use read-only captured sources and disposable copies; never promote an
142+
active Goal merely to validate this refactor.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# 审核后的协调状态晋升与恢复
2+
3+
晋升会把 Goal 的 Todo/lease 权威来源从旧路径切换到选定的 canonical provider。
4+
预览、封住旧写者、提交新存储、收到成功响应是不同的步骤。预览成功并不代表已经切换。
5+
6+
现在可以保存完整预览,执行这份计划,再用同一份计划恢复原事务。计划校验、准入、
7+
fence 与 receipt 证明由 TypeScript 协调边界负责;Python 只读文件、传输请求。
8+
9+
## 操作
10+
11+
先显式启用并 bootstrap runtime shadow,让它捕获真实变更并通过资格校验。
12+
现有 v0 晋升仍要求 Goal 已处于 `hard_lease`;保存 JSON 不会降低这个条件。
13+
14+
```bash
15+
loopx --format json coordination-shadow promote \
16+
--goal-id example-goal --minimum-operations 3 \
17+
--require-event-kind todo_update > reviewed-promotion.json
18+
19+
loopx --format json coordination-shadow promote \
20+
--goal-id example-goal --reviewed-plan reviewed-promotion.json
21+
22+
loopx --format json coordination-shadow promote \
23+
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
24+
```
25+
26+
执行前审核 `ok`、`promotion.status`、目标 provider、源 revision、projection digest
27+
和资格策略。文件可以是完整 CLI 成功预览,也可以是其中的
28+
`promotion.plan.reviewed_plan`。它包含 runtime 路径与 Goal 身份,应保存在本地,
29+
不要贴到公开 PR。
30+
31+
保存的计划决定 operation id 和资格策略,不能再叠加 `--minimum-operations` 或
32+
`--require-event-kind`。不传计划文件的旧命令继续沿用原默认值。
33+
34+
执行会在现有锁内重新捕获、校验源状态。计划变了,就在 fencing 前返回
35+
`local_authority_reviewed_plan_changed`。此时重新预览并审核;不要修改旧 digest
36+
来强行通过。digest 说明“执行的是哪份意图”,持久 fence 和 provider 状态说明
37+
“这份意图现在能否执行”。
38+
39+
## 断点恢复
40+
41+
```bash
42+
loopx --format json coordination-shadow recover-promotion \
43+
--goal-id example-goal --reviewed-plan reviewed-promotion.json
44+
45+
loopx --format json coordination-shadow recover-promotion \
46+
--goal-id example-goal --reviewed-plan reviewed-promotion.json --execute
47+
```
48+
49+
恢复仍要找到注册的 Goal 与 runtime,但不读取旧 Markdown,也不依赖临时 shadow
50+
开关。它必须看到完全相同的持久 writer fence,不能创建缺失的 fence、换 provider,
51+
也不能回退到旧来源。
52+
53+
| 状态 | 仅预览 | 加 `--execute` |
54+
| --- | --- | --- |
55+
| 没有匹配的 fence | 拒绝 | 拒绝 |
56+
| 有 fence、尚未提交、保留的 shadow 仍完全合格 | `recovery_ready` | 提交并读回 |
57+
| 原晋升已提交,甚至 canonical 已继续变更 | `replayed` | `replayed`,不重写 |
58+
| 已被其他事务初始化,或 receipt 与事务链矛盾 | 拒绝 | 拒绝 |
59+
| provider 不可用 | 报告 provider 错误 | 报告 provider 错误 |
60+
61+
尚未提交的恢复仍核对原 shadow revision、projection、capture binding、完整事务链、
62+
outbox 是否结清、操作次数和事件覆盖;校验与提交共用 canonical writer 的维护锁。
63+
它不会把已经失去权威地位的旧文件当作必须重新观察的来源。
64+
65+
提交调用抛错,也可能是“数据已落盘,但响应丢了”。两条晋升路径现在共用一次提交、
66+
持久读回的实现,不盲目重做业务事务。receipt 与第一笔事务的 operation id、cursor、
67+
provider revision、receipt 内容和初始 projection 必须全部一致。
68+
69+
返回的 revision 与 cursor 指向原晋升事务,不一定是当前最新 head。重放返回
70+
`executed=false`,表示本次没有业务写入。失败时还要看 `legacy_writer_fenced` 和
71+
恢复提示;失败不等于旧写者一定还能继续工作。
72+
73+
## 大 Goal 的捕获证明
74+
75+
序号恢复以前会返回完整 head 和历史事务的完整 projection,大 Goal 可能因此超过
76+
现有 2 MiB RPC 响应上限,甚至不能产生晋升所需的 shadow 变更。
77+
现在 `outbox_read` 的 proof read model 在 TS 内完整校验事务链,只传回进度、receipt、
78+
projection digest 和 partition marker;分配序号不返回事务行,drain 使用紧凑行。
79+
原有完整诊断读取保持默认合同,不提高传输上限、不减少持久数据,也不省略链校验。
80+
若 outbox 尚未结清,晋升仍会拒绝。先执行并检查既有的有界 drain:
81+
`authority-shadow drain --goal-id example-goal --budget-seconds 60`。
82+
83+
## 交付边界
84+
85+
这是运维 CLI 的显式管理操作,没有新增 Dashboard/飞书自动迁移按钮、设置项或
86+
capability grant。它们在获授权的切换后继续使用既有 canonical 路由和展示合同。
87+
PostgreSQL 仍需要服务持有的 factory 与租户权限,不能仅靠本地 selector 接通数据库。
88+
89+
PR #4870 提供保留 claim 的 `preserve`/`hard_lease` 转换,属于互补前置工作;
90+
两者涉及同一个晋升编排,需要组合验证。本功能单独合入不会让 v0 checkout 自动获得
91+
这些模式转换。默认切换、SQLite 长时资格、晋升后导出/回退、剩余 Python 删除,仍
92+
遵守 RFC 的独立门槛。
93+
94+
恢复用于向前补齐或确认原切换,不是 rollback。不要删除活跃 fence、重置 canonical
95+
存储或替换源文件来绕过拒绝。执行前放弃一份预览,只需停止使用该文件。
96+
97+
验证包括真实 File/SQLite CLI、完整合成状态的多 provider conformance、receipt
98+
索引与事务链分别被破坏的负例,以及提交前中断、提交后丢响应的故障注入。故障注入
99+
不等于任意进程崩溃或长时 soak 已验证。真实项目演练只在只读快照的可丢弃副本上执行。

0 commit comments

Comments
 (0)