deckhearth/docs/SCHEMA_MAP.md
Randall Stillwell 1944b1ed48 bootstrap: agent pipeline v0.5.0 + ship-readiness review
Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0):

L1 — Context (curated brain)
- AGENTS.md: orientation, conventions, 8 explicit gotchas
- .cursor/rules/: no-go-zones, api-routes, auth-and-permissions,
  db-and-schema, ui-and-theming, schema-map
- .cursor/skills/: add-api-route, add-page recipes
- docs/agent-context/README.md: layer explainer
- docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference
  (replaces Prisma schema map since stack is raw SQL)

L2 — Subagent roles (copied verbatim from upstream templates)
- 9 .cursor/agents/role-*.md files: Conductor, IA-Architect,
  UX-Reviewer, Architect, Implementer, Reviewer,
  Design-System-Auditor, A11y-Auditor, Doc-Writer

L3 — Pipeline scaffolding (Vercel variant)
- CI: lint + schema-map-drift only (no duplicate build —
  Vercel handles it). Test job commented out until vitest lands.
- preview-smoke + visual-diff via wait-for-vercel-preview
- pr-health-rollup sticky comment aggregator
- agent-context-drift weekly cron
- PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged)
- .convoys/ folder + seed ship-readiness.md review
- lib/flags/index.js (JS — converted from TS template)
- scripts/wt.sh (Cursor 3.2 deprecation stub),
  scripts/log-convoy-event.sh
- tests/smoke/app.smoke.spec.ts (Playwright skeleton)

Manifest
- .agent-context-manifest.yml: tracks 31 artifacts by sha256
  for future sync-agent-context drift detection

Review
- .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers,
  5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with
  proposed 13-convoy launch sequence.

No production code changed in this commit. All findings in
the ship-readiness review will be addressed in follow-up convoys
starting with fix-auth-bypass.

Structural brain: user-code-review-graph MCP has indexed the
codebase (122 files, 628 nodes, 5602 edges, 11 communities,
84 flows). Per-developer; not committed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 02:31:26 -05:00

7.3 KiB

SCHEMA_MAP.md

Hand-curated reference for the Neon Postgres schema. The actual schema is the union of scripts/setup-neon-db.js (initial DDL) plus every scripts/add-*.js / scripts/fix-*.js that has been run. Until a real migration tool is adopted, this file is the source of truth for agents and humans.

Last reviewed: 2026-05-22 against scripts/setup-neon-db.js + every scripts/add-*.js and scripts/fix-*.js in repo HEAD.

Quick model groups

Group Tables Purpose
Identity users, user_settings, user_avatars Accounts, profile, preferences
Catalog cards Master card list across MTG / Pokémon / Lorcana
Ownership user_cards, user_favorites What a user owns / has favorited
Collections collections, collection_cards, collection_permissions, collection_activity Curated card lists with sharing
Decks decks, deck_cards Playable deck definitions
Invitations invitations (referenced; verify) Pending share requests

Tables

users

Column Type Notes
id SERIAL PK
email VARCHAR(255) UNIQUE NOT NULL Lowercase before query/insert (no CI collation set)
password VARCHAR(255) NOT NULL bcrypt hash, cost 12
role VARCHAR(50) default 'user' 'user' | 'admin'
created_at, updated_at TIMESTAMP default now
first_name, last_name VARCHAR(255) From add-user-profile-columns.js
username VARCHAR(255) UNIQUE
profile_image_url, avatar_url TEXT Two redundant columns; verify which is canonical
bio TEXT
favorite_games JSONB default '["MTG"]' Per-user game preference array
collection_visibility VARCHAR(20) default 'private'
preferred_currency VARCHAR(3) default 'USD'
cards_per_page INTEGER default 50
default_view VARCHAR(10) default 'grid'
notifications_email BOOLEAN default true
notifications_marketing BOOLEAN default false
two_factor_enabled BOOLEAN default false Not implemented yet
theme VARCHAR(10) default 'system' 'light' | 'dark' | 'system'
language VARCHAR(5) default 'en'
is_pending BOOLEAN default false Set by invitation flow before signup completes

cards

Column Type Notes
id SERIAL PK
name VARCHAR(255) NOT NULL
set_name, set_code, card_number VARCHAR
rarity VARCHAR(50)
game VARCHAR(50) NOT NULL 'mtg' | 'pokemon' | 'lorcana'
mana_cost VARCHAR(50) MTG only
cmc INTEGER MTG only
card_type VARCHAR(255)
colors JSONB MTG color array
oracle_text TEXT LARGE — never SELECT *
power, toughness VARCHAR(10) MTG creatures
image_url, stock_image_url TEXT
current_price, market_price DECIMAL(10,2)
scryfall_id VARCHAR(255) UNIQUE Use for dedupe on MTG import
verified BOOLEAN default false Admin-edited cards
quantity INTEGER default 0 Unused; consider dropping — quantity lives in user_cards
favorited BOOLEAN default false Unused; favorites live in user_favorites
created_at, updated_at TIMESTAMP default now

user_cards

Column Type Notes
id SERIAL PK
user_id INTEGER FK users(id) ON DELETE CASCADE
card_id INTEGER FK cards(id) ON DELETE CASCADE
quantity INTEGER default 1
condition VARCHAR(50) default 'NM' NM / LP / MP / HP / DMG
is_foil BOOLEAN default false
notes TEXT
UNIQUE(user_id, card_id, is_foil)

user_favorites

Column Type Notes
id SERIAL PK
user_id, card_id FKs cascade
created_at TIMESTAMP
UNIQUE(user_id, card_id) (verify constraint exists)

collections

Column Type Notes
id SERIAL PK
user_id INTEGER FK users(id) ON DELETE CASCADE
name VARCHAR(255) NOT NULL
description TEXT
is_public BOOLEAN default false Used by withCollectionPermission('viewer')
slug VARCHAR(100) UNIQUE Format constraint: lowercase kebab, ≤50 chars
image TEXT Cover image
visibility VARCHAR(20) default 'private' Coexists with is_public; verify single source of truth
tcg VARCHAR(50) default 'MTG'
tags TEXT Comma-separated; consider migrating to JSONB array
is_system_collection BOOLEAN default false E.g. "All My Cards" auto-collection
created_at, updated_at TIMESTAMP

collection_cards

Column Type Notes
id SERIAL PK
collection_id, card_id FKs cascade
quantity INTEGER default 1
UNIQUE(collection_id, card_id)

collection_permissions

Column Type Notes
id SERIAL PK
collection_id, user_id FKs cascade
role VARCHAR 'viewer' | 'editor' | 'owner'
status VARCHAR 'pending' | 'active' | 'declined' — used by invite flow
created_at TIMESTAMP

collection_activity

Column Type Notes
id SERIAL PK
collection_id, user_id FKs
action VARCHAR e.g. 'card_added', 'permission_granted'
details JSONB
created_at TIMESTAMP

decks

Column Type Notes
id SERIAL PK
user_id FK
name, description text
game VARCHAR(50)
is_public BOOLEAN default false
created_at, updated_at TIMESTAMP

deck_cards

Column Type Notes
id SERIAL PK
deck_id, card_id FKs cascade
quantity INTEGER default 1
UNIQUE(deck_id, card_id)

user_settings (split from users.*; verify which is canonical)

Defined in add-user-profile-fields.js. Mirrors several users.* columns — there's redundancy that needs to be reconciled.

user_avatars

Tracks uploaded avatar history. Older avatars are typically deleted from blob storage; verify the cleanup job runs.

Known schema smells

  1. Two users avatar columnsprofile_image_url and avatar_url. Pick one.
  2. Two collection-visibility flagscollections.is_public (BOOLEAN) and collections.visibility (VARCHAR). Pick one.
  3. cards.quantity + cards.favorited — these belong on user_cards / user_favorites, not the catalog. Drop them.
  4. user_settingsusers.* — split-brain. Reconcile.
  5. No formal constraints on enumsrole, condition, theme, language, game, visibility are all VARCHAR. Consider CHECK constraints or proper ENUMs.
  6. collections.tags is TEXT — should be JSONB or a join table.

Regeneration

Until a migration tool lands:

rg "ALTER TABLE|CREATE TABLE|ADD COLUMN" scripts/

…then update this file by hand. A npm run schema:map script regenerated from the migration history is in .convoys/.