Skip to content

[verify] bootStack({ databaseFile }) 的数据活不过进程内重启 —— 表在、行没了,挡住冷启动类 e2e #4518

Closed

Description

@os-zhuang

在做 #4470(让验证 harness 走真实持久化路径)时发现的。它挡住了 #4470「最小形态」的第三条,所以单独记录 —— packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts 的文件头、以及 @objectstack/verify 的 changeset 都引用了这条。

问题

#4470 给 bootStack 加了 databaseFile,本意是让两次顺序启动共享同一个 SQLite 文件,从而构成一次真正的冷启动(ADR-0019 的挂起 run 必须活过的那种重启)。实际行为是:第二次 bootStack 打开同一个文件,表结构在,行没了。

关键点是这与挂起 run 无关 —— 普通业务记录同样不保留。所以归因在 harness/驱动这一层,不在挂起存储。

复现

const dir = mkdtempSync(join(tmpdir(), 'probe-'));
const dbFile = join(dir, 'v.sqlite');

const a = await bootStack(someStack, { automation: true, databaseFile: dbFile });
const ta = await a.signIn();
await a.apiAs(ta, 'POST', '/data/suspend_note', { name: 'persisted-me', status: 'new' });
await a.stop();                       // kernel shutdown

const b = await bootStack(someStack, { automation: true, databaseFile: dbFile });
const tb = await b.signIn();
await b.apiAs(tb, 'GET', '/data/suspend_note?limit=10');

实测输出:

PROBE hot row status         200                      ← 第一个进程里读得到
PROBE file exists            true 1073152             ← 文件确实在,1MB
PROBE cold automation_run    {"records":[],"total":0}
PROBE cold suspend_note      {"records":[],"total":0} ← 普通记录也没了

两次运行文件大小完全一致(1073152),像是在某个早期时点(schema sync 之后)写过一次就再没更新。表能查到 200(不是 no such table),说明第二次启动确实读到了那个文件 —— 只是里面没有后来写入的行。

线索

  • packages/plugins/driver-sqlite-wasm/src/sqlite-wasm-driver.ts —— persist 默认 'on-disconnect':只在驱动 disconnect 时把 sql.js 的内存镜像刷回磁盘(外加一个 process.once('beforeExit') 的兜底 flush)。
  • packages/runtime/src/default-datasource-plugin.ts —— destroy() 会 connection.disconnect('default', { asDefault: true }),看起来应该触发那次 flush。
  • packages/verify/src/harness.ts —— stop() 关掉 http server 后调用 kernel.shutdown();日志里确实出现 Graceful shutdown started / ✅ Graceful shutdown complete。

所以从代码看这条链路本该 flush,但结果表明没有(或者 flush 发生得太早)。没有继续深挖,因为这已经在 #4470 的范围之外了。

注意 beforeExit 兜底对进程内重启是无效的:进程根本没退出。objectstack dev 这类「跑到被杀」的用法因此不受影响,这也可能是它一直没被发现的原因。

影响

  1. 挡住 [verify] 端到端验证从构造上绕开了挂起 run 的持久化路径(harness 写死 suspendedRunStore: 'memory') #4470 的第三条最小形态 —— 「冷启动一个新 kernel,resume 能继续走对分支」目前无法在 dogfood 层断言。已在测试文件头标注为 KNOWN GAP,没有用放松断言的方式假装通过。
  2. 任何想验证「跨进程状态存活」的 e2e 都会撞上同一堵墙 —— 而这正是持久化类缺陷([automation/approvals] 进程重启后审批决策静默失效:挂起 flow run 仍只存内存(#1518 标记 COMPLETED 但 17.0.0-rc.1 未生效),approve 落库却永不推进且零报错 #4420 那一类)最需要被覆盖的维度。

期望

bootStack({ databaseFile }) 之后的 stop() 应当保证数据已落盘,使下一次 bootStack 是一次真实的冷启动。可能的方向(未验证,留给认领者判断):

  • 查清 stop() → kernel.shutdown() → 插件 destroy() → 驱动 disconnect() 这条链在 harness 里是否真的走全了;
  • 或让 harness 在 stop() 里显式调用驱动已有的 flush();
  • 或在 databaseFile 模式下改用更即时的 persist 策略(如 debounced:*)。

修好之后,flow-durable-suspend.dogfood.test.ts 最自然的下一个用例就是它最初被写出来的那个:挂起 → 关停 → 冷启动 → resume 走对分支。

参考

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 2, 2026
  2. self-assigned this
    on Aug 2, 2026
  3. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    认领:PM 循环第 1 轮(/pm-dispatch 会话)
    分支:claude/issue-4518-bootstack-durable-flush
    Worktree:objectstack-issue-4518
    开发 agent 即将开工;如另有会话已在做本 issue,请以更早的认领评论为准,本认领让行。


    Generated by Claude Code

  4. os-zhuang commented on Aug 2, 2026

    @os-zhuang
    ContributorAuthor

    PM 验收:ACCEPT → PR #4564(等 CI 全绿后加入 merge queue)。

    根因不在 issue 预判的三个方向,而在 driver-sqlite-wasm 的 Knex 方言:_query 按「是否返回行」选执行分支、只在无行分支标脏,而 ObjectQL 的写入形状正是 INSERT … RETURNING *——几乎全部业务写入都走了不标脏的分支,on-write 与 on-disconnect 两种策略共用同一个 dirty 标志,一起丢写。修复为结构性收敛:「语句是否改库」只有一个判定点(statementMutatesDatabase,同时看 method 与 SQL 文本),执行与持久化两段分离;markDirty() 去参数化,消除双判定点。stop() 返回 ⇒ 落盘,自此是结构保证。dogfood 的 KNOWN GAP 冷启动套件解封:挂起 → 关停 → 冷启动 → resume 走对分支 → 第三次启动仍可读。回滚验证 10 条测试变红。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions