11 KiB
phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, decisions, metrics, actuals, plan_head_before, plan_head_after
| phase | plan | subsystem | tags | status | requires | provides | affects | tech-stack | key-files | decisions | metrics | actuals | plan_head_before | plan_head_after | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-261006-eyj | 01 | admin-extension |
|
complete |
|
|
|
|
|
|
|
|
964145628a |
e723c39 |
Quick 261006-eyj: Widget field payload and data channel Summary
A type: widget admin field can now send its own JSON payload to its controller action (summer-action detail.payload -> body payload -> pact.AdminActionInput.Payload, 64 KiB cap) and receive structured data back (pact.AdminActionResult.Data -> response data, 256 KiB cap, not fill-filtered -> element data attribute plus a summer-result event), with every existing guarantee intact.
What was built
Task 1 (tracer, commit 6af88f9) - server contract end to end
pact.AdminActionInputgainedPayload json.RawMessage;pact.AdminActionResultgainedData any, both documented in the type comments and the pact README.cabana.decodeActionRequestkeeps strict decoding and adds a 64 KiB cap onpayload(422validation_failedonbody, the action never runs).widgetActionassignsinput.Payload = in.Payloadwithout inspection.toolbarActionandrecordActionrefuse any payload (includingnull) with the same 422 they giverecord_id/values.runActionmarshalsDataonce, rejects more than 256 KiB or an encode error with an opaque 500 plusslog.Error("cabana: admin action data rejected", ...), and embeds the raw bytes so the envelope never double-encodes. Fill still passesonlyFillScalars.cabana.AdminActionRequest.Payload(json:"payload,omitempty") andcabana.AdminActionResult.Data(json:"data,omitempty") with swag doc comments; route descriptions updated.- New root
.swaggowithreplace json.RawMessage any. Confirmed: without it swag emittedcabana.AdminActionRequestas a bare object; with it the regeneratedadmin/src/api/schema.d.tshaspayload?: unknownnext torecord_id?: numberandvalues?:, andcabana.AdminActionResulthasdata?: unknown,fill,message. - Fixture
lookupaction (phase101_actions_test.go) decodes a payload and echoes it asDatanext to a deliberately over-wideFill;"big"and"nan"return rejected data. TestWidgetPayloadAndData(6 subtests): byte-exact pass-through and unfiltered data with filtered fill; every JSON value kind plus nil-when-absent; nodatakey when none returned (widget and toolbar); 65536-byte payload passes and 65537 is 422 with no action call; toolbar{"payload":{}}/null/1and recordactivatewith{"payload":1}are 422 and the person stays inactive;"big"/"nan"are opaque 500s with noxxxxin the body.- README/docs: cabana README bullet, route table and API rows; pact README rows;
docs/backend/partials-and-widgets.mdGo-contract paragraph;docs/backend/admin-spa.mdmentions.swaggo.
Task 2 (tdd, commit c6f68e4) - WidgetField.vue
WIDGET_RESULT_EVENT = 'summer-result'exported from formContext.ts.- The listener reads
event.detailand forwards the detail's ownpayloadproperty (Object.hasOwn) orundefined;onActionspreadspayloadinto the body only when defined, so payload-less bodies are byte-identical to before. On success, after the fill patch loop and before the toast, the component setsdatatoJSON.stringify(answer.data)when the response has an owndatakey (otherwise removes the attribute) and dispatchessummer-resultwith{data, fill, message}, skipped when unmounted. Failure path unchanged. - RED observed first: 7 new assertions failed on behaviour while all 22 existing tests passed; GREEN after the implementation: 89/89 across WidgetField, FormView and the extension smoke tests,
vue-tscclean,modules/boardwalk/distrebuilt and matchingscripts/check-admin-dist.sh.
Task 3 (commit e723c39) - fixture widget and element-contract docs
lookup.jsis now a reorder widget: button plus<ol>, in-memory items replaced from the observeddataattribute and from asummer-resultlistener on itself, a click reverses the items and dispatchessummer-actionwithdetail.payload.order; textContent only, no HTTP, no innerHTML.docs/backend/partials-and-widgets.mdgained a "Widget element contract" subsection (attributes, both events, the no-token/no-request rules, textContent guidance, pointer to the fixture). The cabana README bullet names both events.
Verification
Commands run and outcomes (all at the final HEAD unless noted):
| Command | Result |
|---|---|
go vet ./... |
pass (after each task) |
go test ./modules/pact/... |
pass |
go test ./modules/cabana/ -run 'TestWidgetPayloadAndData|TestPhase101Actions|TestPhase10OpenAPIConformance|TestRecordActionSmoke' -count=1 |
pass (Task 1 gate) |
go test ./... -count=1 |
pass, exit 0 (final gate, Docker-backed cabana tests included) |
scripts/check-admin-openapi.sh --check |
pass (after Task 1 and at HEAD) |
go test ./cmd/summer -run TestDocsTree |
pass (after Tasks 1 and 3) |
go run ./cmd/summer docs:build --check |
"no problems found" |
grep -c 'payload?: unknown' admin/src/api/schema.d.ts |
1 |
grep -A12 '"cabana.AdminActionRequest": {' admin/src/api/schema.d.ts | grep -c 'record_id?: number' |
1 (see deviation note on -A4) |
npm --prefix admin test -- tests/form/WidgetField.test.ts tests/smoke/extension.smoke.test.ts tests/form/FormView.test.ts |
89 passed |
npm --prefix admin test (full suite) |
71 files, 1025 tests passed |
npm --prefix admin run typecheck |
pass |
scripts/check-admin-dist.sh |
matches a fresh build |
go test ./modules/boardwalk/... |
pass |
node --check modules/cabana/testdata/extension/assets/js/lookup.js |
pass |
! grep -q innerHTML modules/cabana/testdata/extension/assets/js/lookup.js |
pass |
test -z "$(git status --porcelain -- modules admin docs cmd internal scripts .swaggo)" |
clean |
Deviations from Plan
1. Verify command context width (Task 1). The plan's literal check grep -A4 '"cabana.AdminActionRequest": {' ... | grep -c 'record_id?: number' returns 0 because openapi-typescript now emits the Payload doc comment (four lines) before payload?: unknown, pushing record_id past the 4-line window. The same check with -A12 returns 1 and values?: is present too; the generated file was not hand-edited. The intent of the check (record_id and values survive the .swaggo override) is met.
2. Task 2 TDD cycle committed as one commit. RED (7 failing behaviour tests, 22 existing passing) was observed before implementing, then GREEN, but both landed in the single commit the plan prescribes ("one commit with formContext.ts, WidgetField.vue, WidgetField.test.ts and modules/boardwalk/dist"), keeping the project's green-at-every-commit rule. No separate test(...) commit exists.
3. Fixture length. lookup.js is about 60 lines, slightly over the plan's "roughly 50", to keep the observed-attribute parsing defensive (try/catch on JSON.parse, object filter on items).
None of these change behaviour or scope; the plan was otherwise executed as written.
Threat model outcome
- T-Q261006-01 (payload DoS): mitigated, test "payload cap".
- T-Q261006-02 (data DoS): mitigated, test "data over 256 KiB or unencodable is an opaque 500".
- T-Q261006-03 (payload selecting the record): mitigated, record still loaded through
readScopedRecord/FormExtendQuery; docs tell actions to re-scope ids in the payload;TestPhase101Actionsout-of-scope 404s still pass. - T-Q261006-04 (toolbar/record tampering): mitigated, test "toolbar and record routes refuse a payload".
- T-Q261006-05 (data bypasses fill): accepted and documented in README and docs.
- T-Q261006-06 (XSS via data): mitigated, SPA sets only an attribute string and dispatches an event; fixture and docs use textContent.
- T-Q261006-07, T-Q261006-SC: accepted; no new dependency, payload not logged.
No new threat surface beyond the plan's register.
Known Stubs
None.
Commits
| Hash | Subject |
|---|---|
6af88f9 |
feat(cabana): widget action payload and data channel (quick-261006-eyj) |
c6f68e4 |
feat(admin): widget field payload and summer-result data channel (quick-261006-eyj) |
e723c39 |
docs(cabana): widget element contract and payload fixture (quick-261006-eyj) |
No .planning files and no co-author or "Generated with" trailers in any commit.