--- phase: 12.2-admin-form-fields-date-file-upload-relation-editing-with-def plan: 05 type: execute wave: 5 depends_on: ["12.2-04"] files_modified: - modules/cabana/testdata/deferred/controllers/gadgets/config_form.yaml - modules/cabana/testdata/deferred/controllers/gadgets/config_list.yaml - modules/cabana/testdata/deferred/controllers/gadgets/config_relation.yaml - modules/cabana/testdata/deferred/models/gadget/fields.yaml - modules/cabana/testdata/deferred/models/gadget/columns.yaml - modules/cabana/testdata/deferred/models/part/fields.yaml - modules/cabana/testdata/deferred/models/member/pivot_fields.yaml - modules/cabana/phase122_fixture_test.go - modules/cabana/relation_child_scope_test.go - modules/cabana/protected_file_test.go - modules/cabana/relation_child_test.go - modules/cabana/fileupload_test.go - modules/cabana/deferred_commit_test.go - modules/cabana/datepicker_test.go - modules/lagoon/date_test.go - modules/lagoon/fill_test.go - modules/lagoon/validate_test.go - modules/lagoon/deferred_test.go - modules/lagoon/purge_test.go - modules/lagoon/attach/store_test.go - modules/lagoon/attach/guard_test.go - modules/conga/schedule_test.go - modules/pact/capabilities_test.go - admin/tests/app/sessionKey.test.ts - admin/tests/app/dateFormat.test.ts - admin/tests/form/DatepickerField.test.ts - admin/tests/form/FileuploadField.test.ts - admin/tests/relation/RelationChildModal.test.ts - admin/tests/relation/RelationPivotModal.test.ts - admin/tests/relation/RelationManager.test.ts - admin/tests/list/CellValue.test.ts - admin/tests/fixtures/deferred.files.json - admin/tests/fixtures/deferred.relation-schema.json - scripts/check-phase12.2.sh - .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-SECURITY-REVIEW.md - .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-VALIDATION.md autonomous: false requirements: [SC-1, SC-2, SC-3, SC-4, SC-5] estimate: tokens: 230000 raw_tokens: 230000 tasks: 4 confidence: low must_haves: truths: - "Per D-15 (success criterion 3), a security suite through the assembled router proves that a child, pivot row or child file of another parent answers 404 `not_found` on every child route (records GET and PUT, delete, pivot GET and PUT, and the seven child file routes), that a parent hidden by FormExtendQuery answers 404, and that no such request changes a row." - "Per D-02 and D-15, the suite proves that record id 0 without a valid `X-Session-Key` is 404, a malformed key is 422 on `session_key`, and another admin's key finds none of the first admin's pending files or children (list empty, remove, caption, download and child routes 404, commit applies nothing)." - "Per D-10, the suite proves the protected download and thumb routes answer 404 for another parent's file, another admin's pending file and any `is_public=true` row, serve only jpeg, png, gif and webp inline, serve everything else (SVG and HTML included) as `application/octet-stream` with `Content-Disposition: attachment`, and always send `X-Content-Type-Options: nosniff`, `Cache-Control: private, no-store` and the sandbox CSP; a file list never carries `url` or `thumb_url` for a protected relation." - "Per D-12 and D-14, tests prove each toolbar button gates its routes (403 when undeclared), hasMany delete runs the child's hooks and soft delete and unlink nulls the foreign key, belongsToMany delete removes the pivot row then the related record, pivot input cannot set the pivot foreign keys, id, timestamps or HookPivotColumns, and RelationBeforeLink still stamps its columns." - "Per D-04, tests prove commit happens inside the create and update transactions after FormBefore* and before FormAfter*, a 422 (maxFiles, required fileupload, ineligible deferred link, datepicker bound) rolls back and keeps every binding, a successful save deletes exactly the applied bindings, bindings of undeclared fields or another controller's morph type are ignored, and two concurrent saves with one key apply each binding once." - "Per D-05 and D-22, tests prove `PurgeDeferred` and `deferred:purge --days=N` remove expired bindings, delete created children and unattached files, delete blobs only after commit, keep linked-only records and attached files, skip rows locked by a running save, report unresolvable slave types as skipped, and that the framework schedule entry exists, uses the configured time and disappears with an empty `purge_at`." - "Per D-07 and D-08, tests prove `attach.Store` enforces MaxBytes at the boundary, extensions, MIME patterns and the image guard (a polyglot, an SVG, a truncated image and an image over the pixel ceiling are refused; webp is accepted), sets sort_order to the id and leaves no blob behind on any failure, and that the upload route answers 413 past the request cap and 422 on the field for limit and type failures." - "Per D-18, D-19 and D-20, tests prove lagoon.Date and lagoon.TimeOfDay round-trip through Postgres DATE and TIME, JSON and text, Fill writes strings into every date type and pointer variant without changing earlier conversions, `required` rejects zero dates only, datepicker compile refuses unknown keys, mode and Go-type mismatches and unmapped format tokens, and minDate/maxDate are enforced on the server." - "Per D-11, D-13, D-16, D-17 and D-23, tests prove relation forms compile with the manage/view/top-level fallbacks and `$/` same-plugin paths, refuse relation, relation-manager, widget and partial fields and a cross-plugin path, the zero-Kind contract keeps belongsToMany behaviour, the relation hooks run in order and roll back on error, and child file uploads commit with the child's save under `X-Child-Session-Key`." - "The SPA unit tests cover sessionKey (length, alphabet, uniqueness), dateFormat (every mode, ignoreTimezone, displayFormat tokens, weekStart), DatepickerField, FileuploadField, RelationChildModal, RelationPivotModal, RelationManager toolbar and create-screen deferral, and CellValue date/time, including the four UI-SPEC backstops." - statement: "Calendar popover: Esc returns focus to the trigger and a disabled day cannot be selected" verification: backstop - statement: "Datetime round trip in a fixed non-UTC zone shows the local wall clock and emits the UTC string; ignoreTimezone emits the wall clock unchanged" verification: backstop - statement: "Protected thumbnails are requested with X-Session-Key, rendered from an object URL and the URL is revoked on unmount" verification: backstop - statement: "Keyboard reorder: ArrowUp/ArrowDown on a handle moves the item, keeps focus on its handle, announces fileupload.moved and sends one debounced reorder request" verification: backstop - "Per success criterion 5, the docs checker (`go test ./cmd/summer -run TestDocsTree` and `go run ./cmd/summer docs:build --check`) passes, and `scripts/check-phase12.2.sh` runs the full suite and the named security tests and fails on any missing or skipped named test." - "The v0.1.1 tag is created and pushed only by the user at the final checkpoint; the executor never tags or pushes." artifacts: - path: "modules/cabana/relation_child_scope_test.go" provides: "D-15 security suite" contains: "TestRelationChildScope" - path: "modules/cabana/protected_file_test.go" provides: "D-10 protected file security tests" contains: "nosniff" - path: "modules/cabana/phase122_fixture_test.go" provides: "acme fixture plugin and assembled-router env over testdata/deferred" - path: "scripts/check-phase12.2.sh" provides: "phase gate: full suite, named security tests, drift checks, docs checker, hygiene" contains: "TestRelationChildScope" - path: ".planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-SECURITY-REVIEW.md" provides: "T-12.2 threat to test mapping" key_links: - from: "modules/cabana/phase122_fixture_test.go" to: "modules/cabana/testdata/deferred" via: "the fixture plugin's AdminFS is os.DirFS over testdata/deferred, assembled with surf.Assemble on the cabana Postgres harness" pattern: "testdata/deferred" - from: "scripts/check-phase12.2.sh" to: "modules/cabana/relation_child_scope_test.go" via: "the security stage runs the named tests and refuses a missing or skipped one" pattern: "TestRelationChildScope" prohibitions: - statement: "Test fixtures MUST NOT be registered in production code paths; the fixture plugin lives only in _test.go files and testdata" status: resolved verification: test - statement: "Tests, fixtures, READMEs and docs MUST NOT name a consuming application; neutral acme names only" status: resolved verification: test - statement: "The executor MUST NOT create or push the v0.1.1 tag or push master" status: resolved verification: human - statement: "No production source file is changed in this plan except where a test exposes a defect; such a fix is a separate fix commit named in the summary" status: resolved verification: test --- ## Phase Goal ROADMAP Phase 12.2 goal (verbatim): A plugin's admin forms cover the three gaps a downstream project on SummerCMS v0.1 hit: a date/datetime field, a file upload field, and creating, editing and deleting related records inside the parent form (WinterCMS RelationController parity). Uploads and related-record changes on a record that is not saved yet use Winter-like deferred binding: they are held against a session key and committed with the parent's first save, or discarded with it. This plan's slice: success criterion 5 (unit tests in the phase's last plan, docs checker green) and the evidence for criteria 1-4, led by the D-15 security suite; then the v0.1.1 tag checklist for the user. Bring the Phase 12.2 code (plans 01-04) to full unit, integration and security test coverage following the RESEARCH Validation Architecture test map, add the SPA unit tests and the four UI-SPEC backstops, add a phase gate script, write the security review mapping every T-12.2 threat to a passing test, fill the VALIDATION.md task map, and hand the v0.1.1 tag to the user. Purpose: project rule "unit tests are always the last plan of a phase"; the phase touches authorization scoping and file uploads, so the security review is run. Output: Go and SPA tests, a fixture plugin under modules/cabana/testdata/deferred, scripts/check-phase12.2.sh, 12.2-SECURITY-REVIEW.md, an updated 12.2-VALIDATION.md, and a tagging checklist. Repos: summercms.go (tests, fixtures, gate script, planning docs); fonoteka.go is only exercised by the gate (build, vet, admin tests), never edited. Test code and planning docs in separate commits; a production fix found by a test is its own `fix(12.2-05)` commit. No co-author tags. Neutral acme names. @~/.claude/gsd-core/workflows/execute-plan.md @~/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/STATE.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-CONTEXT.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-RESEARCH.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-VALIDATION.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-UI-SPEC.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-01-SUMMARY.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-02-SUMMARY.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-03-SUMMARY.md @.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-04-SUMMARY.md - Harnesses: `adminGorm(t)` (modules/cabana/auth_test.go: testcontainers Postgres, `lagoon.Migrate(gdb, nil)`, skipped under -short), `newConformEnv`/`conformEnv.send` and the `acme.conform` fixture (modules/cabana/openapi_conformance_test.go), `insertAdmin`, `adminTestSecret`, `adminTestPassword`; lagoon `TestMain` and `dedicatedDB(t, name)` (modules/lagoon/postgres_test.go); attach Postgres helper in modules/lagoon/attach/lifecycle_test.go; conga `schedulePlugins`, `schedulePlugin` (modules/conga/schedule_test.go); SPA `admin/tests/helpers.ts`, `admin/tests/setup.ts`, vitest 3.2.7 with happy-dom and @vue/test-utils 2.4.11. - The smoke tests from plans 01-04 (TestDeferredUploadPurgeTracer, TestStoreSmoke, TestDateSmoke, TestTimeOfDaySmoke, TestFillTextSmoke, TestValidateRequiredZeroDateSmoke, TestFrameworkScheduleSmoke, TestFileuploadSmoke*, TestProtectedFileSmoke, TestDatepickerSmoke*, TestRelationChildSmoke*, admin/tests/smoke/deferred.smoke.test.ts) stay; this plan adds the full tests beside them and may fold a smoke test into its full counterpart when it is strictly covered. - Routes, headers, types and identifiers are those listed in the "Artifacts this phase produces" sections of 12.2-01 to 12.2-04 and in their SUMMARYs (read the SUMMARYs first: names that changed during execution win). - Prior gate scripts to copy the shape from: scripts/check-phase10.sh (stages, named-test refusal, hygiene stage), scripts/check-phase11.1.sh (`--named`). ## Artifacts this phase produces (This plan's share.) - Fixture plugin `acme.deferred` (test-only) over `modules/cabana/testdata/deferred/`: controller `acme.deferred.gadgets` with fileupload fields `photos` (attachMany, public, image) and `manual` (attachOne, protected, file), datepicker fields (date, datetime, time), a deferrable hasMany `parts` relation (nullable `gadget_id`, manage form via a `$/acme/deferred/...` path with a fileupload and a datepicker field, toolbar `create|update|delete|link|unlink`), a belongsToMany `members` relation with `pivot.form` (`pivot[note]`) and a RelationBeforeLink stamp column, FormExtendQuery hiding rows marked hidden, and every relation hook recording calls. - Go tests: `TestRelationChildScope*`, `TestProtectedFile*`, `TestRelationChild*`, `TestFileupload*`, `TestDeferredCommit*`, `TestDatepicker*`, `TestDatepickerBounds*`, `TestDate*`, `TestTimeOfDay*`, `TestFillText*`, `TestValidateRequiredZero*`, `TestDeferredStore*`, `TestDeferredMigrations*`, `TestPurgeDeferred*`, `TestStore*`, `TestIsAllowedImage*`, `TestFrameworkSchedule*`, `TestRelationHookInterfaces*`. - SPA tests: `admin/tests/app/sessionKey.test.ts`, `admin/tests/app/dateFormat.test.ts`, `admin/tests/form/DatepickerField.test.ts`, `admin/tests/form/FileuploadField.test.ts`, `admin/tests/relation/RelationChildModal.test.ts`, `admin/tests/relation/RelationPivotModal.test.ts`, extended `RelationManager.test.ts` and `CellValue.test.ts`; fixtures `deferred.files.json`, `deferred.relation-schema.json`. - `scripts/check-phase12.2.sh` with stages `--go`, `--security`, `--spa`, `--openapi`, `--dist`, `--docs`, `--hygiene`, `--app`, `--all` (default `--all`). - Planning docs: `12.2-SECURITY-REVIEW.md`, task map in `12.2-VALIDATION.md` (`nyquist_compliant: true`, `wave_0_complete: true` once every row is green). Task 1: A child, pivot row or file of another parent, or of another admin's unsaved record, is unreachable through every admin route, proven end to end against a fixture plugin Test-only code and testdata. modules/cabana/testdata/deferred/controllers/gadgets/config_form.yaml, modules/cabana/testdata/deferred/controllers/gadgets/config_list.yaml, modules/cabana/testdata/deferred/controllers/gadgets/config_relation.yaml, modules/cabana/testdata/deferred/models/gadget/fields.yaml, modules/cabana/testdata/deferred/models/gadget/columns.yaml, modules/cabana/testdata/deferred/models/part/fields.yaml, modules/cabana/testdata/deferred/models/member/pivot_fields.yaml, modules/cabana/phase122_fixture_test.go, modules/cabana/relation_child_scope_test.go, modules/cabana/protected_file_test.go .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-02-SUMMARY.md, .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-03-SUMMARY.md, modules/cabana/openapi_conformance_test.go (newConformEnv, conformPlugin, conformFS, send), modules/cabana/auth_test.go (adminGorm, insertAdmin), modules/cabana/relation_child.go (loadChild), modules/cabana/field_file.go (parentFileScope, childFileScope, download headers), modules/cabana/deferred.go, modules/cabana/security_coverage_test.go (phase09Routes), modules/cabana/relation_child_smoke_test.go, modules/cabana/fileupload_smoke_test.go Per D-02, D-10 and D-15 (success criterion 3), with RESEARCH Pattern 3 and the Security Domain table as the checklist. (1) Fixture (Wave 0 gap): write the YAML tree under modules/cabana/testdata/deferred/ described in Artifacts, and modules/cabana/phase122_fixture_test.go with the test-only plugin `acme.deferred` (AdminFS = os.DirFS of testdata/deferred, Models() listing every fixture model, permissions and navigation), its models (gadget implementing attach.Owner and attach.HasRelations, part with `*uint` gadget_id and an attach relation, member, gadget-member pivot with `note` and a hook-stamped `role` column, plus the date/time columns), a controller implementing AdminRelationContractProvider (hasMany parts, belongsToMany members), FormExtendQuery (hides rows whose `hidden` column is true), RelationBeforeLink (stamps role) and the six pact relation hooks recording calls, and an env builder like newConformEnv: Postgres via adminGorm, AutoMigrate of the fixture tables only, a `mem://` bucket published with attach.Publish, two admins (A and B) with the controller permission, surf.Assemble, and helpers to send JSON and multipart requests with a bearer token and optional session headers. (2) modules/cabana/relation_child_scope_test.go, `TestRelationChildScope*` table-driven over every child route from plan 03 (records GET and PUT, delete, pivot GET and PUT, the seven child file routes): with gadgets G1 and G2, a part and a member pivot of G2 requested through G1 answers 404 `not_found` and leaves the database unchanged (row counts and values compared before and after); the same through a hidden G3 (FormExtendQuery) answers 404; record id 0 without X-Session-Key answers 404 and with a malformed key 422 `session_key`; admin B using admin A's key for id 0 sees an empty linked list and gets 404 on A's pending part on GET, PUT, delete, pivot and child-file routes; an undeclared toolbar button answers 403 before any SQL (use a second fixture controller with a reduced toolbar); a pivot PUT naming `gadget_id`, `member_id`, `id`, `created_at` or `role` answers 422 and leaves the pivot unchanged. (3) modules/cabana/protected_file_test.go, `TestProtectedFile*`: G2's protected file through G1's download and thumb routes is 404; admin B cannot download admin A's pending protected file on id 0; a public row on the protected routes is 404; a stored SVG and an HTML file (file mode) download as `application/octet-stream` with `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`, `Cache-Control: private, no-store` and the sandbox CSP; a PNG is served inline as image/png with the same security headers; the file list of the protected `manual` field has no `url` or `thumb_url` keys; parent-route file ids of another gadget answer 404 on caption, remove and reorder. (4) Failing-when-broken check: once the suite is green, change the parent predicate in loadChild (relation_child.go) so it is dropped, run `go test ./modules/cabana -run '^TestRelationChildScope' -count=1` and confirm it fails, then restore the file with `git checkout -- modules/cabana/relation_child.go` and confirm `git diff --quiet -- modules/cabana/relation_child.go`; record the observed failure line in the summary. Commit only test files and testdata. go vet ./... && go test ./modules/cabana -count=1 -v -run '^(TestRelationChildScope.*|TestProtectedFile.*)$' && git diff --quiet -- modules/cabana/relation_child.go modules/cabana/field_file.go Any command exits non-zero; the verbose run prints "no tests to run", "--- FAIL" or "--- SKIP", or lacks a "--- PASS: TestRelationChildScope" line and a "--- PASS: TestProtectedFile" line; git diff reports the mutated production file was not restored. - `grep -c 'func TestRelationChildScope' modules/cabana/relation_child_scope_test.go` prints at least 1 and `grep -c 'func TestProtectedFile' modules/cabana/protected_file_test.go` prints at least 1. - `grep -c 'records/{child}/files' modules/cabana/relation_child_scope_test.go` prints at least 1 (child file routes are in the table) and `grep -c 'nosniff' modules/cabana/protected_file_test.go` prints at least 1. - `grep -rliE 'fonoteka|p[lł]ytarium' modules/cabana/testdata/deferred modules/cabana/phase122_fixture_test.go modules/cabana/relation_child_scope_test.go modules/cabana/protected_file_test.go` prints nothing. - The summary quotes the failing line observed with the parent predicate removed. Success criterion 3's scoping promise is proven through the real router, and the proof fails when the scoping is removed. Task 2: Every Go behaviour of the phase has a test: dates, fill, required, bindings, purge, schedule, upload store and guard, file routes, commit, datepicker and relation child CRUD Test-only code. modules/lagoon/date_test.go, modules/lagoon/fill_test.go, modules/lagoon/validate_test.go, modules/lagoon/deferred_test.go, modules/lagoon/purge_test.go, modules/lagoon/attach/store_test.go, modules/lagoon/attach/guard_test.go, modules/conga/schedule_test.go, modules/pact/capabilities_test.go, modules/cabana/relation_child_test.go, modules/cabana/fileupload_test.go, modules/cabana/deferred_commit_test.go, modules/cabana/datepicker_test.go .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-RESEARCH.md (Validation Architecture: Phase Requirements to Test Map; Pitfalls 1-15), .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-01-SUMMARY.md, 12.2-02-SUMMARY.md, 12.2-03-SUMMARY.md, modules/lagoon/date.go, modules/lagoon/fill.go, modules/lagoon/validate.go, modules/lagoon/deferred.go, modules/lagoon/purge.go, modules/lagoon/commands.go, modules/lagoon/schedule.go, modules/lagoon/attach/store.go, modules/lagoon/attach/guard.go, modules/conga/scheduler.go, modules/cabana/field_file.go, modules/cabana/field_date.go, modules/cabana/deferred.go, modules/cabana/relation.go, modules/cabana/relation_child.go, modules/cabana/relation_form.go, modules/cabana/phase122_fixture_test.go (Task 1) Per D-01 to D-23, one test group per row of the RESEARCH test map, using the Task 1 fixture for every cabana test that needs the router. (1) lagoon (D-01, D-02, D-05, D-19, D-22): `TestDate*`/`TestTimeOfDay*` (constructors, parse errors, JSON/text round trip, Scan from time.Time at UTC midnight and from strings, Value, zero to NULL and back through a real Postgres DATE and TIME column, pointer variants NULL); `TestFillText*` (every date type and pointer variant from strings, RFC 3339 with an offset kept as the instant, garbage is FillTypeError, a table of conversions that worked before the change still produce the same values); `TestValidateRequiredZero*` (zero time.Time, Date, TimeOfDay and non-nil pointers to zero are empty; an int 0, a false bool and a non-date struct keep their old emptiness); `TestDeferredMigrations*` (columns, NOT NULL backend_user_id, six indexes, rollback drops the table, history id summercms.deferred); `TestDeferredStore*` (bind dedupe, bind/unbind cancel returns the cancelled row, admin and master type isolation, refusal of an empty key, zero admin or empty master type, DeferredSlaves bind/unbind subqueries, envelope round trip D-22); `TestPurgeDeferred*` (cut-off boundary, created children deleted through the model with hooks and soft delete, linked-only kept, attached files kept, unattached files and blobs removed only after commit, a rolled-back purge keeps blobs, SKIP LOCKED leaves a row locked by another transaction, unresolvable slave type counted skipped, `deferred:purge --days` parsing and config default, negative days refused). (2) attach (D-07, D-08): `TestStore*` (MaxBytes exactly and plus one, MaxBytes 0, extension case and validation, default lists per mode, MIME patterns with `image/*` and bare extensions per A8, content type from the sniff and from the extension fallback, sort_order equals id, disk name shape 22 hex plus extension, no client path in the key, row failure deletes the blob, Public flag stored); `TestIsAllowedImage*` (jpeg, png, gif, webp accepted; SVG, HTML, a GIF header followed by script bytes, a truncated PNG, empty input and an image over 4096 by 4096 refused). (3) conga and pact: `TestFrameworkSchedule*` (entry first with id summercms.lagoon[0]:deferred:purge, purge_at parsed, empty disables, malformed is an error, nil config unchanged, forged job args for the entry skipped by runScheduled); `TestRelationHookInterfaces*` (each of the six interfaces is satisfied by a test type with the documented signature). (4) cabana (D-04, D-06, D-08 to D-14, D-16, D-17, D-20, D-23): `TestFileupload*` (every compile error: unknown key, datepicker key on fileupload, image-mode fileType, thumbOptions key, missing AttachRelations or Owner, maxFiles on attachOne, maxFilesize above upload_bytes; upload 201 with pending true; 413 past the cap; 422 for size, type, MIME and image guard with the localized message; maxFiles at upload; list order and pending flags; remove of a pending upload deletes row and blob after commit; remove of an attached file is deferred; caption 403 without useCaption and saved at once with it; reorder with a wrong set 422 and the sort_order permutation; attachOne replacement at commit; public list carries url and thumb_url); `TestDeferredCommit*` (order relative to FormBeforeCreate/FormAfterCreate via recording hooks, rollback keeps bindings on maxFiles, required fileupload, ineligible deferred link and datepicker bound failures, success deletes only applied rows, undeclared field and foreign morph type ignored, update-context-only field ignored on create, two concurrent saves with one key apply once); `TestDatepicker*` and `TestDatepickerBounds*` (every compile error from plan 02, displayFormat mapping table, yearRange forms, Go-type mismatch per mode, create/update with date, datetime with offset stored in UTC, time, minDate/maxDate inclusive edges in date and datetime modes, ignoreTimezone date used for bounds, list columns date and time and the Scanner/Valuer relation-detection fix); `TestRelationChild*` (contract validation per kind including the zero-Kind belongsToMany equivalence with today's link/unlink/linked/candidates results, relation form fallbacks and `$/` paths, D-23 refusals, toolbar compile rules, create/show/update/delete for both kinds, hasMany link candidates with NULL key and the live-created exclusion, unlink nulls the key, belongsToMany delete order, pivot link with one id and pivot values, more than one id with pivot 422, pivot GET/PUT whitelist, RelationBeforeLink stamping, hook order and rollback on a hook error, deferred create/link/unlink/delete on id 0, commit of hasMany and belongsToMany binds, child file upload on child 0 committed with the child's create, `deferrable` in the schema and on the FormField, the purge-resolvability boot error). (5) If a test exposes a defect in production code, fix it in a separate `fix(12.2-05)` commit with the test that proves it, and list it in the summary. Then run the whole module suites. go vet ./... && go test ./modules/lagoon ./modules/lagoon/attach ./modules/conga ./modules/pact ./modules/cabana -count=1 -v -run '^(TestDate.*|TestTimeOfDay.*|TestFillText.*|TestValidateRequiredZero.*|TestDeferred.*|TestPurgeDeferred.*|TestStore.*|TestIsAllowedImage.*|TestFrameworkSchedule.*|TestRelationHookInterfaces.*|TestFileupload.*|TestDatepicker.*|TestRelationChild.*)$' && go test ./... -count=1 Any command exits non-zero; any package line of the verbose run prints "no tests to run"; the output contains "--- FAIL" or "--- SKIP"; the verbose output lacks a "--- PASS" line for TestPurgeDeferred, TestStore, TestFrameworkSchedule, TestDeferredCommit, TestDatepickerBounds and TestRelationChild tests; the full `go test ./...` reports a FAIL line. - `grep -c 'func TestPurgeDeferred' modules/lagoon/purge_test.go`, `grep -c 'func TestDeferredCommit' modules/cabana/deferred_commit_test.go`, `grep -c 'func TestDatepickerBounds' modules/cabana/datepicker_test.go` and `grep -c 'func TestIsAllowedImage' modules/lagoon/attach/guard_test.go` each print at least 1. - `grep -c 'SKIP LOCKED\|locked' modules/lagoon/purge_test.go` prints at least 1 and `grep -c 'concurren' modules/cabana/deferred_commit_test.go` prints at least 1. - Every row of the RESEARCH "Phase Requirements to Test Map" names at least one test function that exists (`go test -list` output for the five packages contains each listed prefix). - `go test ./... -count=1` passes with Docker up. Every Go behaviour of success criteria 1-4 has a test that would fail if it regressed. Task 3: The SPA behaviours and UI backstops are unit-tested, one gate script proves the whole phase, and the security review maps every threat to a passing test Tests, a gate script and planning docs. admin/tests/app/sessionKey.test.ts, admin/tests/app/dateFormat.test.ts, admin/tests/form/DatepickerField.test.ts, admin/tests/form/FileuploadField.test.ts, admin/tests/relation/RelationChildModal.test.ts, admin/tests/relation/RelationPivotModal.test.ts, admin/tests/relation/RelationManager.test.ts, admin/tests/list/CellValue.test.ts, admin/tests/fixtures/deferred.files.json, admin/tests/fixtures/deferred.relation-schema.json, scripts/check-phase12.2.sh, .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-SECURITY-REVIEW.md, .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-VALIDATION.md .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-UI-SPEC.md (Component Contracts, UI Considerations backstop rows), .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-04-SUMMARY.md, admin/src/app/sessionKey.ts, admin/src/app/dateFormat.ts, admin/src/api/files.ts, admin/src/components/form/fields/DatepickerField.vue, admin/src/components/form/fields/FileuploadField.vue, admin/src/components/form/fields/FileCaptionModal.vue, admin/src/components/relation/RelationManager.vue, admin/src/components/relation/RelationChildModal.vue, admin/src/components/relation/RelationPivotModal.vue, admin/src/components/list/CellValue.vue, admin/tests/helpers.ts, admin/tests/relation/RelationManager.test.ts, admin/tests/smoke/deferred.smoke.test.ts, scripts/check-phase10.sh, scripts/check-phase11.1.sh, .planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-SECURITY-REVIEW.md (format), .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-VALIDATION.md, every 12.2-0N-PLAN.md threat_model Per D-02, D-09, D-10, D-17, D-18, D-20 and D-21 and success criterion 5. (1) SPA unit tests with the UI-SPEC as the oracle: sessionKey (43 characters, base64url alphabet, crypto source mocked to prove it is used, two calls differ); dateFormat (each mode, ignoreTimezone, null handling, displayFormat tokens, weekStart for pl and en and an explicit firstDay); DatepickerField (backstop 1: Esc returns focus to the trigger and a day outside minDate/maxDate cannot be selected; backstop 2: with the zone fixed to a non-UTC zone the field shows the local wall clock and emits the UTC string, and ignoreTimezone emits the wall clock unchanged; read-only text and the muted em dash; clear button rules; aria-invalid on 422 and on a partly filled segment set; time mode with twelveHour); FileuploadField (dropzone and limits line; client pre-checks for size, type and maxFiles with the too_many line; queued, uploading with progress, failed and retry states; deferred remove marks the form dirty; Unsaved chip only on update forms; backstop 3: a protected thumbnail request carries X-Session-Key, renders from an object URL and the URL is revoked on unmount; backstop 4: ArrowUp/ArrowDown on a handle moves the item, keeps focus on its handle, announces fileupload.moved and sends one request after 400ms; reorder failure restores the order and toasts reorder_failed; caption modal save and 422; read-only mode; load_failed alert); RelationChildModal (create, update and preview titles and submit labels, skeleton, load failure, 422 focus with tab switching, 404 closes with child_gone and reloads, delete confirm, own key and both headers on save, dirty close confirm); RelationPivotModal and the picker pivot step (single select, Back keeps state, link with `{ids:[id], pivot}`, pivot_saved toast, 422 in the dialog); RelationManager (toolbar order and the single primary button, row-click order, pivot button, delete selected with CLDR plurals and kept selection on failure, create-screen rendering only for deferrable fields with the pending note, id 0 and the header, markDirty on create and not on update); CellValue date and time cells. Text assertions use the lang fixtures, never hard-coded copy outside them. (2) scripts/check-phase12.2.sh (executable, `set -euo pipefail`, shape of check-phase10.sh): stages `--go` (`go vet ./... && go test ./... -count=1`), `--security` (runs `TestRelationChildScope`, `TestProtectedFile`, `TestDeferredCommit`, `TestFileupload` and `TestRelationChild` with -v and refuses when any named prefix has no `--- PASS` line or shows `--- SKIP`), `--spa` (`npm --prefix admin run typecheck && npm --prefix admin test`), `--openapi` (`scripts/check-admin-openapi.sh --check`), `--dist` (`scripts/check-admin-dist.sh`), `--docs` (`go test ./cmd/summer -run TestDocsTree -count=1 && go run ./cmd/summer docs:build --check && go test ./modules/phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1`), `--hygiene` (no consuming-application name in modules/cabana, modules/lagoon, modules/conga, modules/pact READMEs, docs/, admin/src or the files this phase added), `--app` (`go -C ../fonoteka.go build ./...`, `vet ./...` and `test ./plugins/golem15/fonoteka -run Admin` plus the parity schema and migrate tests), and `--all` running every stage, printing one PASS or FAIL line per stage and exiting non-zero on the first failure. (3) 12.2-SECURITY-REVIEW.md in the 09-SECURITY-REVIEW.md format: one row per threat T-12.2-01 to T-12.2-36 and every T-12.2-SC row across the five plans with disposition, the mitigation as built (file and function), and the test that proves it (file and test name), each test re-run in this task; accepted threats keep their rationale; note that the review was performed by the executor when no separate security agent is available, as in Phase 08. (4) 12.2-VALIDATION.md: fill the per-task verification map with the real task ids (12.2-01-T1 ...) and test commands, mark rows green, set `nyquist_compliant: true` and `wave_0_complete: true` only when every row is green; keep the manual-only table (calendar keyboard and visual fit, drag reorder and progress feel) for /gsd-verify-work. (5) Commits: SPA tests and the gate script together; the two planning docs in a separate docs commit. npm --prefix admin run typecheck && npm --prefix admin test && go test ./cmd/summer -run TestDocsTree -count=1 && go run ./cmd/summer docs:build --check && bash scripts/check-phase12.2.sh --all Any command exits non-zero; vitest prints "FAIL" or "No test files found"; docs:build --check prints a problem line; check-phase12.2.sh prints a "FAIL" stage line or a "missing named test" message, or does not print a PASS line for every stage. - `test -x scripts/check-phase12.2.sh` succeeds and `grep -c 'TestRelationChildScope' scripts/check-phase12.2.sh` prints at least 1. - `grep -c 'revoke' admin/tests/form/FileuploadField.test.ts` and `grep -c 'Escape' admin/tests/form/DatepickerField.test.ts` each print at least 1. - `grep -c 'T-12.2-19' .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-SECURITY-REVIEW.md` prints at least 1 and the file has a row for every T-12.2 id in the five plans. - `grep -c 'nyquist_compliant: true' .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-VALIDATION.md` prints 1. - `bash scripts/check-phase12.2.sh --all` exits 0 with Docker up. The phase has one command that proves it, the SPA and its backstops are tested, and every threat is tied to a passing test. Task 4: The user tags v0.1.1 after reviewing the gate output and the security review The user reviews the gate output and the security review, runs the manual UI checks, and creates and pushes the v0.1.1 tag; the executor does not tag or push (user instruction for this phase). Already automated: Phase 12.2 is implemented and tested (datepicker and fileupload fields, relation child CRUD with pivot editing, deferred binding with commit and purge, the SPA controls), `bash scripts/check-phase12.2.sh --all` was run and its stage summary is in 12.2-05-SUMMARY.md, and 12.2-SECURITY-REVIEW.md maps every threat to a passing test. No tag was created and nothing was pushed. 1. Read the `check-phase12.2.sh --all` stage summary in 12.2-05-SUMMARY.md (or re-run it): every stage PASS. 2. Read 12.2-SECURITY-REVIEW.md: every high threat is mitigated with a named passing test. 3. Run `/gsd-verify-work 12.2` for the manual UI checks harvested from plan 04 (calendar keyboard and visual fit, drag reorder and upload progress, the child and pivot modal flow) and resolve anything it reports. 4. Note that summercms.go has no tags yet (Phase 11.2 deferred v0.1.0 to the launch step); decide whether v0.1.0 is created first on its intended commit. 5. Create and push the tag yourself: `git tag -a v0.1.1 -m "SummerCMS v0.1.1: datepicker, fileupload, relation child CRUD with deferred binding"` on the phase head, then `git push origin master v0.1.1`. 6. Tell the downstream project that its `TODO: requires SummerCMS change` items for the date field, the file upload field and creating/editing/deleting related records can be resolved on v0.1.1. After "tagged": `git tag --list 'v0.1.1'` prints v0.1.1 and `git rev-parse v0.1.1^{commit}` equals the phase head; `git ls-remote --tags origin v0.1.1` lists the tag. After "defer tag": STATE.md records the pending release step. .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-05-SUMMARY.md (gate output), .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-SECURITY-REVIEW.md, .planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-CONTEXT.md (Specific Ideas: tag v0.1.1) - The executor stops here and reports the checkpoint; `git tag --list 'v0.1.1'` prints nothing when the checkpoint is presented (the executor did not tag). - The user replies with "tagged" (and the tag exists on the phase head) or "defer tag" (recorded in STATE.md as a pending release step). Reply "tagged" after pushing v0.1.1, or "defer tag" to record it as a pending release step. ## Trust Boundaries | Boundary | Description | |----------|-------------| | Test fixtures → production binary | Test-only plugin and testdata must never be compiled into or registered by an application | | Gate script → release | The gate decides whether the user tags a release | | Repository → remote | Tags and pushes publish the framework to downstream projects | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| | T-12.2-34 | Elevation of Privilege | fixture plugin `acme.deferred` | low | mitigate | Defined only in `_test.go` files with testdata under modules/cabana/testdata; never registered through party or a generated plugin list (Task 1). | | T-12.2-35 | Tampering | regression of D-15 scoping or D-10 headers | high | mitigate | Named security tests in the `--security` stage refuse missing or skipped tests; the parent predicate removal is shown to fail the suite (Tasks 1, 3). | | T-12.2-36 | Repudiation | a release tagged without a green gate | medium | mitigate | The executor never tags or pushes; the user tags at a blocking-human checkpoint after reading the gate summary and the security review (Task 4). | | T-12.2-SC | Tampering | dependency installs | low | accept | No Go module or npm package is added in this plan; tests use the already pinned vitest, @vue/test-utils, happy-dom, testify and testcontainers-go. | - `bash scripts/check-phase12.2.sh --all` exits 0 with Docker up (Go suite, named security tests, SPA, OpenAPI drift, dist drift, docs checker, hygiene, fonoteka.go build/vet/admin and parity tests). - `git -C ../fonoteka.go status --porcelain` is empty. - 12.2-SECURITY-REVIEW.md covers every T-12.2 id; 12.2-VALIDATION.md is nyquist-compliant. - No tag exists until the user creates it at Task 4. - Success criterion 5: the phase's code has unit, integration and security tests delivered in this last plan, and the docs checker passes. - The D-15 security suite proves criterion 3's parent scoping through the real router and fails when the scoping is removed. - The user decides and performs the v0.1.1 release. Create `.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-05-SUMMARY.md` when done.