Context
The sync layer in src/sync/fab_cube.rs currently produces an in-memory
Catalog plus Vec<BannedEntry> and Vec<LivingLegendEntry>. Every
process restart re-fetches/re-parses the upstream JSON. There is no DB row
type, no migration, no sqlx queries for cards/printings/banlists.
Why deferred
Substantial slice (estimated 2-4 hours) — schema design, 5+ migrations,
db/cards.rs row types, sqlx-checked queries, the .sqlx cache committed
to repo, integration tests against a real Postgres. Today the in-memory
catalog is plenty fast (HashMap lookups over ~14k printings) and we have no
API endpoints reading it under concurrent load.
When to do this
When at least one of the following is true:
- Validation API endpoint exists and serves traffic (need persistent shared
state, fast lookups under concurrency)
- Multi-instance deployment where each instance should not independently
hold 4k+ cards in memory
- We need historical card data for queries the in-memory catalog cannot
answer efficiently
Scope
- New tables:
cards, printings, card_banned_entries (per format),
card_living_legend_entries (per format), sets
- Migrations following
.claude/rules/database.md conventions (snake_case
plural tables, id uuid PKs, timestamptz timestamps, FK indexes)
- Row structs in
src/db/cards.rs mirroring schema, with From<Row> for
domain conversions at the db/ boundary
- Idempotent upsert logic — the sync runs in a transaction and is no-op
when run twice with no upstream changes
- sqlx queries via
query!/query_as! macros, .sqlx cache committed
Pointers
- Sync producer:
src/sync/fab_cube.rs::build_from_json returns
SyncOutput { catalog, cc_banned, cc_living_legend, ... }
- Conventions:
.claude/rules/database.md
- Architectural memory:
project_architectural_constraints.md — printing-level identity is load-bearing, do not collapse
Context
The sync layer in
src/sync/fab_cube.rscurrently produces an in-memoryCatalogplusVec<BannedEntry>andVec<LivingLegendEntry>. Everyprocess restart re-fetches/re-parses the upstream JSON. There is no DB row
type, no migration, no sqlx queries for cards/printings/banlists.
Why deferred
Substantial slice (estimated 2-4 hours) — schema design, 5+ migrations,
db/cards.rsrow types, sqlx-checked queries, the.sqlxcache committedto repo, integration tests against a real Postgres. Today the in-memory
catalog is plenty fast (HashMap lookups over ~14k printings) and we have no
API endpoints reading it under concurrent load.
When to do this
When at least one of the following is true:
state, fast lookups under concurrency)
hold 4k+ cards in memory
answer efficiently
Scope
cards,printings,card_banned_entries(per format),card_living_legend_entries(per format),sets.claude/rules/database.mdconventions (snake_caseplural tables,
id uuidPKs,timestamptztimestamps, FK indexes)src/db/cards.rsmirroring schema, withFrom<Row>fordomain conversions at the
db/boundarywhen run twice with no upstream changes
query!/query_as!macros,.sqlxcache committedPointers
src/sync/fab_cube.rs::build_from_jsonreturnsSyncOutput { catalog, cc_banned, cc_living_legend, ... }.claude/rules/database.mdproject_architectural_constraints.md— printing-level identity is load-bearing, do not collapse