Skip to content

Commit cd9cfa6

Browse files
authored
Merge pull request #4408 from huangruiteng/codex/bounded-local-authority-profile
refactor(authority): bound the retained sqlite state log
2 parents 7cbc57a + 91d63a1 commit cd9cfa6

20 files changed

Lines changed: 2294 additions & 157 deletions

‎.github/workflows/python-tests.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ jobs:
231231
tests=()
232232
for test in tests/control_plane_ts/*.test.ts; do
233233
case "$test" in
234-
*/sqlite_authority_store.test.ts|*/local_authority_provider.test.ts|*/authority_provider_parity.test.ts|*/sqlite_capacity.test.ts) continue ;;
234+
*/sqlite_authority_store.test.ts|*/sqlite_authority_bounded_profile.test.ts|*/sqlite_authority_migration.test.ts|*/local_authority_provider.test.ts|*/authority_provider_parity.test.ts|*/sqlite_capacity.test.ts) continue ;;
235235
esac
236236
tests+=("$test")
237237
done

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,18 +1231,29 @@ is gated by evidence below, not by calendar dates or this PR's merge status.
12311231
| New-Goal default decision (F) | Maintainers accept the qualified profile and canary results, operational diagnostics, backup/restore procedure, release instructions and default-disable path. Ship the default change in a separate disclosed release change. | Apply only to newly created eligible local Goals. Existing explicit file selections remain pinned. Unsupported runtimes/filesystems require an explicit supported choice; no silent backend switch on open failure. |
12321232
| Existing-Goal migration and file retirement | Migrate opt-in cohorts using the reviewed fenced workflow; reconcile receipts, history, projections and rollback after each cohort. Inventory the last file-primary callers and compatibility windows before removing any path. | Each Goal needs explicit migration authority. Retire file as the ordinary primary only after that evidence; retain reference/import/export support until its own callers and retention duties end. |
12331233

1234-
**Current evidence position (rechecked 2026-09-13).** #4121 merged as
1234+
**Current evidence position (rechecked 2026-09-15).** #4121 merged as
12351235
`bde1632bb6f29aeb9a8b4ac23ead3e98ba2f2f55`, delivering the first candidate
1236-
milestone. It remains subject to profile qualification and promotion; it is
1237-
not completion of lane L. Its head pointer is bounded and
1238-
operation/cursor lookups are indexed, but it retains full historical projections
1239-
and counts a covering index for continuity. That count grows with history;
1240-
current/accessed-row digests are checked, not every historical payload per read.
1241-
The qualification entrypoint now separates a small rehearsal from an explicit
1242-
64-KiB 10k/100k storage axis, with p99/counts, cold CLI, RSS and a
1243-
passed/failed/missing ledger. Unavailable logical/WAL traffic, full-domain,
1244-
large-history recovery and elapsed-soak evidence remain holds; runner completion
1245-
cannot claim the <=2 growth budget or ten-day qualification. See the
1236+
milestone. The bounded retained-storage half of lane L now has a reviewed
1237+
candidate: `loopx_sqlite_authority_store_v2` keeps one checkpoint per 64-commit
1238+
window plus one exact state delta per commit instead of one full projection copy
1239+
per row, proves the live head from the head row, its retained transaction and
1240+
the cursor bounds, rebuilds at most one window per historical read, and proves
1241+
the complete delta chain through `verifyAuthorityHistory`. Cursors, operation
1242+
IDs, commit digests, provider revisions, receipts, events and scan pages are
1243+
unchanged, and the shipped version-1 databases migrate through the reviewed
1244+
`examples/coordination/sqlite-authority-migration.ts` entry point. Rehearsal
1245+
evidence at 1,000 commits/64 KiB now reports 16 checkpoints, a 63-commit replay
1246+
budget, one checkpoint history read and 1,048,576 retained projection bytes plus
1247+
126,714 delta bytes against 65,536,000 bytes for one copy per commit.
1248+
1249+
This is still not completion of lane L. File and NoKV continue to retain and
1250+
decode their complete journal on every load, so bounded recovery is a property
1251+
of the embedded candidate rather than cross-provider parity; the SQLite profile
1252+
also still retains receipts and events without pruning. Unavailable logical/WAL
1253+
traffic and the <=15x cumulative write-growth budget, 1 MiB and 300k headroom,
1254+
full-domain workload, large-history recovery, fenced backup/restore, supported
1255+
upgrade/rollback, OS/runtime coverage and the >=10-day elapsed soak remain
1256+
holds, and runner completion cannot claim them. See the
12461257
[SQLite qualification commands](../../reference/sqlite-authority-store.md#reproduce-validation).
12471258
The public minimum remains Node 22.18 for File; SQLite additionally requires
12481259
synchronous finalization and the WAL-reset fix, with Node 22.22.3/SQLite 3.51.3

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -970,15 +970,23 @@ fencing/export 演练与 maintainer review 都通过才可晋升。发布紧凑
970970
| 新 Goal 默认决策(F) | 维护者接受合格 profile、canary 结果、运维诊断、backup/restore 流程、发布操作说明和关闭默认的路径;在独立且明确披露的发布改动中切默认。 | 仅适用于新建且符合条件的本地 Goal;已有显式 file 选择保持固定。不受支持的 runtime/filesystem 需显式选择支持方案,打开失败不能静默切 backend。 |
971971
| 已有 Goal 迁移与 file 退役 | 按已评审的 fenced workflow 逐批 opt-in 迁移,每批核对 receipt、历史、投影和回滚;删除路径前列清最后的 file-primary caller 与兼容窗口。 | 每个 Goal 需要明确迁移权限;证据满足后才退役常规 primary 角色。参考/导入/导出支持保留到其 caller 与保留责任分别结束。 |
972972

973-
**当前证据位置(2026-09-13 复核)。** #4121 已合并为
974-
`bde1632bb6f29aeb9a8b4ac23ead3e98ba2f2f55`,交付第一个候选节点;
975-
仍需 profile 资格化与晋级,不代表 lane L 完成。
976-
其 head pointer 有界,operation/cursor 查询有索引,但保留完整历史 projection,连续性
977-
校验还会统计覆盖索引,因此该成本随历史增长。它验证当前及访问到的 row digest,
978-
不是每次读取都审计全部历史 payload。资格入口现在区分小型 rehearsal 与显式
979-
64 KiB 10k/100k 存储轴,记录 p99/样本数、cold CLI、RSS 和 passed/failed/missing
980-
账本。逻辑/WAL 流量、完整领域负载、大历史恢复和自然时间 soak 的缺口仍阻止晋升,
981-
工具跑完不等于通过 <=2 增长预算或十天资格。参见
973+
**当前证据位置(2026-09-15 复核)。** #4121 已合并为
974+
`bde1632bb6f29aeb9a8b4ac23ead3e98ba2f2f55`,交付第一个候选节点。
975+
Lane L 中“保留存储有界”的那一半现在有可评审候选:`loopx_sqlite_authority_store_v2`
976+
改为每 64 次提交一个 checkpoint、每次提交一条精确 delta,不再为每一行保留一份完整
977+
projection;活跃头由 head 行、对应保留事务和游标连续性自证;单次历史读取最多重建一个
978+
窗口;完整 delta 链由 `verifyAuthorityHistory` 线性证明。cursor、operation id、commit
979+
digest、provider revision、receipt、event 与 scan 页面字节均未改变,已发布的版本 1
980+
数据库通过评审过的 `examples/coordination/sqlite-authority-migration.ts` 入口迁移。
981+
rehearsal 证据(1000 次提交/64 KiB)现为 16 个 checkpoint、63 次 replay 预算、
982+
单次历史读取最多重建一个 64 次提交窗口,保留 1,048,576 字节 projection 与
983+
126,714 字节 delta,而“每提交一份完整拷贝”为 65,536,000 字节。
984+
985+
这仍不代表 lane L 完成。File 与 NoKV 依旧在每次 load 时保留并解码完整 journal,
986+
因此“有界恢复”是内嵌候选 provider 的性质,不是跨 provider 等价;SQLite profile
987+
也仍不裁剪 receipt 与 event。逻辑/WAL 流量与 <=15x 累计写入预算、1 MiB 与 300k
988+
headroom、完整领域负载、大历史恢复、fenced backup/restore、受支持升级/回滚、
989+
OS/runtime 覆盖和 >=10 天自然时间 soak 仍是 hold,工具跑完不能声称已满足。参见
982990
[SQLite 验证命令](../../reference/sqlite-authority-store.md#reproduce-validation)。
983991
公开 Node 最低版本 22.18 继续用于 File;SQLite 另需同步 finalization 与 WAL-reset
984992
修复,参考组合为 Node 22.22.3/SQLite 3.51.3。Node 24 主 runtime 与 Node 26

‎docs/reference/sqlite-authority-store.md‎

Lines changed: 100 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
SQLite is an **opt-in local conformance candidate**, behind the existing
44
TypeScript `AuthorityStore` interface. File remains the default. This slice
5-
does not promote a goal, migrate existing authority, enable cross-host writes,
6-
or qualify ten elapsed days of operation.
5+
does not promote a goal, run a live cutover, enable cross-host writes, or
6+
qualify ten elapsed days of operation. It does provide the explicit
7+
version-1 to version-2 database migration described below.
78

89
## Placement and persistence
910

@@ -18,24 +19,37 @@ directory. Metadata binds the goal, schema version and random database
1819
incarnation. Provider revisions combine that incarnation with a monotonic
1920
integer sequence; they are not authority revisions or lease epochs.
2021

21-
The version-1 schema contains:
22+
The version-2 schema contains:
2223

2324
| Table | Contract |
2425
| --- | --- |
2526
| `metadata` | Version and database/goal identity |
26-
| `head` | One bounded pointer to the current committed projection |
27-
| `commits` | Unique operation ID, canonical commit digest, ordered cursor, original receipts, events and full projection |
27+
| `head` | The live committed projection, its state digest and one cursor |
28+
| `commits` | Unique operation ID, canonical commit digest, ordered cursor, original receipts and events, one exact state delta, its state digest and parent state digest |
29+
| `checkpoints` | One full projection and its digest per bounded window |
2830

2931
`commits` also serves as the durable projection outbox used by
3032
`scanCommitted`. There is no independent ACK or second receipt authority.
3133
Existing consumers resume by cursor. A unique operation index makes receipt
32-
lookup and cursor paging indexed. The common continuity check counts the compact
33-
covering index, so total read/write cost is not independent of history length.
34-
It does not deserialize the complete retained payload history. Historical receipts and full projections are retained without
35-
pruning. Fixed live state therefore produces linear database growth, not
36-
bounded total disk use. Growing application projections require separate
34+
lookup and cursor paging indexed.
35+
36+
Retention is bounded by window instead of by history: every commit keeps one
37+
exact delta, and one full projection is retained per checkpoint window
38+
(`authority_state_log.ts`, 64 commits per window). A live read resolves the head
39+
from the head row, its retained transaction and the cursor bounds; a historical
40+
read rebuilds at most one window from the covering checkpoint. Retained deltas
41+
are therefore the only part that still grows with history, and their size is
42+
proportional to what each commit changed. Original receipts and events are
43+
retained without pruning, so fixed live state with large receipts still grows
44+
with history. Growing application projections require separate
3745
retention/compaction work.
3846

47+
The rehearsal profile measures this profile directly: at 1,000 commits with a
48+
64 KiB projection, 16 checkpoints retain 1,048,576 projection bytes and 126,714
49+
delta bytes, against 65,536,000 bytes for one full copy per retained commit.
50+
Formal 10k/100k evidence still requires the separately authorized matched
51+
profile.
52+
3953
Writes use `BEGIN IMMEDIATE`, a five-second busy timeout, WAL and
4054
`synchronous=FULL`. The head, receipt, events and outbox row commit together.
4155
Before-COMMIT failures roll back; a COMMIT error reports an ambiguous outcome
@@ -49,18 +63,29 @@ rotation, corruption repair, or network-filesystem sharing is supported.
4963

5064
## Read integrity
5165

52-
Authority reads share one SQLite snapshot for metadata, head and requested rows.
53-
The same check runs inside the write transaction before any new commit row:
54-
positive unique integer cursors must have `min=1` and `count=max=head`. Thus a
55-
missing head, rolled-back head, or internal cursor gap is rejected as
66+
Authority reads share one SQLite snapshot, and writes run the same live proof
67+
inside their transaction before publishing a new commit row. The proof is
68+
layered so that each layer pays only for what it returns:
69+
70+
| Layer | Proves | Cost |
71+
| --- | --- | --- |
72+
| Live head (`loadAuthority`, `commitAuthority`) | Head row digest over the live projection, the retained transaction at that cursor reproducing its exact commit digest, parent linkage, `min=1`/`count=max=head` cursor continuity, and the presence of the checkpoint that covers the head | One head row, one retained row, one parent digest and index lookups; independent of retained history |
73+
| Materialized history (`scanCommitted`, `readReceipt`) | Every row from the covering checkpoint through the requested span, including each delta, state digest and parent lineage; paged scans also prove the lookahead row used for `has_more` | At most one checkpoint window plus the requested span |
74+
| Archive audit (`verifyAuthorityHistory`) | The complete delta chain from the empty root, every checkpoint against retained history, and the final state against the head | Linear in retained history; qualification and recovery only |
75+
76+
A missing head, rolled-back head, internal cursor gap, rewritten receipt/event,
77+
orphaned parent digest or mismatched state digest is rejected as
5678
`provider_protocol_violation` before returning authority or accepting a write.
79+
Preparing a commit also re-applies its own delta and requires byte equality with
80+
the committed projection before anything is written.
5781

58-
The newest row's canonical commit digest is recomputed on every authority read
59-
and write. Historical receipt reads additionally validate their selected row;
60-
paged scans validate each returned row and the lookahead row used for
61-
`has_more`. The digest includes the operation ID, projection, events, receipts
62-
and expected predecessor revision, reconstructed from the unchanged v0 sequence
63-
contract. No schema migration or alternate digest format is introduced.
82+
Two shapes are deliberately outside the live proof because the live head never
83+
reads them: the delta of the newest retained row, and the projection of the
84+
checkpoint the live head resumes from. Both are refused by every read that
85+
materializes their span and by the archive audit, and neither can change the
86+
authority value a live read returns. The commit digest is unchanged from v0
87+
(operation ID, projection, events, receipts, expected predecessor revision), so
88+
cursors, provider revisions and stored digests stay comparable.
6489

6590
This is integrity validation of the current and accessed evidence, not a full
6691
cryptographic audit of every historical payload on each call. Unaccessed older
@@ -192,6 +217,10 @@ npm ci --ignore-scripts
192217
npm run typecheck:control-plane
193218
node --no-warnings --experimental-sqlite --experimental-strip-types --test \
194219
tests/control_plane_ts/sqlite_authority_store.test.ts \
220+
tests/control_plane_ts/sqlite_authority_bounded_profile.test.ts \
221+
tests/control_plane_ts/sqlite_authority_migration.test.ts \
222+
tests/control_plane_ts/authority_state_log.test.ts \
223+
tests/control_plane_ts/authority_provider_parity.test.ts \
195224
tests/control_plane_ts/local_authority_provider.test.ts \
196225
tests/control_plane_ts/sqlite_runtime_admission.test.ts \
197226
tests/control_plane_ts/sqlite_capacity.test.ts
@@ -251,8 +280,42 @@ The real-process regressions exercise SIGKILL before and after business COMMIT,
251280
lost-response receipt readback, exact head/event/receipt/scan equivalence and
252281
SQLite `max_page_count` exhaustion. These are small disposable-database tests,
253282
not power-loss, operating-system ENOSPC or large-history recovery qualification.
254-
The source uses the shared retained-journal snapshot contract. No checkpoint,
255-
retention deletion, restore-incarnation change or migration format is added.
283+
Retention deletion, restore-incarnation change and cross-host sharing are still
284+
absent from this slice.
285+
286+
## Version-1 migration
287+
288+
The shipped version-1 database keeps one full projection per retained row, so it
289+
cannot be read by the version-2 provider. `sqlite_authority_migration.ts`
290+
migrates one Goal database in place: it reads the frozen version-1 rows, proves
291+
every stored commit digest, writes the checkpoint/delta log, proves that each
292+
written delta reconstructs its projection, requires the commit count to match,
293+
swaps tables and updates the schema version inside a single
294+
`BEGIN IMMEDIATE` transaction. Any failure rolls back and leaves version 1
295+
untouched; a second run reports `already_current`; a rewritten proof, a
296+
mismatched goal/incarnation or an existing swap target fails closed. Cursors,
297+
operation IDs, commit digests, provider revisions, receipts, events and scan
298+
pages are byte-identical after the migration.
299+
300+
A version-1 database that published only its schema and metadata — the state a
301+
goal leaves behind when it selected the provider and never committed — migrates
302+
to an equally empty version-2 database instead of failing, so the operator is
303+
never left with a database that neither provider accepts.
304+
305+
Run it from the repository checkout, with `--execute` omitted for a safe plan:
306+
307+
```sh
308+
node --no-warnings --experimental-sqlite --experimental-strip-types \
309+
examples/coordination/sqlite-authority-migration.ts \
310+
--directory "$RUNTIME_ROOT/authority/sqlite-v0" --goal-id example
311+
```
312+
313+
Add `--execute` (optionally with `--expected-identity`, which refuses a database
314+
whose stored incarnation is not the one the operator names) to migrate. The
315+
entry point rewrites only that Goal's database; it does not change provider
316+
selection, promote a goal, or enable cross-host writes. Keep the pre-migration
317+
database copy until the promoted Goal has been validated. A production cutover
318+
command, migration manifest and reverse export remain separate deliverables.
256319

257320
### Qualification holds / 资格保留项
258321

@@ -264,6 +327,12 @@ backup/restore, supported upgrades/rollback, OS/runtime coverage and a real
264327
>=10-day soak. Those holds still block profile promotion. Accelerated volume
265328
never substitutes for elapsed time, and running this command starts no soak.
266329

330+
Retained state is measured where the formal profile runs: an axis reports its
331+
checkpoint count, replay budget, recovery tail and retained projection/delta
332+
bytes, and `bounded_retained_state` compares them against one full copy per
333+
retained commit. That row stays `missing` for a rehearsal, exactly like every
334+
other formal budget.
335+
267336
SQLite 资格参考使用 Node 22.22.3/SQLite 3.51.3;打开前同时检查实际 WAL 修复版本
268337
和 statement 关闭行为。公开 Node 最低版本 22.18 继续用于默认 File 路径。显式
269338
SQLite 选择遇到不合格 runtime 会拒绝,不会改默认 provider 或静默回退。
@@ -278,3 +347,12 @@ RSS 和文件大小;没有量到的累计 WAL/逻辑写入和纯锁等待保
278347
耗尽、长期 consumer backlog 或完整恢复验证。首批测量允许保留 failed/missing;
279348
>=10 天自然时间 soak、迁移和晋升分别评审与授权。本入口不改变持久格式、Todo
280349
语义、默认 provider 或任何活跃 Goal。
350+
351+
版本 2 把“每个提交都保留一份完整投影”改成“每个窗口一个检查点 + 每提交一条精确
352+
delta”:活跃头读取只用自己的行、对应提交和游标连续性自证,历史读取最多重建一个
353+
窗口,完整归档由 `verifyAuthorityHistory` 线性审计。版本 1 数据库需要显式迁移
354+
(`examples/coordination/sqlite-authority-migration.ts`,默认只做 plan,`--execute`
355+
才写入,`--expected-identity` 可拒绝并非操作者所指的 incarnation,失败保持 v1
356+
原样)。只发布过 schema 与 metadata、从未提交的 v1 库会迁移成同样为空的 v2 库,
357+
不会让操作者落在两个 provider 都不接受的状态。迁移不改 cursor、operation id、
358+
commit digest、provider revision、receipt、event 或 scan 页面字节。

0 commit comments

Comments
 (0)