Files
summercms/.planning/phases/03-first-vertical-slice-genres-end-to-end/03-DISCUSSION-LOG.md
2026-09-17 17:47:07 +02:00

9.4 KiB

Phase 3: First vertical slice — genres end to end - Discussion Log

Audit trail only. Do not use as input to planning, research, or execution agents. Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

Date: 2026-09-17 Phase: 3-first-vertical-slice-genres-end-to-end Areas discussed: Handler fidelity, Throwaway JWT guard, Pipeline stage depth, Migrations and seed data


Handler fidelity

How faithful should the Go genres handler be to the PHP query?

Option Description Selected
Faithful query, minimal tables Real user → active collection → accessible-album count join; only the columns the query touches; Phase 5 widens ✓
Genres-only, count hardcoded 0 Fastest green diff; handler rewritten in Phase 12; proves nothing about joins
Genres + albums count, no access scoping Real COUNT join, skips user/active-collection resolution

User's choice: Faithful query, minimal tables Notes: Scout found the fixture shows album_count: 0 for every genre, so the diff cannot force this choice.

How do we prove the non-zero count path?

Option Description Selected
Go integration test alongside parity testcontainers test inserts albums for alice and a foreign collection; fixture untouched ✓
Re-record a richer fixture from PHP New manifest cases recorded after genre-tagged albums exist; reopens Phase 2 corpus
Both Integration test plus a second recorded case

User's choice: Go integration test alongside parity

Where does active collection resolution live?

Option Description Selected
Minimal resolver in the fonoteka plugin Small service porting the default path of ActiveCollectionResolver; Phase 12 extends ✓
Org-context middleware sets it Pipeline stage resolves via a plugin-supplied hook
You decide Research compares against other controllers' context() use

User's choice: Minimal resolver in the fonoteka plugin

Is ?non_empty validation (422) and filtering part of this slice?

Option Description Selected
Port it, test it in Go only Whole handler ported incl. 422 envelope and filter; no new fixtures ✓
Skip until Phase 12 Only the recorded 200 path, with a TODO
Port it and record fixtures Add non_empty=1 and 422 cases to the manifest, re-record

User's choice: Port it, test it in Go only

PolishOrder collation on Postgres

Option Description Selected
Research picks, I confirm at plan review Researcher compares query COLLATE, column collation, DB locale; lands as a lagoon helper ✓
ICU collation in the query Lock COLLATE "pl-x-icu" via helper now
Database-level Polish locale Plain ORDER BY; deployment and testcontainers constraint

User's choice: Research picks, I confirm at plan review


Throwaway JWT guard

What does the guard verify?

Option Description Selected
Real HS256 verify + user row lookup Signature, pinned alg, exp, sub, user row into context; only issuing is throwaway ✓
Static seeded-token table Bearer string compared to a token list; no crypto
Real verify, no DB lookup Identity from sub only

User's choice: Real HS256 verify + user row lookup Notes: Scout: PHP jwt.auth is registered by the user plugin; HS256, JWT_SECRET from env.

Which module owns the guard and minimal users table until Phase 7?

Option Description Selected
Stub user plugin in fonoteka.go Real plugin with minimal User, migration, registers jwt.auth; fonoteka Requires() it ✓
Framework bouncer owns verify, fonoteka plugin owns users Less scaffolding now, a move later
Everything temporary inside the fonoteka plugin One plugin, throwaway auth file

User's choice: Stub user plugin in fonoteka.go

How far does bouncer go?

Option Description Selected
Verifier + context accessors only HS256 helper, user context key and accessors, UserProvider interface ✓
Guard registry skeleton now Three auth groups declared, jwt implemented
No bouncer yet All auth code in the user plugin until Phase 6

User's choice: Verifier + context accessors only

Which failure responses does the guard port now?

Option Description Selected
Port PHP's 401 bodies, Go-tested Same status and JSON as JwtAuthenticate.php; no new fixtures ✓
Add a recorded 401 genres case Extend manifest, record from PHP
Plain 401, shape later Exact bodies left to Phase 6/7

User's choice: Port PHP's 401 bodies, Go-tested


Pipeline stage depth

How real are the stages genres doesn't exercise?

Option Description Selected
Real slot, minimal behavior each Named middleware in fixed order; CORS preflight from config, locale into context, org context pass-through, no-op limiter behind interface ✓
Recover + auth real, the rest pass-through Five named no-ops
All real now Pulls most of Phase 6 forward

User's choice: Real slot, minimal behavior each

must-change-password (423) handling

Option Description Selected
Real check, owned by the plugin that owns it in PHP fonoteka plugin registers inv.must-change-password, attached by name after jwt.auth; real 423 body ✓
Named pass-through Slot exists, always passes

User's choice: Real check, owned by the plugin that owns it in PHP

Route declaration API in surf

Option Description Selected
Laravel-like group builder over ServeMux r.Group(prefix, surf.Use(names...), func(g){ g.Get(...) }); plain handlers ✓
Bare ServeMux patterns + explicit wrap Zero abstraction, repeated prefixes
Declarative route table []surf.Route structs, introspectable

User's choice: Laravel-like group builder over ServeMux

Typed params now?

Option Description Selected
Ship the helper, exercise it on a test route Integer accessor with 404-on-malformed, tested in surf and examples/hello ✓
Defer to the first route that needs it Strictly interleaved; criterion 2 partly unmet

User's choice: Ship the helper, exercise it on a test route


Migrations and seed data

Relation to PHP migration history

Option Description Selected
Squashed final-state migrations per table One migration per table in final shape, comment naming folded PHP files ✓
Mirror PHP one to one One Go migration per PHP file incl. rename detour
Squash now, decide again in Phase 5 Policy revisited later

User's choice: Squashed final-state migrations per table

Partial tables vs Phase 5

Option Description Selected
Minimal now, Phase 5 adds ALTER migrations Phase 3 migrations permanent, never edited ✓
Rewrite in Phase 5 Provisional migrations replaced pre-release
Full-shape tables now Complete column set without Phase 5's security pass

User's choice: Minimal now, Phase 5 adds ALTER migrations

Source of the 15 seeded genres

Option Description Selected
A data migration, as in PHP Idempotent by slug, PHP order, Rollback removes them ✓
The parity seed hook inserts them Test-only data
A db:seed style command New framework concept

User's choice: A data migration, as in PHP

Temporary genres seed hook

Option Description Selected
Insert alice + collection, mint her JWT, set the colliding id vars Fixture untouched; hook declared temporary in manifest ✓
Same, but fix the scrubber collisions first Tighten tide substitution, re-scrub 154 fixtures
You decide Planner checks collision frequency

User's choice: Insert alice + collection, mint her JWT, set the colliding id vars Notes: Scrubber fix recorded as a deferred idea.


Claude's Discretion

  • Package internals of surf, lagoon, bouncer; capability interface names in pact
  • DB config keys and how the shared *sql.DB is published; River listener pool seam left for Phase 11
  • Per-plugin gormigrate version-table naming
  • serve and migrate:* command set and graceful shutdown
  • Fail-boot behavior for unknown middleware names
  • Genre aggregate DTO; [] for empty lists
  • fonoteka.go workspace layout
  • JWT secret config key and fail-boot on empty secret (D-11, not asked; recorded as a security default)
  • Postgres Polish ordering: research proposes, user confirms at plan review

Deferred Ideas

  • Tighten tide scrubber id-placeholder collisions and re-scrub the corpus
  • Recorded fixtures for non-zero counts, non_empty=1, 422 and 401 on genres
  • Active-collection resolution via the org-context stage (Phase 6/12)
  • Guard registry, rate-limit buckets, full CORS/locale (Phase 6)
  • Real token issuing in the user plugin (Phase 7)
  • River LISTEN/NOTIFY pgx pool (Phase 11)
  • Production data import (Phase 15)