spec: add environment-owned surface routing contract
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m42s

This commit is contained in:
Ahmed Darrazi 2026-05-17 00:24:28 +02:00
parent ddf7c15c52
commit cce0904e87
11 changed files with 1142 additions and 0 deletions

View File

@ -0,0 +1 @@
Spec 319 browser verification screenshots are saved here during implementation.

View File

@ -0,0 +1,75 @@
# Specification Quality Checklist: Environment-Owned Surface Routing & Shell Context Contract
**Purpose**: Validate specification completeness and quality before implementation
**Created**: 2026-05-16
**Feature**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/319-environment-owned-surface-routing-shell-context-contract/spec.md)
## Candidate Selection Gate
- [x] Explicit user-provided Spec 319 request was selected as the source of truth for this preparation pass.
- [x] Completed-spec guardrail checked that no existing `specs/319-*` package was present before generation.
- [x] Specs 313, 314, 315, 316, 317, and 318 were treated as dependency/historical context, not rewritten.
- [x] Roadmap/spec-candidate queue was reviewed; active auto-prep queue is empty, so this package proceeds only because the user directly supplied/promoted Spec 319.
- [x] Close alternatives were deferred to follow-up specs 320, 321, and 322.
- [x] The selected slice is Baseline Compare Environment-owned route/shell hardening only.
## Content Quality
- [x] Problem statement is operator-visible and tied to route/shell/copy mismatch.
- [x] User value is clear: self-sufficient Environment-owned Baseline Compare with no hidden fallback.
- [x] Scope is bounded to Baseline Compare, with related pages inspect-only.
- [x] Hard cutover/no compatibility posture is explicit.
- [x] No unresolved `[NEEDS CLARIFICATION]` markers remain.
- [x] Mandatory Spec Candidate Check is complete.
- [x] Spec Scope Fields are complete.
- [x] Shared pattern, OperationRun, provider boundary, UI/surface, testing, acceptance, and browser sections are complete.
## Requirement Completeness
- [x] Functional requirements are testable and unambiguous.
- [x] Requirements cover canonical Environment route, old route rejection, cross-workspace protection, CTA URL generation, shell/copy, reload, back/forward, and Decision Register regression.
- [x] Non-goals prevent Spec 320/321/322 scope creep.
- [x] Edge cases are identified.
- [x] Assumptions and risks are documented.
- [x] Success criteria are measurable.
- [x] Open questions do not block implementation.
## Plan Quality
- [x] Laravel, Filament, Livewire, Pest, PostgreSQL, Sail, and Dokploy context is recorded.
- [x] Livewire v4.0+ compliance is explicitly noted through Livewire 4.1.4.
- [x] Laravel 12 panel provider location remains `apps/platform/bootstrap/providers.php`.
- [x] Global search impact is assessed as unchanged unless a Resource is touched.
- [x] Destructive/high-impact action handling is addressed for existing Compare Now.
- [x] Asset strategy is assessed as no new Filament assets/no new `filament:assets` step.
- [x] No migration, seeder, package, env var, queue, scheduler, storage, or deployment asset change is planned.
- [x] Existing repo seams are named.
- [x] Test strategy and browser verification plan are concrete.
## Task Quality
- [x] Tasks are ordered from guardrails/tests through runtime changes, browser verification, and final validation.
- [x] Tests are required before or alongside implementation.
- [x] Task IDs follow the required checkbox format.
- [x] File paths are concrete where repo surfaces are known.
- [x] Non-tasks explicitly prevent compatibility layers, query alias support, and follow-up-spec scope creep.
- [x] Browser screenshot paths are specified.
- [x] Validation commands are specified.
## Constitution Alignment
- [x] Workspace and Environment isolation are covered.
- [x] Cross-workspace Environment access is 404/safe no-access.
- [x] No new persisted truth is introduced.
- [x] No new abstraction/framework/taxonomy is planned.
- [x] OperationRun semantics are preserved for existing Compare Now behavior.
- [x] Audit/authorization/confirmation expectations for high-impact action remain explicit.
- [x] Test governance lane impact is explicit.
- [x] Provider/platform boundary is explicit.
## Readiness Result
- [x] Candidate Selection Gate passes.
- [x] Spec Readiness Gate passes.
- [x] Ready for separate implementation loop.
- [x] No application implementation was performed during this preparation step.

View File

@ -0,0 +1,403 @@
# 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.

View File

@ -0,0 +1,489 @@
# Feature Specification: Environment-Owned Surface Routing & Shell Context Contract
**Feature Branch**: `319-environment-owned-surface-routing-shell-context-contract`
**Created**: 2026-05-16
**Status**: Completed
**Input**: User supplied Spec 319 draft: Baseline Compare and similar Environment-owned pages must route through explicit Workspace + Environment ownership, with no legacy workspace-style access, no `environment_id` filter model, and no remembered Environment fallback.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: Baseline Compare behaves as an Environment-owned page but currently uses an unbound workspace-style Filament page URL, so route, shell, breadcrumb, copy, and access semantics can disagree.
- **Today's failure**: Operators can see "this environment" copy only because remembered Environment state happens to exist; after context clear, direct clean and direct `environment_id` URLs fail or behave inconsistently instead of being self-sufficient route-owned Environment pages.
- **User-visible improvement**: Baseline Compare is opened from an Environment route, reloads safely, shows Workspace + Environment shell context, rejects cross-workspace access, and no longer looks like a workspace hub or hidden filter state.
- **Smallest enterprise-capable version**: Hard-cut Baseline Compare to a canonical route-bound Environment page and update its entry points, classification, shell/breadcrumb/copy, and regression coverage. Inspect adjacent Environment-owned pages only for the same mismatch.
- **Explicit non-goals**: No workspace-wide Baseline Compare, no `environment_id` filter support for Baseline Compare, no compatibility redirects, no legacy query aliases, no Spec 320 workspace-owned analysis fixes, no Spec 321 Alerts/Audit filter decision, no durable browser infrastructure from Spec 322.
- **Permanent complexity imported**: No new persisted entities, tables, enum families, status families, broad registries, or cross-domain frameworks. Expected complexity is route/classification/link/test updates over existing Filament/Laravel structures.
- **Why now**: Spec 318 identified Baseline Compare as the highest-risk remaining shell/context mismatch after Specs 314-317 stabilized workspace hubs and legacy cleanup.
- **Why not local**: A copy-only or CTA-only fix would leave direct URL, reload, cross-workspace, shell, and remembered-fallback risks unresolved.
- **Approval class**: Cleanup / Consolidation.
- **Red flags triggered**: "Many surfaces, little user value" is avoided by making Baseline Compare required and keeping other pages inspect-only unless the same mismatch is proven. No new axis or framework is introduced.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitat: 2 | Produktnahe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12**
- **Decision**: approve.
## Spec Scope Fields *(mandatory)*
- **Scope**: workspace + environment route-owned admin surface.
- **Primary Routes**: Baseline Compare, canonical Environment Dashboard Baseline Compare CTA/action, relevant Environment sidebar/navigation entry if present.
- **Data Ownership**: Existing workspace-owned Baseline Profile/Snapshot truth and Environment-owned compare/assignment/result state only. No schema changes.
- **RBAC**: Workspace membership plus Environment entitlement. Non-member or cross-workspace Environment access returns 404/safe no-access. Member missing capability follows existing capability behavior.
For canonical-view specs:
- **Default filter behavior when tenant-context is active**: Not a canonical workspace view. Baseline Compare must not use active/remembered Environment as fallback.
- **Explicit entitlement checks preventing cross-tenant leakage**: Environment route resolution must validate `managed_environments.workspace_id === workspaces.id` before rendering or revealing Environment existence.
## Cross-Cutting / Shared Pattern Reuse *(mandatory)*
- **Cross-cutting feature?**: yes.
- **Interaction class(es)**: navigation entry points, shell/context bar, breadcrumbs, dashboard CTAs, route helpers, page copy, action links.
- **Systems touched**: `AdminSurfaceScope`, `WorkspaceHubRegistry`, `WorkspaceSidebarNavigation`, `WorkspaceScopedEnvironmentRoutes`, `ManagedEnvironmentLinks`, `OperateHubShell`, `BaselineCompareLanding`, Environment Dashboard summary/action builders, relevant widgets, routes, and tests.
- **Existing pattern(s) to extend**: Route-bound Environment pattern under `/admin/workspaces/{workspace}/environments/{environment}/...` and existing workspace/environment shell resolution.
- **Shared contract / presenter / builder / renderer to reuse**: Reuse `WorkspaceScopedEnvironmentRoutes`/route-bound Environment conventions where possible, `ManagedEnvironmentLinks` for canonical Environment URLs if a helper is needed, and existing Filament navigation/page patterns.
- **Why the existing shared path is sufficient or insufficient**: Existing route-bound Environment pages already produce correct shell behavior. Baseline Compare is the outlier because it remains a Filament page on `/admin/baseline-compare-landing` plus query/helper fallback.
- **Allowed deviation and why**: None for Baseline Compare. A redirect from old workspace-style URLs is disallowed unless implementation discovers an existing mandatory convention and covers it with tests.
- **Consistency impact**: Environment-owned pages must not behave as workspace hubs or filtered workspace hubs. Copy saying "this environment" is valid only with active Environment shell context.
- **Review focus**: Verify no compatibility path, query alias, remembered fallback, or workspace hub registry entry makes Baseline Compare render outside route-owned Environment context.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, existing Baseline Compare "Compare now" action and run links must keep their current UX contract.
- **Shared OperationRun UX contract/layer reused**: Existing `OperationUxPresenter`, `OpsUxBrowserEvents`, and `OperationRunLinks`.
- **Delegated start/completion UX behaviors**: Existing queued toast, "Open operation" link, tenant/workspace-safe URL resolution, and run-enqueued browser event remain delegated to existing shared helpers.
- **Local surface-owned behavior that remains**: Baseline Compare page still owns initiation inputs and page state.
- **Queued DB-notification policy**: N/A - no new notification policy.
- **Terminal notification path**: Existing OperationRun lifecycle only.
- **Exception required?**: none.
## Provider Boundary / Platform Core Check
- **Shared provider/platform boundary touched?**: yes.
- **Boundary classification**: platform-core route/shell contract; provider-specific Tenant identity remains provider-owned only.
- **Seams affected**: route shape, navigation helpers, page classification, shell resolution, query key handling, and visible Workspace/Environment copy.
- **Neutral platform terms preserved or introduced**: `Workspace`, `Environment`, `Environment-owned page`, `Workspace hub`, `filtered workspace hub`, `Baseline Compare`.
- **Provider-specific semantics retained and why**: Existing Microsoft/Intune baseline compare domain behavior remains unchanged. Provider tenant IDs must not become route or fallback identifiers for this page.
- **Why this does not deepen provider coupling accidentally**: The canonical route uses platform Workspace + Managed Environment route identity, not Microsoft tenant IDs or Graph payload identity.
- **Follow-up path**: Specs 320, 321, and 322 handle adjacent workspace analysis, Alerts/Audit decisions, and durable browser guards.
## UI / Surface Guardrail Impact
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Baseline Compare | yes | Existing Filament Page + Blade view | navigation, shell, breadcrumb, action links | route, shell, page, copy | no | Route/shell hardening only; no redesign. |
| Environment Dashboard Baseline Compare CTA | yes | Existing Filament/Blade summary/action | dashboard CTA, navigation | URL generation | no | Must emit Environment-owned route, not `environment_id`. |
| Decision Register regression | yes | Existing Filament Page | workspace hub/filter contract | URL query, shell, chip | no | Regression only; behavior must not change. |
## Decision-First Surface Role
| 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 |
|---|---|---|---|---|---|---|---|
| Baseline Compare | Secondary Context | Operator decides whether the selected Environment has baseline assignment/result work to run or review | Environment identity, assignment state, compare posture, next action | run detail, findings, evidence gaps, compare matrix | It is Environment governance context, not workspace-wide triage | Opened from Environment Dashboard/governance posture | Removes hidden context reconstruction and broken direct links. |
| Environment Dashboard Baseline Compare CTA | Primary Decision Link | Operator follows Environment posture finding to compare posture | Baseline compare status/action | Baseline Compare page | Link must preserve Environment ownership | Environment Dashboard -> Environment-owned Baseline Compare | One click to the correct scoped page. |
| Decision Register | Primary Decision Surface | Operator reviews workspace governance decisions | workspace shell, optional Environment filter chip if filtered | finding/exception/evidence links | Regression only | Existing workspace hub | Prevents accidental shell/filter drift. |
## Audience-Aware Disclosure
| 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 |
|---|---|---|---|---|---|---|---|
| Baseline Compare | operator-MSP, support-platform | Environment, assignment state, compare posture, next action | coverage, evidence gaps, run links | raw Graph payloads remain out of scope/default-hidden | Assign baseline, compare now, or review results depending on state | raw payloads/debug details | Shell and copy must say the same Environment truth once. |
| Environment Dashboard CTA | operator-MSP | Baseline compare card/action | none | none | Open Baseline Compare | N/A | CTA URL itself carries Environment ownership. |
## UI/UX Surface Classification
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Baseline Compare | Workflow / Page | Environment-owned governance posture page | Open compare, compare now, view run/findings | Page-level Environment route | N/A | Header/actions or existing page sections | Existing Compare Now confirmation stays header/action | N/A | `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare` | Workspace + Environment shell | Baseline Compare | selected Environment, baseline assignment/result state | none |
| Decision Register | List / Hub | Workspace hub / filtered workspace hub | Review decisions | existing page/table | existing behavior | existing behavior | existing behavior | `/admin/governance/decisions` | existing detail links | Workspace shell, optional filter chip | Decision register | workspace-wide or filtered decision state | regression only |
## Operator Surface Contract
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Baseline Compare | Workspace operator / manager | Decide whether to assign baseline, run compare, or inspect compare outcome for one Environment | Environment-owned posture page | What is this Environment's baseline compare posture? | Environment shell, assignment state, compare status, next action | evidence gaps, run detail links, compare matrix | assignment, compare state, data completeness, governance result | TenantPilot operation queue and Microsoft read/sync behavior as already implemented | Compare now, view findings/run, open matrix | Existing Compare Now is high-impact and must keep confirmation + authorization. |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no.
- **New persisted entity/table/artifact?**: no.
- **New abstraction?**: no planned abstraction; reuse existing route/shell helpers.
- **New enum/state/reason family?**: no.
- **New cross-domain UI framework/taxonomy?**: no.
- **Current operator problem**: Environment-owned Baseline Compare is not route-owned and can show environment copy only because hidden context exists.
- **Existing structure is insufficient because**: The current unbound Filament page URL and `environment_id` query helper cannot reliably establish shell/context after environment context is cleared.
- **Narrowest correct implementation**: Put Baseline Compare on the existing Environment route family, update links/classification, and remove invalid workspace-style access.
- **Ownership cost**: Focused route/link/page tests and browser smoke for a high-risk page.
- **Alternative intentionally rejected**: Accept `environment_id` query or remembered session fallback for Baseline Compare; both preserve ambiguity and conflict with Specs 314-317.
- **Release truth**: Current-release cleanup before production.
### Compatibility posture
This feature assumes pre-production hard cutover.
Backward compatibility, legacy aliases, old workspace-style Baseline Compare routes, redirects, migration shims, and compatibility-specific tests are out of scope.
Canonical replacement is preferred over preservation.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Unit for classifier/registry helpers; Feature/Livewire for route/access/page rendering and CTA URL generation; Browser for shell/copy/reload/back-forward verification.
- **Validation lane(s)**: fast-feedback, confidence, browser.
- **Why this classification and these lanes are sufficient**: Unit/feature tests prove route and authorization contracts; browser smoke is required because the issue is visible shell/copy/navigation drift.
- **New or expanded test families**: Focused Spec 319 route/shell tests. Browser screenshots under this spec folder. No durable browser framework.
- **Fixture / helper cost impact**: Existing Workspace, ManagedEnvironment, membership/capability, baseline assignment/snapshot/run factories. Avoid new global seeders or broad helpers.
- **Heavy-family visibility / justification**: Browser smoke is explicit and limited to Baseline Compare plus Decision Register regression.
- **Special surface test profile**: global-context-shell.
- **Standard-native relief or required special coverage**: Special coverage required for route/shell/copy consistency.
- **Reviewer handoff**: Verify no app code can render Baseline Compare from clean workspace-only URL, filtered workspace-style URL, remembered Environment, or cross-workspace Environment.
- **Budget / baseline / trend impact**: none expected.
- **Escalation needed**: none for 319; adjacent surfaces deferred to 320/321/322.
- **Active feature PR close-out entry**: Guardrail and Smoke Coverage.
- **Planned validation commands**:
- `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 flows
- `cd apps/platform && ./vendor/bin/sail pint --test`
- `git diff --check`
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Open Baseline Compare from Environment context (Priority: P1)
As an operator on an Environment Dashboard, I can open Baseline Compare through an Environment-owned URL and see Workspace + Environment shell context, so the page's "this environment" copy is truthful.
**Why this priority**: This fixes the highest-risk Spec 318 mismatch and makes the main Environment Dashboard CTA safe.
**Independent Test**: From an authorized Workspace A / Environment A context, click the Baseline Compare CTA and assert route, shell, header, copy, and data scope all point to Environment A.
**Acceptance Scenarios**:
1. **Given** Workspace A and Environment A, **When** the operator opens Baseline Compare from Environment Dashboard, **Then** the URL includes Workspace A and Environment A route identity and no `environment_id` query.
2. **Given** Environment A has no assigned baseline, **When** Baseline Compare renders, **Then** the shell shows Workspace A + Environment A and copy may say "this environment does not have an assigned baseline yet."
3. **Given** the operator reloads the page, **When** the browser refreshes, **Then** the same Workspace + Environment shell and copy remain aligned without session fallback.
---
### User Story 2 - Reject invalid workspace-style access (Priority: P1)
As a security reviewer, I need workspace-only and workspace-style filtered Baseline Compare URLs to fail safely, so hidden remembered Environment state cannot render Environment-specific pages.
**Why this priority**: The old URL model is the root of the shell/context ambiguity.
**Independent Test**: Clear remembered Environment context, open old clean and filtered Baseline Compare URLs, and assert the page does not render.
**Acceptance Scenarios**:
1. **Given** remembered Environment state exists, **When** it is cleared and the old clean Baseline Compare URL is opened, **Then** the page returns 404/safe no-access or no route and does not render Baseline Compare.
2. **Given** a workspace-style Baseline Compare URL includes `?environment_id=...`, **When** it is opened, **Then** it does not render as canonical Baseline Compare and does not use the query as a filter.
3. **Given** Workspace A and Environment B from Workspace B, **When** a URL combines them, **Then** the response is 404/safe no-access and does not reveal Environment B.
---
### User Story 3 - Preserve workspace hub behavior (Priority: P2)
As an operator using Decision Register and other workspace hubs, I need Spec 319 not to regress the clean workspace hub, filtered workspace hub, and clear-filter contracts from Specs 314-316.
**Why this priority**: Baseline Compare must move out of ambiguous workspace-style handling without destabilizing the workspace hub model.
**Independent Test**: Open Decision Register clean and filtered URLs and verify shell/filter behavior still matches Specs 314-316.
**Acceptance Scenarios**:
1. **Given** the operator opens Decision Register from workspace sidebar, **When** the page loads, **Then** shell has Workspace only and no active Environment.
2. **Given** the operator opens Decision Register with `environment_id`, **When** the page loads, **Then** it shows a visible Environment filter chip and shell remains Workspace only.
3. **Given** the operator clears the Decision Register filter, **When** the page reloads, **Then** URL and state return to the clean workspace hub.
### Edge Cases
- Environment route contains an Environment from another Workspace.
- Environment exists but the actor is not entitled to it.
- Workspace context is missing but the route contains a valid Environment.
- Environment has no baseline assignment.
- Environment has assignment but no usable active snapshot.
- Environment has a running, failed, partially succeeded, or stale compare run.
- Browser back/forward moves between Environment-owned Baseline Compare and workspace-owned Decision Register.
- A legacy query includes `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, `environment`, or `tableFilters`.
- Existing Compare Now action is rendered after the route change and still has confirmation, capability gating, and OperationRun UX.
## Requirements *(mandatory)*
**Constitution alignment (required):** This feature introduces no Graph calls, no new write behavior, and no new long-running/queued work. Existing Baseline Compare run behavior remains in place and must keep `OperationRun` observability, authorization, confirmation, and tests.
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** The spec introduces no new persistence, no new state family, no new broad abstraction, and no new taxonomy. It replaces ambiguous routing with existing Environment route ownership.
**Constitution alignment (XCUT-001):** Baseline Compare links and shell state reuse existing route, navigation, shell, and OperationRun helper paths rather than adding a parallel local context mechanism.
### Functional Requirements
- **FR-001**: Baseline Compare MUST be classified as an Environment-owned page.
- **FR-002**: Baseline Compare MUST NOT be listed in `WorkspaceHubRegistry` or treated as a workspace hub.
- **FR-003**: Baseline Compare MUST NOT use `WorkspaceHubEnvironmentFilter` or `environment_id` as its primary access model.
- **FR-004**: Baseline Compare MUST have a canonical Environment-owned route that includes Workspace and Environment identity.
- **FR-005**: The canonical route MUST validate that the Environment belongs to the Workspace before rendering.
- **FR-006**: Cross-workspace Environment route access MUST return 404/safe no-access without workspace switching or existence leakage.
- **FR-007**: Clean workspace-only Baseline Compare URL MUST NOT render the page.
- **FR-008**: Workspace-style Baseline Compare URL with `?environment_id=...` MUST NOT render as canonical Baseline Compare.
- **FR-009**: Baseline Compare MUST NOT resolve Environment from remembered Environment, last Environment, Filament tenant fallback, provider tenant ID, or session fallback.
- **FR-010**: Valid canonical Baseline Compare route MUST show Workspace + active Environment shell context.
- **FR-011**: Baseline Compare breadcrumbs/header/copy MUST indicate Environment ownership and align with shell context.
- **FR-012**: Copy saying "this environment" MUST NOT render without active Environment shell context.
- **FR-013**: Environment Dashboard Baseline Compare CTA/action MUST use the canonical Environment-owned route.
- **FR-014**: Environment Dashboard Baseline Compare CTA/action MUST emit no `environment_id`, `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, or `tableFilters`.
- **FR-015**: Environment sidebar/navigation entry for Baseline Compare, if present, MUST be visibly Environment-owned and route-bound.
- **FR-016**: Baseline Compare MUST not appear as a workspace-wide sidebar hub.
- **FR-017**: Existing Compare Now action MUST preserve `->action(...)`, `->requiresConfirmation()`, server-side authorization/capability enforcement, OperationRun start UX, and audit behavior.
- **FR-018**: Direct route/browser reload MUST keep shell and copy aligned without session fallback.
- **FR-019**: Browser back/forward between Baseline Compare and Decision Register MUST preserve Environment shell for Baseline Compare and workspace shell for Decision Register.
- **FR-020**: Decision Register MUST remain a workspace hub and optional filtered workspace hub.
- **FR-021**: Specs 314, 315, 316, and 317 regression contracts MUST remain green.
- **FR-022**: Required related Environment-owned pages MUST be inspected and fixed only if the same mismatch is proven: Required Permissions, Provider Readiness/Diagnostics, Inventory/Coverage, Backup/Restore Environment-owned views, Drift/Findings Environment-owned pages, and Environment-owned baseline drilldowns.
- **FR-023**: Workspace-owned baseline analysis/library surfaces such as Baselines and Baseline Snapshots MUST remain out of scope for Spec 319 unless repo analysis proves they are actually Environment-owned.
- **FR-024**: Alerts and Audit Log Environment filter decisions MUST remain out of scope for Spec 321.
- **FR-025**: No migrations, seeders, packages, env vars, queues, scheduler, storage, or deployment asset changes are allowed unless implementation proves the route fix is impossible without one and the spec is updated first.
### Non-Functional Requirements
- **NFR-001**: Access failures must use deny-as-not-found semantics for membership and cross-workspace isolation.
- **NFR-002**: Page render must not make Graph calls.
- **NFR-003**: Route/link changes must not add measurable query overhead beyond existing scoped model resolution.
- **NFR-004**: Tests must use the narrowest honest lane and keep browser coverage focused.
- **NFR-005**: Filament v5 / Livewire v4 compliance must be preserved.
- **NFR-006**: Laravel 12 panel provider registration remains in `apps/platform/bootstrap/providers.php`.
- **NFR-007**: No new Filament assets are planned; deployment `filament:assets` requirement does not change.
### Key Entities
- **Workspace**: Primary platform context and route owner.
- **ManagedEnvironment**: Secondary operational context inside a Workspace; route-bound for Environment-owned pages.
- **BaselineProfile**: Workspace-owned baseline definition.
- **BaselineTenantAssignment**: Existing Environment assignment to a baseline profile.
- **BaselineSnapshot**: Existing immutable baseline artifact.
- **OperationRun**: Existing observable run truth for Baseline Compare execution.
## Current Repo Findings To Preserve
- Spec 318 classified Baseline Compare as "Environment page implemented on unbound URL" with high risk.
- `BaselineCompareLanding` is registered in `AdminPanelProvider` as a Filament Page and currently uses `UsesAdminEnvironmentFilterQueryParameter`.
- `BaselineCompareLanding::canAccess()` requires `resolveTenantContextForCurrentPanel()` to produce a `ManagedEnvironment`.
- `BaselineCompareStats::forTenant()` makes Baseline Compare behavior Environment-specific.
- Environment Dashboard summary and widgets currently call `BaselineCompareLanding::getUrl(tenant: $tenant)`, which produces `/admin/baseline-compare-landing?environment_id=...`.
- `OperateHubShell::resolveQueryTenantHint()` currently resolves `tenant` and `managed_environment_id`, not canonical `environment_id`.
- `WorkspaceHubRegistry` does not include Baseline Compare, which is correct and must remain true.
- Direct clean and direct `environment_id` Baseline Compare URLs fail after context clear, proving the current URL is not self-sufficient.
## Out of Scope
- Decision Register behavior changes beyond regression coverage.
- Workspace hub behavior changes from Specs 314-316.
- `environment_id` filter support for Baseline Compare.
- Workspace-wide Baseline Compare.
- Spec 320 workspace-owned analysis page registration and shell cutover.
- Spec 321 Alerts/Audit Log Environment filter decision.
- Spec 322 durable browser no-drift regression infrastructure.
- Baseline feature redesign.
- Baseline assignment semantic changes except where route correctness requires link/access updates.
- Migrations, seeders, packages, env vars, queues, scheduler, storage, or deployment asset changes.
- Compatibility redirects, dual routes, legacy aliases, or remembered fallback support.
## Acceptance Criteria
### Baseline Compare Classification
- [x] Baseline Compare is classified as Environment-owned.
- [x] Baseline Compare is not in `WorkspaceHubRegistry`.
- [x] Baseline Compare is not treated as a filtered workspace hub.
- [x] Baseline Compare does not use `environment_id` as primary access contract.
- [x] Baseline Compare does not use remembered Environment fallback.
### Baseline Compare Route Contract
- [x] Canonical Baseline Compare route includes Environment ownership.
- [x] Environment belongs to Workspace.
- [x] Cross-workspace Environment is rejected.
- [x] Clean workspace-only Baseline Compare route is removed, invalid, or safe no-access.
- [x] Workspace-style `?environment_id=...` Baseline Compare route is removed, invalid, or non-canonical.
- [x] No legacy query params are accepted.
### Shell / Breadcrumb / Copy
- [x] Baseline Compare shell shows Workspace + Environment.
- [x] Breadcrumbs indicate Environment ownership.
- [x] Header/copy agrees with Environment ownership.
- [x] "This environment" copy never appears without active Environment shell context.
- [x] Reload keeps shell/copy aligned.
- [x] Browser back/forward keeps shell/copy aligned.
### Navigation / CTA
- [x] Environment Dashboard Baseline Compare CTA uses Environment-owned route.
- [x] CTA emits no `environment_id`.
- [x] CTA emits no `tenant`.
- [x] CTA emits no `tenant_id`.
- [x] CTA emits no `managed_environment_id`.
- [x] CTA emits no `tenant_scope`.
- [x] CTA emits no `tableFilters`.
- [x] Baseline Compare does not appear as workspace-wide sidebar hub.
### Related Environment Pages
- [x] Any touched Environment-owned page keeps Environment shell context.
- [x] No touched Environment-owned page becomes workspace hub by accident.
- [x] No touched Environment-owned page uses remembered Environment fallback.
### Regression
- [x] Decision Register remains workspace hub.
- [x] Workspace hub clean entry from Spec 314 remains green.
- [x] Environment CTA `environment_id` contract from Spec 315 remains green for workspace hubs.
- [x] Clear filter contract from Spec 316 remains green.
- [x] Legacy Tenant cleanup from Spec 317 remains green.
- [x] Spec 318 mismatch is resolved for Baseline Compare.
### Tests / Browser
- [x] Required tests added.
- [x] Existing tests updated only if they asserted broken old behavior.
- [x] No broad rebaseline.
- [x] Focused browser verification completed.
- [x] Screenshots saved where useful.
- [x] `git diff --check` passes.
- [x] Formatting passes.
## Browser Verification Required
### Flow A - Environment Dashboard CTA
1. Open Environment Dashboard.
2. Click Baseline Compare CTA/action.
3. Verify URL is Environment-owned route.
4. Verify URL has no `environment_id` or legacy params.
5. Verify shell shows Workspace + Environment.
6. Verify breadcrumb/header/copy align.
7. Capture screenshot:
```text
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png
```
### Flow B - Direct Clean Workspace URL
1. Open old/clean workspace-only Baseline Compare URL if route exists.
2. Verify page does not render as valid Baseline Compare.
3. Verify no remembered Environment fallback.
4. Capture screenshot or document route absence:
```text
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-clean--baseline-compare--rejected.png
```
### Flow C - Direct Workspace Filtered URL
1. Open workspace-style Baseline Compare URL with `?environment_id=...` if route exists.
2. Verify this is not canonical and does not render valid Baseline Compare.
3. Capture screenshot or document route absence:
```text
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-filtered--baseline-compare--rejected.png
```
### Flow D - Reload
1. Open canonical Environment Baseline Compare route.
2. Reload.
3. Verify shell remains Workspace + Environment.
4. Verify no fallback/session mismatch.
5. Capture screenshot:
```text
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--after-reload.png
```
### Flow E - Back/Forward
1. Environment Dashboard -> Baseline Compare.
2. Navigate to Decision Register.
3. Browser back.
4. Verify Baseline Compare has Environment shell.
5. Browser forward.
6. Verify Decision Register has Workspace shell and no active Environment unless filtered.
7. Capture screenshots where useful:
```text
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
```
## Success Criteria *(mandatory)*
- **SC-001**: 100% of Baseline Compare entry points generated by Environment Dashboard and related Environment widgets use the canonical Environment-owned route.
- **SC-002**: 0 valid Baseline Compare renders occur from clean workspace-only URLs or workspace-style `environment_id` URLs.
- **SC-003**: Cross-workspace Environment route attempts return 404/safe no-access in focused tests.
- **SC-004**: Browser verification shows shell/copy consistency for CTA, reload, and back/forward flows.
- **SC-005**: Existing workspace hub regression tests for Decision Register and Specs 314-316 remain green.
## Assumptions
- The canonical route should follow the repo's existing Environment route family, preferably `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare` unless implementation analysis finds a closer established route name.
- `ManagedEnvironment` route key remains the current slug route key used by other Environment routes.
- Existing Baseline Compare data, stats, Compare Now action, and OperationRun behavior are correct and need route/shell hardening only.
- There is no production data and no external consumer requiring old URLs.
## Risks
- Existing tests may assert remembered Environment fallback for Baseline Compare and must be updated because that behavior is now explicitly invalid.
- Filament Page registration may require an explicit route override instead of a simple trait; implementation must use the narrowest repo-consistent path.
- Environment navigation may currently mix workspace-owned baseline surfaces with Environment-owned Baseline Compare; implementation must not pull Baselines/Baseline Snapshots into this spec.
- Browser screenshots may require a running Sail/Vite/browser setup; if deterministic browser setup is blocked, document the blocker and capture available proof.
## Open Questions
None blocking implementation. If implementation discovers that Baseline Compare cannot be placed on the existing Environment route family without a new route helper or page registration pattern, update `plan.md` before coding.
## Follow-Up Spec Candidates
- **320 - Workspace-Owned Analysis Surface Registration & Shell Cutover**: Baselines, Baseline Snapshots, and workspace-owned analysis pages that inherit Environment shell context.
- **321 - Alerts / Audit Log Environment Filter Contract Decision**: decide whether to support `environment_id` with visible chip/clear or reject it.
- **322 - Browser No-Drift Regression Guard**: durable browser regression coverage for workspace hubs, Environment-owned pages, filtered hubs, clear/reload/back behavior, and no legacy alias resurrection.
## Required Final Report
Implementation close-out must report:
```text
Spec 319 completed.
Changed behavior:
...
Baseline Compare classification:
...
Canonical route:
...
Removed/invalidated routes:
...
Files changed:
...
Tests:
- command:
- result:
Browser verification:
...
Remaining follow-ups:
- 320:
- 321:
- 322:
No migrations were created.
No seeders were changed.
No packages, env vars, queues, scheduler, storage, or deployment asset changes were made.
No backwards compatibility layer was introduced.
No legacy query alias support was added.
```
Also include whether Baseline Compare was removed from workspace hub handling, whether Environment Dashboard CTA was updated, how cross-workspace Environment access is handled, whether clean workspace-only URL is removed or returns safe no-access, screenshot paths if generated, and known unrelated residual test failures.

View File

@ -0,0 +1,174 @@
# Tasks: Environment-Owned Surface Routing & Shell Context Contract
**Input**: Design documents from `/specs/319-environment-owned-surface-routing-shell-context-contract/`
**Prerequisites**: `plan.md`, `spec.md`
**Tests**: Required. This is a runtime route/shell/access contract change.
## Test Governance Checklist
- [x] Lane assignment is named and is the narrowest sufficient proof for route, shell, copy, CTA, cross-workspace, remembered fallback, and browser behavior.
- [x] New or changed tests stay in the smallest honest family; browser additions are explicit.
- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default.
- [x] Planned validation commands cover the change without pulling in unrelated lane cost.
- [x] The declared surface test profile `global-context-shell` is explicit.
- [x] Any material budget, baseline, trend, or escalation note is recorded in the implementation close-out.
## Phase 1: Guardrails and Repo Verification
**Purpose**: Confirm current repo truth before runtime edits.
- [x] T001 Verify the implementation starts from branch `319-environment-owned-surface-routing-shell-context-contract` and the worktree has no unrelated user changes.
- [x] T002 Re-read Specs 313-318, especially `specs/318-admin-surface-scope-shell-context-audit/mismatch-findings.md`, `audit-report.md`, `recommended-fixes.md`, and `page-matrix.md`.
- [x] T003 Confirm Laravel/Filament/Livewire/Pest versions through Laravel Boost `application_info`.
- [x] T004 Confirm no migration, seeder, package, env var, queue, scheduler, storage, or deployment asset change is required.
- [x] T005 Inventory current Baseline Compare route names, URLs, page registration, shell resolution, and old URL behavior in `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/routes/web.php`, and `apps/platform/app/Filament/Pages/BaselineCompareLanding.php`.
- [x] T006 Inventory all Baseline Compare link generators with `rg "BaselineCompareLanding::getUrl|baseline-compare-landing|Baseline Compare" apps/platform/app apps/platform/resources apps/platform/tests`.
- [x] T007 Confirm `WorkspaceHubRegistry` does not include Baseline Compare and record it as a must-preserve fact.
- [x] T008 Confirm the canonical route shape to use from existing Environment routes under `/admin/workspaces/{workspace}/environments/{environment}/...`.
- [x] T009 Identify any existing test that asserts remembered Environment fallback for Baseline Compare and mark it for replacement rather than preservation.
## Phase 2: Tests First / Contract Coverage
**Purpose**: Add failing or alongside tests that define the new contract.
- [x] T010 Add/update a unit test in `apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php` proving `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare` is `AdminSurfaceScope::EnvironmentBound`.
- [x] T011 Add/update `apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php` proving Baseline Compare is not a workspace hub and remains excluded from workspace hub handling.
- [x] T012 Add a feature test proving the canonical Baseline Compare Environment route opens for an authorized user and renders the Baseline Compare page with Workspace + Environment shell context.
- [x] T013 Add a feature test proving the old clean workspace-only Baseline Compare URL does not render the page after remembered Environment context is cleared.
- [x] T014 Add a feature test proving the old workspace-style Baseline Compare URL with `?environment_id=...` does not render as canonical Baseline Compare.
- [x] T015 Add a feature test proving remembered Environment state alone does not allow Baseline Compare to render from the old clean URL.
- [x] T016 Add a feature test proving Workspace A + Environment B from Workspace B is rejected as 404/safe no-access with no workspace switch.
- [x] T017 Add a Livewire or feature test proving Baseline Compare "this environment" copy only appears when active Environment shell context exists.
- [x] T018 Add a feature test proving Environment Dashboard Baseline Compare CTA/action URL uses the canonical Environment route.
- [x] T019 Add CTA URL assertions that no `environment_id`, `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, or `tableFilters` appears.
- [x] T020 Replace or update `apps/platform/tests/Feature/Filament/BaselineCompareLandingAdminTenantParityTest.php` so it no longer expects remembered admin tenant fallback.
- [x] T021 Add/keep a test proving existing Compare Now action still uses confirmation, action execution, capability authorization, and OperationRun UX after route hardening.
- [x] T022 Add/keep Decision Register regression coverage proving clean workspace URL remains workspace hub with no active Environment shell.
- [x] T023 Add/keep Decision Register regression coverage proving `?environment_id=...` remains a filtered workspace hub with visible chip and no active Environment shell.
- [x] T024 Add/keep Spec 314-316 regression coverage for workspace hub clean entry, Environment CTA filter, and clear filter behavior.
- [x] T025 Add/keep Spec 317 regression coverage proving legacy Tenant aliases are not resurrected.
## Phase 3: Canonical Route Implementation
**Purpose**: Make Baseline Compare route-owned by Workspace + Environment.
- [x] T026 Implement the canonical Baseline Compare Environment route in `apps/platform/routes/web.php` or the narrowest repo-consistent Filament page route mechanism.
- [x] T027 Ensure the route uses the established Workspace route binding and ManagedEnvironment route key pattern (`{environment:slug}` if consistent with existing routes).
- [x] T028 Ensure route/model resolution validates `environment.workspace_id === workspace.id` before rendering.
- [x] T029 Ensure invalid Workspace/Environment combinations return 404/safe no-access without revealing the foreign Environment.
- [x] T030 Ensure the canonical route flows through admin panel middleware, workspace membership checks, and Environment-bound context handling.
- [x] T031 Disable, remove, or invalidate the old unbound `/admin/baseline-compare-landing` render path without adding a compatibility layer.
- [x] T032 Ensure old URLs with `environment_id` do not become compatibility redirects unless a documented existing convention makes redirect unavoidable.
- [x] T033 If redirect is unavoidable, validate Workspace/Environment relationship, redirect only to canonical route, emit no legacy alias support, and cover with tests.
- [x] T034 Ensure `AdminSurfaceScope` classification for the canonical path is Environment-bound without adding a new broad scope enum unless unavoidable.
## Phase 4: Baseline Compare Page and Shell Context
**Purpose**: Remove hidden context reliance from the page.
- [x] T035 Update `apps/platform/app/Filament/Pages/BaselineCompareLanding.php` so valid rendering requires route-owned Environment context.
- [x] T036 Remove `UsesAdminEnvironmentFilterQueryParameter` from Baseline Compare if it remains a query-based access path.
- [x] T037 Ensure `canAccess()` cannot pass from remembered Environment, last Environment, provider tenant ID, or `environment_id` query on an old URL.
- [x] T038 Ensure `mount()` and `refreshStats()` only operate when canonical route Environment context is active.
- [x] T039 Ensure missing Environment context returns safe no-access or never reaches render.
- [x] T040 Ensure page breadcrumbs/header/title/copy align with active Environment ownership.
- [x] T041 Ensure `getFindingsUrl()`, `getRunUrl()`, and `openCompareMatrixUrl()` still generate safe links from canonical Environment context.
- [x] T042 Ensure Compare Now still uses existing `BaselineCompareService`, `OperationUxPresenter`, `OpsUxBrowserEvents`, and `OperationRunLinks`.
## Phase 5: Entry Points and Navigation
**Purpose**: Update all in-scope links to the canonical Environment route.
- [x] T043 Update `apps/platform/app/Support/EnvironmentDashboard/EnvironmentDashboardSummaryBuilder.php` Baseline Compare action URL.
- [x] T044 Update `apps/platform/app/Filament/Widgets/ManagedEnvironment/BaselineCompareCoverageBanner.php` Baseline Compare landing URL.
- [x] T045 Update `apps/platform/app/Filament/Widgets/Dashboard/BaselineCompareNow.php` Baseline Compare landing URL.
- [x] T046 Update `apps/platform/app/Filament/Widgets/Dashboard/NeedsAttention.php` Baseline Compare action URL.
- [x] T047 Update `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php` per-Environment Baseline Compare links if they remain in scope.
- [x] T048 Add or update `apps/platform/app/Support/ManagedEnvironmentLinks.php` with a narrow Baseline Compare Environment URL helper only if this reduces repeated route generation.
- [x] T049 Ensure Environment Dashboard CTA/action emits no `environment_id`, `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, or `tableFilters`.
- [x] T050 Ensure Baseline Compare appears only as Environment-owned navigation/action when an active Environment context exists.
- [x] T051 Ensure workspace sidebar remains free of Baseline Compare as a workspace-wide hub.
- [x] T052 Ensure `WorkspaceSidebarNavigation` and `WorkspaceHubRegistry` do not gain Baseline Compare as a workspace hub entry.
## Phase 6: Related Environment-Owned Page Inspection
**Purpose**: Fix only same-class mismatches found during implementation.
- [x] T053 Inspect Required Permissions, Provider Readiness/Diagnostics, Environment Diagnostics, Inventory/Coverage, Backup/Restore Environment-owned views, Findings/Risk Exceptions, Evidence, Environment Reviews, Stored Reports, and Review Packs for route/shell/copy mismatch.
- [x] T054 If a touched related page has the same mismatch, update it using the same Environment-owned route/shell rule and add focused tests.
- [x] T055 If a related page is already correct or belongs to Spec 320/321, document it in the implementation close-out and do not modify it.
- [x] T056 Confirm Baselines, Baseline Snapshots, Baseline Compare Matrix, Cross-environment Compare, Alerts, and Audit Log are not changed under Spec 319 unless repo analysis proves direct Baseline Compare dependency.
## Phase 7: Browser Verification
**Purpose**: Prove visible route/shell/copy behavior.
- [x] T057 Start local platform stack using Sail or the repo platform dev command.
- [x] T058 Browser Flow A: Environment Dashboard -> Baseline Compare CTA, verify canonical Environment URL, no legacy query params, Workspace + Environment shell, aligned breadcrumb/header/copy.
- [x] T059 Save Flow A screenshot to `specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png`.
- [x] T060 Browser Flow B: open old clean workspace-only Baseline Compare URL if route exists and verify rejection/no render/no remembered fallback.
- [x] T061 Save Flow B screenshot or route-absence note as `direct-clean--baseline-compare--rejected.png`.
- [x] T062 Browser Flow C: open old workspace-style `?environment_id=...` Baseline Compare URL if route exists and verify rejection/non-canonical behavior.
- [x] T063 Save Flow C screenshot or route-absence note as `direct-filtered--baseline-compare--rejected.png`.
- [x] T064 Browser Flow D: reload canonical Baseline Compare route and verify shell/copy remains Workspace + Environment.
- [x] T065 Save Flow D screenshot to `baseline-compare--after-reload.png`.
- [x] T066 Browser Flow E: Environment Dashboard -> Baseline Compare -> Decision Register -> back -> forward, verify Baseline Compare Environment shell and Decision Register workspace shell.
- [x] T067 Save Flow E screenshots to `baseline-compare--back-forward.png` and `decision-register--regression.png` where useful.
- [x] T068 If browser setup cannot be made deterministic, document exact blocker and available alternate proof in the implementation close-out.
## Phase 8: Final Validation and Close-Out
**Purpose**: Finish with tests, formatting, and implementation report.
- [x] T069 Run focused Baseline Compare tests: `cd apps/platform && ./vendor/bin/sail artisan test --filter=BaselineCompare`.
- [x] T070 Run classifier/registry tests: `cd apps/platform && ./vendor/bin/sail artisan test --filter=AdminSurfaceScope` and `cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubRegistry`.
- [x] T071 Run Decision Register regression tests: `cd apps/platform && ./vendor/bin/sail artisan test --filter=DecisionRegister`.
- [x] T072 Run relevant WorkspaceHub regression tests for Specs 314-316.
- [x] T073 Run relevant LegacyTenantPlatformContextCleanup/Spec 317 regression tests.
- [x] T074 Run formatting for touched PHP files, including `cd apps/platform && ./vendor/bin/sail pint --test` or a scoped Pint command.
- [x] T075 Run `git diff --check`.
- [x] T076 Confirm no migrations, seeders, package files, env vars, queues, scheduler, storage, or deployment asset files changed.
- [x] T077 Confirm no backwards compatibility layer, old route redirect, dual route model, or legacy query alias support was introduced.
- [x] T078 Prepare final implementation report with changed behavior, classification, canonical route, removed/invalidated routes, files changed, tests, browser verification, screenshots, follow-ups 320/321/322, and any unrelated residual failures.
## Non-Tasks / Scope Guards
- [x] NT001 Do not implement Spec 320 workspace-owned analysis shell cutover.
- [x] NT002 Do not implement Spec 321 Alerts/Audit Log filter decision.
- [x] NT003 Do not implement Spec 322 durable browser no-drift infrastructure.
- [x] NT004 Do not add `environment_id` filter support to Baseline Compare.
- [x] NT005 Do not make Baseline Compare workspace-wide.
- [x] NT006 Do not add compatibility redirects, aliases, dual-param support, or remembered fallback.
- [x] NT007 Do not create migrations, seeders, packages, env vars, queues, scheduler, storage, or Filament assets.
## Dependencies
```text
Phase 1 -> Phase 2 -> Phase 3 -> Phase 4 -> Phase 5 -> Phase 6 -> Phase 7 -> Phase 8
```
Route tests (T010-T016) should be in place before route implementation. Entry point tests (T018-T019) should be in place before link updates. Browser verification runs after runtime implementation.
## Parallel Opportunities
- T010-T024 can be split by test file after the canonical route shape is agreed.
- T043-T047 can be updated in parallel because they touch separate entry-point files.
- T069-T073 can run independently after implementation, subject to Sail/test environment availability.
## MVP Scope
The MVP is Baseline Compare canonical Environment route, invalid old URL behavior, Environment Dashboard CTA update, cross-workspace rejection, no remembered fallback, and Decision Register regression. Related Environment-owned pages are inspect-only unless the same mismatch is confirmed.
## Implementation Evidence
- Canonical route registered: `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare`.
- Old `/admin/baseline-compare-landing` and `/admin/baseline-compare-landing?environment_id=...` return Not Found; no redirect or compatibility alias was added.
- Browser smoke artifacts saved under `artifacts/screenshots/`.
- Focused validation passed:
- `./vendor/bin/sail artisan test` on Baseline Compare route/component/navigation/authorization contract files: 112 tests, 345 assertions.
- `./vendor/bin/sail artisan test tests/Feature/Governance/DecisionRegisterWorkspaceHubContractTest.php`
- `./vendor/bin/sail artisan test tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php tests/Feature/Navigation/WorkspaceHubClearFilterContractTest.php`
- `./vendor/bin/sail artisan test tests/Feature/Guards/LegacyTenantPlatformContextCleanupTest.php`
- scoped Pint on touched PHP files.
- `git diff --check`
- Broader `./vendor/bin/sail artisan test --filter=DecisionRegister` surfaced an existing unrelated failure in `FindingExceptionDecisionRegisterNavigationTest` where the expected back URL still includes `managed_environment_id`; no Spec 319 files or Decision Register code were changed for that.