docs(10): add phase verification report
This commit is contained in:
449
.planning/phases/10-admin-vue-spa/10-VERIFICATION.md
Normal file
449
.planning/phases/10-admin-vue-spa/10-VERIFICATION.md
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
---
|
||||||
|
phase: 10-admin-vue-spa
|
||||||
|
verified: 2026-09-27T18:45:00Z
|
||||||
|
status: human_needed
|
||||||
|
score: 4/4 roadmap success criteria verified by automated evidence (plan truths 45/46 verified, 1 abstained non-inferable)
|
||||||
|
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"
|
||||||
|
- "bouncer/cookie_guard_test.go"
|
||||||
|
- "bouncer/jwt.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/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:e78a8c0a010c731fccbd5a20d3f708e03eb957734c07f3739f4eae8249ecb0c0"
|
||||||
|
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."
|
||||||
|
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: []
|
||||||
|
insufficient_spec_items:
|
||||||
|
- truth: "[flagged assumption A3] Browsers accept the Secure admin cookie on http://localhost during development"
|
||||||
|
reason: insufficient_spec
|
||||||
|
note: "The production refusal of cookie_secure=false is tested (TestPhase10Prefix/cookie_secure). Browser acceptance of a Secure cookie on http://localhost is browser behavior that no test can observe."
|
||||||
|
escalations:
|
||||||
|
- finding: "CR-01 (open, critical): POST /auth/refresh ignores tokens_valid_after and is_activated and re-mints iat=now"
|
||||||
|
classification: "Not a failed Phase 10 must-have. It falsifies the Phase 9 09-02 truth that admin:reset-password invalidates earlier tokens (T-09-04). The Phase 10 SPA's automatic refresh on 401 makes it the default path."
|
||||||
|
decision_needed: "Fix before the phase closes (recommended: small change in cabana/auth.go refresh plus one regression test), or accept it with an override and a tracked follow-up."
|
||||||
|
human_verification:
|
||||||
|
- test: "Decide CR-01 before closing the phase. Suggested repro: log in to /plytadmin in a browser, run `summer admin:reset-password <login>`, wait for the access token to expire (or delete nothing and just reload after expiry), then click any list."
|
||||||
|
expected: "Secure behavior: the SPA lands on the login screen. Current code: the SPA silently refreshes and keeps working for up to refresh_ttl (14 days)."
|
||||||
|
why_human: "This is a security-policy decision (fix now vs accept with override). The code path is confirmed by reading cabana/auth.go:217-241, bouncer/refresh.go and bouncer/mint.go:48-60, but no test covers it."
|
||||||
|
- test: "At /plytadmin, log in as a limited admin (role with only golem15.fonoteka.access_genres) and then as a superuser/developer."
|
||||||
|
expected: "Limited admin: the rail shows only fonoteka, the side panel only Genres, no Ustawienia item. Superuser: Albums, Collections, Genres, Styles, Artists plus Ustawienia."
|
||||||
|
why_human: "The server filtering is proven on PostgreSQL and the rail/panel rendering is proven with fixtures in happy-dom. No test renders the real SPA against the real server in a browser (D-23: no browser e2e)."
|
||||||
|
- test: "Walk through Albums, Artists, Collections, Genres and Styles at /plytadmin: list (search, sort, filter, page, bulk delete), open a record, edit, save, create. Include Albums' genre (single relation with emptyOption) and artists (multiple relation chips), and Ustawienia > search_use_typesense."
|
||||||
|
expected: "Each list and form renders the columns and fields from its YAML. Saves show the toast. 422 errors show under their fields. The datetime and switch columns render as designed."
|
||||||
|
why_human: "SPA component tests use neutral acme fixtures. The fonoteka schemas are proven only at the API level. The end-to-end user flow in a real browser is unobserved."
|
||||||
|
- test: "Open an existing Collection, go to the Editors tab, open Dodaj, search a user, select across pages, confirm, then select the linked row and unlink it."
|
||||||
|
expected: "The picker shows 5 per page with the owner excluded. Dodaj (N) is disabled at 0. The linked list refreshes with the plural toast. Unlink asks for confirmation, then removes the row. The relation manager is absent on the create form."
|
||||||
|
why_human: "The link/unlink round trip is proven by TestPhase10AssembledAcceptance (API) and relation.smoke.test.ts (mocked fetch). The real browser round trip, focus trap and Esc behavior need a person."
|
||||||
|
- test: "Visual check in light and dark (system preference) at desktop width and at about 900px, against .planning/phases/10-admin-vue-spa/design."
|
||||||
|
expected: "Matches the design tokens. The sidebar stays dark in both modes. Below about 1100px the section panel collapses to the rail, and hovering or focusing a rail item opens the flyout, which closes on Esc and returns focus."
|
||||||
|
why_human: "Visual appearance and responsive behavior cannot be verified by grep or happy-dom."
|
||||||
|
- test: "Run the admin with backend.cookie_secure unset (default true) on http://localhost:<port>/plytadmin in Chrome and Firefox and log in."
|
||||||
|
expected: "The browser stores the summer_admin cookie and the session works (flagged assumption A3)."
|
||||||
|
why_human: "Browser cookie policy for Secure cookies on localhost is outside any test (non-inferable truth, insufficient_spec)."
|
||||||
|
- test: "Review the 17 judgment-tier plan prohibitions in the Prohibitions table below."
|
||||||
|
expected: "Accept or reject the verifier's non-authoritative verdict for each (all currently 'not violated')."
|
||||||
|
why_human: "The prohibitions are judgment-tier. The verifier's verdict is non-authoritative by design."
|
||||||
|
---
|
||||||
|
|
||||||
|
# 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:45:00Z
|
||||||
|
**Status:** human_needed
|
||||||
|
**Re-verification:** No, initial verification
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## 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` (run: PASS), `check-admin-dist.sh` (run: dist matches a fresh build) | VERIFIED |
|
||||||
|
| Log in | Cookie session, no token in body | `cabana/auth.go` login, `useAuth.login`, `TestPhase10AdminAuth` (run: PASS), `LoginView.test.ts` | VERIFIED |
|
||||||
|
| See permitted navigation | Limited admin sees Genres only | `TestPhase10AssembledAcceptance` SC-1 (run: PASS), `useNavigation.ts` renders server data | VERIFIED (browser: human) |
|
||||||
|
| Use five lists and forms | Schema-driven list and form, create, update | `TestPhase10AssembledAcceptance` SC-2, `TestPhase10Controllers` (run: PASS), ListView/FormView smoke tests | VERIFIED (browser: human) |
|
||||||
|
| Link and unlink an editor | Search candidates, link, unlink | `TestPhase10AssembledAcceptance` SC-3, `relation.smoke.test.ts` | VERIFIED (browser: human) |
|
||||||
|
| 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]`, the developer's is `albums,collections,genres,styles,artists`, and that the limited admin gets 403 on albums and an empty settings list (re-run this session on testcontainers Postgres: PASS 0.70s). SPA: `useNavigation.ts` stores `/navigation` verbatim and `railEntries` only drops plugins with an empty side menu (D-11); `PluginRail.test.ts`, `SectionPanel.test.ts`, `tracer.smoke.test.ts`. Login: `LoginView.vue` → `useAuth.login` → `api.POST('/auth/login')`; `main.ts` boots `/lang` → `/auth/me` → `/navigation`. Real-browser rendering is a human item. CR-01 does not falsify this truth: it concerns session revocation, and permission filtering still runs on every request. |
|
||||||
|
| 2 | Each of the five controllers renders a working list and form generated from its JSON schema. | ✓ VERIFIED | Server: SC-2 loop in `TestPhase10AssembledAcceptance` (list schema, list, form schema, create 201, update, show, album genre/artists relations persisted) and `TestPhase10Controllers` (fields and columns equal the tracked YAML); both PASS. SPA: `ListView.vue` loads `/schema/list` and the list, and `FormView.vue` loads `/schema/form` and the record, then POSTs or PUTs. Every form field type in fonoteka's fields.yaml (text, textarea, dropdown, switch, checkbox, relation, relation-manager) is registered in `registry.ts`. Both column types (datetime, switch) are handled in `CellValue.vue`. 441 Vitest tests pass (re-run: 48 files, 441 passed). WR-05 (loaders without try/catch stay stuck loading on network failure) is an open warning. |
|
||||||
|
| 3 | The Collections form's relation manager lets an admin search, link and unlink an editor. | ✓ VERIFIED | Server: the SC-3 block searches candidates (the owner is excluded), links, lists, sees the linked user drop from the candidates, unlinks, and sees the list empty (PASS). SPA: `RelationManager.vue` (linked list, unlink with confirm, `/relations/{name}/unlink`), `RelationPickerModal.vue` (`/candidates` with 5 per page, `/link` with ids). It is registered as `relation-manager` and rendered only in update mode (`FormView.vue:71`). Tests: `RelationManager.test.ts`, `RelationPickerModal.test.ts`, `relation.smoke.test.ts`. |
|
||||||
|
| 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`. All 26 `api.*` call sites use typed path templates. There is no other `fetch(` in `admin/src`. `types.ts` is aliases onto `components['schemas']` only, and the hygiene gate enforces that. `check-admin-openapi.sh --check` re-run: exit 0 (document and types drift-clean). `vue-tsc --noEmit` re-run: clean. SC-4 in the acceptance test: every called template is in `admin/openapi/admin.json`. Info: `app/controllerRoutes.ts` declares a local `ControllerParams` interface with the same shape as the generated path-params alias. It is used for parsing controller ids, not for API payloads, but it could simply alias the generated type. |
|
||||||
|
|
||||||
|
**Score:** 4/4 ROADMAP truths verified (0 present-but-behavior-unverified).
|
||||||
|
|
||||||
|
### Plan must-have truths (supporting evidence)
|
||||||
|
|
||||||
|
46 plan truths across 10-01..10-05. 45 are verified by named, passing tests or gates:
|
||||||
|
- the prefix, cookie and CSRF truths: `TestPhase10Prefix`, `TestPhase10CookieAuth`, `TestPhase10CSRF`, `TestPhase10CookieGuard`, `TestPhase10AdminPrefixCollision`
|
||||||
|
- boardwalk: `TestPhase10BoardwalkServing`
|
||||||
|
- relation options and saves: `TestPhase10RelationOptions`, `TestPhase10RelationSave`, `TestPhase10RelationForgedID`, `TestPhase10AlbumRelations`, `TestPhase10CollectionOwnerReadOnly`
|
||||||
|
- lang and messages: `TestPhase10Bundle`, `TestPhase10LangOverride`, `TestPhase10Messages`, `TestPhase10SPAKeysResolve`
|
||||||
|
- toolbar and filters: `TestPhase10Toolbar`, `TestPhase10FilterOptions`
|
||||||
|
- conformance: `TestPhase10OpenAPIConformance`
|
||||||
|
- SPA truths: the Vitest suites
|
||||||
|
- gate, security review and validation truths: `check-phase10.sh --all` (orchestrator run: exit 0), `10-SECURITY-REVIEW.md`, `10-VALIDATION.md` (`nyquist_compliant: true`)
|
||||||
|
|
||||||
|
Backstop (non-inferable) truths:
|
||||||
|
|
||||||
|
| Truth | Evidence | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| A1 default prefix /backend; fonoteka /plytadmin | `DefaultAdminPrefix = "/backend"`, `TestPhase10Prefix` case `"" → /backend`, `config/backend.yaml uri: /plytadmin` | VERIFIED |
|
||||||
|
| A3 Secure cookie accepted on http://localhost | Only the production refusal is tested | ⚠️ insufficient_spec (human) |
|
||||||
|
| 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:394` asserts the sort_order rows | VERIFIED |
|
||||||
|
| fonoteka has no RelationExtendOptionsQuery | the `albums_admin_controller.go:58` comment; no implementation in fonoteka; the hook is proven with acme fixtures | VERIFIED |
|
||||||
|
| Required relation is a schema hint only | Covered by the Phase 9 decision 304 tests plus `TestPhase10RelationSave` | VERIFIED |
|
||||||
|
| A6 dark mode follows the system only | `theme.ts` matchMedia, `theme.test.ts` | VERIFIED |
|
||||||
|
| SC-4 mechanical enforcement | `check-phase10.sh` hygiene lines 323-331 | VERIFIED (scope: `admin/src/api` only, see Info) |
|
||||||
|
|
||||||
|
### Prohibitions (judgment tier, non-authoritative verdicts, human review recommended)
|
||||||
|
|
||||||
|
| Plan | Prohibition | Verdict | Evidence |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 01 | No Płytarium/fonoteka names in summercms.go SPA, fixtures, document or dist | not violated | grep over admin/src, tests, openapi, boardwalk, cabana, phrasebook, bouncer, surf: 0 hits; hygiene gate appname plant self-test |
|
||||||
|
| 01 | Cookie login/refresh never carries the JWT; the SPA never reads or stores it | not violated | `cookieLoginData`, `phase10NoToken` in the acceptance test, hygiene storage rule |
|
||||||
|
| 01 | No foreign-origin fonts, icons or scripts | not violated | dist URLs: only w3.org namespaces and a vuejs.org warning string; fonts from @fontsource bundled |
|
||||||
|
| 01 | Non-admin routes and the parity doc change only by dropping admin paths | not violated | `git diff feaca6b..HEAD -- docs/openapi.json`: 1672 deletions, 0 additions, all `/_admin/api/v1/*` and cabana schemas |
|
||||||
|
| 02 | Relation save never writes a protected FK or an out-of-scope id | not violated in declared config | `TestPhase10RelationForgedID`, `TestPhase10CollectionOwnerReadOnly`. WR-02 notes a misconfiguration bypass (a scalar FK field declared next to a relation) |
|
||||||
|
| 02 | Public bundle exposes only backend::lang | not violated | `TestPhase10Bundle` |
|
||||||
|
| 02 | Framework never names a plugin table, pivot or FK | not violated | hygiene appname rule, `relation_field.go` reads the contract |
|
||||||
|
| 02 | Phase 9 security assertions not weakened | not violated | `check-phase9.sh --all` passes (orchestrator) |
|
||||||
|
| 03 | Plugin text rendered as text only | not violated | no `v-html` or `innerHTML` in admin/src; hygiene vhtml plant |
|
||||||
|
| 03 | No hand-written API payload shapes | not violated | `types.ts` aliases only (see the SC-4 Info) |
|
||||||
|
| 03 | SPA does not hide or add nav, actions or fields | not violated | nav, toolbar and fields come from the server; the only local rule is D-11 (hide a plugin with an empty side menu) |
|
||||||
|
| 03 | Winter URLs not used verbatim | not violated | `winterUrl.ts`, `winterUrl.test.ts` |
|
||||||
|
| 04 | SPA does not filter candidates itself | not violated | `RelationPickerModal.fetchPage` renders `data.data` unfiltered |
|
||||||
|
| 04 | localStorage holds only the sidebar preference | not violated | the only use is `useSidebar.ts`; hygiene storage rule |
|
||||||
|
| 05 | Acceptance does not depend on skips, zero-test runs or hand-edited dist/types | not violated | the detector refuses skip and zero-test runs; dist and openapi drift re-run clean; no `it.skip` or `t.Skip` in phase tests |
|
||||||
|
| 05 | No app names in summercms.go tests | not violated | same grep as above |
|
||||||
|
| 05 | High threats cite an executable test or gate | formally met | but see the CR-01 note: the T-10-05 residual risk ("valid until logout or expiry") understates the revocation gap |
|
||||||
|
|
||||||
|
### Required Artifacts
|
||||||
|
|
||||||
|
All 30 plan artifacts pass `verify.artifacts` (exists and substantive). Wiring was checked by hand:
|
||||||
|
|
||||||
|
| Artifact | Status | Details |
|
||||||
|
|---|---|---|
|
||||||
|
| `cabana/prefix.go`, `cabana/csrf.go`, `cabana/relation_field.go`, `cabana/messages.go`, `cabana/lang.go` | ✓ VERIFIED | Wired from `cabana/http.go` and `crud.go`; exercised by the named tests |
|
||||||
|
| `boardwalk/boardwalk.go` + `boardwalk/dist` | ✓ VERIFIED | Mounted by `cabana/http.go` via `boardwalk.Handler`; the dist equals a fresh build |
|
||||||
|
| `internal/tools/swagger2openapi/main.go`, `scripts/check-admin-openapi.sh`, `scripts/check-admin-dist.sh` | ✓ VERIFIED | Both gates re-run: exit 0 |
|
||||||
|
| `admin/src/api/client.ts`, `schema.d.ts`, `types.ts` | ✓ VERIFIED | The only HTTP path in the SPA |
|
||||||
|
| `admin/src/views/{ListView,FormView,SettingsFormView}.vue`, `components/list/*`, `components/form/*` | ✓ VERIFIED | Routed in `router.ts`; data from typed calls |
|
||||||
|
| `admin/src/components/relation/{RelationManager,RelationPickerModal}.vue` | ✓ VERIFIED | Registered as `relation-manager`; calls link, unlink and candidates |
|
||||||
|
| `admin/src/components/shell/{SectionFlyout,UserMenu}.vue`, `state/useSidebar.ts` | ✓ VERIFIED | UserMenu → `useAuth.logout` → `POST /auth/logout` |
|
||||||
|
| `scripts/check-phase10.sh` | ✓ VERIFIED | Fail-closed detector; orchestrator `--all` exit 0 |
|
||||||
|
| `../fonoteka.go/plugins/golem15/fonoteka/admin_phase10_{tracer,controllers,e2e,auth,relations,copy}_test.go` | ✓ VERIFIED | e2e, tracer, controllers, auth and owner tests re-run this session: PASS |
|
||||||
|
| `10-SECURITY-REVIEW.md`, `10-VALIDATION.md` | ✓ VERIFIED | Present and complete; T-10-05 residual wording is inaccurate (CR-01) |
|
||||||
|
|
||||||
|
### Key Link Verification
|
||||||
|
|
||||||
|
`verify.key-links` reported 16/19. The 3 misses are tool false negatives, traced by hand:
|
||||||
|
|
||||||
|
| From | To | Via | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `admin/src/main.ts` | `GET /lang` | `main.ts` → `loadStrings()` (`app/i18n.ts:32` `api.GET('/lang')`) before `me()` | WIRED (indirect) |
|
||||||
|
| `UserMenu.vue` | `POST /auth/logout` | `UserMenu.vue:51 logout(router)` → `useAuth.ts:71 api.POST('/auth/logout')` | WIRED (indirect) |
|
||||||
|
| `10-VALIDATION.md` | 10-01..10-05 verify commands | 25 `10-0x` task rows | WIRED (the tool cannot parse a non-file `to`) |
|
||||||
|
| All others (surf→cabana prefix, cabana→boardwalk, auth→bouncer cookie, client→schema, crud→relation_field, translator→lang, RelationField→options, FilterBar→filter options, Picker→link, registry→RelationManager, gate→detectors) | | | WIRED |
|
||||||
|
|
||||||
|
### Data-Flow Trace (Level 4)
|
||||||
|
|
||||||
|
| Artifact | Data | Source | Real data | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| PluginRail/SectionPanel | `navigation` | `GET /navigation` (server-filtered by permission) | yes, from the Postgres roles in the acceptance test | ✓ FLOWING |
|
||||||
|
| ListView/DataTable | rows, columns | `GET /{v}/{p}/{c}` + `/schema/list` | yes | ✓ FLOWING |
|
||||||
|
| FormView | fields, record, labels | `/schema/form` + `GET /{id}` | yes | ✓ FLOWING |
|
||||||
|
| RelationManager / Picker | linked, candidates | `/relations/{name}`, `/candidates` | yes | ✓ FLOWING |
|
||||||
|
| i18n | strings | `GET /lang` (`backend::lang` + plugin overrides) | yes | ✓ FLOWING |
|
||||||
|
|
||||||
|
### Behavioral Spot-Checks (run this session)
|
||||||
|
|
||||||
|
| Behavior | Command | Result | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| SC-1..SC-4 assembled on Postgres | `go test -run '^TestPhase10AssembledAcceptance$' ./plugins/golem15/fonoteka/` (fonoteka.go) | PASS 0.70s | ✓ PASS |
|
||||||
|
| Tracer, controllers, auth, owner read-only | `go test -run '^(TestPhase10TracerSPA\|TestPhase10Controllers\|TestPhase10CollectionOwnerReadOnly\|TestPhase10AdminAuth)$'` | 4 PASS | ✓ PASS |
|
||||||
|
| SPA unit/component suite | `npx vitest run` (admin) | 48 files, 441 passed | ✓ PASS |
|
||||||
|
| SPA typecheck | `npx vue-tsc --noEmit` | no errors | ✓ PASS |
|
||||||
|
| OpenAPI and types drift | `scripts/check-admin-openapi.sh --check` | exit 0 | ✓ PASS |
|
||||||
|
| Embedded dist drift | `scripts/check-admin-dist.sh` | "boardwalk/dist matches a fresh build" | ✓ PASS |
|
||||||
|
| Phase gate | `scripts/check-phase10.sh --all` (orchestrator) | exit 0, two allow-listed pre-existing parity failures | ✓ PASS |
|
||||||
|
|
||||||
|
### Probe Execution
|
||||||
|
|
||||||
|
No `probe-*.sh` scripts are declared or present. The phase gate `check-phase10.sh` stands in for probes. It was run by the orchestrator, and its sub-gates (openapi, dist) and key tests were re-run here.
|
||||||
|
|
||||||
|
### Test Quality Audit
|
||||||
|
|
||||||
|
| Test File | Linked Req | Skipped | Circular | Assertion level | Verdict |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `admin_phase10_e2e_test.go` | ADMIN-06 SC-1..4 | 0 | no | behavioral (multi-step, DB-asserted) | OK |
|
||||||
|
| `admin_phase10_controllers_test.go` | SC-2 | 0 | no (compares against the tracked YAML) | value | OK |
|
||||||
|
| `cabana/phase10_*_test.go`, `openapi_conformance_test.go` | ADMIN-06 backend | 0 | no | value/behavioral | OK |
|
||||||
|
| `admin/tests/**` (48 files) | SC-1..4 SPA | 0 | no | structural/behavioral with mocked fetch | OK (no real-browser run, D-23) |
|
||||||
|
|
||||||
|
Disabled tests: 0. Circular patterns: 0. Insufficient assertions: 0.
|
||||||
|
|
||||||
|
### 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 (browser UAT pending) | Truths 1-4 above |
|
||||||
|
|
||||||
|
Orphaned requirements: none. ADMIN-06 is the only ID mapped to Phase 10 in REQUIREMENTS.md.
|
||||||
|
|
||||||
|
### Decision Coverage
|
||||||
|
|
||||||
|
All 28 trackable CONTEXT.md decisions are honored by shipped artifacts (`check.decision-coverage-verify`).
|
||||||
|
|
||||||
|
### Anti-Patterns Found
|
||||||
|
|
||||||
|
| File | Line | Pattern | Severity | Impact |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `cabana/auth.go` | 217-241 | Refresh never loads the user; ignores `tokens_valid_after` and `is_activated` (CR-01) | ⚠️ Warning (escalated) | Password reset does not end SPA sessions; the SPA auto-refresh on 401 makes this the default path. Not a Phase 10 must-have; falsifies Phase 9 09-02 "reset invalidates earlier tokens" (T-09-04) |
|
||||||
|
| `cabana/auth.go`, `cabana/http.go` | 243-269, 196 | Logout behind the guard does not expire a rejected cookie (WR-01) | ⚠️ Warning | Stale cookie can linger; combines with CR-01 |
|
||||||
|
| `cabana/relation_field.go`, `crud.go` | — | Scalar FK next to a relation field bypasses the scope check (WR-02); validation runs before relation assignment (WR-03) | ⚠️ Warning | Not reachable with the current fonoteka YAML |
|
||||||
|
| `pact/capabilities.go` | 265-270 | `FilterOptions(scope)` has no ctx or db (WR-04) | ⚠️ Warning | New contract; cheaper to change now |
|
||||||
|
| `admin/src/views/*.vue`, `FilterBar.vue`, `LoginView.vue` | — | Loaders without try/catch (WR-05) | ⚠️ Warning | Network failure leaves the skeleton spinning |
|
||||||
|
| `ListView.vue`, `RelationManager.vue` | — | No page clamp after delete or unlink (WR-06) | ⚠️ Warning | Empty last page shown |
|
||||||
|
| `bouncer/refresh.go` | 52-71 | Sliding refresh with no absolute cap (WR-07) | ⚠️ Warning | Check against the PHP contract |
|
||||||
|
| `admin/src/app/controllerRoutes.ts` | 3 | Local `ControllerParams` interface duplicates the generated path-params alias shape | ℹ️ Info | Not an API payload; aliasing would remove any doubt about SC-4 |
|
||||||
|
| — | — | IN-01..IN-07 from 10-REVIEW.md | ℹ️ Info | Open, non-blocking |
|
||||||
|
|
||||||
|
No `TBD`, `FIXME` or `XXX` markers in any file changed by Phase 10 in either repository. The `PLACEHOLDER` hits in `DropdownField.vue` are a legitimate constant for the placeholder option.
|
||||||
|
|
||||||
|
### Other observations (Info)
|
||||||
|
|
||||||
|
- `scripts/check-phase1.sh` fails with `surf: config http.body_limits.default_bytes is required` in `examples/hello`. Confirmed this session. It dates from Phase 6, not Phase 10. The orchestrator reports the same for phase 4, and a missing admin JWT secret in check-phase8 (since 09-01).
|
||||||
|
- The working tree has an uncommitted change to `examples/hello/main.go` that adds `cabana` and `RouteListCommand`. It is not part of any Phase 10 commit. Decide whether to keep or discard it separately.
|
||||||
|
- Phase 9 has no VERIFICATION.md, its ROADMAP entry is unchecked, and ADMIN-01..05 are still "Pending" in REQUIREMENTS.md, although Phase 10 depends on Phase 9. Close Phase 9's verification before or alongside this phase.
|
||||||
|
- Two fonoteka `parity` tests fail since Phase 9 (deferred-items.md). The gate allow-lists them by package and name and refuses once either passes.
|
||||||
|
|
||||||
|
### Human Verification Required
|
||||||
|
|
||||||
|
1. **CR-01 decision (escalation).** `/auth/refresh` does not re-check `tokens_valid_after` or `is_activated`, and it re-mints `iat=now`. The guard (`bouncer/jwt.go:144`) therefore accepts the refreshed token, so after `summer admin:reset-password` the SPA's automatic refresh brings the old session back for up to 14 days. Recommendation: fix before closing. Load the principal in `refresh`, reject when `iat < tokens_valid_after` or the user is not activated, expire the cookie, and add a reset-then-refresh 401 regression test. Also correct T-10-05's residual-risk text. If you accept it instead, record an override and a follow-up.
|
||||||
|
2. **Permission-gated menu in a real browser:** limited admin (Genres only) vs superuser at /plytadmin.
|
||||||
|
3. **Five-controller walkthrough plus Ustawienia:** list, search, sort, filter, page, bulk delete, open, edit, save, create, 422 feedback; Albums genre and artists relations.
|
||||||
|
4. **Editor link/unlink round trip** on a real Collection: picker paging, owner excluded, Dodaj (N), confirm unlink, focus trap and Esc.
|
||||||
|
5. **Visual fidelity:** light and dark, desktop and about 900px, collapsed rail and flyout, against `design/`.
|
||||||
|
6. **A3:** the Secure cookie is accepted on http://localhost in the target dev browsers.
|
||||||
|
7. **Prohibitions review:** accept or reject the 17 non-authoritative verdicts above.
|
||||||
|
|
||||||
|
### Gaps Summary
|
||||||
|
|
||||||
|
No Phase 10 must-have failed. The backend contract is proven on PostgreSQL for all four success criteria, and I re-ran the acceptance test. The SPA is fully wired to it through the generated, drift-clean types, and its 441 component tests pass. The embedded dist matches a fresh build. The status is `human_needed` rather than `passed` for two reasons. First, the SPA has never been exercised in a real browser against the real server (D-23 excludes browser e2e). Second, CR-01 is an open critical security defect. It does not falsify a Phase 10 truth, but the Phase 10 cookie auto-refresh makes it the default path, and it breaks the Phase 9 password-reset revocation guarantee. It needs an explicit fix-or-accept decision before the phase is marked complete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Verified: 2026-09-27T18:45:00Z_
|
||||||
|
_Verifier: Claude (gsd-verifier)_
|
||||||
Reference in New Issue
Block a user