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
+16 -5
View File
@@ -1,8 +1,7 @@
// @vitest-environment node
import { spawn } from 'node:child_process'
import { createRequire } from 'node:module'
import { copyFile, mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { copyFile, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { createServer } from 'vite'
import react from '@vitejs/plugin-react'
@@ -12,10 +11,14 @@ import { AssistantDatabase } from '../src/main/assistant/assistant-database'
import { SupervisionStoryStore } from '../src/main/assistant/supervision-stories'
import { SupervisionExperienceStore } from '../src/main/assistant/supervision-experiences'
it(process.env.GOODBUDDY_SUPERVISOR_SIDEBAR
it(process.env.GOODBUDDY_STORY_GRAPH_FOCUS
? 'focuses the production 3D story camera through canvas and sidebar selection'
: process.env.GOODBUDDY_SUPERVISOR_SIDEBAR
? 'renders the production supervision card at narrow widths in both themes'
: 'renders the production supervisor at desktop, narrow, and mobile widths', async () => {
const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-supervisor-'))
const temporaryRoot = resolve('temp/story-graph-focus')
await mkdir(temporaryRoot, { recursive: true })
const directory = await mkdtemp(join(temporaryRoot, 'supervisor-'))
// Optional local acceptance run against an online backup. Never initialize/migrate or run models.
let portable: string | undefined
if (process.env.GOODBUDDY_SUPERVISOR_BACKUP) {
@@ -48,6 +51,11 @@ it(process.env.GOODBUDDY_SUPERVISOR_SIDEBAR
{
name: 'supervisor-fixture',
configureServer(server) {
if (process.env.GOODBUDDY_STORY_GRAPH_FOCUS) server.middlewares.use('/focus-export.json', async (_request, response) => {
const source = await readFile('docs/features/story-graph/3d-demo-data.js', 'utf8')
response.setHeader('Content-Type', 'application/json')
response.end(source.slice(source.indexOf('window.STORY_DATA = ') + 'window.STORY_DATA = '.length).trim().replace(/;$/, ''))
})
if (portable) server.middlewares.use('/portable-review.json', (_request, response) => {
response.setHeader('Content-Type', 'application/json')
response.end(portable)
@@ -73,6 +81,7 @@ it(process.env.GOODBUDDY_SUPERVISOR_SIDEBAR
await server.listen()
const env: NodeJS.ProcessEnv = {
...process.env,
TEMP: directory, TMP: directory, TMPDIR: directory,
GOODBUDDY_SUPERVISOR_URL:
server.resolvedUrls!.local[0] + 'supervisor.html' + (process.env.GOODBUDDY_SUPERVISOR_SIDEBAR ? '?sidebar=1' : ''),
GOODBUDDY_SUPERVISOR_DIRECTORY: directory,
@@ -89,6 +98,7 @@ it(process.env.GOODBUDDY_SUPERVISOR_SIDEBAR
driver
)
await copyFile(resolve('tests/support/supervisor-selection-driver.mjs'), join(directory, 'supervisor-selection-driver.mjs'))
await copyFile(resolve('tests/support/story-graph-focus-driver.mjs'), join(directory, 'story-graph-focus-driver.mjs'))
const child = spawn(createRequire(import.meta.url)('electron'), [driver], {
env,
stdio: ['ignore', 'pipe', 'pipe']
@@ -107,7 +117,8 @@ it(process.env.GOODBUDDY_SUPERVISOR_SIDEBAR
child.once('error', reject)
})
expect(code, output).toBe(0)
console.log(output)
if (process.env.GOODBUDDY_STORY_GRAPH_FOCUS) process.stdout.write(output)
else console.log(output)
} finally {
clearTimeout(timeout)
}