From 2f496f62000f04e5ce300a14b1f1ffbaaf3654d0 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 22 Oct 2026 14:59:34 +0200 Subject: [PATCH] test(19.6.1): re-verify after relation-label gap closure --- .../19.6.1-VERIFICATION.md | 277 ++++++++---------- 1 file changed, 122 insertions(+), 155 deletions(-) diff --git a/.planning/phases/19.6.1-backend-administrators-screen/19.6.1-VERIFICATION.md b/.planning/phases/19.6.1-backend-administrators-screen/19.6.1-VERIFICATION.md index 41e94bc7..71741645 100644 --- a/.planning/phases/19.6.1-backend-administrators-screen/19.6.1-VERIFICATION.md +++ b/.planning/phases/19.6.1-backend-administrators-screen/19.6.1-VERIFICATION.md @@ -1,8 +1,8 @@ --- phase: 19.6.1-backend-administrators-screen -verified: 2026-10-22T11:46:44Z +verified: 2026-10-22T12:58:00Z status: human_needed -score: 25/25 must-haves verified +score: 28/28 must-haves verified covered_files: - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-01-PLAN.md - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-01-SUMMARY.md @@ -12,12 +12,15 @@ covered_files: - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-03-SUMMARY.md - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-04-PLAN.md - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-04-SUMMARY.md + - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-05-PLAN.md + - .planning/phases/19.6.1-backend-administrators-screen/19.6.1-05-SUMMARY.md - admin/openapi/admin.json - admin/src/api/schema.d.ts - admin/src/api/types.ts - admin/src/components/form/FormField.vue - admin/src/components/form/control.ts - admin/src/components/form/fields/PermissionEditorField.vue + - admin/src/components/form/fields/RelationField.vue - admin/src/components/form/formContext.ts - admin/src/components/form/formState.ts - admin/src/components/form/trigger.ts @@ -28,12 +31,15 @@ covered_files: - admin/tests/form/FormView.test.ts - admin/tests/form/PermissionEditorField.test.ts - admin/tests/form/PreviewView.test.ts + - admin/tests/form/RelationField.test.ts - admin/tests/form/formState.test.ts - admin/tests/form/trigger.test.ts - admin/vitest.browser.config.ts - docs/backend/admin-controllers.md - docs/backend/forms.md - docs/backend/users-and-permissions.md + - modules/boardwalk/dist/assets/index-BtP5LUQA.js + - modules/boardwalk/dist/index.html - modules/cabana/README.md - modules/cabana/action_extensions.go - modules/cabana/action_extensions_postgres_test.go @@ -84,7 +90,7 @@ covered_files: - modules/phrasebook/backend/lang/en/lang.yaml - modules/phrasebook/backend/lang/pl/lang.yaml - scripts/check-phase19.6.1.sh -covered_digest: "v2:sha256:6d68842e532371c8363a75ba453d8c7f439d1d12205fd1584ef23ed182419445" +covered_digest: "v2:sha256:a5616df163b8bd493b54f6ee5fb739c43c32ab07b10cf8a2f87753f77ced72d9" behavior_unverified: 0 overrides_applied: 0 decision_coverage: @@ -92,163 +98,147 @@ decision_coverage: total: 22 not_honored: [] re_verification: - previous_status: gaps_found - previous_score: 22/25 + previous_status: human_needed + previous_score: 25/25 gaps_closed: - - "SC-2 / D-07: an admin cannot raise their own role or permissions, and a non-superuser cannot add a permission they lack to any role (Roles screen ceiling)" - - "D-19: a non-superuser holding backend.manage_users gets 403 user.outranked when running a record action on a non-superuser admin whose effective grants they do not cover" + - "G-19.6.1-T2 implementation: picked relation labels persist across FormView tab remount (FORM_REMEMBER_LABEL + two named vitest cases). Human UAT row 2 was not re-run." gaps_remaining: [] regressions: [] advisory: [] human_verified: - test: "Settings tiles and Administrators list (superuser)" uat_row: 1 - recorded_at: "2026-10-22T11:17:34.000Z" + recorded_at: "2026-10-22T12:30:13.000Z" - test: "Superuser switch locks the permission overrides" uat_row: 3 - recorded_at: "2026-10-22T11:17:34.000Z" + recorded_at: "2026-10-22T12:30:13.000Z" - test: "Deactivate, delete, restore" uat_row: 4 - recorded_at: "2026-10-22T11:17:34.000Z" + recorded_at: "2026-10-22T12:30:13.000Z" - test: "Own record restrictions (superuser)" uat_row: 5 - recorded_at: "2026-10-22T11:17:34.000Z" + recorded_at: "2026-10-22T12:30:13.000Z" + - test: "Roles screen" + uat_row: 6 + recorded_at: "2026-10-22T12:30:13.000Z" - test: "Limited admin with only manage_users" uat_row: 7 - recorded_at: "2026-10-22T11:17:34.000Z" + recorded_at: "2026-10-22T12:30:13.000Z" - test: "Limited admin with only manage_roles" uat_row: 8 - recorded_at: "2026-10-22T11:17:34.000Z" + recorded_at: "2026-10-22T12:30:13.000Z" + - test: "Review the judgment-tier prohibitions in Plans 01-04 (listed under Prohibitions in the report) and confirm or reject the LLM-judge verdicts" + uat_row: 26 + recorded_at: "2026-10-22T12:30:13.000Z" human_verification: - test: "Create an administrator" - expected: "Create opens the form with login, email, name, password + confirmation, role, a Superuser switch and Permission overrides. Saving with a mismatched or 3-character password shows a field error. Saving a valid admin (role e.g. developer, Superuser off) lands on the record and shows a success toast." - why_human: "UAT row 2 recorded an issue: after picking Developer and switching tabs, the role dropdown shows the role id instead of the label. Save still assigns Developer. Pre-existing generic RelationField remount, not a 19.6.1-04 gap-closure item." - - test: "Roles screen" - expected: "Roles lists roles; create a role with a code, name and permissions via the permission editor, then save it. Code-declared grants on system roles show as locked rows with a note. Deleting a role still held by an admin is refused with a toast that names the role and the count; deleting a system role is refused; a system role's code is not editable." - why_human: "Visual SPA check; the recorded pass predates the Roles hook change." - retest_reason: "modules/cabana/backend_roles_admin.go changed after the UAT pass (2026-10-22T11:17:34.000Z)" - - test: "Review the judgment-tier prohibitions in Plans 01-04 (listed under Prohibitions in the report) and confirm or reject the LLM-judge verdicts" - expected: "Each prohibition confirmed as held" - why_human: "unverified-prohibition: human review recommended (judgment tier, verdicts here are non-authoritative)" + expected: "Create opens the form with login, email, name, password + confirmation, role, a Superuser switch and Permission overrides. Saving with a mismatched or 3-character password shows a field error. Saving a valid admin (role e.g. developer, Superuser off) lands on the record and shows a success toast. After picking Developer and switching to Permissions and back, the role dropdown still shows Developer, not the raw id." + why_human: "UAT row 2 is still result: issue. Plan 05 shipped FORM_REMEMBER_LABEL and both named vitest cases pass on the widgets fixture; the human has not re-run the Administrators form in the browser. Do not treat the unit backstops as a UAT pass." --- # Phase 19.6.1: Backend Administrators screen Verification Report **Phase Goal:** cabana gets WinterCMS's Administrators screens, so backend admins and their roles are managed in the admin SPA instead of only through `admin:*` console commands. It reuses cabana's CRUD, permission field and record actions, and follows the sm-user-plugin Users/Groups screens and WinterCMS `Backend\Controllers\Users`/`UserRoles`. -**Verified:** 2026-10-22T11:46:44Z +**Verified:** 2026-10-22T12:58:00Z **Status:** human_needed -**Re-verification:** Yes — after gap-closure plan 19.6.1-04 (G-19.6.1-1 / CR-01 and G-19.6.1-2 / WR-03) +**Re-verification:** Yes — after gap-closure plan 19.6.1-05 (G-19.6.1-T2 only) ## Goal Achievement -The two privilege holes from the prior pass are closed in the codebase and proven on Postgres. All 25 must-have truths now hold. Recorded human UAT settled the superuser and limited-admin walk-throughs except one already-diagnosed labeling issue and a Roles-screen retest (the Roles save hooks changed after that UAT row). Judgment-tier prohibitions still need a human look. +Plan 05 closed the T2 remount-label hole in the SPA: `FORM_REMEMBER_LABEL` persists a picked relation option on the form host, `choose()` writes it before emit, and a remounted `RelationField` seeds `known` from `props.labels`. Both named vitest cases passed in this run. The embedded dist carries `summer.form.rememberLabel`. The prior 25 phase truths still hold (no Go/pact/YAML/OpenAPI change this wave). + +UAT row 2 is still `issue`. Plan 05 had no human-verify checkpoint. This report does not invent a UAT pass. Status stays `human_needed`. ### Observable Truths | # | Truth | Status | Evidence | |---|-------|--------|----------| -| SC-1 | Admin with manage-administrators lists/creates/edits/deactivates backend users and edits roles with the permission editor; existing payloads and `admin:*` commands unchanged | ✓ VERIFIED (human UAT, rows 1, 3–5, 7) | YAML screens in `modules/cabana/backendscreens/`; controllers `backend_users_admin.go`, `backend_roles_admin.go`; this-run `--docker` and `--sc1` (23 cabana named + 10 SC-1 + 162 surf); no phase commit touches commands.go / token_commands.go / account.go; new payload keys remain omitempty | -| SC-2 | Only a superuser can create/edit/grant superuser; an admin cannot raise their own role or permissions; last active superuser protected | ✓ VERIFIED | Superuser and last-superuser halves unchanged (TestAdministratorsSuperuserFlag, TestAdministratorsLastSuperuser*). Self-raise via role **code** is now refused: `refuseCodeDeclaredCode` on Roles create and update; TestRolesCodeDeclaredCodeRefused (non-superuser and superuser PUT/POST onto a code-declared code → 422 `code_locked`, row unchanged / never inserted). Self role/permissions still refused on the Administrators form (TestAdministratorsSelfEdit) | -| SC-3 | Record-action seam usable by plugins; unit tests cover privilege rules | ✓ VERIFIED | `pact.HasAdminActionExtensions` / `AdminActionExtension` (capabilities.go:378–412); `compileActionExtensions` at http.go:137 before compileContributions; TestActionExtensionBoot, TestActionExtensionRun, TestActionExtensionOutranked | -| D-01 | `backend.manage_users`, `backend.manage_roles` registered by internal pseudo-plugin | ✓ VERIFIED | backend_screens.go Permissions(); TestBackendScreensActivation | -| D-02 | Settings → System placement, no navigation item | ✓ VERIFIED | settings entries orders 100/110; no HasNavigation; UAT row 1 | -| D-03 | Superusers hidden from non-superusers | ✓ VERIFIED | `scopeAdministrators` on List/FormExtendQuery; `hiddenSchemaFields` at http.go:784/879; TestAdministratorsSuperuserHidden; UAT row 7 | -| D-04 | No default role grant | ✓ VERIFIED | Permissions() carries no Roles; TestBackendScreensActivation | -| D-05 | Only superuser sets is_superuser; non-superuser sending it gets 403 | ✓ VERIFIED | FormBeforeCreate/Update virtual-field check; TestAdministratorsSuperuserFlag | -| D-06 | Per-user radio permission override, disabled while superuser on | ✓ VERIFIED | fields.yaml radio + trigger on is_superuser; SPA applyTriggers; vitest FormView backstops; UAT row 3 | -| D-07 | Non-superuser can't grant more than they hold (permissions, role picker, Roles screen) | ✓ VERIFIED | TestAdministratorsPermissionCeiling, TestAdministratorsRoleCeiling, TestRolesPermissionCeiling still pass. CR-01 closed: a new code with `len(reg.rolePermissions(newCode)) > 0` is 422 on create and update (`refuseCodeDeclaredCode`, backend_roles_admin.go:162–208); TestRolesCodeDeclaredCodeRefused | -| D-08 | Own record: profile editable; role/superuser/permissions/activation/delete refused | ✓ VERIFIED | TestAdministratorsSelfEdit; UAT row 5 | -| D-09 | Last active superuser protected on every write path under lock | ✓ VERIFIED (behavioral) | guardLastSuperuser + pg_advisory_xact_lock; TestAdministratorsLastSuperuser (5 paths) and TestAdministratorsLastSuperuserConcurrent in this-run docker gate | -| D-10 | Deactivate + soft delete + restore, no hard delete | ✓ VERIFIED | TestAdministratorsLifecycle; UAT row 4 | -| D-11 | Password typed in form, required on create, bcrypt | ✓ VERIFIED | storePassword min 4 / max 72; TestAdministratorsPasswordAndIdentity | -| D-12 | Deactivate/delete/password-set bump tokens_valid_after | ✓ VERIFIED | TestAdministratorsRevocation | -| D-13 | No invite or welcome mail | ✓ VERIFIED | No mail calls in backend_users_admin.go | -| D-14 | Generic cross-controller action extension with boot errors | ✓ VERIFIED | action_extensions.go; TestActionExtensionBoot | -| D-15 | Contributed actions on preview only | ✓ VERIFIED | compileActionExtensions requires `cc.Form.preview`; users config_form.yaml has preview | -| D-16 | Roles fields; system roles code locked; a role never becomes a code-declared system role through the screen | ✓ VERIFIED | userrole/fields.yaml; systemRole() on stored code; refuseCodeDeclaredCode on submitted code ≠ stored (empty on create), including for a superuser; same-code update of developer still 200; TestRolesLifecycle + TestRolesCodeDeclaredLocks + TestRolesCodeDeclaredCodeRefused | -| D-17 | Held role or system role cannot be deleted | ✓ VERIFIED | FormBeforeDelete unscoped holder count; TestRolesLifecycle | -| D-18 | Unit tests last plan; docs updated | ✓ VERIFIED | Plan 03 tests plus Plan 04 regression names in DOCKER_TESTS; cabana/pact READMEs and docs/backend/admin-controllers.md updated with the wrap; TestDocsTree and `summer docs:build --check` clean this run | -| D-19 | No outranking on update/delete/record/bulk actions, contributed Administrators Runs included | ✓ VERIFIED | Built-in paths: TestAdministratorsOutranked. Seam: `compileActionExtensions` replaces contributed `Run` with `wrapAdministratorsContributedRun` when `ext.Controller == AdministratorsControllerID`; wrapper calls unexported `guardOutranked` before `next`; type-assert failure does not call `next`. TestActionExtensionOutranked: delegate POST acme_ping on an outranking target → 403 `enOutranked`, `spy.runs()` empty; built-in deactivate still 403; covered target still 200 with TxFromContext | -| D-20 | Own password refused on this screen | ✓ VERIFIED | TestAdministratorsOwnPassword; UAT row 5 | -| D-21 | SPA support: field trigger + per-record locked permission values | ✓ VERIFIED | field_trigger.go, PermissionRecordLockProvider, trigger.ts, data-locked-by; Go and SPA tests; UAT rows 3 and 5 | -| D-22 | Three-plan split (plus gap-closure wave 4) | ✓ VERIFIED | 01 server, 02 SPA, 03 tests, 04 gap closure; plans 01–03 files untouched this wave | +| SC-1 | Admin with manage-administrators lists/creates/edits/deactivates backend users and edits roles with the permission editor; existing payloads and `admin:*` commands unchanged | ✓ VERIFIED (human UAT, rows 1, 3–5, 7) | YAML screens and controllers unchanged this wave; git log since prior `verified:` empty on cabana/pact/phrasebook/openapi | +| SC-2 | Only a superuser can create/edit/grant superuser; an admin cannot raise their own role or permissions; last active superuser protected | ✓ VERIFIED | `refuseCodeDeclaredCode` still present (4); prior Postgres names unchanged | +| SC-3 | Record-action seam usable by plugins; unit tests cover privilege rules | ✓ VERIFIED | `wrapAdministratorsContributedRun` still present (3); `func guardOutranked` still unexported | +| D-01 | `backend.manage_users`, `backend.manage_roles` registered by internal pseudo-plugin | ✓ VERIFIED | `backend_screens.go` exists; prior pass | +| D-02 | Settings → System placement, no navigation item | ✓ VERIFIED | UAT row 1; no this-wave change | +| D-03 | Superusers hidden from non-superusers | ✓ VERIFIED | UAT row 7; no this-wave change | +| D-04 | No default role grant | ✓ VERIFIED | prior pass | +| D-05 | Only superuser sets is_superuser; non-superuser sending it gets 403 | ✓ VERIFIED | prior pass | +| D-06 | Per-user radio permission override, disabled while superuser on | ✓ VERIFIED | UAT row 3; no this-wave change | +| D-07 | Non-superuser can't grant more than they hold (permissions, role picker, Roles screen) | ✓ VERIFIED | prior pass + `refuseCodeDeclaredCode` | +| D-08 | Own record: profile editable; role/superuser/permissions/activation/delete refused | ✓ VERIFIED | UAT row 5 | +| D-09 | Last active superuser protected on every write path under lock | ✓ VERIFIED | prior behavioral tests; files unchanged this wave | +| D-10 | Deactivate + soft delete + restore, no hard delete | ✓ VERIFIED | UAT row 4 | +| D-11 | Password typed in form, required on create, bcrypt | ✓ VERIFIED | prior pass | +| D-12 | Deactivate/delete/password-set bump tokens_valid_after | ✓ VERIFIED | prior pass | +| D-13 | No invite or welcome mail | ✓ VERIFIED | no this-wave change to `backend_users_admin.go` | +| D-14 | Generic cross-controller action extension with boot errors | ✓ VERIFIED | prior pass | +| D-15 | Contributed actions on preview only | ✓ VERIFIED | prior pass | +| D-16 | Roles fields; system roles code locked; a role never becomes a code-declared system role through the screen | ✓ VERIFIED | `refuseCodeDeclaredCode` still wired | +| D-17 | Held role or system role cannot be deleted | ✓ VERIFIED | prior pass; UAT row 6 settled (plan 05 files do not bear on Roles checks) | +| D-18 | Unit tests last plan; docs updated | ✓ VERIFIED | plans 01–04 files untouched this wave; plan 05 added SPA tests only | +| D-19 | No outranking on update/delete/record/bulk actions, contributed Administrators Runs included | ✓ VERIFIED | wrap still compiled in | +| D-20 | Own password refused on this screen | ✓ VERIFIED | UAT row 5 | +| D-21 | SPA support: field trigger + per-record locked permission values | ✓ VERIFIED | UAT rows 3 and 5; plan 05 did not change trigger/lock path | +| D-22 | Plan split (plus gap-closure waves 4 and 5) | ✓ VERIFIED | 01 server, 02 SPA, 03 tests, 04 privilege gaps, 05 remount labels | +| T2-1 | After picking a relation option on a tabbed form, switching tabs and coming back still shows that option's name, not String(id) | ✓ VERIFIED (behavioral) | this-run `npm --prefix admin run test -- … -t "keeps a picked relation label after switching tabs"` — 1 passed. `panelFields` still filters to the active tab; `v-show` count in FormView is 0 | +| T2-2 | A remounted RelationField seeds its id→label cache from the form's remembered labels, not only from the server meta.labels captured at load | ✓ VERIFIED (behavioral) | this-run `-t "shows a picked label after remount when the form remembered it"` — 1 passed. `choose()` calls `rememberLabel` before emit; remount watch is `watch(() => props.labels, remember, { immediate: true })` | +| T2-3 | modules/boardwalk/dist matches a fresh admin build so the embedded SPA used by the local demo carries the fix | ✓ VERIFIED | `index.html` present; `assets/index-BtP5LUQA.js` contains `summer.form.rememberLabel` (1) and `rememberLabel` (1); commit `51e1a1e`. `verify.artifacts` reports the directory as "Not a file" (tool limitation) | -**Score:** 25/25 truths verified (0 present, behavior-unverified) +**Score:** 28/28 truths verified (0 present, behavior-unverified) ### Advisory (New Scope, Unevidenced) -None. Re-verification scanned Step 7 on files touched since the prior `verified:` timestamp. No new-scope blocker lacked deterministic evidence. WR-01, WR-02 and IN-01..IN-06 remain the same pre-gap-closure warnings/info; they are not in the prior `gaps:` list and those files were not modified in plan 04. +None. Re-verification scanned Step 7 on files touched since the prior `verified:` timestamp (`formContext.ts`, `FormView.vue`, `RelationChildModal.vue`, `RelationField.vue`, the two test files, boardwalk dist). No new-scope blocker lacked deterministic evidence. WR-01, WR-02 and IN-01..IN-06 remain the same pre-gap-closure warnings/info; those Go files were not modified in plan 05. ### Required Artifacts | Artifact | Expected | Status | Details | |----------|----------|--------|---------| -| `modules/pact/capabilities.go` | AdminActionExtension, HasAdminActionExtensions, user.outranked docs | ✓ VERIFIED | lines 378–412; `user.outranked` and `summercms.cabana.users` present | -| `modules/cabana/action_extensions.go` | compileActionExtensions + wrapAdministratorsContributedRun | ✓ VERIFIED | wrap at compile time when Controller is AdministratorsControllerID; `guardOutranked(` called; `guardOutranked` stays unexported | -| `modules/cabana/backend_screens.go` | pseudo-plugin, AdministratorsControllerID | ✓ VERIFIED | withBackendScreens after the no-controllers return | -| `modules/cabana/backend_users_admin.go` | controller with hooks, FormAfterUpdate | ✓ VERIFIED | substantive; unchanged this wave | -| `modules/cabana/backend_roles_admin.go` | roles controller; refuseCodeDeclaredCode | ✓ VERIFIED | FormBeforeCreate and FormBeforeUpdate both call `refuseCodeDeclaredCode`; `rolePermissions(` of the submitted code | -| `modules/cabana/backend_privileges.go` | guards, pg_advisory_xact_lock | ✓ VERIFIED | unchanged this wave; wrap calls into it | -| `modules/cabana/backendscreens/models/user/fields.yaml` | permissioneditor | ✓ VERIFIED | radio + trigger | -| `modules/cabana/backend_screens_smoke_test.go` | TestBackendScreensPostgresSmoke | ✓ VERIFIED | in docker gate | -| `modules/cabana/field_trigger.go` | FieldTrigger | ✓ VERIFIED | | -| `modules/cabana/field_permission.go` | PermissionRecordLockProvider | ✓ VERIFIED | | -| `modules/cabana/relation_field.go` | permissionLocks,omitempty | ✓ VERIFIED | | -| `admin/src/components/form/trigger.ts` | applyTriggers | ✓ VERIFIED | FormView.vue:142, PreviewView.vue:79 | -| `admin/src/components/form/fields/PermissionEditorField.vue` | data-locked-by | ✓ VERIFIED | no v-html | -| `modules/boardwalk/dist` | rebuilt dist | ✓ VERIFIED | prior gate admin stage; plan 04 did not change SPA | -| `modules/cabana/roles_postgres_test.go` | TestRolesCodeDeclaredCodeRefused | ✓ VERIFIED | package cabana_test; 422 + unchanged row + no insert + superuser also refused + plain create still 201 | -| `modules/cabana/action_extensions_postgres_test.go` | TestActionExtensionOutranked | ✓ VERIFIED | 403 + Run not called + covered target still runs | -| `scripts/check-phase19.6.1.sh` | both new names in DOCKER_TESTS, not SHORT_TESTS | ✓ VERIFIED | TestRolesCodeDeclaredCodeRefused, TestActionExtensionOutranked | -| Plan 03 harness and named rule tests | still present | ✓ VERIFIED | no rename, no skip | +| `admin/src/components/form/formContext.ts` | FORM_REMEMBER_LABEL injection key | ✓ VERIFIED | lines 174–180; typed `InjectionKey<(field, option) => void>`; missing provider is a no-op | +| `admin/src/views/FormView.vue` | rememberRelationLabel + provide | ✓ VERIFIED | helper at 251–261 merges by `option.value`; `provide(FORM_REMEMBER_LABEL, rememberRelationLabel)` at 352; `:labels="labels"` on FormGrid at 655 | +| `admin/src/components/form/fields/RelationField.vue` | choose() remembers before emit | ✓ VERIFIED | inject at 26; `rememberLabel?.(...)` inside `choose()` at 231–236 before `update:modelValue` | +| `admin/src/components/relation/RelationChildModal.vue` | same helper and provide | ✓ VERIFIED | helper at 218–228; provide at 229 | +| `admin/tests/form/FormView.test.ts` | tab-switch backstop | ✓ VERIFIED | `keeps a picked relation label after switching tabs` at 944; this-run PASS | +| `admin/tests/form/RelationField.test.ts` | remount backstop | ✓ VERIFIED | `shows a picked label after remount when the form remembered it` at 249; this-run PASS | +| `modules/boardwalk/dist` | remount-safe embed | ✓ VERIFIED | directory; `index.html` + `index-BtP5LUQA.js` with `summer.form.rememberLabel` | +| Prior cabana/pact/YAML/SPA artifacts from plans 01–04 | still present | ✓ VERIFIED | existence check this run; 16/16 prior paths OK | -`msd_run query verify.artifacts` on 19.6.1-04-PLAN.md: 6/6 passed. +`msd_run query verify.artifacts` on 19.6.1-05-PLAN.md: 5/6 passed (dist directory flagged "Not a file"; contents verified by hand). ### Key Link Verification | From | To | Via | Status | Details | |------|----|-----|--------|---------| -| http.go | action_extensions.go | `compileActionExtensions(reg, plugins)` before compileContributions | ✓ WIRED | http.go:137 | -| http.go | backend_screens.go | `withBackendScreens(` after `len(items)==0` return | ✓ WIRED | | -| backend_users_admin.go | backend_privileges.go | `guardLastSuperuser(` on update, deactivate, delete | ✓ WIRED | | -| crud.go | backend_users_admin.go | `extendRecordData(` in projectFullRecord | ✓ WIRED | crud.go:692 | -| http.go | backend_users_admin.go | `hiddenSchemaFields(` for form and list schema | ✓ WIRED | | -| crud.go | field_permission.go | `AdminPermissionRecordLocks(` | ✓ WIRED | | -| FormView.vue | trigger.ts | `applyTriggers(` | ✓ WIRED | | -| formState.ts | control.ts | `permissionValues(` with record-locked codes | ✓ WIRED | | -| user/fields.yaml | field_trigger.go | `field: is_superuser` | ✓ WIRED | | -| lifecycle test | export_test.go | SuperuserLockKey | ✓ WIRED | | -| backend_roles_admin.go | registry.go | both Roles hooks consult `rolePermissions(` of the submitted code | ✓ WIRED | refuseCodeDeclaredCode | -| action_extensions.go | backend_privileges.go | wrap calls `guardOutranked(` before contributor Run | ✓ WIRED | was NOT WIRED (WR-03); now compiled in | -| check-phase19.6.1.sh | roles_postgres_test.go | DOCKER_TESTS requires TestRolesCodeDeclaredCodeRefused | ✓ WIRED | | -| check-phase19.6.1.sh | action_extensions_postgres_test.go | DOCKER_TESTS requires TestActionExtensionOutranked | ✓ WIRED | | +| RelationField.vue | formContext.ts | choose injects FORM_REMEMBER_LABEL and writes the picked option before update:modelValue | ✓ WIRED | pattern found; write is before emit | +| FormView.vue | RelationField.vue | remembered labels flow back as props.labels so remount's watch seeds known | ✓ WIRED | `rememberRelationLabel` mutates `labels`; FormGrid `:labels="labels"`; RelationField watch immediate | +| FormView.vue | FormGrid | single panel, inactive tabs unmounted | ✓ WIRED | `panelFields` filters by active tab; `v-show` count 0 | +| RelationChildModal.vue | formContext.ts | child host provides FORM_REMEMBER_LABEL against its own labels ref | ✓ WIRED | provide at 229 | +| Prior links (http.go compileActionExtensions, refuseCodeDeclaredCode, wrapAdministratorsContributedRun, applyTriggers) | — | — | ✓ WIRED | files unchanged this wave; symbols still present | -`msd_run query verify.key-links` on 19.6.1-04-PLAN.md: 4/4 verified. +`msd_run query verify.key-links` on 19.6.1-05-PLAN.md: 2/2 verified. ### Data-Flow Trace (Level 4) | Artifact | Data Variable | Source | Produces Real Data | Status | |----------|---------------|--------|--------------------|--------| -| Administrators list/show | backend_users rows | GORM via cabana CRUD + List/FormExtendQuery | yes | ✓ FLOWING | -| Roles list/show | backend_user_roles rows | GORM via cabana CRUD | yes | ✓ FLOWING | -| permissionLocks | rolePermissions(code) / stored overrides | Registry + record JSON | yes | ✓ FLOWING | -| Contributed Run | locked *BackendUser | CRUDService.RecordAction loadRecord then wrap | yes; wrap reads in.Record | ✓ FLOWING | -| refuseCodeDeclaredCode | submitted code | fillable role.Code vs reg.rolePermissions | yes | ✓ FLOWING | +| RelationField combobox | `known[id]` / `labelOf` | `props.labels` (server `meta.labels` plus FORM_REMEMBER_LABEL writes) and options pages | yes — picked `{value,label}` from the options list the admin already saw | ✓ FLOWING | +| FormView `labels` | `labels.value[field]` | `adopt()` from `record.meta.labels`; `rememberRelationLabel` merge on pick | yes — replace-or-append by id; adopt still overwrites on load/save | ✓ FLOWING | +| Administrators role field | role id | cabana relation options + user/fields.yaml `type: relation` on Account tab | yes — same RelationField path; Permissions tab remounts Account | ✓ FLOWING | +| Prior list/show/permissionLocks/contributed Run | — | unchanged this wave | yes | ✓ FLOWING | ### Behavioral Spot-Checks | Behavior | Command | Result | Status | |----------|---------|--------|--------| -| CR-01 closed: rename/create onto a code-declared code | `go test ./modules/cabana -run '^TestRolesCodeDeclaredCodeRefused$'` (this run) | --- PASS | ✓ PASS | -| WR-03 closed: contributed action on outranking target | `go test ./modules/cabana -run '^TestActionExtensionOutranked$'` (this run) | --- PASS | ✓ PASS | -| Roles and seam regression | `go test ./modules/cabana -run '^(TestRolesLifecycle\|TestRolesPermissionCeiling\|TestActionExtensionRun)$'` (this run) | 3/3 PASS | ✓ PASS | -| Phase docker gate | `bash scripts/check-phase19.6.1.sh --docker` (this run) | exit 0; 23 cabana named + 10 SC-1 + 162 surf | ✓ PASS | -| Docs | `go test ./cmd/summer -run TestDocsTree`; `summer docs:build --check` (this run) | PASS / clean | ✓ PASS | -| Short regression | `go vet ./...`; `go test -short ./...`; `go build ./...` (this run) | PASS | ✓ PASS | +| T2 tab-switch label | `npm --prefix admin run test -- tests/form/RelationField.test.ts tests/form/FormView.test.ts -t "keeps a picked relation label after switching tabs\|shows a picked label after remount when the form remembered it"` | 2 passed / 65 skipped | ✓ PASS | +| Dist carries remember inject | python count of `summer.form.rememberLabel` in `index-BtP5LUQA.js` | 1 | ✓ PASS | +| Inactive tabs stay unmounted | `grep -c v-show admin/src/views/FormView.vue` | 0 | ✓ PASS | +| Plans 01–04 / Go / pact / YAML / OpenAPI / npm lock untouched | `git log --since=2026-10-22T11:46:44Z` on those paths | empty | ✓ PASS | +| Prior privilege wrap still compiled | `grep -c wrapAdministratorsContributedRun` / `func guardOutranked` | 3 / 1 | ✓ PASS | + +Full-suite `go test ./...` was not re-run (prior post-merge pass; this wave is SPA-only). `check-admin-dist.sh` was not re-run (rebuild, over 10s). Dist symbol + `51e1a1e` stand in. ### Probe Execution -No `scripts/*/tests/probe-*.sh` is declared for this phase. The fail-closed phase gate is `scripts/check-phase19.6.1.sh`; `--docker` passed in this run (see spot-checks). +No `scripts/*/tests/probe-*.sh` is declared for this phase. The fail-closed phase gate is `scripts/check-phase19.6.1.sh` (not re-run; no Go change). | Probe | Command | Result | Status | |-------|---------|--------|--------| @@ -256,59 +246,50 @@ No `scripts/*/tests/probe-*.sh` is declared for this phase. The fail-closed phas ### Requirements Coverage -REQUIREMENTS.md maps no IDs to 19.6.1 (`phase_req_ids` null; no orphaned requirements). Plan `requirements` fields use CONTEXT IDs (SC-*, D-*). Every ID declared across plans 01–04 is accounted for: +REQUIREMENTS.md maps no IDs to 19.6.1 (`phase_req_ids` null; no orphaned requirements). Plan 05 `requirements: []`. Every ID declared across plans 01–04 is accounted for (unchanged from the prior pass): | Requirement | Source Plan | Description | Status | Evidence | |-------------|-------------|-------------|--------|----------| -| SC-1 | 01, 02, 03 | list/create/edit/deactivate; roles editor; payloads and admin:* unchanged | ✓ SATISFIED | gate sc1 + UAT rows 1, 3–5, 7 | -| SC-2 | 01, 02, 03, 04 | superuser-only flag; cannot self-raise; last superuser | ✓ SATISFIED | was BLOCKED (CR-01); refuseCodeDeclaredCode + TestRolesCodeDeclaredCodeRefused | -| SC-3 | 01, 03 | seam + privilege tests | ✓ SATISFIED | TestActionExtension* | -| D-01..D-06, D-08..D-15, D-17, D-18, D-20..D-22 | 01/02/03 | as in CONTEXT.md | ✓ SATISFIED | prior pass + regression | -| D-07 | 01, 03, 04 | ceiling including Roles code path | ✓ SATISFIED | was BLOCKED (CR-01); now refused | -| D-16 | 01, 02, 03, 04 | system role code locked; never created through the screen | ✓ SATISFIED | stricter reading now enforced for submitted codes | -| D-19 | 01, 03, 04 | no outranking, contributed Runs included | ✓ SATISFIED | was PARTIAL (WR-03); wrap + TestActionExtensionOutranked | +| SC-1 | 01, 02, 03 | list/create/edit/deactivate; roles editor; payloads and admin:* unchanged | ✓ SATISFIED | prior + this-wave git empty on command/payload files | +| SC-2 | 01, 02, 03, 04 | superuser-only flag; cannot self-raise; last superuser | ✓ SATISFIED | prior | +| SC-3 | 01, 03 | seam + privilege tests | ✓ SATISFIED | prior | +| D-01..D-22 | 01/02/03/04 | CONTEXT.md decisions | ✓ SATISFIED | 22/22 decision coverage; no this-wave Go change | +| G-19.6.1-T2 | 05 (UAT gap) | picked relation label survives tab remount | ✓ SATISFIED (code); human UAT row 2 not re-run | vitest + inject wiring | ### Decision Coverage All trackable CONTEXT.md decisions are honored by shipped artifacts (22/22, `check.decision-coverage-verify`). Non-blocking. -### Prohibitions (judgment tier, verdicts not authoritative, flagged for human review) +### Prohibitions -| Plan | Prohibition | LLM-judge verdict | -|------|-------------|-------------------| -| 01 | Existing payloads unchanged; commands.go/token_commands.go/account.go not modified | holds (gate sc1; plan 04 git log on those files empty) | -| 01 | No hard delete of backend users | holds | -| 01 | Neither code granted to any role in code | holds | -| 01 | No invite/reset-link/force-change/impersonation/groups/per-row menu/nav item | holds | -| 01 | No consuming app named; no new dependency; goccy/go-yaml | holds | -| 02 | SPA never the enforcement point | holds | -| 02 | No new npm dependency/token/icon/.vue file | holds this wave (plan 04 did not touch admin/) | -| 02 | Notes never v-html | holds | -| 02 | Payloads of other controllers unchanged | holds (omitempty) | -| 03 | No production change except test-proven fixes | holds for plan 03; plan 04 is an approved gap-closure | -| 03 | Tests seed and clean their own rows | holds (new tests use stamp + t.Cleanup) | -| 03 | Docker-unrunnable test reported NOT RUN | holds | -| 04 | Do not take the weaker position that contributors own authorization; wrap in cabana; do not export guardOutranked | holds (`func wrapAdministratorsContributedRun`; `func guardOutranked` still unexported; docs say cabana applies the check) | -| 04 | Do not edit 19.6.1-01/02/03-PLAN.md; no SPA, phrasebook, commands.go, token_commands.go, or account.go | holds (git log since prior verified empty for those plans and command files) | -| 04 | Framework code, READMEs, docs and tests name no consuming application; no new dependency; goccy/go-yaml | holds (name grep on the plan 04 files is 0) | +Plan 05 prohibitions are grep-tier (verified this run). Plans 01–04 judgment-tier items were human-passed as UAT row 26. + +| Plan | Prohibition | Verdict | +|------|-------------|---------| +| 05 | Do not keep inactive tab panels mounted with v-show | holds (`v-show` count 0; `panelFields` still filters) | +| 05 | Do not edit 19.6.1-01/02/03/04-PLAN.md; no cabana Go, pact, phrasebook, YAML, OpenAPI; no new npm dependency | holds (git log since prior `verified:` empty on those paths and `admin/package.json`) | +| 05 | Framework code, READMEs, docs and tests name no consuming application | holds (fonoteka/plytarium/grzyby/wavepath count 0 on the six plan-05 source/test files) | +| 01–04 | judgment-tier set from the prior report | human-passed (UAT row 26) | ### Test Quality Audit | Test File | Linked Req | Active | Skipped | Circular | Assertion Level | Verdict | |-----------|-----------|--------|---------|----------|-----------------|---------| -| roles_postgres_test.go#TestRolesCodeDeclaredCodeRefused | CR-01 / D-16 / D-07 / SC-2 | yes | 0 | no | behavioral (422, enCodeLocked, row snapshot, no insert, superuser too, plain create 201) | sufficient | -| action_extensions_postgres_test.go#TestActionExtensionOutranked | WR-03 / D-19 | yes | 0 | no | behavioral (403 enOutranked, spy.runs empty, deactivate still 403, covered 200 + tx) | sufficient | -| Plan 03 named rule tests | D-03..D-21 | yes | 0 | no | behavioral | sufficient (prior pass) | +| FormView.test.ts#keeps a picked relation label after switching tabs | G-19.6.1-T2 / T2-1 | yes | 0 | no | behavioral (pick Acme Works, Details tab, back, combobox is name not `1`) | sufficient | +| RelationField.test.ts#shows a picked label after remount when the form remembered it | T2-2 | yes | 0 | no | behavioral (choose writes bag, remount with bag labels, value is name not `1`) | sufficient | +| Plan 03/04 named rule tests | D-03..D-21, CR-01, WR-03 | yes | 0 | no | behavioral | sufficient (prior pass; files unchanged) | -**Disabled tests on requirements:** 0 +**Disabled tests on requirements:** 0 (`it.skip` / `test.todo` none in the two plan-05 test files) **Circular patterns detected:** 0 **Insufficient assertions:** 0 +The FormView case uses the widgets fixture (`maker` on default, `serial` on Details). That is the same one-panel remount path the Administrators form uses (`role` on Account, permissions on Permissions). Fixture supplies the same shape the real form has — not coincidental-reliance. + ### Anti-Patterns Found | File | Line | Pattern | Severity | Impact | |------|------|---------|----------|--------| -| modules/cabana/backend_users_admin.go, backend_privileges.go | — | Ceilings use the owner's full grants under a scoped personal token (WR-01) | ⚠️ Warning | Pre-gap-closure; files not modified in plan 04; not a carried-forward gap | +| modules/cabana/backend_users_admin.go, backend_privileges.go | — | Ceilings use the owner's full grants under a scoped personal token (WR-01) | ⚠️ Warning | Pre-gap-closure; files not modified in plan 05 | | modules/cabana/backend_users_admin.go | — | Stored override values other than 1/-1 dropped on save (WR-02) | ⚠️ Warning | Same as prior pass | | modules/cabana/backend_privileges.go | — | Last-superuser count relies on READ COMMITTED (IN-01) | ℹ️ Info | | | modules/cabana/backend_users_admin.go / crud.go | — | Re-delete of a trashed admin reports success, bumps tokens (IN-02) | ℹ️ Info | | @@ -316,45 +297,31 @@ All trackable CONTEXT.md decisions are honored by shipped artifacts (22/22, `che | modules/cabana/backend_users_admin.go | — | toggleOn reads unknown encodings as off (IN-05) | ℹ️ Info | | | modules/cabana/backend_users_admin.go | — | Own role picker editable, refused only on save (IN-06) | ℹ️ Info | UX | -No TBD/FIXME/XXX/TODO/HACK markers in the files plan 04 modified. No consuming-application names in those files. +No TBD/FIXME/XXX/TODO/HACK markers in the files plan 05 modified. No consuming-application names. Code review of plan 05 was clean (`587f9dd` / 19.6.1-REVIEW.md status clean). ### Human Verification Required -Recorded UAT (`19.6.1-UAT.md`, `recorded_at` 2026-10-22T11:17:34.000Z, status complete, 24 pass / 1 issue) settled the prior superuser and limited-admin walk-throughs except as listed. `changed_since_uat`: `backend_roles_admin.go`, `action_extensions.go`, `pact/capabilities.go`. Action-extension and pact comment changes do not bear on the visual rows that passed. The Roles save hooks do bear on UAT row 6. +Recorded UAT (`19.6.1-UAT.md`, `recorded_at` 2026-10-22T12:30:13.000Z, status complete, 25 pass / 1 issue) still has test 2 as `issue`. `changed_since_uat`: `RelationField.vue`, `formContext.ts`, `RelationChildModal.vue`, `FormView.vue`, `modules/boardwalk/dist` — those files are exactly the T2 remount fix and they bear on test 2. + +Roles (row 6) stays settled: the Roles form has no relation field (name/code/description/permissioneditor only). Plan 05's remember-label path is unused there. `backend_roles_admin.go` is not in `changed_since_uat` for this recorded_at. Judgment-tier prohibitions (row 26) passed and plan 05's new prohibitions are grep-tier, verified above. + +The plan-02 harvested `` (superuser switch / locked rows) is UAT rows 3 and 5 — already passed. ### 1. Create an administrator **Test:** Create an administrator -**Expected:** Create opens the form with login, email, name, password + confirmation, role, a Superuser switch and Permission overrides. Saving with a mismatched or 3-character password shows a field error. Saving a valid admin (role e.g. developer, Superuser off) lands on the record and shows a success toast. -**Why human:** UAT row 2 is already an issue: after picking Developer and switching to Permissions and back, the dropdown shows `1` (role id) instead of the label. Save still assigns Developer. Diagnosed as generic RelationField remount / label-cache loss. Not a gap-closure item and not a failed must-have. +**Expected:** Create opens the form with login, email, name, password + confirmation, role, a Superuser switch and Permission overrides. Saving with a mismatched or 3-character password shows a field error. Saving a valid admin (role e.g. developer, Superuser off) lands on the record and shows a success toast. After picking Developer and switching to Permissions and back, the role dropdown still shows Developer, not the raw id. +**Why human:** UAT row 2 is still `issue`. Plan 05 closed the remount path in code and in vitest (widgets `maker`/`serial`). The human has not re-run the Administrators form. Do not invent a UAT pass. -### 2. Roles screen - -**Test:** Roles screen -**Expected:** Roles lists roles; create a role with a code, name and permissions via the permission editor, then save it. Code-declared grants on system roles show as locked rows with a note. Deleting a role still held by an admin is refused with a toast that names the role and the count; deleting a system role is refused; a system role's code is not editable. -**Why human:** Visual SPA check. The recorded pass predates `refuseCodeDeclaredCode`. Server create of a unique non-declared code still returns 201 (TestRolesCodeDeclaredCodeRefused), but Step 8b requires a retest when a changed file bears on the row. -**Retest reason:** `modules/cabana/backend_roles_admin.go` changed after the UAT pass (2026-10-22T11:17:34.000Z) - -### 3. Prohibition review - -**Test:** Review the judgment-tier prohibitions in Plans 01-04 (listed under Prohibitions in the report) and confirm or reject the LLM-judge verdicts -**Expected:** Each prohibition confirmed as held -**Why human:** unverified-prohibition — human review recommended (judgment tier) - -UAT row 8's note that CR-01 was a known open gap is stale: that hole is closed on the server. Do not fail the limited-admin visual re-check on CR-01. +UAT row 8's note that CR-01 is a known open gap is stale: that hole is closed on the server (plan 04). Do not fail the limited-admin visual re-check on CR-01. ### Gaps Summary -**No blocking gaps.** Both prior `gaps:` items are closed: +**No blocking gaps.** G-19.6.1-T2 is closed in the SPA and the embedded dist. The remaining item is a recorded UAT issue the human has not re-checked after plan 05. That is `human_needed`, not `gaps_found`. -1. **G-19.6.1-1 / CR-01 (SC-2, D-07, D-16).** `refuseCodeDeclaredCode` refuses a submitted code when `len(reg.rolePermissions(submitted)) > 0` and it is not the stored code. Create and update, including by a superuser. 422 `user.role.code_locked`. Proven by TestRolesCodeDeclaredCodeRefused. -2. **G-19.6.1-2 / WR-03 (D-19).** `wrapAdministratorsContributedRun` runs `guardOutranked` inside the action transaction before the contributor's Run. `guardOutranked` is not exported. Proven by TestActionExtensionOutranked. - -Neither item is covered by a later phase as leftover work: 19.7 consumes the seam and now inherits the wrap. - -The remaining human items are a recorded UAT labeling issue (not a must-have), a Roles-screen retest after the hook change, and judgment-tier prohibition review. +Later phases do not cover this UAT row: 19.7 consumes the Administrators action seam; 19.8 is console; 20 is the demo instance. Do not defer the browser re-check. --- -_Verified: 2026-10-22T11:46:44Z_ +_Verified: 2026-10-22T12:58:00Z_ _Verifier: Claude (msd-verifier)_