Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-DISCUSSION-LOG.md
2026-09-23 10:24:22 +02:00

171 lines
7.3 KiB
Markdown

# Phase 9: Backend admin authentication and schema pipeline - 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-23
**Phase:** 09-backend-admin-authentication-and-schema-pipeline
**Areas discussed:** Admin identity and login, Schema pipeline shape, Admin CRUD API contract, Relation manager and settings
---
## Admin identity and login
| Option | Description | Selected |
|--------|-------------|----------|
| Winter-shaped tables | backend_users + backend_user_roles, groups skipped, is_superuser and role codes kept | ✓ |
| Lean admin model | One summer_admins table with role string and permissions JSON | |
| Reuse frontend users table with a flag | is_backend + role on users | |
**User's choice:** Winter-shaped tables
| Option | Description | Selected |
|--------|-------------|----------|
| JWT via a `backend` bouncer guard | Second guard beside `jwt`, own secret and audience, reuses Phase 7 mint/verify/blacklist | ✓ |
| Cookie sessions with scs | HttpOnly cookie, Postgres session store, CSRF | |
| You decide | | |
**User's choice:** JWT via a `backend` bouncer guard
| Option | Description | Selected |
|--------|-------------|----------|
| Winter strings with wildcard | HasPermissions in registerPermissions shape, is_superuser, role JSON, `.*` prefix match, RequiredPermissions() enforced by framework | ✓ |
| Flat permission list, no wildcards | Exact match only | |
| You decide | | |
**User's choice:** Winter strings with wildcard
| Option | Description | Selected |
|--------|-------------|----------|
| Console command | `summer admin:create` + `admin:reset-password` | ✓ |
| Seeded from config | Boot-time superuser from YAML/env | |
| Both | Command canonical, env seed opt-in | |
**User's choice:** Console command
---
## Schema pipeline shape
| Option | Description | Selected |
|--------|-------------|----------|
| Winter layout | config_form/config_list point at models/<model>/fields.yaml and columns.yaml; scaffold updated | ✓ |
| Scaffold layout | Keep controllers/<name>/fields.yaml as Phase 4 emits | |
| You decide | | |
**User's choice:** Winter layout
| Option | Description | Selected |
|--------|-------------|----------|
| Typed schema, Winter key names | Go structs per field type, unknown keys fail, Winter spelling in JSON | ✓ |
| Pass-through map | Generic map served as-is | |
| Typed schema, normalized names | snake_case house style | |
**User's choice:** Typed schema, Winter key names
| Option | Description | Selected |
|--------|-------------|----------|
| Resolved server-side | phrasebook resolves labels in the admin's locale; SPA gets strings | ✓ |
| Raw keys plus a lang bundle endpoint | SPA resolves locally | |
| Both fields | label and labelKey side by side | |
**User's choice:** Resolved server-side
| Option | Description | Selected |
|--------|-------------|----------|
| Options provider interface on the model | DropdownOptions(field) []Option; static YAML maps also work | ✓ |
| Registry keyed by method name | Controller registers a map of func by name | |
| You decide | | |
**User's choice:** Options provider interface on the model
---
## Admin CRUD API contract
| Option | Description | Selected |
|--------|-------------|----------|
| /_admin/api/v1, plain JSON, no envelope | Plain resource bodies, `{message, errors}` errors | |
| Reuse the fonoteka house envelope | Same helpers as /_fonoteka/api/v1 | |
| You decide | | |
**User's choice:** Free text: "Do you really recommend 1? Our chance to introduce nice and consistent api layer." Claude proposed a framework-wide layer (plugin-namespaced paths, one envelope with a fixed error-code vocabulary, one list contract, one verb set, relation endpoints under the record, swag-annotated framework package). User: "Yes, let's proceed with that."
**Notes:** Recorded as D-09 to D-11.
| Option | Description | Selected |
|--------|-------------|----------|
| Winter list features only | search, sort, dir, page, recordsPerPage, relation columns | |
| Add per-column filters | filter[column]=value exact matches | |
| You decide | | |
**User's choice:** Free text: "Filters in Winter are done with config_filters.yaml we need filters like that here." Claude found the user and journal plugins' config_filter.yaml files (switch, daterange, model-backed scope) and asked about scope coverage and raw SQL `conditions:`. User: "Port 3 shapes now, note for future phases that this will be extended and all shapes will be introduced in time. Model scope methods instead of SQL strings are fully acceptable."
**Notes:** Recorded as D-12; remaining scope types deferred.
| Option | Description | Selected |
|--------|-------------|----------|
| Optional interfaces on the controller | Type-asserted hooks with admin principal in context; bulk delete per record | ✓ |
| Struct of func fields | Optional func fields on AdminController | |
| You decide | | |
**User's choice:** Optional interfaces on the controller
| Option | Description | Selected |
|--------|-------------|----------|
| Port the email mapping | Admin email must match exactly one frontend user; reuse its active collection; 422 otherwise | ✓ |
| Explicit collection picker | SPA header on every request | |
| Both | Mapping default, header override for superusers | |
**User's choice:** Port the email mapping
---
## Relation manager and settings
| Option | Description | Selected |
|--------|-------------|----------|
| config_relation.yaml as a first-class schema | Typed relation schema; `type: relation-manager` replaces `partial` | ✓ |
| Keep `partial` as an alias | Map partial to relation manager | |
| You decide | | |
**User's choice:** config_relation.yaml as a first-class schema
| Option | Description | Selected |
|--------|-------------|----------|
| Pivot fields stamped by the framework hook | RelationBeforeLink stamps role, granted_at, granted_by; unlink deletes pivot | ✓ |
| Bare pivot insert | Only the two foreign keys | |
| You decide | | |
**User's choice:** Pivot fields stamped by the framework hook
| Option | Description | Selected |
|--------|-------------|----------|
| HasSettings capability + singleton model | registerSettings shape, /settings/{code} GET/PUT via Fillable | ✓ |
| Settings as an ordinary admin controller | Form with no list, hand-written per page | |
| You decide | | |
**User's choice:** HasSettings capability + singleton model
| Option | Description | Selected |
|--------|-------------|----------|
| HasNavigation in Winter's shape, URL becomes a route key | url replaced by controller ID; /navigation filtered by permissions | ✓ |
| Winter shape with literal URLs | Keep url strings | |
| You decide | | |
**User's choice:** HasNavigation in Winter's shape, URL becomes a route key
---
## Claude's Discretion
- Package naming and layout for the admin API and schema packages.
- YAML parse timing (boot vs lazy), `rules:` in fields.yaml vs model Rules().
- Admin login throttling and password policy.
- Soft-deleted rows in admin lists; settings row creation timing; relation candidate pagination.
## Deferred Ideas
- Remaining Winter filter scope types.
- backend_user_groups, preferences, access logs.
- Policy-based row/field-level permissions.
- Boot-time admin seed from environment.