Files
Dimension-47/.planning/phases/01-level-up-pf2e-regelkonform/01-03b-PLAN.md
Alexander Zielonka a8c4944bb6
All checks were successful
Deploy Dimension47 / deploy (push) Successful in 49s
chore(phase-01): checkpoint after Wave 3 Task 1 — pause for resume on another machine
Phase 1 execution state at pause:
- Wave 1 ✓ (01-01 schema/migration, 01-02 pure-function lib)
- Wave 2 ✓ (01-03 seed pipeline + Wizard worked example)
- Wave 3 partial (01-03b Task 1 of 3 done — 6 caster overlays salvaged from c5d40cd)
- Wave 4 pending (01-04 LevelingModule)
- Wave 5 pending (01-05 React wizard UI)

To resume: \`/gsd-execute-phase 1\` discovers SUMMARY files for completed plans
and picks up at 01-03b Task 2 (class-feature-options bulk curation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:13:50 +02:00

31 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, tags, must_haves
phase plan type wave depends_on files_modified autonomous requirements tags must_haves
01-level-up-pf2e-regelkonform 03b execute 3
01-01
01-02
01-03
server/prisma/data/spell-slot-overlays.ts
server/prisma/data/class-feature-options.ts
true
LVL-08
LVL-14
seeding
curation
spellcaster
class-features
level-up
data-only
truths artifacts key_links
After Plan 03b appends data and the seed re-runs, spell-slot-overlays.ts has L1..L20 entries for the 6 remaining caster classes (Cleric, Druid, Witch, Bard, Sorcerer, Oracle) and minimal Champion entries — total ≥120 spell-slot/cantrip/repertoire entries across all casters.
After Plan 03b appends data and the seed re-runs, class-feature-options.ts contains the joint-goal of ≥50 ClassFeatureOption entries across all classes that have L1 (or other) choice points (Plan 03 ships ≥1; Plan 03b ships ≥49 more).
Spontaneous casters (Bard, Sorcerer, Oracle) have at least one repertoireIncrement entry per the D-18 contract.
Re-running `npm run db:seed:class-progression` after the data appends results in `0 created, ≥320 updated` for ClassProgression and ≥49 newly created ClassFeatureOption rows on the first re-run.
No code changes to seed-class-progression.ts — the script's iteration over D16_CLASS_NAMES + array iteration over CLASS_FEATURE_OPTIONS already handles the new entries.
Cross-references are intact: every choiceOptionsRef set in seed-class-progression.ts L1_CHOICE_MAP has at least one matching ClassFeatureOption row after this plan.
path provides contains
server/prisma/data/spell-slot-overlays.ts Entries for Cleric/Druid/Witch/Bard/Sorcerer/Oracle/Champion appended (replacing the [] stubs from Plan 03). Wizard remains untouched. SPELL_SLOT_OVERLAY
path provides contains
server/prisma/data/class-feature-options.ts ≥49 additional ClassFeatureOption entries appended (Cleric Doctrines, Champion Causes, Druid Orders, Sorcerer Bloodlines, Bard Muses, Barbarian Instincts, Witch Patrons, Oracle Mysteries, Investigator Methodologies, Ranger Edges, Rogue Rackets, Swashbuckler Styles, Alchemist Research Fields, plus remaining Wizard Schools). CLASS_FEATURE_OPTIONS
from to via pattern
spell-slot-overlays.ts ClassProgression rows (via seed-class-progression.ts) seed re-run picks up the appended entries automatically SPELL_SLOT_OVERLAY
from to via pattern
class-feature-options.ts ClassFeatureOption table seed re-run iterates over CLASS_FEATURE_OPTIONS array CLASS_FEATURE_OPTIONS
from to via pattern
class-feature-options.ts optionsRef seed-class-progression.ts L1_CHOICE_MAP / HIGHER_LEVEL_CHOICES stable string match (e.g. 'cleric-doctrine') optionsRef.*[a-z-]+
Bulk curation pass for Phase 1 ClassProgression seeding. Plan 03 owns the pipeline + Wizard worked example; Plan 03b owns the data entry for the remaining 15 classes — populating `spell-slot-overlays.ts` for the 6 other caster classes and `class-feature-options.ts` for all classes with L1 (or other) choice points.

Why split: this is curation work transcribed from Archives of Nethys per-class entries. Independent rows, no shared logic, no schema changes. Splitting it out gives the curation a separate review surface (a reviewer can audit L1 Cleric Doctrines without paging through pipeline code) and isolates the data-entry pass into its own wave. Plan 03b runs in Wave 3 directly after Plan 03 (Wave 2) because both modify the same data files (spell-slot-overlays.ts, class-feature-options.ts), so file-ownership rules require sequential execution.

Purpose: After Plan 03b, the seed produces a complete dataset that satisfies LVL-08 + LVL-14 across all 16 D-16 classes — Plan 04's atomic commit transaction can recompute correctly for any class/level combination.

Output: Two data files extended with appended entries. No code changes. Re-running the seed bulk-updates the database.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/REQUIREMENTS.md @.planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md @.planning/phases/01-level-up-pf2e-regelkonform/01-RESEARCH.md @.planning/phases/01-level-up-pf2e-regelkonform/01-03-SUMMARY.md @server/prisma/data/spell-slot-overlays.ts @server/prisma/data/class-feature-options.ts @server/prisma/seed-class-progression.ts
export type SpellTradition = 'ARCANE' | 'DIVINE' | 'OCCULT' | 'PRIMAL';
export interface SpellSlotOverlayEntry {
  level: number;
  spellSlotIncrement?: { tradition: SpellTradition; spellLevel: number; count: number };
  cantripIncrement?: number;
  repertoireIncrement?: number;
}
export const SPELL_SLOT_OVERLAY: Record<string, SpellSlotOverlayEntry[]> = {
  Wizard: [ ... 19 entries ... ],   // <-- DO NOT TOUCH (Plan 03 owns Wizard)
  Cleric: [],                       // <-- Plan 03b populates this
  Druid: [],                        // <-- Plan 03b populates this
  Witch: [],                        // <-- Plan 03b populates this
  Bard: [],                         // <-- Plan 03b populates this (with repertoireIncrement entries — D-18)
  Sorcerer: [],                     // <-- Plan 03b populates this (with repertoireIncrement entries — D-18)
  Oracle: [],                       // <-- Plan 03b populates this (with repertoireIncrement entries — D-18)
  Champion: [],                     // <-- Plan 03b populates this (focus-only, minimal)
  Alchemist: [],                    // <-- Plan 03b confirms empty
  Barbarian: [],                    // <-- Plan 03b confirms empty
  Fighter: [],                      // <-- Plan 03b confirms empty
  Investigator: [],                 // <-- Plan 03b confirms empty
  Monk: [],                         // <-- Plan 03b confirms empty
  Ranger: [],                       // <-- Plan 03b confirms empty
  Rogue: [],                        // <-- Plan 03b confirms empty
  Swashbuckler: [],                 // <-- Plan 03b confirms empty
};
export interface ClassFeatureOptionEntry {
  optionsRef: string;
  optionKey: string;
  name: string;
  nameGerman?: string;
  description: string;
  grants: string[];
  proficiencyChanges?: Partial<Record<'fortitude' | 'reflex' | 'will' | 'perception' | 'classDc' | 'ac', Proficiency>>;
}
export const CLASS_FEATURE_OPTIONS: ClassFeatureOptionEntry[] = [
  // 1 Wizard School entry from Plan 03 (battle-magic) — DO NOT TOUCH
  // Plan 03b appends ≥49 more entries below
];
Task 1: Append spell-slot overlay entries for the 6 remaining caster classes (Cleric, Druid, Witch, Bard, Sorcerer, Oracle) + minimal Champion server/prisma/data/spell-slot-overlays.ts - server/prisma/data/spell-slot-overlays.ts (the Plan 03 file with stubs in place — append values into the existing keys) - .planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md (D-18 — spontaneous casters get repertoire) - .planning/phases/01-level-up-pf2e-regelkonform/01-RESEARCH.md (lines 656-661 — Pitfall #6 hand-curation rationale) - Archives of Nethys class entries (cited as authoritative source for hand-curation): - Cleric: https://2e.aonprd.com/Classes.aspx?ID=4 - Druid: https://2e.aonprd.com/Classes.aspx?ID=6 - Witch: https://2e.aonprd.com/Classes.aspx?ID=27 - Bard: https://2e.aonprd.com/Classes.aspx?ID=2 - Sorcerer: https://2e.aonprd.com/Classes.aspx?ID=15 - Oracle: https://2e.aonprd.com/Classes.aspx?ID=12 - Champion: https://2e.aonprd.com/Classes.aspx?ID=3 (URL IDs are illustrative — verify current canonical URLs at execution time.) Open `server/prisma/data/spell-slot-overlays.ts`. The file (from Plan 03) already declares all 16 D-16 keys; the 6 caster classes + Champion currently hold `[]`. **Replace each `[]` with the curated array** for that class.
**Reference template (Wizard, fully populated by Plan 03 — DO NOT modify Wizard):**

```typescript
Wizard: [
  { level: 1, cantripIncrement: 5 },
  { level: 1, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 1, count: 2 } },
  { level: 2, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 1, count: 1 } },
  { level: 3, spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 2, count: 2 } },
  // ... continues to L19 with the +2/+1 cadence
],
```

**Per-class curation requirements:**

**Cleric** (PREPARED, DIVINE) — same +2/+1 cadence as Wizard, replacing tradition with `'DIVINE'`. L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'DIVINE', spellLevel: 1, count: 2 }`. Continue per AoN Cleric spell-slot table through L19. Expected ~19-20 entries.

**Druid** (PREPARED, PRIMAL) — same cadence, tradition `'PRIMAL'`. L1: 5 cantrips + 2 grade-1 slots. Continue per AoN Druid table through L19. Expected ~19-20 entries.

**Witch** (PREPARED) — Witch tradition depends on patron (which the player picks at L1). For the overlay, default to `'ARCANE'` and document the caveat in a comment above the Witch array:
```typescript
// CAVEAT: Witch tradition depends on patron (Plan 03b — picked in wizard at L1).
// The overlay defaults to ARCANE for slot bookkeeping; the recompute pipeline (Plan 04)
// may need to remap this to the actual patron's tradition at commit time. For Phase 1
// we accept the default — the slot count is the same regardless of tradition.
```
Same +2/+1 cadence per AoN Witch table. Expected ~19-20 entries.

**Bard** (SPONTANEOUS, OCCULT) — D-18 requires `repertoireIncrement` entries on each level-up. L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'OCCULT', spellLevel: 1, count: 2 }`. From L2 onward, add `repertoireIncrement: 1` per level the spontaneous caster table grants a new spell-known. Per AoN Bard repertoire growth: typically +1 spell per spell-level per level after L2. Expected ~25-30 entries (slot rows + repertoire rows). Use multiple entries per level when both apply (e.g. L3 Bard gets a grade-2 slot AND a repertoire-pick — two separate entries).

**Sorcerer** (SPONTANEOUS) — Tradition depends on bloodline (similar caveat as Witch). Default to `'ARCANE'`. Per AoN Sorcerer table:
- L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'ARCANE', spellLevel: 1, count: 3 }` (Sorcerer L1 = 3 grade-1 slots)
- Add `repertoireIncrement` entries per level per AoN.
Document the bloodline-tradition caveat in a comment.
Expected ~25-30 entries.

**Oracle** (SPONTANEOUS, DIVINE) — Tradition is fixed (DIVINE — Mystery doesn't change it). L1: `cantripIncrement: 5` + `spellSlotIncrement: { tradition: 'DIVINE', spellLevel: 1, count: 3 }` (3 grade-1 slots like Sorcerer). Add `repertoireIncrement` entries per level per AoN. Expected ~25-30 entries.

**Champion** — focus-only caster (Devotion Spells via focus pool, NOT slot-based). Phase 1 records minimal entries:
```typescript
Champion: [
  // Focus-only caster -- focus-spell mechanics handled outside slot table (Plan 04 Phase 2 may
  // add focus-pool tracking). Phase 1 treats Champion as effectively non-caster for slots.
  // No cantrip/slot entries here.
],
```
Champion remains `[]` (or with the explanatory comment) — this is correct.

**Total expected entries across the 7 added classes:** 19 (Cleric) + 19 (Druid) + 19 (Witch) + 28 (Bard) + 28 (Sorcerer) + 28 (Oracle) + 0 (Champion) ≈ **141 new entries**, comfortably above the ≥120 must_haves goal.

**Constraints:**
- Do NOT modify the Wizard array (Plan 03 owns it).
- Do NOT remove any of the 16 keys (the seed depends on key presence, even if the value is `[]`).
- Every entry strictly typed against `SpellSlotOverlayEntry` — no `: any`.
- Cite the AoN source (URL or page) in a comment block above each newly-populated class.
cd server && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -E "spell-slot-overlays" || echo "tsc clean" - File `server/prisma/data/spell-slot-overlays.ts` still contains all 16 D-16 class keys (no key removed) - Wizard array still has at least 19 entries (Plan 03 unchanged) - Cleric, Druid, Witch arrays each have at least 18 entries - Bard, Sorcerer, Oracle arrays each have at least 1 `repertoireIncrement` entry (D-18) AND at least 18 entries total - Total entries across all caster classes is at least 120 (verifiable via grep counting `level:` keys outside the Wizard block) - File contains NO `: any` outside comments - `cd server && npx tsc --noEmit` exits 0 Spell-slot overlay file extended with curated data for the 6 other casters + Champion. Type-clean. Wizard unchanged. Task 2: Append ClassFeatureOption entries for the 13 remaining classes (Cleric, Champion, Druid, Sorcerer, Bard, Barbarian, Witch, Oracle, Investigator, Ranger, Rogue, Swashbuckler, Alchemist) + remaining Wizard Schools server/prisma/data/class-feature-options.ts - server/prisma/data/class-feature-options.ts (the Plan 03 file — append into CLASS_FEATURE_OPTIONS array using the section anchors) - server/prisma/seed-class-progression.ts (Plan 03 — verify L1_CHOICE_MAP optionsRef strings match exactly) - .planning/phases/01-level-up-pf2e-regelkonform/01-CONTEXT.md (D-15 — choiceOptionsRef; D-19 — Wahl-Klassenmerkmale) - server/prisma/schema.prisma (verify ResearchField enum for Alchemist optionKey alignment) - Archives of Nethys class entries — see Task 1's URL list, plus: - Investigator: https://2e.aonprd.com/Classes.aspx?ID=8 - Ranger: https://2e.aonprd.com/Classes.aspx?ID=13 - Rogue: https://2e.aonprd.com/Classes.aspx?ID=14 - Swashbuckler: https://2e.aonprd.com/Classes.aspx?ID=16 - Alchemist: https://2e.aonprd.com/Classes.aspx?ID=1 - Barbarian: https://2e.aonprd.com/Classes.aspx?ID=2 (verify ID — this clashes with Bard above; use the canonical AoN landing page) Open `server/prisma/data/class-feature-options.ts`. The file (from Plan 03) already has the type definitions and 1 Wizard School entry. **Append additional entries to the `CLASS_FEATURE_OPTIONS` array**, organized into the section blocks the Plan 03 file already comments out.
**Per-class curation requirements (joint goal: ≥50 entries total across this file; Plan 03 ships 1 → Plan 03b ships ≥49 more):**

1. **Wizard Schools** (optionsRef: `'wizard-school'`) — Plan 03 has `battle-magic`. Add: `civic-wizardry`, `mentalism`, `protean-form`, `unified-magical-theory`, `universalist`. Expected: 5 more.

2. **Cleric Doctrines** (optionsRef: `'cleric-doctrine'`) — Player Core: `cloistered-cleric`, `warpriest`. Add any APG additions. Expected: 2-4.

3. **Champion Causes** (optionsRef: `'champion-cause'`) — `liberator`, `paladin`, `redeemer`, plus Evil-aligned `antipaladin`, `tyrant`, `desecrator`, plus any neutral or remaster-equivalent. Expected: 6.

4. **Druid Orders** (optionsRef: `'druid-order'`) — `animal`, `leaf`, `storm`, `wild`, plus APG additions. Expected: 4-5.

5. **Sorcerer Bloodlines** (optionsRef: `'sorcerer-bloodline'`) — `aberrant`, `angelic`, `demonic`, `diabolic`, `draconic`, `elemental`, `fey`, `genie`, `hag`, `imperial`, `nymph`, `phoenix`, `psychopomp`, `shadow`, `undead`, plus APG additions. Expected: ≥15.

6. **Bard Muses** (optionsRef: `'bard-muse'`) — `enigma`, `maestro`, `polymath`, plus APG (`warrior` etc.). Expected: 3-4.

7. **Barbarian Instincts** (optionsRef: `'barbarian-instinct'`) — `animal`, `dragon`, `fury`, `giant`, `spirit`, plus APG (`superstition` etc.). Expected: 5-6.

8. **Witch Patrons** (optionsRef: `'witch-patron'`) — `faith`, `fervor`, `mosquito-witch`, `resentment`, `silence`, `spinner-of-threads`, `starless-shadow`, `wilding`, plus any Curse-of-the-Hag-Eye etc. Expected: ≥5.

9. **Oracle Mysteries** (optionsRef: `'oracle-mystery'`) — `battle`, `bones`, `cosmos`, `flames`, `life`, `lore`, `tempest`, plus APG. Expected: ≥7.

10. **Investigator Methodologies** (optionsRef: `'investigator-methodology'`) — `empiricism`, `forensic-medicine`, `interrogation`, `sensate`, `stratagem` (or whatever exists in APG). Expected: 3-5.

11. **Ranger Edges** (optionsRef: `'ranger-edge'`) — `flurry`, `outwit`, `precision`. Expected: 3.

12. **Rogue Rackets** (optionsRef: `'rogue-racket'`) — `eldritch-trickster`, `mastermind`, `ruffian`, `scoundrel`, `thief`. Expected: 5.

13. **Swashbuckler Styles** (optionsRef: `'swashbuckler-style'`) — `battledancer`, `braggart`, `fencer`, `gymnast`, `wit`. Expected: 5.

14. **Alchemist Research Fields** (optionsRef: `'alchemist-research-field'`) — `bomber`, `chirurgeon`, `mutagenist`, `toxicologist`. **CRITICAL:** these `optionKey` values must match the existing `ResearchField` enum in `server/prisma/schema.prisma` (lowercase-kebab transformation of `BOMBER`, `CHIRURGEON`, etc.) so the existing `CharacterAlchemyState.researchField` column can be set from the option pick. Verify the enum values against the schema before appending. Expected: 4.

**Total entry count check:** 5 + 4 + 6 + 5 + 15 + 4 + 6 + 5 + 7 + 5 + 3 + 5 + 5 + 4 = **79 entries** added. Plus Plan 03's 1 Wizard School entry = **80 total** — comfortably above the ≥50 joint goal.

**Per-entry shape:**

```typescript
{
  optionsRef: 'cleric-doctrine',
  optionKey: 'cloistered-cleric',
  name: 'Cloistered Cleric',
  // nameGerman omitted — TranslationsService handles on demand (D-15)
  description: 'You have devoted your life to the study of religion and the casting of divine spells…',
  grants: ['Cloistered Cleric Doctrine'],
  // proficiencyChanges optional — only set if the option modifies a proficiency level at L1 (e.g. Warpriest grants martial weapon trained)
},
```

For options with proficiency effects (e.g. **Warpriest** grants martial trained at L1):
```typescript
{
  optionsRef: 'cleric-doctrine',
  optionKey: 'warpriest',
  name: 'Warpriest',
  description: 'You are a martial defender of your faith…',
  grants: ['Warpriest Doctrine'],
  proficiencyChanges: { /* if a proficiency bump applies at L1, encode it here */ },
},
```

**Constraints:**
- Append only — do NOT modify the existing Wizard School `battle-magic` entry from Plan 03.
- `optionsRef` strings MUST match the L1_CHOICE_MAP / HIGHER_LEVEL_CHOICES values in `seed-class-progression.ts` (Plan 03 Task 4) — copy them verbatim.
- `optionKey` values: lowercase-kebab of the option's English name. Once chosen, do not rename — these are stable identifiers persisted in CharacterFeat rows after commit.
- Cite the AoN source URL in a comment block above each class section.
- No `: any` outside comments.
- Every entry must have non-empty `optionsRef`, `optionKey`, `name`, `description`, `grants` fields.
cd server && npx tsc --noEmit -p tsconfig.json 2>&1 | grep -E "class-feature-options" || echo "tsc clean" - File `server/prisma/data/class-feature-options.ts` exists and has been extended - Plan 03's `battle-magic` entry is still present (unchanged) - Total `CLASS_FEATURE_OPTIONS.length` is at least 50 (joint goal across Plans 03 and 03b) - At least 13 distinct `optionsRef` values appear: `wizard-school`, `cleric-doctrine`, `champion-cause`, `druid-order`, `sorcerer-bloodline`, `bard-muse`, `barbarian-instinct`, `witch-patron`, `oracle-mystery`, `investigator-methodology`, `ranger-edge`, `rogue-racket`, `swashbuckler-style`, `alchemist-research-field` - For Alchemist: the four optionKey values are exactly `bomber`, `chirurgeon`, `mutagenist`, `toxicologist` (matches `ResearchField` enum lowercase-kebab — verify against `server/prisma/schema.prisma`) - Every entry has non-empty `optionsRef`, `optionKey`, `name`, `description`, `grants` fields (verify with a quick scan) - File contains NO `: any` outside comments - `cd server && npx tsc --noEmit` exits 0 Class-feature options file extended with ≥49 new entries across all 13 remaining classes (plus the rest of Wizard Schools). All optionsRef strings match seed-class-progression.ts L1_CHOICE_MAP. Alchemist optionKeys cross-validate against ResearchField enum. Task 3: Re-run the seed and verify cumulative row counts (no file writes — execution + verification only) - .planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md (verify Foundry clone is in place from Plan 03 Task 5) - server/prisma/seed-class-progression.ts (Plan 03 — generic over D16_CLASS_NAMES + CLASS_FEATURE_OPTIONS, no changes needed) **Step 1 — Verify the Foundry clone is still present:**
```bash
ls server/prisma/data/foundry-pf2e/packs/classes/
```

Should still list ≥16 .json files from Plan 03's Task 5 step 1.

**Step 2 — Run the seed:**

```bash
cd server && npm run db:seed:class-progression
```

Expected output (Plan 03 already populated 320 ClassProgression rows + 1 ClassFeatureOption row):

```
Seeding ClassProgression for 16 classes x 20 levels...
  ClassProgression: 0 created, 320 updated, 0 errors
Seeding ~80 ClassFeatureOption rows...
  ClassFeatureOption: ~79 created, 1 updated, 0 errors
ClassProgression + ClassFeatureOption seed complete.
```

The 320 ClassProgression rows are UPDATED in place — the new spell-slot/cantrip/repertoire data from Task 1's overlay appends gets merged into the existing rows.

**Step 3 — Run the seed AGAIN to confirm idempotency:**

```bash
cd server && npm run db:seed:class-progression
```

Expected: `0 created, 320 updated, 0 errors` for both tables.

**Step 4 — Verify ClassProgression row counts and overlay data:**

```bash
psql $DATABASE_URL -c 'SELECT "className", COUNT(*) FROM "ClassProgression" GROUP BY "className" ORDER BY "className"'
```

Expected: 16 rows, each `count = 20`.

```bash
psql $DATABASE_URL -c 'SELECT "className", COUNT(*) FROM "ClassProgression" WHERE "spellSlotIncrement" IS NOT NULL GROUP BY "className" ORDER BY "className"'
```

Expected: rows for `Bard`, `Cleric`, `Druid`, `Oracle`, `Sorcerer`, `Witch`, `Wizard` (no `Champion` since it's focus-only; no non-casters). Each caster should have ≥18 rows.

```bash
psql $DATABASE_URL -c 'SELECT "className", COUNT(*) FROM "ClassProgression" WHERE "repertoireIncrement" IS NOT NULL GROUP BY "className" ORDER BY "className"'
```

Expected: rows for `Bard`, `Sorcerer`, `Oracle` (the three spontaneous casters per D-18).

**Step 5 — Verify ClassFeatureOption row counts:**

```bash
psql $DATABASE_URL -c 'SELECT "optionsRef", COUNT(*) FROM "ClassFeatureOption" GROUP BY "optionsRef" ORDER BY "optionsRef"'
```

Expected: at least 13 distinct `optionsRef` values (one per class with an L1 choice point), each with ≥2 options. Total row count ≥50.

**Step 6 — Cross-reference verification (optional but recommended):**

```bash
# Every choiceOptionsRef in ClassProgression should have at least one matching ClassFeatureOption
psql $DATABASE_URL -c '
  SELECT cp."choiceOptionsRef", COUNT(cfo.id) AS option_count
  FROM "ClassProgression" cp
  LEFT JOIN "ClassFeatureOption" cfo ON cfo."optionsRef" = cp."choiceOptionsRef"
  WHERE cp."choiceOptionsRef" IS NOT NULL
  GROUP BY cp."choiceOptionsRef"
  ORDER BY cp."choiceOptionsRef"
'
```

Expected: every `choiceOptionsRef` row has `option_count >= 1`. If any has 0, a class chose a choiceType but no options exist — append the missing options to class-feature-options.ts and re-run the seed.

If any verification step fails, fix the curation data and re-run.
cd server && psql $DATABASE_URL -t -c 'SELECT COUNT(*) FROM "ClassFeatureOption"' | tr -d ' \n' - `cd server && npm run db:seed:class-progression` exits 0 - First Plan 03b run reports `0 created, 320 updated` for ClassProgression (Plan 03 already created the rows; Plan 03b updates them with new overlay data) - First Plan 03b run reports `≥49 created` for ClassFeatureOption (joint with Plan 03's 1 entry → ≥50 total) - Second run reports `0 created, ≥320 updated` for ClassProgression and `0 created, ≥50 updated` for ClassFeatureOption (idempotency) - `psql $DATABASE_URL -t -c 'SELECT COUNT(*) FROM "ClassFeatureOption"'` outputs at least 50 - `psql $DATABASE_URL -t -c 'SELECT COUNT(DISTINCT "optionsRef") FROM "ClassFeatureOption"'` outputs at least 13 - For all 7 caster classes (Bard, Cleric, Druid, Oracle, Sorcerer, Witch, Wizard): the count of ClassProgression rows with non-null spellSlotIncrement is ≥18 - For Bard, Sorcerer, Oracle: count of ClassProgression rows with non-null repertoireIncrement is ≥1 (D-18) - Every `choiceOptionsRef` declared on a ClassProgression row has at least one matching ClassFeatureOption row (cross-reference query passes) Seed re-run succeeds; full dataset of ≥320 ClassProgression rows (with overlay data merged for all casters) and ≥50 ClassFeatureOption rows is present in the DB; idempotency holds; cross-references intact. Plan 04 can rely on the complete dataset for any (className, level) recompute.

<threat_model>

Trust Boundaries

Boundary Description
dev-shell → seed script Same boundary as Plan 03 — developer runs the seed with DB write privilege.
Hand-curated data → DB Curation can have transcription errors (wrong slot count, typo'd optionKey).

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-1-W2b-01 Tampering Hand-curation transcription error (e.g. wrong slot count for Bard L5) mitigate Plan 04's integration tests assert ClassProgression-driven recompute results for Wizard L1 + at least one spontaneous caster. Bugs surface there. Curator double-checks against AoN before committing.
T-1-W2b-02 Tampering Stable optionKey is renamed after persist (orphaning CharacterFeat rows) mitigate This task's acceptance criteria + the comment in Plan 03's class-feature-options.ts both flag optionKey as stable; once committed in production, optionKey changes require a migration. Plan 04 LVL-V2-02 (reverse-level-up) will exercise the read path.
T-1-W2b-03 Information Disclosure (No new exposure beyond Plan 03's accept disposition.) accept Same trust model as Plan 03 — self-hosted single-tenant.
T-1-W2b-04 Repudiation Curated data quality drift over time (e.g. Witch tradition changes in a remaster) mitigate The Witch / Sorcerer "tradition depends on patron/bloodline" caveats are documented inline; recompute pipeline (Plan 04) treats the overlay as a starting point and may remap at commit. Phase 1 accepts the default.
</threat_model>
After Tasks 1-3 complete:
# Type-check clean (no shape changes; just data appends)
cd server && npx tsc --noEmit -p tsconfig.json

# Idempotency check
cd server && npm run db:seed:class-progression && npm run db:seed:class-progression

# Joint row counts after both Plan 03 and Plan 03b
psql $DATABASE_URL -c 'SELECT COUNT(*) FROM "ClassProgression"'        # >= 320
psql $DATABASE_URL -c 'SELECT COUNT(*) FROM "ClassFeatureOption"'      # >= 50

# Caster verification
psql $DATABASE_URL -c 'SELECT className, COUNT(*) FROM "ClassProgression" WHERE "spellSlotIncrement" IS NOT NULL GROUP BY className'
# Expected: 7 rows (Bard, Cleric, Druid, Oracle, Sorcerer, Witch, Wizard), each count >= 18

# Spontaneous caster repertoire (D-18)
psql $DATABASE_URL -c 'SELECT className, COUNT(*) FROM "ClassProgression" WHERE "repertoireIncrement" IS NOT NULL GROUP BY className'
# Expected: 3 rows (Bard, Sorcerer, Oracle), each count >= 1

# Cross-reference integrity
psql $DATABASE_URL -c '
  SELECT cp."choiceOptionsRef", COUNT(cfo.id)
  FROM "ClassProgression" cp
  LEFT JOIN "ClassFeatureOption" cfo ON cfo."optionsRef" = cp."choiceOptionsRef"
  WHERE cp."choiceOptionsRef" IS NOT NULL
  GROUP BY cp."choiceOptionsRef"
  HAVING COUNT(cfo.id) = 0
'
# Expected: empty result (no orphaned choiceOptionsRef)

<success_criteria>

  • spell-slot-overlays.ts has curated entries for Cleric, Druid, Witch, Bard, Sorcerer, Oracle (≥18 entries each, ≥120 total beyond Wizard); Champion remains minimal/empty per design; non-casters confirmed empty
  • class-feature-options.ts has ≥50 total entries (Plan 03's 1 + Plan 03b's ≥49) across ≥13 distinct optionsRef values
  • Re-running the seed bulk-updates the database without touching the seed script
  • Idempotency holds — second run reports 0 created
  • All cross-references intact: every choiceOptionsRef in ClassProgression has at least one matching ClassFeatureOption
  • Plan 04 LevelingService can recompute correctly for any of the 16 D-16 classes at any level 1..20 </success_criteria>
After completion, create `.planning/phases/01-level-up-pf2e-regelkonform/01-03b-SUMMARY.md` documenting: - Final cumulative row counts (ClassProgression by className + total; ClassFeatureOption by optionsRef + total) - Any deviations from the planned overlay contents (e.g. Witch tradition default, Sorcerer bloodline default) - Any classes/levels where AoN data was ambiguous and a documented choice was made - Confirmation idempotency observed on second run - Confirmation cross-reference integrity check passed