7.3 KiB
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//fields.yaml and columns.yaml; scaffold updated | ✓ |
| Scaffold layout | Keep controllers//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.