@@ -119,7 +119,7 @@ Requirements for v1 (the Płytarium port). Each maps to roadmap phases. "User" b
- [x]**QA-01**: A fixture recorder captures requests and responses from the running PHP backend for every route plus the Nuxt app's and MCP server's real flows
- [x]**QA-02**: A replay-and-diff harness runs fixtures against the Go backend with a normalizer for nondeterministic fields and assertions for the parity classes (nil vs [], date formats, tri-state booleans, envelopes, conditional keys)
- [x]**QA-03**: go vet and go test ./... are green at every commit; each phase ends with a unit-test plan; integration tests use testcontainers Postgres
- []**QA-04**: The first vertical slice (GET /_fonoteka/api/v1/genres) passes the parity diff end to end before further kernel abstraction
- [x]**QA-04**: The first vertical slice (GET /_fonoteka/api/v1/genres) passes the parity diff end to end before further kernel abstraction
- [ ]**QA-05**: Cutover: the parity harness is green on all 154 routes and vue-fonoteka-app and fonoteka-mcp run unchanged against the Go backend
## v2 Requirements
@@ -228,7 +228,7 @@ Which phases cover which requirements. Updated during roadmap creation.
@@ -91,6 +92,10 @@ Recent decisions affecting current work:
- [Phase 03]: lagoon.OrderBy takes a caller allow-list so the framework never hardcodes Fonoteka table names; the handler passes PHP PolishOrder::ALLOWED_COLUMNS — summercms.go must stay app-agnostic; PolishOrder columns live at the Fonoteka call site
- [Phase 03]: Duplicate non_empty query keys last-win, matching PHP parse_str; invalid then 1 is accepted, 1 then invalid is 422 — PHP parse_str last-wins confirmed with php -r; Go uses vals[len(vals)-1]
- [Phase 03]: Invalid stored context is rewritten to the lowest-ID accessible kind=collection row; auto-provisioning stays out of this slice — Plan 03-02 ports only the JWT default resolve path; CollectionProvisioner is Phase 12
- [Phase 03]: Route constraints compile regex and enum allow-lists at registration; request path text is only matched — D-15 T-03-07: PHP ->where() maps onto surf.Where/WhereIn; malformed and unknown IDs share a 404
- [Phase 03]: A ported route with a trusted seed_hook skips the global PHP bootstrap replay — D-20: unported register/login and POST genres; the hook mints a test-only JWT
- [Phase 03]: Corpus passing increments only after the ported subtest succeeds; pending never counts as passing — QA-04 T-03-06: 154 recorded, 1 passing, 153 pending
- [Phase 03]: Colliding fixture IDs are derived from CanonicalGenres seed order (rock=1, electronic=2, jazz=4) — D-20: set id:token, id:wishlist-album, id:genre from PHP seed order, not user input
### Pending Todos
@@ -112,6 +117,6 @@ Items acknowledged and carried forward from previous milestone close:
# 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**
-`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`
- 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`.
**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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.