---
phase: 09-backend-admin-authentication-and-schema-pipeline
plan: 04
type: execute
wave: 4
depends_on: [09-02, 09-03]
files_modified:
- cabana/schema.go
- cabana/schema_types.go
- cabana/list_schema.go
- cabana/filter_schema.go
- cabana/query.go
- cabana/http.go
- cabana/list_schema_test.go
- cabana/query_test.go
- cabana/testdata/list/all_columns.yaml
- cabana/testdata/list/all_filters.yaml
autonomous: true
requirements: [ADMIN-02]
estimate:
tokens: 30000
raw_tokens: 30000
tasks: 3
confidence: low
must_haves:
truths:
- "Per D-06 and D-11, list schemas expose ordered columns, searchable/sortable flags, filters, row actions, bulk actions, default sort, search-term name, and pagination defaults with Winter-compatible JSON spelling."
- "ADMIN-02 edge contract: an empty query returns data: [] (never null), page 1, perPage default, total 0, lastPage 1; a single row remains a one-element array; absent optional schema collections serialize as []."
- "ADMIN-02 equality/adjacency contract: rows that compare equal on the requested sort are ordered by primary key ascending, so adjacent pages are stable and contain neither duplicates nor gaps."
- "ADMIN-02 encoding contract: column/filter/scope identifiers compare exactly and case-sensitively against compiled declarations; values use typed JSON scalars and all user values remain bound parameters."
- "Per D-12, switch, date-range, and model-scope filters execute only through typed declarations; no YAML or request value can become a raw SQL condition or arbitrary method call."
artifacts:
- path: "cabana/list_schema.go"
provides: "Strict ordered list-schema compilation"
- path: "cabana/filter_schema.go"
provides: "Typed switch/date-range/model-scope filter contracts"
- path: "cabana/query.go"
provides: "Allowlisted search, sort, filter, scope, and pagination execution"
- path: "cabana/query_test.go"
provides: "Determinism, adjacency, injection, and pagination coverage"
key_links:
- from: "cabana/list_schema.go"
to: "cabana/query.go"
via: "compiled finite identifiers are the only query selectors"
- from: "cabana/http.go"
to: "cabana/query.go"
via: "authenticated index handler passes typed query input and receives D-11 envelope"
- from: "cabana/query.go"
to: "lagoon/paginate.go"
via: "framework pagination semantics plus deterministic tie-break"
prohibitions:
- "[flagged-unverified] A controller YAML file must not inject SQL fragments or name an arbitrary Go method through search, sort, filter, or scope configuration."
- "[flagged-unverified] Equal sort values must not make records jump, duplicate, or disappear across adjacent list pages."
---
## 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.
Compile the complete ADMIN-02 list contract and execute it through a deterministic, allowlisted query engine.
Purpose: Turn the tracer's one Genre index into the reusable list behavior every backend controller needs while preserving D-06, D-11, and D-12 exactly.
Output: Strict list/filter compilation, safe query planning, and authenticated JSON list responses with explicit edge semantics.
@/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-02-SUMMARY.md
@.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-03-SUMMARY.md
@lagoon/paginate.go
@lagoon/relations.go
@cabana/schema.go
@cabana/http.go
## Artifacts this phase produces
- `cabana.ListSchema`, `cabana.ListColumn`, `cabana.ListFilter`, `cabana.RowAction`, and `cabana.BulkAction`
- `cabana.CompileListSchema` and strict fixtures under `cabana/testdata/list/`
- `cabana.ListQuery`, `cabana.QueryPlanner`, and `cabana.ListResult`
- Authenticated controller `GET /admin/api/v1/{controller}` list execution with D-11 JSON
Task 1: Compile ordered list columns and action declarations
D-06 and D-11 define a generated-client JSON contract; spelling or scalar changes require a coordinated downstream regeneration.
cabana/schema.go, cabana/schema_types.go, cabana/list_schema.go, cabana/list_schema_test.go, cabana/testdata/list/all_columns.yaml
cabana/schema.go, cabana/schema_types.go, cabana/form_schema.go, lagoon/paginate.go, .planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md
- Test 1: every D-06 list column key, row action, bulk action, default sort, search term, records-per-page option, and show-setup switch compiles with exact typed JSON.
- Test 2: empty/single/multiple declarations marshal as non-null arrays and retain YAML declaration order across repeated compilation.
- Test 3: duplicate names, unsupported keys/actions, invalid defaults, path escape, and mismatched controller/model assets fail activation with context.
Extend the strict compiler from Plan 03 with typed list documents and DTOs per D-06 and D-11. Preserve declaration order without map iteration, validate all field and action identifiers against their controller/model contract, normalize omitted collections to allocated empty slices, and reject ambiguous or unsupported configuration during cabana activation. Treat names as exact case-sensitive identifiers and keep raw locale keys in cached IR for request-time translation.
go test ./cabana -run '^TestListSchema(Compile|Empty|Single|Ordering|Rejects)$' -count=1
The command exits non-zero, reports no matching test, any empty collection marshals null, source order changes, an undeclared identifier survives, or a schema error lacks plugin/controller/file context.
The compiler represents every D-06/D-11 list key as stable typed JSON and rejects every unsupported declaration before routes serve traffic.
List columns, actions, defaults, and pagination metadata have deterministic schema semantics.
Task 2: Compile switch, date-range, and model-scope filters
cabana/list_schema.go, cabana/filter_schema.go, cabana/list_schema_test.go, cabana/testdata/list/all_filters.yaml
cabana/list_schema.go, lagoon/relations.go, lagoon/lifecycle.go, pact/capabilities.go, ../fonoteka.go/plugins/golem15/fonoteka/models/album.go
- Test 1: switch filters retain typed true/false values, date-range filters identify a finite column, and model scopes resolve only a registered typed provider.
- Test 2: empty filter arrays and one filter serialize deterministically; option labels localize while values and identifiers do not change.
- Test 3: unknown scope/provider/column/type, raw condition text, duplicate filter name, and arbitrary method names fail activation.
Implement the three D-12 filter kinds as discriminated types. Validate switch values, date columns, and registered model-scope identifiers during compilation; represent selections as typed values; resolve scope declarations through an explicit framework capability rather than reflection over request text; and share Plan 03's request-time localization and ordered options. The compiled schema may carry only finite selectors, never an executable condition string.
go test ./cabana -run '^TestListSchema(Filter|Scope|RejectsRawCondition)$' -count=1
The command exits non-zero, reports no matching test, a raw condition or arbitrary method is accepted, filter values lose type, localization mutates identifiers, or missing scope capability does not fail activation.
D-12 filters compile only when their type, finite identifier, value shape, and provider capability are valid.
Controller filters are expressive enough for the locked schema and cannot carry executable SQL or arbitrary method names.
Task 3: Execute safe deterministic list queries and D-11 envelopes
cabana/query.go, cabana/query_test.go, cabana/http.go
cabana/list_schema.go, cabana/filter_schema.go, cabana/http.go, lagoon/paginate.go, lagoon/relations.go, lagoon/connection.go, bouncer/guard.go
- Test 1: search, sort, switch, date-range, model-scope, and pagination produce the expected SQL/results using bound values and allowlisted identifiers.
- Test 2: zero and one record produce the exact edge envelopes; equal sort values use primary-key ascending tie-break across adjacent pages.
- Test 3: unknown/case-changed identifiers, malicious identifier/value strings, invalid ranges/pages, and excessive perPage return D-10 validation errors without reaching unsafe SQL.
Create a query planner that resolves requested search/sort/filter/scope tokens exclusively through the compiled schema, then applies bound values to GORM and the existing pagination conventions. Always append the primary key as an ascending tie-break unless already present, cap records per page to compiled choices, and return D-11's exact data/meta shape with allocated arrays and lastPage 1 for zero results. Wire the authenticated list handler in cabana/http.go through this planner after RequiredPermissions middleware.
go test ./cabana -run '^TestListQuery(Contract|Empty|Single|Adjacent|Filters|RejectsInjection)$' -count=1
The command exits non-zero, reports no matching test, an identifier reaches SQL outside the allowlist, values are interpolated, empty metadata differs, equal rows duplicate/gap across adjacent pages, or the handler bypasses RequiredPermissions.
Every ADMIN-02 query dimension works through a finite compiled selector set, bound values, stable ordering, and the exact D-11 response contract.
All controllers can serve deterministic, injection-resistant index responses from their strict list schema.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| YAML/request→query planner | Trusted embedded schema and untrusted query values select database behavior |
| query planner→PostgreSQL | Identifiers and bound values cross into SQL generation |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-09-07 | Tampering / Elevation | `cabana/query.go` identifiers and scopes | high | mitigate | Resolve search/sort/filter/scope names only through compiled finite maps, bind every value, reject case variants/unknowns, and run injection fixtures in Task 3. |
| T-09-08 | Denial of Service | list pagination/search | medium | mitigate | Restrict searchable columns, cap perPage to compiled options, validate ranges, and test excessive/invalid input. |
| T-09-SC | Tampering | npm/pip/cargo installs | high | mitigate | No npm/pip/cargo install occurs; existing Go dependencies only, so the package-legitimacy gate remains closed. |
Run `go test ./cabana -run '^(TestListSchema|TestListQuery)' -count=1`; it fails on non-zero exit, zero matched tests, unstable array/order semantics, unsafe identifiers/values, missing filter coverage, or D-11 envelope drift.
- Every ADMIN-02 column, filter, action, search, sort, and pagination declaration compiles strictly.
- Empty, single, equal, and adjacent-page behavior matches the explicit edge contract.
- Query identifiers are finite and values are bound; T-09-07 fails closed under executable injection tests.
- The authenticated list handler emits exact D-10/D-11 JSON after permission enforcement.