feat(storage): move desktop SQLite ownership into a storage utility process

Main no longer opens desktop business SQLite. A single utility-process
storage owner holds the Assistant, Knowledge, attachment, document-result,
remote-binding and native model-ledger databases and runs whole domain
transactions; Main and Runtime callers use explicit async domain operations.

- Remove the Main readWithFallback path and the supervision worker's
  separate business write connection; manifests are scanned read-only and
  committed by the sole writer.
- Remote events are ACKed only after commit; process loss reports
  unconfirmed writes instead of replaying them.
- Use native utilityProcess messaging with one in-flight bound (32) and
  ordered waiting instead of capacity rejection and retries.
- Start the storage process before the window so startup overlaps.
- Keep short tool output in memory and store full output once in owned
  storage; paging, branching, restart and deletion reuse that copy.
- Put required runtime launch files under goodbuddy-prefixed owned temp
  directories and reclaim confirmed stale ones; stop writing duplicate
  parsed.md.
- Add lint guards for SQLite ownership and architecture, concurrency
  acceptance fixtures, and maintenance/architecture documentation.

Validation: typecheck and lint pass. Alternating baseline (edf019aa) vs
candidate runs show no regression on single-session, C01, C02, C04 and a
5-minute C06; Main p99 drops from ~21 ms to ~16.7 ms under concurrency.
Fresh portable copy (1.75M events) upgrade content unchanged; real-model
output paging passes with 4 external requests. Full npm test runs had
1-9 different timing-sensitive failures per run that pass when rerun.
Not covered: C03 review publication, C07 source body, app-level C05.
This commit is contained in:
mesalogo
2026-10-06 15:40:18 +08:00
parent bc677a3718
commit d80d42b032
229 changed files with 13632 additions and 3800 deletions
+6 -6
View File
@@ -89,9 +89,9 @@ describe('Telegram delivery through loopback HTTP and SQLite', () => {
await vi.waitFor(async () => expect((await f.manager.snapshot()).telegram.status).toMatchObject({
state: 'error', lastError: expect.stringContaining('polling conflict')
}))
await vi.waitFor(() => expect(f.outbox.listUndelivered().every(entry => entry.state === 'terminal')).toBe(true))
await vi.waitFor(async () => expect((await f.outbox.listUndelivered()).every(entry => entry.state === 'terminal')).toBe(true))
expect(f.state.sends.map(send => send.text)).toEqual(['same-bot'])
expect(f.outbox.listUndelivered().map(entry => [entry.message.eventId, entry.state])).toEqual([['other-bot', 'terminal']])
expect((await f.outbox.listUndelivered()).map(entry => [entry.message.eventId, entry.state])).toEqual([['other-bot', 'terminal']])
expect(f.state.polls).toBe(0)
f.state.webhook = ''
await f.manager.reload('telegram')
@@ -107,7 +107,7 @@ describe('Telegram delivery through loopback HTTP and SQLite', () => {
f.state.sendErrors.push({ code: 403 })
f.inbound(1)
await f.manager.initialize()
await vi.waitFor(() => expect(f.outbox.listUndelivered()[0]).toMatchObject({ state: 'terminal', attempts: 1 }))
await vi.waitFor(async () => expect((await f.outbox.listUndelivered())[0]).toMatchObject({ state: 'terminal', attempts: 1 }))
await vi.waitFor(() => expect(f.state.polls).toBeGreaterThanOrEqual(2))
expect((await f.manager.snapshot()).telegram.status).toEqual({ state: 'running',
lastError: expect.stringMatching(/sending failed.*forbidden/i) })
@@ -129,7 +129,7 @@ describe('Telegram delivery through loopback HTTP and SQLite', () => {
await f.manager.initialize()
await vi.waitFor(() => expect(f.state.sends).toHaveLength(5), { timeout: 10_000 })
expect(f.state.sends[4]!.time - f.state.sends[3]!.time).toBeGreaterThanOrEqual(2995)
await vi.waitFor(() => expect(f.outbox.listUndelivered()).toEqual([]))
await vi.waitFor(async () => expect(await f.outbox.listUndelivered()).toEqual([]))
expect(f.state.sends.every(send => send.text === 'synthetic result')).toBe(true)
expect(f.execute).toHaveBeenCalledOnce()
} finally { await f.close() }
@@ -144,10 +144,10 @@ describe('Telegram delivery through loopback HTTP and SQLite', () => {
await f.manager.initialize()
await vi.waitFor(() => expect(f.state.sends).toHaveLength(4), { timeout: 5000 })
await f.manager.stopAll()
expect(f.outbox.listUndelivered()[0]).toMatchObject({ state: 'pending', attempts: 0 })
expect((await f.outbox.listUndelivered())[0]).toMatchObject({ state: 'pending', attempts: 0 })
expect(f.state.sends).toHaveLength(4)
await f.manager.initialize()
await vi.waitFor(() => expect(f.outbox.listUndelivered()).toEqual([]))
await vi.waitFor(async () => expect(await f.outbox.listUndelivered()).toEqual([]))
expect(f.state.sends).toHaveLength(5)
expect(f.execute).not.toHaveBeenCalled()
} finally { await f.close() }