Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-VERIFICATION.md

32 KiB
Raw Blame History

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)
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-01-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-01-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-03-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-03-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-04-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-04-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-05-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-05-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-06-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-06-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-07-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-07-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-08-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-08-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-09-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-09-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-10-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-10-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-11-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-11-SUMMARY.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-12-PLAN.md
.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-12-SUMMARY.md
internal/build/artifact.go
internal/build/build.go
internal/build/build_test.go
internal/build/stubs/artifacts.tmpl
modules/bouncer/audience_test.go
modules/bouncer/backend_guard_test.go
modules/bouncer/context.go
modules/bouncer/jwt.go
modules/bouncer/mint.go
modules/bouncer/refresh.go
modules/cabana/admin_openapi.go
modules/cabana/auth.go
modules/cabana/auth_test.go
modules/cabana/bulk_test.go
modules/cabana/commands.go
modules/cabana/commands_test.go
modules/cabana/contracts.go
modules/cabana/crud.go
modules/cabana/crud_lifecycle_test.go
modules/cabana/crud_test.go
modules/cabana/filter_schema.go
modules/cabana/form_schema.go
modules/cabana/form_schema_test.go
modules/cabana/http.go
modules/cabana/list_schema.go
modules/cabana/list_schema_test.go
modules/cabana/metadata_settings_test.go
modules/cabana/navigation.go
modules/cabana/phase09_contract_test.go
modules/cabana/query.go
modules/cabana/query_test.go
modules/cabana/registry.go
modules/cabana/relation.go
modules/cabana/relation_test.go
modules/cabana/schema.go
modules/cabana/schema_types.go
modules/cabana/security_coverage_test.go
modules/cabana/security_test.go
modules/cabana/settings.go
modules/cabana/testdata/list/all_columns.yaml
modules/cabana/testdata/list/all_filters.yaml
modules/lagoon/backend_admin_migrations.go
modules/lagoon/backend_admin_migrations_test.go
modules/lagoon/fill.go
modules/lagoon/fill_test.go
modules/lagoon/migrations.go
modules/pact/capabilities.go
modules/phrasebook/translator.go
modules/surf/router.go
scripts/check-phase9.sh
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.
previous_status previous_score reason gaps_closed gaps_remaining regressions human_items_resolved
human_needed 5/5 Previous report (2026-09-28T00:10Z) went stale: Phase 10.2 moved 42 covered framework files under modules/, so its covered_files no longer existed.
CR-01 (numeric writes / 500 instead of 422): fixed in code after Phase 9 by Phase 10.1 commits c3efbc3 and e60e697; TestCRUDFillTypeIsValidation, TestFillJSONNumber and TestFillTypeErrorNamesTheKey pass. The ledger entry in 09-REVIEW-DISPOSITION.md is still `open` and is folded into human item 1.
test expected why_human
Triage the open code-review findings in 09-REVIEW-DISPOSITION.md (all 32 still `open`). Record CR-01 as fixed (Phase 10.1, c3efbc3 + e60e697), then decide the 19 warnings, in particular the four that touch AUTH-08 semantics: WR-01 (wildcard requirements never match, ALL instead of Winter's ANY), WR-02 (denied navigation parent emitted with its albums target), WR-17 (user-level backend_users.permissions ignored, so Winter denies are lost at cutover), WR-14 (logout cannot revoke an expired-but-refreshable token) Each finding set to fixed, deferred (with reason) or skipped; none left `open`. The WR-01/WR-02/WR-17 code paths are unchanged at HEAD (modules/cabana/contracts.go Allows, navigation.go Metadata, auth.go FindByID). None defeats a success criterion for the current Płytarium configuration (controllers declare single exact codes; the navigation leak matches Winter's own wildcard behavior), but D-01/D-03 promise Winter permission semantics at cutover, and that is a policy call.
test expected why_human
Review the 24 judgment-tier prohibitions (verdicts in the Prohibitions table) Accept or reject the verifier's non-authoritative verdicts. Three need 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; 09-11 #1 (navigation must not reveal the target of an inaccessible entry; WR-02 shows the parent's albums target to a genres-only admin); and 09-12 #1 (zero-test detection is per invocation, not per package; WR-18). Judgment-tier prohibitions need human resolution

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, surf moved to modules/.... Pure relocation; 57e7b56 retargeted test fixtures.
  • c3efbc3 (10.1-03) and e60e697 (10.1 CR-01): lagoon.Fill converts json.Number into 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: form type: partial is accepted again, but only with a bare-name path rendered 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
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)