A full state–context–outcome matrix analysis crossing Card Archetypes (CA1–CA6) against Card Tiers (CT1–CT3) against the FSRS/lifecycle machinery — built to test your redundancy hunch ("lemma and sense CA are redundant, should be represented by CT — vocab has lemma, sense, and form") against what's actually documented and actually wired. Plus 12 doc contradictions found along the way, and the leftover open items from last round.
Two axes ride on every card, plus five state systems layered on top. This is the vocabulary the rest of the page uses.
Communication labels only — there's ONE card abstraction in code. Stored column: t070_study_card_catalog.card_archetype, NOT NULL CHECK, values lemma | sense | form | script_unit, reserved phrase | vocab, proposed passage (ADR-082 A1).
| ID | Name | Plain-English | Tier | Shipped? |
|---|---|---|---|---|
| CA1 | Sense card | One discrete meaning of a word — the primary card | CT2 | ✅ |
| CA2 | Family (form) card | An inflected/derived spelling, drilled as its own card | CT3 | 🟡 label ships, studiable-forms deferred (F6) |
| CA3 | Phrase card | Multi-word sense / collocation, same renderer | CT2 | ✅ |
| CA4 | Vocab card | L1=L2 vocab / domain jargon ("merge conflict") | CT2 per docs — but your framing wants all 3 tiers | 📋 future |
| CA5 | Character card | A letter/cluster/script unit + pronunciation; alphabet renderer, not sense renderer | CT2 | ✅ (31 live, Indonesian) |
| CA6 | Passage card (proposed) | A formatted, sectioned passage/rules-set revealed in blocks | not stated (F12) | 📋 proposed, ADR-082 |
| — none — | Stored 'lemma' | Container row exists live but has no CA number — "not a displayed card" yet "always schedulable" | CT1 | live |
| ID | Name | Gloss | parent_card_id |
|---|---|---|---|
| CT1 | Lemma | Headword/container row — frequency, content-state, enrichment version | NULL |
| CT2 | Sense | One discrete meaning — the primary FSRS learning unit | → CT1 |
| CT3 | Form | Inflected/derived surface form under a sense | → CT2 |
1-indexed, no CT0; plant-metaphor words are banned on this axis (CP-002/CP-009).
✅ valid + defined · ⚠️ valid but under-specified · ❌ impossible by definition · ∅ undefined — the gap IS the point.
| CT1 Lemma | CT2 Sense | CT3 Form | |
|---|---|---|---|
| CA1 Sense | ❌ a lemma is not a meaning | ✅ the canonical pairing | ❌ |
| CA2 Family/form | ❌ | ❌ | ✅ the canonical pairing |
| CA3 Phrase | ⚠️ CT1 rows carry is_multiword_expression too — a multi-word lemma container exists but has no display definition | ✅ multi-word CT2 sense | ∅ forms of a phrase (e.g. reduplication) not addressed |
| CA4 Vocab | ∅ your "vocab has lemma, sense, form" contradicts TERMINOLOGY pinning CA4 to CT2 only | ⚠️ documented, "future direction", field expectations all TBD | ∅ same gap |
| CA5 Character | ∅ do the 31 live script_unit rows have CT1 parents? CT2 requires a CT1 parent per FSRS-CARD-STATE § 12 — docs never say. Needs live verification. | ✅ 31 live (Indonesian) | ❌ no "form of a letter" concept |
| CA6 Passage (proposed) | ∅ | ⚠️ presumed CT2 but ADR-082 never states the tier — if CT2, what CT1 parent does a vows card hang off? (F12) | ∅ per-section scheduling explicitly out of scope (ADR-082 A8) |
| stored 'lemma' (no CA) | ⚠️ live rows; "not a displayed card" (field matrix) yet "always schedulable" (CP-005 A2.1) and "the study unit" in flat custom decks (CP-005 A3.3) — what a CT1 card displays in-session is undefined | ❌ | ❌ |
8 of 21 cells are ∅/⚠️. The defined diagonal (CA1↔CT2, CA2↔CT3, lemma↔CT1) is exactly your redundancy claim — every off-diagonal archetype (CA3/CA5/CA6, future CA4) is CT2-anchored, meaning the archetype axis today is effectively "which kind of CT2" plus two tier-mirror labels.
Because of the one-abstraction rule, all adopted cards share the same CL/GS/US/FSRS machinery. The differences are reachability — which states a combo can actually get to today.
| Combo | CL states reachable today | Notable caveats |
|---|---|---|
| CA1/CA3 × CT2 | CL10→CL80, all reachable | With CT3 forms: CL60 needs stability ≥120 AND all forms learned; can be GS6 Tree while still CL50 |
| CA5 × CT2 | CL10→CL80 (dev re-seed inserts alphabet rows into t320; drip releases as CT2) | Rendered by alphabet assessment, not the carousel; tags/translation_states correctly NULL |
| CA2 × CT3 | CL10–CL20 only — the live drip filters card_tier=2, so CT3 candidates in t320 are never released (verified live 2026-06-03) | Display-only "RELATED FORMS" is what actually ships; machinery is tier-blind, only entry gates block it |
| CA6 × CT2(?) | Designed: all, identical to CA1 (ADR-082 A5: FSRS coupling zero) | Enrichment/authoring path for archetype_extras.passage is unbuilt |
| lemma × CT1 | Contradiction — one doc says CT1 follows CL10→CL50; the same doc's drip step releases card_tier=2 only, so CL30→CL40 is unreachable for CT1 through the only wired path | Flat custom decks (CP-005 A3.3) require CT1 to be studiable — see F5 |
All grades flow through one pipeline: POST /api/review {type:'grade'} → processReview → guarded t310 UPDATE → side effects. User actions (mark-learned, pause, compost, star, etc.) flow through one handler.
CA1/CA3/CA5 (CT2): full transition-table participation — graduation, learned/demote, all user levers; compost-with-forms cascades to CT3 rows.
CA2 (CT3): designed outcomes exist and are unit-tested but have no production caller. Today the only real outcome of a CT3 row is being displayed and being cascade-retired by parent compost.
CA6: identical to CA1 by design — zero new transitions.
CT1: direct-inserted by the profile-create wizard, bypassing t320; no CT1-specific transition exists anywhere in the transition table.
These are doc-vs-doc or doc-vs-live conflicts surfaced while cross-checking every cell — not new opinions.
| Flag | Contradiction | Where |
|---|---|---|
| F1 | T25 defined two ways — TERMINOLOGY says CL30→CL40 "priority skip"; CARD-LIFECYCLE-FLOW says CL10→CL30 "priority star" | TERMINOLOGY:420 vs CLF § 4 |
| F2 | T72 defined two ways — TERMINOLOGY says CL70→CL30; flow cardinal says CL70→CL50 (and that's what's WIRED) | TERMINOLOGY:428 vs CLF § 4 T72 |
| F3 | T20 "drip-select CL20→CL30" vs flow-cardinal T20 writes released_at (= CL40) — the CL20/CL30/CL40 boundary is muddy | TERMINOLOGY:419 vs CLF § 4 T20 |
| F4 | TERMINOLOGY says CL20 rows can have is_candidate=true — no such column exists in the t310 column model | TERMINOLOGY:393 vs FSRS-CARD-STATE § 4 |
| F5 | CT1 "always-schedulable, never skipped" vs live drip releasing card_tier=2 only — CT1 has no wired release path | CLF § 3 vs § 5 |
| F6 | CA2 marked "🟢 Shipped 2026-05-22" while studiable forms are deferred — only display-only RELATED FORMS shipped | TERMINOLOGY CA2 row vs CLF § 7 |
| F7 | US3 Learned "4× scheduling modifier" — that branch was removed from getSchedulingModifier (returns 1.0 now) | TERMINOLOGY:468 vs FSRS-CARD-STATE § 7 |
| F8 | TERMINOLOGY § Card Lifecycle still says "card archetype (CA1–CA4 above)" — stale, taxonomy is CA1–CA6 | TERMINOLOGY:407 |
| F9 | TERMINOLOGY § Session still cites ADR-057 as the live contract; ADR-057 is archived/historical per CLAUDE.md's FSRS canonical reset | TERMINOLOGY:190,214 |
| F10 | The canonical-docs registry itself still lists ADR-057 under "canonical living ADRs" — direct evidence for the "keep this doc alive" worry; last_verified is 33+ days stale and TERMINOLOGY has no maintainer | _CANONICAL-DOCS.md § ADRs |
| F11 | Field-expectation matrix's "CA1/CA3 = archetype_extras IS NULL" discriminator breaks the day CA6 lands, since passage content lives in archetype_extras.passage — ADR-081's stored column saves it, but the legacy text should be re-scoped | TERMINOLOGY field matrix vs ADR-082 § 2 |
| F12 | ADR-082 never assigns CA6 a card_tier or a parent_card_id answer — a CT2 row requires a CT1 parent; what does a Bodhisattva-vows card hang off? | ADR-082 (absence) |
Your claim: "The lemma and sense CA are redundant. Should be represented by CT. Vocab has lemma, sense, and form."
The stored lemma/sense/form values are functionally derivable from card_tier 1/2/3 for every default-language card — two columns, one fact. ADR-081 materialized archetype precisely because deriving from shape signals was fragile, but for these three values the derivation is a trivial tier lookup, not shape-sniffing.
"Vocab has lemma, sense and form" exposes the deeper flaw: CA4 isn't a row property at all — a vocab deck has its own CT1/CT2/CT3 structure. A vocab form-row would need card_archetype='vocab' AND ='form' simultaneously — the single-valued column can't express it. Same will eventually bite phrase forms.
Display dispatch is real: CA5 routes to the alphabet assessment, CA6 routes to the passage-sections block. card_tier=2 alone can't make that dispatch — something must discriminate CT2 sub-kinds.
Field expectations are archetype-scoped, not tier-scoped: a sense row and a script_unit row (both CT2) have opposite NULL-expectations for tags/translation_states — the 2026-06-09 false "needs-backfill" alarm is the documented cost of getting this wrong. And ADR-081 is 3 weeks old, adopted specifically to stop re-deriving archetype from shape — reversing lemma/sense/form is a partial reversal in letter (tier-lookup is deterministic, not shape-sniffing) but should be named as such in whatever ADR does it.
Two coherent target shapes compete. Neither is obviously wrong; picking one is item M1 below.
Drop lemma/sense/form from the stored enum; store only CT2 sub-kind overrides (script_unit/phrase/vocab/passage), NULL = default-for-tier. CA1/CA2 become computed labels.
Cheapest migration. But the discriminator becomes two-column (card_tier + nullable override) — every completeness query / renderer dispatch reads both. The exact two-signal complexity ADR-081 tried to end.
card_tier (structure: 1/2/3) × content_domain (language | script_unit | phrase-ish | vocab | passage; naming TBD via TERMINOLOGY-first). Vocab-with-forms falls out naturally (domain='vocab', tier=3).
Cleanest model. Biggest rename ripple — column, CHECK, stamp sites, verify-enrichment, TERMINOLOGY, ADR-081 supersedure.
What breaks under either shape, if CA1/CA2 distinctions stop being stored: verify-enrichment.ts scoping predicates; the CARD_ARCHETYPE_ID const + consumers; the ADR-081 backfill just performed; the field-expectation matrix text; RevealView/loader dispatch (must switch on the pair); every doc sentence saying "CA2 maps to a CT3 row". None of it is FSRS — the wrapper and lifecycle never read archetype (ADR-082 A5 verified this).
| Surface | What changes | Size |
|---|---|---|
| Schema | CHECK swap (α) or column split + backfill (β) + COMMENT ON COLUMN; corpus ~1.1K cards + 31 script_units — minutes of SQL | S (α) / M (β) |
| verify-enrichment.ts + stamp sites | re-scope discriminators | S–M |
| fsrs.ts | CARD_ARCHETYPE_ID const only — no FSRS logic reads archetype | XS |
| /api/review | untouched — no archetype reads in grade/user-action paths | 0 |
| /api/sessions loader + carousel-card-adapter | expose/rename the discriminator on CarouselCardV36 | S |
| UI dispatch (RevealView, alphabet routing, filters) | switch on new discriminator | S–M |
| Docs | TERMINOLOGY archetype/field-matrix/CT sections; ADR-081 superseded-in-part; ADR-082 amended (tier+parent); CLAUDE.md overview block — F1–F12 cleanup should ride along | M |
| Untouched | ts-fsrs, t310 shape, CL/GS/US axes, drip RPC, t390, counters, all games' grade paths | — |
Verdict: pre-launch-cheap on data and code, moderate on docs/consensus. Real costs are (a) partially unwinding a 3-week-old ADR-081 decision — needs an honest superseding ADR, not a quiet edit; (b) the doc reconciliation pass (a PLAN-159-style scoped sweep is the right altitude for F1–F12). Migration risk is low: no user data tables touched (t310 unchanged), corpus is re-derivable, pre-launch rules allow destructive schema ops. Do it before CA6/CA4 ship — every new archetype landed on the conflated enum raises the price.
Three options considered:
W4-A — Frontmatter-driven generation. Add canonical:true + owns: to each cardinal's frontmatter; a script regenerates the registry table from a vault scan. ~half day. Risk: frontmatter rot moves the problem down a layer; prose "what it owns" degrades in YAML; generated file may conflict with hand edits.
W4-B — /audit weekly diff step (recommended). Extend /audit --weekly to cross-check registry rows ↔ files claiming canonical status, flag additions/removals/archived-ADR drift (would've caught the ADR-057 row), flag the registry's own last_verified. Small effort. Risk: detects drift, doesn't prevent it.
W4-C — Write-time hook/skill gate. /doc prompts "is this canonical?" when writing to references/ or accepting an ADR. Small effort. Risk: catches additions only, not the archival/supersedure case that actually bit.
These are the 7 codable questions the matrix work surfaced, verbatim from the draft. Each has its own options + note + submit.
Two open tensions block ADR-082 acceptance. Context/full mockup: passage-card-ca6-checkpoint →