Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-04-PLAN.md
2026-09-24 16:45:45 +02:00

183 lines
13 KiB
Markdown

---
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.
<objective>
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.
</objective>
<execution_context>
@/home/jin/.codex/gsd-core/workflows/execute-plan.md
@/home/jin/.codex/gsd-core/templates/summary.md
</execution_context>
<context>
@.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
</context>
## 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
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Compile ordered list columns and action declarations</name>
<reversibility rating="costly">D-06 and D-11 define a generated-client JSON contract; spelling or scalar changes require a coordinated downstream regeneration.</reversibility>
<files>cabana/schema.go, cabana/schema_types.go, cabana/list_schema.go, cabana/list_schema_test.go, cabana/testdata/list/all_columns.yaml</files>
<read_first>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</read_first>
<behavior>
- 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.
</behavior>
<action>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.</action>
<verify>
<automated>go test ./cabana -run '^TestListSchema(Compile|Empty|Single|Ordering|Rejects)$' -count=1</automated>
<fails_when>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.</fails_when>
</verify>
<acceptance_criteria>The compiler represents every D-06/D-11 list key as stable typed JSON and rejects every unsupported declaration before routes serve traffic.</acceptance_criteria>
<done>List columns, actions, defaults, and pagination metadata have deterministic schema semantics.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Compile switch, date-range, and model-scope filters</name>
<files>cabana/list_schema.go, cabana/filter_schema.go, cabana/list_schema_test.go, cabana/testdata/list/all_filters.yaml</files>
<read_first>cabana/list_schema.go, lagoon/relations.go, lagoon/lifecycle.go, pact/capabilities.go, ../fonoteka.go/plugins/golem15/fonoteka/models/album.go</read_first>
<behavior>
- 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.
</behavior>
<action>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.</action>
<verify>
<automated>go test ./cabana -run '^TestListSchema(Filter|Scope|RejectsRawCondition)$' -count=1</automated>
<fails_when>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.</fails_when>
</verify>
<acceptance_criteria>D-12 filters compile only when their type, finite identifier, value shape, and provider capability are valid.</acceptance_criteria>
<done>Controller filters are expressive enough for the locked schema and cannot carry executable SQL or arbitrary method names.</done>
</task>
<task type="auto" tdd="true">
<name>Task 3: Execute safe deterministic list queries and D-11 envelopes</name>
<files>cabana/query.go, cabana/query_test.go, cabana/http.go</files>
<read_first>cabana/list_schema.go, cabana/filter_schema.go, cabana/http.go, lagoon/paginate.go, lagoon/relations.go, lagoon/connection.go, bouncer/guard.go</read_first>
<behavior>
- 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.
</behavior>
<action>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.</action>
<verify>
<automated>go test ./cabana -run '^TestListQuery(Contract|Empty|Single|Adjacent|Filters|RejectsInjection)$' -count=1</automated>
<fails_when>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.</fails_when>
</verify>
<acceptance_criteria>Every ADMIN-02 query dimension works through a finite compiled selector set, bound values, stable ordering, and the exact D-11 response contract.</acceptance_criteria>
<done>All controllers can serve deterministic, injection-resistant index responses from their strict list schema.</done>
</task>
</tasks>
<threat_model>
## 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. |
</threat_model>
<verification>
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.
</verification>
<success_criteria>
- 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.
</success_criteria>
<output>
Create `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-04-SUMMARY.md` when done.
</output>