docs(09): verification passed
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
phase: 09-backend-admin-authentication-and-schema-pipeline
|
phase: 09-backend-admin-authentication-and-schema-pipeline
|
||||||
verified: 2026-10-01T19:47:49Z
|
verified: 2026-10-01T21:17:13Z
|
||||||
status: human_needed
|
status: passed
|
||||||
score: 5/5 roadmap success criteria verified (plan truths 54/54 verified, including 3 backstop truths with direct test evidence)
|
score: 5/5 roadmap success criteria verified (plan truths 54/54 verified, including 3 backstop truths with direct test evidence)
|
||||||
covered_files:
|
covered_files:
|
||||||
- ".planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-01-PLAN.md"
|
- ".planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-01-PLAN.md"
|
||||||
@@ -89,37 +89,33 @@ covered_files:
|
|||||||
- "modules/phrasebook/translator.go"
|
- "modules/phrasebook/translator.go"
|
||||||
- "modules/surf/router.go"
|
- "modules/surf/router.go"
|
||||||
- "scripts/check-phase9.sh"
|
- "scripts/check-phase9.sh"
|
||||||
covered_digest: "v2:sha256:915c2ad92c7250d07cf220cb1e97ef419209530f5838654dfa0b93b361dd3599"
|
covered_digest: "v2:sha256:23346bc11e5eaa8e8dd1d27c7eb748e4d2628d4c1c0ae8bbe32057696d375222"
|
||||||
covered_files_note: "Current root-relative paths (framework packages live under modules/ since Phase 10.2 commit 5e50b16). The code-review fix run (1a878b3..2ecc85e) added modules/bouncer/{refresh_test,registry,registry_test}.go and modules/cabana/{auth_internal_test,backend_guard_collision_test,model_fields,model_fields_test,permissions_test,tx_context}.go as Phase 9 evidence; relation_field.go and relation_field_test.go are covered because WR-03/WR-16 changed them. fonoteka.go files are outside the project root and cannot be fingerprinted; they are listed under Required Artifacts and were checked at fonoteka.go HEAD e62f4fc (clean working tree). summercms.go was checked at HEAD 2ecc85e."
|
covered_files_note: "Current root-relative paths (framework packages live under modules/ since Phase 10.2 commit 5e50b16). The code-review fix run (1a878b3..2ecc85e) added modules/bouncer/{refresh_test,registry,registry_test}.go and modules/cabana/{auth_internal_test,backend_guard_collision_test,model_fields,model_fields_test,permissions_test,tx_context}.go as Phase 9 evidence; relation_field.go and relation_field_test.go are covered because WR-03/WR-16 changed them. modules/lagoon/backend_admin_migrations.go and its test carry the WR-11 index added in 2da8112. fonoteka.go files are outside the project root and cannot be fingerprinted; they are listed under Required Artifacts and were checked at fonoteka.go HEAD e62f4fc (clean working tree). summercms.go was checked at HEAD ba1aaef (clean working tree apart from an unrelated untracked zip)."
|
||||||
behavior_unverified: 0
|
behavior_unverified: 0
|
||||||
overrides_applied: 0
|
overrides_applied: 0
|
||||||
mvp_mode_note: "ROADMAP marks Phase 9 mode: mvp, but the ROADMAP goal is not a User Story. Every Phase 9 PLAN carries a valid User Story, used for User Flow Coverage. As in the Phase 1/3/5/8/10 reports, the five ROADMAP success criteria are the contract."
|
mvp_mode_note: "ROADMAP marks Phase 9 mode: mvp, but the ROADMAP goal is not a User Story. Every Phase 9 PLAN carries a valid User Story, used for User Flow Coverage. As in the Phase 1/3/5/8/10 reports, the five ROADMAP success criteria are the contract."
|
||||||
re_verification:
|
re_verification:
|
||||||
previous_status: human_needed
|
previous_status: human_needed
|
||||||
previous_score: 5/5
|
previous_score: 5/5
|
||||||
reason: "Covered files changed in the Phase 9 code-review fix run (summercms.go 1a878b3..2ecc85e: 19 fix commits plus docs 9d2128a, 2ecc85e; fonoteka.go b925dd6, c45fb99, e62f4fc)."
|
reason: "Covered files changed after the user's 2026-10-01 decisions: summercms.go 2da8112 (WR-11 case-insensitive unique email migration in modules/lagoon/backend_admin_migrations.go and its test); ba1aaef recorded the decisions in 09-REVIEW-FIX.md and closed 09-UAT.md."
|
||||||
gaps_closed: []
|
gaps_closed: []
|
||||||
gaps_remaining: []
|
gaps_remaining: []
|
||||||
regressions: []
|
regressions: []
|
||||||
human_items_resolved:
|
human_items_resolved:
|
||||||
- "Code-review triage: 09-REVIEW-DISPOSITION.md records CR-01 and WR-01..WR-19 as fixed (12 Info findings stay open, out of scope). The fixes are in the code at HEAD and each has a named test that passes (see Behavioral Spot-Checks). Three decisions from 09-REVIEW-FIX.md remain and are human item 1."
|
- "Code-review triage: 09-REVIEW-DISPOSITION.md records CR-01 and WR-01..WR-19 as fixed (12 Info findings stay open, out of scope). Each fix is in the code at HEAD with a passing named test."
|
||||||
- "Prohibition 09-11 #1 (navigation must not reveal an inaccessible entry's target, WR-02): now not violated. Metadata drops a main item the principal may not open and repoints an allowed parent to its first openable child (TestNavigationDropsDeniedParentAndRepointsTarget PASS)."
|
- "WR-11 decision (user, 2026-10-01): index added. Migration 202610010001_backend_users_email_ci_unique creates backend_users_email_lower_unique on lower(email), refuses to run on case-duplicate emails and names the logins; rollback drops the index. TestBackendAdminEmailCaseInsensitiveUnique and TestBackendAdminEmailIndexRefusesCaseDuplicates PASS."
|
||||||
- "Prohibition 09-12 #1 (acceptance must not depend on zero-test matches, WR-18): now not violated. phase9_detect judges zero tests per package; --self-test refuses a run in which one package has no passing test ('refuse: zero tests in a/cabana')."
|
- "WR-03 decision (user, 2026-10-01): single-record delete kept as Winter (allowed whenever a form exists). Code matches: operationDeclared 'delete' requires a form only; TestCRUDOperationsFollowDeclarations PASS."
|
||||||
human_verification:
|
- "WR-17 decision (user, 2026-10-01): exact-code deny does not remove a wildcard grant, as Winter's getMergedPermissions. Code matches applyUserPermissions; TestBackendUserPermissionsOverrideRole PASS."
|
||||||
- test: "Decide the three open choices recorded under 'Decisions needed' in 09-REVIEW-FIX.md"
|
- "Judgment-tier prohibitions (user, 2026-10-01): all 24 verdicts accepted; 09-03 #1 confirmed as superseded by Phase 10.1 D-09. 09-11 #1 (WR-02) and 09-12 #1 (WR-18) are not violated on code and test evidence."
|
||||||
expected: "(a) WR-11: whether to add a unique index on lower(backend_users.email), and whether copied Winter rows are deduplicated first (login-time ambiguity is already refused and admin:create already blocks collisions). (b) WR-03: whether single-record delete stays allowed whenever a form exists (as now, matching Winter's form-screen delete button) or must also follow the list toolbar's `delete`, which needs a new form-schema field and an admin API/OpenAPI change. (c) WR-17: whether an exact-code deny in backend_users.permissions should also remove a role's wildcard grant (stricter than Winter's getMergedPermissions, which the current code follows)."
|
- "09-UAT.md: status complete, 3/3 pass (ba1aaef)."
|
||||||
why_human: "Each is a policy or scope choice against the Winter-parity rule, not a defect the verifier can decide. None defeats a ROADMAP success criterion."
|
|
||||||
- test: "Review the 24 judgment-tier prohibitions (verdicts in the Prohibitions table)"
|
|
||||||
expected: "Accept or reject the verifier's non-authoritative verdicts. One still needs attention: 09-03 #1 (form compiler must not accept `type: partial`), which Phase 10.1 D-09 deliberately superseded with a bare-name, sanitized html/template partial; the legacy path-less Winter partial is still refused. The previously qualified 09-11 #1 (WR-02) and 09-12 #1 (WR-18) are now not violated."
|
|
||||||
why_human: "Judgment-tier prohibitions need human resolution"
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Phase 9: Backend admin authentication and schema pipeline. Verification Report
|
# Phase 9: Backend admin authentication and schema pipeline. Verification Report
|
||||||
|
|
||||||
**Phase Goal:** Backend admin users with roles are separate from frontend users and gate navigation and controller access; `fields.yaml`/`columns.yaml` drive a JSON form/list schema, including a first-class relation-manager schema replacing the one `partial` field.
|
**Phase Goal:** Backend admin users with roles are separate from frontend users and gate navigation and controller access; `fields.yaml`/`columns.yaml` drive a JSON form/list schema, including a first-class relation-manager schema replacing the one `partial` field.
|
||||||
**Verified:** 2026-10-01T19:47:49Z (summercms.go HEAD 2ecc85e, fonoteka.go HEAD e62f4fc)
|
**Verified:** 2026-10-01T21:17:13Z (summercms.go HEAD ba1aaef, fonoteka.go HEAD e62f4fc)
|
||||||
**Status:** human_needed
|
**Status:** passed
|
||||||
**Re-verification:** Yes. The 2026-10-01T18:35Z report went stale when the Phase 9 code-review fixes changed covered files. Every truth was re-checked at HEAD. The covered-file list was rebuilt at current paths and extended with the files the fixes created.
|
**Re-verification:** Yes (final). The 2026-10-01T19:47Z report went stale when the WR-11 index (2da8112) changed `modules/lagoon/backend_admin_migrations.go` and its test. Every truth was re-checked at HEAD, the covered-file list was rebuilt at current paths, and the human items are resolved by the user's recorded decisions (09-REVIEW-FIX.md "Decisions", 09-UAT.md complete 3/3, ba1aaef).
|
||||||
|
|
||||||
**MVP note:** ROADMAP marks this phase `mode: mvp`, but its goal is not a User Story. The PLAN files carry a valid one, which is used below. The five ROADMAP success criteria are the contract, and the plan `must_haves` are supporting evidence.
|
**MVP note:** ROADMAP marks this phase `mode: mvp`, but its goal is not a User Story. The PLAN files carry a valid one, which is used below. The five ROADMAP success criteria are the contract, and the plan `must_haves` are supporting evidence.
|
||||||
|
|
||||||
@@ -132,6 +128,7 @@ human_verification:
|
|||||||
- **Schema/query (WR-06 to WR-09, WR-16):** relation default sort is the first sortable column; relation column order comes from an ordered decode, not indentation; list search uses `LOWER(CAST(col AS TEXT))` (`query.go:296`); the scaffold declares a required permission and a record source; reflection helpers see embedded structs and explicit `column:` tags (`model_fields.go`).
|
- **Schema/query (WR-06 to WR-09, WR-16):** relation default sort is the first sortable column; relation column order comes from an ordered decode, not indentation; list search uses `LOWER(CAST(col AS TEXT))` (`query.go:296`); the scaffold declares a required permission and a record source; reflection helpers see embedded structs and explicit `column:` tags (`model_fields.go`).
|
||||||
- **Gate and transactions (WR-18, WR-19):** `check-phase9.sh` judges zero tests per package; hooks and scopes read the write transaction through `cabana.TxFromContext` (`tx_context.go:27`), used by Fonoteka's album and collection hooks.
|
- **Gate and transactions (WR-18, WR-19):** `check-phase9.sh` judges zero tests per package; hooks and scopes read the write transaction through `cabana.TxFromContext` (`tx_context.go:27`), used by Fonoteka's album and collection hooks.
|
||||||
- **Fonoteka (WR-15):** an admin editor link leaves `granted_by` NULL.
|
- **Fonoteka (WR-15):** an admin editor link leaves `granted_by` NULL.
|
||||||
|
- **WR-11 index (2da8112, user decision):** migration `202610010001_backend_users_email_ci_unique` (`modules/lagoon/backend_admin_migrations.go`) checks for emails that differ only in case, fails naming the clashing logins, otherwise creates `backend_users_email_lower_unique ON backend_users (lower(email))`; rollback drops it. Registered through `BackendAdminMigrations` (`migrations.go:74`, plugin id `summercms.cabana`).
|
||||||
|
|
||||||
## User Flow Coverage
|
## User Flow Coverage
|
||||||
|
|
||||||
@@ -152,7 +149,7 @@ User story (from every 09-*-PLAN.md): *As a backend administrator, I want to aut
|
|||||||
|
|
||||||
| # | Truth | Status | Evidence |
|
| # | Truth | Status | Evidence |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | A backend admin user with a role logs in separately from frontend users, and navigation/controller access is gated by the permissions registry. | ✓ VERIFIED | Separate Winter-shaped `backend_users`/`backend_user_roles` (`modules/lagoon/backend_admin_migrations.go`; `TestBackendAdmin*` PASS in the full run). Separate `backend` guard with its own secret and required audience; cross-audience tokens fail both ways (`TestPhase09GuardIsolation`, `TestAdminTracerAuthBoundary`: PASS); a foreign guard fails boot (`TestActivateRefusesAForeignBackendGuard`: PASS). Grants = role JSON + `HasPermissions` role assignments + user-level overlay (`auth.go:39-77`; `TestBackendUserPermissionsOverrideRole`: PASS on PostgreSQL). Every controller, relation and CRUD route goes through `protect` (`http.go:780`), settings through `protectSetting` (line 387). Permission matching follows Winter's `hasAnyAccess` (`TestAllowsFollowsWinterHasAnyAccess`, 17 cases: PASS). Navigation and settings filter by the same `Allows`; denied parents are dropped and targets repointed (`TestNavigationDropsDeniedParentAndRepointsTarget`: PASS). Also `TestPhase09PermissionMatrix`, `TestPhase09SecurityMatrix`, `TestAdminMetadataFiltering`, `TestAdminMetadataRejectsFrontendPrincipal`: PASS. The previous WR-01/WR-02/WR-17 caveats are closed in code; WR-17's wildcard-deny nuance is a human decision. |
|
| 1 | A backend admin user with a role logs in separately from frontend users, and navigation/controller access is gated by the permissions registry. | ✓ VERIFIED | Separate Winter-shaped `backend_users`/`backend_user_roles` (`modules/lagoon/backend_admin_migrations.go`; `TestBackendAdmin*` PASS in the full run). Separate `backend` guard with its own secret and required audience; cross-audience tokens fail both ways (`TestPhase09GuardIsolation`, `TestAdminTracerAuthBoundary`: PASS); a foreign guard fails boot (`TestActivateRefusesAForeignBackendGuard`: PASS). Grants = role JSON + `HasPermissions` role assignments + user-level overlay (`auth.go:39-77`; `TestBackendUserPermissionsOverrideRole`: PASS on PostgreSQL). Every controller, relation and CRUD route goes through `protect` (`http.go:780`), settings through `protectSetting` (line 387). Permission matching follows Winter's `hasAnyAccess` (`TestAllowsFollowsWinterHasAnyAccess`, 17 cases: PASS). Navigation and settings filter by the same `Allows`; denied parents are dropped and targets repointed (`TestNavigationDropsDeniedParentAndRepointsTarget`: PASS). Also `TestPhase09PermissionMatrix`, `TestPhase09SecurityMatrix`, `TestAdminMetadataFiltering`, `TestAdminMetadataRejectsFrontendPrincipal`: PASS. The previous WR-01/WR-02/WR-17 caveats are closed in code; WR-17's wildcard-deny behaviour was kept as Winter by user decision. |
|
||||||
| 2 | `fields.yaml` for a real controller parses (goccy/go-yaml) into a JSON form schema covering text, textarea, checkbox, switch, dropdown (model-method options) and relation (nameFrom, emptyOption), with span/tabs/context/attributes layout hints. | ✓ VERIFIED | `modules/cabana/form_schema.go` decodes with goccy/go-yaml and `yaml.DisallowUnknownField()` (line 286), walks the AST to keep order, rejects unknown keys and types. The real Fonoteka YAML covers every listed item. `required` now honours `context` (`TestCRUDRequiredFollowsContext`: PASS). Tests: `TestFormSchemaCompile`, `TestFormSchemaRejects`, `TestAlbumsAdminForm`, `TestAlbumsAdminDropdowns`, `TestStylesAdminForm`, `TestCollectionsAdminForm`: PASS. |
|
| 2 | `fields.yaml` for a real controller parses (goccy/go-yaml) into a JSON form schema covering text, textarea, checkbox, switch, dropdown (model-method options) and relation (nameFrom, emptyOption), with span/tabs/context/attributes layout hints. | ✓ VERIFIED | `modules/cabana/form_schema.go` decodes with goccy/go-yaml and `yaml.DisallowUnknownField()` (line 286), walks the AST to keep order, rejects unknown keys and types. The real Fonoteka YAML covers every listed item. `required` now honours `context` (`TestCRUDRequiredFollowsContext`: PASS). Tests: `TestFormSchemaCompile`, `TestFormSchemaRejects`, `TestAlbumsAdminForm`, `TestAlbumsAdminDropdowns`, `TestStylesAdminForm`, `TestCollectionsAdminForm`: PASS. |
|
||||||
| 3 | `columns.yaml` parses into a JSON list schema with searchable/sortable/relation columns and datetime/switch renderers. | ✓ VERIFIED | `modules/cabana/list_schema.go` column types `text`, `datetime`, `switch`; `ListColumn` carries `searchable`, `sortable`, `relation`, `select` (`schema_types.go:20-24`). Search/sort/filter resolve only through compiled allowlists with a PK tie-break; non-text searchable columns are cast to text (`query.go:296`; `TestListSearchNonTextColumns` on PostgreSQL: PASS). Tests: `TestListSchemaCompile`, `TestAlbumsAdminList`, `TestArtistsAdminList`, `TestCollectionsAdminListCRUD`, `TestGenresAdminEdges`: PASS. WR-08 caveat closed. |
|
| 3 | `columns.yaml` parses into a JSON list schema with searchable/sortable/relation columns and datetime/switch renderers. | ✓ VERIFIED | `modules/cabana/list_schema.go` column types `text`, `datetime`, `switch`; `ListColumn` carries `searchable`, `sortable`, `relation`, `select` (`schema_types.go:20-24`). Search/sort/filter resolve only through compiled allowlists with a PK tie-break; non-text searchable columns are cast to text (`query.go:296`; `TestListSearchNonTextColumns` on PostgreSQL: PASS). Tests: `TestListSchemaCompile`, `TestAlbumsAdminList`, `TestArtistsAdminList`, `TestCollectionsAdminListCRUD`, `TestGenresAdminEdges`: PASS. WR-08 caveat closed. |
|
||||||
| 4 | The relation-manager schema supports search/link/unlink/manage-or-view lists for Collections' editors tab, replacing the `partial` field entirely. | ✓ VERIFIED | `fonoteka.go/.../models/collection/fields.yaml:19` `editors: type: relation-manager`; no `partial` in the Fonoteka model YAML. `config_relation.yaml` has view/manage list columns, `toolbarButtons: link\|unlink`, `showSearch`. `modules/cabana/relation.go` serves schema, linked, candidates (`RelationExtendManageQuery` at line 473), link and unlink (`RelationBeforeLink` at line 671); link/unlink now require the panel's `toolbarButtons` (`TestRelationMutationsFollowToolbarButtons`: PASS); column order is indentation-independent (`TestRelationColumnOrderIsIndependentOfIndentation`: PASS); default sort is the first sortable column (`TestRelationDefaultSort`: PASS). Fonoteka: `TestCollectionsAdminRelation*`, `TestCollectionsAdminRejectsPartial` (legacy path-less partial fails boot), `TestRelationCandidateExclusions`: PASS. **Note:** Phase 10.1 D-09 lifted Phase 9's blanket `type: partial` boot error for a bare-name sanitized html/template partial (`compilePartialPath`, `form_schema.go:508`); the Collections editors tab is still a relation manager. WR-05, WR-07, WR-15 caveats closed. |
|
| 4 | The relation-manager schema supports search/link/unlink/manage-or-view lists for Collections' editors tab, replacing the `partial` field entirely. | ✓ VERIFIED | `fonoteka.go/.../models/collection/fields.yaml:19` `editors: type: relation-manager`; no `partial` in the Fonoteka model YAML. `config_relation.yaml` has view/manage list columns, `toolbarButtons: link\|unlink`, `showSearch`. `modules/cabana/relation.go` serves schema, linked, candidates (`RelationExtendManageQuery` at line 473), link and unlink (`RelationBeforeLink` at line 671); link/unlink now require the panel's `toolbarButtons` (`TestRelationMutationsFollowToolbarButtons`: PASS); column order is indentation-independent (`TestRelationColumnOrderIsIndependentOfIndentation`: PASS); default sort is the first sortable column (`TestRelationDefaultSort`: PASS). Fonoteka: `TestCollectionsAdminRelation*`, `TestCollectionsAdminRejectsPartial` (legacy path-less partial fails boot), `TestRelationCandidateExclusions`: PASS. **Note:** Phase 10.1 D-09 lifted Phase 9's blanket `type: partial` boot error for a bare-name sanitized html/template partial (`compilePartialPath`, `form_schema.go:508`); the Collections editors tab is still a relation manager. WR-05, WR-07, WR-15 caveats closed. |
|
||||||
@@ -170,7 +167,7 @@ Backstop (non-inferable) truths:
|
|||||||
|
|
||||||
| Truth | Evidence | Status |
|
| Truth | Evidence | Status |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 09-01: login accepts login or normalized email, one opaque failure, never falls back to a frontend user | `TestAdminAuthLifecycle`, `TestAdminTracerGenreList` (frontend user with the admin's email; frontend password rejected), `TestLoginAmbiguousIdentifier` (a cross-field collision now fails opaquely instead of taking the first match), `TestMissingUserHashUsesConfiguredCost`: PASS | VERIFIED (WR-11 caveat closed; index decision open) |
|
| 09-01: login accepts login or normalized email, one opaque failure, never falls back to a frontend user | `TestAdminAuthLifecycle`, `TestAdminTracerGenreList` (frontend user with the admin's email; frontend password rejected), `TestLoginAmbiguousIdentifier` (a cross-field collision now fails opaquely instead of taking the first match), `TestMissingUserHashUsesConfiguredCost`: PASS | VERIFIED (WR-11 closed; `lower(email)` unique index added, `TestBackendAdminEmailCaseInsensitiveUnique` PASS) |
|
||||||
| 09-11: missing settings GET is side-effect-free with `exists: false`; first PUT creates; identical PUT is idempotent | `TestAdminSettingsMissingRead`, `TestAdminSettingsCreate`, `TestAdminSettingsIdempotentUpdate`: PASS | VERIFIED |
|
| 09-11: missing settings GET is side-effect-free with `exists: false`; first PUT creates; identical PUT is idempotent | `TestAdminSettingsMissingRead`, `TestAdminSettingsCreate`, `TestAdminSettingsIdempotentUpdate`: PASS | VERIFIED |
|
||||||
| 09-12: AUTH-08 is the separate D-01/D-02 principal only; no shared role table or dual-purpose token | Separate tables, `Principal.Backend` flag, audience checks; `TestPhase09GuardIsolation`, `TestAdminMetadataRejectsFrontendPrincipal`, `TestVerifyRefreshableClaimsAudience` (refresh-window verification keeps the audience check): PASS | VERIFIED |
|
| 09-12: AUTH-08 is the separate D-01/D-02 principal only; no shared role table or dual-purpose token | Separate tables, `Principal.Backend` flag, audience checks; `TestPhase09GuardIsolation`, `TestAdminMetadataRejectsFrontendPrincipal`, `TestVerifyRefreshableClaimsAudience` (refresh-window verification keeps the audience check): PASS | VERIFIED |
|
||||||
|
|
||||||
@@ -184,7 +181,7 @@ Backstop (non-inferable) truths:
|
|||||||
| 02 | No boot, web-wizard or env-seeded first admin | not violated (migration seeds roles only; `admin:create` only) |
|
| 02 | No boot, web-wizard or env-seeded first admin | not violated (migration seeds roles only; `admin:create` only) |
|
||||||
| 02 | No cookie session store and no merge with the frontend user model | not violated |
|
| 02 | No cookie session store and no merge with the frontend user model | not violated |
|
||||||
| 02 | Auth logs carry no password, JWT, secret or hash | not violated (`TestAdminAuthLogging`, `TestPhase09SecurityCoverage`; the `--password` deprecation warning never echoes the value) |
|
| 02 | Auth logs carry no password, JWT, secret or hash | not violated (`TestAdminAuthLogging`, `TestPhase09SecurityCoverage`; the `--password` deprecation warning never echoes the value) |
|
||||||
| 03 | Form compiler must not accept `type: partial` | **superseded**: Phase 10.1 D-09 lifted this for a supported partial contract (bare-name `path`, html/template, allowlisted node renderer, D-18 sanitizer). The legacy Winter path-less partial is still refused (`TestCollectionsAdminRejectsPartial`). A recorded later decision, not a regression, but the Phase 9 text no longer holds literally. |
|
| 03 | Form compiler must not accept `type: partial` | **superseded (accepted by the user 2026-10-01)**: Phase 10.1 D-09 lifted this for a supported partial contract (bare-name `path`, html/template, allowlisted node renderer, D-18 sanitizer). The legacy Winter path-less partial is still refused (`TestCollectionsAdminRejectsPartial`). |
|
||||||
| 03 | Labels not deferred to the client or cached in the first request's locale | not violated (per-request `Localize`) |
|
| 03 | Labels not deferred to the client or cached in the first request's locale | not violated (per-request `Localize`) |
|
||||||
| 03 | Unknown keys, types or providers not silently ignored | not violated (`DisallowUnknownField`, `formFieldKeys`) |
|
| 03 | Unknown keys, types or providers not silently ignored | not violated (`DisallowUnknownField`, `formFieldKeys`) |
|
||||||
| 04 | YAML must not inject SQL or name an arbitrary method | not violated (`conditions:` rejected, finite `FilterScopes`; search cast is on an allowlisted, quoted column) |
|
| 04 | YAML must not inject SQL or name an arbitrary method | not violated (`conditions:` rejected, finite `FilterScopes`; search cast is on an allowlisted, quoted column) |
|
||||||
@@ -207,7 +204,7 @@ Backstop (non-inferable) truths:
|
|||||||
|
|
||||||
| Artifact | Expected | Status | Details |
|
| Artifact | Expected | Status | Details |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `modules/lagoon/backend_admin_migrations.go` | backend_users/roles, system-role seed | ✓ VERIFIED | Explicit SQL up/down; no lower(email) index (WR-11 decision open) |
|
| `modules/lagoon/backend_admin_migrations.go` | backend_users/roles, system-role seed, case-insensitive unique email | ✓ VERIFIED | Explicit SQL up/down; `TestBackendAdmin*` (6 incl. `EmailCaseInsensitiveUnique`, `EmailIndexRefusesCaseDuplicates`) PASS |
|
||||||
| `modules/cabana/auth.go` | backend provider, login/refresh/logout/me, user-level permission overlay | ✓ VERIFIED | `BackendUsers` reads only `backend_users` |
|
| `modules/cabana/auth.go` | backend provider, login/refresh/logout/me, user-level permission overlay | ✓ VERIFIED | `BackendUsers` reads only `backend_users` |
|
||||||
| `modules/cabana/commands.go` | admin:create, admin:reset-password | ✓ VERIFIED | prompt/stdin password, collision checks |
|
| `modules/cabana/commands.go` | admin:create, admin:reset-password | ✓ VERIFIED | prompt/stdin password, collision checks |
|
||||||
| `modules/cabana/http.go` | route mounting, `protect`, `operationDeclared`, `relationMutation` | ✓ VERIFIED | Mounted from `modules/surf/router.go:522` via `cabana.Activate` |
|
| `modules/cabana/http.go` | route mounting, `protect`, `operationDeclared`, `relationMutation` | ✓ VERIFIED | Mounted from `modules/surf/router.go:522` via `cabana.Activate` |
|
||||||
@@ -259,6 +256,7 @@ Backstop (non-inferable) truths:
|
|||||||
| Phase 9 framework tests, named | `go test ./modules/cabana -v -run '^(TestAllowsFollowsWinterHasAnyAccess\|TestNavigationDropsDeniedParentAndRepointsTarget\|TestRelationMutationsFollowToolbarButtons\|TestBulkDelete(Duplicates\|Idempotent\|Rollback)\|TestCRUDHooks\|TestCRUDFillTypeIsValidation\|TestFormSchema(Compile\|Rejects)\|TestListSchemaCompile\|TestRelationCandidateExclusions\|TestPhase09PermissionMatrix\|TestAdminAuthLifecycle\|TestAdminCreateCommand\|TestAdminResetPasswordCommand)$'` | 16 PASS | ✓ PASS |
|
| Phase 9 framework tests, named | `go test ./modules/cabana -v -run '^(TestAllowsFollowsWinterHasAnyAccess\|TestNavigationDropsDeniedParentAndRepointsTarget\|TestRelationMutationsFollowToolbarButtons\|TestBulkDelete(Duplicates\|Idempotent\|Rollback)\|TestCRUDHooks\|TestCRUDFillTypeIsValidation\|TestFormSchema(Compile\|Rejects)\|TestListSchemaCompile\|TestRelationCandidateExclusions\|TestPhase09PermissionMatrix\|TestAdminAuthLifecycle\|TestAdminCreateCommand\|TestAdminResetPasswordCommand)$'` | 16 PASS | ✓ PASS |
|
||||||
| Review-fix tests, named | `go test ./modules/cabana ./modules/bouncer -v -run '^(TestActivateRefusesAForeignBackendGuard\|TestAdminCreateRejectsCrossFieldCollision\|TestAdminLogoutRevokesExpiredRefreshableToken\|TestAdminPasswordWithoutFlag\|TestBackendUserPermissionsOverrideRole\|TestCRUDOperationsFollowDeclarations\|TestCRUDRequiredFollowsContext\|TestFieldByColumnTagBeatsGoName\|TestHooksReceiveTheWriteTransaction\|TestListSearchNonTextColumns\|TestLoginAmbiguousIdentifier\|TestMissingUserHashUsesConfiguredCost\|TestModelHelpersLookThroughEmbeddedStructs\|TestRegistryOwner\|TestRelationColumnOrderIsIndependentOfIndentation\|TestRelationDefaultSort\|TestVerifyRefreshableClaimsAudience)$'` | 17 PASS (WR-09 scaffold assertions run inside `TestScaffoldAllArtifacts`, full run PASS) | ✓ PASS |
|
| Review-fix tests, named | `go test ./modules/cabana ./modules/bouncer -v -run '^(TestActivateRefusesAForeignBackendGuard\|TestAdminCreateRejectsCrossFieldCollision\|TestAdminLogoutRevokesExpiredRefreshableToken\|TestAdminPasswordWithoutFlag\|TestBackendUserPermissionsOverrideRole\|TestCRUDOperationsFollowDeclarations\|TestCRUDRequiredFollowsContext\|TestFieldByColumnTagBeatsGoName\|TestHooksReceiveTheWriteTransaction\|TestListSearchNonTextColumns\|TestLoginAmbiguousIdentifier\|TestMissingUserHashUsesConfiguredCost\|TestModelHelpersLookThroughEmbeddedStructs\|TestRegistryOwner\|TestRelationColumnOrderIsIndependentOfIndentation\|TestRelationDefaultSort\|TestVerifyRefreshableClaimsAudience)$'` | 17 PASS (WR-09 scaffold assertions run inside `TestScaffoldAllArtifacts`, full run PASS) | ✓ PASS |
|
||||||
| Guard isolation | `go test ./modules/bouncer -run '^TestPhase09GuardIsolation$' -v` | PASS | ✓ PASS |
|
| Guard isolation | `go test ./modules/bouncer -run '^TestPhase09GuardIsolation$' -v` | PASS | ✓ PASS |
|
||||||
|
| Backend admin migrations incl. WR-11 index | `go test ./modules/lagoon -count=1 -v -run '^(TestBackendAdmin.*)$'` | 6 PASS (`Migration`, `Seed`, `Rollback`, `WinterRow`, `EmailCaseInsensitiveUnique`, `EmailIndexRefusesCaseDuplicates`) | ✓ PASS |
|
||||||
| Assembled Phase 9 acceptance and controllers | `go test ./plugins/golem15/fonoteka -count=1 -v -run '^(TestPhase09.*\|TestAdminSettings.*\|TestAdminMetadata.*\|TestCollectionsAdmin.*\|TestAlbumsAdmin.*\|TestGenresAdmin.*\|TestArtistsAdmin.*\|TestStylesAdmin.*\|TestAdminTracer.*\|TestAdminAuthLifecycleAssembled)$'` (fonoteka.go) | exit 0; 79 PASS, 0 SKIP, 0 FAIL | ✓ PASS |
|
| Assembled Phase 9 acceptance and controllers | `go test ./plugins/golem15/fonoteka -count=1 -v -run '^(TestPhase09.*\|TestAdminSettings.*\|TestAdminMetadata.*\|TestCollectionsAdmin.*\|TestAlbumsAdmin.*\|TestGenresAdmin.*\|TestArtistsAdmin.*\|TestStylesAdmin.*\|TestAdminTracer.*\|TestAdminAuthLifecycleAssembled)$'` (fonoteka.go) | exit 0; 79 PASS, 0 SKIP, 0 FAIL | ✓ PASS |
|
||||||
|
|
||||||
### Probe Execution
|
### Probe Execution
|
||||||
@@ -291,35 +289,27 @@ REQUIREMENTS.md maps exactly these six IDs to Phase 9, so there are no orphaned
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `internal/build/stubs/artifacts.tmpl` | 106 | `// TODO: return a pointer to the plugin's model` in the generated controller | ℹ️ Info | Deliberate scaffold placeholder (WR-09); generated text, not a debt marker in phase code. Until filled in, the scaffold answers 500 and boot refuses the undeclared permission |
|
| `internal/build/stubs/artifacts.tmpl` | 106 | `// TODO: return a pointer to the plugin's model` in the generated controller | ℹ️ Info | Deliberate scaffold placeholder (WR-09); generated text, not a debt marker in phase code. Until filled in, the scaffold answers 500 and boot refuses the undeclared permission |
|
||||||
| `modules/cabana/settings.go` | 149 | no test writes a numeric settings field | ℹ️ Info | The `json.Number` conversion lives in the shared `lagoon.Fill`; settings Fill failures already answer 422 |
|
| `modules/cabana/settings.go` | 149 | no test writes a numeric settings field | ℹ️ Info | The `json.Number` conversion lives in the shared `lagoon.Fill`; settings Fill failures already answer 422 |
|
||||||
| `.planning/.../09-UAT.md` | tests 1-3 | still pending for items this report resolves | ℹ️ Info | UAT file not regenerated by this run |
|
|
||||||
|
|
||||||
No `TBD`/`FIXME`/`XXX` in any covered implementation file (grep: no hits). The 12 Info review findings (IN-01..IN-12) remain `open` in the ledger and are out of the fix run's scope; none defeats a success criterion.
|
No `TBD`/`FIXME`/`XXX` in any covered implementation file (grep: no hits). The 12 Info review findings (IN-01..IN-12) remain `open` in the ledger and are out of the fix run's scope; none defeats a success criterion.
|
||||||
|
|
||||||
### Human Verification Required
|
### Human Verification Required
|
||||||
|
|
||||||
#### 1. Decide the three open choices from 09-REVIEW-FIX.md
|
None open. The previous items are resolved by the user's recorded decisions on 2026-10-01 (09-REVIEW-FIX.md "Decisions", 09-UAT.md status complete, 3/3 pass, commit ba1aaef), and the code at HEAD matches each decision:
|
||||||
|
|
||||||
**Test:** Read "Decisions needed" in 09-REVIEW-FIX.md and choose for each.
|
| Item | Decision | Code evidence |
|
||||||
**Expected:**
|
|---|---|---|
|
||||||
- **WR-11:** add a unique index on `lower(backend_users.email)` or not, and whether copied Winter rows are deduplicated first. Ambiguous logins are already refused and `admin:create` already blocks collisions; the index would only add race protection.
|
| WR-11 unique index on `lower(email)` | add it | migration `202610010001_backend_users_email_ci_unique`; `TestBackendAdminEmailCaseInsensitiveUnique`, `TestBackendAdminEmailIndexRefusesCaseDuplicates` PASS |
|
||||||
- **WR-03:** keep single-record delete allowed whenever a form exists (current behaviour, matching Winter's form-screen delete button), or also require the list toolbar's `delete`. The second option needs a new form-schema field and an admin API and OpenAPI change.
|
| WR-03 single-record delete | keep as Winter (allowed whenever a form exists) | `operationDeclared` in `modules/cabana/http.go`; `TestCRUDOperationsFollowDeclarations` PASS |
|
||||||
- **WR-17:** keep Winter's exact-code merge (a `-1` on `acme.a` does not remove a role's `acme.*`), or make denies stricter than Winter.
|
| WR-17 deny versus wildcard | keep as Winter (exact-code merge) | `applyUserPermissions` in `modules/cabana/auth.go`; `TestBackendUserPermissionsOverrideRole` PASS |
|
||||||
|
| 24 judgment-tier prohibitions | verdicts accepted; 09-03 #1 superseded by Phase 10.1 D-09 | Prohibitions table above |
|
||||||
**Why human:** These are policy and parity choices, not defects.
|
|
||||||
|
|
||||||
#### 2. Review the 24 judgment-tier prohibitions
|
|
||||||
|
|
||||||
**Test:** Accept or reject the verdicts in the Prohibitions table.
|
|
||||||
**Expected:** Confirm the superseded 09-03 `type: partial` verdict (lifted by Phase 10.1 D-09). The previously qualified 09-11 (WR-02) and 09-12 (WR-18) verdicts are now "not violated" on code and test evidence.
|
|
||||||
**Why human:** Judgment-tier prohibitions need human resolution.
|
|
||||||
|
|
||||||
### Gaps Summary
|
### Gaps Summary
|
||||||
|
|
||||||
No ROADMAP success criterion failed, so there are no gaps. Separate admin authentication, permission gating of controllers, settings and navigation, the strict goccy/go-yaml form and list pipeline, the relation manager that replaces the Collections editors `partial`, the CRUD hooks, per-record bulk delete and the settings binding all exist under `modules/`, are wired, and pass their tests on real PostgreSQL at HEAD in both repositories. CR-01 and all 19 review warnings are fixed in code, and each fix has a passing named test.
|
No ROADMAP success criterion failed, so there are no gaps. Separate admin authentication, permission gating of controllers, settings and navigation, the strict goccy/go-yaml form and list pipeline, the relation manager that replaces the Collections editors `partial`, the CRUD hooks, per-record bulk delete and the settings binding all exist under `modules/`, are wired, and pass their tests on real PostgreSQL at HEAD in both repositories. CR-01 and all 19 review warnings are fixed in code, and each fix has a passing named test.
|
||||||
|
|
||||||
The phase is still `human_needed` for two reasons. Three policy choices from the fix run (WR-11 index, WR-03 single-delete gating, WR-17 wildcard deny) are open. And the judgment-tier prohibitions need sign-off, including one that Phase 10.1 deliberately superseded. Neither blocks Płytarium.
|
The three policy choices from the fix run are decided (WR-11 index added and tested; WR-03 and WR-17 kept as Winter), the judgment-tier prohibition verdicts are accepted, and UAT is complete. No truth failed, no human item is open, so the phase is `passed`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Verified: 2026-10-01T19:47:49Z_
|
_Verified: 2026-10-01T21:17:13Z_
|
||||||
_Verifier: Claude (gsd-verifier)_
|
_Verifier: Claude (gsd-verifier)_
|
||||||
|
|||||||
Reference in New Issue
Block a user