Checkpoint 1 · Card Taxonomy Matrix

Card Taxonomy Matrix — CA × CT × FSRS (+ round-2 decisions)

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.

21-cell CA × CT grid 8 undefined/under-specified cells 12 doc contradictions (F1–F12) Shape α vs Shape β refactor: pre-launch-cheap 7 decision items
Checkpoints Latest →
1 · Inventory

What's on the board today

Two axes ride on every card, plus five state systems layered on top. This is the vocabulary the rest of the page uses.

1a · Card Archetypes (CA) — what the card DISPLAYS

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).

IDNamePlain-EnglishTierShipped?
CA1Sense cardOne discrete meaning of a word — the primary cardCT2
CA2Family (form) cardAn inflected/derived spelling, drilled as its own cardCT3🟡 label ships, studiable-forms deferred (F6)
CA3Phrase cardMulti-word sense / collocation, same rendererCT2
CA4Vocab cardL1=L2 vocab / domain jargon ("merge conflict")CT2 per docs — but your framing wants all 3 tiers📋 future
CA5Character cardA letter/cluster/script unit + pronunciation; alphabet renderer, not sense rendererCT2✅ (31 live, Indonesian)
CA6Passage card (proposed)A formatted, sectioned passage/rules-set revealed in blocksnot stated (F12)📋 proposed, ADR-082
— none —Stored 'lemma'Container row exists live but has no CA number — "not a displayed card" yet "always schedulable"CT1live

1b · Card Tiers (CT) — the row-kind on t070

IDNameGlossparent_card_id
CT1LemmaHeadword/container row — frequency, content-state, enrichment versionNULL
CT2SenseOne discrete meaning — the primary FSRS learning unit→ CT1
CT3FormInflected/derived surface form under a sense→ CT2

1-indexed, no CT0; plant-metaphor words are banned on this axis (CP-002/CP-009).

Plain English Every card also carries five state systems riding on ONE t310 row: FSRS's own internal jargon (new/learning/review/relearning — never shown to you), your CL10–CL80 lifecycle (catalog → candidate → held → intro → active → learned → paused/retired), the growth-stage emoji ladder GS1–GS7 (seed → tree, plus a wilting flag), your five user levers (active/priority/learned/paused/retired), and the T10–T100 transition wiring between all of it. All of that is shared machinery — CA/CT don't change how it works, only what gets displayed and which entry gates a card can reach.
2 · The full matrix

2a — CA × CT validity grid

✅ valid + defined · ⚠️ valid but under-specified · ❌ impossible by definition · ∅ undefined — the gap IS the point.

CT1 LemmaCT2 SenseCT3 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.

2b

State reachability per combo

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.

ComboCL states reachable todayNotable caveats
CA1/CA3 × CT2CL10→CL80, all reachableWith CT3 forms: CL60 needs stability ≥120 AND all forms learned; can be GS6 Tree while still CL50
CA5 × CT2CL10→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 × CT3CL10–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 × CT1Contradiction — 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 pathFlat custom decks (CP-005 A3.3) require CT1 to be studiable — see F5
2c

Outcomes per combo

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.

2d · Don't smooth these over

12 contradictions found while building the matrix

These are doc-vs-doc or doc-vs-live conflicts surfaced while cross-checking every cell — not new opinions.

FlagContradictionWhere
F1T25 defined two ways — TERMINOLOGY says CL30→CL40 "priority skip"; CARD-LIFECYCLE-FLOW says CL10→CL30 "priority star"TERMINOLOGY:420 vs CLF § 4
F2T72 defined two ways — TERMINOLOGY says CL70→CL30; flow cardinal says CL70→CL50 (and that's what's WIRED)TERMINOLOGY:428 vs CLF § 4 T72
F3T20 "drip-select CL20→CL30" vs flow-cardinal T20 writes released_at (= CL40) — the CL20/CL30/CL40 boundary is muddyTERMINOLOGY:419 vs CLF § 4 T20
F4TERMINOLOGY says CL20 rows can have is_candidate=true — no such column exists in the t310 column modelTERMINOLOGY:393 vs FSRS-CARD-STATE § 4
F5CT1 "always-schedulable, never skipped" vs live drip releasing card_tier=2 only — CT1 has no wired release pathCLF § 3 vs § 5
F6CA2 marked "🟢 Shipped 2026-05-22" while studiable forms are deferred — only display-only RELATED FORMS shippedTERMINOLOGY CA2 row vs CLF § 7
F7US3 Learned "4× scheduling modifier" — that branch was removed from getSchedulingModifier (returns 1.0 now)TERMINOLOGY:468 vs FSRS-CARD-STATE § 7
F8TERMINOLOGY § Card Lifecycle still says "card archetype (CA1–CA4 above)" — stale, taxonomy is CA1–CA6TERMINOLOGY:407
F9TERMINOLOGY § Session still cites ADR-057 as the live contract; ADR-057 is archived/historical per CLAUDE.md's FSRS canonical resetTERMINOLOGY:190,214
F10The 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
F11Field-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-scopedTERMINOLOGY field matrix vs ADR-082 § 2
F12ADR-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)
3 · Your redundancy hypothesis

Evaluated honestly

Your claim: "The lemma and sense CA are redundant. Should be represented by CT. Vocab has lemma, sense, and form."

Steelman — you're substantially right at the storage layer

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.

Stress-test — where CA genuinely carries info CT doesn't

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.

The tension, named — not resolved

Two coherent target shapes compete. Neither is obviously wrong; picking one is item M1 below.

Shape α — derived display label

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.

Shape β — two orthogonal columns

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).

4 · Refactor scoping

What actually moves if you pick a shape

SurfaceWhat changesSize
SchemaCHECK swap (α) or column split + backfill (β) + COMMENT ON COLUMN; corpus ~1.1K cards + 31 script_units — minutes of SQLS (α) / M (β)
verify-enrichment.ts + stamp sitesre-scope discriminatorsS–M
fsrs.tsCARD_ARCHETYPE_ID const only — no FSRS logic reads archetypeXS
/api/reviewuntouched — no archetype reads in grade/user-action paths0
/api/sessions loader + carousel-card-adapterexpose/rename the discriminator on CarouselCardV36S
UI dispatch (RevealView, alphabet routing, filters)switch on new discriminatorS–M
DocsTERMINOLOGY archetype/field-matrix/CT sections; ADR-081 superseded-in-part; ADR-082 amended (tier+parent); CLAUDE.md overview block — F1–F12 cleanup should ride alongM
Untouchedts-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.

5 · Keeping the registry alive (W4)

_CANONICAL-DOCS.md is already demonstrably stale (F10)

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.

Recommendation W4-B now (one audit step, catches today's known drift class), with W4-A's frontmatter flag added opportunistically so a generator stays possible later. W4-C is complementary but doesn't cover the archived-ADR case that actually bit.
Decisions

What's explicit vs. inferred going into this checkpoint

1
FSRS/t310/API stay untouched by this refactor regardless of shape choice
Verified: no archetype reads exist in the grade or user-action paths (ADR-082 A5 + fsrs.ts audit).
Decided by Frank
2
The matrix should surface contradictions rather than silently reconcile them
Your "don't smooth these over" framing on prior review rounds — F1–F12 are reported as found, not pre-resolved.
Inferred from what you said
3
This is a review artifact, not an implementation kickoff
You asked for "an extensive HTML" to read on your phone — nothing here has been implemented; M1–M7 are open questions waiting on your ruling.
Inferred from what you said
6 · Decision items

Open questions — answer any subset, one click each

These are the 7 codable questions the matrix work surfaced, verbatim from the draft. Each has its own options + note + submit.

M1
Archetype axis shape
(a) keep stored enum as-is, just add "passage". (b) Shape α — derive lemma/sense/form from tier, store CT2 overrides only. (c) Shape β — split into card_tier × content_domain. Draft's lean: β if CA4 vocab-with-forms is real; α if minimal.
M2
CA4 vocab structure
(a) full 3-tier lemma/sense/form as your framing says. (b) flat CT1 deck (CP-005 A3.3 pattern). (c) CT2-only as currently documented.
M3
CT1 as a studied card
(a) keep "always-schedulable" — define what a CT1 card displays + wire a drip path. (b) retract CP-005 A2.1 — CT1 is container-only except flat custom decks. (c) defer.
M4
CA6 tier + parent (blocks ADR-082 acceptance either way)
(a) CT2 under a synthetic CT1 container. (b) CT1 flat-deck row (fits "the CT1 IS the study unit"). (c) new tier-exempt rule.
M5
Registry mechanism (_CANONICAL-DOCS.md keep-alive)
W4-A / W4-B / W4-C / B+A as recommended above in § 5.
M6
Contradiction cleanup route (F1–F12)
(a) scoped reconciliation plan now (docs_match_reality favors this). (b) fold into the refactor plan. (c) fix only the transition-table conflicts (F1–F4) now.
M7
HTML matrix artifact form (this page is the answer to this question)
(a) checkpoint page via the checkpoint agent — clickable URL, matches your review workflow. (b) static page in qa-exploration/. (c) the markdown draft alone is enough.
7 · Re-asked from last round

A4 — ADR-082 Passage Card rulings (still unanswered)

Two open tensions block ADR-082 acceptance. Context/full mockup: passage-card-ca6-checkpoint →

A4a
Archetype name
Keep "Passage Card" (working name in ADR-082), or rename it. No prior "passage/rules card/checklist/sectioned/formatted" term exists — no collision either way.
A4b
Content shape / formatting library
(A) Structured blocks, stored in archetype_extras.passage — recommended: no HTML sink, every text field auto-escaped, no sanitizer needed, LLM-enrichment-friendly. (B) Sanitized markdown fallback — react-markdown@10.1.0 + remark-gfm@4.0.1 + rehype-sanitize@6.0.0, mandatory sanitize allowlist, only if authors need freeform rich text the block schema can't express. (C) markdown-it + DOMPurify — already rejected in the ADR as heavier with an SSR DOM caveat, listed here only for completeness. Security rule regardless of pick: never dangerouslySetInnerHTML; if B is ever adopted, rehype-sanitize is mandatory and rehype-raw/MDX are prohibited.
💬
Anything else before I start /digest-ing and closing the remaining board? — free note, not tied to a specific item above.