docs(09): create phase plan
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user