# Implementation Plan: Environment-Owned Surface Routing & Shell Context Contract **Branch**: `319-environment-owned-surface-routing-shell-context-contract` | **Date**: 2026-05-16 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/319-environment-owned-surface-routing-shell-context-contract/spec.md) **Input**: Feature specification from `/specs/319-environment-owned-surface-routing-shell-context-contract/spec.md` **Preparation status**: Specification artifacts only. No runtime implementation has been performed by this preparation step. ## Summary Spec 319 hard-cuts Baseline Compare from an ambiguous workspace-style Filament page into an explicit Environment-owned admin surface: ```text Environment-owned Baseline Compare -> Workspace + Environment route -> Workspace + Environment shell -> copy/breadcrumb/header aligned -> no clean workspace-only route -> no ?environment_id filtered hub model -> no remembered Environment fallback ``` The implementation must make Baseline Compare self-sufficient through the existing Environment route family, update all Environment Dashboard and Environment-owned entry points, reject cross-workspace Environment access, and keep Decision Register/workspace hub contracts from Specs 314-316 green. ## Technical Context **Language/Version**: PHP 8.4.15, Laravel 12.52.0 **Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Laravel Sail, Laravel Socialite, Laravel MCP **Storage**: PostgreSQL; no schema changes planned **Testing**: Pest 4.3.1 / PHPUnit 12.5.4; focused browser smoke for shell/copy/URL behavior **Validation Lanes**: fast-feedback, confidence, browser **Target Platform**: Laravel admin application under `apps/platform`, local development through Sail, staging/production through Dokploy **Project Type**: Web application, Laravel/Filament admin panel **Performance Goals**: No material performance change. Route-bound Environment resolution should reuse existing Workspace/Environment model binding and shell resolution. **Constraints**: No migrations, seeders, packages, env vars, queues, scheduler, storage, compatibility redirects, legacy query aliases, or broad UI redesign. **Scale/Scope**: Focused route/shell/context hardening for Baseline Compare, with inspect-only handling for adjacent Environment-owned pages if they show the same mismatch. ## UI / Surface Guardrail Plan - **Guardrail scope**: Baseline Compare route/shell/copy/navigation contract and Environment Dashboard CTA URL generation. - **Native vs custom classification summary**: Existing Filament Page/Blade view and existing Environment Dashboard/Widget links. No new styling system. - **Shared-family relevance**: Navigation, dashboard CTA/action links, shell context, breadcrumbs, and OperationRun action links. - **State layers in scope**: Route params, shell context, Filament Page access, page copy, browser reload/history, CTA URL query. - **Audience modes in scope**: Operator-MSP and support-platform. - **Decision/diagnostic/raw hierarchy plan**: Baseline Compare remains a decision-support Environment posture page. Diagnostics/evidence details remain on existing page sections. - **Raw/support gating plan**: No raw/support evidence exposure change. - **One-primary-action / duplicate-truth control**: Environment route + shell become the single ownership truth; no hidden query/session fallback. - **Handling modes by drift class or surface**: Hard-stop for workspace-style Baseline Compare render; review-mandatory for any adjacent Environment-owned page touched. - **Repository-signal treatment**: Spec 318 browser evidence is the starting signal; implementation must produce tests and focused browser evidence. - **Special surface test profiles**: global-context-shell. - **Required tests or manual smoke**: route/classification tests, CTA URL tests, remembered-fallback rejection, cross-workspace rejection, Decision Register regression, browser smoke. - **Exception path and spread control**: If old URL redirect is unavoidable, it must be explicitly justified, validate Workspace/Environment relationship, emit no legacy alias support, and be covered by tests. Preferred path is no route/404. - **Active feature PR close-out entry**: Guardrail and Smoke Coverage. ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes. - **Systems touched**: `BaselineCompareLanding`, `AdminPanelProvider` page registration, route definitions if explicit page route is needed, `AdminSurfaceScope`, `WorkspaceScopedEnvironmentRoutes`, `WorkspaceSidebarNavigation`, `ManagedEnvironmentLinks`, `OperateHubShell`, Environment Dashboard summary/actions/widgets, Baseline Compare tests, workspace hub regression tests. - **Shared abstractions reused**: Existing Environment route pattern under `/admin/workspaces/{workspace}/environments/{environment}/...`, `WorkspaceScopedEnvironmentRoutes` where practical, `ManagedEnvironmentLinks`, existing `OperateHubShell`, existing OperationRun UX helpers. - **New abstraction introduced? why?**: none planned. - **Why the existing abstraction was sufficient or insufficient**: Existing Environment-bound pages work correctly; Baseline Compare bypasses them by using `/admin/baseline-compare-landing` plus `environment_id`/remembered context. - **Bounded deviation / spread control**: Any helper added must replace the current query-based Baseline Compare URL path, not add a second supported path. ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes, existing Baseline Compare action is high-impact but no new OperationRun lifecycle is introduced. - **Central contract reused**: `OperationUxPresenter`, `OpsUxBrowserEvents`, `OperationRunLinks`, existing Baseline Compare service behavior. - **Delegated UX behaviors**: queued toast, `Open operation` URL, run-enqueued browser event, tenant/workspace-safe URL resolution. - **Surface-owned behavior kept local**: Compare initiation button and Baseline Compare page state remain local. - **Queued DB-notification policy**: N/A. - **Terminal notification path**: Existing central lifecycle behavior. - **Exception path**: none. ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: yes. - **Provider-owned seams**: Microsoft/Intune compare details and provider tenant identity remain unchanged. - **Platform-core seams**: Workspace/Environment route ownership, shell classification, and query/fallback rejection. - **Neutral platform terms / contracts preserved**: Workspace, Environment, Environment-owned page, Workspace hub, Environment filter. - **Retained provider-specific semantics and why**: Existing baseline compare content still compares Microsoft/Intune-governed subjects because that is the current provider implementation. - **Bounded extraction or follow-up path**: Workspace-owned baseline analysis pages are deferred to Spec 320. ## Constitution Check *GATE: Must pass before implementation. Re-check after runtime changes.* - Inventory-first: no inventory/snapshot truth change. - Read/write separation: no new write behavior. Existing Compare Now remains confirmation + authorization + audit/OperationRun governed. - Graph contract path: no new Graph calls; page render must remain DB-only. - Deterministic capabilities: existing capability checks remain; tests must cover route/access boundaries. - RBAC-UX: non-member / not entitled Workspace or Environment access returns 404/safe no-access; member missing capability follows existing behavior. - Workspace isolation: route model must validate Environment belongs to Workspace. - Tenant isolation: no cross-workspace Environment leakage; no provider tenant ID lookup. - Run observability: existing Baseline Compare run behavior remains observable through OperationRun. - Test governance: lane, fixture cost, browser coverage, and reviewer handoff are explicit in spec/plan/tasks. - Proportionality: no new persisted truth or broad abstraction; route replacement is the narrowest correct fix. - No premature abstraction: reuse existing route/shell helpers. - Persisted truth: no tables/entities/artifacts added. - Behavioral state: no new state/status/reason family. - UI semantics: route/shell/copy direct mapping only. - Shared pattern first: reuse Environment route family and shared OperationRun helpers. - Provider boundary: route identity uses platform Environment, not provider tenant ID. - V1 explicitness / few layers: hard cutover, no compatibility layer. - Filament-native UI: no ad-hoc styling or published internals. - Filament v5 / Livewire v4: Livewire 4.1.4 satisfies Filament v5 requirement; no Livewire v3 APIs. - Provider registration: Laravel 12 panel providers remain in `apps/platform/bootstrap/providers.php`; this spec should not register panel providers in `bootstrap/app.php`. - Global search: no globally searchable resource behavior should change. If a touched Resource is affected, verify View/Edit/global-search status remains valid or disabled. - Destructive actions: no destructive action is added. Existing Compare Now high-impact action must keep `->action(...)`, `->requiresConfirmation()`, capability authorization, OperationRun UX, and audit behavior. - Asset strategy: no Filament assets planned; no new `filament:assets` deployment requirement. ## Test Governance Check - **Test purpose / classification by changed surface**: Unit for classifier/registry paths; Feature/Livewire for route/access/render/action URL contracts; Browser for integrated URL/shell/copy/reload/history behavior. - **Affected validation lanes**: fast-feedback, confidence, browser. - **Why this lane mix is the narrowest sufficient proof**: The defect spans code classification and browser-visible shell/copy; both must be proven. - **Narrowest proving command(s)**: - `cd apps/platform && ./vendor/bin/sail artisan test --filter=BaselineCompare` - `cd apps/platform && ./vendor/bin/sail artisan test --filter=AdminSurfaceScope` - `cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubRegistry` - `cd apps/platform && ./vendor/bin/sail artisan test --filter=DecisionRegister` - focused browser smoke for Spec 319 - `cd apps/platform && ./vendor/bin/sail pint --test` - `git diff --check` - **Fixture / helper / factory / seed / context cost risks**: Existing workspace/environment/member setup plus baseline assignment/snapshot/run fixtures. Keep setup local to the new tests and avoid broad shared defaults. - **Expensive defaults or shared helper growth introduced?**: no. - **Heavy-family additions, promotions, or visibility changes**: Focused browser smoke only. Durable browser no-drift family is Spec 322. - **Surface-class relief / special coverage rule**: global-context-shell requires browser verification. - **Closing validation and reviewer handoff**: Reviewers must verify old URL invalidation, no query alias support, no remembered fallback, cross-workspace 404, and Decision Register regression. - **Budget / baseline / trend follow-up**: none expected. - **Review-stop questions**: Does any Baseline Compare path still render from `/admin/baseline-compare-landing`? Does any CTA still emit `environment_id`? Does any code path use remembered Environment to render Baseline Compare? Are Baselines/Baseline Snapshots accidentally pulled into this spec? - **Escalation path**: follow-up-spec for 320/321/322 only; update this spec if implementation reveals a route architecture blocker. - **Active feature PR close-out entry**: Guardrail and Smoke Coverage. - **Why no dedicated follow-up spec is needed**: Spec 319 is the dedicated Baseline Compare Environment-owned route fix. ## Project Structure ### Documentation (this feature) ```text specs/319-environment-owned-surface-routing-shell-context-contract/ |-- spec.md |-- plan.md |-- tasks.md |-- checklists/ | `-- requirements.md `-- artifacts/ `-- screenshots/ ``` No `research.md`, `data-model.md`, `quickstart.md`, or `contracts/` artifact is required for preparation because this feature introduces no new data model, external API contract, or standalone workflow API. ### Source Code (repository root) Likely runtime files to inspect or update during implementation: ```text apps/platform/routes/web.php apps/platform/app/Providers/Filament/AdminPanelProvider.php apps/platform/app/Filament/Pages/BaselineCompareLanding.php apps/platform/app/Filament/Concerns/UsesAdminEnvironmentFilterQueryParameter.php apps/platform/app/Filament/Concerns/WorkspaceScopedEnvironmentRoutes.php apps/platform/app/Support/Navigation/AdminSurfaceScope.php apps/platform/app/Support/Navigation/WorkspaceHubRegistry.php apps/platform/app/Support/Navigation/WorkspaceSidebarNavigation.php apps/platform/app/Support/ManagedEnvironmentLinks.php apps/platform/app/Support/OperateHub/OperateHubShell.php apps/platform/app/Support/Middleware/EnsureEnvironmentContextSelected.php apps/platform/app/Filament/Pages/EnvironmentDashboard.php apps/platform/app/Support/EnvironmentDashboard/EnvironmentDashboardSummaryBuilder.php apps/platform/app/Filament/Widgets/ManagedEnvironment/BaselineCompareCoverageBanner.php apps/platform/app/Filament/Widgets/Dashboard/BaselineCompareNow.php apps/platform/app/Filament/Widgets/Dashboard/NeedsAttention.php apps/platform/app/Filament/Pages/BaselineCompareMatrix.php apps/platform/resources/views/filament/pages/baseline-compare-landing.blade.php apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php apps/platform/tests/Feature/Workspaces/GlobalContextShellContractTest.php apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php apps/platform/tests/Feature/Filament/BaselineCompareLandingAdminTenantParityTest.php apps/platform/tests/Feature/Filament/BaselineCompareLandingStartSurfaceTest.php apps/platform/tests/Feature/Governance/DecisionRegisterWorkspaceHubContractTest.php apps/platform/tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php apps/platform/tests/Feature/Navigation/WorkspaceHubClearFilterContractTest.php apps/platform/tests/Browser/ ``` Related pages to inspect, not automatically rewrite: ```text apps/platform/app/Filament/Pages/EnvironmentRequiredPermissions.php apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php apps/platform/app/Filament/Pages/InventoryCoverage.php apps/platform/app/Filament/Resources/InventoryItemResource.php apps/platform/app/Filament/Resources/BackupScheduleResource.php apps/platform/app/Filament/Resources/BackupSetResource.php apps/platform/app/Filament/Resources/RestoreRunResource.php apps/platform/app/Filament/Resources/FindingResource.php apps/platform/app/Filament/Resources/FindingExceptionResource.php ``` Explicitly out-of-scope unless analysis proves Environment ownership: ```text apps/platform/app/Filament/Resources/BaselineProfileResource.php apps/platform/app/Filament/Resources/BaselineSnapshotResource.php apps/platform/app/Filament/Pages/BaselineCompareMatrix.php apps/platform/app/Filament/Pages/CrossEnvironmentComparePage.php apps/platform/app/Filament/Pages/Monitoring/AuditLog.php apps/platform/app/Filament/Pages/Monitoring/Alerts.php apps/platform/app/Filament/Resources/Alert* ``` **Structure Decision**: Laravel/Filament platform app under `apps/platform`; any runtime changes stay inside existing Page, Support, route, view, and test locations. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |---|---|---| | None planned | Route ownership can use existing Environment route/shell mechanisms | New compatibility adapters, query resolvers, or route frameworks are explicitly rejected | ## Proportionality Review - **Current operator problem**: Baseline Compare renders Environment-specific copy/data through hidden context instead of explicit route ownership. - **Existing structure is insufficient because**: `/admin/baseline-compare-landing` cannot self-sufficiently establish Environment context after clear/reload and should not use `environment_id` as a workspace hub filter. - **Narrowest correct implementation**: Make Baseline Compare route-bound to Workspace + Environment, update links, and reject old access. - **Ownership cost created**: Focused tests and browser evidence for a high-risk page. - **Alternative intentionally rejected**: Supporting both old and new routes, redirecting old URLs by default, or accepting `environment_id` as canonical page context. - **Release truth**: Current-release hardening before production. ## Phase 0: Discovery Completed During Preparation Repository facts confirmed before authoring this plan: - Current branch before Spec Kit creation was `platform-dev`, clean, at `1c27af4f spec: add admin surface scope shell context audit (#373)`. - No existing `319-*` branch or `specs/319-*` package was present. - Spec Kit script created branch `319-environment-owned-surface-routing-shell-context-contract` and `specs/319-environment-owned-surface-routing-shell-context-contract/spec.md`. - Spec 318 is analysis-only and does not have plan/tasks files; it provides `mismatch-findings.md`, `audit-report.md`, `recommended-fixes.md`, `page-matrix.md`, screenshots, and state captures. - Spec 318 finding M1: Baseline Compare URLs are not self-sufficient. Direct clean and direct `environment_id` URLs return 403 after clearing Environment context; environment sidebar works only with remembered context. - Spec 318 recommended fix: route-bound Environment URL under `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare`. - `BaselineCompareLanding` currently extends `Page`, is registered in `AdminPanelProvider`, uses `ResolvesPanelTenantContext` and `UsesAdminEnvironmentFilterQueryParameter`, and loads stats through `BaselineCompareStats::forTenant()`. - `BaselineCompareLanding::canAccess()` returns false without `ManagedEnvironment` from `resolveTenantContextForCurrentPanel()`. - `UsesAdminEnvironmentFilterQueryParameter` appends `environment_id` to the unbound page URL when `tenant` is provided. - `EnvironmentDashboardSummaryBuilder`, `BaselineCompareCoverageBanner`, `BaselineCompareNow`, `NeedsAttention`, and `BaselineCompareMatrix` currently generate Baseline Compare links via `BaselineCompareLanding::getUrl(tenant: $tenant)`. - `WorkspaceHubRegistry` does not list Baseline Compare; this is correct and must remain true. - `AdminSurfaceScope` currently treats `/admin/workspaces/{workspace}/environments/{environment}/...` as `EnvironmentBound`. - `EnsureEnvironmentContextSelected` already configures environment navigation for environment surfaces and workspace sidebar for workspace surfaces. - Existing test `BaselineCompareLandingAdminTenantParityTest` asserts access from remembered admin tenant; this likely asserts now-invalid behavior and must be replaced/updated. ## Technical Approach ### 1. Establish canonical Environment route Preferred route shape: ```text /admin/workspaces/{workspace}/environments/{environment}/baseline-compare ``` Use the repo's existing route-bound Environment pattern. The route should: - run through admin panel middleware and workspace membership checks; - use existing `workspace` route binding; - use `{environment:slug}` or the established ManagedEnvironment route key; - validate Environment belongs to Workspace; - fail safe with 404/safe no-access on mismatch or missing entitlement; - produce `AdminSurfaceScope::EnvironmentBound`. Implementation should prefer one canonical route and remove/disable the old unbound page route. If Filament Page auto-registration cannot be moved directly, wrap or override route generation narrowly without creating a compatibility layer. ### 2. Update Baseline Compare URL generation Replace query-based Baseline Compare URL generation with route-owned URL generation for: - Environment Dashboard summary/action payloads; - managed-environment Baseline Compare coverage banner; - dashboard Baseline Compare widget; - Needs Attention widget; - Baseline Compare Matrix per-Environment links; - any direct `BaselineCompareLanding::getUrl(tenant: $tenant)` call that opens the Environment-owned page. The generated URL must not include: ```text environment_id tenant tenant_id managed_environment_id tenant_scope tableFilters ``` ### 3. Remove remembered fallback for Baseline Compare Baseline Compare must render only when route context resolves an Environment. Implementation must not: - rely on `WorkspaceContext::rememberedEnvironment()`; - rely on `Filament::getTenant()` as fallback for old URL; - infer from provider tenant identifiers; - treat `environment_id` as page context; - silently switch Workspace. If `BaselineCompareLanding` keeps `ResolvesPanelTenantContext`, its route/category must force the context to come from the route. ### 4. Classification and navigation Baseline Compare should classify as Environment-owned through the Environment route. Tests must prove: - new canonical path is `AdminSurfaceScope::EnvironmentBound`; - old path is not a workspace hub and does not render; - `WorkspaceHubRegistry` does not include Baseline Compare; - workspace sidebar does not show Baseline Compare as a workspace-wide hub; - environment sidebar/action placement remains Environment-owned if present. ### 5. Cross-workspace and no-access protection Route/model resolution must enforce: ```php (int) $environment->workspace_id === (int) $workspace->getKey() ``` Invalid combinations return 404/safe no-access. Do not leak Environment existence, switch workspace, or fallback to remembered Environment. ### 6. Shell, breadcrumb, copy The valid page must show: - selected Workspace; - selected Environment; - Baseline Compare header/title; - breadcrumb or equivalent ownership signal; - "this environment" copy only with active Environment shell. When Environment context is missing, the page should not render. ### 7. Regression scope Keep these contracts unchanged: - Decision Register clean workspace hub route; - Decision Register `environment_id` filtered workspace hub route with visible chip; - clear filter behavior from Spec 316; - legacy tenant cleanup from Spec 317; - workspace hubs from Specs 314-316. ## Implementation Phases 1. Guardrail and discovery: re-check branch/status, confirm exact current route names, inspect affected Baseline Compare links, and record the old route behavior. 2. Tests first/alongside: add classification, access, route, CTA, remembered fallback, cross-workspace, copy/shell, and Decision Register regression tests. 3. Route/link implementation: make canonical Environment route authoritative and update Baseline Compare URL generators. 4. Shell/copy/navigation cleanup: remove query/remembered fallback assumptions and ensure breadcrumbs/shell align. 5. Related page inspection: only fix pages with the same proven Environment-owned mismatch. 6. Validation: focused Pest, browser smoke, formatting, `git diff --check`, and implementation close-out. ## Migration / Data / Deployment Impact - **Migrations**: none planned. - **Seeders**: none planned. - **Packages**: none planned. - **Env vars**: none planned. - **Queues/scheduler/storage**: no changes planned. - **Filament assets**: no new assets planned; no new `filament:assets` deployment step. - **Dokploy/Sail**: ordinary code deployment only if implementation changes runtime files. ## Browser Verification Plan Use the in-app browser or the repository's browser smoke workflow against local Sail once runtime changes exist. Required screenshots: ```text specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-clean--baseline-compare--rejected.png specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-filtered--baseline-compare--rejected.png specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--after-reload.png specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--back-forward.png specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/decision-register--regression.png ``` If a route is fully absent and cannot be screenshotted, document route absence in the close-out and capture the nearest browser proof if useful. ## Risks and Controls - **Risk**: Old tests assert remembered Environment fallback. **Control**: Replace them with no-remembered-fallback and canonical route tests. - **Risk**: Filament auto page registration keeps old slug alive. **Control**: Use the narrowest supported page route override or explicit Laravel route pattern and test old URL invalidation. - **Risk**: Link helpers spread query-based Baseline Compare URLs. **Control**: `rg` all `BaselineCompareLanding::getUrl`, `baseline-compare-landing`, and `environment_id` occurrences and update only in-scope Baseline Compare links. - **Risk**: Workspace-owned baseline pages get pulled into scope. **Control**: Keep Baselines/Baseline Snapshots/Matrix out unless proven Environment-owned; record follow-up 320. - **Risk**: Browser flow depends on local fixture data. **Control**: Use existing local fixture environment from Spec 318 where possible or create test data through normal app factories/tests; document browser blockers. ## Readiness Gate Spec 319 is implementation-ready when: - `spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md` exist. - The scope is Baseline Compare-first and bounded. - No open question blocks implementation. - Required tests and browser flows are named. - No application implementation has happened during prep. - Candidate Selection Gate and Spec Readiness Gate pass.