31 KiB
phase, verified, status, score, covered_files, covered_digest, covered_files_note, behavior_unverified, overrides_applied, mvp_mode_note, human_verification
| phase | verified | status | score | covered_files | covered_digest | covered_files_note | behavior_unverified | overrides_applied | mvp_mode_note | human_verification | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-backend-admin-authentication-and-schema-pipeline | 2026-09-28T00:10:00Z | human_needed | 5/5 roadmap success criteria verified (plan truths 54/54 verified, including 3 backstop truths with direct test evidence) |
|
v2:sha256:510b91f54dea0918569d267a7aba22fcf879ad69899aa6e2033677a68a84de41 | 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 21c0f12 (clean working tree). summercms.go was checked at HEAD 2ad19bd. |
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 (user-story.validate: true), 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-09-28T00:10:00Z (summercms.go HEAD 2ad19bd, fonoteka.go HEAD 21c0f12)
Status: human_needed
Re-verification: No. This is the first verification of Phase 9. Phase 10 was already built and verified on top of it, so the checks below run against HEAD.
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.
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 |
cabana/commands.go, RuntimeCommands in generated main.go; TestAdminCreateCommand, TestAdminResetPasswordCommand (full suite PASS) |
VERIFIED |
| Log in separately | Backend login by login or email returns a backend-audience JWT signed with admin.jwt.secret; frontend credentials fail |
cabana/auth.go, bouncer.NewBackendJWTGuard; TestAdminAuthLifecycle, TestAdminTracerGenreList (a frontend user with the same email is rejected), TestPhase09GuardIsolation (re-run: PASS) |
VERIFIED |
| See permitted navigation | /navigation lists only permitted items |
cabana/navigation.go Metadata; TestAdminMetadataFiltering (developer sees fonoteka, publisher sees []), re-run: PASS |
VERIFIED (WR-02 caveat) |
| Open a controller | 403 without the controller permission, before any schema or SQL work | cabana/http.go protect; 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, real PostgreSQL) |
VERIFIED |
| Outcome: permission-gated, reusable, decoupled | Framework has no app names; admin identity never touches frontend users | grep -i "fonoteka|collection_editors|granted_by|golem15_" on non-test cabana/*.go: no hits. BackendUsers.FindByID reads only backend_users |
VERIFIED |
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 (lagoon/backend_admin_migrations.go, developer/publisher seeded as system roles). Separate backend guard with its own secret and a required backend audience. Cross-audience tokens fail in both directions (TestPhase09GuardIsolation, TestAdminTracerAuthBoundary). Grants come from the role JSON plus HasPermissions role assignments (cabana/auth.go:36-61). Every controller, relation and CRUD route goes through protect: guard, then controller lookup, then Allows(principal, RequiredPermissions()), then work (cabana/http.go:701-719). Settings go through protectSetting. /navigation and /settings filter entries by the same Allows. The five Fonoteka controllers each declare one exact code (access_albums etc.). Tests re-run and passing: TestPhase09PermissionMatrix, TestPhase09SecurityMatrix, TestAdminMetadataFiltering, TestAdminMetadataRejectsFrontendPrincipal. Caveats (warnings, not failures): WR-01 (a wildcard requirement never matches a specific grant, and multi-code requirements use ALL where Winter uses ANY; no Fonoteka controller or setting uses either), WR-02 (a genres-only admin gets the fonoteka parent entry with controller: golem15.fonoteka.albums, which then answers 403; Winter's hasAnyAccess wildcard shows the same parent), WR-17 (user-level backend_users.permissions is 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 | cabana/form_schema.go decodes with github.com/goccy/go-yaml and yaml.DisallowUnknownField() (line 279), walks the AST to keep order, and rejects unknown keys and types and type: partial (line 344). The real Fonoteka YAML covers every listed item: text (all models), textarea (genre, collection), checkbox (artist is_various), switch (settings), dropdown options: getFormatOptions (album, served by Album.DropdownOptions), relation with nameFrom and emptyOption (album genre, collection owner), span (all), tab (collection editors), context (style slug, collection editors), attributes (style slug readonly). 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 | cabana/list_schema.go column types text, datetime, switch (line 23); ListColumn carries searchable, sortable, relation, select (cabana/schema_types.go:20-24). Real YAML: searchable (all models), sortable (album format, collection created_at), relation + select (album genre.name, collection owner.username, mapped to email by ListRelationColumnMapper), type: datetime (collection created_at), type: switch (artist is_various). The list query resolves search/sort/filter only through compiled allowlists, with a primary-key tie-break. Tests re-run: TestListSchemaCompile, TestAlbumsAdminList, TestArtistsAdminList, TestCollectionsAdminListCRUD, TestGenresAdminEdges: PASS. Caveat: WR-08 (a non-text searchable column gives a PostgreSQL error and a 500; no Fonoteka searchable column is non-text, but the scaffold emits id: searchable: true). |
| 4 | The relation-manager schema supports search/link/unlink/manage-or-view lists for Collections' editors tab, replacing the partial field entirely. |
✓ VERIFIED | models/collection/fields.yaml editors: type: relation-manager, relation: editors, tab, span: full, context: update. There is no partial anywhere in the Fonoteka fields YAML, and the compiler rejects it. controllers/collections/config_relation.yaml has view.list.columns, manage.list.columns, toolbarButtons: link|unlink, showSearch. cabana/relation.go serves schema, linked, candidates (with RelationExtendManageQuery applied at line 493), link and unlink (RelationBeforeLink at line 683), with explicit pivot writes and no hardcoded pivot names. Tests re-run on PostgreSQL: TestCollectionsAdminRelationSchema, Link, Unlink, Idempotent, ForgedPivot, CrossScope, Concurrent, RelationEdges, RejectsPartial, TestRelationCandidateExclusions: PASS. Caveats: WR-05 (link/unlink do not check toolbarButtons), WR-07 (column order depends on 16-space indentation; the tracked file uses it), WR-15 (granted_by stores a backend admin id in a frontend-user column). |
| 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 | Hooks are optional interfaces in pact/capabilities.go:193-254, type-asserted in cabana/query.go:109, cabana/crud.go:439,528,575,591, cabana/relation.go:493. Albums implements the list/form/before-create/before-update hooks (D-14 collection scoping), and Collections implements RelationExtendManageQuery and RelationBeforeLink. Bulk delete (CRUDService.BulkDelete, cabana/crud.go:187) normalizes and sorts ids, locks the scoped rows through ListExtendQuery, and deletes each row separately with tx.Delete(model), so GORM model hooks and Form*Delete hooks fire per record. It never issues a single DELETE ... IN. TestBulkDeleteDuplicates, TestBulkDeleteIdempotent, TestBulkDeleteRollback and TestCRUDHooks (re-run: PASS) assert per-record hook ids in ascending order. The Fonoteka setting is registered through HasSettings with a compiled models/settings/fields.yaml. Schema, GET and PUT use the same FormSchema.Localize, writable projection, lagoon.Fill and lagoon.Validate (cabana/settings.go). TestAdminSettings* (8 tests, re-run on PostgreSQL: PASS). CR-01 (confirmed, escalated): see Human Verification item 1. It does not affect this truth for the delivered app, because search_use_typesense is a bool switch and no Fonoteka form writes a numeric column. |
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 (TestAlbumsAdmin*, TestArtistsAdmin*, TestGenresAdmin*, TestStylesAdmin*, TestCollectionsAdmin*, TestAdminMetadata*, TestAdminSettings*, TestAdminTracer*, TestAdminAuthLifecycleAssembled, TestPhase09*) with -v: 79 PASS, 0 SKIP, 0 FAIL. The framework suites (bouncer, cabana, lagoon, internal/build) passed in the full go test ./... run.
Truth 09-12 #4 names ../fonoteka.go/docs/openapi.json as the admin OpenAPI home. Phase 10 D-15 intentionally moved the admin paths to the framework-owned summercms.go/admin/openapi/admin.json, and fonoteka.go/docs/openapi.json is again the parity document. The intent still holds. admin.json lists all 21 D-09 paths, scripts/check-admin-openapi.sh --check exits 0, and scripts/check-phase9.sh --openapi exits 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 (login by life and by life@example.test against Life@Example.Test), TestAdminInactive/TestAdminDeleted (assertSameOpaque), TestAdminTracerGenreList (inserts a frontend user with the admin's email and asserts the frontend password is rejected). All re-run: PASS |
VERIFIED (WR-11 caveat: login = ? OR lower(email) = ? takes the first match on a cross-field collision) |
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 and migration, Principal.Backend provenance 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; there is 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 |
not violated (form_schema.go:344) |
| 03 | Labels not deferred to the client or cached in the first request's locale | not violated (per-request Localize; T-09-06) |
| 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; adjacent-page tests) |
| 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 (the album form has no user choice field) |
| 07 | Artist parity not reached by silently omitting Winter keys | not violated (Phase 10 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 (grep of non-test cabana/*.go: no hits) |
| 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 controller: golem15.fonoteka.albums (WR-02). Only the parent's default target leaks, and 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; six rows are "owned by 09-0x; not re-run in 09-12", and all of those passed in this session) |
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
lagoon/backend_admin_migrations.go |
backend_users/roles, system-role seed | ✓ VERIFIED | Explicit SQL up/down, no AutoMigrate; TestBackendAdmin* in lagoon suite PASS |
cabana/auth.go |
backend provider, login/refresh/logout/me | ✓ VERIFIED | Wired from Activate; BackendUsers reads only backend_users |
cabana/commands.go |
admin:create, admin:reset-password | ✓ VERIFIED | RuntimeCommands appended in the generated main |
cabana/http.go |
route mounting, protect |
✓ VERIFIED | Mounted from surf.BuildRouter via cabana.Activate |
cabana/form_schema.go, schema_types.go |
strict typed form compiler | ✓ VERIFIED | goccy/go-yaml strict decode |
cabana/list_schema.go, filter_schema.go, query.go |
list compiler and allowlisted query | ✓ VERIFIED | |
cabana/crud.go |
CRUD, projection, hooks, bulk delete | ✓ VERIFIED (CR-01) | Numeric writes fail inside lagoon.Fill |
cabana/relation.go |
relation schema and link/unlink | ✓ VERIFIED | |
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 | Embedded; 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 etc. PASS |
fonoteka.go/.../admin_phase09_e2e_test.go |
assembled acceptance | ✓ VERIFIED | TestPhase09AssembledAcceptance PASS |
Key Link Verification
| From | To | Via | Status |
|---|---|---|---|
surf/router.go |
cabana/http.go |
cabana.Activate in BuildRouter |
WIRED |
cabana/http.go guard |
bouncer/jwt.go |
NewBackendJWTGuard(secret, users, bl, ...) with backend audience |
WIRED |
cabana/http.go handlers |
compiled schemas | s.reg.Get(id) then cc.List/cc.Form/cc.Relations |
WIRED |
cabana/crud.go |
lagoon.Fill/lagoon.Validate |
save transaction |
WIRED (CR-01 breaks numeric values) |
cabana/crud.go bulk |
model lifecycle hooks | per-row tx.Delete(model) |
WIRED |
cabana/settings.go |
form pipeline | setting.Form.Localize, Fill, 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 the owner and linked users excluded | Yes | ✓ FLOWING |
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
| Framework regression | go vet ./... && go test ./... -count=1 (summercms.go) |
exit 0, 23 packages ok | ✓ PASS |
| App regression | go vet + go test $(go list -f '{{.Dir}}/...' -m) -count=1 (fonoteka.go) |
exit 0, 13 packages ok | ✓ PASS |
| Assembled Phase 9 acceptance | `go test ./plugins/golem15/fonoteka -run '^(TestPhase09.* | TestAdminSettings.* | TestAdminMetadata.* |
| Controllers, auth and tracer | `go test ./plugins/golem15/fonoteka -run '^(TestAlbumsAdmin.* | TestGenresAdmin.* | TestArtistsAdmin.* |
| Bulk hooks, CRUD hooks, schema compile, permission matrix | `go test ./cabana -run '^(TestBulkDelete(Duplicates | Idempotent | Rollback) |
| Guard isolation | go test ./bouncer -run '^TestPhase09GuardIsolation$' |
PASS | ✓ PASS |
| CR-01 reproduction | scratch module calling lagoon.Fill(m, {"year"}, {"year": json.Number("1990")}) on an int field |
lagoon: fill year: cannot assign json.Number to int |
✗ confirms CR-01 |
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 |
"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 (CR-01 escalated) | Truth 5 |
| 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. Info: REQUIREMENTS.md still shows all six as [ ] / Pending, and the traceability update is left to phase completion.
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
cabana/crud.go + lagoon/fill.go |
625 / 155-166 | UseNumber values passed to Fill, which cannot convert json.Number |
🛑 (review CR-01; escalated, not a roadmap gap) | Writable number fields 500; type mismatches 500 instead of 422 |
cabana/contracts.go |
105-137 | wildcard requirement unmatched; ALL not ANY | ⚠️ Warning (WR-01) | Latent for future Winter ports |
cabana/navigation.go |
42-54 | denied parent emitted with its target | ⚠️ Warning (WR-02) | Minor metadata disclosure |
cabana/auth.go |
36-76 | user-level permissions ignored | ⚠️ Warning (WR-17) | Winter denies lost at cutover |
cabana/query.go, internal/build/stubs/artifacts.tmpl |
294, 139-142 | LOWER(col) on non-text columns; scaffold makes id searchable |
⚠️ Warning (WR-08) | Scaffolded controllers 500 on search |
internal/build/stubs/artifacts.tmpl |
92-106 | scaffold has no permissions or record source | ⚠️ Warning (WR-09) | Open-to-all admins once completed naively |
internal/build/artifact.go |
179 | "TODO: describe " string in generated command stub |
ℹ️ Info | Generated text, not a debt marker in phase code |
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 of them defeats a ROADMAP success criterion for the current configuration. No unreferenced TBD/FIXME/XXX was found in phase files.
Human Verification Required
1. Decide CR-01 (numeric fields cannot be saved)
Test: Choose one: fix before closing the phase, defer with a tracked follow-up, or accept with an override.
Expected: If fixed: normalize json.Number before lagoon.Fill or in lagoon.convertValue, map conversion failures to a per-field 422 instead of a CapabilityError 500, and add a CRUD test that writes a non-protected type: number field bound to an int column (plus the settings equivalent).
Why human: The verifier reproduced it. It breaks the D-06 in-scope number type on writes, which Phase 10's NumberField.vue renders, but no ROADMAP criterion and no Płytarium form depends on it. No later phase in the roadmap covers it.
2. Triage the open review warnings, starting with the AUTH-08 ones
Test: Set dispositions in 09-REVIEW-DISPOSITION.md, where all 32 findings are open.
Expected: WR-01, WR-02, WR-14 and WR-17 explicitly fixed or deferred with a reason. 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.
3. Review the 24 judgment-tier prohibitions
Test: Accept or reject the verdicts in the Prohibitions table. Expected: Pay particular attention to the two qualified verdicts (09-11 navigation target, 09-12 per-invocation zero-test detection). Why human: Judgment-tier prohibitions need human resolution.
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 partial, the CRUD hooks, per-record bulk delete and the settings binding all exist, are wired, and pass their tests on real PostgreSQL at HEAD in both repositories.
The phase is still not passed, for three reasons. The critical review finding CR-01 is real and reproduced: the framework CRUD and settings engine cannot persist numbers, even though number is a D-06 in-scope field type. All 19 review warnings are untriaged, and four of them bear on the Winter permission semantics that AUTH-08 and D-03 promise. And 24 judgment-tier prohibitions need human sign-off. None of these blocks Płytarium today, but they are decisions for the developer, not for the verifier.
Verified: 2026-09-28T00:10:00Z Verifier: Claude (gsd-verifier)