From 87006d12d9d891fa6ca5dd0f9679f184c95e5772 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 18 Sep 2026 18:49:59 +0200 Subject: [PATCH] docs(05-01): complete Winter layout and lagoon primitives plan Tasks completed: 4/4 - Folded-todo models-leaf probe - Winter-directory plugin restructure - lagoon Fill and lifecycle cascade - Paginate, RegisterJoinTable, DATA-09 wording SUMMARY: .planning/phases/05-data-layer-full-fidelity/05-01-SUMMARY.md --- .../05-01-SUMMARY.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .planning/phases/05-data-layer-full-fidelity/05-01-SUMMARY.md diff --git a/.planning/phases/05-data-layer-full-fidelity/05-01-SUMMARY.md b/.planning/phases/05-data-layer-full-fidelity/05-01-SUMMARY.md new file mode 100644 index 0000000..2e47478 --- /dev/null +++ b/.planning/phases/05-data-layer-full-fidelity/05-01-SUMMARY.md @@ -0,0 +1,170 @@ +--- +phase: 05-data-layer-full-fidelity +plan: 01 +subsystem: database +tags: [gorm, fillable, lifecycle, pagination, winter-layout, models-leaf, gormigrate] + +requires: + - phase: 03-first-vertical-slice-genres-end-to-end + provides: lagoon shared *sql.DB, Phase-3 flat fonoteka/user plugins, genres parity fixture + - phase: 04-cli-scaffolding-i18n-and-mail + provides: models-leaf enforcement in summer build, Winter-shaped make:* stubs +provides: + - confirmed models-leaf rule against keios.eu, jz, and pxpx plugins + - Winter-directory fonoteka.go plugins with self-registering models/updates/classes registries + - lagoon.Fill allow-list copy with once-per-key non-production log + - GORM-native Has* lifecycle interfaces and WithSoftDeleteCascade + - lagoon.Paginate {data, meta} envelope and RegisterJoinTable pivot contract +affects: [05-02-album-collection, 05-03-secrets, 05-04-attachments, 05-05-remaining-models, 05-06-tests] + +tech-stack: + added: [] + patterns: + - self-registering init() registries so later plans add files without editing plugin.go + - Fill allow-list then apply, matching OrderBy + - WithSoftDeleteCascade names GORM's in-transaction BeforeDelete cascade + +key-files: + created: + - lagoon/fill.go + - lagoon/fill_test.go + - lagoon/lifecycle.go + - lagoon/lifecycle_test.go + - lagoon/paginate.go + - lagoon/paginate_test.go + - lagoon/relations.go + - lagoon/relations_test.go + - .planning/todos/done/verify-models-leaf-rule.md + modified: + - .planning/notes/plugin-layout-winter-directories.md + - .planning/ROADMAP.md + - .planning/REQUIREMENTS.md + - plugins/golem15/fonoteka/plugin.go + - plugins/golem15/user/plugin.go + - fonoteka.go/parity/*_test.go + +key-decisions: + - "Models-leaf rule holds on keios.eu user, jz chat, and pxpx checkout; contracts/VOs/jobs/broadcasting stay inside the cast-or-hook conversion treatments" + - "plugin.go Models()/Migrations() return registry All(); Boot calls classes.RegisterHooks when *gorm.DB is published" + - "lagoon.Fill matches gorm column tags against the caller allow-list and logs dropped keys once per type+key in non-production" + - "Lifecycle Has* interfaces use GORM-native signatures; WithSoftDeleteCascade does not open a new transaction" + - "Paginate coerces nil data to []; RegisterJoinTable fails loud on nil db" + - "DATA-09 migration count is not an acceptance number (D-01); HTTP DTO fuzz moves to Phase 12 (D-07)" + +patterns-established: + - "One Register call per model/migration file init(); plugin.go is not edited by later Phase 5 plans" + - "House REST pagination is Page[T] {Data, Meta} with no links key" + +requirements-completed: [DATA-03, DATA-06, DATA-10] + +duration: 16 min +completed: 2026-09-18 +--- + +# Phase 5 Plan 01: Winter layout and lagoon primitives Summary + +**Models-leaf rule confirmed on three more plugins; fonoteka.go restructured into Winter directories with self-registering registries; lagoon.Fill, lifecycle cascade, Paginate, and RegisterJoinTable shipped with tests** + +## Performance + +- **Duration:** 16 min +- **Started:** 2026-09-18T16:32:17Z +- **Completed:** 2026-09-18T16:48:36Z +- **Tasks:** 4 +- **Files modified:** 37 + +## Accomplishments + +- Probed keios.eu `golem15/user`, jz `golem15/chat`, and pxpx `pixelpixel/checkout`; every live `models/` → sibling edge is a cast or service-calling hook, so the leaf rule holds and the folded todo is closed +- Restructured both Phase-3 `fonoteka.go` plugins into `models/`, `classes/`, `updates/` (plus `controllers/` and `middleware/` on fonoteka) with `Register`/`All` collectors; `plugin.go` returns `models.All()`/`updates.All()` and never needs a later-plan edit +- `lagoon.Fill` copies only allow-listed keys, logs dropped keys once in non-production, and stays silent in production; five `Has*` hook interfaces plus `WithSoftDeleteCascade` are integration-tested on testcontainers Postgres +- `lagoon.Paginate` emits `{data, meta{current_page,last_page,per_page,total}}` with no `links`; `RegisterJoinTable` documents the pivot-write contract and refuses a nil db +- ROADMAP.md and REQUIREMENTS.md no longer treat "27 migrations" as a target; criterion 3's HTTP DTO fuzz is Phase 12's job + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Folded-todo probe** - `a9c77fc` (docs) in summercms.go +2. **Task 2: Restructure plugins into Winter directories** - `3171a49` (feat) in fonoteka.go +3. **Task 3: Fill/lifecycle tests (RED)** - `fec23e2` (test) in summercms.go +4. **Task 3: Fill/lifecycle implementation (GREEN)** - `969a5f5` (feat) in summercms.go +5. **Task 4: Paginate and RegisterJoinTable** - `52638fb` (feat) in summercms.go +6. **Task 4: DATA-09 / criterion-3 wording** - `c85fa24` (docs) in summercms.go + +**Plan metadata:** pending (this file) + +## Files Created/Modified + +- `.planning/notes/plugin-layout-winter-directories.md` — three dated Evidence sections plus widened cast/hook conversion treatments +- `.planning/todos/done/verify-models-leaf-rule.md` — closed folded todo +- `fonoteka.go/plugins/golem15/fonoteka/{models,classes,controllers,middleware,updates}/*` — Winter layout and registries +- `fonoteka.go/plugins/golem15/fonoteka/plugin.go`, `routes.go` — `models.All()`/`updates.All()`, hook Boot, genres route +- `fonoteka.go/plugins/golem15/user/{models,classes,updates}/*` and `plugin.go` — same layout for the user plugin +- `fonoteka.go/parity/*_test.go` — import paths follow the new packages +- `lagoon/fill.go`, `lagoon/fill_test.go` — allow-list Fill + HasFillable/HasHidden +- `lagoon/lifecycle.go`, `lagoon/lifecycle_test.go` — Has* hooks and transactional cascade +- `lagoon/paginate.go`, `lagoon/paginate_test.go` — DATA-10 envelope +- `lagoon/relations.go`, `lagoon/relations_test.go` — SetupJoinTable wrapper and pivot-write contract +- `.planning/ROADMAP.md`, `.planning/REQUIREMENTS.md` — D-01/D-07 wording + +## Decisions Made + +- The models-leaf rule is confirmed, not excepted: PHP contracts, value objects, formatters, job dispatch, and broadcasting traits convert as casts (move into `models/`) or service-calling hooks (register from `classes/`) +- Later Phase 5 plans add files under `models/`, `updates/`, and `classes/`; they do not edit `plugin.go` +- `WithSoftDeleteCascade` is a named one-line wrapper around the GORM `BeforeDelete` transaction, not a second transaction +- `Paginate` does not slice rows; callers pass an already-sliced DTO list and the helper only computes `LastPage` +- HTTP-level fill fuzz is explicitly Phase 12; this plan's DATA-06 delivery is the `lagoon.Fill` primitive + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 2 - Missing Critical] Nil pagination data marshals as `[]`** +- **Found during:** Task 4 (Paginate envelope) +- **Issue:** `json.Marshal` of a nil slice is `null`, which is Pitfall 4 and would break house REST `[]` parity +- **Fix:** `Paginate` replaces a nil `rows` argument with `[]T{}` +- **Files modified:** `lagoon/paginate.go`, `lagoon/paginate_test.go` +- **Verification:** `TestPaginateNilDataIsEmptyArray` +- **Committed in:** `52638fb` (Task 4 code) + +**2. [Rule 3 - Blocking] Shipped migration Go identifiers needed package qualifiers after the move** +- **Found during:** Task 2 (plugin restructure) +- **Issue:** `updates/00_base.go` cannot stay `package fonoteka` and still live in the `updates` subpackage; `CanonicalGenres`/`Genre{}` live in `models/` +- **Fix:** SQL strings unchanged (P3 D-17); package clause and `models.CanonicalGenres`/`models.Genre{}` qualifiers only +- **Files modified:** `fonoteka.go/plugins/golem15/fonoteka/updates/00_base.go` +- **Verification:** `go test ./... -run TestParityCorpus` passing 1/ported 1; seed rollback still uses the same table/slug SQL +- **Committed in:** `3171a49` (Task 2) + +--- + +**Total deviations:** 2 auto-fixed (1 missing critical, 1 blocking) +**Impact on plan:** Both required for `[]`-not-null parity and a compiling Winter-layout move. No scope creep. + +## Issues Encountered + +None. + +## Authentication Gates + +None. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +Ready for 05-02 (Album/Collection slice). Wave-2 plans can add model, update, and class files without editing `plugin.go`. `lagoon.Fill`, lifecycle hooks, `Paginate`, and `RegisterJoinTable` are available for those models. + +## Self-Check: PASSED + +- Key files exist on disk (lagoon primitives, plugin registries, closed todo, three Evidence sections) +- Commits `a9c77fc`, `3171a49`, `fec23e2`, `969a5f5`, `52638fb`, `c85fa24` exist +- `go vet ./...` and `go test ./... -short` green in summercms.go +- `go vet ./...` and `go test ./... -run TestParityCorpus` green in fonoteka.go +- `grep "27 migrations" .planning/ROADMAP.md .planning/REQUIREMENTS.md` has no matches + +--- +*Phase: 05-data-layer-full-fidelity* +*Completed: 2026-09-18*