Four plans: framework Go contracts and routes, framework SPA hosts,
Albums proof in fonoteka.go, and unit tests with the phase gate.
Adds ADMIN-07 to REQUIREMENTS.md and fills the Phase 10.1 roadmap goal,
success criteria and plan list.
Per CLAUDE.md rule 3, every Phase 10.1 Go change has named tests covering success and failure branches: TestPhase101FormExtensionSchema, TestPhase101PartialSchema, TestPhase101Toolbar, TestPhase101PartialSanitizer, TestPhase101Assets and TestPhase101Actions in cabana, TestPhase101BoardwalkExports in boardwalk, and TestPhase101AlbumsExtension in fonoteka.go; both repositories pass go vet ./... and their test suites.
TestPhase101AlbumsExtension proves on real PostgreSQL through the assembled router at /plytadmin: the stats strip counts only the admin's collection (two collections), shows All albums 0 with no format items for an empty collection and No shelf only when above zero; the Discogs widget fills year 1977 and format LP and a save persists them; discogsSync toasts; a Genres-only admin gets 403 on the widget, toolbar and partial routes; the declared assets are served with JavaScript and CSS types; every rendered label resolves in pl and en; every route template it calls is in admin/openapi/admin.json.
Every new SPA module and changed component has a Vitest suite (pluginAssets, WidgetField, PartialField, PartialHost with partialNodes, ListToolbar, ListView, registry, formState, FormField, FormView) asserting the UI-SPEC states and accessibility attributes with neutral acme fixtures, and npm --prefix admin test passes offline.
Assumption-delta invariant: TestPhase101Toolbar proves every toolbar.buttons name resolves to exactly one built-in or registered action, and a registered action named create or delete fails boot.
scripts/check-phase10.1.sh --all exits non-zero on a failing, skipped or zero-test go run, a named test that did not pass, OpenAPI or dist drift, a hygiene violation (Phase 10 rules plus HTML-string parsers in admin/src, network, cookie or storage access in application plugin asset JS, and script or event-handler markup in application partial templates) or an evidence gap; --self-test proves each detector and hygiene rule fails closed.
10.1-SECURITY-REVIEW.md lists T-10.1-01 to T-10.1-22 and T-10.1-SC with severity, disposition, production mitigation, the exact failing-when-broken test or gate stage and the observed result; every high threat records a removal check; 10.1-VALIDATION.md maps every 10.1 plan task to its command with nyquist_compliant true only after the gate passes; scripts/check-phase10.sh --all still passes.
path
provides
scripts/check-phase10.1.sh
Fail-closed Phase 10.1 gate with self-test, go, security, postgres, spa, openapi, dist, hygiene, evidence and all stages
phase101_detect refusing fail, skip, zero-test, non-JSON and missing required tests
phase101_detect
from
to
via
pattern
scripts/check-phase10.1.sh
scripts/check-phase10.sh --hygiene
--hygiene stage reuses the Phase 10 rules before the 10.1 rules
check-phase10.sh --hygiene
from
to
via
pattern
10.1-VALIDATION.md
10.1-01..10.1-04 task verify commands
per-task verification map
10.1-0
Phase acceptance must not rest on skipped PostgreSQL tests, zero-test runs, or a hand-edited dist or schema.d.ts.
No test or fixture inside summercms.go uses application names; application names appear only in fonoteka.go tests.
No high threat is marked mitigated without a named test or gate stage that fails when the mitigation is removed.
No coverage-provider or other package is added.
Phase Goal
A plugin extends the compiled admin SPA without a Node rebuild: controller JS/CSS served same-origin from embedded files, type: widget custom elements whose actions the SPA posts, type: partial and list headerPartial rendered server-side without a raw-HTML sink, and registered toolbar actions (ADMIN-07).
Close Phase 10.1 with full unit test coverage for its Go and SPA code, an assembled Albums acceptance test, one fail-closed gate script, and the security review and validation evidence.
Purpose: CLAUDE.md rule 3 (unit tests are the last plan of a phase); Plans 10.1-01 to 10.1-03 carried smoke tests only. The gate makes phase acceptance a single command.
Output: cabana, boardwalk and fonoteka Go tests with an acme testdata tree; Vitest suites; scripts/check-phase10.1.sh; 10.1-SECURITY-REVIEW.md; finalized 10.1-VALIDATION.md.
Repos: Task 1 summercms.go and fonoteka.go (the Albums test is committed in fonoteka.go); Task 2 summercms.go; Task 3 summercms.go (script) plus planning docs in a separate docs commit. Never add co-author tags.
@.planning/PROJECT.md
@.planning/STATE.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-CONTEXT.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-RESEARCH.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-UI-SPEC.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-VALIDATION.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-01-SUMMARY.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-02-SUMMARY.md
@.planning/phases/10.1-runtime-admin-extension-point/10.1-03-SUMMARY.md
@.planning/phases/10-admin-vue-spa/10-SECURITY-REVIEW.md
@scripts/check-phase10.sh
Gate pattern to reuse: scripts/check-phase10.sh (`phase10_detect` parses `go test -json`: exit 1 fail, 2 skip, 3 zero tests, 4 non-JSON, 5 a required test did not pass, 6 an allow-listed failure now passes; `phase10_go DIR PKGS...`; `phase10_tests DIR PKG TEST...`; `hygiene_checks TREE APPTREE`; `--self-test` scratch-copy plants; `KNOWN_APP_FAILURES` allow-lists exactly the two fonoteka parity failures TestMigrateSeedsCanonicalGenres and TestSchemaMatchesPHPSnapshot). check-phase10.sh ends with a case dispatch and cannot be sourced; copy the detector as `phase101_detect`.
Test harnesses: cabana `adminGorm(t)` and `newConformEnv(t)` (openapi_conformance_test.go, testcontainers PostgreSQL), `phase09DeniedService()`, `handlerRouter`, `csrfRequest` (phase10_csrf_test.go); fonoteka `bootDB`, `assembleTracer`, `phase10CookieAdmin`, `albumsFrontend`, `phase10Call`, `phase10GetJSON`, `phase10OpenAPIPaths` (admin_phase10_e2e_test.go); Vitest `tests/helpers.ts` (`mountApp`, `requestsTo`, `queryOf`, API `/admin-test/api/v1`), typed fixtures in `tests/fixtures/typed.ts`.
Threat IDs in this phase: T-10.1-01..09, 11, 12 (10.1-01); T-10.1-13, 14, 16, 17, 18 (10.1-02); T-10.1-10, 15, 21, 22 (10.1-03); T-10.1-19, 20 (this plan); T-10.1-SC (all plans).
Planning notes
Spec-less probe fallback skipped: no requirement IDs were mapped for Phase 10.1 before this planning run; ADMIN-07 is introduced by it. Truths come from CONTEXT D-01..D-17 and the UI-SPEC.
10.1-VALIDATION.md seeded tests/views/ListView.test.ts; the real suite is admin/tests/list/ListView.test.ts. Task 3 corrects the row.
Fixture tree modules/cabana/testdata/extension/ (controllers/gadgets config and _stats.htm, _summary.htm; models/gadget fields and columns; assets/js/lookup.js, assets/css/gadgets.css)
Task 1: Cover every Phase 10.1 Go change and prove the Albums surfaces end to end
modules/cabana/testdata/extension/**, modules/cabana/phase101_schema_test.go, modules/cabana/phase101_render_test.go, modules/cabana/phase101_assets_test.go, modules/cabana/phase101_actions_test.go, modules/boardwalk/boardwalk_test.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_albums_test.go
modules/pact/capabilities.go, modules/cabana/extension.go, modules/cabana/actions.go, modules/cabana/plugin_assets.go, modules/cabana/partial_render.go, modules/cabana/form_schema.go, modules/cabana/list_schema.go, modules/cabana/settings.go, modules/cabana/messages.go, modules/cabana/http.go, modules/cabana/openapi_conformance_test.go (conform fixture, newConformEnv), modules/cabana/form_schema_test.go (boot-error table idiom), modules/cabana/phase10_csrf_test.go, modules/boardwalk/boardwalk.go, modules/boardwalk/boardwalk_test.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/albums_admin_controller.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_smoke_test.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_e2e_test.go (phase10Acceptance, phase10OpenAPIPaths)
- TestPhase101FormExtensionSchema: a valid widget compiles with Widget, Action, Fill and ActionLabel; boot fails, naming plugin, controller and file, for a widget key on a text field, type widget without widget or action, a tag without a hyphen, with uppercase, with another plugin's prefix, a reserved name (plugin ID `font.face` with tag `font-face-src`), an unregistered action, a registered action named create or delete, a nil Run, a duplicate action, a fill key that is not a field, is protected (collection_id), is a relation field or repeats, a widget on a controller without AdminJS, an unknown key, and a widget or partial in a settings form; an unknown action permission fails compileContributions; an action label phrase key that does not resolve fails validateMessageKeys while a literal label passes.
- TestPhase101PartialSchema: headerPartial and a form partial compile; boot fails for a non-identifier headerPartial, a missing `_name.htm`, a template parse error, a controller without AdminPartialData, a partial without path, `$/…`, `~/…` and `a/b` paths (with the partial-name hint), and `path` on another type.
- TestPhase101Toolbar: a registered name compiles in declared order; an unknown name fails with "unsupported action"; create and delete keep Phase 10 behaviour (delete needs showCheckboxes; create dropped without a form while custom names stay); a toolbar action without a label fails; toolbarActions labels localize per request and are filtered to actions the principal may run; invariant: every toolbar name resolves to exactly one built-in or registered action.
- TestPhase101PartialSanitizer: each dropped-with-subtree tag (script, style, template, iframe, object, embed, noscript, textarea, title, xmp, svg, math, form, input, button, select, link, meta, base) is gone with its children; unknown elements are unwrapped with children kept; on*, style and id attributes are dropped; class, title, lang, dir, role, aria-* and data-* are kept; href "/x" and "#top" are kept, "//x", "/\x", "javascript:…" and "https://x" are dropped; img src "/a.png" is kept, "data:…" dropped; td colspan, time datetime and meter attributes are kept; a view-model string holding markup renders as a text node; `trans` resolves en and pl on two requests against the same compiled partial (proving the per-request Clone and a never-executed pristine template); output over 64 KiB, over 2000 nodes and deeper than 32 each return an error; comments are dropped; a view model of the model type, a pointer to it or a slice of it is refused.
- TestPhase101Assets: an exact hit serves JS and CSS with `text/javascript; charset=utf-8` / `text/css; charset=utf-8`, nosniff, the CSP containing `script-src 'self'`, CORP same-origin, `Cache-Control: no-cache` and an ETag; If-None-Match answers 304; HEAD has no body; an undeclared fixture file (YAML, `_stats.htm`) and an encoded traversal fall through to the SPA handler and are not served; a dist `assets/index-*.js` still comes from the SPA handler; boot fails for a path outside `assets/`, a `..` segment, a `.txt` file, a stylesheet in AdminJS, a missing file, a duplicate path and a three-segment plugin ID; schema URLs carry `?v=` plus 12 hex characters; two controllers of one plugin declaring the same file share one entry.
- TestPhase101Actions (PostgreSQL): the widget POST with an in-scope record returns only fill keys and the action receives only fill-key scalar Values; an out-of-scope record_id is 404; no record_id passes a nil Record; an unknown body key, trailing JSON and a negative record_id are 422; a non-widget or unknown field is 404; missing action permission with the controller permission is 403; an action ValidationError is 422 with its details; a plain action error is 500 without its text in the body; cookie-only POSTs without X-Requested-With are 403 on both action routes; a toolbar name not in toolbar.buttons is 404 and a toolbar body with record_id or values is 422; an undeclared partial is 404, `?id=` on a header partial is 404, `?id=abc` is 404, an out-of-scope id is 404, a PartialData error is 500.
- TestPhase101BoardwalkExports: ContentType for .js, .mjs, .css, .woff2 and an unknown extension; SetSecurityHeaders sets nosniff, the CSP, X-Frame-Options DENY, Referrer-Policy and X-Robots-Tag.
- TestPhase101AlbumsExtension: as stated in must_haves.
Write the tests in ``. Build `modules/cabana/testdata/extension/` as an acme fixture tree (controllers/gadgets config_list.yaml with headerPartial and a registered toolbar action, config_form.yaml, `_stats.htm`, `_summary.htm`; models/gadget fields.yaml with a widget and a partial, columns.yaml; `assets/js/lookup.js` and `assets/css/gadgets.css`) loaded with os.DirFS for the schema, sanitizer and asset tests, and use table cases with fstest.MapFS variants for the boot-error rows (the form_schema_test.go idiom). Database cases reuse the testcontainers helpers (adminGorm or newConformEnv) and never an in-memory substitute. The fonoteka test seeds two collections (albumsFrontend for the admin's email plus a second user's collection), a developer-permission admin and a Genres-only admin through phase10CookieAdmin, calls every route through the assembled router at /plytadmin with the cookie and X-Requested-With, records each called path template, and finally checks them against phase10OpenAPIPaths. For each high threat in 10.1-01 to 10.1-03, confirm the named test fails when the mitigation is removed (temporarily edit the production code, run, restore) and note the result for Task 3. Neutral names only in summercms.go (acme, gadgets, lookup).
go vet ./... && go test ./modules/cabana ./modules/boardwalk -run '^(TestPhase101FormExtensionSchema|TestPhase101PartialSchema|TestPhase101Toolbar|TestPhase101PartialSanitizer|TestPhase101Assets|TestPhase101Actions|TestPhase101BoardwalkExports)$' -count=1 -v && go test ./... -count=1 && (cd ../fonoteka.go && go vet ./... ./plugins/golem15/fonoteka/... && go test ./plugins/golem15/fonoteka -run '^(TestPhase101AlbumsExtension|TestPhase101AlbumsSmoke)$' -count=1 -v && go test ./plugins/golem15/fonoteka/... -count=1)
Any command exits non-zero; the verbose runs lack a "--- PASS" line for any of TestPhase101FormExtensionSchema, TestPhase101PartialSchema, TestPhase101Toolbar, TestPhase101PartialSanitizer, TestPhase101Assets, TestPhase101Actions, TestPhase101BoardwalkExports, TestPhase101AlbumsExtension or TestPhase101AlbumsSmoke, or print "no tests to run" or "--- SKIP".
- Each behaviour above is a named, passing test; TestPhase101Actions and TestPhase101AlbumsExtension run against real PostgreSQL.
- `test -f modules/cabana/testdata/extension/assets/js/lookup.js && test -f modules/cabana/testdata/extension/controllers/gadgets/_stats.htm` succeeds.
- `scripts/check-phase10.sh --hygiene` exits 0 (no application names in modules/cabana tests or testdata).
- Both repositories pass `go vet ./...`; summercms.go passes `go test ./...` and fonoteka.go passes `go test ./plugins/golem15/fonoteka/...`.
Every Go path added in Phase 10.1 has branch-level tests, and one assembled test proves the three Albums surfaces, their scoping and permissions on PostgreSQL.
Task 2: Bring every new SPA module and changed component under Vitest
admin/tests/fixtures/extension.form-schema.json, admin/tests/fixtures/extension.list-schema.json, admin/tests/fixtures/extension.partial.json, admin/tests/fixtures/typed.ts, admin/tests/app/pluginAssets.test.ts, admin/tests/form/WidgetField.test.ts, admin/tests/form/PartialField.test.ts, admin/tests/list/PartialHost.test.ts, admin/tests/list/ListToolbar.test.ts, admin/tests/list/ListView.test.ts, admin/tests/form/registry.test.ts, admin/tests/form/formState.test.ts, admin/tests/form/FormField.test.ts, admin/tests/form/FormView.test.ts
admin/src/app/pluginAssets.ts, admin/src/components/form/formContext.ts, admin/src/components/form/fields/WidgetField.vue, admin/src/components/form/fields/PartialField.vue, admin/src/components/partial/PartialHost.vue, admin/src/components/partial/partialNodes.ts, admin/src/components/form/registry.ts, admin/src/components/form/FormField.vue, admin/src/components/list/ListToolbar.vue, admin/src/views/ListView.vue, admin/src/views/FormView.vue, admin/tests/helpers.ts, admin/tests/fixtures/typed.ts, admin/tests/smoke/extension.smoke.test.ts, admin/tests/list/ListToolbar.test.ts, .planning/phases/10.1-runtime-admin-extension-point/10.1-UI-SPEC.md (S1-S6, UI Considerations)
- pluginAssets: URLs outside `${runtime.base}/assets/` (other origin, protocol-relative, another prefix) are refused; the same URL twice appends one script and returns one promise; an error rejects, removes the entry and a retry appends a new element; loadStyles tags links with the controller; activateStyles disables other controllers' links and enables its own; loadControllerAssets resolves after the scripts load.
- WidgetField: skeleton and aria-busy while loading; a 5000 ms timeout (fake timers) and a script error each show the widget_failed box with role=alert; attributes record-id, field-name, locale, fill-values, label and busy-label are set and no property or function is assigned to the element; fill-values follows form value changes; summer-action POSTs {record_id, values}; a repeat event while busy sends nothing; success patches only fill keys present in the response and toasts; failure toasts danger with the server message or action_failed and sets state="error"; on create record-id is ""; unmount removes the listener.
- PartialHost with partialNodes: an exhaustive allowlist table (allowed and dropped tags and attributes, href and src rules, depth cap), text stays text; loading skeleton sizes for header (80px) and field (44px); empty renders nothing; error shows partial_failed; a reloadKey change keeps the previous nodes with aria-busy; the id query is sent only with recordId.
- PartialField: fetch without id on create and with id on update; label span and role=group with a label; no label row without one.
- ListToolbar: custom names render after delete in declared order as outline buttons, enabled without a selection, disabled with aria-busy while busy, and emit action; names missing from actions do not render.
- ListView: the header partial slot appears only with headerPartial; it refetches after bulk delete and after a custom action but not after search, sort, filter or page changes; a custom action POSTs `{}`, toasts and reloads; a failure toasts danger; assets load when the schema arrives.
- registry and formState: widget and partial are registered, not in isRegistered, not needsRecord, groupLabelled; editablePayload omits them.
- FormField and FormView: widget and partial rows use the span label; FormView provides values, patch and locale, patch marks the form dirty and clears the field's errors, widgets and partials render on create, assets load when the schema arrives.
Write the Vitest suites listed in `` with @vue/test-utils and happy-dom, mocking HTTP through the fetch mock in tests/helpers.ts (never a real network) and defining test custom elements where a widget must upgrade. Keep fixtures neutral (acme.demo.*) and typed through typed.ts against the generated schemas so drift fails typecheck. Assert behaviour and the accessibility attributes named in ``, not markup snapshots. Keep the smoke tests. Add no package (no coverage provider).
npm --prefix admin run typecheck && npm --prefix admin test -- tests/app tests/form tests/list tests/smoke && npm --prefix admin test
Non-zero exit; vitest prints "No test files found", any "FAIL" line or "Unhandled Rejection"; vue-tsc reports an error.
- Every `.ts` and `.vue` file added or changed under admin/src in Phase 10.1 is imported by a suite in admin/tests/app, admin/tests/form or admin/tests/list (not only by the smoke test).
- `grep -c 'whenDefined\|5000' admin/tests/form/WidgetField.test.ts` prints at least 1 and `grep -c 'javascript:' admin/tests/list/PartialHost.test.ts` prints at least 1.
- `git diff --quiet b2845e016b -- admin/package.json admin/package-lock.json` succeeds (no package change since Phase 10.1 planning).
The SPA side of the extension point has behaviour and accessibility tests that run offline in seconds.
Task 3: One fail-closed Phase 10.1 gate plus threat and validation evidence
scripts/check-phase10.1.sh, .planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md, .planning/phases/10.1-runtime-admin-extension-point/10.1-VALIDATION.md
scripts/check-phase10.sh, scripts/check-phase10.2.sh, scripts/check-admin-openapi.sh, scripts/check-admin-dist.sh, .planning/phases/10.1-runtime-admin-extension-point/10.1-VALIDATION.md, .planning/phases/10-admin-vue-spa/10-SECURITY-REVIEW.md (format and removal-check table), every 10.1-0N-PLAN.md threat_model and verify block, every 10.1-0N-SUMMARY.md
(1) `scripts/check-phase10.1.sh` (bash, `set -euo pipefail`, committed executable), modelled on scripts/check-phase10.sh: ROOT, APP (../fonoteka.go), PHASE_DIR, REVIEW, VALIDATION, APP_PLUGINS and the same KNOWN_APP_FAILURES allow-list; `phase101_detect` copied from phase10_detect with PHASE101_ALLOW and PHASE101_REQUIRE; `phase101_go` and `phase101_tests`. Stages:
- `--self-test`: `bash -n`; the detector's synthetic pass, fail, skip, zero, non-JSON, build-fail, package-fail, required-missing, allowed, allowed-other, wrong-package and now-passes cases; every mode flag present in the case dispatch; hygiene_101 passes on a clean scratch copy and refuses, each for its own named reason, three plants: an HTML-string parser call in a scratch admin/src module, a network call in a scratch plugin asset JS file, and a script element in a scratch partial template.
- `--go`: go vet and go test ./... in summercms.go; go vet and go test ./... plus APP_PLUGINS in fonoteka.go with the allow-list.
- `--security`: cabana TestPhase10CSRF, TestPhase09PermissionMatrix, TestPhase101Assets, TestPhase101PartialSanitizer, TestPhase101Actions, TestPhase101FormExtensionSchema, TestPhase101Toolbar; boardwalk package; fonoteka TestPhase09SecurityRoutes.
- `--postgres`: cabana TestPhase10OpenAPIConformance and TestPhase101Actions; fonoteka TestPhase101AlbumsExtension, TestPhase101AlbumsSmoke, TestPhase10Controllers, TestPhase10ControllerCopy, TestAlbumsAdminForm, TestAlbumsAdminList, TestPhase10AssembledAcceptance (a skip or zero match fails).
- `--spa`: `npm --prefix admin ci --no-audit --no-fund`, typecheck, `npm --prefix admin test` refusing "No test files found", "FAIL " and unhandled errors.
- `--openapi`: `scripts/check-admin-openapi.sh --check` plus cabana TestPhase10OpenAPIConformance and TestPhase09ContractInventory.
- `--dist`: `scripts/check-admin-dist.sh`.
- `--hygiene`: `scripts/check-phase10.sh --hygiene`, then `hygiene_101 "$ROOT" "$APP"`, which refuses: `setHTML`, `setHTMLUnsafe`, `createContextualFragment`, `DOMParser`, `srcdoc` or `document.write` anywhere in admin/src; `fetch(`, `XMLHttpRequest`, `document.cookie`, `localStorage`, `sessionStorage` or `indexedDB` in any `plugins/*/*/assets/**/*.js` of the application; `<script`, a `style=` attribute or an inline `on…=` handler in any application `controllers/**/_*.htm` partial template.
- `--evidence`: the review exists and has a table row for every T-10.1-01 to T-10.1-22 and T-10.1-SC, and a high mitigated row names a Test*, check-phase*.sh stage or tests/ path; the validation has `nyquist_compliant: true`, no table row still carrying the seeded pending status, and names ADMIN-07; then runs --security, --postgres and --openapi.
- `--all`: every stage in order, then prints "phase10.1 all passed".
(2) 10.1-SECURITY-REVIEW.md (frontmatter phase "10.1", reviewed date, threats_open, gate scripts/check-phase10.1.sh --all): a fresh code-and-test review of every threat in the four plans' registers (T-10.1-01 to T-10.1-22, T-10.1-SC) with category, component, severity, disposition, the production mitigation with file references, the exact test or gate stage, the observed result and residual risk; a removal-check table for each high threat (the mutation made, the command run, the observed failure) from Task 1's checks; accepted threats keep their rationale verbatim from the originating plan. Note that T-10-16's residual now reads "plugin HTML reaches the DOM only as a sanitized node tree" and that T-10-04 needed no framing carve-out.
(3) 10.1-VALIDATION.md: replace the TBD rows with the actual plan and task IDs and commands from the executed plans (correcting tests/views/ListView.test.ts to tests/list/ListView.test.ts), record each row's status from the final gate, tick the Wave 0 items, keep the manual-only row pointing at the human-check blocks of 10.1-02 Task 3 and 10.1-03 Task 3, and set nyquist_compliant: true, wave_0_complete: true and status: validated only after scripts/check-phase10.1.sh --all passes.
(4) Run scripts/check-phase10.1.sh --all, then scripts/check-phase10.sh --all to prove Phase 10's gate is still green. Commit the script with the code and the two documents in a separate docs commit.
scripts/check-phase10.1.sh --self-test && scripts/check-phase10.1.sh --all && scripts/check-phase10.sh --all
<fails_when>Non-zero exit; any output line starting with "refuse:"; the final lines "phase10.1 all passed" or "phase10 all passed" are absent.</fails_when>
<acceptance_criteria>
- scripts/check-phase10.1.sh --all prints "phase10.1 all passed" and scripts/check-phase10.sh --all prints "phase10 all passed".
- test -x scripts/check-phase10.1.sh succeeds.
- grep -cE '^\| T-10\.1-(0[1-9]|1[0-9]|2[0-2]|SC) ' .planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md prints 23.
- grep -c '^nyquist_compliant: true$' .planning/phases/10.1-runtime-admin-extension-point/10.1-VALIDATION.md prints 1, grep -E '^\|' .planning/phases/10.1-runtime-admin-extension-point/10.1-VALIDATION.md | grep -ci 'pending' prints 0 (table rows only; the status legend line is not a row), and grep -c 'ADMIN-07' .planning/phases/10.1-runtime-admin-extension-point/10.1-VALIDATION.md prints at least 1.
</acceptance_criteria>
Phase 10.1 has one command that proves everything, Phase 10's gate still passes, and the threat and validation evidence is auditable.
Multi-Source Coverage Audit (phase 10.1)
Source
Item
Coverage
Plan evidence
GOAL
Plugins extend the compiled SPA without a Node rebuild (assets, widgets, partials, toolbar actions), proven on a nameless fixture and on three Albums surfaces
Deferred ideas (Discogs client, disk override, WASM, Ctrl+K, badge column, Playwright, user/media navigation, admin personal tokens)
EXCLUDED
No task implements a deferred item
<threat_model>
Trust Boundaries
Boundary
Description
Test and gate results → phase acceptance
Gate completeness decides whether the extension point can be declared done
Framework repo → application repo
summercms.go must stay free of application knowledge; application assets and templates must follow the plugin JS and partial conventions
STRIDE Threat Register
Threat ID
Category
Component
Severity
Disposition
Mitigation Plan
T-10.1-19
Repudiation
Phase 10.1 acceptance evidence
high
mitigate
check-phase10.1.sh refuses failed, skipped, zero-test, non-JSON and build-failed runs and missing required tests, OpenAPI and dist drift, hygiene and evidence gaps; --self-test proves each detector; the review names a failing-when-broken test and a removal check per high threat.
T-10.1-20
Tampering
framework/app boundary and extension conventions
low
mitigate
--hygiene runs the Phase 10 rules plus refusals for HTML-string parsers in admin/src, network, cookie or storage access in application plugin asset JS, and script or event-handler markup in application partial templates, each proven by a --self-test plant.
T-10.1-SC
Tampering
npm and Go dependencies
high
mitigate
No package added in this plan (no coverage provider); npm ci against the committed lockfile; swag pinned at v1.16.6.
</threat_model>
`scripts/check-phase10.1.sh --all` is the phase acceptance command, and `scripts/check-phase10.sh --all` must stay green. The manual-only checks (real-browser CSP and module loading, the 768px layout backstops, the Vite dev proxy) are the human-check blocks in 10.1-02 Task 3 and 10.1-03 Task 3, collected at /gsd-verify-work.
<success_criteria>
Every Phase 10.1 Go and SPA change has named tests; both repositories are green.
TestPhase101AlbumsExtension proves the Albums strip, widget and toolbar action with scoping and permissions on real PostgreSQL.
scripts/check-phase10.1.sh --all and scripts/check-phase10.sh --all pass; the security review and validation map are complete and truthful.
The multi-source audit has no missing GOAL, REQ, RESEARCH or CONTEXT item and no deferred item in scope.
</success_criteria>
Create `.planning/phases/10.1-runtime-admin-extension-point/10.1-04-SUMMARY.md` when done.