# 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.
