---
phase: 09-backend-admin-authentication-and-schema-pipeline
plan: 03
type: execute
wave: 3
depends_on: [09-01, 09-02]
files_modified:
- cabana/contracts.go
- cabana/schema.go
- cabana/schema_types.go
- cabana/form_schema.go
- cabana/form_schema_test.go
- internal/build/artifact.go
- internal/build/stubs/artifacts.tmpl
- internal/build/build_test.go
autonomous: true
requirements: [ADMIN-01]
estimate:
tokens: 40000
raw_tokens: 40000
tasks: 3
confidence: low
must_haves:
truths:
- "D-05/D-06 Winter-shaped config_form.yaml and fields.yaml compile at boot into typed text, textarea, number, checkbox, switch, dropdown, relation, and relation-manager fields; unsupported keys/types and `type: partial` fail with plugin/controller/file context."
- "D-07 labels, comments, tabs, emptyOption, and option labels resolve at response time from Accept-Language then app.locale, and every schema response reports the resolved locale in meta."
- "D-08 dropdown maps preserve literal values and model-method options require DropdownOptions at boot; missing providers fail activation, while YAML maps remain supported."
- "ADMIN-01 edge rule: an empty fields document serializes as `fields: []` rather than null, a single field stays a one-element array, source order is stable, option values preserve JSON scalar type, and field/provider identifiers compare exactly and case-sensitively."
artifacts:
- path: "cabana/schema_types.go"
provides: "Typed discriminated form/list/filter/relation schema contracts"
- path: "cabana/form_schema.go"
provides: "Strict boot compiler and request-locale serializer for forms"
- path: "cabana/form_schema_test.go"
provides: "Golden JSON, empty/single/order, option-provider, localization, and malformed-YAML coverage"
- path: "internal/build/stubs/artifacts.tmpl"
provides: "Winter-layout make:admin-controller templates"
key_links:
- from: "cabana/form_schema.go"
to: "github.com/goccy/go-yaml"
via: "DisallowUnknownField boot decoding"
pattern: "DisallowUnknownField"
- from: "cabana/form_schema.go"
to: "phrasebook/translator.go"
via: "request-time display-string resolution"
pattern: "phrasebook.Translator"
- from: "internal/build/artifact.go"
to: "pact/capabilities.go"
via: "generated controller preserves AdminController contract and ConfigDir"
pattern: "AdminController"
prohibitions:
- "[FLAGGED-UNVERIFIED] The form compiler must not accept `type: partial` or execute server-rendered partial paths."
- "[FLAGGED-UNVERIFIED] Display labels must not be deferred to a client-side translation catalogue or cached in the first request's locale."
- "[FLAGGED-UNVERIFIED] Unknown YAML keys/types/providers must not be silently ignored or represented primarily as map[string]any."
---
## Phase Goal
**As a** backend administrator, **I want to** authenticate separately and manage resources described by Winter-shaped schemas, **so that** the administration surface stays permission-gated and reusable without coupling it to frontend users.
Expand the tracer compiler into the complete typed form-schema contract and update scaffolding to emit the locked Winter layout.
Purpose: ADMIN-01 is the reusable server-side contract Phase 10 renders, so malformed assets and locale leakage must fail before handlers serve traffic.
Output: Discriminated schema types, strict form compiler/localizer/options providers, golden tests, and corrected make:admin-controller output.
@/home/jin/.codex/gsd-core/workflows/execute-plan.md
@/home/jin/.codex/gsd-core/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md
@.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-RESEARCH.md
@.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-01-SUMMARY.md
@phrasebook/translator.go
@pact/capabilities.go
@internal/build/artifact.go
@internal/build/stubs/artifacts.tmpl
Preserve 09-01's immutable `cabana.Registry` and plugin `AdminFS` ownership. Use `phrasebook.Translator.Get` for request-localized strings. The existing scaffold returns `pact.AdminController` with ID/ModelName/ConfigDir; retain those methods while changing generated asset paths.
## Artifacts this phase produces
- Typed `cabana.FormSchema`, `Field` variants, layout/context/attributes metadata, and `Option`
- `pact.DropdownOptionsProvider`
- Boot compiler/validator and request-time form localizer
- Strict fixtures/golden contract tests for empty, single, ordered, all-field, unknown-key/type/provider, and partial rejection cases
- Winter-shaped controller/model YAML scaffolding from `summer make:admin-controller`
Task 1: Compile every locked form field with strict ordered semantics
D-06 fixes the JSON field spelling and discriminated schema shape that Phase 10 generates types from.
cabana/contracts.go, cabana/schema.go, cabana/schema_types.go, cabana/form_schema.go, cabana/form_schema_test.go
cabana/schema.go, cabana/contracts.go, phrasebook/translator.go, pact/capabilities.go, .planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md, .planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-RESEARCH.md
- Test 1: all D-06 field kinds and layout keys compile in source order and marshal with Winter-spelled JSON keys and correct string/bool/number scalar types.
- Test 2: empty and one-field inputs marshal as stable non-null arrays; repeated compilation produces byte-equivalent normalized JSON.
- Test 3: unknown keys, unknown types, duplicate names, path escape, mismatched modelClass, raw partial fields, and missing assets fail boot with plugin/controller/file context.
Define typed controller/form documents and discriminated JSON DTOs, then compile embedded assets once at cabana activation with goccy/go-yaml `DisallowUnknownField`. Preserve YAML declaration order explicitly instead of ranging over Go maps; validate config_form modelClass and model fields path against the registered controller/plugin FS; keep controller/model identifiers exact and finite; and fail with contextual errors for every unsupported construct. Cover text, textarea, number, checkbox, switch, dropdown, relation, and relation-manager plus nameFrom, emptyOption, span, tab, context, attributes, size, default, and required. Reject the PHP partial form entirely per D-15.
go test ./cabana -run '^TestFormSchema(Compile|Empty|Single|Ordering|Rejects)' -count=1
The command exits non-zero, reports no matching test, empty fields become null, order changes between runs, scalar types drift, a path escapes the embedded FS, or any unsupported key/type/partial/provider is accepted.
All form fields/layout hints compile into one stable typed contract, and every malformed or unsupported input fails activation with actionable context.
The form pipeline has complete ADMIN-01 type coverage and deterministic empty/single/ordered behavior.
Task 2: Resolve locale and dropdown options without polluting the cache
cabana/contracts.go, cabana/form_schema.go, cabana/form_schema_test.go
cabana/form_schema.go, phrasebook/translator.go, towel/context.go, pact/capabilities.go
- Test 1: the same cached source schema serializes independently in pl and en, with meta.locale and raw-key fallback; one request never changes another locale's result.
- Test 2: YAML option maps preserve declaration order and value scalar type; method options invoke DropdownOptions(field), localize only label keys, and reject a missing provider during activation.
- Test 3: Accept-Language parent fallback and app.locale behavior match phrasebook, including label/comment/tab/emptyOption.
Keep cached schemas as untranslated source-key IR. At each schema response, select Accept-Language with app.locale fallback, resolve D-07 display keys through the existing phrasebook translator, and attach the actual resolved locale to meta. Add D-08's exact `DropdownOptions(field string) []Option` capability: validate method-name configuration and provider presence at boot, obtain provider options for the registered model, preserve option values, and translate only the label keys. Ensure map options and provider options share one ordered JSON representation.
go test ./cabana -run '^TestFormSchema(Localization|DropdownOptions|LocaleIsolation)' -count=1
The command exits non-zero, reports no matching test, locale results contaminate one another, meta.locale is absent/wrong, option values change type/order, or a string provider survives boot without the required interface.
Cached source contracts are locale-neutral; every response is localized independently and all dropdown sources obey the D-08 provider contract.
ADMIN-01 schemas carry display-ready request-localized strings and stable option values.
Task 3: Make admin scaffolding emit the Winter controller/model layout
internal/build/artifact.go, internal/build/stubs/artifacts.tmpl, internal/build/build_test.go
internal/build/artifact.go, internal/build/stubs/artifacts.tmpl, internal/build/registry.go, internal/build/build_test.go, pact/capabilities.go
- Test 1: make:admin-controller creates controller Go plus controllers/name/config_form.yaml, config_list.yaml and models/model/fields.yaml, columns.yaml with ConfigDir pointing to controllers/name.
- Test 2: generated assets compile through the strict cabana loader and registry regeneration remains byte-stable.
- Test 3: duplicate controller/model asset paths fail without partially writing files.
Change D-05 scaffolding from controller-local fields/columns to the Winter directory split: controller config files point at model fields/columns, the generated AdminController retains its stable ID/ModelName/ConfigDir contract, and all assets remain under the plugin FS. Update rollback-on-error and duplicate detection for the complete file set. Extend build tests to inspect exact paths/content, compile the generated plugin, and prove a second run is byte-stable.
go test ./internal/build -run '^Test.*AdminController' -count=1
The command exits non-zero, reports no matching test, generated paths retain the old controller-local fields/columns layout, ConfigDir is wrong, generated YAML fails strict compilation, or a failed scaffold leaves partial files.
`summer make:admin-controller` creates the exact D-05 asset layout and a compiling registered controller without rewriting handwritten plugin files.
New plugins can scaffold directly into the same typed form pipeline used by Fonoteka.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Embedded plugin YAML → compiled schema | Configuration text selects field kinds, asset paths, providers, and serialized contract data. |
| Request locale → schema serializer | Per-request language choice affects display data but must not mutate shared cached state. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-09-05 | Tampering | strict form-schema compiler | high | mitigate | Discriminated types, finite exact identifiers, embedded-FS path validation, unknown-key/type/provider/partial boot failures, and executable rejection fixtures. |
| T-09-06 | Information Disclosure / Tampering | localized schema cache | medium | mitigate | Cache untranslated IR only and execute concurrent pl/en isolation tests with explicit meta.locale. |
| T-09-SC | Tampering | Go module dependency set | high | mitigate | Reuse the already-pinned goccy/go-yaml and phrasebook packages; no install task exists, and any dependency proposal requires the package-legitimacy gate. |
- Form-schema golden and malformed fixture suites pass.
- Locale and option-provider behavior remains stable across repeated/concurrent serialization.
- The corrected generator produces compiling strict-loader-compatible assets.
- Every in-scope form field/layout feature is typed and deterministic.
- Malformed or unsupported plugin assets fail before serving requests.
- Request-localized output never pollutes the boot cache.
- Scaffolding emits the same Winter layout the runtime consumes.