10 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-data-layer-full-fidelity | 02 | database |
|
|
|
|
|
|
|
|
|
27min | 2026-09-18 |
Phase 5 Plan 02: Album/Collection vertical slice Summary
Album/Collection slice with six gormigrate steps, Jsonable/MoneyString casts, lagoon.Validate, ordered album_artists pivot writes, and PHP lifecycle hooks proven on real Postgres
Performance
- Duration: 27 min
- Started: 2026-09-18T16:55:11Z
- Completed: 2026-09-18T17:22:30Z
- Tasks: 3
- Files modified: 32
Accomplishments
- Six Fonoteka migrations widen collections/albums (including
track_titlesand TEXTdiscogs_id) and create artists (Various Artists seed), styles, ratings, and reservations; each rolls back in isolation lagoon.Jsonable[T]round-trips SQL NULL vs'[]';MoneyStringformats Postgresnumeric(10,4)to a 4-decimal string with no binary floating type in the cast- Artist/Genre/Style
BeforeValidate/AfterDeleteand CollectionBeforeDeletecascade (viaWithSoftDeleteCascade) are ported from PHP and tested against real Postgres lagoon.Validatetranslates this slice's rule strings (between, quotedoneof, custom money range,unique:tablewithdeleted_at) and fails loudly on unknown tokensSaveAlbumuses the PHPFILL_FIELDSsubset,syncArtistsdelete-then-insert, andAlbum.BeforeSave(refreshTrackTitles+stampMarketPrice) including the PHP ceiling case"12.55555"→"12.5556"
Task Commits
Each task was committed atomically:
- Task 1: Album/collection slice migrations -
e24a614(feat) in fonoteka.go - Task 2: lagoon.Jsonable -
20b0199(feat) in summercms.go - Task 2: Widen models and port lifecycle hooks -
445133e(feat) in fonoteka.go - Task 3: lagoon.Validate -
2c5d92d(feat) in summercms.go - Task 3: Write services and BeforeSave -
4879996(feat) in fonoteka.go
Plan metadata: pending (this file)
Files Created/Modified
lagoon/jsonable.go,lagoon/jsonable_test.go— TEXT JSON cast, NULL vs[]lagoon/validate.go,lagoon/validate_test.go— rule-string enginelagoon/fill.go— nil zeros the field; jsonable columns Scan from JSONfonoteka.go/plugins/golem15/fonoteka/updates/10_album_slice.go— six migrationsfonoteka.go/plugins/golem15/fonoteka/models/album.go— final columns, Fillable/Rules, BeforeSavefonoteka.go/plugins/golem15/fonoteka/models/{artist,style,album_artist,album_rating,album_reservation,money_string,slug}.gofonoteka.go/plugins/golem15/fonoteka/models/{collection,collection_editor,genre}.go— widened + hooksfonoteka.go/plugins/golem15/fonoteka/classes/{album_write_service,collection_write_service,artist_resolver,join_tables,serialize}.gofonoteka.go/parity/migrate_test.go,album_slice_models_test.go,album_write_service_test.go
Decisions Made
- Jsonable payload is
Data(notValue) becausedriver.Valuer.Value()plus a field namedValuefailsgo vet KeepMarketPriceSourceis agorm:"-"flag on Album, set bySaveAlbumwhenrequestedcontainsmarket_price_sourceCollectionFillFieldsisname,description— PHP has no CollectionWriteService, so the service list is a subset ofFillable()excludingowner_id- Various Artists seed uses PHP's natural key
name_key = "various artists"(slugvarious-artists) github.com/go-playground/validator/v10 v10.30.4is added to the framework module as the STACK-named engine behindlagoon.Validate
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Jsonable cannot use a field named Value
- Found during: Task 2 (Jsonable cast)
- Issue:
go vetrejects a struct field and method both namedValue;type Jsonable[T] Tis also illegal in Go - Fix: Struct with
Data/Valid/NullOnEmptytaggedgorm:"-"so GORM uses Scanner/Valuer instead of walking inner fields - Files modified:
lagoon/jsonable.go,lagoon/jsonable_test.go - Verification:
go vet ./lagoon/...andgo test ./lagoon/... -run TestJsonable - Committed in:
20b0199(Task 2)
2. [Rule 2 - Missing Critical] Fill ignored nil on non-pointer fields
- Found during: Task 3 (SaveAlbum money nil-in → NULL-out)
- Issue:
lagoon.FillleftMoneyStringunchanged on explicit nil, so the blank-to-NULL path could not be requested - Fix: nil now zeros any field; types implementing
sql.Scanner(Jsonable) Scan from JSON when convertValue cannot assign - Files modified:
lagoon/fill.go - Verification:
TestSaveAlbumMoneyNormalizationnil case - Committed in:
2c5d92d(Task 3)
3. [Rule 3 - Blocking] Existing migrate tests hardcoded two Fonoteka IDs
- Found during: Task 1 (six new migrations)
- Issue:
TestMigrateSeedsCanonicalGenresandTestRollbackLastIsolatesFonotekaexpected exactly the Phase-3 pair - Fix: Expect eight IDs; RollbackLast now asserts
create_album_reservationsdrops only that table - Files modified:
fonoteka.go/parity/migrate_test.go - Verification: those tests plus
TestAlbumSliceMigrationsUpDown - Committed in:
e24a614(Task 1)
4. [Rule 2 - Missing Critical] widen_albums/collections needed columns omitted from Phase 3 stubs
- Found during: Task 1 (reading folded PHP files)
- Issue:
description(collections),public_enabled/public_token_generated_at, andshelf/quantity/notesexist in PHP but not in00_base.go - Fix: Added every column the folded PHP files (and the RESEARCH fillable list) require
- Files modified:
fonoteka.go/plugins/golem15/fonoteka/updates/10_album_slice.go - Verification:
TestAlbumSliceMigrationsUpDownand later model tests - Committed in:
e24a614(Task 1)
Total deviations: 4 auto-fixed (2 missing critical, 2 blocking) Impact on plan: Required for vet, money nil-out, and a compiling schema that matches PHP. No scope creep. Album.Fillable() has 21 names copied from PHP/RESEARCH (plan said 20 — a count error).
Issues Encountered
None beyond the auto-fixes above.
Authentication Gates
None.
Known Stubs
classes/artist_resolver.goalbumBeforeSaveCallbackis a type-guard registration seam; name-to-id find-or-create stays in PHP'sArtistResolver::resolveOneand is not needed for this plan's ID-basedsyncArtistsSerializeAlbum/SerializeCollectionomit photos/thumbs (Plan 05-04) and reservation context (Phase 12)
User Setup Required
None - no external service configuration required.
Next Phase Readiness
Ready for 05-03 (secrets/encrypted credentials). Wave-2 Album/Collection models, validations, and write-service fill boundaries are in place. lagoon.Validate and lagoon.Jsonable are reusable for later models.
Self-Check: PASSED
- Key files exist on disk (jsonable, validate, 10_album_slice, write services, join_tables, models)
- Commits
e24a614,20b0199,445133e,2c5d92d,4879996exist go vet ./...andgo test ./lagoon/...green in summercms.gogo vet ./...andgo test ./...green in fonoteka.go (parity + plugin modules)
Phase: 05-data-layer-full-fidelity Completed: 2026-09-18