# Feature Specification: Tenant-Owned Surface Route Audit **Feature Branch**: `302-tenant-owned-surface-route-audit` **Created**: 2026-05-14 **Status**: Draft **Input**: User description: "tenant-owned-surface-route-audit als 302" ## Spec Candidate Check *(mandatory - SPEC-GATE-001)* - **Problem**: The repo has moved toward workspace-first managed-environment routing, but the tenant-owned admin surface set is still understood through scattered code, tests, and candidate notes. Operators and implementers cannot tell which tenant-owned surfaces are fully migrated, intentionally hidden, partially cut over, blocked by product IA decisions, or dependent on retired tenant-panel behavior without reconstructing the answer manually. - **Today's failure**: After Spec 301 restored Inventory navigation, adjacent surfaces such as Entra Groups, policies, backups, restore runs, findings, evidence, reports, diagnostics, and subordinate relation surfaces still need a repo-verified posture. Without an audit matrix, follow-up work risks either mass-enabling navigation too broadly or preserving stale hide-first assumptions as if they were product decisions. - **User-visible improvement**: The next repair can be chosen from one clear, repo-verified inventory of tenant-owned admin surfaces, including route posture, navigation posture, context source, global-search posture, RBAC posture, existing proof, blocker, and recommended next action. - **Smallest enterprise-capable version**: Produce one bounded audit artifact for admin-reachable tenant-owned surfaces and one sequenced repair-prep order. Do not enable routes, change navigation, migrate resources, delete dead code, or decide product IA for individual surfaces in this slice. - **Explicit non-goals**: No mass navigation re-enablement, no broad runtime migration, no Entra Groups cutover decision, no navigation contract split, no tenant-panel dead-code deletion, no application code changes, no new persisted state, and no new framework for route auditing. - **Permanent complexity imported**: One repo-local audit artifact in this spec package plus review tasks. No runtime models, tables, enums, services, registries, UI components, assets, jobs, or policies are introduced. - **Why now**: `docs/product/spec-candidates.md` lists this as the second step after `admin-inventory-navigation-cutover`; Spec 301 is completed and explicitly deferred this generic route audit. - **Why not local**: A local fix to one resource would hide the cross-surface drift. The current problem is incomplete shared understanding, so the narrowest correct step is a read-only repo audit and repair order. - **Approval class**: Cleanup - **Red flags triggered**: One lightweight document-only classification vocabulary is used for the audit matrix. It is not a runtime taxonomy or product state family. - **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 1 | Wiederverwendung: 1 | **Gesamt: 10/12** - **Decision**: approve ## Spec Scope Fields *(mandatory)* - **Scope**: canonical-view - **Primary Routes**: - `/admin/workspaces/{workspace}` - `/admin/workspaces/{workspace}/environments` - `/admin/workspaces/{workspace}/environments/{environment}` - `/admin/workspaces/{workspace}/environments/{environment}/...` tenant-owned resource/page routes generated by Filament - retired or legacy tenant route families such as `/admin/t/{tenant}/...` and `/admin/tenants/{tenant}/...` as negative-control inputs only - **Data Ownership**: The audit covers tenant-owned and managed-environment-owned runtime records that require workspace plus managed-environment entitlement at authorization time. No product data is created or changed. - **RBAC**: The audit must record membership and capability expectations for each surface. Non-member workspace or tenant access remains deny-as-not-found. Member-without-capability remains 403 where capability checks apply. UI visibility remains non-authoritative. For canonical-view specs, the spec MUST define: - **Default filter behavior when tenant-context is active**: Environment-bound routes resolve their managed-environment context from the canonical workspace/environment route or current trusted context helpers. Workspace-level routes must not show tenant-owned surfaces unless a valid environment context is established. - **Explicit entitlement checks preventing cross-tenant leakage**: The audit must verify whether each surface uses existing context/authorization paths such as `WorkspaceScopedTenantRoutes`, `NavigationScope`, `OperateHubShell`, `ScopesGlobalSearchToTenant`, `canAccess`, `canViewAny`, policy methods, and route middleware. Any missing or ambiguous check is documented as a repair blocker, not patched in this slice. ## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)* - **Cross-cutting feature?**: yes - **Interaction class(es)**: navigation entry points, route generation, global search result destinations, tenant-owned detail/list surfaces, relation-manager entry points - **Systems touched**: - `apps/platform/app/Filament/Concerns/WorkspaceScopedTenantRoutes.php` - `apps/platform/app/Support/Navigation/NavigationScope.php` - `apps/platform/app/Filament/Concerns/ScopesGlobalSearchToTenant.php` - `apps/platform/app/Support/OperateHub/OperateHubShell.php` - `apps/platform/app/Support/WorkspaceIsolation/TenantOwnedModelFamilies.php` - `apps/platform/app/Filament/Resources/*` - `apps/platform/app/Filament/Pages/*` - `apps/platform/routes/web.php` - focused Filament feature tests under `apps/platform/tests/Feature/Filament/` - **Existing pattern(s) to extend**: No runtime path is extended in this slice. The audit must measure surfaces against existing shared paths: workspace-scoped tenant routes, navigation scope, tenant-scoped global search, route middleware, and tenant-owned query helpers. - **Shared contract / presenter / builder / renderer to reuse**: N/A for runtime. The audit should refer to the existing shared helpers above rather than defining a new route/navigation framework. - **Why the existing shared path is sufficient or insufficient**: Sufficiency is the question being audited per surface. The output must state whether each surface already uses the shared path, legitimately deviates, or needs a follow-up repair. - **Allowed deviation and why**: Document-only deviation. The audit may classify exceptions, but it must not create executable exception logic. - **Consistency impact**: Future repair specs should use the audit to avoid parallel local navigation/search/routing behavior. - **Review focus**: Reviewers must verify that the audit is repo-derived, that no runtime code was changed, and that follow-up recommendations are split into bounded candidates. ## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)* - **Touches OperationRun start/completion/link UX?**: no - **Shared OperationRun UX contract/layer reused**: N/A - **Delegated start/completion UX behaviors**: N/A - **Local surface-owned behavior that remains**: The audit may mention tenant-owned surfaces that link to runs, but it does not change OperationRun start, completion, link, notification, or monitoring behavior. - **Queued DB-notification policy**: N/A - **Terminal notification path**: N/A - **Exception required?**: none ## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)* - **Shared provider/platform boundary touched?**: no runtime boundary change - **Boundary classification**: N/A for implementation. The audit may record provider-owned surfaces such as Entra Groups separately from platform-core route/context rules. - **Seams affected**: Read-only review of route, navigation, global search, and authorization seams. - **Neutral platform terms preserved or introduced**: Use `workspace`, `managed environment`, `tenant-owned surface`, `route posture`, `navigation posture`, and `repair blocker`. - **Provider-specific semantics retained and why**: Provider-specific names such as Entra Groups remain only as audited surface names. They do not become platform-core vocabulary. - **Why this does not deepen provider coupling accidentally**: The output is a bounded audit artifact and does not change shared contracts or runtime labels. - **Follow-up path**: document-in-feature for the audit; follow-up-spec for any product-sensitive or runtime repair. ## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)* | Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | |---|---|---|---|---|---|---| | Tenant-owned surface audit artifact | no runtime UI change | N/A | navigation, route, global search review only | none | no | `N/A - audit/prep artifact only` | | Existing tenant-owned admin surfaces | no change in this slice | Native Filament surfaces are inspected, not changed | navigation, route, search | shell, page, route context as audited facts only | no runtime exception | The audit records current posture without re-rendering or enabling surfaces | ## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)* | Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction | |---|---|---|---|---|---|---|---| | Tenant-owned surface audit artifact | Secondary Context | Product/engineering chooses the next bounded repair spec | Surface classification, blocker, recommended next action | Route names, resource/page classes, tests, search posture, RBAC notes | Not an operator runtime surface; it guides repair sequencing | Follows Admin Workspace Navigation & Tenant-owned Surface Repair candidate order | Prevents repeated manual reconstruction and broad repair bundling | ## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)* | Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention | |---|---|---|---|---|---|---|---| | Tenant-owned surface audit artifact | internal implementer/reviewer | Surface name, classification, blocker, next action | Source files, test files, route names, global-search notes | N/A | Promote the next bounded repair | Raw payloads and product data are out of scope | One matrix row per surface prevents duplicate scattered posture notes | ## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)* | Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| | Tenant-owned surface audit artifact | Audit / Planning | Repo audit matrix | Choose next repair candidate | Read matrix row | N/A | N/A | N/A | `specs/302-tenant-owned-surface-route-audit/surface-route-audit.md` | N/A | Workspace/environment route posture fields | Tenant-owned surface | Migration state, blocker, next action | docs-only audit artifact | ## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)* | Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions | |---|---|---|---|---|---|---|---|---|---|---| | Tenant-owned surface audit artifact | Product owner / implementer / reviewer | Decide which tenant-owned surface repair should be promoted next | Internal audit matrix | Which surfaces are already migrated, blocked, stale, or ready for a bounded repair? | Surface classification, blocker, next action, proof status | File/test/route evidence | route posture, navigation posture, search posture, RBAC posture, proof status | Docs-only; no TenantPilot or Microsoft tenant mutation | Promote next bounded spec | none | ## Proportionality Review *(mandatory when structural complexity is introduced)* - **New source of truth?**: no runtime source of truth. The audit artifact is repair-prep evidence, not product truth. - **New persisted entity/table/artifact?**: no runtime persistence. One spec-package audit artifact is expected during implementation. - **New abstraction?**: no - **New enum/state/reason family?**: no runtime family. The audit uses document-only labels: migrated, partial cutover, stale panel logic, valid context gate, valid RBAC, ambiguous product IA, dead-code dependent. - **New cross-domain UI framework/taxonomy?**: no runtime framework or taxonomy. - **Current operator problem**: The team cannot safely choose the next tenant-owned route/navigation repair without a repo-verified surface inventory. - **Existing structure is insufficient because**: Candidate notes, tests, resources, route helpers, and model-family metadata each hold part of the answer, but no single artifact records the cross-surface posture. - **Narrowest correct implementation**: Create one audit matrix and repair order from current repo evidence. - **Ownership cost**: The matrix must be maintained only when a follow-up repair changes the posture. No runtime code or framework cost is added. - **Alternative intentionally rejected**: A broad migration or route/navigation framework split is rejected because the audit must first identify the actual drift and product-decision blockers. - **Release truth**: Current-release repair preparation. ### Compatibility posture This feature assumes a pre-production environment. Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec. Canonical replacement is preferred over preservation. ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* - **Test purpose / classification**: Feature-validation review using existing Pest feature tests; no new runtime test family is required unless the audit discovers that the audit artifact itself needs a repo guard in a follow-up spec. - **Validation lane(s)**: confidence for existing focused Filament tests; N/A for runtime because this slice does not change app behavior. - **Why this classification and these lanes are sufficient**: The deliverable is a repo audit. Existing route, navigation, global-search, and admin-scope tests provide evidence; any missing proof is documented as an audit gap and follow-up blocker rather than patched here. - **New or expanded test families**: none in this spec. - **Fixture / helper cost impact**: none. - **Heavy-family visibility / justification**: none. - **Special surface test profile**: standard-native-filament for any referenced existing Filament surface; no browser proof is required because no UI changes are made. - **Standard-native relief or required special coverage**: ordinary feature-test evidence and static route/resource review only. - **Reviewer handoff**: Confirm no runtime code changed, every audit row links to repo evidence, test gaps are marked as gaps, and follow-up repairs are split. - **Budget / baseline / trend impact**: none. - **Escalation needed**: document-in-feature. - **Active feature PR close-out entry**: Guardrail. - **Planned validation commands**: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/PanelNavigationSegregationTest.php tests/Feature/Filament/AdminTenantSurfaceParityTest.php tests/Feature/Filament/AdminSharedSurfacePanelParityTest.php tests/Feature/Filament/TenantOwnedResourceScopeParityTest.php tests/Feature/Filament/EntraGroupAdminScopeTest.php tests/Feature/Filament/EntraGroupGlobalSearchScopeTest.php tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php` - `git diff --check` ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Inventory the tenant-owned admin surface set (Priority: P1) As an implementer preparing the next repair, I need a repo-verified matrix of tenant-owned admin surfaces so I can see which surfaces are migrated, hidden, blocked, or stale before changing behavior. **Why this priority**: Without the matrix, every follow-up risks repeating the broad drift that Spec 301 intentionally avoided. **Independent Test**: Review `surface-route-audit.md` and confirm each first-slice tenant-owned family from `TenantOwnedModelFamilies::firstSlice()` plus named residual exceptions has a row with route posture, navigation posture, context source, global-search posture, RBAC posture, proof status, blocker, and next action. **Acceptance Scenarios**: 1. **Given** the current repo surface set, **When** the audit is completed, **Then** every tenant-owned first-slice model/resource family is represented exactly once. 2. **Given** a tenant-owned surface with no direct primary resource, **When** it is subordinate or indirect, **Then** the audit records the parent relation or indirect surface instead of inventing a primary route. 3. **Given** a surface with unclear product IA, **When** it is classified, **Then** the blocker is recorded as ambiguous product IA and the next action is a bounded follow-up decision. --- ### User Story 2 - Classify route, navigation, search, and RBAC posture (Priority: P2) As a reviewer, I need each surface classified against the existing route/navigation/global-search/RBAC contracts so I can distinguish valid gates from stale panel logic. **Why this priority**: The audit only helps if it separates legitimate denials from implementation drift. **Independent Test**: Sample at least one surface in each classification group and verify the matrix cites concrete files, route families, or tests that support the classification. **Acceptance Scenarios**: 1. **Given** a migrated surface, **When** it is classified, **Then** the matrix identifies the canonical workspace/environment route path or route-generation helper. 2. **Given** a stale hidden surface, **When** it is classified, **Then** the matrix names the hidden navigation or panel logic and marks it as a repair candidate, not a product decision. 3. **Given** a global-search capable resource, **When** it is classified, **Then** the matrix records whether it has an Edit or View page or whether global search is disabled. 4. **Given** an inaccessible tenant-owned surface, **When** RBAC posture is classified, **Then** non-member 404 and member-missing-capability 403 expectations are recorded separately where applicable. --- ### User Story 3 - Produce a sequenced repair order (Priority: P3) As a product owner, I need a recommended follow-up order so adjacent repairs remain small and do not become a single umbrella migration. **Why this priority**: The candidate group already names follow-ups, but the audit must make the next one defensible from repo evidence. **Independent Test**: Confirm the audit includes one ordered follow-up list with blockers and explicitly keeps runtime fixes out of this spec. **Acceptance Scenarios**: 1. **Given** surfaces classified as stale panel logic or partial cutover, **When** the audit proposes follow-up work, **Then** each recommendation is scoped as a bounded spec candidate. 2. **Given** surfaces blocked by product IA, **When** the audit proposes follow-up work, **Then** it calls out the decision needed before implementation. 3. **Given** tenant-panel dead-code dependencies, **When** the audit proposes follow-up work, **Then** it keeps deletion after route/navigation replacement decisions. ## Edge Cases - A surface may be tenant-owned by data but intentionally workspace-canonical by route; the audit must record both facts. - A surface may be tenant-owned but subordinate to a relation manager; the audit must not create an artificial top-level route recommendation. - A surface may be tenant-bound but not globally searchable; the audit must distinguish disabled/not-applicable search from missing search coverage. - A direct URL may be reachable while navigation is hidden; the audit must classify route access and navigation registration separately. - A test may encode stale behavior; the audit must record that as a test-contract finding without changing tests in this slice. - Legacy `/admin/t/{tenant}` and `/admin/tenants/{tenant}` behavior must be treated as negative-control evidence, not as a route family to revive. ## Requirements *(mandatory)* ### Functional Requirements - **FR-302-001**: The implementation MUST create `specs/302-tenant-owned-surface-route-audit/surface-route-audit.md` as the single audit matrix artifact. - **FR-302-002**: The audit MUST cover every first-slice tenant-owned family listed by `TenantOwnedModelFamilies::firstSlice()`. - **FR-302-003**: The audit MUST cover documented residual or exception surfaces from `TenantOwnedModelFamilies::residualRolloutInventory()` and `scopeExceptions()` when they have admin-reachable or admin-relevant surfaces. - **FR-302-004**: Each audited row MUST include surface name, model/resource/page owner, route posture, navigation posture, context source, global-search posture, RBAC posture, existing proof, blocker, migration state, and recommended next action. - **FR-302-005**: Migration state MUST be one of: migrated, partial cutover, stale panel logic, valid context gate, valid RBAC, ambiguous product IA, or dead-code dependent. - **FR-302-006**: The audit MUST distinguish route reachability from navigation registration. - **FR-302-007**: The audit MUST distinguish workspace-home cleanliness from environment-bound admin visibility. - **FR-302-008**: The audit MUST verify global-search posture for resources using tenant-scoped search or search-disabled posture, including the Filament v5 requirement that globally searchable resources have an Edit or View page. - **FR-302-009**: The audit MUST record existing high-signal tests that prove each posture or explicitly mark proof as missing. - **FR-302-010**: The audit MUST produce a recommended repair order that keeps `admin-directory-groups-cutover`, `navigation-contract-split`, and `tenant-panel-dead-code-retirement` as separate follow-up candidates unless repo evidence justifies a different bounded order. - **FR-302-011**: The implementation MUST NOT modify application runtime code, routes, migrations, models, services, jobs, Filament resources, Livewire components, policies, commands, views, assets, or tests. - **FR-302-012**: Any discovered runtime defect MUST be documented as a follow-up blocker or candidate, not fixed in this spec. ### Non-Functional Requirements - **NFR-302-001**: The audit must be repo-derived from current files and tests, not roadmap-aspirational. - **NFR-302-002**: The audit must be concise enough to review in one PR and structured enough for later implementation agents to use directly. - **NFR-302-003**: The feature must remain docs/spec-artifact only and must not change runtime behavior. - **NFR-302-004**: Filament v5 and Livewire v4 compliance must be preserved by not introducing legacy APIs or new runtime code. - **NFR-302-005**: No new assets are registered; deployment `filament:assets` requirements are unchanged. ### RBAC / Security Requirements - **SEC-302-001**: The audit MUST record whether each surface's expected denial semantics are non-member 404 and member-without-capability 403, where applicable. - **SEC-302-002**: The audit MUST treat UI visibility as non-authoritative and identify server-side authorization proof separately. - **SEC-302-003**: The audit MUST flag any surface where route/context access appears to depend only on navigation hiding. ### Auditability / Observability Requirements - **AUD-302-001**: This docs-only slice introduces no `AuditLog` event and no `OperationRun`. - **AUD-302-002**: The audit artifact must provide evidence references sufficient for reviewers to trace each conclusion back to repo files or tests. ### Data / Truth-Source Requirements - **DATA-302-001**: No product data or database schema is changed. - **DATA-302-002**: Tenant-owned runtime truth remains in existing models/tables; the audit artifact is repair evidence only. - **DATA-302-003**: The audit must not promote document classification labels into product state or persisted truth. ## Success Criteria *(mandatory)* - **SC-302-001**: `surface-route-audit.md` exists and covers the expected tenant-owned surface inventory. - **SC-302-002**: Every row has route, navigation, context, search, RBAC, proof, blocker, migration state, and next-action fields. - **SC-302-003**: The audit identifies at least one ordered follow-up path and explains why close alternatives are deferred or blocked. - **SC-302-004**: No application runtime files are changed. - **SC-302-005**: Existing focused Filament validation commands either pass or any failures are documented as pre-existing/runtime follow-up blockers. ## Assumptions - The user explicitly requested feature number `302`; `specs/302-tenant-owned-surface-route-audit` did not exist before this preparation. - `301-admin-inventory-navigation-cutover` is completed context and must not be rewritten. - The implementation deliverable is an audit artifact, not an application behavior change. - `TenantOwnedModelFamilies` is the starting inventory, but the audit may add admin-relevant pages/resources discovered from Filament files and tests. - The current Laravel/Filament stack is Laravel 12, Filament 5, Livewire 4, Pest 4, and PHP 8.4. ## Risks - The audit could become too broad if it tries to solve every route or IA problem it finds. - Document-only labels could be mistaken for runtime taxonomy if not clearly bounded. - Some tests may assert stale behavior; those must be reported as findings rather than edited in this spec. - A later implementation loop could accidentally patch runtime code unless the tasks keep the docs-only boundary explicit. ## Out of Scope - Any application code change. - Any migration, model, service, job, policy, command, view, route, Filament resource/page, Livewire component, or test edit. - Broad runtime route migration. - Mass navigation re-enablement. - Entra Groups cutover implementation. - Navigation contract split implementation. - Tenant-panel dead-code deletion. - New global-search enablement. - New audit log or OperationRun behavior. ## Follow-up Spec Candidates - `admin-directory-groups-cutover` - `navigation-contract-split`, only if the audit shows shared contract drift remains after bounded surface repairs - `tenant-panel-dead-code-retirement` - surface-specific repair specs named by `surface-route-audit.md` ## Manual Promotion Notes - **Selected candidate title**: `tenant-owned-surface-route-audit` - **Source location**: `docs/product/spec-candidates.md`, Admin Workspace Navigation & Tenant-owned Surface Repair candidate group. - **Why selected**: User explicitly promoted it as `302`; it is the recommended follow-up after `admin-inventory-navigation-cutover`. - **Close alternatives deferred**: `admin-directory-groups-cutover` needs the audit/product IA evidence first; `navigation-contract-split` should happen only if drift remains after bounded repairs; `tenant-panel-dead-code-retirement` should wait until dependencies are known. - **Roadmap relationship**: UI and Product Maturity Polish, parallel immediate repair lane for Admin Workspace Navigation & Tenant-owned Surface Repair. - **Completed-spec guardrail result**: Spec 301 has completed task markers and is treated as historical/completed context only. Specs 279-301 are not modified by this preparation. - **Smallest viable implementation slice**: Create the audit matrix and repair-prep order only; do not patch runtime.