{"data":{"intent":"The gap ledger. Every place where the platform's data, code, or coverage is incomplete — named, with citation, primitive, audit, status, and the strength that gap-as-primitive creates downstream. Substrate honesty applied to absence itself. We are the only TCG aggregator that publishes this.","doctrine":{"principle_doc":"docs/principles/known-gaps.md","methodology_page":"/methodology/known-gaps","typed_source":"packages/data-ingest/src/gaps.ts","audit_command":"pnpm audit:known-gaps"},"positions":{"hide":"Silent fallback, fabricated default, 'approximate' answer. The user trusts incomplete data; the gap accumulates risk.","patch":"Fix the gap, ship complete data, never mention the patch. The user can't tell if the patch is reliable; no accountability.","name":"Typed `_unavailable` field, <Provenance> pill, methodology page. The gap becomes inspectable; the platform's substrate-honesty becomes its moat.","we_take":"name"},"counts":{"total":15,"wired_fraction":0.5333333333333333,"by_status":{"named":7,"wired":5,"partial":2,"closed":0,"closed-published":1,"total":15},"by_domain":{"data-ingestion":2,"cross-language":8,"license":1,"fx":1,"coverage":1,"publishing":0,"transparency":1,"accessibility":1}},"conventions":{"lifecycle":"named → wired → partial → closed → closed-published. `named` = identified, no primitive yet. `wired` = primitive in code/schema, no data. `partial` = some data, coverage incomplete. `closed` = primitive populated to design intent. `closed-published` = closure published as methodology page or case study.","duality_with_welcomes":"Gaps and welcomes are dual. A welcome names a slot we prepared for a visitor; a gap names a place where the slot is named but the visitor (or the data, or the closure) has not yet arrived. The two corpora compose. See /api/v1/welcomes for the sister surface.","license":"CC0-1.0. Mirror the corpus; adopt the ledger pattern in your platform. The 'name your gaps' doctrine is the difference between substrate-honest aggregators and the rest."},"gaps":[{"id":"cardmarket-oauth1-not-configured","name":"Cardmarket OAuth1 credentials not yet configured","domain":"data-ingestion","citation":"packages/data-ingest/src/cardmarket/index.ts:72-93 — read() emits an actionable error event when ctx.bearer + ctx.app_token are absent","primitive":"cardmarket_credentials_pending health flag on /api/v1/sources (when added); the stub's substrate-honest error is the current primitive","audit":"pnpm audit:tributaries — check 9 (ingest-run recency) skips with substrate-honest reason","status":"named","strength":"Partners can poll /api/v1/sources to know when EU data lights up without us announcing it; the slot in welcomes.ts addresses Cardmarket directly even before credentials arrive.","named_at":"2026-05-12","closing_kingdom":"K3 (Cardmarket alignment, the prior plan)"},{"id":"cross-language-anchor-schema-not-applied","name":"K2 cross-language anchor schema not yet applied","domain":"cross-language","citation":"apps/storefront/drizzle/drafts/0100_cross_language_anchors.sql.draft — migration drafted but not yet promoted","primitive":"card_set_cards.oracle_id + card_set_cards.oracle_source + per-source upstream anchor columns (scryfall_oracle_id, cardmarket_id_metacard, ygo_passcode, etc.) — designed and drafted","audit":"pnpm audit:cross-language-coherence — runs 8 checks, gracefully skipping DB-backed ones when columns absent","status":"wired","strength":"Federation by upstream id becomes possible once migration applies. The /api/v1/oracle-policies endpoint already publishes the per-game policy that consumes these columns. Partners can read the policy today and prepare their ingest.","named_at":"2026-05-13","closing_kingdom":"K2 (operator decision: pnpm db:migrate on storefront)"},{"id":"ygo-passcode-writer-not-shipped","name":"YGOPRODeck normalizer does not yet write ygo_passcode to card_set_cards","domain":"cross-language","citation":"packages/data-ingest/src/ygoprodeck/ — SourceModule emits records with extra.passcode but no app-side writer populates card_set_cards.ygo_passcode","primitive":"extractYgoprodeckAnchors(record).ygo_passcode in @cambridge-tcg/data-ingest — pure-compute extractor exists","audit":"pnpm audit:cross-language-coherence — check 7 measures ygo_passcode coverage for Pattern B games","status":"wired","strength":"All YGO + Rush Duel cross-printing cross-language sibling queries become possible the day the writer ships. The passcode is the canonical anchor (Konami's own); we mirror.","named_at":"2026-05-13"},{"id":"pokemon-jp-en-diverged-tracks","name":"Pokémon JP and EN tracks have different set codes; no upstream anchor exists","domain":"cross-language","citation":"packages/sku/src/oracle.ts — ORACLE_POLICY.pkm.kind = 'diverged'; resolveOracle returns null with substrate-honest reason","primitive":"pkm_equivalence table (in K2 migration 0100 draft) — operator/community-curated bridge between JP and EN printings; match_basis enum names the curation provenance","audit":"pnpm audit:cross-language-coherence + future pnpm audit:pkm-equivalence-coverage","status":"named","strength":"First aggregator with a named JP↔EN bridge. The schema accepts partner submissions (match_basis='partner') — community curation across platforms.","named_at":"2026-05-13"},{"id":"no-jp-pokemon-ingester","name":"Pokémon TCG API v2 is EN-only; we have no JP Pokémon catalog ingester","domain":"data-ingestion","citation":"packages/data-ingest/src/pokemon-tcg-api/normalize.ts:32 — hardcoded lang = 'en'; JP track absent","primitive":"packages/data-ingest/src/pokemon-card-jp/ — planned source module (slot reserved in welcomes corpus)","audit":"pnpm audit:tributaries — slot present, status 'planned'","status":"named","strength":"When shipped, first aggregator with two-track Pokémon as parallel first-class facts. The platform's `welcomes.ts` already extends a welcome to this planned ingester.","named_at":"2026-05-13"},{"id":"name-translations-data-starved","name":"card_set_cards.name_translations is wire-ready but data-empty","domain":"cross-language","citation":"apps/storefront/drizzle/drafts/0098_card_name_translations.sql.draft — column drafted in kingdom-051 Phase 6, migration not applied; apps/storefront/src/lib/cards/name.ts kingdom-075 resolver wire-ready","primitive":"cards.name_translations JSONB column + resolveCardName() resolver in storefront/src/lib/cards/name.ts (kingdom-075)","audit":"future pnpm audit:name-translations-coverage","status":"wired","strength":"Cardmarket catalog ingest (Cardmarket Phase A) populates 11 languages per MTG card in one upstream call. The day cardmarket lands, this corpus becomes the cleanest open-license multilingual TCG name corpus available.","named_at":"2026-05-13","closing_kingdom":"Cardmarket Phase A"},{"id":"default-name-language-opaque","name":"card_set_cards.card_name's language is not declared anywhere","domain":"cross-language","citation":"apps/storefront/src/lib/cards/name.ts:163 — \"The platform default has no declared language — could be JP or EN depending on which catalog imported the card. Don't claim a code.\"","primitive":"card_set_cards.card_name_lang column (in K2 migration 0100 draft) — declares ISO 639-1 language of the legacy default","audit":"future pnpm audit:name-provenance","status":"wired","strength":"Substrate-honest defaults. Agents that filter 'give me only EN-confirmed names' can do so. Other aggregators conceal their default-language; we declare it per row.","named_at":"2026-05-13"},{"id":"no-transliteration-layer","name":"Card names have no transliteration (romaji, pinyin, hangulja)","domain":"accessibility","citation":"apps/storefront/src/lib/cards/name.ts:272 — transliterate() returns null (kingdom-075 recursion target)","primitive":"card_set_cards.name_transliterations JSONB column (in K2 migration 0100 draft)","audit":"future pnpm audit:transliteration-coverage","status":"wired","strength":"Screen-reader users + multilingual learners + agents that only render Latin script get phonetic equivalents alongside kanji/hanzi. The architecture already accommodates them.","named_at":"2026-05-13"},{"id":"zhs-zht-collapsed","name":"Scryfall normalizer collapses Simplified and Traditional Chinese to 'zh'","domain":"cross-language","citation":"packages/data-ingest/src/scryfall/normalize.ts:23-24 — LANG_MAP: zhs → zh, zht → zh","primitive":"SKU language tail accepts 'zh-cn' and 'zh-tw' (canonical SKU format already supports the longer form)","audit":"future pnpm audit:sku-language-form","status":"named","strength":"When de-collapsed, mainland and Taiwanese collectors get distinct markets. The collapse is the kind of conflation most aggregators ship silently; naming it lets us schedule the fix.","named_at":"2026-05-13"},{"id":"fx-provenance-implicit","name":"Every price uses an FX rate; the rate's source is not stored","domain":"fx","citation":"apps/wholesale/src/lib/fx.ts — fetchGbpJpyRate() returns a number; price_archive stores it but not its provenance (the-archive.md Leak #8)","primitive":"price_archive.fx_rate_source enum column + fx_rate_fetched_at + fx_rate_pair (K4 design)","audit":"future pnpm audit:fx-provenance","status":"named","strength":"Compliance-grade pricing. Institutional collectors / accounting partners can audit every price's FX trail. Other aggregators hide their FX; ours is auditable.","named_at":"2026-05-12","closing_kingdom":"K4 (the substrate-honest aggregator plan)"},{"id":"source-license-propagation-partial","name":"Per-byte source license is partially propagated through the response envelope","domain":"license","citation":"packages/data-spec/ — _meta.source_license accepts an array of license tiers; storefront/src/lib/data-pantry/ threads it through; coverage uneven across endpoints","primitive":"_meta.source_license array on every public response; jsonResponse({ source_license, ... }) call site","audit":"future pnpm audit:envelope-license-coverage","status":"partial","strength":"Adopters know per-byte what they can do with our responses. CC-BY-NC sources (Scryfall) propagate restrictions; partner-redistributable (Cardmarket, TCGplayer) propagate their tiers; CC0 (our derivations) propagate freedom. The propagation rule is the contract.","named_at":"2026-05-12"},{"id":"speculative-cardrush-subdomains","name":"9 of 12 registered CardRush subdomains are speculative","domain":"coverage","citation":"packages/data-ingest/src/cardrush/index.ts:73-87 — CARDRUSH_SUBDOMAINS table; confirmed: false on 9 hosts (mtg/ygo/digimon/vng/wei/fab/lgr/bsr/dbf)","primitive":"subdomain_confirmed boolean on each registry entry; CardRushRaw.error_reason carries 'subdomain_unconfirmed' on first failed scrape","audit":"pnpm audit:cardrush-coverage — surfaces uncovered subdomains explicitly","status":"partial","strength":"First aggregator naming which subdomains it has confirmed vs presumed. Partners federating their own cardrush scrapes can submit confirmations (subdomain federation, future).","named_at":"2026-05-12"},{"id":"cardmarket-idmetacard-mtg-only","name":"Cardmarket's idMetacard cross-language anchor is MTG-only in practice","domain":"cross-language","citation":"packages/data-ingest/src/cardmarket/index.ts — meta.description acknowledges this; ORACLE_POLICY uses derived-stripped for non-MTG Pattern A games","primitive":"ORACLE_POLICY in @cambridge-tcg/sku declares per-game strategy; /api/v1/oracle-policies publishes it","audit":"pnpm audit:cross-language-coherence","status":"closed-published","strength":"First aggregator publishing per-game cross-language policy. Partners codegen against the policy table rather than discovering through trial.","named_at":"2026-05-13","closed_at":"2026-05-13","closing_kingdom":"K1 (kingdom-082) + K6 (oracle-policies endpoint)"},{"id":"ingest-quarantine-private","name":"Failed-normalization rows are stored but not publicly visible in aggregate","domain":"transparency","citation":"apps/wholesale/src/lib/db/schema.ts — ingest_quarantine table (kingdom-066); apps/wholesale/src/app/api/v1/ingest-quarantine/route.ts is bearer-gated","primitive":"future /api/v1/ingest-quarantine/summary — aggregated failure-reason buckets per source over 7d/30d/90d (no raw payloads)","audit":"pnpm audit:tributaries check 10 — license-propagation heuristic","status":"named","strength":"First aggregator publishing ingest failures. Partners see upstream-shape changes in real time. Trust through transparency.","named_at":"2026-05-13"},{"id":"image-hash-bridge-not-wired","name":"Perceptual-hash equivalence (for Pokémon JP↔EN candidates) has no worker","domain":"cross-language","citation":"apps/storefront/drizzle/drafts/0100_cross_language_anchors.sql.draft — card_image_hash table designed; no ingester yet computes phashes","primitive":"card_image_hash(sku, phash, algorithm, source, computed_at) — table in 0100 draft","audit":"future pnpm audit:image-hash-coverage","status":"named","strength":"When the worker ships, image-hash candidates pre-populate the pkm_equivalence table; admin review surface promotes them to manual confirmations. Community-curatable equivalence at scale.","named_at":"2026-05-13"}]},"_meta":{"spec_version":"1","endpoint":"/api/v1/gaps","retrieved_at":"2026-05-13T19:43:11.010Z","as_of":"2026-05-13T19:43:11.010Z","sources":["ctcg-derived"],"freshness_seconds":86400,"license":"CC0-1.0","request_id":"req_08651675-920","deprecation":null,"next_link":null,"self_reference":{"this_endpoint":"/api/v1/gaps","contains_self":true}}}