---
phase: 10.1-runtime-admin-extension-point
plan: 04
type: execute
wave: 4
depends_on: [10.1-01, 10.1-02, 10.1-03]
files_modified:
- 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
- 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
- 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
autonomous: true
requirements: [ADMIN-07]
estimate:
tokens: 140000
raw_tokens: 140000
tasks: 3
confidence: low
must_haves:
truths:
- "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."
artifacts:
- path: "scripts/check-phase10.1.sh"
provides: "Fail-closed Phase 10.1 gate with self-test, go, security, postgres, spa, openapi, dist, hygiene, evidence and all stages"
- path: "../fonoteka.go/plugins/golem15/fonoteka/admin_phase101_albums_test.go"
provides: "Assembled PostgreSQL acceptance of the three Albums surfaces"
- path: "modules/cabana/phase101_render_test.go"
provides: "Sanitizer, escaping, caps and model-guard coverage"
- path: ".planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md"
provides: "Threat-to-test evidence ledger"
key_links:
- from: "scripts/check-phase10.1.sh"
to: "go test -json output"
via: "phase101_detect refusing fail, skip, zero-test, non-JSON and missing required tests"
pattern: "phase101_detect"
- from: "scripts/check-phase10.1.sh"
to: "scripts/check-phase10.sh --hygiene"
via: "--hygiene stage reuses the Phase 10 rules before the 10.1 rules"
pattern: "check-phase10.sh --hygiene"
- from: "10.1-VALIDATION.md"
to: "10.1-01..10.1-04 task verify commands"
via: "per-task verification map"
pattern: "10.1-0"
prohibitions:
- "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.
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
@.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.
## Artifacts this phase produces
- Go tests: `TestPhase101FormExtensionSchema`, `TestPhase101PartialSchema`, `TestPhase101Toolbar` (modules/cabana/phase101_schema_test.go), `TestPhase101PartialSanitizer` (phase101_render_test.go), `TestPhase101Assets` (phase101_assets_test.go), `TestPhase101Actions` (phase101_actions_test.go), `TestPhase101BoardwalkExports` (modules/boardwalk/boardwalk_test.go), `TestPhase101AlbumsExtension` (fonoteka.go admin_phase101_albums_test.go)
- 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`)
- Vitest suites `tests/app/pluginAssets.test.ts`, `tests/form/WidgetField.test.ts`, `tests/form/PartialField.test.ts`, `tests/list/PartialHost.test.ts`, extended ListToolbar, ListView, registry, formState, FormField and FormView suites
- `scripts/check-phase10.1.sh` with modes `--self-test`, `--go`, `--security`, `--postgres`, `--spa`, `--openapi`, `--dist`, `--hygiene`, `--evidence`, `--all`; functions `phase101_detect`, `phase101_go`, `phase101_tests`, `hygiene_101`
- `.planning/phases/10.1-runtime-admin-extension-point/10.1-SECURITY-REVIEW.md`; finalized `10.1-VALIDATION.md` (`nyquist_compliant: true`, `wave_0_complete: true`)
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 b2845e016b0d6a89eac744edb690b44f3f443deb -- 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; `