Files
Dimension-47/.planning/phases/01-level-up-pf2e-regelkonform/01-01-SUMMARY.md
Alexander Zielonka 3e8698306b docs(01-01): complete level-up foundation plan summary
- Schema, migration, partial unique index, regenerated Prisma Client
- Pure-function module + 9/9 passing Jest spec (Pitfall #8 fixture)
- npm script and .gitignore prepared for Wave 2
- All deviations documented (env setup blocking issue, .js->.ts verification fix)
- Self-check PASSED on files, commits, and runtime verification
2026-04-27 14:33:06 +02:00

16 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
01-level-up-pf2e-regelkonform 01 database
prisma
schema
migration
jest-infrastructure
level-up
postgresql
partial-index
tdd
phase provides
00-init existing Prisma schema (Character, CharacterFeat, CharacterSkill, etc.) and PostgreSQL dev database
Four new Prisma models in DB and generated client (LevelUpSession, LevelUpHistory, ClassProgression, ClassFeatureOption)
Two new Character columns (freeArchetype Boolean, prereqViolations Json?)
Partial unique index "LevelUpSession_characterId_open_unique" enforcing one open DRAFT per character
Proven Jest infrastructure on src/modules/**/*.spec.ts (first-ever passing spec in this codebase)
Pure-function module pattern at server/src/modules/leveling/lib/ (no NestJS, no Prisma, no I/O)
applyAttributeBoost (Pitfall
npm script db:seed:class-progression (placeholder for Wave 2)
.gitignore exclusion for server/prisma/data/foundry-pf2e/
01-02
01-03
01-04
01-05
02-
03-
04-
05-
added patterns
Pure-function lib pattern (server/src/modules/<feature>/lib/*.ts + adjacent .spec.ts)
Hand-edited migration SQL for partial unique indexes Prisma cannot emit
created modified
server/prisma/migrations/20260427122603_add_level_up_sessions_and_class_progression/migration.sql
server/src/modules/leveling/lib/apply-attribute-boost.ts
server/src/modules/leveling/lib/apply-attribute-boost.spec.ts
server/prisma/schema.prisma
server/package.json
.gitignore
Used --create-only flow for migration to allow hand-editing the partial-index SQL before applying
Cascade onDelete on both LevelUpSession and LevelUpHistory FKs (acceptable in self-hosted single-tenant per RESEARCH.md §Pitfall 8)
ClassFeatureOption carries grants String[] and proficiencyChanges Json? per RESEARCH.md §Open Questions Q2 — keeps recompute pipeline uniform
Pure-function modules at server/src/modules/leveling/lib/ have NO @Injectable, NO Prisma, NO NestJS imports — adjacent .spec.ts file follows existing inline Jest config (testRegex ".*.spec.ts$", rootDir "src")
Pure-function lib pattern: math/validation/parser logic lives in <feature>/lib/*.ts, side-effect free, with adjacent *.spec.ts — first usage cross-cuts the codebase as the testing precedent (per ROADMAP First-Phase Note)
Migration SQL hand-editing for partial unique indexes: generate via prisma migrate dev --create-only, append raw SQL, then prisma migrate dev applies the entire file atomically
TDD RED → GREEN ordering: failing spec committed first (test commit), then production module committed (feat commit) — proves the test actually exercises the code
LVL-08
LVL-11
LVL-12
LVL-13
~30min 2026-04-27

Phase 01 Plan 01: Foundation — Schema, Migration, Jest Infrastructure Summary

Four new Prisma tables (LevelUpSession, LevelUpHistory, ClassProgression, ClassFeatureOption) plus two Character columns (freeArchetype, prereqViolations) live in PostgreSQL with a partial unique index enforcing one open DRAFT per character; first-ever Jest spec passes 9/9 inside src/modules/leveling/lib/.

Performance

  • Duration: ~30 min
  • Tasks: 3 completed (Task 1: schema, Task 2: migration + client, Task 3: TDD pure-function module + npm script + gitignore)
  • Files modified: 6 (schema.prisma, migration.sql, apply-attribute-boost.ts, apply-attribute-boost.spec.ts, package.json, .gitignore)

Accomplishments

  • Prisma schema extended: Four new models appended at the bottom of server/prisma/schema.prisma. Character model gained freeArchetype Boolean @default(false) and prereqViolations Json?, plus reverse relations levelUpSessions and levelUpHistories.
  • Migration generated and applied: 20260427122603_add_level_up_sessions_and_class_progression created via prisma migrate dev --create-only, hand-edited to append the partial unique index, then applied to the live dev database via prisma migrate dev. prisma migrate status reports "Database schema is up to date".
  • Partial unique index in live DB: LevelUpSession_characterId_open_unique exists on ("characterId") WHERE "committedAt" IS NULL — confirmed via psql ... \d "LevelUpSession" and pg_indexes query. This is the DB-level "one open DRAFT per character" race-condition-safe constraint that Prisma 7 cannot express in schema.
  • Prisma Client regenerated: server/src/generated/prisma/ contains LevelUpSession.ts, LevelUpHistory.ts, ClassProgression.ts, ClassFeatureOption.ts. Character.ts contains 98 references to freeArchetype and 92 to prereqViolations. prisma.levelUpSession, prisma.levelUpHistory, prisma.classProgression, prisma.classFeatureOption are exposed.
  • Jest proven on src/modules/:** First-ever passing spec inside server/src/modules/. The existing inline Jest config (testRegex: ".*\\.spec\\.ts$", rootDir: "src") was sufficient — no jest.config.cjs was needed (per VALIDATION.md Wave 0 default).
  • Pitfall #8 fixture in code: applyAttributeBoost(18) === 19 is now a unit test that will fail loudly if anyone re-implements the boost formula incorrectly.
  • Wave 2 unblocked: db:seed:class-progression npm script registered in server/package.json. Foundry pf2e clone path excluded in .gitignore.

Task Commits

Each task was committed atomically:

  1. Task 1: Extend Prisma schemade2ec38 (feat)
  2. Task 2: Generate migration + hand-edit partial index + regenerate client78a69c5 (feat)
  3. Task 3: TDD pure-function module + spec + npm script + gitignore
    • RED: 1e17225 (test) — failing spec committed first
    • GREEN: 65fcebd (feat) — production module makes 9/9 tests pass
    • Chores: 8b8e822 (chore) — npm script + gitignore

TDD compliance: RED commit precedes GREEN commit in git log; both gates documented.

Files Created/Modified

  • server/prisma/schema.prisma (modified) — Added four models (LevelUpSession, LevelUpHistory, ClassProgression, ClassFeatureOption) and extended Character with two columns + two reverse relations. Formatted by prisma format.
  • server/prisma/migrations/20260427122603_add_level_up_sessions_and_class_progression/migration.sql (created) — Auto-generated DDL for the four tables plus hand-appended partial unique index LevelUpSession_characterId_open_unique with WHERE "committedAt" IS NULL clause.
  • server/src/modules/leveling/lib/apply-attribute-boost.ts (created) — Pure-function module with applyAttributeBoost (Pitfall #8 mitigation) and isValidBoostSet. No NestJS, no Prisma, no I/O. Named exports only.
  • server/src/modules/leveling/lib/apply-attribute-boost.spec.ts (created) — 9 test cases covering boost-cap-at-18 rule (Pitfall #8 fixture is applyAttributeBoost(18) === 19) and 4-distinct-abilities validation.
  • server/package.json (modified) — Added db:seed:class-progression script entry alongside other db:seed:* entries.
  • .gitignore (modified) — Appended exclusion for server/prisma/data/foundry-pf2e/ (Wave 2 dev clone path).

Decisions Made

  • Migration flow: Used prisma migrate dev --create-only (rather than migrate dev directly), then hand-appended the partial-index SQL, then ran prisma migrate dev to apply the entire file atomically. Single migration file = single source of truth for prod deploy via migrate deploy. No raw psql was needed because Prisma's apply step ran our hand-edited file as one transaction. The plan's alternative path (apply auto-gen then run partial-index DDL via psql separately) was avoided as it would split the migration's truth across two locations.
  • Prisma Client .js.ts discrepancy: The plan's acceptance criterion node -e "const {PrismaClient}=require('./src/generated/prisma/client.js')..." does not work because Prisma 7's prisma-client provider (used here, not legacy prisma-client-js) emits TypeScript files (client.ts, models/Character.ts). Verification was done by grep over the generated .ts files and by running the migration successfully. The Prisma Client itself works at runtime via the project's PrismaService (see server/src/prisma/prisma.service.ts), which extends the generated client with the @prisma/adapter-pg adapter.
  • Schema formatter normalized whitespace: prisma format rewrote freeArchetype Boolean @default(false) (plan-specified spacing) into freeArchetype Boolean @default(false) (Prisma canonical spacing). The semantic content is identical; the literal-string acceptance criterion was relaxed to "field exists with correct type and default" since prisma format is mandatory under "Lieber langsam und richtig" (no fighting the formatter).
  • TDD discipline: RED commit was made with the spec only; jest was run and confirmed to fail (Cannot find module './apply-attribute-boost'); then GREEN commit added the source. The git log shows test → feat ordering, satisfying TDD gate.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Worktree had no node_modules and no .env file

  • Found during: Task 1 verification (running prisma format)
  • Issue: The worktree was created clean (no npm install had been run, .env is gitignored so it didn't transfer). prisma, tsx, and jest binaries were missing; DATABASE_URL was unreadable.
  • Fix: Copied .env from parent repo (C:/Users/ZielonkaA/Documents/Dimension-47/server/.env → worktree server/.env) since the file is gitignored and identical. Ran npm install in the worktree's server/ directory to install all dependencies. (An earlier cp -r of the parent's node_modules left a partial copy, which was removed before the clean npm install.)
  • Files modified: server/.env (gitignored, not staged), server/node_modules/ (gitignored), server/package-lock.json (no actual change since lockfile is already committed)
  • Verification: node_modules/.bin/prisma, node_modules/.bin/jest, node_modules/.bin/tsx all present; prisma validate, prisma migrate status, jest all worked.
  • Committed in: N/A — environment setup, no source files changed

2. [Rule 1 - Bug] Plan's runtime verification command used wrong file extension

  • Found during: Task 2 acceptance criterion verification
  • Issue: Plan's criterion node -e "const {PrismaClient}=require('./src/generated/prisma/client.js'); ..." fails because Prisma 7's prisma-client generator (versus the legacy prisma-client-js) emits .ts files, not .js. The path client.js does not exist; the actual path is client.ts. Additionally, new PrismaClient() requires explicit options (the @prisma/adapter-pg adapter) per Prisma 7 — bare instantiation throws PrismaClientInitializationError.
  • Fix: Verified the generated client a different way: (a) grep -c "freeArchetype" src/generated/prisma/models/Character.ts returned 98 matches; prereqViolations returned 92; (b) ls src/generated/prisma/models/ shows LevelUpSession.ts, LevelUpHistory.ts, ClassProgression.ts, ClassFeatureOption.ts present; (c) the production code path (PrismaService in server/src/prisma/prisma.service.ts) constructs the client correctly with the adapter, and prisma migrate dev succeeded — both prove the client works at runtime.
  • Files modified: None — verification approach changed, no code changes required.
  • Committed in: N/A — verification deviation only.

Total deviations: 2 (1 blocking infra, 1 verification-command bug) Impact on plan: Neither deviation changed any code or scope. Both were environment/verification concerns, not implementation issues. The plan's intent (schema applied, migration committed, partial index in DB, client regenerated, Jest works) was met exactly.

Issues Encountered

  • cp -r on Windows partially failed: Initial attempt to copy the parent repo's node_modules into the worktree left an incomplete tree (missing @angular-devkit, @anthropic-ai, etc.). Resolution: deleted the partial copy and ran npm install cleanly. Took ~3 minutes total.
  • rm -rf node_modules partial failure on Windows: First removal attempt failed with cannot remove 'node_modules/effect/dist/esm': Directory not empty — Windows file lock issue. Resolution: re-ran rm -rf twice; second attempt succeeded.

User Setup Required

None — all setup was infrastructural (npm install in worktree). No environment variables, dashboard configuration, or external service setup required.

Next Phase Readiness

Wave 1 unblocked:

  • Jest infrastructure proven on src/modules/leveling/lib/*.spec.ts. Subsequent pure-function modules (skill-cap, prereq-evaluator parser/evaluator/formatter, recompute-derived-stats) can follow the same <feature>/lib/*.ts + .spec.ts pattern.

Wave 2 unblocked:

  • db:seed:class-progression npm script registered (placeholder pointing at prisma/seed-class-progression.ts — the script itself is implemented in Wave 2).
  • .gitignore excludes the Foundry pf2e dev clone path so Wave 2 can clone source data without polluting git.
  • All four new tables exist in DB; Wave 2's seed script can write ClassProgression and ClassFeatureOption rows immediately.

Wave 3 unblocked:

  • Schema migration is applied; LevelingService can be written against the regenerated Prisma Client.
  • Partial unique index on LevelUpSession(characterId) WHERE committedAt IS NULL enforces "one open DRAFT per character" at the DB level — the start-or-resume endpoint becomes a simple upsert without race-condition gymnastics.
  • Character.prereqViolations Json? column is ready for D-06's Pathbuilder-import warning banner (also for the future Reverse-Level-Up's orphan-feat surface — Pitfall #4 forward-compat).

Wave 4 unblocked:

  • LevelUpHistory is append-only with committedAt defaulting to now(). Wave 4's commit-transaction can write a snapshot row safely.

No blockers, no concerns.

TDD Gate Compliance

This plan included one TDD task (Task 3, tdd="true"):

  • RED commit 1e17225 (test) — failing spec committed before any production code
  • GREEN commit 65fcebd (feat) — production module added; 9/9 tests pass
  • No REFACTOR commit — code was minimal and clean (4 lines of logic across two functions); no refactor needed

Plan-level TDD gates: not applicable (this plan is type: execute, not type: tdd).

Self-Check: PASSED

Verified files exist:

  • server/prisma/schema.prisma (modified)
  • server/prisma/migrations/20260427122603_add_level_up_sessions_and_class_progression/migration.sql (created)
  • server/src/modules/leveling/lib/apply-attribute-boost.ts (created)
  • server/src/modules/leveling/lib/apply-attribute-boost.spec.ts (created)
  • server/package.json (modified — db:seed:class-progression entry present)
  • .gitignore (modified — server/prisma/data/foundry-pf2e/ line present)

Verified commits exist:

  • de2ec38 — feat(01-01): extend Prisma schema with level-up tables and Character columns
  • 78a69c5 — feat(01-01): add level-up migration with partial unique index
  • 1e17225 — test(01-01): add failing tests for applyAttributeBoost and isValidBoostSet
  • 65fcebd — feat(01-01): implement applyAttributeBoost and isValidBoostSet pure functions
  • 8b8e822 — chore(01-01): add db:seed:class-progression script and gitignore foundry-pf2e

Verified runtime checks:

  • prisma validateThe schema at prisma\schema.prisma is valid
  • prisma migrate statusDatabase schema is up to date!
  • Partial unique index LevelUpSession_characterId_open_unique exists in DB with WHERE ("committedAt" IS NULL) clause (verified via pg_indexes)
  • Prisma Client generated at server/src/generated/prisma/ — contains models/LevelUpSession.ts, models/LevelUpHistory.ts, models/ClassProgression.ts, models/ClassFeatureOption.ts; models/Character.ts references freeArchetype (98 hits) and prereqViolations (92 hits)
  • Jest test suite: 9 passed, 9 total for apply-attribute-boost.spec.ts

Phase: 01-level-up-pf2e-regelkonform Completed: 2026-04-27