Skip to content

fix(storage,ai,gemini,llamacpp): shared-connection safety + bulk BigInt + Gemini NOT_FOUND + checkpoint scoping - #657

Merged
sroussey merged 6 commits into
mainfrom
claude/dazzling-archimedes-o87xem
Jul 24, 2026
Merged

fix(storage,ai,gemini,llamacpp): shared-connection safety + bulk BigInt + Gemini NOT_FOUND + checkpoint scoping#657
sroussey merged 6 commits into
mainfrom
claude/dazzling-archimedes-o87xem

Conversation

@sroussey

Copy link
Copy Markdown
Collaborator

Summary

  • Plan A (storage — shared-connection concurrency, CRITICAL/HIGH): Extract a module-level ConnectionMutex (packages/storage/src/tabular/ConnectionMutex.ts) keyed on the connection handle, backed by a lazy-loaded node:async_hooks AsyncLocalStorage (browser bundle degrades to a synchronous shim). BaseSqlTabularStorage gains a connectionHandle() hook; SqliteTabularStorage, SqliteAiVectorStorage, and PostgresTabularStorage (PGlite / PGLitePool branch only) opt in so two storages that share a Database / PGlite session queue on one chain, same-instance re-entry runs inline via ALS, and cross-instance re-entry throws ConnectionReentryError instead of hanging. Also fixes an existing latent bug where Sqlite.Database's inTransaction check missed the wrapper — the code now consults our own this.inTransaction flag as well, unblocking putBulk via the tx proxy.
  • Plan B (storage — BigInt-safe bulk fingerprint, HIGH): New pkFingerprint() (packages/storage/src/tabular/pkFingerprint.ts) tags bigint and Uint8Array with { __t: "n" | "u8", v: … } while every other input shape stays byte-identical to the previous JSON.stringify output. BaseSqlTabularStorage.bulkKeyOf and SupabaseTabularStorage.primaryKeyFingerprint route through it so BigInt / byte-key primary keys no longer collide in the runBulkPut dedup map or Supabase's response alignment.
  • Plan C (gemini — NOT_FOUND matcher two-tier, HIGH): Deferred. providers/google-gemini/src/ai/common/Gemini_CachedContentFallback.ts does not yet live on main — it is on the ai-provider-cache-checkpoints feature branch — so the matcher rewrite has no target file. Landed a describe.skip("TODO — Plan C: …") module (packages/test/src/test/ai-provider/Gemini_CachedContentFallback.test.ts) enumerating the five assertions to enable once the fallback file merges to main.
  • Plan D (util,llamacpp — checkpoint tenant scope + per-session lock, 2× HIGH): Partial. Added readonly id: string = uuid4() to ResourceScope with tenant-scope tsdoc. Added a sessionId-keyed promise-chain withSessionLock helper on providers/node-llama-cpp/src/ai/common/LlamaCpp_Runtime.ts (map entry cleaned up in finally so a thrown body does not strand it). Deferred the rest of Plan D — CheckpointEntry.scopeToken, validateParentCheckpoint / resolveCheckpointSession / finalizeEmittedCheckpoint / ResolvedCheckpoint signature changes, the CacheCheckpointTask / TextGenerationTask / ToolCallingTask / AiChatTask caller threading, and the LlamaCpp keep-parent / supersede branches — because packages/ai/src/provider/CheckpointRegistry.ts, packages/ai/src/task/base/CheckpointPorts.ts, CacheCheckpointTask.ts, and the checkpoint-consumption blocks in LlamaCpp_TextGeneration.ts / LlamaCpp_ToolCalling.ts all live on the ai-provider-cache-checkpoints feature branch and are not present on main. Re-open once that branch merges.

Test plan

  • bunx vitest run packages/test/src/test/storage-tabular/ — 1345 passed / 14 skipped (Plan A + Plan B)
  • bunx vitest run packages/test/src/test/storage-tabular/SqliteTabularStorage.integration.test.ts — new: sibling single-op blocks across a tx on the shared handle, cross-instance nested withTransaction throws ConnectionReentryError under 500 ms, same-instance tx-proxy nested writes still work, BigInt PK alignment + last-wins dedupe
  • bunx vitest run packages/test/src/test/storage-tabular/PostgresTabularStorage.integration.test.ts — new: shared-connection safety on the PGlite path (sibling block + cross-instance ConnectionReentryError)
  • bunx vitest run packages/test/src/test/storage-tabular/pkFingerprint.test.ts — unit-level BigInt / Uint8Array / plain-value fingerprint distinctness (5 tests)
  • bunx vitest run packages/test/src/test/resource/ResourceScope.test.ts — new: id is minted, distinct per instance, stable across dispose (10 tests total)
  • bunx vitest run packages/test/src/test/ai-provider-nodellama/LlamaCpp_Runtime.test.ts — new: withSessionLock serializes same-session callers, unrelated sessions run in parallel, lock releases even when the body throws (29 tests total)
  • bunx vitest run packages/test/src/test/ai-provider/Gemini_CachedContentFallback.test.ts — skip block loads cleanly
  • bun run build:types (40 packages, all cache-miss on this branch) — clean

Deferrals

  • Plan C in full and most of Plan D are gated on files that only exist on the ai-provider-cache-checkpoints feature branch. See the summary bullets for the exact seam list. Re-open once that branch merges into main; the skipped Gemini test module holds the assertion checklist.

Generated by Claude Code

claude added 4 commits July 23, 2026 08:48
…andle

Two storage instances that were handed the same SQLite `Database` or PGlite
session share every write path but never saw each other's per-instance
mutex, so an unrelated caller could slip a statement between another
instance's `BEGIN` and `COMMIT` on the shared connection. Extract a
`ConnectionMutex` keyed on the connection handle so every instance that
binds the same handle queues on one chain, and use `AsyncLocalStorage`
to detect same-instance re-entry (inline, no chain wait) versus
cross-instance re-entry (`ConnectionReentryError`, no hang).

Co-Authored-By: Claude <noreply@anthropic.com>
`JSON.stringify` silently drops `bigint` values and stringifies
`Uint8Array` as its index-keyed object form, so two rows with a
distinct BigInt or byte-string primary key produced identical
fingerprints in `runBulkPut`'s dedupe map and Supabase's response
alignment. Replace the direct `JSON.stringify` with a `pkFingerprint`
helper that tags `bigint` (via `toString()`) and `Uint8Array` (via hex)
so those primary keys stay distinct while every other input shape
serializes byte-identically to today.

Co-Authored-By: Claude <noreply@anthropic.com>
…k file

The Gemini NOT_FOUND matcher two-tier work targets
`providers/google-gemini/src/ai/common/Gemini_CachedContentFallback.ts`,
which does not yet live on `main` — the cache-checkpoint feature branch
carries it. Land a skipped test module now so the intended assertions
are queued next to the file that will host them, and re-enable once the
fallback file merges into `main`.

Co-Authored-By: Claude <noreply@anthropic.com>
Add a stable `id` on `ResourceScope` so downstream tenant-scoped resource
caches can gate re-use by the mint-site scope's identity, and a
sessionId-keyed `withSessionLock` helper on the node-llama-cpp runtime
so two concurrent keep-parent checkpoint consumers of one cached chat
session can serialize their `session.prompt` / `LlamaChat.generateResponse`
calls (and rewind KV between them) without overlapping on the sequence.
The lock is session-scoped so unrelated sessions still run in parallel,
and releases in `finally` so a thrown body does not strand the map entry.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 63.15% 27533 / 43593
🔵 Statements 63.02% 28540 / 45283
🔵 Functions 63.63% 5260 / 8266
🔵 Branches 51.88% 13584 / 26183
File CoverageNo changed files found.
Generated in workflow #2781 for commit 0dd2a12 by the Vitest Coverage Report Action

…ctionReentryError

Two follow-ups on the ConnectionMutex introduced in the previous commit.

F1 (browser ALS shim cannot detect cross-instance re-entry across await):
the ALS-first check missed a legitimate cross-instance sibling call whenever
the shim reset its `current` at the first `await`, so a sibling call on a
different storage instance could sneak into another's BEGIN/COMMIT window
on the browser bundle. Move the `state.txOwner` check ahead of `ensureAls`
so the throw path is synchronous and ALS-independent; ALS survives only as
an inline optimization on Node (browser shim falls back to chain-wait for
same-instance re-entry, which the sanctioned tx-proxy path avoids anyway).
Add a `classifyReentry(state, owner, alsStore)` helper and a
`__resetAlsForTesting(useShim?)` export so the test suite can exercise
both the real ALS and the shim paths.

F2 (ConnectionReentryError gave no migration path): thread a
`mode: "sibling-op" | "nested-transaction"` discriminator onto the error
and rewrite the message to a multi-line block naming both callers, the
mode, and the three supported refactors: (a) route the sibling through
the `tx` proxy, (b) open a SAVEPOINT on `tx` for a nested rollback
boundary, (c) collapse to a single storage instance or give each its own
connection. `runOnConnection` throws with `"sibling-op"`,
`runInTransactionOnConnection` with `"nested-transaction"`.

Tests: new `ConnectionMutex.test.ts` covers F1 across the real-ALS and
browser-shim paths (via `__resetAlsForTesting(true)`), asserting the
throw path is synchronous on both and same-instance nested inlines under
real ALS. Sqlite and Postgres integration tests replace the earlier
"sibling blocks" contract with the new "sibling throws" contract and
assert the F2 mode/message shape for both sibling-op and
nested-transaction reach-throughs; the same-instance tx-proxy regression
check is preserved (and added to the Postgres suite for parity).

Co-Authored-By: Claude <noreply@anthropic.com>
…3e56is

fix(storage): browser-safe cross-instance re-entry + actionable ConnectionReentryError
@sroussey
sroussey merged commit e67ed25 into main Jul 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants