docs(10-05): Phase 10 security review and final validation map
- 10-SECURITY-REVIEW.md: T-10-01..T-10-25 and T-10-SC with mitigation, test or gate stage, observed result, residual risk and the removal (mutation) checks behind every high threat - 10-VALIDATION.md: executed task commands, gate statuses, Wave 0 done, nyquist_compliant after scripts/check-phase10.sh --all passed
This commit is contained in:
66
.planning/phases/10-admin-vue-spa/10-SECURITY-REVIEW.md
Normal file
66
.planning/phases/10-admin-vue-spa/10-SECURITY-REVIEW.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
phase: "10"
|
||||
reviewed: "2026-09-27"
|
||||
threats_open: 0
|
||||
gate: "scripts/check-phase10.sh --all"
|
||||
---
|
||||
|
||||
# Phase 10 Security Review
|
||||
|
||||
This is a fresh code-and-test review of every threat in the registers of Plans 10-01 to 10-05. A high threat counts as mitigated only when its named test fails with the protection removed. That was checked by mutating the production code and re-running the test, as recorded under "Removal check" below. Accepted and transferred threats keep their rationale from the originating plan.
|
||||
|
||||
Commands run from `summercms.go`. `../fonoteka.go` tests run inside that repository. Gate stages are modes of `scripts/check-phase10.sh`.
|
||||
|
||||
| Threat | Category | Component | Severity | Disposition | Production mitigation | Test or gate stage | Observed result | Residual risk |
|
||||
|--------|----------|-----------|----------|-------------|-----------------------|--------------------|-----------------|---------------|
|
||||
| T-10-01 | Information Disclosure | cabana login/refresh cookie transport | high | mitigated | `cabana/auth.go`: an X-Requested-With login or cookie refresh writes the JWT only into the HttpOnly `summer_admin` cookie and returns `cookieLoginData` (token_type, expires_in). `admin/src/api/client.ts` never reads a token | `TestPhase10CookieAuth` (cabana), `TestPhase10TracerSPA` (fonoteka), `TestPhase10Coverage/cookie refresh…`; stages `--security`, `--postgres` | pass; removal check fails both tests | A script injected into the admin origin could still act with the session. The CSP `script-src 'self'` and the no-raw-HTML rule (T-10-16) limit that |
|
||||
| T-10-02 | Tampering | cookie-authenticated unsafe admin routes (CSRF) | high | mitigated | `cabana/csrf.go` `requireAjax` wraps every POST/PUT/DELETE except login and refuses a request with neither Bearer nor `X-Requested-With: XMLHttpRequest` before decoding. The cookie is SameSite=Strict | `TestPhase10CSRF` (walks every mounted handler with a body-read spy), `TestPhase10Coverage/every unsafe mounted route is CSRF-walked` (fails when an unsafe route is added without the walk), `TestPhase10TracerSPA` step 8; stage `--security` | pass; removal check fails TestPhase10CSRF and TestPhase10TracerSPA | Relies on browsers not sending custom headers cross-origin without a CORS preflight, and the admin API answers none |
|
||||
| T-10-03 | Information Disclosure | boardwalk static serving | medium | mitigated | `boardwalk/boardwalk.go` serves the embedded fs only, with `path.Clean`. It lists no directory, answers 404 for an extension miss and the JSON envelope for `api/` misses | `go test ./boardwalk` (TestTraversalIsCleaned, TestDirectoryIsNeverListed, TestMissingFileWithExtensionIs404, TestPhase10BoardwalkServing encoded traversal); stage `--security` | pass | None known |
|
||||
| T-10-04 | Tampering | admin HTML responses (clickjacking, sniffing, indexing) | medium | mitigated | `setSecurityHeaders`: X-Frame-Options DENY, CSP frame-ancestors none, script-src self, nosniff, Referrer-Policy same-origin, X-Robots-Tag noindex. The index has no inline script | `TestSecurityHeadersOnEveryResponse`, `TestNoInlineScript`, `TestPhase10BoardwalkServing/HEAD…`; stage `--security` | pass | None known |
|
||||
| T-10-05 | Spoofing | admin session cookie attributes and logout | high | mitigated | `cabana/auth.go` `sessionCookie`: HttpOnly, Secure (opt-out refused in production), SameSite=Strict, Path=prefix, Max-Age=refresh TTL. `logout` blacklists the jti and expires the cookie | `TestPhase10CookieAuth`, `TestPhase10Coverage/cookie refresh…` (attributes on refresh), `TestPhase10AdminAuth` (fonoteka), `TestPhase10AssembledAcceptance` (old cookie is 401 after logout), `TestPhase10Prefix` (cookie_secure false refused in production); stages `--security`, `--postgres` | pass; removal checks (HttpOnly off, SameSite Lax, blacklist skipped) each fail | A stolen cookie is valid until logout or expiry, the same window as a Bearer token |
|
||||
| T-10-06 | Elevation of Privilege | prefix and controller ID collisions | medium | mitigated | `cabana.AdminPrefix` validation, `checkReservedSegments`, and `surf.checkAdminPrefix` rejecting non-cabana routes at or under the prefix | `TestPhase10Prefix`, `TestPhase10AdminPrefixCollision` (exact, deeper, raw, default `/backend`, sibling allowed); stage `--security` | pass | None known |
|
||||
| T-10-07 | Denial of Service | concurrent cookie refresh from two tabs | low | mitigated | fonoteka `blacklist_grace: 30`. `client.ts` single-flights one refresh and replays once | `tests/app/client.test.ts` (single-flight, one replay), `tests/smoke/tracer.smoke.test.ts`; stage `--spa` | pass | Two tabs refreshing more than 30 s apart with the same old cookie: the second tab re-logs in |
|
||||
| T-10-08 | Tampering | committed dist and generated types drift from source | medium | mitigated | `scripts/check-admin-dist.sh` rebuilds from the lockfile. `scripts/check-admin-openapi.sh --check` regenerates the document and types. Tailwind skips `admin/tests` and the generated files | stages `--dist`, `--openapi` | pass | None known |
|
||||
| T-10-09 | Elevation of Privilege | cabana relation save (IDOR via relation ids) | high | mitigated | `cabana/relation_field.go` `checkRelationScope` revalidates every submitted id through `scopedRelationQuery` (the same `RelationExtendOptionsQuery` scope) inside the save transaction. It answers 422 and rolls back | `TestPhase10RelationForgedID` (cabana), `TestPhase10AlbumRelations` (fonoteka forged artist); stages `--security`, `--postgres` | pass; removal check fails both | None known |
|
||||
| T-10-10 | Tampering | belongs-to mapping of protected foreign keys (mass assignment) | high | mitigated | A belongsTo on a protected fill key is `ReadOnly`. `parseRelationValues` skips it and `assignBelongsTo` never writes it, and its options endpoint is 404 | `TestPhase10CollectionOwnerReadOnly` (fonoteka), `TestPhase10Coverage/read-only relation label…`; stages `--security`, `--postgres` | pass. Removal check: dropping only the parse-time skip fails TestPhase10Coverage (422 on the read-only key). Dropping both layers fails TestPhase10CollectionOwnerReadOnly (owner_id overwritten) | Two independent layers. Removing one alone is still caught by the cabana coverage test |
|
||||
| T-10-11 | Information Disclosure | fields/{field}/options enumeration | medium | mitigated | `protect()` before SQL, the hook scope, per_page capped at 100, and 404 for non-relation and read-only fields | `TestPhase10RelationOptions`, `TestPhase10Coverage/relation option edges`; stages `--postgres`, `--go` | pass | None known |
|
||||
| T-10-12 | Information Disclosure | public /lang bundle | low | mitigated | `cabana/lang.go` serves only keys under `backend::lang.` | `TestPhase10Bundle`, `TestPhase10Coverage/bundle falls back…`; stage `--security` | pass | Framework UI strings are public by design |
|
||||
| T-10-13 | Tampering | messages and toolbar YAML | low | mitigated | Strict decoding with unknown-key rejection, a custom toolbar unmarshal and boot-time key checks | `TestPhase10Messages`, `TestPhase10Toolbar`, `TestPhase10Coverage/relation messages default…`; stage `--go` | pass | None known |
|
||||
| T-10-14 | Tampering | OpenAPI document versus handler output | medium | mitigated | Handlers write the documented types. The converter emits exact unions | `TestPhase10OpenAPIConformance` (every route, unknown fields disallowed), `TestUnionRewrite`, `check-admin-openapi.sh --check`; stage `--openapi` | pass | None known |
|
||||
| T-10-15 | Elevation of Privilege | filters/{scope}/options | medium | mitigated | `protect()` before the provider. Scope names are allow-listed against the compiled filters; anything else is 404 | `TestPhase10FilterOptions`, `TestPhase10Coverage/filter option edges`; stage `--go` | pass | None known |
|
||||
| T-10-16 | Tampering | SPA rendering of plugin labels, messages and record values (XSS) | high | mitigated | Text interpolation only. `admin/src` has no `v-html`, `innerHTML` or `insertAdjacentHTML`. `interpolate` works on plain strings | `--hygiene` (raw-HTML rule, proven by `--self-test` plant), `tests/list/CellValue.test.ts`, `tests/ui/ui.test.ts` (toast and confirm text), `tests/form/fields.test.ts` (hostile type name); stages `--hygiene`, `--spa` | pass; removal check (CellValue via `v-html`) fails the CellValue suite and `--hygiene` | Vue's own escaping is trusted |
|
||||
| T-10-17 | Tampering | login redirect parameter (open redirect) | medium | mitigated | `safeRedirect` accepts only a path starting with exactly one slash (not `//` or `/\`) | `tests/app/router.test.ts`, `tests/views/LoginView.test.ts`; stage `--spa` | pass | None known |
|
||||
| T-10-18 | Elevation of Privilege | client-side hiding of actions and fields | low | accepted | The server enforces permissions, toolbar actions, writable fields and relation scope (Plans 10-01/10-02). The SPA renders only what it receives and never adds entries, so client manipulation gains nothing | Server enforcement evidence: `TestPhase10AssembledAcceptance` (limited admin gets 403 on Albums), `TestPhase09PermissionMatrix`; stage `--postgres` | pass | Accepted: UI hiding is cosmetic |
|
||||
| T-10-19 | Information Disclosure | list state (search terms, filters) in the URL | low | accepted | Admin-only, same-origin, Referrer-Policy same-origin and noindex from Plan 10-01. Search terms are not secrets | `TestSecurityHeadersOnEveryResponse`; stage `--security` | pass | Accepted: terms stay in browser history |
|
||||
| T-10-20 | Tampering | Winter redirect and recordUrl strings used for navigation | low | mitigated | `mapWinterUrl` produces only the current controller's list, create and record routes. Anything else falls back to the list | `tests/app/winterUrl.test.ts` (foreign, absolute, protocol-relative, javascript: inputs); stage `--spa` | pass | None known |
|
||||
| T-10-21 | Elevation of Privilege | relation link of candidates outside scope (owner, inactive users) | medium | transferred | Enforced server-side by Phase 9: RelationExtendManageQuery, ExcludedRelatedIDs, and TestCollectionsAdminForgedPivot/CrossScope. The SPA only posts ids chosen from the server's candidates and runs those suites as a regression in Task 1 | `TestCollectionsAdmin*` (fonoteka), `TestPhase10AssembledAcceptance` (owner not offered); stages `--go`, `--postgres` | pass | Transferred to the Phase 9 server contract |
|
||||
| T-10-22 | Information Disclosure | localStorage | low | mitigated | Only `useSidebar.ts` writes browser storage: the `summer-admin.sidebar` boolean | `--hygiene` storage rule (proven by the `--self-test` plant), `tests/state/useSidebar.test.ts` (only that key is ever written) | pass | None known |
|
||||
| T-10-23 | Spoofing | logout on a shared browser | medium | mitigated | `useAuth.logout` POSTs `/auth/logout` (the server blacklists and expires the cookie), then clears user, navigation and settings and routes to login, even on a failure | `tests/state/useAuth.test.ts` (success, 500, network failure), `tests/shell/UserMenu.test.ts`, `TestPhase10AssembledAcceptance` (old cookie is 401 after logout); stages `--spa`, `--postgres` | pass | None known |
|
||||
| T-10-24 | Repudiation | Phase 10 acceptance evidence | high | mitigated | `scripts/check-phase10.sh`: `phase10_detect` refuses failed, skipped, zero-test, non-JSON and build-failed runs, and named tests that did not pass. Only the two documented parity failures are allow-listed, and they refuse once they pass again. OpenAPI and dist drift, hygiene and evidence stages | `scripts/check-phase10.sh --self-test` (synthetic fail, skip, zero, non-JSON, build, package, required, allow-list cases; hygiene plants); stage `--all` | pass; every synthetic bad run is refused with its own exit code | The allow-list names two tests owned by a Phase 9 follow-up (deferred-items.md) |
|
||||
| T-10-25 | Tampering | framework/app boundary and hand-maintained API types | low | mitigated | The `--hygiene` stage refuses: app or Polish catalogue names in summercms.go admin, boardwalk, cabana and phrasebook; `types.ts` shapes that are not aliases onto the generated schema; direct fetch; raw HTML; foreign origins in dist; icon namespace imports; retired-prefix routes; untested SPA modules | `--hygiene`, `--self-test` | pass | None known |
|
||||
| T-10-SC | Tampering | npm/Go dependencies | high | mitigated | No package was added in Plans 10-02 to 10-05. `admin/` installs with `npm ci` against the lockfile approved at the 10-01 blocking-human gate (17 exact pins). swag stays pinned at v1.16.6 via `go run` | `scripts/check-phase10.sh --spa` (runs `npm ci` against the lockfile); removal check: a changed pin in a scratch copy makes `npm ci` exit 1 | pass | npm 12 blocks the esbuild and vue-demi postinstall scripts. Neither is needed |
|
||||
|
||||
## Removal check
|
||||
|
||||
The production code was changed, the named tests were run, and the file was restored (`git status` clean afterwards). A mitigation counts only if its test fails.
|
||||
|
||||
| Threat | Mutation | Command | Result |
|
||||
|--------|----------|---------|--------|
|
||||
| T-10-01 | Cookie login body also carries `access_token` | `go test ./cabana -run '^TestPhase10CookieAuth$'`; fonoteka `-run '^TestPhase10TracerSPA$'` | both exit 1 |
|
||||
| T-10-02 | `requireAjax` lets every request through | `go test ./cabana -run '^TestPhase10CSRF$'`; fonoteka `-run '^TestPhase10TracerSPA$'` | both exit 1 |
|
||||
| T-10-05 | `HttpOnly: false` | `go test ./cabana -run '^TestPhase10Coverage$'`; fonoteka `-run '^TestPhase10AdminAuth$'` | both exit 1 |
|
||||
| T-10-05 | `SameSite: Lax` | same two tests | both exit 1 |
|
||||
| T-10-05 | logout skips the blacklist | `go test ./cabana -run '^TestPhase10CookieAuth$'`; fonoteka `-run '^TestPhase10AssembledAcceptance$'` | both exit 1 |
|
||||
| T-10-09 | `checkRelationScope` never returns the 422 | `go test ./cabana -run '^TestPhase10RelationForgedID$'`; fonoteka `-run '^TestPhase10AlbumRelations$'` | both exit 1 |
|
||||
| T-10-10 | parse no longer skips read-only relations | `go test ./cabana -run '^TestPhase10Coverage$'` | exit 1 |
|
||||
| T-10-10 | parse skip and write skip both removed | fonoteka `-run '^TestPhase10CollectionOwnerReadOnly$'` | exit 1 (owner_id overwritten) |
|
||||
| T-10-16 | `CellValue` renders text through `v-html` | `npx vitest run tests/list/CellValue.test.ts`; `scripts/check-phase10.sh --hygiene` | both exit 1 |
|
||||
| T-10-24 | synthetic fail, skip, zero, non-JSON, build, package, missing required and stale allow-list runs | `scripts/check-phase10.sh --self-test` | each refused with its exit code |
|
||||
| T-10-SC | `vue` pin changed in a scratch `package.json` | `npm ci --dry-run --offline` against the committed lockfile | exit 1 |
|
||||
|
||||
Only one single-layer mutation left its named test green. Removing just the parse-time skip for T-10-10 did not fail `TestPhase10CollectionOwnerReadOnly`, because `assignBelongsTo` independently refuses protected keys. The review therefore names `TestPhase10Coverage`, which catches that layer, as well as the fonoteka test, which catches the full removal.
|
||||
|
||||
## Residual risk
|
||||
|
||||
- Visual fidelity and the full browser flow are manual checks, not security controls (10-VALIDATION.md, manual-only rows).
|
||||
- The fonoteka.go `parity` package still fails `TestMigrateSeedsCanonicalGenres` and `TestSchemaMatchesPHPSnapshot`. Both predate Phase 10, are logged in deferred-items.md, and are the only failures the gate allows.
|
||||
@@ -3,15 +3,17 @@ phase: "10"
|
||||
slug: "admin-vue-spa"
|
||||
# status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6)
|
||||
# audit-milestone §5.5 distinguishes NOT-VALIDATED (draft) from PARTIAL (validated + nyquist_compliant: false) (#2117)
|
||||
status: draft
|
||||
nyquist_compliant: false
|
||||
wave_0_complete: false
|
||||
status: validated
|
||||
nyquist_compliant: true
|
||||
wave_0_complete: true
|
||||
created: "2026-09-27"
|
||||
validated: "2026-09-27"
|
||||
gate: "scripts/check-phase10.sh --all"
|
||||
---
|
||||
|
||||
# Phase 10 — Validation Strategy
|
||||
|
||||
> Per-phase validation contract for feedback sampling during execution.
|
||||
> Per-phase validation contract for feedback sampling during execution. Plan 10-05 Task 3 finalized it from the executed plans. The statuses record the final `scripts/check-phase10.sh --all` run.
|
||||
|
||||
---
|
||||
|
||||
@@ -19,43 +21,43 @@ created: "2026-09-27"
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Framework** | Go 1.27 `testing` + testify (Testcontainers Postgres harness); Vitest ^3.2 + @vue/test-utils + happy-dom for the SPA |
|
||||
| **Config file** | none for Go; `admin/vitest.config.ts` (Wave 0 installs) |
|
||||
| **Quick run command** | `go test ./cabana ./bouncer ./phrasebook ./boardwalk -count=1` / `(cd admin && npx vitest run --reporter=dot)` |
|
||||
| **Full suite command** | `go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./...) && (cd admin && npm run typecheck && npm test)` |
|
||||
| **Estimated runtime** | ~180 seconds (Postgres-backed suites dominate) |
|
||||
| **Framework** | Go 1.27 `testing` + testify (Testcontainers Postgres harness); Vitest 3.2.7 + @vue/test-utils 2.4.11 + happy-dom 20.11.6 for the SPA |
|
||||
| **Config file** | none for Go; `admin/vitest.config.ts` (happy-dom, `restoreMocks: true`, `tests/setup.ts`) |
|
||||
| **Quick run command** | `go test ./cabana ./bouncer ./phrasebook ./boardwalk -count=1` / `npm --prefix admin test` |
|
||||
| **Full suite command** | `scripts/check-phase10.sh --all` (go vet and go test in both repos including the fonoteka plugin modules, security, PostgreSQL, SPA, OpenAPI, dist, hygiene and evidence stages) |
|
||||
| **Estimated runtime** | about 5 minutes for `--all` (Postgres-backed suites and the SPA build dominate); `npm --prefix admin test` about 20 s |
|
||||
|
||||
---
|
||||
|
||||
## Sampling Rate
|
||||
|
||||
- **After every task commit:** narrowest Go package test + `go vet ./...` in the touched repo; for SPA tasks `npx vitest run <dir>` + `npm run typecheck`
|
||||
- **After every plan wave:** full suite in both repos + `cd admin && npm run build` + `scripts/check-admin-dist.sh` + `scripts/check-admin-openapi.sh`
|
||||
- **After every plan wave:** full suite in both repos + `npm --prefix admin run build` + `scripts/check-admin-dist.sh` + `scripts/check-admin-openapi.sh --check`
|
||||
- **Before `/gsd-verify-work`:** `scripts/check-phase10.sh --all` must be green
|
||||
- **Max feedback latency:** 180 seconds
|
||||
- **Max feedback latency:** 180 seconds per stage
|
||||
|
||||
---
|
||||
|
||||
## Per-Task Verification Map
|
||||
|
||||
Filled by the planner from each PLAN.md `<verify>` block (cwd = summercms.go; the app repo is reached via `(cd ../fonoteka.go && ...)`). Plan 10-05 Task 3 replaces statuses with the final gate result.
|
||||
cwd = summercms.go. The app repo is reached via `(cd ../fonoteka.go && ...)`. The Status column is the result of the final gate run of Plan 10-05, which re-runs each row's tests through its stage.
|
||||
|
||||
| Task ID | Plan | Wave | Requirement / Decisions | Threat Ref | Test Type | Automated Command | File Exists | Status |
|
||||
|---------|------|------|-------------------------|------------|-----------|-------------------|-------------|--------|
|
||||
| 10-01-T1 | 01 | 1 | ADMIN-06 (package gate) | T-10-SC | human (blocking-human) | n/a — npm package legitimacy checkpoint before install | n/a | ⬜ pending |
|
||||
| 10-01-T2 | 01 | 1 | ADMIN-06 SC1, SC4; D-01 D-02 D-03 D-06 D-07 D-10 D-11 D-15 D-16 D-19 | T-10-01 T-10-02 T-10-03 T-10-04 T-10-17 | assembled Postgres + unit + smoke + script | `(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestPhase10TracerSPA$' -count=1 -v) && go vet ./... && go test ./cabana ./bouncer ./boardwalk -count=1 && npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke && scripts/check-admin-openapi.sh --check` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-01-T3 | 01 | 1 | ADMIN-06 SC1; D-04 D-11 D-19 D-25 | T-10-05 T-10-06 T-10-07 T-10-08 | unit + assembled + script | `go test ./cabana -run '^TestPhase10(CookieAuth\|CSRF\|Prefix)$' -count=1 -v && go test ./surf -run '^TestPhase10AdminPrefixCollision$' -count=1 -v && go test ./boardwalk -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestPhase10AdminAuth\|TestPhase10LangCatalog\|TestAdminMetadataNavigation)$' -count=1 -v) && npm --prefix admin test -- tests/smoke && scripts/check-admin-dist.sh && scripts/check-phase9.sh --security` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-02-T1 | 02 | 2 | ADMIN-06 SC2; D-17 D-18 D-26 | T-10-09 T-10-10 T-10-11 | unit + assembled Postgres | `go test ./cabana -run '^TestPhase10Relation(Options\|Save\|ForgedID\|Boot)$\|^TestPhase09(PermissionMatrix\|ContractInventory)$' -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestPhase10AlbumRelations\|TestPhase10CollectionOwnerReadOnly\|TestAlbumsAdmin.*\|TestCollectionsAdmin.*)$' -count=1) && scripts/check-admin-openapi.sh --check` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-02-T2 | 02 | 2 | ADMIN-06 SC2; D-13 D-14 D-20 D-24 | T-10-12 T-10-13 | unit + assembled | `go test ./phrasebook ./cabana -run '^TestPhase10(Forms\|LangOverride\|SPAKeysResolve\|Bundle\|Messages\|Toolbar)$\|^TestPhase09(PermissionMatrix\|ContractInventory)$' -count=1 -v && go test ./internal/build -run '^Test.*AdminController' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestPhase10ControllerCopy\|TestPhase10LangCatalog)$' -count=1 -v) && scripts/check-admin-openapi.sh --check` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-02-T3 | 02 | 2 | ADMIN-06 SC2, SC4; D-08 D-15 D-16 D-27 | T-10-14 T-10-15 | unit + assembled + script | `go test ./cabana -run '^TestPhase10(FilterOptions\|OpenAPIConformance)$\|^TestPhase09(PermissionMatrix\|ContractInventory)$' -count=1 -v && scripts/check-admin-openapi.sh --check && npm --prefix admin run typecheck && scripts/check-admin-dist.sh && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestPhase10Controllers$' -count=1 -v) && go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./...)` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-03-T1 | 03 | 3 | ADMIN-06 SC2; D-05 D-09 D-10 D-18 D-20 D-24 | T-10-16 T-10-20 | smoke + unit + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke/edit.smoke.test.ts && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-03-T2 | 03 | 3 | ADMIN-06 SC2; D-12 D-13 D-14 D-22 D-27 | T-10-16 T-10-19 | smoke + unit + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke/list.smoke.test.ts && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-03-T3 | 03 | 3 | ADMIN-06 SC2; D-05 D-17 D-18 D-21 D-26 | T-10-18 | smoke + unit + script + assembled | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestPhase10Controllers$' -count=1 -v)` + human-check (five controllers in a browser) | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-04-T1 | 04 | 4 | ADMIN-06 SC3; D-05 D-06 | T-10-21 | smoke + assembled + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke/relation.smoke.test.ts && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestCollectionsAdmin(RelationSchema\|Link\|Unlink\|CrossScope\|RelationEdges)$' -count=1 -v)` + human-check (editors link/unlink in a browser) | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-04-T2 | 04 | 4 | ADMIN-06 SC1; D-06 D-10 | T-10-22 T-10-23 | smoke + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh` + human-check (visual fidelity, limited vs superuser rail) | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-05-T1 | 05 | 5 | ADMIN-06 SC1-SC4; D-08 D-23 | T-10-25 | component + unit | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/app tests/state tests/shell tests/list tests/form tests/relation tests/views tests/smoke` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-05-T2 | 05 | 5 | ADMIN-06 SC1-SC4; D-23 | T-10-24 | unit + assembled Postgres | `go vet ./... && go test ./boardwalk ./bouncer ./cabana ./phrasebook ./surf ./internal/tools/swagger2openapi -count=1 && (cd ../fonoteka.go && go vet ./... && go test ./plugins/golem15/fonoteka -run '^TestPhase10AssembledAcceptance$' -count=1 -v && go test ./... -count=1)` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-05-T3 | 05 | 5 | ADMIN-06 (phase gate) | T-10-24 T-10-25 | gate script | `scripts/check-phase10.sh --self-test && scripts/check-phase10.sh --all` | ❌ W0 (created by task) | ⬜ pending |
|
||||
| 10-01-T1 | 01 | 1 | ADMIN-06 (package gate) | T-10-SC | human (blocking-human) | n/a: the user approved the 17 exact npm pins before install; later installs are `npm ci` (stage `--spa`) | ✅ package-lock.json | ✅ green (approved; `--spa` npm ci passes) |
|
||||
| 10-01-T2 | 01 | 1 | ADMIN-06 SC1, SC4; D-01 D-02 D-03 D-06 D-07 D-10 D-11 D-15 D-16 D-19 | T-10-01 T-10-02 T-10-03 T-10-04 T-10-17 | assembled Postgres + unit + smoke + script | `(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestPhase10TracerSPA$' -count=1 -v) && go vet ./... && go test ./cabana ./bouncer ./boardwalk -count=1 && npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke && scripts/check-admin-openapi.sh --check` | ✅ | ✅ green (`--postgres`, `--go`, `--spa`, `--openapi`) |
|
||||
| 10-01-T3 | 01 | 1 | ADMIN-06 SC1; D-04 D-11 D-19 D-25 | T-10-05 T-10-06 T-10-07 T-10-08 | unit + assembled + script | `go test ./cabana -run '^TestPhase10(CookieAuth\|CSRF\|Prefix)$' -count=1 -v && go test ./surf -run '^TestPhase10AdminPrefixCollision$' -count=1 -v && go test ./boardwalk -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestPhase10AdminAuth\|TestPhase10LangCatalog\|TestAdminMetadataNavigation)$' -count=1 -v) && npm --prefix admin test -- tests/smoke && scripts/check-admin-dist.sh && scripts/check-phase9.sh --security` | ✅ | ✅ green (`--security`, `--go`, `--spa`, `--dist`) |
|
||||
| 10-02-T1 | 02 | 2 | ADMIN-06 SC2; D-17 D-18 D-26 | T-10-09 T-10-10 T-10-11 | unit + assembled Postgres | `go test ./cabana -run '^TestPhase10Relation(Options\|Save\|ForgedID\|Boot)$\|^TestPhase09(PermissionMatrix\|ContractInventory)$' -count=1 -v && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestPhase10AlbumRelations\|TestPhase10CollectionOwnerReadOnly\|TestAlbumsAdmin.*\|TestCollectionsAdmin.*)$' -count=1) && scripts/check-admin-openapi.sh --check` | ✅ | ✅ green (`--security`, `--postgres`, `--go`, `--openapi`) |
|
||||
| 10-02-T2 | 02 | 2 | ADMIN-06 SC2; D-13 D-14 D-20 D-24 | T-10-12 T-10-13 | unit + assembled | `go test ./phrasebook ./cabana -run '^TestPhase10(Forms\|LangOverride\|SPAKeysResolve\|Bundle\|Messages\|Toolbar)$\|^TestPhase09(PermissionMatrix\|ContractInventory)$' -count=1 -v && go test ./internal/build -run '^Test.*AdminController' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestPhase10ControllerCopy\|TestPhase10LangCatalog)$' -count=1 -v) && scripts/check-admin-openapi.sh --check` | ✅ | ✅ green (`--security`, `--go`, `--openapi`) |
|
||||
| 10-02-T3 | 02 | 2 | ADMIN-06 SC2, SC4; D-08 D-15 D-16 D-27 | T-10-14 T-10-15 | unit + assembled + script | `go test ./cabana -run '^TestPhase10(FilterOptions\|OpenAPIConformance)$\|^TestPhase09(PermissionMatrix\|ContractInventory)$' -count=1 -v && scripts/check-admin-openapi.sh --check && npm --prefix admin run typecheck && scripts/check-admin-dist.sh && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestPhase10Controllers$' -count=1 -v) && go vet ./... && go test ./... && (cd ../fonoteka.go && go vet ./... && go test ./...)` | ✅ | ✅ green (`--go` accepts only the two deferred parity failures; `--openapi`, `--dist`, `--postgres`) |
|
||||
| 10-03-T1 | 03 | 3 | ADMIN-06 SC2; D-05 D-09 D-10 D-18 D-20 D-24 | T-10-16 T-10-20 | smoke + unit + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke/edit.smoke.test.ts && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh` | ✅ | ✅ green (`--spa`, `--go`, `--dist`) |
|
||||
| 10-03-T2 | 03 | 3 | ADMIN-06 SC2; D-12 D-13 D-14 D-22 D-27 | T-10-16 T-10-19 | smoke + unit + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke/list.smoke.test.ts && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh` | ✅ | ✅ green (`--spa`, `--go`, `--dist`) |
|
||||
| 10-03-T3 | 03 | 3 | ADMIN-06 SC2; D-05 D-17 D-18 D-21 D-26 | T-10-18 | smoke + unit + script + assembled | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestPhase10Controllers$' -count=1 -v)`; browser part in the manual-only table | ✅ | ✅ green (`--spa`, `--dist`, `--postgres`); manual part: see Manual-Only |
|
||||
| 10-04-T1 | 04 | 4 | ADMIN-06 SC3; D-05 D-06 | T-10-21 | smoke + assembled + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke/relation.smoke.test.ts && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestCollectionsAdmin(RelationSchema\|Link\|Unlink\|CrossScope\|RelationEdges)$' -count=1 -v)`; browser part in the manual-only table | ✅ | ✅ green (`--spa`, `--dist`, `--go`); manual part: see Manual-Only |
|
||||
| 10-04-T2 | 04 | 4 | ADMIN-06 SC1; D-06 D-10 | T-10-22 T-10-23 | smoke + script | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/smoke && go test ./phrasebook -run '^TestPhase10SPAKeysResolve$' -count=1 -v && scripts/check-admin-dist.sh`; browser part in the manual-only table | ✅ | ✅ green (`--spa`, `--dist`, `--hygiene`); manual part: see Manual-Only |
|
||||
| 10-05-T1 | 05 | 5 | ADMIN-06 SC1-SC4; D-08 D-23 | T-10-25 | component + unit | `npm --prefix admin run typecheck && npm --prefix admin test -- tests/app tests/state tests/shell tests/list tests/form tests/relation tests/views tests/smoke` | ✅ 48 suites, 441 tests | ✅ green (`--spa`, `--hygiene` module-import rule) |
|
||||
| 10-05-T2 | 05 | 5 | ADMIN-06 SC1-SC4; D-23 | T-10-24 | unit + assembled Postgres | `go vet ./... && go test ./boardwalk ./bouncer ./cabana ./phrasebook ./surf ./internal/tools/swagger2openapi -count=1 && (cd ../fonoteka.go && go vet ./... && go test ./plugins/golem15/fonoteka -run '^TestPhase10AssembledAcceptance$' -count=1 -v && go test ./... -count=1)` | ✅ | ✅ green (`--go`, `--security`, `--postgres`; fonoteka `go test ./...` fails only the two deferred parity tests, which the gate names) |
|
||||
| 10-05-T3 | 05 | 5 | ADMIN-06 (phase gate) | T-10-24 T-10-25 | gate script | `scripts/check-phase10.sh --self-test && scripts/check-phase10.sh --all` | ✅ | ✅ green ("phase10 all passed") |
|
||||
|
||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||
|
||||
@@ -63,12 +65,12 @@ Filled by the planner from each PLAN.md `<verify>` block (cwd = summercms.go; th
|
||||
|
||||
## Wave 0 Requirements
|
||||
|
||||
- [ ] `admin/package.json` scripts: `dev`, `build`, `typecheck`, `test`, `gen:api` — Plan 10-01 Task 2
|
||||
- [ ] `admin/vitest.config.ts` + `admin/tests/setup.ts` (fetch mock for openapi-fetch) — Plan 10-01 Task 2 (extended in 10-05 Task 1)
|
||||
- [ ] `admin/tests/fixtures/` — neutral schema fixtures (tabs, all field types, unsupported type, three filter shapes) — Plans 10-01, 10-03, 10-04 (completed in 10-05 Task 1)
|
||||
- [ ] `boardwalk/boardwalk_test.go` — Plan 10-01 Task 3 (extended in 10-05 Task 2)
|
||||
- [ ] `scripts/check-admin-openapi.sh` (10-01 Task 2), `scripts/check-admin-dist.sh` (10-01 Task 3), `scripts/check-phase10.sh` (10-05 Task 3)
|
||||
- [ ] Go test helper `adminAPI(rel)` for prefix-relative admin API paths in cabana and fonoteka admin tests — Plan 10-01 Task 2
|
||||
- [x] `admin/package.json` scripts: `dev`, `build`, `typecheck`, `test`, `gen:api` — Plan 10-01 Task 2
|
||||
- [x] `admin/vitest.config.ts` + `admin/tests/setup.ts` (fetch mock for openapi-fetch, desktop light matchMedia) — Plan 10-01 Task 2, extended in 10-04 and 10-05 Task 1 (`restoreMocks: true`)
|
||||
- [x] `admin/tests/fixtures/` — neutral schema fixtures (tabs, all field types, unsupported `colorpicker`, three filter shapes, relation manager) plus `typed.ts`, which types every fixture as its generated OpenAPI schema — Plans 10-01, 10-03, 10-04, 10-05
|
||||
- [x] `boardwalk/boardwalk_test.go` — Plan 10-01 Task 3, extended in 10-05 Task 2
|
||||
- [x] `scripts/check-admin-openapi.sh` (10-01 Task 2), `scripts/check-admin-dist.sh` (10-01 Task 3), `scripts/check-phase10.sh` (10-05 Task 3)
|
||||
- [x] Go test helper `adminAPI(rel)` for prefix-relative admin API paths in the cabana and fonoteka admin tests — Plan 10-01 Task 2
|
||||
|
||||
---
|
||||
|
||||
@@ -76,18 +78,18 @@ Filled by the planner from each PLAN.md `<verify>` block (cwd = summercms.go; th
|
||||
|
||||
| Behavior | Requirement | Why Manual | Test Instructions |
|
||||
|----------|-------------|------------|-------------------|
|
||||
| Visual fidelity to Direction C v2 (tokens, dark mode, collapse below ~1100px) | ADMIN-06 (D-06) | No browser e2e in Phase 10 (D-23) | Human-check in 10-04 Task 2: run `summer serve` for fonoteka, open `{backend.uri}`, compare screens against `design/Direction C v2.dc.html` in light and dark mode at desktop and tablet widths |
|
||||
| Full login → navigate → edit → relation link flow in a real browser | ADMIN-06 SC1–SC3 | Playwright deferred | Human-checks in 10-03 Task 3 (five controllers) and 10-04 Tasks 1-2 (editors link/unlink; limited admin vs superuser rail) |
|
||||
| Visual fidelity to Direction C v2 (tokens, dark mode, collapse below ~1100px) | ADMIN-06 (D-06) | No browser e2e in Phase 10 (D-23) | Human-check in 10-04 Task 2: run `summer serve` for fonoteka, open `{backend.uri}`, compare screens against `design/Direction C v2.dc.html` in light and dark mode at desktop and tablet widths. Collected at `/gsd-verify-work` |
|
||||
| Full login → navigate → edit → relation link flow in a real browser | ADMIN-06 SC1–SC3 | Playwright deferred | Human-checks in 10-03 Task 3 (five controllers) and 10-04 Tasks 1-2 (editors link/unlink; limited admin vs superuser rail). Collected at `/gsd-verify-work` |
|
||||
|
||||
---
|
||||
|
||||
## Validation Sign-Off
|
||||
|
||||
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
|
||||
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
|
||||
- [ ] Wave 0 covers all MISSING references
|
||||
- [ ] No watch-mode flags
|
||||
- [ ] Feedback latency < 180s
|
||||
- [ ] `nyquist_compliant: true` set in frontmatter
|
||||
- [x] All tasks have `<automated>` verify or Wave 0 dependencies
|
||||
- [x] Sampling continuity: no 3 consecutive tasks without automated verify
|
||||
- [x] Wave 0 covers all MISSING references
|
||||
- [x] No watch-mode flags (`vitest run`, `go test -count=1`)
|
||||
- [x] Feedback latency < 180s per stage
|
||||
- [x] Nyquist compliance set in frontmatter after `scripts/check-phase10.sh --all` passed
|
||||
|
||||
**Approval:** pending
|
||||
**Approval:** approved by the Plan 10-05 gate run (2026-09-27); manual-only rows await `/gsd-verify-work`
|
||||
|
||||
Reference in New Issue
Block a user