Files
summercms/.planning/phases/10-admin-vue-spa/10-VERIFICATION.md
2026-09-27 20:45:11 +02:00

468 lines
30 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 10-admin-vue-spa
verified: 2026-09-27T18:43:00Z
status: passed
score: 4/4 roadmap success criteria verified (plan truths 46/46 verified; A3 closed by human UAT)
covered_files:
- ".gitignore"
- ".planning/phases/10-admin-vue-spa/10-01-PLAN.md"
- ".planning/phases/10-admin-vue-spa/10-01-SUMMARY.md"
- ".planning/phases/10-admin-vue-spa/10-02-PLAN.md"
- ".planning/phases/10-admin-vue-spa/10-02-SUMMARY.md"
- ".planning/phases/10-admin-vue-spa/10-03-PLAN.md"
- ".planning/phases/10-admin-vue-spa/10-03-SUMMARY.md"
- ".planning/phases/10-admin-vue-spa/10-04-PLAN.md"
- ".planning/phases/10-admin-vue-spa/10-04-SUMMARY.md"
- ".planning/phases/10-admin-vue-spa/10-05-PLAN.md"
- ".planning/phases/10-admin-vue-spa/10-05-SUMMARY.md"
- "admin/env.d.ts"
- "admin/index.html"
- "admin/openapi/admin.json"
- "admin/package-lock.json"
- "admin/package.json"
- "admin/src/App.vue"
- "admin/src/api/client.ts"
- "admin/src/api/schema.d.ts"
- "admin/src/api/types.ts"
- "admin/src/app/controllerRoutes.ts"
- "admin/src/app/i18n.ts"
- "admin/src/app/icons.ts"
- "admin/src/app/listQuery.ts"
- "admin/src/app/router.ts"
- "admin/src/app/runtime.ts"
- "admin/src/app/theme.ts"
- "admin/src/app/winterUrl.ts"
- "admin/src/components/form/FieldRenderer.vue"
- "admin/src/components/form/FormErrorBanner.vue"
- "admin/src/components/form/FormField.vue"
- "admin/src/components/form/FormGrid.vue"
- "admin/src/components/form/FormTabs.vue"
- "admin/src/components/form/control.ts"
- "admin/src/components/form/fields/CheckboxField.vue"
- "admin/src/components/form/fields/DropdownField.vue"
- "admin/src/components/form/fields/NumberField.vue"
- "admin/src/components/form/fields/RelationField.vue"
- "admin/src/components/form/fields/SwitchField.vue"
- "admin/src/components/form/fields/TextField.vue"
- "admin/src/components/form/fields/TextareaField.vue"
- "admin/src/components/form/fields/UnsupportedField.vue"
- "admin/src/components/form/formState.ts"
- "admin/src/components/form/registry.ts"
- "admin/src/components/list/CellValue.vue"
- "admin/src/components/list/DataTable.vue"
- "admin/src/components/list/FilterBar.vue"
- "admin/src/components/list/ListToolbar.vue"
- "admin/src/components/list/Pagination.vue"
- "admin/src/components/relation/RelationManager.vue"
- "admin/src/components/relation/RelationPickerModal.vue"
- "admin/src/components/shell/AppShell.vue"
- "admin/src/components/shell/Breadcrumbs.vue"
- "admin/src/components/shell/PluginRail.vue"
- "admin/src/components/shell/SectionFlyout.vue"
- "admin/src/components/shell/SectionPanel.vue"
- "admin/src/components/shell/UserMenu.vue"
- "admin/src/components/ui/Button.vue"
- "admin/src/components/ui/ConfirmDialog.vue"
- "admin/src/components/ui/Toast.vue"
- "admin/src/components/ui/confirm.ts"
- "admin/src/main.ts"
- "admin/src/state/useAuth.ts"
- "admin/src/state/useBreadcrumbs.ts"
- "admin/src/state/useNavigation.ts"
- "admin/src/state/useSettings.ts"
- "admin/src/state/useSidebar.ts"
- "admin/src/state/useToasts.ts"
- "admin/src/styles/main.css"
- "admin/src/views/FormView.vue"
- "admin/src/views/ListView.vue"
- "admin/src/views/LoginView.vue"
- "admin/src/views/NotFoundView.vue"
- "admin/src/views/SettingsFormView.vue"
- "admin/src/views/SettingsIndexView.vue"
- "admin/tests/app/client.test.ts"
- "admin/tests/app/controllerRoutes.test.ts"
- "admin/tests/app/i18n.test.ts"
- "admin/tests/app/icons.test.ts"
- "admin/tests/app/listQuery.test.ts"
- "admin/tests/app/router.test.ts"
- "admin/tests/app/runtime.test.ts"
- "admin/tests/app/theme.test.ts"
- "admin/tests/app/winterUrl.test.ts"
- "admin/tests/fixtures/lang.json"
- "admin/tests/fixtures/navigation.json"
- "admin/tests/fixtures/settings.json"
- "admin/tests/fixtures/typed.ts"
- "admin/tests/fixtures/widgets.form-schema.json"
- "admin/tests/fixtures/widgets.list-schema.json"
- "admin/tests/fixtures/widgets.list.json"
- "admin/tests/fixtures/widgets.options.json"
- "admin/tests/fixtures/widgets.record.json"
- "admin/tests/fixtures/widgets.relation-candidates.json"
- "admin/tests/fixtures/widgets.relation-linked.json"
- "admin/tests/fixtures/widgets.relation-schema.json"
- "admin/tests/form/FormField.test.ts"
- "admin/tests/form/FormGrid.test.ts"
- "admin/tests/form/FormTabs.test.ts"
- "admin/tests/form/FormView.test.ts"
- "admin/tests/form/RelationField.test.ts"
- "admin/tests/form/Settings.test.ts"
- "admin/tests/form/fields.test.ts"
- "admin/tests/form/formState.test.ts"
- "admin/tests/form/registry.test.ts"
- "admin/tests/helpers.ts"
- "admin/tests/list/CellValue.test.ts"
- "admin/tests/list/DataTable.test.ts"
- "admin/tests/list/FilterBar.test.ts"
- "admin/tests/list/ListToolbar.test.ts"
- "admin/tests/list/ListView.test.ts"
- "admin/tests/list/Pagination.test.ts"
- "admin/tests/relation/RelationManager.test.ts"
- "admin/tests/relation/RelationPickerModal.test.ts"
- "admin/tests/setup.ts"
- "admin/tests/shell/AppShell.test.ts"
- "admin/tests/shell/Breadcrumbs.test.ts"
- "admin/tests/shell/PluginRail.test.ts"
- "admin/tests/shell/SectionFlyout.test.ts"
- "admin/tests/shell/SectionPanel.test.ts"
- "admin/tests/shell/UserMenu.test.ts"
- "admin/tests/smoke/edit.smoke.test.ts"
- "admin/tests/smoke/form.smoke.test.ts"
- "admin/tests/smoke/list.smoke.test.ts"
- "admin/tests/smoke/relation.smoke.test.ts"
- "admin/tests/smoke/settings.smoke.test.ts"
- "admin/tests/smoke/shell.smoke.test.ts"
- "admin/tests/smoke/tracer.smoke.test.ts"
- "admin/tests/state/useAuth.test.ts"
- "admin/tests/state/useBreadcrumbs.test.ts"
- "admin/tests/state/useNavigation.test.ts"
- "admin/tests/state/useSettings.test.ts"
- "admin/tests/state/useSidebar.test.ts"
- "admin/tests/state/useToasts.test.ts"
- "admin/tests/ui/ui.test.ts"
- "admin/tests/views/App.test.ts"
- "admin/tests/views/LoginView.test.ts"
- "admin/tsconfig.json"
- "admin/vite.config.ts"
- "admin/vitest.config.ts"
- "boardwalk/boardwalk.go"
- "boardwalk/boardwalk_test.go"
- "boardwalk/dist/index.html"
- "bouncer/cookie_guard_test.go"
- "bouncer/jwt.go"
- "bouncer/jwt_guard_test.go"
- "bouncer/refresh.go"
- "bouncer/refresh_test.go"
- "bouncer/registry_test.go"
- "cabana/admin_openapi.go"
- "cabana/admin_paths_test.go"
- "cabana/auth.go"
- "cabana/auth_test.go"
- "cabana/bulk_test.go"
- "cabana/commands_test.go"
- "cabana/contracts.go"
- "cabana/crud.go"
- "cabana/crud_lifecycle_test.go"
- "cabana/csrf.go"
- "cabana/export_test.go"
- "cabana/filter_options_test.go"
- "cabana/filter_schema.go"
- "cabana/form_schema.go"
- "cabana/form_schema_test.go"
- "cabana/http.go"
- "cabana/lang.go"
- "cabana/list_schema.go"
- "cabana/list_schema_test.go"
- "cabana/messages.go"
- "cabana/messages_test.go"
- "cabana/openapi_conformance_test.go"
- "cabana/phase09_contract_test.go"
- "cabana/phase10_auth_test.go"
- "cabana/phase10_coverage_test.go"
- "cabana/phase10_csrf_test.go"
- "cabana/prefix.go"
- "cabana/query_test.go"
- "cabana/refresh_revocation_test.go"
- "cabana/registry.go"
- "cabana/relation.go"
- "cabana/relation_field.go"
- "cabana/relation_field_test.go"
- "cabana/schema_types.go"
- "cabana/security_coverage_test.go"
- "cabana/security_test.go"
- "go.mod"
- "internal/build/build_test.go"
- "internal/build/stubs/artifacts.tmpl"
- "internal/tools/swagger2openapi/main.go"
- "internal/tools/swagger2openapi/main_test.go"
- "pact/capabilities.go"
- "phrasebook/backend/lang/en/lang.yaml"
- "phrasebook/backend/lang/pl/lang.yaml"
- "phrasebook/lang.go"
- "phrasebook/loader.go"
- "phrasebook/phase10_test.go"
- "phrasebook/translator.go"
- "phrasebook/translator_test.go"
- "scripts/check-admin-dist.sh"
- "scripts/check-admin-openapi.sh"
- "scripts/check-phase10.sh"
- "surf/admin_prefix_test.go"
- "surf/cors_coverage_test.go"
- "surf/cors_test.go"
- "surf/middleware_test.go"
- "surf/router.go"
- "surf/router_test.go"
covered_digest: "v2:sha256:d9ac088393759ff9aab5aa67b3cb4a80a04c978e48adb20618eaa71b6154a3b3"
covered_files_note: "fonoteka.go files are outside the project root and cannot be fingerprinted. They are listed in the report body (Required Artifacts) and were checked at fonoteka.go HEAD 3359a83, which is unchanged since the previous verification and has a clean working tree."
behavior_unverified: 0
overrides_applied: 0
mvp_mode_note: "ROADMAP marks Phase 10 mode: mvp, but the goal is not a User Story. Following the Phase 1/3/5/8 precedent, the four ROADMAP success criteria are the contract and User Flow Coverage is derived from them."
decision_coverage:
honored: 28
total: 28
not_honored: []
re_verification:
previous_status: human_needed
previous_score: "4/4 roadmap success criteria (plan truths 45/46, 1 abstained non-inferable)"
previous_head: f47a560
gaps_closed:
- "CR-01 escalation: admin POST /auth/refresh now enforces tokens_valid_after, is_activated and soft delete through bouncer.RefreshAudienceFor (be4a923, a13a121)"
- "A3 insufficient_spec: Secure admin cookie accepted on http://localhost (closed by human UAT, 10-UAT.md test 6)"
- "All 7 human verification items approved in 10-UAT.md (status: complete)"
gaps_remaining: []
regressions: []
human_verification:
- test: "Decide CR-01 (refresh ignores tokens_valid_after / is_activated)"
expected: "Fix now or accept with override"
why_human: "Security-policy decision"
resolution: "Fixed in quick 260927-q23 (be4a923, a13a121). TestAdminRefreshRevocation and TestRefreshAudienceForSubject re-run by the verifier: PASS. Approved in 10-UAT.md test 1."
- test: "Limited admin vs superuser navigation at /plytadmin"
expected: "Limited admin sees only fonoteka > Genres, no Ustawienia; superuser sees all five plus Ustawienia"
why_human: "Real browser against the real server (D-23: no browser e2e)"
resolution: "Approved in 10-UAT.md test 2 (local fonoteka binary, superuser plus genres-only admin)."
- test: "Walkthrough of the five controllers and Ustawienia"
expected: "Lists and forms render from YAML; search, sort, filter, paging, bulk delete, save toast, 422 field errors, album relations, search_use_typesense"
why_human: "End-to-end user flow in a real browser"
resolution: "Approved in 10-UAT.md test 3."
- test: "Collection editors link/unlink round trip"
expected: "Picker 5 per page, owner excluded, Dodaj (N) disabled at 0, plural toast, confirm unlink, focus trap and Esc, no manager on create"
why_human: "Real browser round trip, focus trap and Esc"
resolution: "Approved in 10-UAT.md test 4."
- test: "Visual check in light/dark at desktop and about 900px"
expected: "Matches design/, dark sidebar, rail collapse and flyout below about 1100px"
why_human: "Visual appearance and responsive behavior"
resolution: "Approved in 10-UAT.md test 5, before and after the full-width form change (2585671)."
- test: "Secure cookie on http://localhost (assumption A3)"
expected: "Chrome and Firefox store summer_admin with default cookie_secure"
why_human: "Browser cookie policy, non-inferable"
resolution: "Approved in 10-UAT.md test 6."
- test: "Review the 17 judgment-tier prohibitions"
expected: "Accept or reject the verifier's non-authoritative verdicts"
why_human: "Judgment-tier prohibitions need human resolution"
resolution: "All 17 'not violated' verdicts accepted in 10-UAT.md test 7."
---
# Phase 10: Admin Vue SPA Verification Report
**Phase Goal:** A minimal Vue 3 + TypeScript admin SPA renders login, permission-gated navigation, lists, forms and the relation manager for Albums, Artists, Collections, Genres and Styles, typed from the generated OpenAPI document.
**Verified:** 2026-09-27T18:43:00Z (summercms.go HEAD c7487f6, fonoteka.go HEAD 3359a83)
**Status:** passed
**Re-verification:** Yes. The previous report (f47a560, human_needed) went stale when CR-01 was fixed (be4a923, a13a121) and the forms became full width (2585671).
**MVP note:** ROADMAP marks this phase `mode: mvp`, but the goal is not a User Story. Following the precedent of Phases 1, 3, 5 and 8, the four ROADMAP success criteria are the contract and plan `must_haves` are supporting evidence.
## What changed since the previous verification
| Commit | Change | Effect on this report |
|---|---|---|
| be4a923 | `cabana/auth.go` refresh calls `bouncer.RefreshAudienceFor(r.Context(), s.users, ...)`. `s.users` is the same `lazyBackendUsers` provider the backend guard uses (`cabana/http.go`). A refusal of the subject over cookie transport expires `summer_admin`. `bouncer/refresh.go` runs `subjectPrincipal` and `issuedBeforeCutoff` after every token-only check and before minting. `Refresh` and `RefreshAudience` pass a nil hook, so their behavior is unchanged. | Closes the CR-01 escalation. The diff was read, and the named tests were re-run (see Spot-Checks). |
| a13a121 | Adds `TestRefreshAudienceForSubject`, a `TestJWTGuardTokensValidAfter` pin, and a `TestPhase10Coverage` subtest for cookie expiry on subject refusal | Behavioral evidence for the fix |
| 2585671 | `FormView.vue` and `SettingsFormView.vue` drop `mx-auto max-w-[980px]`. The dist was rebuilt. | CSS only. The dist drift gate and the 441 Vitest tests re-run clean. |
| c7487f6 | 10-REVIEW.md re-review (0 open critical), 10-UAT.md complete (7/7 pass), disposition updated | Human verification closed |
## User Flow Coverage
Derived user story: *As a Płytarium admin, I want to log in to /plytadmin, see only what my role permits, and manage Albums, Artists, Collections, Genres, Styles and Collection editors, so that the catalogue can be administered without the PHP backend.*
| Step | Expected | Evidence | Status |
|---|---|---|---|
| Open /plytadmin | Embedded SPA served with base /plytadmin | `boardwalk/boardwalk.go`, `TestPhase10TracerSPA` (re-run: PASS), `check-admin-dist.sh` (re-run: dist matches a fresh build) | VERIFIED |
| Log in | Cookie session, no token in body | `cabana/auth.go` login, `useAuth.login`, `TestPhase10AdminAuth`, `LoginView.test.ts`; UAT test 6 (Secure cookie on localhost) | VERIFIED |
| See permitted navigation | Limited admin sees Genres only | `TestPhase10AssembledAcceptance` SC-1 (re-run: PASS); UAT test 2 | VERIFIED |
| Use five lists and forms | Schema-driven list and form, create, update | `TestPhase10AssembledAcceptance` SC-2, `TestPhase10Controllers` (re-run: PASS), ListView/FormView tests; UAT test 3 | VERIFIED |
| Link and unlink an editor | Search candidates, link, unlink | `TestPhase10AssembledAcceptance` SC-3, `relation.smoke.test.ts`; UAT test 4 | VERIFIED |
| Stay signed in / be signed out | Refresh keeps an active session; a reset, deactivation or deletion ends it | `TestAdminRefreshRevocation` (re-run on Postgres: 5/5 subtests PASS) | VERIFIED |
| Log out | Cookie expired, old cookie 401 | `TestPhase10AssembledAcceptance` (logout then /auth/me 401) | VERIFIED |
## Goal Achievement
### Observable Truths (ROADMAP contract)
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | An admin logs in through the SPA and sees only the navigation items their permissions allow. | ✓ VERIFIED | Server: `TestPhase10AssembledAcceptance` asserts the limited admin's nav is exactly `fonoteka:[genres]` and the developer's is `albums,collections,genres,styles,artists`, that the limited admin gets 403 on albums, and that the limited admin gets an empty settings list. Re-run this session on testcontainers Postgres: PASS. SPA: `useNavigation.ts` stores `/navigation` verbatim. `railEntries` drops only plugins whose side menu is empty (D-11). Tests: `PluginRail.test.ts`, `SectionPanel.test.ts`, `tracer.smoke.test.ts`. Real browser: UAT test 2 approved with a superuser and a genres-only admin. The CR-01 fix makes the session end correctly on reset (`TestAdminRefreshRevocation`). |
| 2 | Each of the five controllers renders a working list and form generated from its JSON schema. | ✓ VERIFIED | Server: the SC-2 loop in `TestPhase10AssembledAcceptance` and `TestPhase10Controllers` (fields and columns equal the tracked YAML). Both re-run: PASS. SPA: `ListView.vue` loads `/schema/list` and the list. `FormView.vue` loads `/schema/form` and the record, then POSTs or PUTs. The full-width change (2585671) touches only the section's class attribute. Vitest re-run: 48 files, 441 passed. Real browser: UAT test 3 approved. WR-05 (loaders without try/catch) is still open as a warning. |
| 3 | The Collections form's relation manager lets an admin search, link and unlink an editor. | ✓ VERIFIED | Server: the SC-3 block covers candidates with the owner excluded, link, list, the linked user dropping out of the candidates, unlink, and an empty list afterwards (re-run: PASS). SPA: `RelationManager.vue` and `RelationPickerModal.vue`, registered as `relation-manager` and shown in update mode only. Tests: `RelationManager.test.ts`, `RelationPickerModal.test.ts`, `relation.smoke.test.ts`. Real browser: UAT test 4 approved. |
| 4 | API calls in the SPA use TypeScript types generated from the OpenAPI document, with no hand-maintained duplicate type. | ✓ VERIFIED | `client.ts` is `createClient<paths>` over the generated `schema.d.ts`. `admin/src` has no other `fetch(`. `types.ts` contains only aliases onto `components['schemas']`, and the hygiene gate enforces that. `check-admin-openapi.sh --check` re-run: exit 0. The CR-01 fix changed no route or response shape, and the document did not drift. Info: `controllerRoutes.ts` has a local `ControllerParams` interface. It parses route ids and is not an API payload. |
**Score:** 4/4 ROADMAP truths verified (0 present-but-behavior-unverified).
### Plan must-have truths (supporting evidence)
There are 46 plan truths across 10-01..10-05. 45 were verified by named, passing tests or gates in the previous run, and their files are unchanged apart from the two form views and the refresh path. Those two are covered again below.
The 46th is backstop truth A3: browsers accept the Secure admin cookie on http://localhost. The previous run abstained on it as `insufficient_spec`. It is now closed by directly observed behavior: in UAT test 6 the user ran the fonoteka binary at http://localhost:8080/plytadmin with the default `cookie_secure`, and the session worked.
The refresh path affects truths that touch cookie refresh (10-01 cookie auth, T-10-05). These were re-checked with `TestPhase10Coverage` (PASS), `TestAdminRefreshRevocation` (PASS, Postgres) and `TestRefreshAudienceForSubject` (PASS). The quick task's removal check (run with the check hook set to nil) makes both unit tests fail, so the tests do exercise the hook.
Backstop (non-inferable) truths:
| Truth | Evidence | Status |
|---|---|---|
| A1 default prefix /backend; fonoteka /plytadmin | `DefaultAdminPrefix = "/backend"`, `TestPhase10Prefix`, fonoteka `config/backend.yaml uri: /plytadmin` | VERIFIED |
| A3 Secure cookie accepted on http://localhost | 10-UAT.md test 6: pass (observed in a real browser) | VERIFIED (human-observed) |
| A10 30 s blacklist grace plus single-flight refresh | `config/admin.yaml blacklist_grace: 30`, `client.test.ts` single-flight cases | VERIFIED |
| A8 pivot sort_order = array index | `admin_phase10_relations_test.go` sort_order assertions | VERIFIED |
| fonoteka has no RelationExtendOptionsQuery | no implementation in fonoteka; the hook is proven with acme fixtures | VERIFIED |
| Required relation is a schema hint only | Phase 9 decision 304 tests plus `TestPhase10RelationSave` | VERIFIED |
| A6 dark mode follows the system only | `theme.ts` matchMedia, `theme.test.ts`; UAT test 5 | VERIFIED |
| SC-4 mechanical enforcement | `check-phase10.sh` hygiene rules | VERIFIED |
### Prohibitions (judgment tier)
All 17 verdicts from the previous report were "not violated", and the user accepted them in 10-UAT.md test 7. The changes since then do not touch what they cover. The CR-01 fix adds no app names, no token in a response body and no new route. The CSS change adds no `v-html` and no foreign-origin asset. The one qualified verdict from before was the 05 prohibition "High threats cite an executable test or gate", which was only formally met. It is now met outright: the T-10-05 row in 10-SECURITY-REVIEW.md cites `TestAdminRefreshRevocation` and `TestRefreshAudienceForSubject`, and its residual-risk text now describes revocation on reset, deactivation and deletion accurately, with WR-07 named as the remaining sliding-window risk.
| Plan | Prohibition | Verdict |
|---|---|---|
| 01 | No Płytarium/fonoteka names in summercms.go SPA, fixtures, document or dist | not violated (human-accepted) |
| 01 | Cookie login/refresh never carries the JWT; the SPA never reads or stores it | not violated (human-accepted; the refresh cookie path still returns `token_type: cookie` only) |
| 01 | No foreign-origin fonts, icons or scripts | not violated (human-accepted) |
| 01 | Non-admin routes and the parity doc change only by dropping admin paths | not violated (human-accepted) |
| 02 | Relation save never writes a protected FK or an out-of-scope id | not violated in declared config (human-accepted; WR-02 open) |
| 02 | Public bundle exposes only backend::lang | not violated (human-accepted) |
| 02 | Framework never names a plugin table, pivot or FK | not violated (human-accepted) |
| 02 | Phase 9 security assertions not weakened | not violated; strengthened by the CR-01 fix, which restores T-09-04 for admin sessions |
| 03 | Plugin text rendered as text only | not violated (human-accepted) |
| 03 | No hand-written API payload shapes | not violated (human-accepted) |
| 03 | SPA does not hide or add nav, actions or fields | not violated (human-accepted) |
| 03 | Winter URLs not used verbatim | not violated (human-accepted) |
| 04 | SPA does not filter candidates itself | not violated (human-accepted) |
| 04 | localStorage holds only the sidebar preference | not violated (human-accepted) |
| 05 | Acceptance does not depend on skips, zero-test runs or hand-edited dist/types | not violated (dist and openapi drift re-run clean) |
| 05 | No app names in summercms.go tests | not violated (human-accepted) |
| 05 | High threats cite an executable test or gate | not violated (T-10-05 now cites the revocation tests) |
### Required Artifacts
All 30 plan artifacts passed `verify.artifacts` in the previous run, and none was deleted. Changed or added since then:
| Artifact | Status | Details |
|---|---|---|
| `bouncer/refresh.go` (`RefreshAudienceFor`) | ✓ VERIFIED | Substantive, and wired from `cabana/auth.go:224`. `Refresh` and `RefreshAudience` keep their signatures and pass a nil hook, so the core user plugin contract is unchanged. |
| `bouncer/jwt.go` (`subjectPrincipal`, `issuedBeforeCutoff`, `ErrSubjectRejected`) | ✓ VERIFIED | Shared by the guard and refresh |
| `cabana/auth.go`, `cabana/http.go` | ✓ VERIFIED | `service.users` is the guard's `lazyBackendUsers` |
| `cabana/refresh_revocation_test.go`, `bouncer/refresh_test.go`, `bouncer/jwt_guard_test.go` | ✓ VERIFIED | Re-run: PASS |
| `admin/src/views/FormView.vue`, `SettingsFormView.vue` + `boardwalk/dist` | ✓ VERIFIED | CSS class change only; the dist matches a fresh build |
| `../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_*_test.go` | ✓ VERIFIED | Unchanged at 3359a83. Acceptance, tracer and controllers re-run: PASS |
| All other artifacts from 10-01..10-05 | ✓ VERIFIED | Unchanged since the previous run |
### Key Link Verification
The previous run checked all 19 links (16 by the tool, 3 traced by hand), and all were WIRED. The one new link:
| From | To | Via | Status |
|---|---|---|---|
| `cabana/auth.go` refresh | `bouncer.RefreshAudienceFor` → the guard's `UserProvider` | `s.users` set from the same `lazyBackendUsers` value passed to `NewBackendJWTGuard` (`cabana/http.go:113,137`) | WIRED |
| `bouncer/refresh.go` check hook | `subjectPrincipal` / `issuedBeforeCutoff` | closure passed as `check` to `refreshAudience`, called before `MintAudience` | WIRED |
### Data-Flow Trace (Level 4)
These are unchanged from the previous run. Every flow is ✓ FLOWING:
- navigation comes from `GET /navigation`, filtered on the server
- list rows and columns come from the list endpoint and `/schema/list`
- form fields and the record come from `/schema/form` and `GET /{id}`
- the relation manager's linked rows and candidates come from `/relations/{name}` and `/candidates`
- strings come from `GET /lang`
### Behavioral Spot-Checks (run this session)
| Behavior | Command | Result | Status |
|---|---|---|---|
| CR-01: reset, deactivation and deletion end admin refresh | `go test -count=1 -v -run '^TestAdminRefreshRevocation$' ./cabana/` | 5/5 subtests PASS (pre-reset cookie refused and expired, pre-reset Bearer refused, deactivated, soft-deleted, active still refreshes) | ✓ PASS |
| RefreshAudienceFor unit and guard pin | `go test -count=1 -run '^(TestRefreshAudienceForSubject\|TestJWTGuardTokensValidAfter)$' ./bouncer/` | ok | ✓ PASS |
| Cookie refresh coverage (incl. expiry on subject refusal) | `go test -count=1 -run '^TestPhase10Coverage$' ./cabana/` | ok | ✓ PASS |
| SC-1..SC-4 assembled on Postgres plus tracer and controllers | `go test -count=1 -run '^(TestPhase10AssembledAcceptance\|TestPhase10TracerSPA\|TestPhase10Controllers)$' ./plugins/golem15/fonoteka/` (fonoteka.go) | ok 6.8s | ✓ PASS |
| SPA unit/component suite | `npx vitest run` (admin) | 48 files, 441 passed | ✓ PASS |
| Embedded dist drift | `scripts/check-admin-dist.sh` | vue-tsc clean, "boardwalk/dist matches a fresh build" | ✓ PASS |
| OpenAPI and types drift | `scripts/check-admin-openapi.sh --check` | exit 0 | ✓ PASS |
| Phase gate | `scripts/check-phase10.sh --all` (orchestrator, after the changes) | exit 0, "phase10 all passed", the two pre-existing parity failures allow-listed by name | ✓ PASS |
### Probe Execution
No `probe-*.sh` scripts are declared or present. `check-phase10.sh` is the phase gate. The orchestrator ran it, and I re-ran its sub-gates (openapi, dist) and its key tests.
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|---|---|---|---|---|
| ADMIN-06 | 10-01..10-05 | Minimal Vue 3 + TS SPA renders login, permission-gated navigation, lists, forms and the relation manager for the five controllers using generated types | ✓ SATISFIED | Truths 1-4, UAT 7/7. REQUIREMENTS.md marks it `[x]` and Complete. |
Orphaned requirements: none. ADMIN-06 is the only ID REQUIREMENTS.md maps to Phase 10.
### Decision Coverage
All 28 trackable CONTEXT.md decisions are honored (unchanged since the previous run).
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
| `cabana/auth.go` | refresh | CR-01 | resolved | Fixed in be4a923 and a13a121; verified above |
| `bouncer/refresh.go`, fonoteka `golem15/user` api_controller | — | WR-08: the frontend user refresh still ignores tokens_valid_after | ⚠️ Warning | Site-user audience, outside the Phase 10 contract. Deliberately left unchanged to keep the core user plugin's contract. Needs a parity check against PHP first. |
| `cabana/auth.go`, `cabana/http.go` | — | WR-01: logout behind the guard does not expire a rejected cookie | ⚠️ Warning | Open, non-blocking |
| `cabana/relation_field.go`, `crud.go` | — | WR-02 and WR-03 | ⚠️ Warning | Not reachable with the current fonoteka YAML |
| `pact/capabilities.go` | — | WR-04: `FilterOptions(scope)` has no ctx or db | ⚠️ Warning | Open |
| `admin/src/views/*.vue` and others | — | WR-05: loaders have no try/catch | ⚠️ Warning | A network failure leaves the skeleton spinning |
| `ListView.vue`, `RelationManager.vue` | — | WR-06: no page clamp after delete or unlink | ⚠️ Warning | Open |
| `bouncer/refresh.go` | — | WR-07: the refresh window slides with no absolute cap | ⚠️ Warning | Named as residual risk in T-10-05 |
| — | — | IN-01..IN-10 in 10-REVIEW.md | ℹ️ Info | Open, non-blocking |
No `TBD`, `FIXME` or `XXX` markers are in any file changed since the previous verification (`bouncer/jwt.go`, `bouncer/refresh.go`, `cabana/auth.go`, `cabana/http.go`, the new tests, and the two form views). The previous run found none in the rest of the Phase 10 files.
### Other observations (Info)
- The summercms.go working tree is clean apart from `.planning/milestone.lock`, `.planning/state.json`, `.gsd/` and `go.work.sum`. None of these is Phase 10 code. The uncommitted `examples/hello/main.go` change noted in the previous report is gone.
- Two fonoteka `parity` tests have failed since Phase 9 (deferred-items.md). The gate allow-lists them by name and refuses once either passes.
- The quick task saw one-off load flakes in fonoteka `golem15/user` (`TestCodes`, `TestForgotPassword`) during a parallel gate run. The orchestrator reports those tests pass when run uncached, and the final `--all` gate exited 0.
- Phase 9 still has no VERIFICATION.md. CR-01 was the Phase 9 T-09-04 concern, and it is now resolved for the admin audience.
### Human Verification
Complete. 10-UAT.md has `status: complete` with 7/7 passed. The user ran the fonoteka binary at http://localhost:8080/plytadmin with a superuser and a genres-only admin, and approved:
1. the CR-01 decision (fixed)
2. navigation for the limited admin versus the superuser
3. the walkthrough of the five controllers and Ustawienia
4. the editor link/unlink round trip
5. the visual check in light and dark at desktop and responsive widths
6. A3, the Secure cookie on localhost
7. the 17 judgment-tier prohibitions
Each item is recorded as resolved in the `human_verification` frontmatter.
### Gaps Summary
None. All four ROADMAP success criteria are verified by automated evidence re-run this session:
- the assembled Postgres acceptance test
- 441 SPA tests
- the dist and OpenAPI drift gates
- the CR-01 revocation tests
The approved UAT covers the real-browser behavior that D-23 keeps out of automated tests. CR-01, the reason the previous run stopped at human_needed, is fixed and proven by a test that fails without the fix. The open warnings (WR-01..WR-08) and info items are non-blocking review findings. None of them falsifies a Phase 10 must-have.
---
_Verified: 2026-09-27T18:43:00Z_
_Verifier: Claude (gsd-verifier)_