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.
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.
exporttypeSpellTradition='ARCANE'|'DIVINE'|'OCCULT'|'PRIMAL';exportinterfaceSpellSlotOverlayEntry{level: number;spellSlotIncrement?:{tradition: SpellTradition;spellLevel: number;count: number};cantripIncrement?: number;repertoireIncrement?: number;}exportconstSPELL_SLOT_OVERLAY: Record<string,SpellSlotOverlayEntry[]>={Wizard:[...19entries...],// <-- 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
};
exportinterfaceClassFeatureOptionEntry{optionsRef: string;optionKey: string;name: string;nameGerman?: string;description: string;grants: string[];proficiencyChanges?: Partial<Record<'fortitude'|'reflex'|'will'|'perception'|'classDc'|'ac',Proficiency>>;}exportconstCLASS_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 checkcd 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