diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/.gitkeep b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/.gitkeep new file mode 100644 index 00000000..5e683e28 --- /dev/null +++ b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/.gitkeep @@ -0,0 +1 @@ +Spec 319 browser verification screenshots are saved here during implementation. diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--after-reload.png b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--after-reload.png new file mode 100644 index 00000000..12302a98 Binary files /dev/null and b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--after-reload.png differ diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--back-forward.png b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--back-forward.png new file mode 100644 index 00000000..12302a98 Binary files /dev/null and b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--back-forward.png differ diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/decision-register--regression.png b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/decision-register--regression.png new file mode 100644 index 00000000..d89e0169 Binary files /dev/null and b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/decision-register--regression.png differ diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-clean--baseline-compare--rejected.png b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-clean--baseline-compare--rejected.png new file mode 100644 index 00000000..74afb3e9 Binary files /dev/null and b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-clean--baseline-compare--rejected.png differ diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-filtered--baseline-compare--rejected.png b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-filtered--baseline-compare--rejected.png new file mode 100644 index 00000000..74afb3e9 Binary files /dev/null and b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-filtered--baseline-compare--rejected.png differ diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png new file mode 100644 index 00000000..78ea0564 Binary files /dev/null and b/specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png differ diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/checklists/requirements.md b/specs/319-environment-owned-surface-routing-shell-context-contract/checklists/requirements.md new file mode 100644 index 00000000..63399508 --- /dev/null +++ b/specs/319-environment-owned-surface-routing-shell-context-contract/checklists/requirements.md @@ -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. diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/plan.md b/specs/319-environment-owned-surface-routing-shell-context-contract/plan.md new file mode 100644 index 00000000..e61aaa39 --- /dev/null +++ b/specs/319-environment-owned-surface-routing-shell-context-contract/plan.md @@ -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. diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/spec.md b/specs/319-environment-owned-surface-routing-shell-context-contract/spec.md new file mode 100644 index 00000000..33a21f61 --- /dev/null +++ b/specs/319-environment-owned-surface-routing-shell-context-contract/spec.md @@ -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. diff --git a/specs/319-environment-owned-surface-routing-shell-context-contract/tasks.md b/specs/319-environment-owned-surface-routing-shell-context-contract/tasks.md new file mode 100644 index 00000000..69cfc9aa --- /dev/null +++ b/specs/319-environment-owned-surface-routing-shell-context-contract/tasks.md @@ -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.