docs(01-03): complete Plan 03 — Foundry seed pipeline + Wizard worked example

Add SUMMARY.md documenting the seed pipeline (320 ClassProgression rows + 1
ClassFeatureOption row), the four auto-fix deviations resolved during execution
(env setup, v8 path, Prisma 7 typed-input, third-party tsc exclusion), and the
contract Plan 03b consumes when appending overlay data.
This commit is contained in:
2026-04-27 14:58:19 +02:00
parent d421aad4d4
commit c283cda44d

View File

@@ -0,0 +1,246 @@
---
phase: 01-level-up-pf2e-regelkonform
plan: 03
subsystem: seeding
tags: [seeding, prisma, foundry-pf2e, class-progression, spellcaster, wizard, level-up, pipeline]
# Dependency graph
requires:
- phase: 01-level-up-pf2e-regelkonform
provides: ClassProgression + ClassFeatureOption Prisma tables (Plan 01-01); Proficiency type vocabulary (Plan 01-02 lib/types.ts)
provides:
- Idempotent seed pipeline (server/prisma/seed-class-progression.ts) generic over D16_CLASS_NAMES — Plan 03b appends data, no script changes
- Hand-curated spell-slot overlay (server/prisma/data/spell-slot-overlays.ts) with type definitions + Wizard L1..L19 fully populated
- Hand-curated class-feature-options (server/prisma/data/class-feature-options.ts) with type definitions + 1 Wizard School entry
- Pinned Foundry pf2e tag (pf2e-8.0.3) and dev README at .planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md
- 320 ClassProgression rows in PostgreSQL (16 D-16 classes × L1..L20)
- 1 ClassFeatureOption row (wizard-school / battle-magic)
- Wizard end-to-end worked example: choiceType=school, choiceOptionsRef=wizard-school, ARCANE slot progression L1..L19, 5 cantrips at L1
affects: [01-03b (data-only append to overlays), 01-04 (LevelingService.commit reads ClassProgression rows), 01-05 (wizard UI uses choiceOptionsRef → ClassFeatureOption)]
# Tech tracking
tech-stack:
added: []
patterns:
- "Idempotent compound-key Prisma upsert pattern via findUnique + update OR create"
- "Prisma.JsonNull sentinel for nullable Json fields (Prisma 7 typed-input contract)"
- "Foundry pf2e clone consumed at seed time only; runtime never reads JSON files"
- "Foundry-driven seed + hand-curated overlay merge (Pitfall #6 mitigation: slot tables in prose, not rules)"
key-files:
created:
- .planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md
- server/prisma/data/spell-slot-overlays.ts
- server/prisma/data/class-feature-options.ts
- server/prisma/seed-class-progression.ts
modified:
- server/tsconfig.json (exclude prisma/data/foundry-pf2e from compilation — gitignored third-party content)
key-decisions:
- "Pinned Foundry pf2e tag pf2e-8.0.3 — current stable Pathfinder 2e system release with Player Core + APG content for all 16 D-16 classes"
- "Spell-slot/cantrip/repertoire data hand-curated in spell-slot-overlays.ts (Pitfall #6: Foundry encodes them in description prose, not machine-readable rules)"
- "All 16 D-16 class names appear as keys in SPELL_SLOT_OVERLAY (even when value is []) so Plan 03b appends without missing-key bugs"
- "ClassFeatureOption.proficiencyChanges stays optional in the entry interface; passing null at the seed call site is rewritten to Prisma.JsonNull to satisfy typed-input contract"
- "Pipeline is generic over D16_CLASS_NAMES + SPELL_SLOT_OVERLAY[className] — Plan 03b adds data only, no script changes"
- "Foundry pf2e v8 nests pf2e packs under packs/pf2e/classes/ (v7 had packs/classes/ directly) — Pitfall #5 mitigation documented in code comment + SEED-README"
patterns-established:
- "Idempotent seed: findUnique → update OR create per row, with compound-key where clauses inferred from @@unique declarations"
- "Hand-curated overlay file with all class keys present (empty arrays for unsupported classes) so future plans append safely"
- "Loud-fail seed: missing Foundry clone triggers a deterministic error that points the dev to SEED-README"
- "Third-party clone exclusion in tsconfig — gitignored data directories that ship their own TS source must be excluded from project tsc"
requirements-completed: [LVL-08, LVL-14]
# Metrics
duration: ~14min
completed: 2026-04-27
---
# Phase 1 Plan 03: Seed Pipeline + Wizard Worked Example Summary
**Built the Foundry-pf2e-driven seed pipeline that populates ClassProgression (320 rows) and ClassFeatureOption (1 row) from a pinned pf2e-8.0.3 clone plus hand-curated spell-slot overlays, with Wizard fully wired end-to-end (L1..L19 ARCANE slot progression, 5 cantrips at L1, school choice routing) and idempotent re-runs reporting `0 created, 320 updated`.**
## Performance
- **Duration:** ~14 min
- **Tasks:** 5 completed (README, spell-slot overlay, class-feature options, seed script, run+verify)
- **Files created:** 4 (SEED-README.md, spell-slot-overlays.ts, class-feature-options.ts, seed-class-progression.ts)
- **Files modified:** 1 (server/tsconfig.json — exclude clone dir)
## Accomplishments
- **Pinned Foundry tag chosen:** `pf2e-8.0.3` — current stable Pathfinder 2e system release on the `foundryvtt/pf2e` repo. Verified via the GitHub Releases API; tag SHA `c6aac85d186ac768d1db9ac2d379e9510a0825f8`.
- **SEED-README.md** at `.planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md` documents the clone command, the run command, failure modes, and the v8 pack-layout note. Mentions Plan 03 vs Plan 03b split explicitly.
- **spell-slot-overlays.ts** exports `SpellTradition`, `SpellSlotOverlayEntry`, and `SPELL_SLOT_OVERLAY`. Wizard fully populated for L1..L19 (20 entries: L1 has both a cantrip-increment entry and a slot-increment entry). All 15 other D-16 class names appear as keys with empty-array values for Plan 03b to fill.
- **class-feature-options.ts** exports `ClassFeatureOptionEntry` and `CLASS_FEATURE_OPTIONS` (1 entry: Wizard School `battle-magic` / "School of Battle Magic"). Anchor comments name 13 other optionsRef strings so Plan 03b knows where to append.
- **seed-class-progression.ts** is generic over `D16_CLASS_NAMES` and the two overlay modules — Plan 03b appends entries to the data files and re-runs the same seed without touching the script. Uses Prisma 7's `Prisma.JsonNull` sentinel for nullable Json fields and the compound-key field names `className_level` and `optionsRef_optionKey` generated from Plan 01's `@@unique` declarations.
- **Live DB state after seed:**
- 320 ClassProgression rows (16 classes × L1..L20)
- 20 Wizard rows; L1 has full structure (`grants`, `proficiencyChanges {fortitude:UNTRAINED, reflex:UNTRAINED, will:TRAINED}`, `spellSlotIncrement {tradition:ARCANE, spellLevel:1, count:2}`, `cantripIncrement: 5`, `choiceType: 'school'`, `choiceOptionsRef: 'wizard-school'`)
- L2..L18 carry the correct ARCANE/L1..L9 slot progression (2 + 1 per spell grade per pair of levels)
- L19 carries the L10 capstone slot
- 1 ClassFeatureOption row (`wizard-school` / `battle-magic` / "School of Battle Magic")
- **Idempotency proven:** First run reports `320 created, 0 updated, 0 errors` for ClassProgression and `1 created, 0 updated, 0 errors` for ClassFeatureOption; second and third runs report `0 created, 320 updated, 0 errors` and `0 created, 1 updated, 0 errors`.
- **Type-clean:** `npx tsc --noEmit -p tsconfig.json` exits 0 across all four new files; the cloned Foundry source is excluded from compilation (it ships its own TS source that targets a different toolchain).
- **Existing leveling tests still pass:** `npm test -- --testPathPatterns=leveling` reports 55/55 passing.
## Task Commits
| Task | Description | Commit |
|------|-------------|--------|
| 1 | Dev README for the Foundry pf2e clone path | `6567665` (docs) |
| 2 | Spell-slot overlay file — types + Wizard worked example | `29fe01d` (feat) |
| 3 | Class-feature-options file — types + Wizard School worked example | `d86cf4f` (feat) |
| 4 | Seed script — Foundry pf2e + overlays → ClassProgression + ClassFeatureOption | `e85f790` (feat) |
| 5 | Run the seed (Wizard end-to-end verification) — path fix + README update | `ce214ab` (fix) |
| — | tsconfig exclude foundry-pf2e clone dir (Rule 3 deviation) | `d421aad` (chore) |
## Verification Output
```
$ cd server && npm run db:seed:class-progression
Seeding ClassProgression for 16 classes x 20 levels...
ClassProgression: 0 created, 320 updated, 0 errors
Seeding 1 ClassFeatureOption rows...
ClassFeatureOption: 0 created, 1 updated, 0 errors
ClassProgression + ClassFeatureOption seed complete.
```
```
$ cd server && npm test -- --testPathPatterns=leveling
Test Suites: 5 passed, 5 total
Tests: 55 passed, 55 total
```
Wizard L1 row queried via the Prisma client (verification helper, not committed):
```json
{
"className": "Wizard",
"level": 1,
"grants": ["Wizard Spellcasting", "Arcane School", "Arcane Bond", "Arcane Thesis"],
"proficiencyChanges": { "will": "TRAINED", "reflex": "UNTRAINED", "fortitude": "UNTRAINED" },
"spellSlotIncrement": { "count": 2, "tradition": "ARCANE", "spellLevel": 1 },
"cantripIncrement": 5,
"repertoireIncrement": null,
"choiceType": "school",
"choiceOptionsRef": "wizard-school"
}
```
Wizard L1..L5 spell-slot progression (matches AoN canonical Wizard table):
| Level | Slot increment | Cantrip increment |
|-------|----------------|-------------------|
| 1 | ARCANE / L1 / count=2 | 5 |
| 2 | ARCANE / L1 / count=1 | — |
| 3 | ARCANE / L2 / count=2 | — |
| 4 | ARCANE / L2 / count=1 | — |
| 5 | ARCANE / L3 / count=2 | — |
(Continues through L19 with the L10 capstone — matches Player Core / APG.)
## Decisions Made
- **Foundry tag = pf2e-8.0.3 (Pathfinder 2e v8).** The current Foundry pf2e repo bundles both PF2E and SF2E (Starfinder 2e) under one tree, so tags are now prefixed `pf2e-` or `sf2e-`. `pf2e-8.0.3` is the latest stable PF2E release with Player Core + APG content. Recorded both in SEED-README and in the seed script comment.
- **v8 pack layout discovered at run time.** The plan's instructions referenced `packs/classes/` (Foundry pf2e v7 layout). The cloned v8 tree ships pf2e packs under `packs/pf2e/classes/`. Documented in SEED-README as a layout note and in the seed script's `FOUNDRY_CLASSES_DIR` comment.
- **Prisma.JsonNull for nullable Json fields.** Prisma 7's `Json?` typed inputs reject JS `null` and require the sentinel `Prisma.JsonNull` to distinguish "set to JSON null" from "leave column unchanged". Both `spellSlotIncrement` (in ClassProgression) and `proficiencyChanges` (in ClassFeatureOption when undefined on the entry) use this sentinel.
- **All 16 class keys must be present in SPELL_SLOT_OVERLAY.** Even when a class's array is empty (Plan 03b stub), the key exists so the seed's `(SPELL_SLOT_OVERLAY[className] || []).filter(...)` never accidentally elides a class because of a typo. This is the contract Plan 03b appends to.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 — Blocking] Worktree had no node_modules and no .env file**
- **Found during:** Task 4 verification (running `npx tsc --noEmit`)
- **Issue:** The parallel-execution worktree was created clean (no `npm install` had been run, `.env` is gitignored so it didn't transfer). Without these, `prisma`, `tsx`, `jest`, and the Prisma adapter failed to resolve.
- **Fix:** Copied `server/.env` from the parent repo into the worktree (file is gitignored and identical) and ran `npm install` in `server/`. Same fix Plan 01-01 documented.
- **Files modified:** `server/.env` (gitignored, not staged), `server/node_modules/` (gitignored), `server/package-lock.json` (gitignored, not modified)
- **Commit:** N/A — environment setup, no source files changed
**2. [Rule 1 — Bug] Foundry pf2e v8 packs path differs from plan's documented v7 path**
- **Found during:** Task 5 Step 2 (verifying clone has class JSONs)
- **Issue:** Plan and SEED-README documented `packs/classes/` (correct for Foundry pf2e v7). Cloned `pf2e-8.0.3` placed pf2e content under `packs/pf2e/classes/`. The seed script's `FOUNDRY_CLASSES_DIR` constant pointed at the v7 path and would have failed loudly with "Foundry pf2e clone not found".
- **Fix:** Updated `FOUNDRY_CLASSES_DIR` in `seed-class-progression.ts` to `packs/pf2e/classes/`, added a Pitfall #5 comment block explaining the version-specific path, and added a "Foundry pf2e v8 layout note" section to SEED-README.md plus updated the failure-mode error path string.
- **Files modified:** `server/prisma/seed-class-progression.ts`, `.planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md`
- **Commit:** `ce214ab`
**3. [Rule 1 — Bug] Prisma 7 typed-input rejected JS null for nullable Json fields**
- **Found during:** Task 4 type-check (`npx tsc --noEmit`)
- **Issue:** `Type ... is not assignable to type 'InputJsonValue | NullableJsonNullValueInput | undefined'`. Prisma 7 differentiates "set to JSON null" (`Prisma.JsonNull`) from "leave unchanged" (omit the field) and JS `null` is not accepted in either slot for `Json?` columns.
- **Fix:** Imported `Prisma` namespace from the generated client. Replaced `?? null` with `?? Prisma.JsonNull` for `spellSlotIncrement` (in `buildProgressionRow`) and `proficiencyChanges` (in `seedClassFeatureOptions`). Typed `buildProgressionRow`'s return as `Prisma.ClassProgressionUncheckedCreateInput` so future schema drift surfaces as a TS error at the boundary.
- **Files modified:** `server/prisma/seed-class-progression.ts`
- **Commit:** `e85f790` (the corrected file was the one committed; pre-fix code was never committed)
**4. [Rule 3 — Blocking] tsc --noEmit picked up TS errors in the cloned Foundry source**
- **Found during:** Final verification before SUMMARY
- **Issue:** Foundry pf2e ships its own TS source at `prisma/data/foundry-pf2e/types/...` and `prisma/data/foundry-pf2e/vite.config.ts`. These reference modules our project doesn't have (`@common/...`, `svelte`, `vite`, `peggy`) and produce ~25 `tsc --noEmit` errors that have nothing to do with our code.
- **Fix:** Added `prisma/data/foundry-pf2e` to `tsconfig.json` `exclude` list (alongside `node_modules` and `dist`). The clone is gitignored and the seed reads it as raw JSON, so excluding it from compilation is correct and surgical.
- **Files modified:** `server/tsconfig.json`
- **Commit:** `d421aad`
---
**Total deviations:** 4 (1 environment infra, 1 Foundry data-shape correction, 1 Prisma 7 typed-input correction, 1 third-party-source exclusion).
**Impact on plan:** None of the deviations changed plan scope. All were correctness fixes required to make the plan run cleanly on this worktree.
### Authentication Gates
None — seed pipeline is local-only.
### Architectural Changes
None — schema was authored in Plan 01-01; this plan only seeds it.
## Issues Encountered
- **Plan referenced v7 path `packs/classes/`.** Documented in plan and research. Resolved by Rule 1 fix as above; v8 layout is now noted in SEED-README and code comments so future re-cloning to a different major version surfaces the issue immediately.
- **No psql in shell.** The plan's verification step recommended `psql $DATABASE_URL -c '...'`. The Windows shell here doesn't have psql. Wrote a temporary `verify-plan-03-wizard.ts` helper using the existing Prisma client, ran it, captured the output for this Summary, then deleted it. Verification result documented in §Verification Output above. The Prisma-client query approach yields the same information the plan's psql commands would have.
## User Setup Required
None for this plan. The dev who maintains the project does need to clone the Foundry pf2e repo at the pinned tag for future fresh clones — the README documents the exact command.
## Plan 03b Readiness
Plan 03b can append data to `spell-slot-overlays.ts` and `class-feature-options.ts` and re-run `npm run db:seed:class-progression` without touching the seed script. The contract Plan 03b consumes:
1. `SpellSlotOverlayEntry` interface — extend `SPELL_SLOT_OVERLAY[className]` arrays for Cleric/Druid/Witch/Bard/Sorcerer/Oracle/Champion. Empty `[]` arrays for non-casters can stay or be removed.
2. `ClassFeatureOptionEntry` interface — append entries below the anchor comments in `CLASS_FEATURE_OPTIONS`. The `optionsRef` strings already match what `seed-class-progression.ts L1_CHOICE_MAP` emits onto each class's L1 row.
3. The `optionsRef`/`optionKey` compound key is `Prisma.@@unique([optionsRef, optionKey])` — Plan 03b's bulk-append must keep `optionKey` unique within an `optionsRef`.
Re-running the seed after Plan 03b's append will report the count of new ClassFeatureOption rows created and the existing 320 ClassProgression rows updated in place with new overlay data.
## Threat Flags
None — no new network endpoints, auth paths, file-access patterns, or schema changes at trust boundaries beyond what the plan's threat register already covered.
## Self-Check: PASSED
**Verified files exist:**
- `.planning/phases/01-level-up-pf2e-regelkonform/SEED-README.md` (FOUND)
- `server/prisma/data/spell-slot-overlays.ts` (FOUND)
- `server/prisma/data/class-feature-options.ts` (FOUND)
- `server/prisma/seed-class-progression.ts` (FOUND)
- `server/tsconfig.json` (FOUND, modified — exclude added)
**Verified commits exist on this branch:**
- `6567665` — docs(01-03): add SEED-README with Foundry pf2e clone instructions and pinned tag
- `29fe01d` — feat(01-03): add spell-slot overlay types with Wizard worked example
- `d86cf4f` — feat(01-03): add class-feature-options types with Wizard School worked example
- `e85f790` — feat(01-03): add idempotent ClassProgression + ClassFeatureOption seed script
- `ce214ab` — fix(01-03): align Foundry path with pf2e-8.0.3 layout
- `d421aad` — chore(01-03): exclude foundry-pf2e dev clone from tsconfig
**Verified runtime checks:**
- `npx tsc --noEmit -p tsconfig.json` exits 0 (foundry-pf2e clone excluded; project code clean)
- `npm run db:seed:class-progression` first run: 320 created / 1 created
- `npm run db:seed:class-progression` second/third runs: 0 created / 320 updated / 0 errors (idempotent)
- Wizard L1 row query returns full structure with ARCANE slot progression, cantrip=5, choiceType=school
- Wizard L1..L19 carry non-null spellSlotIncrement; ClassFeatureOption has 1 row for `wizard-school`
- Existing 55 leveling tests still pass
---
*Phase: 01-level-up-pf2e-regelkonform*
*Completed: 2026-04-27*