Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-06-SUMMARY.md
2026-09-24 20:22:27 +02:00

264 lines
12 KiB
Markdown

---
phase: 09-backend-admin-authentication-and-schema-pipeline
plan: 06
subsystem: admin
tags: [cabana, albums, winter-yaml, collection-scope, dropdowns, postgres]
requires:
- phase: 09-backend-admin-authentication-and-schema-pipeline
provides: compiled form and list schemas, permission gate, and schema-projected CRUD
provides:
- Registered Albums admin controller with embedded Winter form and list YAML
- Format, genre, and style dropdown options without cross-field leakage
- Exact active-collection binding for album create, update, list, and bulk delete
affects: [09-backend-admin-authentication-and-schema-pipeline, admin-api, phase-10-spa]
actuals:
tokens: 14974
tasks: 3
commits: 2
tech-stack:
added: []
patterns:
- "Winter relation keys match exported Go fields case-insensitively; served JSON keeps the YAML spelling"
- "Album admin scope is the active collection of the one active frontend user with the backend email"
- "Non-scalar required flags stay on the form schema and are not save-time column rules"
key-files:
created:
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums/config_form.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums/config_list.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/models/album/fields.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/models/album/columns.yaml
- ../fonoteka.go/plugins/golem15/fonoteka/classes/backend_album_collection.go
- ../fonoteka.go/plugins/golem15/fonoteka/admin_albums_test.go
modified:
- cabana/http.go
- cabana/query.go
- cabana/list_schema.go
- cabana/crud.go
- ../fonoteka.go/plugins/golem15/fonoteka/models/album.go
- ../fonoteka.go/plugins/golem15/fonoteka/admin.go
key-decisions:
- "The album foreign key written by D-14 is collection_id of the matched frontend user's active collection; albums have no user_id column"
- "Genre and style option values are decimal strings because pact.Option.Value is a string"
- "relation: genre resolves to field Genre while the schema JSON keeps relation genre"
- "Required relation fields remain in the form schema and are not applied as save validation"
patterns-established:
- "Pattern: albumsAdminController resolves *gorm.DB per call from the booted app, never a process-global collection id"
- "Pattern: zero, duplicate, inactive, and no-active-collection email matches share one 422 and name no candidate rows"
requirements-completed: [ADMIN-01, ADMIN-02, ADMIN-04]
coverage:
- id: D1
description: Albums form schema serves every Winter field, locale key, and format scalar in source order for pl and en.
requirement: ADMIN-01
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminForm
status: pass
human_judgment: false
- id: D2
description: Albums registration resolves the album model and embedded assets and rejects a mismatched model or missing directory.
requirement: ADMIN-01
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminRegistration
status: pass
human_judgment: false
- id: D3
description: Denied form, create, and update requests return 403 before album, genre, style, or frontend-user SQL.
requirement: ADMIN-01
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminPermissionOrder
status: pass
human_judgment: false
- id: D4
description: Albums list schema keeps columns, toolbar actions, filters, and locale keys in declaration order.
requirement: ADMIN-02
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminList
status: pass
human_judgment: false
- id: D5
description: Format, genre, and style dropdowns are ordered and do not leak choices across fields.
requirement: ADMIN-01
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminDropdowns
status: pass
human_judgment: false
- id: D6
description: Permitted album lists keep empty, single, equal-value, and adjacent page behavior, and unknown sorts return 422.
requirement: ADMIN-02
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminListEdges
status: pass
human_judgment: false
- id: D7
description: One active normalized email persists that frontend user's active collection id.
requirement: ADMIN-04
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminCollectionMatch
status: pass
human_judgment: false
- id: D8
description: Duplicate, inactive, and no-active-collection email matches return 422 and persist nothing.
requirement: ADMIN-04
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminAmbiguousEmail
status: pass
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminInactiveUser
status: pass
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminCrossCollection
status: pass
human_judgment: false
- id: D9
description: Client user_id and collection_id cannot override the resolved collection, and responses do not echo those keys.
requirement: ADMIN-04
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminProtectedAssociation
status: pass
human_judgment: false
- id: D10
description: Scoped update, show, and atomic bulk delete keep foreign albums unchanged.
requirement: ADMIN-04
verification:
- kind: integration
ref: plugins/golem15/fonoteka/admin_albums_test.go#TestAlbumsAdminCRUD
status: pass
human_judgment: false
duration: 29min
completed: 2026-09-24
status: complete
plan_head_before: 3e7738ff32012699bccc4e3f53ea347c87b45f8b
plan_head_after: 0caa86ec0b6d255dd22a55ee5923a40d3a821977
---
# Phase 9 Plan 06: Albums Admin Collection Boundary Summary
**Albums admin form, list, and writes bind only the active collection of the one active frontend user matched by the backend account email.**
## Performance
- **Duration:** 29 min
- **Started:** 2026-09-24T17:50:44Z
- **Completed:** 2026-09-24T18:20:13Z
- **Tasks:** 3
- **Files modified:** 15
## Accomplishments
- Embedded the Winter Albums form and list YAML and served the localized form at `GET /_admin/api/v1/golem15/fonoteka/albums/schema/form`.
- Format options keep Album::FORMATS scalars and phrase keys; genre and style options are active rows ordered by name then id.
- Create and update set `collection_id` from `ResolveBackendAlbumCollectionUser`. Lists, shows, updates, and bulk deletes cannot see another collection.
## TDD Gate Compliance
Each task has a RED `test(09-06)` commit before its GREEN `feat(09-06)` commit. RED evidence checks returned `RED_EVIDENCE_OK` for `TestAlbumsAdminForm`, `TestAlbumsAdminDropdowns`, and `TestAlbumsAdminCollectionMatch`.
## Task Commits
1. **Task 1: Port complete Albums form and controller registration** - `60a4b10` (test, fonoteka.go), `c63146a` (feat, summercms.go), `652bfda` (feat, fonoteka.go)
2. **Task 2: Port Albums list and typed option providers** - `81f1aa4` (test, fonoteka.go), `5de760d` (feat, fonoteka.go)
3. **Task 3: Enforce exact active-collection user resolution** - `6004c6a` (test, fonoteka.go), `0caa86e` (feat, summercms.go), `15dfefa` (feat, fonoteka.go)
The summercms.go ledger `3e7738ff..0caa86e` contains the two framework commits above. Fonoteka commits are in `fonoteka.go` `60a4b10..15dfefa`.
**Plan metadata:** `2a955d6` (docs: complete albums admin collection boundary plan)
## Files Created/Modified
- `fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go` - Albums controller, permissions, dropdowns, and collection hooks
- `fonoteka.go/plugins/golem15/fonoteka/classes/backend_album_collection.go` - exact normalized email resolution
- `fonoteka.go/plugins/golem15/fonoteka/models/album/fields.yaml` - Winter album form, copied unchanged
- `fonoteka.go/plugins/golem15/fonoteka/models/album/columns.yaml` - Winter album columns, copied unchanged
- `fonoteka.go/plugins/golem15/fonoteka/models/album.go` - `Genre` association and `getFormatOptions`
- `cabana/http.go` - `GET .../schema/form` behind the existing permission check
- `cabana/query.go` and `cabana/list_schema.go` - case-insensitive relation field match
- `cabana/crud.go` - required validation limited to scalar writable fields
- `fonoteka.go/plugins/golem15/fonoteka/admin_albums_test.go` - assembled PostgreSQL proof
## Decisions Made
- D-14 stores `collection_id`, the active collection of the matched frontend user. The albums table has no `user_id` column, so none was added.
- `pact.Option.Value` is a string, so genre and style ids are decimal strings. Format values stay the Winter scalars (`LP`, `EP 7"`, and the rest).
- Served list JSON keeps `relation: genre`. Joins and preloads use the exported Go field `Genre`.
- `artists` stays `required: true` on the form schema. Save validation does not require it, because the admin CRUD path cannot bind a relation.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Embedded the list YAML while registering the controller**
- **Found during:** Task 1
- **Issue:** Activation compiles `config_list.yaml` for every admin controller. Registering Albums with only the form files failed boot.
- **Fix:** Copied the Winter list and columns YAML in the same commit as the form.
- **Files modified:** `controllers/albums/config_list.yaml`, `models/album/columns.yaml`
- **Verification:** `TestAlbumsAdminRegistration` and `TestAlbumsAdminList`
- **Committed in:** `652bfda`
**2. [Rule 2 - Missing Critical] Served the form schema over HTTP**
- **Found during:** Task 1
- **Issue:** Form compilation existed, but no route served it, so permission-denied form requests could not be proven.
- **Fix:** Added `GET /{vendor}/{plugin}/{controller}/schema/form` using the same `protect` gate as the list schema.
- **Files modified:** `cabana/http.go`
- **Verification:** `TestAlbumsAdminForm` and `TestAlbumsAdminPermissionOrder`
- **Committed in:** `c63146a`
**3. [Rule 1 - Bug] Matched Winter relation names to exported Go fields**
- **Found during:** Task 1
- **Issue:** `relation: genre` did not match field `Genre`, so the unchanged columns YAML could not activate.
- **Fix:** Accept an exact field name, otherwise a case-insensitive relation match. JSON still says `genre`.
- **Files modified:** `cabana/list_schema.go`, `cabana/query.go`, `models/album.go`
- **Verification:** `go test ./cabana` and `TestAlbumsAdminList`
- **Committed in:** `c63146a`, `652bfda`
**4. [Rule 2 - Missing Critical] Did not save-validate unbound relation required flags**
- **Found during:** Task 3
- **Issue:** `artists.required` made every album create 422 before the collection hook ran. The field is not a writable column.
- **Fix:** Merge `required` into save rules only for scalar writable field types. The schema JSON is unchanged.
- **Files modified:** `cabana/crud.go`
- **Verification:** `go test ./cabana` and `TestAlbumsAdminCollectionMatch`
- **Committed in:** `0caa86e`
---
**Total deviations:** 4 auto-fixed (1 bug, 2 missing critical, 1 blocking)
**Impact on plan:** Required for unchanged Winter YAML to boot and for album creates to reach the collection hook. No new album columns and no extra controllers.
## Issues Encountered
`TestPhase8RedMCPMe` and `TestOAuthToolsMeRouteIsolation` fail because `GET /_admin/api/v1/auth/me` ends in `/me`. That route was already mounted with the admin API before this plan. It was left unchanged.
`ADMIN-01`, `ADMIN-02`, and `ADMIN-04` stay pending in `REQUIREMENTS.md`. Later plans in this phase still declare them.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
Ready for 09-07. Albums is the only new admin controller. Artists, Styles, and Collections controllers were not added. Genre remains the existing list tracer.
## Self-Check: PASSED
- FOUND: `fonoteka.go` `60a4b10`, `652bfda`, `81f1aa4`, `5de760d`, `6004c6a`, `15dfefa`
- FOUND: `summercms.go` `c63146a`, `0caa86e`
- FOUND: albums controller, form YAML, columns YAML, and `backend_album_collection.go`
---
*Phase: 09-backend-admin-authentication-and-schema-pipeline*
*Completed: 2026-09-24*