--- phase: 09-backend-admin-authentication-and-schema-pipeline plan: 11 type: execute wave: 8 depends_on: [09-07, 09-08, 09-09, 09-10] files_modified: - cabana/registry.go - cabana/navigation.go - cabana/settings.go - cabana/http.go - ../fonoteka.go/plugins/golem15/fonoteka/admin_permissions.go - ../fonoteka.go/plugins/golem15/fonoteka/admin_navigation.go - ../fonoteka.go/plugins/golem15/fonoteka/admin_settings.go - ../fonoteka.go/plugins/golem15/fonoteka/models/settings/fields.yaml - ../fonoteka.go/plugins/golem15/fonoteka/admin_metadata_test.go - ../fonoteka.go/plugins/golem15/fonoteka/admin_settings_test.go autonomous: true requirements: [AUTH-08, ADMIN-05] estimate: tokens: 30000 raw_tokens: 30000 tasks: 3 confidence: low must_haves: truths: - "Per D-01/D-03, the developer role/superuser path receives all registered permissions while publisher receives only its explicit assignments; wildcard semantics remain deterministic and every controller/setting permission resolves from the registry." - "Per D-18, navigation preserves the plugin's Winter IDs, labels, icons, ordering, and controller targets, removes entries the principal cannot use, and never exposes an unauthorized route or setting through metadata." - "Per D-18, settings-list entries preserve code, label, description, category, icon, order, keywords, and permissions, are stable by order then code, and are filtered through the same backend principal permission set." - "Per D-17, the Fonoteka singleton setting serves its localized schema and GET/PUT through Fill then Validate with `required: true` producing a 422; only compiled fillable fields can change." - statement: "[flagged assumption ADMIN-05] Before a singleton row exists, GET is side-effect-free and returns exists: false plus the model's default-valued data; the first valid PUT creates it, and repeating an identical PUT is an idempotent success with unchanged persisted values." verification: backstop - "AUTH-08 identity no-change remains in force: all permission/navigation/settings evaluation uses the distinct backend_users principal and backend guard, never a generalized frontend user or add-alongside role." artifacts: - path: "../fonoteka.go/plugins/golem15/fonoteka/admin_permissions.go" provides: "Complete exact plugin permission registry and role assignments" - path: "cabana/navigation.go" provides: "Permission-filtered stable navigation and settings-list metadata" - path: "cabana/settings.go" provides: "HasSettings registry plus singleton Fill/Validate GET/PUT service" - path: "../fonoteka.go/plugins/golem15/fonoteka/models/settings/fields.yaml" provides: "Fonoteka settings form schema" key_links: - from: "Fonoteka permission registry" to: "backend role HasPermissions" via: "exact registered codes plus wildcard/superuser evaluation" - from: "cabana/navigation.go" to: "controller/settings registry" via: "permission-filtered target validation" - from: "cabana/settings.go" to: "lagoon.Fill and lagoon.Validate" via: "compiled writable projection and singleton transaction" prohibitions: - "[flagged-unverified] Navigation and settings metadata must not reveal the existence, label, or target of entries the backend principal cannot access." - "[flagged-unverified] Reading a missing settings singleton must not create a database row or mutate defaults." --- ## Phase Goal **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. Complete the backend permission catalog, permission-filtered navigation, and singleton settings capability. Purpose: Make AUTH-08 authorization visible and coherent across discovery metadata and deliver ADMIN-05 without bypassing the schema/lifecycle pipeline. Output: Exact plugin registries, stable filtered metadata endpoints, and permissioned singleton settings schema/read/write routes. @/home/jin/.codex/gsd-core/workflows/execute-plan.md @/home/jin/.codex/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md @.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-RESEARCH.md @.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-SUMMARY.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-SUMMARY.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-SUMMARY.md @pact/capabilities.go @lagoon/fill.go @lagoon/validate.go @../fonoteka.go/plugins/golem15/fonoteka/models/settings.go ## Artifacts this phase produces - Complete Fonoteka backend permission declarations and publisher assignments - `cabana.NavigationItem`, `cabana.SettingsEntry`, and filtered metadata response services - `cabana.SettingsService` and D-09 `/settings` schema/GET/PUT routes - `fonoteka.AdminSettings` registration plus embedded settings `fields.yaml` - Assembled `TestAdminMetadata*` and `TestAdminSettings*` suites Task 1: Register exact permissions and validate every operation reference cabana/registry.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_permissions.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_metadata_test.go cabana/registry.go, pact/capabilities.go, bouncer/registry.go, bouncer/guard.go, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/Plugin.php, .planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md - Test 1: every source Fonoteka permission code/label is registered exactly once, publisher assignments match the source, and developer/superuser wildcard grants all registered codes. - Test 2: every controller operation, relation, navigation target, and setting names a registered permission; unknown or duplicate codes fail activation. - Test 3: a frontend user identity with matching numeric ID/permissions cannot satisfy a backend permission check. Implement the complete D-03 permission contribution from the tracked plugin source and bind it to D-01's developer/publisher roles. Extend cabana activation validation so all controller operations, relations, navigation items, and settings reference registered codes and duplicate/unknown declarations stop boot. Keep evaluation on the backend `bouncer.Principal` established by the named backend guard, preserving exact and wildcard semantics; do not adapt frontend-user roles into the admin registry. (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestAdminMetadata(Permissions|Publisher|Wildcard|RegistryReferences|RejectsFrontendPrincipal)$' -count=1) The command exits non-zero, reports no matching test, a source permission/assignment is absent, duplicate/unknown references activate, wildcard/exact semantics drift, or a frontend identity passes backend authorization. The exact permission catalog is complete, referentially valid, role-assigned, and exclusively evaluated against the distinct backend identity. Every backend operation and metadata entry has a validated permission code with correct role behavior. Task 2: Serve exact permission-filtered navigation and settings metadata cabana/navigation.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_navigation.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_metadata_test.go /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/Plugin.php, ../fonoteka.go/plugins/golem15/fonoteka/admin_permissions.go, cabana/registry.go, bouncer/guard.go - Test 1: navigation/settings metadata exactly preserves registered labels/icons/order/targets/keywords and sorts by order then stable code. - Test 2: developer sees all source entries, publisher sees only permitted entries, and a no-permission admin receives allocated empty arrays. - Test 3: a metadata item with missing target or permission fails activation; responses never include denied item fields. Port D-18's exact `registerNavigation()` shape and provide typed HasNavigation/HasSettings registry consumption. Validate controller/setting targets and permissions at activation, then filter whole entries before serialization through `HasPermissions`; never serialize then mask selected fields. Preserve the declared plugin shape and stable order/code tie-break, use request-time localization, and return non-null empty arrays. (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestAdminMetadata(Navigation|SettingsList|Filtering|StableOrder|RejectsInvalidTarget)$' -count=1) The command exits non-zero, reports no matching test, source metadata differs, ordering is unstable, empty output is null, an invalid target activates, or any denied entry field appears in the response. Navigation and settings-list metadata are exact, stable, localized, referentially valid, and filtered as whole entries by backend permission. An admin discovers only the controllers and settings pages that they can actually open. Task 3: Serve permissioned singleton settings through Fill and Validate cabana/settings.go, cabana/http.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_settings.go, ../fonoteka.go/plugins/golem15/fonoteka/models/settings/fields.yaml, ../fonoteka.go/plugins/golem15/fonoteka/admin_settings_test.go ../fonoteka.go/plugins/golem15/fonoteka/models/settings.go, /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/models/settings/fields.yaml, lagoon/fill.go, lagoon/validate.go, cabana/form_schema.go, cabana/http.go - Test 1: schema/GET/PUT require the settings permission before model/database work and return the D-17 localized typed form contract. - Test 2: missing GET creates no row and returns exists false/default data; valid first PUT creates one row; identical repeated PUT leaves one row with unchanged values. - Test 3: unknown/protected fields are ignored/rejected by writable projection, required/modeled Rules failures return 422, and failed update rolls back the singleton. Implement the D-17 HasSettings registry/service and D-09 routes for schema, GET, and PUT. Register the existing typed Fonoteka settings model and a complete embedded form schema under code `fonoteka` with `golem15.fonoteka.manage_settings`. For the flagged ADMIN-05 edge assumption, make missing GET side-effect-free with `exists: false` and a default-valued safe DTO, then make PUT lock/find-or-create the singleton and apply compiled writable projection, lagoon.Fill, and lagoon.Validate in one transaction. Treat identical PUT as success without adding rows or changing timestamps solely due to replay. (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestAdminSettings(Schema|PermissionOrder|MissingRead|Create|IdempotentUpdate|Projection|Validation|Rollback)$' -count=1) The command exits non-zero, reports no matching test, PostgreSQL is skipped, permission follows model/database work, GET creates a row, repeated PUT creates/mutates again, a protected key changes, required/Rules validation is skipped, or failure commits state. ADMIN-05 has explicit missing/create/repeat semantics and all D-17 schema, permission, Fill, Validate, projection, singleton, and transaction guarantees. Fonoteka settings are discoverable only when permitted and safely readable/updatable as one validated singleton. ## Trust Boundaries | Boundary | Description | |----------|-------------| | backend principal→metadata | Authorization determines which controller/setting existence can be disclosed | | settings JSON→singleton row | Untrusted fields attempt to mutate persistent global configuration | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| | T-09-18 | Tampering / Elevation | settings PUT | high | mitigate | Permission first, compiled writable projection, Fill/Validate, singleton row lock, transaction rollback, and protected-field fixtures in Task 3. | | T-09-19 | Information Disclosure | navigation/settings metadata | medium | mitigate | Filter whole entries before serialization, validate targets/permissions at activation, and test no-permission responses for field leakage. | | T-09-SC | Tampering | npm/pip/cargo installs | high | mitigate | No npm/pip/cargo install occurs; existing Go dependencies only, so the package-legitimacy gate remains closed. | Run `(cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^(TestAdminMetadata|TestAdminSettings)' -count=1)`; it fails on non-zero exit, zero matched tests, catalog/source drift, backend/frontend identity crossover, metadata disclosure, settings mass assignment, singleton replay drift, or validation/transaction bypass. - Permission declarations and publisher/developer behavior match the tracked plugin source and D-01/D-03. - D-18 navigation and settings metadata are stable and filter denied entries before serialization. - D-17 settings schema/GET/PUT use permission-first writable projection, Fill, Validate, and singleton transactions. - The flagged ADMIN-05 missing/create/repeat assumption has executable assembled backstop tests. Create `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-11-SUMMARY.md` when done.