Files
summercms/.planning/phases/03-first-vertical-slice-genres-end-to-end/03-03-SUMMARY.md

160 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 03-first-vertical-slice-genres-end-to-end
plan: 03
subsystem: api
tags: [servemux, typed-params, gormigrate, rollback, parity, jwt, seed-hook]
requires:
- phase: 03-first-vertical-slice-genres-end-to-end
provides: Shared pgx-stdlib pool, JWT genre route, tenant-scoped album counts
- phase: 02-api-parity-harness-bootstrap
provides: App-owned newTarget/seedHooks seam and recorded GET genres fixture
provides:
- Typed integer path params plus registration-time regex and enum constraints
- Per-plugin migrate:status IDs and migrate:rollback --plugin isolation
- Temporary trusted genres seed hook and real-app fixture replay
- Honest corpus counts: 154 recorded, 1 ported/passing, 153 pending
affects: [03-04, 05, 06, 07, 12]
tech-stack:
added: []
patterns:
- surf.Where/WhereIn compile constraints at registration; request text never builds regex or SQL
- Ported routes with seed_hook skip global PHP bootstrap replay
- Corpus passing increments only after the ported subtest succeeds
key-files:
created:
- surf/params.go
- ../fonoteka.go/parity/genres_seed_test.go
modified:
- surf/router.go
- pact/capabilities.go
- lagoon/migrations.go
- lagoon/commands.go
- examples/hello/plugins/greeter/plugin.go
- ../fonoteka.go/parity/parity_test.go
- ../fonoteka.go/parity/manifest.yaml
- ../fonoteka.go/README.md
key-decisions:
- "Route constraints compile regex and enum allow-lists at registration; request path text is only matched"
- "A ported route with a trusted seed_hook skips the global PHP bootstrap replay"
- "Corpus passing increments only after the ported subtest succeeds; pending never counts as passing"
- "Colliding fixture IDs are derived from CanonicalGenres seed order (rock=1, electronic=2, jazz=4)"
patterns-established:
- "Pattern: pact.Router.Where/WhereIn plus surf.IntParam; malformed and unknown IDs both 404"
- "Pattern: lagoon.ErrUnknownPlugin named error; rollback of golem15.fonoteka seed leaves user history intact"
- "Pattern: seedHooks[\"genres\"] mints a test-only JWT into an in-memory tide.Store"
requirements-completed: [DATA-02, HTTP-01, HTTP-02, QA-04]
duration: 8min
completed: 2026-09-17
---
# Phase 03 Plan 03: Typed params, isolated rollback, first real parity pass Summary
**Typed ServeMux params, per-plugin `migrate:rollback`, and one honest Go pass of the recorded PHP genres fixture (154 recorded, 1 passing, 153 pending)**
## Performance
- **Duration:** 8 min
- **Started:** 2026-09-17T18:14:59Z
- **Completed:** 2026-09-17T18:23:20Z
- **Tasks:** 2
- **Files modified:** 17
## Accomplishments
- `surf.IntParam`, `Where`, and `WhereIn` compile regex/enum constraints at registration; `examples/hello` serves `/items/1`, 404s malformed and unknown IDs, and rejects enum values outside the allow-list.
- `migrate:status` lists each plugin's applied IDs; `migrate:rollback --plugin=golem15.fonoteka` rolls back only the genre seed and returns `lagoon.ErrUnknownPlugin` for a missing plugin.
- `newTarget` boots the real app; the temporary `genres` hook seeds Alice, an owned collection, a test-only JWT, and colliding IDs from CanonicalGenres order. The unmodified `get_genres_jwt.yaml` passes. Corpus: 154 recorded, 1 ported/passing, 153 pending.
## Task Commits
Each task was committed atomically (framework `summercms.go` then app `fonoteka.go` when both change):
1. **Task 1: Expose reversible plugin migrations and typed route parameters**
- `8e3bf266d860c7416e8988845b70b190535c4b55` (feat, summercms.go)
- `91e3df41e33c85818f4cc3470668853f04b67315` (feat, fonoteka.go)
2. **Task 2: Turn the recorded genres route into one honest Go parity pass**
- `0cc1a4da84ced8ed2451f2ea1c9a045f1c2ea682` (feat, fonoteka.go)
**Plan metadata:** (this commit)
## Files Created/Modified
- `surf/params.go` — `IntParam`, `Regex`, `Enum`; constraints compiled at registration
- `pact/capabilities.go` — `Router.Where` / `WhereIn`
- `lagoon/migrations.go` — `ErrUnknownPlugin`, `ErrNoMigrations`, isolated `RollbackLast`
- `lagoon/commands.go` — `migrate:status` prints every applied ID
- `examples/hello/plugins/greeter/plugin.go` — `{id}` regex route and enum `/kinds/{kind}`
- `../fonoteka.go/parity/genres_seed_test.go` — trusted GORM Alice/collection hook and test-only JWT
- `../fonoteka.go/parity/parity_test.go` — real `app.Handler`, skip global bootstrap when `seed_hook` is set, passing count after successful replay, mutated-response check
- `../fonoteka.go/parity/manifest.yaml` — `GET /_fonoteka/api/v1/genres jwt` is `ported` with `seed_hook: genres`
- `../fonoteka.go/README.md` — ICU `pl-PL` create, `migrate`, `migrate:status`, `migrate:rollback --plugin=golem15.fonoteka`, `serve`
## Decisions Made
- Constraints are compiled when the route is registered; enum values are an allow-list, not a regex built from request text.
- A ported route with `seed_hook` does not replay unported global bootstrap endpoints.
- Passing is incremented only after the ported subtest succeeds.
- Fixture collisions `id:token=1`, `id:wishlist-album=2`, `id:genre=4` come from CanonicalGenres insertion order.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Synthetic proof tests kept `newSyntheticHandler` after `newTarget` became the real app**
- **Found during:** Task 2 (real handler swap)
- **Issue:** `TestParitySynthetic` and the contract `synthetic-postgres` subtest posted `/synthetic/items` through `newTarget`. The real app does not mount those routes.
- **Fix:** Call `newSyntheticHandler` for the Phase 2 synthetic proof; `newTarget` boots `app.Handler`.
- **Files modified:** `../fonoteka.go/parity/synthetic_test.go`, `../fonoteka.go/parity/parity_contract_test.go`, `../fonoteka.go/parity/parity_test.go`
- **Verification:** `go test ./parity` including `TestParitySynthetic` and `TestParityCorpus`
- **Committed in:** `0cc1a4d` (Task 2)
**2. [Rule 3 - Blocking] Rollback isolation uses a dedicated `rollback_iso` database**
- **Found during:** Task 1 (rollback test)
- **Issue:** Shared TestMain Postgres would lose the 15 genre rows (and shift serial IDs) if seed rollback ran in-place.
- **Fix:** Create ICU `pl-PL` database `rollback_iso`, migrate/rollback there, leave the parity pool untouched.
- **Files modified:** `../fonoteka.go/parity/migrate_test.go`
- **Verification:** `TestRollbackLastIsolatesFonoteka` plus later genre/parity tests still see ids 1–15
- **Committed in:** `91e3df4` (Task 1)
**3. [Discretion] `pact.Router` gained `Where`/`WhereIn`**
- **Found during:** Task 1 (typed params)
- **Issue:** Plan listed `surf/params.go` but plugins receive `pact.Router`; PHP `->where()` needs a method on that interface.
- **Fix:** Add `Where`/`WhereIn` to `pact.Router` and implement them on `*surf.Router`/`*surf.Group`.
- **Files modified:** `pact/capabilities.go`, `surf/router.go`
- **Verification:** hello typed-route tests and `go vet ./...`
- **Committed in:** `8e3bf26` (Task 1)
---
**Total deviations:** 3 (2 blocking test-isolation, 1 interface completeness)
**Impact on plan:** No scope creep. QA-04 is green on one route; 153 remain pending.
## Issues Encountered
None that blocked the slice. `go.work.sum` and `.planning/config.json` `_auto_chain_active` were left uncommitted.
## User Setup Required
None - no external service configuration required beyond the existing Postgres ICU `pl-PL` database.
## Next Phase Readiness
Ready for `03-04-PLAN.md` (unit, integration, and security verification). The genres corpus route is `ported`. Do not treat pending routes as passing.
## Self-Check: PASSED
- Key files exist on disk (`surf/params.go`, `lagoon/commands.go`, `../fonoteka.go/parity/genres_seed_test.go`, `../fonoteka.go/parity/parity_test.go`)
- `git log --grep=03-03` returns Task 1 and Task 2 commits in both repos
- Acceptance: per-plugin rollback isolation, hello typed/enum 404s, unmodified `get_genres_jwt.yaml` 200 against the real handler, corpus 154/1/153, mutated response fails, no tide or fixture edits, `go vet`/`go test ./...` green in both repos
---
*Phase: 03-first-vertical-slice-genres-end-to-end*
*Completed: 2026-09-17*