diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index c9cee11..7085ba8 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -44,7 +44,7 @@ Requirements for v1 (the Płytarium port). Each maps to roadmap phases. "User" b - [ ] **DATA-06**: Mass assignment goes through per-endpoint request DTOs honoring each model's fillable allow-list; serialization honors a hidden deny-list with an explicit per-call override - [ ] **DATA-07**: Custom casts exist for jsonable columns, money as a fixed four-decimal string, and encrypted-at-rest secrets (AES-GCM, app-key derived) that are also hidden from serialization - [ ] **DATA-08**: A polymorphic file attachment table (owner type, owner id, field, disk path, sort order, public/private) backs attachOne and attachMany, stored via gocloud.dev/blob with the same public URL shape -- [ ] **DATA-09**: All 25 Płytarium models and 27 migrations are ported with matching table names, columns, indexes and defaults +- [ ] **DATA-09**: All 25 Płytarium models and their squashed migration set are ported with matching table names, columns, indexes and defaults (migration count is not itself an acceptance number — squashed per plan-time decision D-01 in 05-CONTEXT.md) - [ ] **DATA-10**: Paginated responses use the exact `{data, meta{current_page, last_page, per_page, total}}` envelope without a links key - [ ] **DATA-11**: Other plugins can hook a model's lifecycle through the GORM callback registry and extend its schema with a companion migration diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0fd5676..cc31d21 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -17,7 +17,7 @@ Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 2: API parity harness bootstrap** - Fixture recorder + replay-and-diff harness against the live PHP backend, built on the Phase 1 command kernel (completed 2026-09-17) - [x] **Phase 3: First vertical slice — genres end to end** - `GET /_fonoteka/api/v1/genres` passes the parity diff through every layer (completed 2026-09-17) - [x] **Phase 4: CLI scaffolding, i18n and mail** - Scaffolding commands, translated/pluralized strings, mail templates (completed 2026-09-18) -- [ ] **Phase 5: Data layer full fidelity** - All 25 models and 27 migrations with fillable/hidden/cast/soft-delete discipline +- [ ] **Phase 5: Data layer full fidelity** - All 25 models and their squashed migrations with fillable/hidden/cast/soft-delete discipline - [ ] **Phase 6: HTTP routing, auth groups and rate limiting** - Three auth groups, named rate buckets, OAuth-safe middleware structure - [ ] **Phase 7: User plugin and authentication** - Registration, login, JWT, organizations, personal tokens, must-change-password - [ ] **Phase 8: OAuth2.1 authorization server** - zitadel/oidc server for fonoteka-mcp and the ChatGPT connector @@ -171,16 +171,16 @@ Plans: ### Phase 5: Data layer full fidelity -**Goal**: All 25 Płytarium models and 27 migrations are ported with matching relations, casts, hooks, and the fillable/hidden/encrypted-cast mass-assignment and serialization discipline. This is security-load-bearing — mass-assignment boundaries and encrypted-at-rest credential casts are named security invariants in the PHP source — apply the security-review agent and the DTO-vs-model convention from the first model onward. +**Goal**: All 25 Płytarium models and their squashed migration set are ported with matching relations, casts, hooks, and the fillable/hidden/encrypted-cast mass-assignment and serialization discipline. This is security-load-bearing — mass-assignment boundaries and encrypted-at-rest credential casts are named security invariants in the PHP source — apply the security-review agent and the DTO-vs-model convention from the first model onward. **Mode:** mvp **Depends on**: Phase 1, Phase 3 **Repos:** summercms.go, fonoteka.go **Requirements**: DATA-03, DATA-04, DATA-05, DATA-06, DATA-07, DATA-08, DATA-09, DATA-10, DATA-11, CLI-03 **Success Criteria** (what must be TRUE): - 1. All 25 models exist with matching table names, columns, indexes and defaults; all 27 migrations run up and down individually, and `summer migrate:rollback --plugin=fonoteka` rolls back only that plugin's last migration. + 1. All 25 models exist with matching table names, columns, indexes and defaults, and every Go migration runs up and down individually; the final schema matches PHP's (migrations are squashed per final-state table, not a 1:1 port of PHP's 38 files — the historical migration count is not a target). `summer migrate:rollback --plugin=fonoteka` rolls back only that plugin's last migration. 2. A many-to-many relation with pivot business columns (`album_artists.sort_order`, `CollectionEditor.role/granted_at/granted_by`) round-trips correctly for a 3+ artist album; belongsTo/hasOne/hasMany relations return ordered results. - 3. Every write endpoint uses a request DTO that enforces its model's fillable allow-list (a fuzz test posting unknown fields asserts they are rejected or ignored, never persisted), and serialization honors the hidden deny-list with an explicit per-call override. + 3. The fill boundary of the PHP write services (at minimum Album, Collection and the four credential models) is fuzzed against real Postgres with random extra and server-owned keys, asserting nothing outside the allow-list is persisted (service-level, this phase); a request-DTO-level fuzz over every write endpoint is Phase 12's criterion (the HTTP layer does not exist until Phase 6/12). Serialization honors the hidden deny-list with an explicit per-call override. 4. The money cast round-trips the PHP ceiling and blank-string cases as a fixed 4-decimal JSON string (never `float64`), and an encrypted-at-rest credential column is AES-GCM encrypted at rest and hidden from serialization. 5. Paginated responses use the exact `{data, meta{current_page,last_page,per_page,total}}` envelope with no `links` key; another plugin extends a model's lifecycle through the GORM callback registry and a companion migration without editing the owning plugin's file; soft-deletable + uniquely-keyed tables pass a delete-then-recreate test. @@ -324,6 +324,7 @@ Plans: 2. Albums CRUD, ratings, reservations, photo upload, manual cover URL and Discogs cover price all pass the parity diff. 3. Album search treats Typesense results as a pre-filter re-gated in SQL, verified by a security test that a stale/mis-scoped search document cannot leak an unauthorized result. 4. Artists/genres/styles lookup endpoints used by the Albums UI pass the parity diff. + 5. A request-DTO-level fuzz over every write endpoint asserts unknown and server-owned keys are never persisted (inherits the HTTP half of Phase 5 criterion 3; the HTTP layer does not exist until Phase 6/12). **Plans**: TBD