Skip to content

refactor(core): cursor decode into core, queries and caches host-side (phase 8, stateful tier) - #840

Merged
iamtoruk merged 1 commit into
feat/core-extractionfrom
phase8/stateful-cursor
Jul 27, 2026
Merged

refactor(core): cursor decode into core, queries and caches host-side (phase 8, stateful tier)#840
iamtoruk merged 1 commit into
feat/core-extractionfrom
phase8/stateful-cursor

Conversation

@iamtoruk

Copy link
Copy Markdown
Member

Phase 8 stateful tier (tracking #809), second of three: cursor. The pure decode over all three emit arms (per-bubble, per-conversation-input, agentKv-only) moves into @codeburn/core as tagged records; the five sqlite query families keep their lazy order and exact non-uniform failure semantics host-side (bubble-query failure still early-returns AND writes the empty cache), along with blobToText, workspace routing, paging/cap, resolveModel/CURSOR_COST_MODEL, and the results cache.

The 19-key emitted shape is a cache-compatibility contractCURSOR_CACHE_VERSION stays 6, so live caches on users' disks must read back shape-identical. The goldens gate it with explicit sorted-key assertions; validation proved by mutation that those key-set gates (not toEqual, which is blind to present-with-undefined keys) are what hold the contract, and strengthened the cache round-trip golden to toStrictEqual after showing its claimed guarantee was false as written.

Flow: Opus spec (18-hazard checklist) → Kimi in checkpointed steps (12 goldens verified 12/12 pre-migration; core module; CLI conversion) after an earlier monolithic draft attempt wedged and was discarded → Opus validation with oracle restoration (54/54 identical under the pre-migration original) and mutation batteries (queue-pop-before-skip and key-burn-before-timestamp-check both killed).

Also surfaced and documented: composer ids flow verbatim into envelope deduplicationKeys by design (they are client-generated UUIDs; identifier-class fields are exempt from secret-planting per the established convention, reasoning in the smuggling block) — uniform envelope-side dedup-key hashing is logged as a schema-wide follow-up, not a cursor-local change. Validator additionally fixed an env-var leak between test files, restored condensed must-move-verbatim comments, and relocated the smuggling block to content-smuggling.test.ts matching all 25 other providers.

Gates: core 465/465; CLI suite and root npm test 2440 passed, 5 skipped; the five pre-existing cursor test files byte-identical; tsc --noEmit clean; no cache/parse-version changes.

Known wart (pre-existing, flagged): cursor-cache.ts ignores CODEBURN_CACHE_DIR (uses homedir() directly).

… (phase 8, stateful tier)

Cursor is the bucket-D stateful multi-store provider: it keeps its bespoke
adapter (no createBridgedProvider) and the host retains every store read.

Tagged-record composition over five query families. The host issues all five
queries in the load-bearing order [1] composerData meta, [2] bubble COUNT,
[3] bubbles (paged or since), [4] agentKv, [5] user messages, preserving the
per-query degradation semantics: [1]/[4]/[5] degrade to empty, [2] to total=0,
and a [3] failure early-returns zero calls while still writing the (empty)
cache and skipping [4]/[5]. decodeCursor() receives the four row sets plus the
host-supplied agentKv timestamp and performs the whole stitched pass — the
pre-pass scan, the agentKv fold, the user-message queue, and all three emit
arms (per-bubble, per-conversation input, stream-only).

The 19-key emitted call shape is a cache-compatibility contract. CURSOR_CACHE_VERSION
stays 6 and PROVIDER_PARSE_VERSIONS['cursor'] is unchanged, so existing
cursor-results.json files on disk are replayed through the new path; adding,
dropping, or undefined-ing any key would poison them. The goldens gate the key
set with Object.keys() on every arm, which is what catches a key present with an
undefined value — toEqual cannot see one, and JSON.stringify drops exactly those
on the way into the cache.

Pricing, bash base-name extraction, display names, project attribution, SQL,
paging, env reads and clock reads all stay host-side; toProviderCall is the
single mapper that re-adds costBasis 'estimated', costIsEstimated true and the
resolved pricing model. No costUSD is ever emitted.

Composer-id / dedup-key envelope finding: composer ids and request ids flow into
sessionId and into the envelope's dedupKeys by design, exactly like every other
provider's machine identifiers, so the content-smuggling block deliberately does
not plant a secret there. Hashing dedup keys uniformly is a schema-wide
follow-up, not a cursor-local change.

Validator fixes on top of the migration:
- relocated the content-smuggling block from cursor-decode.test.ts into
  content-smuggling.test.ts, matching all 25 other providers, and added a probe
  proving each planted secret actually reaches the field it guards
- G11 now gates the undefined-key cache contract it claimed to (toEqual passed
  under an injected extra undefined key; toStrictEqual plus key-set assertions
  catch it)
- restored the CODEBURN_CURSOR_MAX_BUBBLES override in the golden's afterEach so
  G12 cannot leak the scan budget into cursor-large-db-cap.test.ts
- restored the comment rationales that must travel with their code verbatim
  (pos-cursor queue performance note, parseComposerIdFromKey CR/LF signature,
  agentKv stream and pending-flush notes, dedup-key history, costIsEstimated)

The 12 goldens were cross-checked against the pre-migration provider by
restoring it in place: they pass identically on both sides. H2 (queue pop before
skip) and H4 (arm B dedup-key burn before the timestamp check) were mutation
tested and are killed by G8 and C6 respectively.
@iamtoruk
iamtoruk merged commit 74741a8 into feat/core-extraction Jul 27, 2026
4 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.

1 participant