Skip to content

refactor(storage): establish canonical Bun schema and DuckDB dialect - #1343

Open
mariusvniekerk wants to merge 19 commits into
mainfrom
t3code/bun-storage-foundation
Open

refactor(storage): establish canonical Bun schema and DuckDB dialect#1343
mariusvniekerk wants to merge 19 commits into
mainfrom
t3code/bun-storage-foundation

Conversation

@mariusvniekerk

@mariusvniekerk mariusvniekerk commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

SQLite, PostgreSQL, and DuckDB previously evolved independent schema and query foundations, so parity depended on duplicated declarations and backend-specific plumbing. This first stack layer establishes one Bun model registry, guarded backend handles, and a dedicated DuckDB dialect while converging SQLite and PostgreSQL in place under their existing serialization boundaries.

Stamped schemas now validate structural, index, and trigger drift without repair; expensive row invariants remain limited to one-time convergence. Shared timestamps preserve the shipped SQLite empty sentinel and RFC3339Nano text contract, and Quack rejects direct driver arguments that would otherwise be discarded.

Direct database/sql access remains limited to lifecycle, connection-local, transport, and compatibility seams. DuckDB remains a disposable mirror, while narrowly scoped FTS and vector behavior stays adapter-specific. Stack 1 of 5.

generated by a clanker

@roborev-ci

roborev-ci Bot commented Aug 5, 2026

Copy link
Copy Markdown

roborev: Combined Review (ce3270c)

Medium-severity schema compatibility issues remain in the storage refactor.

Medium

  • internal/db/bunmodel/identity.go:72 — The canonical worktree mapping model generates TIMESTAMP DEFAULT CURRENT_TIMESTAMP columns in DuckDB, while Quack attach rejects catalogs containing dynamic timestamp defaults. Schema v10 mirrors therefore break remote Quack access.

    • Fix: Remove these defaults for DuckDB and explicitly supply created_at and updated_at in mirror writers, or strip the defaults in the DuckDB schema adapter.
  • internal/postgres/schema.go:1414 — PostgreSQL convergence rejects sessions with empty source provenance without first migrating legacy rows. Databases upgraded from versions predating these columns receive empty defaults and fail EnsureSchema, blocking the push that would populate them.

    • Fix: Backfill legacy provenance where ownership is known, or allow legacy rows through convergence and require a full push before stamping compatibility.

Reviewers: 2 done | Synthesis: codex, 13s | Total: 19m21s

@mariusvniekerk mariusvniekerk self-assigned this Aug 5, 2026
@roborev-ci

roborev-ci Bot commented Aug 5, 2026

Copy link
Copy Markdown

roborev: Combined Review (7899144)

The refactor has two medium-severity data-integrity issues; no security regressions were identified.

Medium

  • internal/duckdb/push.go:829 — Pin ID reuse can abort curation refreshes. Curation refresh upserts pins before deleting stale rows. SQLite may reuse a deleted highest pin ID, causing a new pin to collide with a stale DuckDB primary key despite having a different (session_id, ordinal). Delete stale pins before inserting current pins, or avoid using source pin IDs as DuckDB primary keys. Add a regression test for SQLite ID reuse.

  • internal/postgres/schema.go:1349 — Schema convergence omits canonical foreign keys. Legacy tables already exist when CreateCommonSchema runs, so CREATE TABLE IF NOT EXISTS cannot add constraints such as the composite message/tool-call relationships. CheckCommonSchema can nevertheless stamp the schema as compatible. Add idempotent ALTER TABLE ... ADD CONSTRAINT migrations for all canonical foreign keys and verify them through pg_constraint before writing the compatibility stamp.


Reviewers: 2 done | Synthesis: codex, 12s | Total: 20m2s

@mariusvniekerk
mariusvniekerk force-pushed the t3code/bun-storage-foundation branch from 7899144 to 0a688f7 Compare August 7, 2026 03:46
@roborev-ci

roborev-ci Bot commented Aug 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (0a688f7)

High-severity PostgreSQL schema convergence issue and two medium-severity migration gaps must be addressed before merge.

High

  • internal/postgres/schema.go:2655pushSchemaCurrent accepts pre-change PostgreSQL schemas without validating the common-schema stamp, newly required columns, or native pricing timestamp types. A normal pg push can therefore skip convergence and leave updated_at as TEXT, causing later pricing updates that use timestamp/interval operators to fail.
    • Fix: Require bun_common_schema_v1, then validate the common schema and pricing column types before taking the fast path.

Medium

  • internal/postgres/schema.go:1337, internal/postgres/push.go:2253 — PostgreSQL push, scan, compatibility, and fingerprint paths do not fully handle the newly canonical session fields: file_size, file_mtime, file_inode, file_device, file_hash, and local_modified_at. Values remain NULL, and changes to most fields can be silently skipped.

    • Fix: Add these fields to insert/update arguments, the conflict predicate, sessionPushFingerprint, pgSessionCols, scanPGSession, and the compatibility probe.
  • internal/postgres/schema.go:1404CreateCommonSchema relies on CREATE TABLE IF NOT EXISTS, which cannot add canonical composite foreign keys to tables previously created by coreDDL. Required message-to-tool-call, tool-call-to-result, and message-to-pin relationships may remain absent even though CheckCommonSchema stamps the migration complete.

    • Fix: Add explicit idempotent PostgreSQL constraint migrations and validate the required foreign keys before writing the compatibility stamp.

Reviewers: 2 done | Synthesis: codex, 12s | Total: 13m20s

@mariusvniekerk
mariusvniekerk force-pushed the t3code/bun-storage-foundation branch from 7b0173e to 95a5c2b Compare August 7, 2026 14:05
@roborev-ci

roborev-ci Bot commented Aug 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (95a5c2b)

Changes requested: four medium-severity backend persistence and schema-convergence issues remain.

Medium

  • internal/postgres/schema.go:1337 — Six new session file-metadata columns are omitted from PostgreSQL pushes, reads, compatibility checks, and mostly from sessionPushFingerprint. Values remain NULL, and metadata-only updates may be skipped. Add all six columns throughout these paths and add round-trip tests.

  • internal/duckdb/push.go:1038parser_parent_session_id is omitted from DuckDB insert, update, argument, and fingerprint paths, losing parser-derived parent relationships. Include ParserParentSessionID and add a round-trip test where it differs from ParentSessionID.

  • internal/postgres/worktree_mappings_push.go:157 — Mapping publication omits created_at, replacing source creation timestamps with migration or insertion time. Publish CreatedAt, update compatibility checks, and republish existing mappings to correct defaulted values.

  • internal/postgres/schema.go:1349 — Canonical foreign keys are not migrated into existing PostgreSQL tables. Add idempotent constraint migrations, validate them against the PostgreSQL catalog, and test that orphaned relationships are rejected.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 24m27s

@roborev-ci

roborev-ci Bot commented Aug 7, 2026

Copy link
Copy Markdown

roborev: Combined Review (6d0f950)

Storage convergence has two medium-severity correctness issues; no critical or high-severity findings were identified.

Medium

  • DuckDB pin ID collisioninternal/db/bunmodel/curation.go:15, internal/duckdb/push.go:829
    DuckDB treats copied SQLite pin IDs as primary keys. Because SQLite can reuse an INTEGER PRIMARY KEY after unpinning, inserting a replacement before deleting the stale mirror row can fail on id, despite the upsert targeting (session_id, ordinal). Let DuckDB generate mirror-local IDs, make id non-key data, or clear existing pins before reinsertion.

  • Missing PostgreSQL foreign-key convergenceinternal/postgres/schema.go:1349
    Convergence stamps the schema without adding most registered canonical foreign keys. CREATE TABLE IF NOT EXISTS cannot retrofit constraints onto existing coreDDL tables, leaving relationships such as sessions-to-archives, pins-to-messages, and results-to-tool-calls unenforced. Add idempotent constraint migrations, update fresh DDL, and validate constraints before writing the compatibility stamp.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 14m47s

@mariusvniekerk
mariusvniekerk force-pushed the t3code/bun-storage-foundation branch from 6d0f950 to 6791f37 Compare August 9, 2026 03:46
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (6791f37)

The storage refactor has four medium-severity schema-parity and data-integrity issues across PostgreSQL and DuckDB.

Medium

  • PostgreSQL provenance fields are not returnedinternal/postgres/sessions.go:93
    Session queries neither select nor scan source_archive_id and source_database_generation, so PostgreSQL-backed reads silently return empty provenance even though push code stores it. Add both columns to pgSessionCols and destinations to scanPGSession, plus a PostgreSQL round-trip test.

  • DuckDB drops parser lineageinternal/duckdb/push.go:1026, internal/duckdb/sync.go:1041, internal/duckdb/store.go:344
    Although parser_parent_session_id is created, it is omitted from writes, fingerprints, reads, and scans. Lineage is lost, and changes cannot trigger incremental updates. Wire the field through each path and test differing parser and effective parent IDs.

  • DuckDB schema version was not incrementedinternal/duckdb/schema.go:25
    SchemaVersion remains 10 despite an incompatible layout change, allowing materially different mirror schemas to appear compatible. Increment the version and update metadata expectations, fixtures, and documentation.

  • PostgreSQL convergence omits canonical foreign keysinternal/postgres/schema.go:1501
    Convergence creates existing tables with IF NOT EXISTS and stamps the canonical schema without installing or validating required foreign keys for source provenance, tool calls/results, and pinned messages. Add idempotent constraint migrations and validate the PostgreSQL catalog before writing the compatibility stamp.


Reviewers: 2 done | Synthesis: codex, 26s | Total: 23m48s

The three database stores currently duplicate most query and scanning behavior, so schema parity depends on maintaining independent implementations. Record the approved cutover to one Bun-backed store and one canonical model registry before the large refactor begins.\n\nThe design preserves SQLite as the archive, PostgreSQL as an in-place-migrated sync target, and DuckDB as a rebuildable mirror. It also fixes the allowed backend seams to lifecycle, operational metadata, and small FTS/vector capabilities, avoiding a permanent legacy path during the transition.
The storage cutover spans schema, lifecycle, query, search, replication, and migration boundaries that must land in a dependency-safe order.

Record executable TDD slices and verification gates so the three stores converge without weakening archive safety, PostgreSQL write capabilities, DuckDB rebuild semantics, or performance checks.
DuckDB needs a first-class Bun identity so the unified store can generate portable schema and queries without inheriting SQLite sequence or type behavior. Keep the advertised feature set conservative and tied to real driver execution, including rollback, conflict, returning, CTE, and update-from behavior.
The cutover plan needs executable decisions for identity ownership, replication conflicts, pricing state, search hit identity, Quack coverage, and performance enforcement before later slices can safely remove backend-specific paths. Record those boundaries and make the DuckDB execution suite portable to platforms without a Windows ARM64 driver binary.
DuckDB must remain distinguishable from both current and future built-in Bun dialects, and mirror IDs must stay source-assigned even when a canonical model carries an autoincrement tag for another engine. Prove the generated catalog has neither a default nor a sequence before later schema work depends on this invariant.
A unified schema still needs explicit compatibility rules for SQLite's shipped message rowid and a non-empty provenance source for parser-created sessions. Fix the logical message key, preserve the archive's physical alias without rebuilding it, and require archive/database identity to flow through migration and batch writes.
The storage cutover needs one durable row contract before adapter migration can replace three independent query paths. Define the common serving tables, source-scoped identity shapes, UTC timestamp boundary, and lossless session/message conversions so later slices can converge schema and queries against a single Bun model registry.\n\nKeep SQLite's shipped message row ID as an accepted physical alias while making session/ordinal the generated logical key, avoiding a destructive archive rebuild.
The shared Bun store must not retain raw pools across SQLite archive reopens or DuckDB mirror replacements, and PostgreSQL's coarse remote-mode flag cannot describe its narrower curation capabilities.

Bind Bun execution to each adapter's existing lifetime guard, authorize writes by operation family, and route Quack SELECTs through its query table function. Keep handle ownership with the native pools so the cutover does not introduce a second close lifecycle.
The unified store cannot cut queries over safely while canonical schemas omit deletion and dedup semantics, row conversion can silently erase malformed timestamps, or guarded backends disagree about cursor and recovery state. Make those contracts explicit before schema convergence depends on them.\n\nPreserve SQLite's shipped row-ID relationships as physical aliases while defining ordinal-based common keys, and keep DuckDB's only constraint difference to its unsupported cascade syntax with explicit child-first mirror deletion.
A shared Bun store can only replace the three query paths once every backend exposes the same durable row contract without risking persistent data. Converge SQLite and PostgreSQL transactionally in place, stamp source provenance before new archive writes, and rebuild DuckDB from the canonical registry at schema version 10.

Keep shipped SQLite row-id aliases intact and let DuckDB omit only foreign-key DDL that conflicts with atomic mirror replacement; canonical ordinal relationships and explicit replacement ordering preserve the common behavior.
A stamped common schema must not replay stale identity inputs, accept empty session provenance, or preserve PostgreSQL constraints that reject canonical ordinal writes. Make the cutover fail closed while keeping SQLite upgrades additive and DuckDB rebuild-only.\n\nRoute local identity, snapshot, mapping, resync, and backfill behavior through the source-scoped tables so the legacy SQLite inputs become inert after their one-time migration.
The unified storage plan overstated fresh SQLite constraints, treated pricing metadata as timestamps, and split write ownership across stack layers. Those contradictions obscured which guarantees the foundation can enforce and where the final write cutover belongs.\n\nRecord one SQLite physical relationship matrix, real pricing timestamps with separate refresh metadata, workflow-owned provenance completeness, lock-bound PostgreSQL validation, and unsupported downgrade recovery. Assign trigger removal to the final stack layer after application writes are centralized.
Canonical pricing rows must carry real timestamps, workflow staging must remain valid before publication, and stamped PostgreSQL schemas must be validated under the migration lock. Keeping these invariants in the foundation prevents later stack layers from inheriting dialect-specific repair paths.

Move SQLite pricing refresh state into dedicated metadata, normalize PostgreSQL pricing columns in the existing convergence transaction, and require explicit DuckDB mapping timestamps. Trigger removal remains owned by the final cutover in PR #1347 and kata vzty.
The common schema upgrade treated every underscore-prefixed model as internal metadata and accepted legacy text timestamp columns after stamping. That could delete valid private model pricing and let read-only or push startup use a schema that no longer matches the canonical timestamp contract.

Limit migration to the three reserved metadata keys and reject non-native PostgreSQL timestamp columns. Unstamped writable schemas can still converge under the advisory lock, while stamped or read-only drift now fails closed.
Stamped SQLite archives reinstalled source identity triggers and reran table-wide invariants on every open, masking drift and making startup scale with archive size. Validate non-conflicting source-owned triggers and canonical indexes after the one-time convergence stamp, while leaving row invariants in the convergence transaction.

Keep the shared timestamp boundary compatible with SQLite's empty sentinel and persist canonical UTC RFC3339Nano text instead of driver-native time values.
The Quack Bun resolver receives fully formatted SQL, so silently discarding direct driver arguments can execute a different query than the caller intended. Reject argument-bearing calls before forwarding while preserving Bun's zero-argument path and surfacing QueryRow failures through a recoverable row.
A compatibility stamp attests row invariants at convergence time. Rechecking foreign-key and duplicate-row invariants on every stamped open rejects legacy ownerless rows that replication intentionally adopts and turns startup into an unbounded data scan.

Keep stamped PostgreSQL validation structural while retaining the complete row checks before the one-time stamp is written.
The screenshot compatibility fixture started from a current foundation database but removed only the legacy mapping journals. Leaving source-scoped journal triggers and the common-schema stamp described an impossible historical archive and made the test depend on stale schema state.
The pre-journal archive fixture can be replayed before or after the top-layer legacy-trigger removal. Treat both legacy and source-scoped trigger names as optional so the fixture removes whichever schema generation exists instead of failing during setup.
@mariusvniekerk
mariusvniekerk force-pushed the t3code/bun-storage-foundation branch from f2df2ed to 6791f37 Compare August 10, 2026 15:43
@mariusvniekerk
mariusvniekerk force-pushed the t3code/bun-storage-foundation branch from 6791f37 to f2df2ed Compare August 10, 2026 15:43
@roborev-ci

roborev-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (f2df2ed)

Changes requested: two medium-severity compatibility issues could break CockroachDB initialization and leave stale DuckDB mirrors unreconstructed.

Medium

  • internal/postgres/schema.go:1514 — CockroachDB does not support PostgreSQL advisory locks. Common-schema convergence now unconditionally calls pg_advisory_xact_lock, causing CockroachDB schema checks to fail before compatibility validation, even for current databases. Detect CockroachDB and use a compatible migration-serialization mechanism while retaining advisory locks for PostgreSQL. Add integration coverage for both initialization paths.

  • internal/duckdb/schema.go:25 — DuckDB schema version was not incremented. The schema remains at version 10 despite material changes to columns, types, keys, indexes, and tables. Existing version-10 mirrors may pass compatibility checks despite having an incompatible physical schema. Increment SchemaVersion and update related expectations so existing mirrors are rebuilt.


Reviewers: 2 done | Synthesis: codex, 18s | Total: 18m31s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant