32 KiB
phase, verified, status, score, covered_files, covered_digest, covered_files_note, behavior_unverified, overrides_applied, mvp_mode_note, re_verification, human_verification
| phase | verified | status | score | covered_files | covered_digest | covered_files_note | behavior_unverified | overrides_applied | mvp_mode_note | re_verification | human_verification | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-backend-admin-authentication-and-schema-pipeline | 2026-10-01T18:35:29Z | human_needed | 5/5 roadmap success criteria verified (plan truths 54/54 verified, including 3 backstop truths with direct test evidence) |
|
v2:sha256:642c5cedcadd932448c7a273b70474c751f6d9bf84030c331b63b101427d813d | Paths are current root-relative paths after Phase 10.2 (commit 5e50b16) moved the framework packages under modules/. 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 2da9482 (clean working tree). summercms.go was checked at HEAD 6a2ee9d. |
0 | 0 | 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. |
|
|
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.
Verified: 2026-10-01T18:35:29Z (summercms.go HEAD 6a2ee9d, fonoteka.go HEAD 2da9482)
Status: human_needed
Re-verification: Yes. The 2026-09-28T00:10Z report went stale after Phase 10.2 (commit 5e50b16) moved every framework package under modules/. Every truth was re-checked against the code under modules/ at HEAD, including later changes from Phases 10.1, 11 and the quick tasks. The covered-file list was rebuilt from the Phase 9 commits at current paths, not copied.
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.
Changes since the previous report that bear on Phase 9:
5e50b16(10.2-01):bouncer,cabana,lagoon,pact,phrasebook,surfmoved tomodules/.... Pure relocation;57e7b56retargeted test fixtures.c3efbc3(10.1-03) ande60e697(10.1 CR-01):lagoon.Fillconvertsjson.Numberinto integer, unsigned and float fields and returns*lagoon.FillTypeError; the cabana save path maps that to a per-field 422. This resolves the Phase 9 CR-01 defect.771d2cc,9df9fae(10.1-01/02), Phase 10.1 D-09: formtype: partialis accepted again, but only with a bare-namepathrendered through an allowlisted html/template node renderer. The legacy Winter partial (no path) still fails boot. See truth 4 and the 09-03 prohibition.f7b6b0c(11-08): cabana writes made commit-safe (crud.go,relation.go);037dc53: per-query collation in lagoon. Neither changes a Phase 9 contract; all Phase 9 tests still pass.
User Flow Coverage
User story (from every 09-*-PLAN.md): As a backend administrator, I want to authenticate separately and manage resources described by Winter-shaped schemas, so that the administration surface stays permission-gated and reusable without coupling it to frontend users.
| Step | Expected | Evidence | Status |
|---|---|---|---|
| Provision an admin | summer admin:create creates a bcrypt admin with a role; no boot or web seed |
modules/cabana/commands.go, RuntimeCommands in the generated main.go; TestAdminCreateCommand, TestAdminResetPasswordCommand re-run: PASS |
VERIFIED |
| Log in separately | Backend login by login or email returns a backend-audience JWT signed with admin.jwt.secret; frontend credentials fail |
modules/cabana/auth.go, bouncer.NewBackendJWTGuard; TestAdminAuthLifecycle, TestAdminTracerGenreList (frontend user with the same email rejected), TestPhase09GuardIsolation re-run: PASS |
VERIFIED |
| See permitted navigation | /navigation lists only permitted items |
modules/cabana/navigation.go Metadata (line 36); TestAdminMetadataFiltering re-run: PASS |
VERIFIED (WR-02 caveat) |
| Open a controller | 403 without the controller permission, before any schema or SQL work | modules/cabana/http.go protect (line 742): controller lookup, backend principal, Allows, then work; TestPhase09PermissionMatrix, TestAdminTracerPermissionBoundary, TestPhase09SecurityRoutes re-run: PASS |
VERIFIED |
| Work with schema-driven lists and forms | Form/list/relation schemas compiled from Winter YAML; CRUD, bulk delete, relation manager, settings | Sections below; TestPhase09AssembledAcceptance re-run: PASS on real PostgreSQL |
VERIFIED |
| Outcome: permission-gated, reusable, decoupled | Framework has no app names; admin identity never touches frontend users | `grep -niE "fonoteka | collection_editors |
Goal Achievement
Observable Truths (ROADMAP contract)
| # | 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 tables (modules/lagoon/backend_admin_migrations.go; TestBackendAdminMigration, Seed, Rollback, WinterRow re-run: PASS). Separate backend guard with its own secret and a required backend audience; cross-audience tokens fail both ways (TestPhase09GuardIsolation, TestAdminTracerAuthBoundary: PASS). Grants come from the role JSON plus HasPermissions role assignments (modules/cabana/auth.go FindByID/principalFrom). Every controller, relation and CRUD route goes through protect (modules/cabana/http.go:742); settings through protectSetting (line 375). /navigation and /settings filter entries with the same Allows (contracts.go:127). Tests re-run: TestPhase09PermissionMatrix, TestPhase09SecurityMatrix, TestAdminMetadataFiltering, TestAdminMetadataRejectsFrontendPrincipal: PASS. Caveats (warnings, unchanged at HEAD): WR-01 (granted matches grant wildcards only; a wildcard requirement never matches, and Allows requires ALL codes where Winter uses ANY), WR-02 (Metadata keeps a denied parent when a child is allowed and emits it with its own target), WR-17 (user-level backend_users.permissions ignored). |
| 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 imports github.com/goccy/go-yaml (+ ast), decodes with yaml.DisallowUnknownField() (line 286), walks the AST to keep order, rejects unknown keys and types. The real Fonoteka YAML covers every listed item (text, textarea, checkbox is_various, switch in settings, dropdown options: getFormatOptions, relation with nameFrom/emptyOption, span, tab, context, attributes). Tests re-run: 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:23 column types text, datetime, switch; ListColumn carries searchable, sortable, relation, select (schema_types.go:20-24). Real YAML exercises each. Search/sort/filter resolve only through compiled allowlists with a primary-key tie-break. Tests re-run: TestListSchemaCompile, TestAlbumsAdminList, TestArtistsAdminList, TestCollectionsAdminListCRUD, TestGenresAdminEdges: PASS. Caveat: WR-08 (LOWER(col) at query.go:294 on a non-text searchable column; the scaffold still emits searchable: true at artifacts.tmpl:137). |
| 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 anywhere 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 494), link and unlink (RelationBeforeLink at line 684), with explicit pivot writes and no hardcoded pivot names. Tests re-run on PostgreSQL: TestCollectionsAdminRelation*, TestCollectionsAdminRejectsPartial (the legacy path-less Winter editors partial still fails with "type partial needs a path"), TestRelationCandidateExclusions: PASS. Note: Phase 10.1 D-09 lifted Phase 9's blanket boot error for type: partial in favor of a bare-name, sanitized html/template partial (form_schema.go:504-524). The Collections editors tab is still a relation manager, so the criterion holds; the plan-level prohibition is flagged in the Prohibitions table. Caveats: WR-05, WR-07, WR-15. |
| 5 | Admin CRUD endpoints expose listExtendQuery/formExtendQuery/formBeforeCreate/formBeforeUpdate/relationExtendManageQuery hooks, bulk delete runs each record's lifecycle hooks, and the Settings model binds to a settings screen through the same schema pipeline. | ✓ VERIFIED | Hook interfaces in modules/pact/capabilities.go:331-394, type-asserted in modules/cabana/query.go:109, crud.go:442,531,578,594, relation.go:494. CRUDService.BulkDelete (crud.go:186) locks the scoped rows in one transaction, refuses a partial selection, and calls deleteRecord per row, so GORM and Form*Delete hooks fire per record. TestBulkDeleteDuplicates, Idempotent, Rollback, TestCRUDHooks re-run: PASS. Settings use the same Localize, writable projection, lagoon.Fill and lagoon.Validate (modules/cabana/settings.go); TestAdminSettings* re-run on PostgreSQL: PASS. CR-01 is now fixed: lagoon.convertValue handles json.Number (fill.go:179-221), and save maps *lagoon.FillTypeError to a 422 on the field (crud.go:324-333); TestCRUDFillTypeIsValidation writes a type: number field into an *int column, rejects fraction/exponent/overflow/wrong-type with 422 and no row: PASS. |
Score: 5/5 ROADMAP truths verified (0 present-but-behavior-unverified).
Plan must-have truths (supporting evidence)
There are 54 plan truths across 09-01 to 09-12, and each maps to a named test in its plan's <verify> block. I re-ran every named Fonoteka suite with -v: 78 top-level PASS, 0 SKIP, 0 FAIL (36 in TestPhase09*|TestAdminSettings*|TestAdminMetadata*|TestCollectionsAdmin*, 42 in TestAlbumsAdmin*|TestArtistsAdmin*|TestGenresAdmin*|TestStylesAdmin*|TestAdminTracer*|TestAdminAuthLifecycleAssembled). The 42 equals every function with those prefixes in the package, including TestAlbumsAdminSearchUsesCommittedArtists added in 11-08; no Phase 9 test was removed since 2026-09-27 (checked with git log -p). The previous report's "79" was a miscount by one.
Truth 09-12 #4 names ../fonoteka.go/docs/openapi.json as the admin OpenAPI home. Phase 10 D-15 moved the admin paths to the framework-owned summercms.go/admin/openapi/admin.json. The intent still holds: scripts/check-admin-openapi.sh --check and scripts/check-phase9.sh --openapi exit 0.
Backstop (non-inferable) truths:
| 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). Re-run: PASS |
VERIFIED (WR-11 caveat) |
09-11: missing settings GET is side-effect-free with exists: false; first PUT creates; identical PUT is idempotent |
TestAdminSettingsMissingRead, TestAdminSettingsCreate, TestAdminSettingsIdempotentUpdate re-run: 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: PASS |
VERIFIED |
Prohibitions (judgment tier, non-authoritative verdicts)
| Plan | Prohibition | Verdict |
|---|---|---|
| 01 | Backend identities must not share frontend user rows, the jwt guard, or a signing secret | not violated |
| 01 | Hidden navigation must not replace server-side authorization | not violated (protect/protectSetting on every route) |
| 01 | Tracer must not be mock-only | not violated (testcontainers PostgreSQL, assembled router) |
| 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 (Phase 10 carries the same JWT in a cookie; no server-side session store) |
| 02 | Auth logs carry no password, JWT, secret or hash | not violated (TestAdminAuthLogging, TestPhase09SecurityCoverage) |
| 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 prohibition text no longer holds literally. |
| 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) |
| 04 | YAML must not inject SQL or name an arbitrary method | not violated (conditions: rejected, finite FilterScopes) |
| 04 | Equal sort values must not make rows jump across pages | not violated (PK tie-break) |
| 05 | No partially committed bulk operation | not violated (TestBulkDeleteRollback) |
| 05 | Replay must not rerun destructive hooks | not violated (TestBulkDeleteIdempotent) |
| 06 | No cross-collection or silently chosen duplicate user on albums | not violated (TestAlbumsAdminAmbiguousEmail, CrossCollection) |
| 06 | Album form choices must not expose inactive or cross-collection users | not violated |
| 07 | Artist parity not reached by silently omitting Winter keys | not violated (TestPhase10Controllers asserts fields and columns equal the tracked YAML) |
| 08 | No second Genre identity and no weakened permission | not violated (TestGenresAdminTracerIdentity, DuplicateRegistration) |
| 09 | Style values not coerced between scalar types | not violated (TestStylesAdminTypedOptions) |
| 10 | No PHP partial and no hardcoded Fonoteka pivot names in the framework | not violated (non-test modules/cabana/*.go grep: no hits; 10.1 partials are html/template, not PHP) |
| 10 | Owner, cross-collection or already-linked candidates not linkable by forgery | not violated (ForgedPivot, CrossScope, Idempotent) |
| 11 | Navigation/settings metadata must not reveal existence, label or target of inaccessible entries | qualified: a genres-only admin receives the parent fonoteka entry with its albums target (WR-02). Winter behaves the same way. |
| 11 | Reading a missing singleton must not create a row | not violated (TestAdminSettingsMissingRead) |
| 12 | Acceptance must not depend on skipped PostgreSQL tests or zero-test matches | qualified: check-phase9.sh --self-test refuses skips and zero-test runs (re-run: PASS), but detection is per invocation, not per package (WR-18). |
| 12 | High threats marked mitigated only with a named failing-when-broken test | not violated (09-SECURITY-REVIEW.md names a command per threat) |
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
modules/lagoon/backend_admin_migrations.go |
backend_users/roles, system-role seed | ✓ VERIFIED | Explicit SQL up/down; TestBackendAdmin* PASS |
modules/cabana/auth.go |
backend provider, login/refresh/logout/me | ✓ VERIFIED | Wired from Activate; BackendUsers reads only backend_users |
modules/cabana/commands.go |
admin:create, admin:reset-password | ✓ VERIFIED | RuntimeCommands appended in the generated main (internal/build) |
modules/cabana/http.go |
route mounting, protect |
✓ VERIFIED | Mounted from modules/surf/router.go:522 via cabana.Activate |
modules/cabana/form_schema.go, schema_types.go |
strict typed form compiler | ✓ VERIFIED | goccy/go-yaml strict decode |
modules/cabana/list_schema.go, filter_schema.go, query.go |
list compiler and allowlisted query | ✓ VERIFIED | |
modules/cabana/crud.go |
CRUD, projection, hooks, bulk delete | ✓ VERIFIED | CR-01 fixed (Phase 10.1) |
modules/lagoon/fill.go |
allowlisted fill used by CRUD and settings | ✓ VERIFIED | json.Number conversion, FillTypeError |
modules/cabana/relation.go |
relation schema and link/unlink | ✓ VERIFIED | |
modules/cabana/navigation.go, settings.go |
filtered metadata, singleton settings | ✓ VERIFIED (WR-02) | |
scripts/check-phase9.sh |
fail-closed gate | ✓ VERIFIED | --self-test, --openapi re-run: exit 0 |
fonoteka.go/.../controllers/{albums,artists,collections,genres,styles}_admin_controller.go |
five controllers with permissions | ✓ VERIFIED | Registered through HasAdminControllers |
fonoteka.go/.../models/*/fields.yaml, columns.yaml, controllers/*/config_*.yaml |
Winter-shaped YAML | ✓ VERIFIED | partial replaced by relation-manager |
fonoteka.go/.../admin_permissions.go, admin_navigation.go, admin_settings.go |
registerPermissions/Navigation/Settings ports | ✓ VERIFIED | |
fonoteka.go/.../classes/backend_album_collection.go |
D-14 email-to-collection resolver | ✓ VERIFIED | TestAlbumsAdminCollectionMatch PASS |
fonoteka.go/.../admin_phase09_e2e_test.go |
assembled acceptance | ✓ VERIFIED | TestPhase09AssembledAcceptance PASS |
Key Link Verification
| From | To | Via | Status |
|---|---|---|---|
modules/surf/router.go |
modules/cabana/http.go |
cabana.Activate(app, plugins) at line 522, then RegisterMiddleware("summercms.cabana", "backend", ...) |
WIRED |
modules/cabana/http.go guard |
modules/bouncer/jwt.go |
NewBackendJWTGuard with backend audience |
WIRED |
modules/cabana/http.go handlers |
compiled schemas | s.reg.Get(id) then list/form/relation |
WIRED |
modules/cabana/crud.go |
lagoon.Fill/lagoon.Validate |
save transaction (line 288; Fill at 324) |
WIRED (CR-01 fixed) |
modules/cabana/crud.go bulk |
model lifecycle hooks | per-row deleteRecord inside one transaction |
WIRED |
modules/cabana/settings.go |
form pipeline | Localize, Fill (line 149), Validate |
WIRED |
models/collection/fields.yaml |
config_relation.yaml |
relation: editors compiled at activation |
WIRED |
internal/build/build.go |
cabana.RuntimeCommands |
generated main | WIRED |
Data-Flow Trace (Level 4)
| Artifact | Data | Source | Real data | Status |
|---|---|---|---|---|
| List endpoint | data rows |
GORM query on the registered model, scoped by ListExtendQuery |
Yes (PostgreSQL rows in assembled tests) | ✓ FLOWING |
| Navigation | entries | plugin Navigation() filtered by principal grants from backend_user_roles |
Yes | ✓ FLOWING |
| Settings GET | data |
golem15_fonoteka_settings row or compiled defaults |
Yes | ✓ FLOWING |
| Relation linked/candidates | rows | pivot-joined user query with owner and linked users excluded | Yes | ✓ FLOWING |
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
| Framework vet | go vet ./... (summercms.go) |
exit 0, no output | ✓ PASS |
| Framework regression (single full run) | go test ./... -count=1 (summercms.go) |
exit 0; 35 packages ok (plus 4 with no test files), including modules/cabana 28.7s, modules/bouncer 15.8s, modules/lagoon 23.5s, modules/pact, modules/phrasebook, modules/surf, internal/build 33.1s; 0 FAIL |
✓ PASS |
| Phase 9 framework tests, named | go test ./modules/cabana -v -run '^(TestBulkDelete(Duplicates|Idempotent|Rollback)|TestCRUDHooks|TestCRUDFillTypeIsValidation|TestFormSchema(Compile|Rejects)|TestListSchemaCompile|TestRelationCandidateExclusions|TestPhase09PermissionMatrix|TestAdminAuthLifecycle|TestAdminCreateCommand|TestAdminResetPasswordCommand)$' |
13 PASS | ✓ PASS |
| Guard isolation | go test ./modules/bouncer -run '^TestPhase09GuardIsolation$' -v |
PASS | ✓ PASS |
| Admin tables and numeric fill | go test ./modules/lagoon -v -run '^(TestFillJSONNumber|TestFillTypeErrorNamesTheKey|TestBackendAdmin.*)$' |
6 PASS | ✓ PASS |
| App vet | go vet $(go list -f '{{.Dir}}/...' -m) (fonoteka.go) |
exit 0 | ✓ 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) |
ok 17.7s; 78 PASS, 0 SKIP, 0 FAIL | ✓ PASS |
Probe Execution
No scripts/*/tests/probe-*.sh exists and no plan declares a probe. The phase gate ran instead:
| Probe | Command | Result | Status |
|---|---|---|---|
scripts/check-phase9.sh |
--self-test |
"refuse: zero tests", "refuse: failed TestPhase09ContractInventory", "phase9 self-test passed", exit 0 | PASS |
scripts/check-phase9.sh |
--openapi |
"phase9 openapi passed", exit 0 | PASS |
scripts/check-admin-openapi.sh |
--check |
exit 0 | PASS |
Requirements Coverage
| Requirement | Source Plans | Description | Status | Evidence |
|---|---|---|---|---|
| AUTH-08 | 09-01, 09-02, 09-11, 09-12 | Backend admins with roles and a permission registry, separate from frontend users, gating navigation and controllers | ✓ SATISFIED | Truth 1 (warnings WR-01, WR-02, WR-14, WR-17) |
| ADMIN-01 | 09-03, 09-06..09-10, 09-12 | fields.yaml to JSON form schema | ✓ SATISFIED | Truth 2 |
| ADMIN-02 | 09-01, 09-04, 09-06..09-10, 09-12 | columns.yaml to JSON list schema | ✓ SATISFIED | Truth 3 |
| ADMIN-03 | 09-10, 09-12 | relation-manager replaces partial |
✓ SATISFIED | Truth 4 |
| ADMIN-04 | 09-05..09-10, 09-12 | CRUD hooks and per-record bulk delete | ✓ SATISFIED | Truth 5 (CR-01 fixed in Phase 10.1) |
| ADMIN-05 | 09-11, 09-12 | settings model bound through the same pipeline | ✓ SATISFIED | Truth 5 |
REQUIREMENTS.md maps exactly these six IDs to Phase 9, so there are no orphaned requirements.
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
modules/cabana/contracts.go |
127-150 | wildcard requirement unmatched; ALL not ANY | ⚠️ Warning (WR-01) | Latent for future Winter ports |
modules/cabana/navigation.go |
46-57 | denied parent emitted with its target | ⚠️ Warning (WR-02) | Minor metadata disclosure |
modules/cabana/auth.go |
36-73 | user-level permissions ignored | ⚠️ Warning (WR-17) | Winter denies lost at cutover |
modules/cabana/query.go, internal/build/stubs/artifacts.tmpl |
294, 137 | LOWER(col) on non-text columns; scaffold makes id searchable |
⚠️ Warning (WR-08) | Scaffolded controllers 500 on search |
internal/build/stubs/artifacts.tmpl |
scaffold controller | no permissions or record source | ⚠️ Warning (WR-09) | Open to all admins once completed naively |
modules/cabana/settings.go |
149 | no test writes a numeric settings field | ℹ️ Info | CR-01's fix is in lagoon.Fill, shared by settings; Fill failures there already answer 422 |
.planning/.../09-REVIEW-DISPOSITION.md |
ledger | CR-01 still open although fixed |
ℹ️ Info | Ledger out of date; see human item 1 |
The other open review warnings (WR-03 to WR-07, WR-10 to WR-16, WR-18, WR-19) and the 12 info findings are recorded in 09-REVIEW.md. None defeats a ROADMAP success criterion. No TBD/FIXME/XXX in any covered implementation file (grep: no hits).
Human Verification Required
1. Triage the open review findings, starting with the AUTH-08 ones
Test: Set dispositions in 09-REVIEW-DISPOSITION.md, where all 32 findings are still open. Record CR-01 as fixed (Phase 10.1, c3efbc3 + e60e697, covered by TestCRUDFillTypeIsValidation).
Expected: WR-01, WR-02, WR-14 and WR-17 explicitly fixed or deferred with a reason. Their code is unchanged at HEAD. D-01/D-03 promise Winter permission semantics and a straight backend_users copy at cutover (Phase 15).
Why human: This is a policy and scope decision.
2. Review the 24 judgment-tier prohibitions
Test: Accept or reject the verdicts in the Prohibitions table.
Expected: Pay attention to the superseded 09-03 type: partial prohibition (lifted by Phase 10.1 D-09) and the two qualified verdicts (09-11 navigation target, 09-12 per-invocation zero-test detection).
Why human: Judgment-tier prohibitions need human resolution.
The previous item "Decide CR-01" is resolved by code and test evidence and is no longer a separate item. 09-UAT.md still lists it as pending.
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. Since the last report the critical review finding CR-01 was fixed in Phase 10.1.
The phase is still not passed because two human decisions are open. All review findings are untriaged in the ledger, and four warnings bear on the Winter permission semantics that AUTH-08 and D-03 promise. And 24 judgment-tier prohibitions need sign-off, one of which a later phase deliberately superseded. Neither blocks Płytarium today.
Verified: 2026-10-01T18:35:29Z Verifier: Claude (gsd-verifier)