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

8.0 KiB
Raw Blame History

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
03-first-vertical-slice-genres-end-to-end 03 api
servemux
typed-params
gormigrate
rollback
parity
jwt
seed-hook
phase provides
03-first-vertical-slice-genres-end-to-end Shared pgx-stdlib pool, JWT genre route, tenant-scoped album counts
phase provides
02-api-parity-harness-bootstrap App-owned newTarget/seedHooks seam and recorded GET genres fixture
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
03-04
05
06
07
12
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
created modified
surf/params.go
../fonoteka.go/parity/genres_seed_test.go
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
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)
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
DATA-02
HTTP-01
HTTP-02
QA-04
8min 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