183 lines
13 KiB
Markdown
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>
|