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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10.1-runtime-admin-extension-point | 01 | api |
|
|
|
|
|
9b98d8409f |
771d2ccce0 |
|
|
|
|
|
|
25min | 2026-09-28 | complete |
Phase 10.1 Plan 01: Runtime admin extension point (framework Go) Summary
cabana-owned widget, toolbar and partial routes backed by new pact contracts, an exact-allowlist plugin asset route under the admin prefix, and an html/template partial renderer whose output is sanitized with x/net/html into a capped JSON node tree, all typed in the admin OpenAPI document and proven on the acme conformance fixture
Performance
- Duration: 25 min
- Started: 2026-09-28T21:28:17Z
- Completed: 2026-09-28T21:52:58Z
- Tasks: 3
- Files modified: 31 in summercms.go, 2 in fonoteka.go
Accomplishments
- Six pact contracts (
AdminClientAssets,AdminAction,AdminActionInput,AdminActionResult,HasAdminActions,AdminPartialData) that plans 02 and 03 build against. type: widgetfields with boot checks: the tag must start with the plugin's{vendor}-{plugin}-prefix and must not be a reserved name, the action must be registered, fill keys must be writable scalar fields of the same form, and the controller must declare JS.POST .../widgets/{field}loads the record throughFormExtendQuerywithout a row lock and filters fill keys on the server.- Registered toolbar actions share one namespace with widget actions, with
createanddeletereserved.toolbarActionsin the list schema is filtered by permission, andPOST .../toolbar/{action}accepts only{}. - Plugin JS/CSS is read and sha256-hashed at boot and served by exact key with
boardwalk.SetSecurityHeaders,Cross-Origin-Resource-Policy: same-origin,no-cacheand an ETag. List and form schemas carryassetsURLs with?v=. A lookup miss falls through to the SPA, so dist assets still load. type: partialis supported andheaderPartialis added. Templates are parsed at boot and cloned for each request. Output goes through x/net/htmlParseFragmentand a tag, attribute and URL allowlist, with caps of 64 KiB, 2000 nodes and depth 32.GET .../partials/{name}with an optional scoped?id=serves the result.- OpenAPI regenerated with the recursive
cabana.PartialNode. Route inventories were updated in both repositories, and the acme conformance fixture exercises widget, toolbar, both partials and both assets end to end.
Task Commits
summercms.go:
- Task 1: widget action tracer -
f928194(feat) - Task 2: controller assets and toolbar actions -
8b1cb24(feat) - Task 3: header and form partials -
771d2cc(feat)
fonoteka.go (route inventory, separate repository):
dcb64c9test(10.1-01): expect the framework widget action route75ab47ftest(10.1-01): expect the framework toolbar action routebe3fbf4test(10.1-01): expect the framework partial route and new partial error
Files Created/Modified
modules/pact/capabilities.go: the six extension contractsmodules/cabana/extension.go: boot validation of actions, widgets, client assets and partialsmodules/cabana/actions.go: widget and toolbar handlers, strict body decode,readScopedRecord, fill filtermodules/cabana/plugin_assets.go: exact-allowlist asset handler and schema URL buildermodules/cabana/partial_render.go: template parse and render, allowlist walk, caps, view-model guard, partial handlermodules/cabana/form_schema.go,list_schema.go,settings.go: widget, partial,headerPartialand toolbar YAML rulesmodules/cabana/schema_types.go,contracts.go,registry.go,messages.go,http.go,admin_openapi.go: types, registry wiring, label checks, routes, annotationsmodules/boardwalk/boardwalk.go: exportedContentType,SetSecurityHeadersadmin/openapi/admin.json,admin/src/api/schema.d.ts: regeneratedadmin/tests/fixtures/*.json:assetsandtoolbarActionskeys for the typed fixtures- READMEs of pact, cabana and boardwalk
Decisions Made
- An action label containing
::is a phrase key and must resolve at boot. Any other label is literal text. - A toolbar action body must be exactly
{}. Arecord_idorvalueskey is a 422, so a toolbar action can never become a record lookup. - The partial view-model guard also refuses types that contain html/template's trusted content types (
template.HTMLand its siblings). This enforces the plan's "no raw HTML string marked safe" rule at the type level. The allowlist still backs it up for values stored behindany. - Any href or src containing whitespace or control characters is refused. Browsers strip those characters, so
/<tab>/hostwould otherwise become a protocol-relative URL.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Renamed the new scalar helper to avoid a collision
- Found during: Task 1
- Issue:
scalarValuealready exists inquery.go(it converts a filter jsonScalar), so the package did not compile. - Fix: Named the new predicate
isJSONScalar. - Files modified: modules/cabana/actions.go
- Committed in:
f928194
2. [Rule 3 - Blocking] Updated the unsafe-route pins in TestPhase10CSRF and TestPhase10Coverage
- Found during: Tasks 1 and 2
- Issue: Both tests pin the exact set of mounted unsafe routes (9, besides login). The plan's inventory step named only
phase09Routesandphase09ProtectedCalls. - Fix: Added each new POST to the pinned list and raised the count to 10, then 11. The CSRF walk still exercises every new route automatically.
- Files modified: modules/cabana/phase10_csrf_test.go, modules/cabana/phase10_coverage_test.go
- Committed in:
f928194,8b1cb24
3. [Rule 3 - Blocking] Updated the exact-JSON list schema expectations
- Found during: Task 2
- Issue:
TestListSchemaCompile/Empty/Single/Filtercompare wholeListSchemaJSON, andtoolbarActionsandassetsare always emitted. - Fix: Inserted
"toolbarActions":[],"assets":{"scripts":[],"styles":[]}aftertoolbarButtonsin the expected strings. - Files modified: modules/cabana/list_schema_test.go
- Committed in:
8b1cb24
4. [Rule 3 - Blocking] Updated a fonoteka test that pinned the Phase 9 partial rejection text
- Found during: Task 3 (full fonoteka suite)
- Issue:
TestCollectionsAdminRejectsPartialexpected "type partial is not supported", which the plan removes. - Fix: It now expects "type partial needs a path". A bare legacy
type: partialstill fails boot, for the new reason. - Files modified: ../fonoteka.go/plugins/golem15/fonoteka/admin_collections_test.go
- Committed in: be3fbf4 (fonoteka.go)
5. [Rule 2 - Missing critical] Extra URL and view-model hardening
- Found during: Task 3
- Issue: The plan's URL rule (one leading
/) is bypassable with tab or newline characters. Its model-type guard does not covertemplate.HTMLfields. - Fix:
safePartialURLrefuses whitespace and control characters.refusedViewModelrefuses trusted template content types. - Files modified: modules/cabana/partial_render.go
- Committed in:
771d2cc
Total deviations: 5 auto-fixed (4 blocking, 1 missing critical) Impact on plan: All were needed to keep pinned tests green or to close a security gap. No scope creep.
Issues Encountered
go.sumdid not change;go mod tidyonly movedgolang.org/x/netto the direct block.go.sumis listed in the plan's files but needed no edit.- Task 1 and Task 2 ran the plan's targeted fonoteka tests. The full fonoteka suite ran at Task 3 and surfaced deviation 4, which was fixed in the same task.
Known Stubs
None. The fixture actions are test doubles by design, and the Discogs stubs belong to plan 10.1-03.
User Setup Required
None. No external service configuration required.
Next Phase Readiness
- Plan 10.1-02 (SPA) can consume
assets,toolbarActions,headerPartial, the widget field keys, and thePartialNode/PartialView/AdminActionResultschema aliases fromschema.d.ts. - Plan 10.1-03 (Albums) implements
AdminClientAssets,HasAdminActionsandAdminPartialDataon the albums controller. - Plan 10.1-04 owns the named unit tests (TestPhase101FormExtensionSchema, Actions, Toolbar, Assets, PartialSanitizer) and
scripts/check-phase10.1.sh. - The README partial example uses the
summer-statsclass names. The admin SPA only styles them after plan 10.1-02.
Self-Check: PASSED
- FOUND: modules/cabana/extension.go, actions.go, plugin_assets.go, partial_render.go, modules/pact/capabilities.go, admin/openapi/admin.json
- FOUND commits:
f928194,8b1cb24,771d2cc(summercms.go); dcb64c9, 75ab47f, be3fbf4 (fonoteka.go) - Plan verification:
go vet ./... && go test ./...(summercms.go),go vetandgo test ./plugins/golem15/fonoteka/...(fonoteka.go),scripts/check-admin-openapi.sh --check,npm --prefix admin run typecheck,scripts/check-phase10.sh --hygiene: all pass.
Phase: 10.1-runtime-admin-extension-point Completed: 2026-09-28