Files
goodbuddy/docs/architecture/desktop-storage-runtime-integration.md
T
mesalogo d80d42b032 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.
2026-10-06 15:40:18 +08:00

97 lines
5.2 KiB
Markdown

# Desktop Runtime Storage Integration
S05 domain implementation and production composition, 2026-10-05. This supplies
the runtime domain for the [desktop storage foundation](./desktop-storage-foundation.md).
The current composition passes the storage-owned binding store and model-call
ledger factory to Main runtime services. Full desktop migration and performance
acceptance remain incomplete.
## Integration API
`src/main/desktop-storage-runtime-operations.ts` exports:
- `DesktopStorageRuntimeOperations`: the existing `RuntimeSessionBindingStore`
interface plus named ledger lifecycle and transition operations.
- `runtimeStorageMethods`: the explicit host dispatch allowlist.
- `DesktopStorageRuntimeOwner.open(userDataPath)`: creates the sole binding owner
at `remote-runtime-bindings.sqlite`. Await it before publishing storage readiness.
Native ledgers open lazily at their existing paths through `openLedger(id, path)`.
- `createDesktopRuntimeStorageAdapters(call)`: Main adapters returning
`bindingStore` and `openModelCallLedger`. `call` has the same domain/method/tuple
shape as `DesktopStorageClient.call`, restricted to the `runtime` domain.
- `DesktopStorageRuntimeOwner.close()`: closes remaining native ledgers and the
binding database. The outer storage owner must first drain admitted operations.
This method is not part of the external domain allowlist.
The storage-core integration adds `runtime: DesktopStorageRuntimeOperations` to
`DesktopStorageDomains`, initializes one runtime owner from the existing user-data
directory, and dispatches allowed runtime methods against that owner. Include its
close in the storage owner's release path, including startup failure. No new RPC
client, SQL endpoint, admission queue or process is needed.
After registering the domain, Main composition is:
```ts
await storage.ready
const runtimeStorage = createDesktopRuntimeStorageAdapters(
(domain, method, args) => storage.call(domain, method, args)
)
// ManagedRemoteExecutionServices options:
// bindingStore: runtimeStorage.bindingStore
// The former userDataPath option is removed; the host owns that path.
// NativeClientCoordinator, NativeTerminalClient and NativeDshWebClientService options:
// openModelCallLedger: runtimeStorage.openModelCallLedger
```
`ManagedRemoteExecutionServices` borrows the binding interface and does not close
it on disposal. Native clients stop their producers and proxy, await their ledger
close, then delete launch material. DS Web retains native history. A failed or
unconfirmed close retains files and propagates the error. A lost open reply also
retains files because the host may still own the connection. Shutdown must stop
native/remote producers before closing the shared storage client.
## Ordering And Ownership
The shared `ModelCallLedger` type allows synchronous or Promise-returning methods;
the concrete `AgentModelCallLedger` stays synchronous and remains the remote
Agent's owner. Schema, database names, call identities, retention and transaction
algorithms are unchanged.
`AgentModelGateway` awaits claim before provider dispatch, complete before returning
the response, delivered before resolving the delivery acknowledgement, and
outcome-unknown before reporting provider failure. Cancellation while claim is
pending does not dispatch after claim completes. Persistence rejection propagates;
the gateway does not replay a provider request after an unconfirmed write.
## Validation
The final focused run passed 113 tests across eight files, with 11 existing opt-in
cases skipped. It included the official DS Web process using standard Node,
binding uniqueness/rotation/cursor persistence, concrete ledger reopen and uncertain
outcomes, delayed/rejected persistence, and both native clients' close ordering.
`desktop-storage-runtime-operations.test.ts` also builds real Electron fixtures.
The production native terminal loopback gateway uses Main adapters and the real
`DesktopStorageClient`/transport to reach a utility-process runtime owner, then
closes its ledger before removing launch files. Production core registration is
covered by `src/main/index.ts`; the fixture remains focused on the runtime domain.
Its model response is local and deterministic, with no external provider call.
The shared gateway was separately validated on the pinned Linux x64 test Host via
the existing lifecycle harness rebuilt from current source. OpenCode ran a bounded
tool command in a dedicated `/root/tmp/gb-lifecycle-*` workspace. Desktop detached,
the test Agent was promoted, the controller resumed, and the prompt completed.
There were exactly **2 real text-provider calls**, both HTTP 200, **0 image calls**,
33 persisted event entries, 17 checkpoints, and successful replay deduplication.
The old test daemon retired; final owned process count was zero. The harness removed
its remote directories. Local artifacts were isolated in
`temp/goodbuddy-ledger-upgrade` and cleaned after validation.
Scoped ESLint and `git diff --check` passed. The current composition supplies
`bindingStore` to `ManagedRemoteExecutionServices` and `openModelCallLedger` to
the native coordinator. Whole-application acceptance, packaged cross-platform
checks and concurrent performance measurements are not established by these
focused tests.