docs(09-03): complete typed winter form schema plan

This commit is contained in:
Jakub Zych
2026-09-24 18:27:51 +02:00
parent 68715fc260
commit db3d7222e9
3 changed files with 259 additions and 10 deletions

View File

@@ -375,7 +375,7 @@ Plans:
4. The relation-manager schema supports search/link/unlink/manage-or-view lists for Collections' editors tab, replacing the `partial` field entirely.
5. Admin CRUD endpoints expose `listExtendQuery`/`formExtendQuery`/`formBeforeCreate`/`formBeforeUpdate`/`relationExtendManageQuery` hooks, bulk delete runs each record's lifecycle hooks, and the Settings model binds to a settings screen through the same schema pipeline.
**Plans**: 2/12 plans executed
**Plans**: 3/12 plans executed
**Research flag:** yes
Plans:
@@ -387,7 +387,7 @@ Plans:
- [x] 09-02-PLAN.md — Complete backend identity lifecycle and operator provisioning
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 09-03-PLAN.md — Compile the typed form-schema contract and Winter scaffolding
- [x] 09-03-PLAN.md — Compile the typed form-schema contract and Winter scaffolding
**Wave 4** *(blocked on Wave 3 completion)*
- [ ] 09-04-PLAN.md — Compile the list contract and the allowlisted query engine
@@ -529,7 +529,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 6. HTTP routing, auth groups and rate limiting | 14/14 | Complete | 2026-09-21 |
| 7. User plugin and authentication | 8/8 | Complete | 2026-09-23 |
| 8. OAuth2.1 authorization server | 10/10 | Complete | 2026-09-23 |
| 9. Backend admin authentication and schema pipeline | 2/12 | In Progress| |
| 9. Backend admin authentication and schema pipeline | 3/12 | In Progress| |
| 10. Admin Vue SPA | 0/TBD | Not started | - |
| 11. Jobs, realtime and search infrastructure | 0/TBD | Not started | - |
| 12. Płytarium API — Collections and Albums | 0/TBD | Not started | - |

View File

@@ -4,16 +4,16 @@ milestone: v1.0
current_phase: 09
current_phase_name: Backend admin authentication and schema pipeline
status: executing
stopped_at: Completed 09-02-PLAN.md
last_updated: "2026-09-24T15:59:20.547Z"
stopped_at: Completed 09-03-PLAN.md
last_updated: "2026-09-24T16:27:09.874Z"
last_activity: 2026-09-24
last_activity_desc: Phase 09 execution started
state_head: 5f218977e4cc61b103a3be4e459fe5aa6962006f
state_head: 68715fc2603f129f0e9f05d8d1db23f5d7594e12
progress:
total_phases: 15
completed_phases: 8
total_plans: 67
completed_plans: 57
completed_plans: 58
milestone_name: milestone
---
@@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-09-16)
## Current Position
Phase: 09 (Backend admin authentication and schema pipeline) — EXECUTING
Plan: 3 of 12
Plan: 4 of 12
Status: Ready to execute
Last activity: 2026-09-24 — Phase 09 execution started
@@ -111,6 +111,7 @@ Progress: [██████████] 100%
|------|----------|-------|-------|
| Phase 09 P01 | 26min | 2 tasks | 26 files |
| Phase 09 P02 | 22 min | 3 tasks | 14 files |
| Phase 09 P03 | 25min | 3 tasks | 10 files |
## Accumulated Context
@@ -276,6 +277,10 @@ Recent decisions affecting current work:
- [Phase 09]: Admin jti rows live in backend_jwt_blacklist and cabana does not republish the frontend BlacklistStore — Refresh and logout must not revoke frontend tokens or be revoked by them.
- [Phase 09]: backend_user_roles.code is indexed and not unique so Winter rows can repeat a code — admin:create rejects zero or many matches instead of a unique constraint the cutover table does not have.
- [Phase 09]: tokens_valid_after is a nullable additive column used to revoke admin JWTs on password reset — The guard already honors Principal.TokensValidAfter and Winter's required columns stay unchanged.
- [Phase 09]: Cached form schemas keep source phrase keys; each response localizes a copy and records meta.locale
- [Phase 09]: Absent config_form.yaml does not fail activation, so the 09-01 Genre list controller still boots
- [Phase 09]: YAML option keys keep their JSON scalar type; method options call DropdownOptions with the exact field name
- [Phase 09]: make:admin-controller writes controllers/name config_form and config_list pointing at models/name fields and columns
### Pending Todos
@@ -298,6 +303,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-09-24T15:59:20.134Z
Stopped at: Completed 09-02-PLAN.md
Last session: 2026-09-24T16:27:09.536Z
Stopped at: Completed 09-03-PLAN.md
Resume file: None

View File

@@ -0,0 +1,244 @@
---
phase: 09-backend-admin-authentication-and-schema-pipeline
plan: 03
subsystem: admin
tags: [yaml, goccy, phrasebook, cabana, admin-schema, scaffolding]
requires:
- phase: 09-backend-admin-authentication-and-schema-pipeline
provides: cabana list compiler, admin controller registry, and phrasebook translator
provides:
- Typed Winter form schema compiled at activation with source order and JSON scalar types
- Per-request localization of labels, comments, tabs, emptyOption, and option labels
- make:admin-controller output that matches the Winter controller/model YAML split
affects: [09-backend-admin-authentication-and-schema-pipeline, admin-api, phase-10-spa]
actuals:
tokens: 13818
tasks: 3
commits: 6
tech-stack:
added: []
patterns:
- "Form IR caches source phrase keys; Localize copies the schema per request"
- "config_form.yaml is compiled only when the file is present, so list-only controllers still boot"
- "Dropdown method names require DropdownOptions on the controller or its NewRecord model"
key-files:
created:
- cabana/schema_types.go
- cabana/form_schema.go
- cabana/form_schema_test.go
modified:
- cabana/contracts.go
- cabana/registry.go
- cabana/schema.go
- phrasebook/translator.go
- internal/build/artifact.go
- internal/build/stubs/artifacts.tmpl
- internal/build/build_test.go
key-decisions:
- "Cached form schemas keep source phrase keys; each response localizes a copy and records meta.locale"
- "Absent config_form.yaml does not fail activation, so the 09-01 Genre list controller still boots"
- "YAML option keys keep their JSON scalar type; method options call DropdownOptions(field) with the exact field name"
- "make:admin-controller writes controllers/<name>/config_form.yaml and config_list.yaml pointing at models/<name>/fields.yaml and columns.yaml"
patterns-established:
- "Pattern: boot compiler walks the YAML AST so field order and option scalar types survive DisallowUnknownField"
- "Pattern: a missing dropdown provider fails with plugin, controller, file, and the exact method name"
requirements-completed: [ADMIN-01]
coverage:
- id: D1
description: Every locked form field and layout hint compiles in source order with Winter JSON keys and typed scalars.
requirement: ADMIN-01
verification:
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaCompile
status: pass
human_judgment: false
- id: D2
description: Empty fields serialize as an array, a single field stays one element, and repeated compilation is byte-stable.
requirement: ADMIN-01
verification:
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaEmpty
status: pass
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaSingle
status: pass
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaOrdering
status: pass
human_judgment: false
- id: D3
description: Unknown keys, unknown types, duplicates, path escape, a mismatched modelClass, partials, and missing assets fail with plugin, controller, and file context.
requirement: ADMIN-01
verification:
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaRejects
status: pass
human_judgment: false
- id: D4
description: One cached schema localizes independently in pl and en, including Accept-Language parent fallback, app.locale, and raw-key fallback.
requirement: ADMIN-01
verification:
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaLocalization
status: pass
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaLocaleIsolation
status: pass
human_judgment: false
- id: D5
description: YAML option maps keep order and scalar type, and a string options method requires DropdownOptions at boot.
requirement: ADMIN-01
verification:
- kind: unit
ref: cabana/form_schema_test.go#TestFormSchemaDropdownOptions
status: pass
human_judgment: false
- id: D6
description: summer make:admin-controller emits the Winter controller/model YAML split, compiles through the strict loader, and refuses a duplicate without partial files.
requirement: ADMIN-01
verification:
- kind: unit
ref: internal/build/build_test.go#TestMakeAdminControllerLayout
status: pass
human_judgment: false
duration: 25min
completed: 2026-09-24
status: complete
plan_head_before: af3312aa9260daa7106f66dff892924db965b930
plan_head_after: 68715fc2603f129f0e9f05d8d1db23f5d7594e12
---
# Phase 9 Plan 03: Typed Winter form schema Summary
**Winter form YAML now compiles into an ordered typed JSON schema, each response localizes its own copy, and `make:admin-controller` emits that same layout.**
## Performance
- **Duration:** 25 min
- **Started:** 2026-09-24T16:01:20Z
- **Completed:** 2026-09-24T16:25:51Z
- **Tasks:** 3
- **Files modified:** 10
## Accomplishments
- `config_form.yaml` and `fields.yaml` compile once at cabana activation into text, textarea, number, checkbox, switch, dropdown, relation, and relation-manager fields, in source order, with Winter JSON spelling.
- Empty fields are `[]`, option values keep string, number, and boolean JSON types, and `type: partial` plus unknown keys, types, path escape, and a mismatched `modelClass` fail before serving.
- Labels, comments, tabs, emptyOption, and option labels resolve per request through phrasebook. The cache stays source keys. `meta.locale` is the selected request locale, or `app.locale` when the request has none.
- `summer make:admin-controller` writes `controllers/<name>/config_form.yaml` and `config_list.yaml` that point at `models/<name>/fields.yaml` and `columns.yaml`. A duplicate asset fails before any new file is written.
## Task Commits
Each task was committed atomically. `commits: 6` is `git rev-list --count` from the plan ledger.
1. **Task 1: Strict ordered form compilation (RED)** - `ea3f070` (test)
2. **Task 1: Strict ordered form compilation (GREEN)** - `4c814e5` (feat)
3. **Task 2: Locale and dropdown options (RED)** - `ad1b760` (test)
4. **Task 2: Locale and dropdown options (GREEN)** - `da8828e` (feat)
5. **Task 3: Winter admin scaffolding (RED)** - `af8e58a` (test)
6. **Task 3: Winter admin scaffolding (GREEN)** - `68715fc` (feat)
**Plan metadata:** pending docs commit
## Files Created/Modified
- `cabana/schema_types.go` - form schema, field, option, and localized view types
- `cabana/form_schema.go` - strict boot compiler and request-time localizer
- `cabana/form_schema_test.go` - golden, empty/single/order, rejection, locale, and dropdown tests
- `cabana/registry.go` - compiles a form when `config_form.yaml` is present
- `cabana/contracts.go` - compiled controllers carry the form schema
- `cabana/schema.go` - exported `CompileList` for the same strict list loader
- `phrasebook/translator.go` - `Locale()` reports the configured app locale
- `internal/build/artifact.go` - Winter directory split and duplicate checks for the full file set
- `internal/build/stubs/artifacts.tmpl` - `config_form.yaml` and `config_list.yaml` stubs
- `internal/build/build_test.go` - layout, strict compile, registry stability, and partial-write tests
## Decisions Made
- The cached schema stores phrase keys. `Localize` copies fields and options, so one request's locale cannot change another's result or the boot cache.
- `meta.locale` is the primary Accept-Language tag when the request has one (`pl-PL,en;q=0.5` becomes `pl-PL`). Phrasebook still parent-falls back to `pl`. With no request locale, meta uses the translator's app locale.
- A controller with only `config_list.yaml` keeps activating. The 09-01 Genre tracer has no form yet. A present but invalid form still fails activation.
- Dropdown `options:` maps keep declaration order and key scalar type. A string such as `getFormatOptions` is a method name, compared exactly, and the controller or `NewRecord()` model must implement `DropdownOptions`. Only the label is translated.
- Scaffolded `modelClass` is the controller ident (`Albums`), matching `ModelName()`, and `ConfigDir()` stays `controllers/<name>`.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 2 - Missing Critical] Exposed the configured app locale**
- **Found during:** Task 2 (locale and dropdown options)
- **Issue:** `phrasebook.Translator` kept `app.locale` unexported, so a request with no Accept-Language could not put the real app locale in `meta.locale`.
- **Fix:** Added `Translator.Locale()`. Cabana does not read config itself.
- **Files modified:** `phrasebook/translator.go`, `cabana/form_schema.go`
- **Verification:** `TestFormSchemaLocalization/parent_and_app_locale` passed
- **Committed in:** `da8828e`
**2. [Rule 3 - Blocking] Exported the list compiler for scaffold verification**
- **Found during:** Task 3 (Winter admin scaffolding)
- **Issue:** Generated `config_list.yaml` had to compile through the strict loader, and `compileList` was unexported to the build tests.
- **Fix:** Exported `cabana.CompileList` as a wrapper. No behavior change for activation.
- **Files modified:** `cabana/schema.go`, `internal/build/build_test.go`
- **Verification:** `TestMakeAdminControllerLayout/winter_files` passed
- **Committed in:** `af8e58a` (export) and `68715fc` (scaffold)
---
**Total deviations:** 2 auto-fixed (1 missing critical, 1 blocking)
**Impact on plan:** Both keep the locked locale and loader contracts testable. No new dependency and no response-shape change outside the form schema this plan owns.
## TDD Gate Compliance
| Gate | Commit | Result |
|------|--------|--------|
| RED task 1 | `ea3f070` test(09-03) | `TestFormSchemaCompile` failed because compilation was not implemented |
| GREEN task 1 | `4c814e5` feat(09-03) | compile, empty, single, ordering, and reject tests passed |
| RED task 2 | `ad1b760` test(09-03) | `TestFormSchemaLocalization` failed because localization was not implemented |
| GREEN task 2 | `da8828e` feat(09-03) | localization, dropdown, and race-isolated locale tests passed |
| RED task 3 | `af8e58a` test(09-03) | `TestMakeAdminControllerLayout` failed on the old controller-local YAML paths |
| GREEN task 3 | `68715fc` feat(09-03) | Winter layout, strict compile, and duplicate-without-partial-write passed |
`gsd_run check tdd-red-evidence` returned `RED_EVIDENCE_OK` for all three RED runs. Go's test output is not TAP, so each evidence record appends a TAP trailer naming the test the go harness failed.
## Authentication Gates
None.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Ready for 09-04. Form compilation, localization, and the Winter scaffold layout are in place.
- ADMIN-01 stays pending in REQUIREMENTS.md until the later plans that also declare it have summaries.
- List-only controllers remain valid until those plans add `config_form.yaml`.
## Self-Check: PASSED
- FOUND: cabana/form_schema.go
- FOUND: cabana/schema_types.go
- FOUND: cabana/form_schema_test.go
- FOUND: internal/build/stubs/artifacts.tmpl
- FOUND: ea3f070
- FOUND: 4c814e5
- FOUND: ad1b760
- FOUND: da8828e
- FOUND: af8e58a
- FOUND: 68715fc
---
*Phase: 09-backend-admin-authentication-and-schema-pipeline*
*Completed: 2026-09-24*