Files
summercms/.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-05-SUMMARY.md

16 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before plan_head_after tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
12.2-admin-form-fields-date-file-upload-relation-editing-with-def 05 testing
cabana
lagoon
attach
conga
pact
admin-spa
vitest
security
deferred-binding
gate
phase provides
12.2-01 deferred_bindings store, attach.Store and IsAllowedImage, PurgeDeferred and deferred:purge, lagoon.Date/TimeOfDay, the framework schedule entry, the relation child hooks
phase provides
12.2-02 fileupload and datepicker fields, the record file routes, commitDeferred
phase provides
12.2-03 relation child CRUD, pivot forms, the child file routes, deferral on id 0
phase provides
12.2-04 the SPA session key, DatepickerField, FileuploadField, the relation modals
test-only fixture plugin acme.deferred over modules/cabana/testdata/deferred (two controllers, recording hooks, two admins)
D-15 security suite (TestRelationChildScope*) and D-10 protected file suite (TestProtectedFile*), shown to fail with the parent predicate removed
full Go tests for every behaviour of the phase (lagoon, attach, conga, pact, cabana)
SPA unit tests and the four UI-SPEC backstops
scripts/check-phase12.2.sh with nine stages and a detector self-test
12.2-SECURITY-REVIEW.md, the filled 12.2-VALIDATION.md (nyquist_compliant), deferred-items.md
v0.1.1 release (user tags at the checkpoint)
/gsd-verify-work 12.2
tokens tasks commits
81700 3 6
162a8ec5a1 912a466
added patterns
A test fixture plugin over os.DirFS(testdata/...) with dfOverlay for per-test YAML edits and a recorder for Form and Relation hooks
Security tests snapshot every fixture table, the fixture's files and bindings, and the blob count before a refused request and compare after
Gate stages run in their own process from --all so errexit holds inside each stage
created modified
modules/cabana/phase122_fixture_test.go
modules/cabana/testdata/deferred/ (controllers gadgets and locked, models gadget, part, member)
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/purge_test.go
modules/lagoon/attach/guard_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/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/deferred-items.md
modules/lagoon/date_test.go
modules/lagoon/fill_test.go
modules/lagoon/validate_test.go
modules/lagoon/deferred_test.go
modules/lagoon/attach/store_test.go
modules/conga/schedule_test.go
modules/pact/capabilities_test.go
admin/tests/relation/RelationManager.test.ts
admin/tests/list/CellValue.test.ts
admin/tests/fixtures/typed.ts
admin/src/components/form/fields/DatepickerField.vue (fix)
modules/boardwalk/dist (rebuilt for the fix)
.planning/phases/12.2-admin-form-fields-date-file-upload-relation-editing-with-def/12.2-VALIDATION.md
12.2-05: the concurrent first-bind duplicate (no unique index on deferred_bindings) is documented by TestDeferredConcurrentFirstBind, not fixed; a partial unique index needs a user decision and a migration
12.2-05: the header-only image guard accepting a valid GIF header followed by HTML is an accepted residual (served as image/gif with nosniff and sandbox CSP on the protected route); the public static handler's missing nosniff is a deferred hardening item
12.2-05: reorder through another gadget answers the same 422 set mismatch as an id that exists nowhere (no existence oracle), not 404
Each Phase 12.2 security test names its threat in the security review, and the gate's --security stage refuses a missing, renamed or skipped one
SC-1
SC-2
SC-3
SC-4
SC-5
id description requirement verification human_judgment
D1 A child, pivot row or child file of another parent, or of a parent hidden by FormExtendQuery, answers 404 not_found on all 12 child routes and changes nothing; the suite fails when the loadChild parent predicate is removed SC-3
kind ref status
integration modules/cabana/relation_child_scope_test.go#TestRelationChildScope pass
false
id description requirement verification human_judgment
D2 Record id 0 without a key 404, a malformed key 422 session_key; another admin with the key sees nothing and commits nothing; undeclared toolbar buttons 403 before SQL; the pivot PUT whitelist SC-3
kind ref status
integration modules/cabana/relation_child_scope_test.go#TestRelationChildScopeSessionKey|TestRelationChildScopeToolbar|TestRelationChildScopePivotWhitelist pass
false
id description requirement verification human_judgment
D3 Protected download and thumb: foreign, pending-foreign and public rows 404; only jpeg, png, gif and webp inline; nosniff, private no-store and sandbox CSP; no url or thumb_url on protected lists SC-2
kind ref status
integration modules/cabana/protected_file_test.go#TestProtectedFileScope|TestProtectedFileHeaders|TestProtectedFileListHasNoURLs pass
false
id description requirement verification human_judgment
D4 Every Go behaviour of SC-1 to SC-4 (dates, fill, required, bindings, purge, schedule, store and guard, file routes, commit, datepicker, relation child CRUD) has a test SC-4
kind ref status
integration go test ./modules/lagoon ./modules/lagoon/attach ./modules/conga ./modules/pact ./modules/cabana -count=1 pass
false
id description requirement verification human_judgment
D5 SPA unit tests and the four UI-SPEC backstops (Escape focus and disabled days, fixed-zone datetime round trip, protected thumbnails, keyboard reorder) SC-1
kind ref status
unit admin/tests/form/DatepickerField.test.ts|admin/tests/form/FileuploadField.test.ts|admin/tests/relation/*|admin/tests/app/* pass
false
id description requirement verification human_judgment
D6 One gate proves the phase: go, security, spa, openapi, dist, docs, hygiene and app stages, plus a detector self-test SC-5
kind ref status
other bash scripts/check-phase12.2.sh --all pass
false
id description verification human_judgment
D7 The user reviews the gate output and the security review and tags v0.1.1
kind ref status
manual 12.2-05 Task 4 checkpoint pending
true
54min 2026-10-02 awaiting-checkpoint

Phase 12.2 Plan 05: Unit, integration and security tests, phase gate and release checklist Summary

This plan adds an acme.deferred fixture plugin that proves through the real router that no child, pivot row or file of another parent or admin can be reached. That proof fails when the parent scoping is removed. Go and SPA tests now cover every Phase 12.2 behaviour, and a nine-stage gate passes end to end. The user then tags v0.1.1.

Performance

  • Duration: about 54 min
  • Started: 2026-10-02T18:01:19Z
  • Paused at the checkpoint: 2026-10-02T18:55:00Z
  • Tasks: 3 of 4 done. Task 4 is the user's v0.1.1 tag, a blocking-human checkpoint.
  • Files: 51 created or modified in summercms.go. fonoteka.go was not changed.

Accomplishments

  • Fixture plugin acme.deferred (test-only, os.DirFS(testdata/deferred)). It has two controllers:

    • gadgets: datepicker date, datetime and time fields; a public attachMany photos and a protected attachOne manual; a belongsToMany members with manage and pivot forms (RelationBeforeLink stamps role); and a deferrable hasMany parts on a soft-deletable model, whose manage form has an image field, an attachOne field and a datepicker.
    • locked: a reduced toolbar and a required fileupload.

    FormExtendQuery hides rows whose hidden column is true. Every Form and Relation hook records its calls and can be made to fail. Admins A and B, a memblob bucket and dfOverlay give per-test YAML edits.

  • D-15 and D-10 security suites. A child, pivot row or file of another parent is 404 not_found on all 12 child routes and changes nothing (row, file, binding and blob snapshots are compared). The same holds through a FormExtendQuery-hidden parent and through another admin's session key. An owning-parent control runs the same routes with 2xx, which shows each 404 comes from the scope. The protected routes serve only the four image types inline and always send the D-10 headers.

  • Go coverage of every behaviour.

    • Date and TimeOfDay through real DATE and TIME columns. Fill, without changing earlier conversions. required on zero dates.
    • The deferred_bindings shape, history and rollback. Store isolation and the envelope.
    • PurgeDeferred: cut-off, after-commit blobs, rollback, SKIP LOCKED, the nil bucket, --days, and the command end to end.
    • attach.Store limits and key shape. IsAllowedImage polyglots and the pixel ceiling.
    • The framework schedule and forged jobs. The six hook interfaces.
    • In cabana: fileupload and datepicker compile and routes; commit order, rollback, applied-only and concurrent saves; relation contracts, the zero-Kind equivalence, forms, CRUD, deferral and the schema.
  • SPA tests.

    • sessionKey and dateFormat.
    • DatepickerField: backstops 1 and 2.
    • FileuploadField: backstops 3 and 4, plus states, retry, removal, caption and load failure.
    • RelationChildModal and RelationPivotModal.
    • RelationManager: row-action order, the pivot button, plural delete and create-screen deferral.
    • Date and time cells.
  • Gate scripts/check-phase12.2.sh. Its detector refuses a failure, a skip, zero tests, "no tests to run", a build failure or a missing named test, and --self-test proves it on planted inputs. A planted missing security test name was refused with exit 1.

Gate stage summary

FORCE_COLOR= bash scripts/check-phase12.2.sh --all on 2026-10-02 (about 3 min 16 s, Docker up):

PASS self-test
PASS go
PASS security
PASS spa        (Test Files 60 passed, Tests 768 passed)
PASS openapi
PASS dist
PASS docs
PASS hygiene
PASS app        (fonoteka.go build, vet, Admin tests, parity schema and migrate; tree clean)
phase12.2 all stages passed

Failing-when-broken check (Task 1)

The parent predicate in loadChild (modules/cabana/relation_child.go) was dropped by adding a matching case true: ahead of the scoped cases. go test ./modules/cabana -run '^TestRelationChildScope' -count=1 then failed:

relation_child_scope_test.go:97: GET records/{child} through G1: status=200 want 404 body={"data":{"due_on":null,"id":2,"label":"p2"},"meta":{"labels":{}}}
relation_child_scope_test.go:222: B show of A's pending part: status=200 want 404 body={"data":{"due_on":null,"id":1,"label":"pending-..."},"meta":{"labels":{}}}

The file was restored with git checkout -- modules/cabana/relation_child.go, and git diff --quiet confirmed it.

Task Commits

  1. Task 1: D-15 and D-10 suites with the fixture plugin - 9d2b1c1 (test)
  2. Task 2: every Go behaviour of the phase - c8d41a6 (test)
  3. Task 3: SPA tests and the gate - 5b06b20 (fix, see Deviations), d5494fa (test); planning docs 912a466 (docs)
  4. Task 4: v0.1.1 tag: checkpoint, awaiting the user. Nothing was tagged or pushed.

The measured count of 6 commits since plan_head_before includes 9c87a59 docs(13): create phase plan. Another session committed it on master during this run, and it is not part of this plan.

Decisions Made

See key-decisions in the frontmatter.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] A partly filled time-mode datepicker was never marked invalid

  • Found during: Task 3 (DatepickerField tests)
  • Issue: refreshPartial read only [data-reka-date-field-segment]. Reka's TimeField marks its segments data-reka-time-field-segment, so in time mode a half-typed value never got aria-invalid.
  • Fix: It now reads both segment attributes. The test "marks a partly filled time invalid" failed before the fix and passes after it. modules/boardwalk/dist was rebuilt.
  • Files modified: admin/src/components/form/fields/DatepickerField.vue, modules/boardwalk/dist
  • Commit: 5b06b20

Fixture and scope adjustments

  • The fixture tree is larger than listed. The plan named seven YAML files. The tree also has controllers/locked/* (the "second fixture controller with a reduced toolbar"), models/gadget/locked_fields.yaml (a required fileupload) and models/member/fields.yaml (a manage form for belongsToMany create, update and delete). The locked list declares create so the required-fileupload save can run. photos gained maxFilesize: 0.5, so the 413 and the 422 size paths can be tested.
  • The fixture controller registers one widget action (lookup). Without it, a widget field in a relation form fails on its action before the D-23 refusal.
  • Reorder through another gadget answers 422, not 404. Reorder takes the whole id set. A foreign id gives the same 422 set mismatch as an id that exists nowhere. The test asserts that the two answers are identical, so there is no existence oracle.
  • TIME columns in test models use type:time without time zone. GORM reads a bare type:time as its own time type, and AutoMigrate would create timestamptz. Production migrations are hand-written SQL, so they are unaffected. This is logged in deferred-items.md.

Total deviations: 1 auto-fixed bug and 4 fixture or scope adjustments. Impact on plan: The fix is in production SPA code, in its own fix(12.2-05) commit with the test that proves it. Nothing else touches production code.

Issues Encountered

  • Shared database. Other cabana tests leave bindings behind, so the fixture's state snapshot counts only the fixture's own morph types.
  • Shared index. git diff --cached was checked before every commit and each commit used explicit pathspecs. No foreign file was staged, and the other session's phase 13 files were not touched.
  • FORCE_COLOR. It was unset for every Go run.

Known Stubs

None.

Threat Flags

None. The plan adds tests, a gate script and docs. The only production change, the DatepickerField fix, adds no network, auth, file or schema surface.

User Setup Required

None for the tests. Task 4 needs the user (see below).

Checkpoint: v0.1.1 tag (Task 4, blocking-human)

  1. Read the gate stage summary above, or re-run FORCE_COLOR= bash scripts/check-phase12.2.sh --all.
  2. Read 12.2-SECURITY-REVIEW.md. Every high threat has a named passing test.
  3. Run /gsd-verify-work 12.2 for the manual UI checks: calendar keyboard and visual fit, drag reorder and upload progress, and the child and pivot modal flow.
  4. summercms.go has no tags yet, because v0.1.0 was deferred to the launch step in Phase 11.2. Decide whether to create v0.1.0 first on its intended commit.
  5. Tag and push it 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 related-record editing can be resolved on v0.1.1.

Reply "tagged" or "defer tag".

Self-Check: PASSED