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:
@@ -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() }
|
||||
|
||||
Reference in New Issue
Block a user