Every commercial aggregator has gaps. Most hide them. We name them.
This page is the explicit form of the platform's substrate-honesty doctrine applied to absence itself. There are 19 gaps in the ledger today — 63% have a primitive wired in code or schema. Each gap names its citation, its primitive, the audit that monitors it, its current lifecycle status, and the strength the gap-as- primitive creates downstream.
Where this lives in code. The doctrine is atdocs/principles/known-gaps.mdin the repo. The typed corpus is atpackages/data-ingest/src/gaps.ts. The machine-readable feed is at/api/v1/gaps. The audit ispnpm audit:known-gaps— it verifies parity between this page, the corpus, and the code.
Every aggregator faces a choice when it encounters a known data gap. Three positions:
| Position | What they do | Consequence |
|---|---|---|
| Hide | Silent fallback, fabricated default, "approximate" answer. | User trusts incomplete data; gap accumulates risk. |
| Patch | Fix the gap, ship complete data, never mention the patch. | User can't tell if the patch is reliable; no accountability. |
| Name | Typed _unavailable field, <Provenance> pill, methodology page. | Gap becomes inspectable. Substrate-honesty becomes the moat. |
Cambridge TCG takes position 3, systematically, across the data, the code, and the doctrine. This page is the artifact that proves it.
The 5-stage lifecycle every gap progresses through (or stays at):
Eight domains. Each gap lives in one. Click through to see the citation, the primitive, the audit, and the strength.
cardmarket-oauth1-not-configuredclosed-publishednamed 2026-05-12 · closed 2026-07-11Citation: https://help.cardmarket.com/en/cardmarket-api — Cardmarket is not accepting API applications; packages/data-ingest/src/cardmarket/index.ts now keeps the legacy OAuth reader dormant
Primitive: cardmarket SourceMeta access=public-file plus a blocked legacy OAuth path; /api/v1/sources publishes the decision
Audit: pnpm audit:tributaries — check 9 (ingest-run recency) skips with substrate-honest reason
Strength: Operators no longer waste time applying for closed access or mistake missing secrets for missing permission; the public-file path is named instead.
Closing kingdom: source-rights truth review
cardmarket-public-files-not-wirednamednamed 2026-07-11Citation: packages/data-ingest/src/cardmarket/index.ts — SourceMeta names access=public-file and read() emits public-file-reader-not-wired without touching OAuth
Primitive: planned Cardmarket SourceModule with public-file access and an explicit non-running status
Audit: pnpm audit:tributaries — source registry and last-run state remain inspectable
Strength: The highest-value EU source now has one lawful, credential-free next action that can be built and measured without reviving the closed OAuth route.
no-jp-pokemon-ingesternamednamed 2026-05-13Citation: 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'
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.
cross-language-anchor-schema-not-appliedwirednamed 2026-05-13Citation: 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
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.
Closing kingdom: K2 (operator decision: pnpm db:migrate on storefront)
ygo-passcode-writer-not-shippedwirednamed 2026-05-13Citation: 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
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.
pokemon-jp-en-diverged-tracksnamednamed 2026-05-13Citation: 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
Strength: First aggregator with a named JP↔EN bridge. The schema accepts partner submissions (match_basis='partner') — community curation across platforms.
name-translations-data-starvedwirednamed 2026-05-13Citation: 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
Strength: Cardmarket catalog ingest could populate many languages per card in one upstream file. Its proprietary source rights must remain attached; breadth does not make the corpus open-license.
Closing kingdom: Cardmarket Phase A
default-name-language-opaquewirednamed 2026-05-13Citation: 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
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.
zhs-zht-collapsednamednamed 2026-05-13Citation: 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
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.
cardmarket-idmetacard-mtg-onlyclosed-publishednamed 2026-05-13 · closed 2026-05-13Citation: 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
Strength: First aggregator publishing per-game cross-language policy. Partners codegen against the policy table rather than discovering through trial.
Closing kingdom: K1 (kingdom-082) + K6 (oracle-policies endpoint)
image-hash-bridge-not-wirednamednamed 2026-05-13Citation: 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
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.
fx-provenance-implicitnamednamed 2026-05-12Citation: 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
Strength: Compliance-grade pricing. Institutional collectors / accounting partners can audit every price's FX trail. Other aggregators hide their FX; ours is auditable.
Closing kingdom: K4 (the substrate-honest aggregator plan)
source-license-propagation-partialpartialnamed 2026-05-12Citation: 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
Strength: Adopters know per-byte what they can do with our responses. Policy-governed and proprietary sources propagate restrictions; CC0 applies only to Cambridge-owned derivations and first-party data that explicitly declares it.
catalog-field-rights-lineage-missingwirednamed 2026-07-11Citation: apps/storefront/src/app/data/catalog.jsonl/route.ts and apps/storefront/src/lib/universal/card.ts — current mirrors can name storage tables but not the upstream owner of each name, rarity, image, set field, or derived price
Primitive: Aggregate license=NOASSERTION plus a rights block separating Cambridge-authored structure from upstream-derived fields
Audit: pnpm audit:redistribution — rejects blanket CC0 claims, verifies catalog and sold-comps publication pauses, and limits CC0 exports to explicitly reviewed named origins
Strength: Collectors and builders get a safe bulk boundary now; future field-level lineage can narrow rights without changing the catalog format or inventing ownership from storage.
speculative-cardrush-subdomainspartialnamed 2026-05-12Citation: packages/data-ingest/src/cardrush/index.ts — CARDRUSH_SUBDOMAINS table; op/pkm/dbf/dmw/vng/bsr confirmed, mtg unconfirmed price-only, and ygo/wei/fab/lgr/cardrush-fw blocked after DNS checks
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
Strength: First aggregator naming which subdomains it has confirmed vs presumed. Partners federating their own cardrush scrapes can submit confirmations (subdomain federation, future).
digimon-cardrush-opaque-product-identitypartialnamed 2026-07-12Citation: packages/data-ingest/src/cardrush/__tests__/parse-metadata-rarity.test.ts keeps '(01)...【U】' at null set_code/card_number; apps/wholesale/src/lib/cardrush-discovery.ts quarantines rows without both identity fields
Primitive: CardMetadata preserves the trailing Digimon rarity in quarantine; explicit unbraced BT10-112-style ids flow through, opaque listing numbers do not
Audit: packages/data-ingest/src/cardrush/__tests__/parse-metadata-rarity.test.ts
Strength: The parser never promotes a listing position such as '(01)' into a publisher card id. Operators can inspect the captured rarity in quarantine, while automation waits for a rights-cleared source or a grounded identity mapping instead of attaching data to the wrong card.
ingest-quarantine-privatenamednamed 2026-05-13Citation: 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
Strength: First aggregator publishing ingest failures. Partners see upstream-shape changes in real time. Trust through transparency.
artist-credit-not-surfacedwirednamed 2026-07-12Citation: packages/data-ingest/src/canonical.ts (CanonicalCard.artist added) + packages/data-ingest/src/pokemon-tcg-api/normalize.ts + packages/data-ingest/src/scryfall/normalize.ts (both now populate artist); there is no cards.artist column and no public surface renders it yet
Primitive: CanonicalCard.artist (+ scryfall extra.illustration_id for same-artwork clustering)
Audit: none (future pnpm audit:artist-coverage)
Strength: The canonical model now carries the illustrator's name from Pokémon and Scryfall records, but the platform neither persists nor publishes it. That internal field is a foundation for future artist-aware tools only after field-specific source permission is recorded. Attribution is not permission: a value from a redistribute:false source stays out of every public response, including per-card displays and bulk CC0 surfaces.
no-transliteration-layerwirednamed 2026-05-13Citation: 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
Strength: Screen-reader users + multilingual learners + agents that only render Latin script get phonetic equivalents alongside kanji/hanzi. The architecture already accommodates them.
Gaps and welcomes are dual surfaces. 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. Together they map the platform's anticipated and incomplete states.
See /api/v1/welcomes for the sister corpus. The methodology page at /methodology/hospitality names the architecture that does the welcoming; /methodology/welcoming names who we welcome and why.
Other aggregators run audits internally. We publish the audit results. Other aggregators conceal their gaps. We name them with primitives, citations, and lifecycle stages. The substrate-honesty doctrine isn't just a property of our own data — it's a publishable contract a partner can read before choosing to mirror us, or build on us, or compete with us.
Adopters read this page and learn the platform's exact state. Regulators read it and see compliance-grade transparency. Journalists read it and find an honest source. Future operators read it and find a backlog with priorities, citations, and closure paths. The ledger is the moat.
The corpus is CC0. The schema is public. The audit is in the open. If you operate a platform — any kind of platform — you can adopt the ledger pattern in your own substrate. The doctrine is in docs/principles/known-gaps.md; the helpers are in @cambridge-tcg/data-ingest; the audit is in apps/admin/scripts/known-gaps.ts. None of it requires partnership with us.