Compare commits

..

3 Commits

Author SHA1 Message Date
Ahmed Darrazi
09e87ea134 merge: update 320 branch from platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m42s
2026-05-17 00:57:27 +02:00
Ahmed Darrazi
b4b2d9a4a8 spec: add workspace-owned analysis surface cutover contract 2026-05-17 00:57:24 +02:00
edc33a5a17 spec: add environment-owned surface routing contract (#377)
## Summary\n- add completed Spec 319 artifacts for the environment-owned Baseline Compare routing contract\n- include browser-smoke screenshots and focused validation notes\n- keep the PR diff limited to Spec 319 artifacts because runtime is already present in platform-dev via #374\n\n## Testing\n- git diff --check\n- focused validation recorded in specs/319-environment-owned-surface-routing-shell-context-contract/tasks.md

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #377
2026-05-16 22:55:57 +00:00
23 changed files with 1999 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.

View File

@ -0,0 +1,75 @@
# Specification Quality Checklist: Workspace-Owned Analysis Surface Registration & Shell Cutover
**Purpose**: Validate specification completeness and quality before implementation
**Created**: 2026-05-16
**Feature**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/320-workspace-owned-analysis-surface-registration-shell-cutover/spec.md)
## Candidate Selection Gate
- [x] Explicit user-provided Spec 320 request was selected as the source of truth for this preparation pass.
- [x] Completed-spec guardrail checked that no existing `specs/320-*` package was present before generation.
- [x] Specs 313, 314, 315, 316, 317, 318, and 319 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 320.
- [x] Close alternatives were deferred to follow-up Specs 321 and 322.
- [x] The selected slice is workspace-owned analysis/library shell classification and remembered Environment fallback removal only.
## Content Quality
- [x] Problem statement is operator-visible and tied to workspace-owned page / Environment shell mismatch.
- [x] User value is clear: clean workspace-owned analysis URLs show Workspace shell only.
- [x] Scope is bounded to Spec 318 M2/M4 targets plus directly related unregistered workspace analysis pages.
- [x] Hard cutover/no compatibility posture is explicit.
- [x] No unresolved clarification-marker placeholders 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 Baselines, Baseline Snapshots, baseline detail/edit/matrix pages, My Findings, Findings Intake, Findings Hygiene, Cross-environment Compare, legacy aliases, remembered fallback, reload/back-forward, Baseline Compare regression, and Decision Register regression.
- [x] Non-goals prevent Spec 321/322 scope creep.
- [x] Edge cases are identified.
- [x] Assumptions and risks are documented.
- [x] Success and acceptance criteria are measurable.
- [x] Open questions do not block implementation; they require per-page documentation during 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 touched Resource is deliberately changed and tested.
- [x] Destructive/high-impact action handling is addressed for existing archive/capture/compare/preflight actions.
- [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 classification, page/link alignment, 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, Baseline Compare changes, 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 filters cannot create shell context or leak identity.
- [x] No new persisted truth is introduced.
- [x] Possible classifier complexity has a proportionality review.
- [x] OperationRun semantics are preserved for existing high-impact actions.
- [x] Audit/authorization/confirmation expectations for existing high-impact actions 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,287 @@
# Implementation Plan: Workspace-Owned Analysis Surface Registration & Shell Cutover
**Branch**: `320-workspace-owned-analysis-surface-registration-shell-cutover` | **Date**: 2026-05-16 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/320-workspace-owned-analysis-surface-registration-shell-cutover/spec.md)
**Input**: Feature specification from `/specs/320-workspace-owned-analysis-surface-registration-shell-cutover/spec.md`
**Preparation status**: Specification artifacts only. No runtime implementation has been performed by this preparation step.
## Summary
Spec 320 hard-cuts workspace-owned analysis/library pages away from remembered Environment shell inheritance:
```text
Workspace-owned analysis surface
-> clean workspace route
-> Workspace shell only
-> no active Environment shell
-> no remembered Environment fallback
-> optional Environment filter only through canonical environment_id + visible chip
```
The implementation must classify the audited workspace-owned analysis surfaces from Spec 318, especially Baselines/Baseline Profiles, Baseline Snapshots, Baseline Compare Matrix, My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare. Baseline Compare remains Environment-owned from Spec 319. Alerts/Audit Log filter decisions remain Spec 321.
## Technical Context
**Language/Version**: PHP 8.4.15, Laravel 12.52.0
**Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, Laravel Sail
**Storage**: PostgreSQL; no schema changes planned
**Testing**: Pest 4 / PHPUnit 12, Filament/Livewire tests, focused browser smoke
**Validation Lanes**: fast-feedback, confidence, browser
**Target Platform**: `apps/platform` Laravel/Filament admin panel, local Sail, staging/production through Dokploy
**Project Type**: Web application
**Performance Goals**: No material performance change. Classification and shell resolution must remain cheap request-time logic.
**Constraints**: No migrations, seeders, packages, env vars, queues, scheduler, storage, deployment assets, compatibility routes, redirects, or legacy query alias support.
**Scale/Scope**: Focused shell/context classification for workspace-owned analysis surfaces plus targeted regressions for Specs 314-319.
## UI / Surface Guardrail Plan
- **Guardrail scope**: route/shell/query/copy/navigation behavior for existing operator-facing workspace-owned analysis pages.
- **Native vs custom classification summary**: Existing Filament Resources/Pages and Blade views. No styling redesign.
- **Shared-family relevance**: navigation, shell context, breadcrumbs, filter chips, clear actions, OperationRun links.
- **State layers in scope**: shell, page, record/detail, URL query, Filament table filter state, Livewire referer classification.
- **Audience modes in scope**: operator-MSP and support-platform.
- **Decision/diagnostic/raw hierarchy plan**: Workspace ownership must be default-visible; Environment context may appear as data/filter state only.
- **Raw/support gating plan**: no raw/support evidence exposure change.
- **One-primary-action / duplicate-truth control**: The shell is the single ownership signal; page filters/selectors do not become shell ownership.
- **Handling modes by drift class or surface**: hard-stop for remembered Environment shell on in-scope clean URLs; review-mandatory for any newly discovered workspace analysis route.
- **Repository-signal treatment**: Spec 318 M2/M4 are implementation inputs; pages outside those findings are inspect-only unless repo evidence proves same mismatch.
- **Special surface test profiles**: `global-context-shell`.
- **Required tests or manual smoke**: classifier tests, URL/query tests, Livewire/Feature render tests, browser smoke for workspace origin, Environment origin, reload, and back/forward.
- **Exception path and spread control**: A distinct workspace-owned analysis classifier is allowed only if `WorkspaceHubRegistry` would overstate hub/filter behavior. Document the choice in implementation close-out.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage.
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes.
- **Systems touched**: `AdminSurfaceScope`, `WorkspaceHubRegistry`, `WorkspaceSidebarNavigation`, `WorkspaceHubEnvironmentFilter`, `WorkspaceHubFilterStateResetter`, `WorkspaceScopedEnvironmentRoutes`, `ManagedEnvironmentLinks`, `OperateHubShell`, baseline resources/pages, findings analysis pages, cross-environment compare page, tests, browser smoke artifacts.
- **Shared abstractions reused**: existing workspace hub shell contract, `AdminSurfaceScope`, `WorkspaceHubRegistry` query cleaning, `WorkspaceHubEnvironmentFilter`, `CanonicalAdminEnvironmentFilterState`, and current route/link helpers.
- **New abstraction introduced? why?**: none preferred. If necessary, add the narrowest possible `AdminSurfaceScope` classification for workspace-owned analysis surfaces to force environmentless shell without implying full workspace-hub behavior.
- **Why the existing abstraction was sufficient or insufficient**: `WorkspaceHubRegistry` works for registered hubs; generic `WorkspaceScoped` is insufficient because it allows remembered Environment restore.
- **Bounded deviation / spread control**: Do not introduce a general surface taxonomy beyond the audited route list. Do not register pages as hubs unless they truly satisfy hub/filter/clear behavior.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, as regression only for existing baseline capture/compare actions.
- **Central contract reused**: `OperationUxPresenter`, `OpsUxBrowserEvents`, `OperationRunLinks`, current services/jobs.
- **Delegated UX behaviors**: existing queued toast, run link, browser event, authorization, and audit behavior remain unchanged.
- **Surface-owned behavior kept local**: existing initiation inputs/actions on Baseline Profile and Baseline Compare Matrix.
- **Queued DB-notification policy**: N/A.
- **Terminal notification path**: Existing central lifecycle only.
- **Exception path**: none.
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: yes.
- **Provider-owned seams**: Microsoft/Intune baseline content, provider IDs, Graph-backed snapshot evidence, compare strategy internals.
- **Platform-core seams**: Workspace/Environment route ownership, shell classification, query key semantics, navigation ownership language.
- **Neutral platform terms / contracts preserved**: Workspace, Environment, workspace-owned analysis surface, workspace hub, filtered workspace hub, Environment-owned page.
- **Retained provider-specific semantics and why**: Baseline content remains Microsoft/Intune-shaped because it is current provider implementation truth.
- **Bounded extraction or follow-up path**: Spec 321 and Spec 322 remain follow-ups; no provider abstraction work here.
## Constitution Check
*GATE: Must pass before implementation. Re-check after runtime changes.*
- Inventory-first: no inventory/snapshot truth change. Baseline snapshots remain immutable evidence/artifacts.
- Read/write separation: no new write behavior. Existing capture/compare/preflight actions keep confirmation, authorization, audit, and OperationRun semantics.
- Graph contract path: no new Graph calls; page render must remain DB-only.
- Deterministic capabilities: existing capability checks remain; route/shell classification must be testable.
- RBAC-UX: non-member / not entitled Workspace or Environment access remains deny-as-not-found where applicable; member missing capability follows existing behavior.
- Workspace isolation: current Workspace remains the primary shell and authorization boundary.
- Tenant/Environment isolation: Environment-owned rows remain scoped by accessible environments; optional filters must resolve only inside current Workspace and entitlement.
- Run observability: existing baseline operation runs remain observable; no new operation type.
- Test governance: lane purpose, heavy/browser visibility, fixture cost, and reviewer handoff are explicit.
- Proportionality: possible classifier addition is justified by current browser-proven shell mismatch and bounded route list.
- No premature abstraction: prefer extending existing classifier/registry paths over a new framework.
- Persisted truth: no tables/entities/artifacts added.
- Behavioral state: no new business state/status/reason family.
- UI semantics: no new badge/status taxonomy; direct shell/page/filter truth only.
- Shared pattern first: reuse workspace hub and shell resolution contracts.
- Provider boundary: no provider tenant ID alias or fallback becomes platform shell truth.
- 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`; no provider registration work planned.
- Global search: no global search behavior should change. Baseline Profile and Baseline Snapshot global search remain disabled unless implementation explicitly verifies a safe View/Edit route and updates tests.
- Destructive/high-impact actions: no new destructive action. Existing archive/capture/compare/preflight actions must keep `->action(...)`, confirmation where required, authorization, audit, notifications, and tests.
- 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; Feature/Livewire for shell/access/filter behavior; Browser for integrated shell/sidebar/reload/history.
- **Affected validation lanes**: fast-feedback, confidence, browser.
- **Why this lane mix is the narrowest sufficient proof**: The defect is both classification logic and browser-visible shell drift.
- **Narrowest proving command(s)**:
- `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=WorkspaceOwnedAnalysis`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=BaselineCompareEnvironmentRouteContract`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubEnvironmentFilterContract`
- focused Spec 320 browser smoke.
- **Fixture / helper / factory / seed / context cost risks**: workspace/environment/member plus small baseline/finding fixtures; keep any full browser fixture explicit and named.
- **Expensive defaults or shared helper growth introduced?**: no. If a helper is needed, make expensive setup opt-in.
- **Heavy-family additions, promotions, or visibility changes**: explicit browser smoke only; no broad discovery guard in this spec.
- **Surface-class relief / special coverage rule**: `global-context-shell`.
- **Closing validation and reviewer handoff**: prove clean URLs, filtered URLs, legacy aliases, remembered fallback rejection, Baseline Compare regression, Decision Register regression, and browser screenshots.
- **Budget / baseline / trend follow-up**: none expected; document if browser smoke runtime grows materially.
- **Review-stop questions**: Are any workspace-owned analysis URLs still generic `WorkspaceScoped` with remembered restore? Did any page get registered as a hub without supporting hub/filter/clear behavior? Did legacy aliases return?
- **Escalation path**: document-in-feature.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage.
- **Why no dedicated follow-up spec is needed**: This is the dedicated follow-up for Spec 318 M2/M4. Alerts/Audit and durable browser guard already have 321/322.
## Project Structure
### Documentation (this feature)
```text
specs/320-workspace-owned-analysis-surface-registration-shell-cutover/
+-- spec.md
+-- plan.md
+-- tasks.md
+-- checklists/
+-- requirements.md
```
### Source Code (likely affected during later implementation)
```text
apps/platform/app/
+-- Support/Navigation/
| +-- AdminSurfaceScope.php
| +-- WorkspaceHubRegistry.php
| +-- WorkspaceSidebarNavigation.php
+-- Support/OperateHub/OperateHubShell.php
+-- Support/ManagedEnvironmentLinks.php
+-- Filament/Resources/
| +-- BaselineProfileResource.php
| +-- BaselineSnapshotResource.php
+-- Filament/Pages/
| +-- BaselineCompareMatrix.php
| +-- CrossEnvironmentComparePage.php
| +-- Findings/
| +-- MyFindingsInbox.php
| +-- FindingsIntakeQueue.php
| +-- FindingsHygieneReport.php
+-- Filament/Concerns/WorkspaceScopedEnvironmentRoutes.php
apps/platform/tests/
+-- Unit/Tenants/AdminSurfaceScopeTest.php
+-- Unit/Support/OperateHub/OperateHubShellResolutionTest.php
+-- Feature/Navigation/WorkspaceHubRegistryTest.php
+-- Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php
+-- Feature/Filament/
| +-- BaselineCompareEnvironmentRouteContractTest.php
| +-- BaselineProfile*.php
| +-- BaselineSnapshot*.php
+-- Feature/Findings/
| +-- MyFindingsInboxNavigationContextTest.php
| +-- FindingsIntakeQueueNavigationContextTest.php
+-- Browser/
+-- Spec320WorkspaceOwnedAnalysisSurfaceSmokeTest.php
```
**Structure Decision**: Use existing Laravel/Filament app structure. Do not create new base folders. Add test files only where the existing test families already live, or extend existing focused files when lower churn is clearer.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| Possible new `AdminSurfaceScope` value | Generic `WorkspaceScoped` allows remembered Environment restore; workspace hubs already force environmentless shell but not every analysis page is a hub | Blindly adding every page to `WorkspaceHubRegistry` could falsely imply hub/filter/clear behavior |
## Proportionality Review
- **Current operator problem**: Workspace-owned pages can appear Environment-owned in the shell with no filter chip or route ownership.
- **Existing structure is insufficient because**: `WorkspaceScoped` restores remembered Environment; `WorkspaceHubRegistry` is reserved for hub-like surfaces.
- **Narrowest correct implementation**: Classify a route list or narrow scope category as workspace-owned analysis and make it force environmentless shell.
- **Ownership cost created**: Focused classifier tests, URL/filter tests, browser smoke, and implementation close-out documentation.
- **Alternative intentionally rejected**: Keep generic fallback, add legacy aliases, or add all pages to workspace hub registry regardless of actual hub behavior.
- **Release truth**: Current-release cleanup before production.
## Implementation Phases
### Phase 0 - Repo Verification
Re-read Spec 318 artifacts and current code paths. Confirm the final in-scope list before runtime edits:
- Baselines/Baseline Profiles.
- Baseline Profile view/edit/create if applicable.
- Baseline Compare Matrix.
- Baseline Snapshots.
- My Findings, Findings Intake, Findings Hygiene.
- Cross-environment Compare.
- Any other Spec 318 unregistered workspace analysis surface still present in routes/navigation.
### Phase 1 - Tests First
Add focused tests that fail on current remembered Environment inheritance:
- `AdminSurfaceScope` classification for in-scope paths.
- Shell resolution with remembered Environment present.
- Clean URL opens without active Environment.
- Legacy alias rejection.
- Optional `environment_id` filter behavior where supported.
- Baseline Compare and Decision Register regressions.
### Phase 2 - Classification / Registry Cutover
Implement the narrowest classifier mechanism:
- Prefer existing registry/classifier extensions.
- Add a distinct workspace-owned analysis surface classification only if needed.
- Ensure `forcesEnvironmentlessShellContext()` returns true for these paths.
- Ensure `allowsRememberedEnvironmentRestore()` is false for these paths.
- Preserve Workspace state and authorization.
### Phase 3 - Page / Link / Copy Alignment
Update only in-scope surfaces:
- Sidebar/global/workspace links emit clean workspace URLs unless explicit `environment_id` filter is supported.
- Environment-origin links do not carry active Environment shell ownership.
- Headers, breadcrumbs, empty states, and copy use workspace/library/work-queue wording.
- Existing Environment columns, selectors, badges, and filters remain data/filter state.
### Phase 4 - Regression and Browser Proof
Run focused tests and browser smoke:
- Baselines workspace origin, environment origin, reload.
- Baseline Snapshots workspace origin, environment origin, reload.
- Baseline Compare Matrix direct/reload/back-forward.
- My Findings/Intake/Hygiene and Cross-environment Compare direct/from Environment context.
- Baseline Compare Environment-owned regression.
- Decision Register workspace hub/filtered hub regression.
Save screenshots under:
```text
specs/320-workspace-owned-analysis-surface-registration-shell-cutover/artifacts/screenshots/
```
### Phase 5 - Close-Out
Document:
- classification outcomes for Baselines/Baseline Snapshots.
- unregistered workspace analysis pages fixed or excluded.
- whether any page was reclassified Environment-owned.
- how remembered Environment inheritance was prevented.
- tests and browser verification.
- no migrations/packages/env vars/queues/scheduler/storage/assets.
- no backwards compatibility or legacy alias support.
## Rollout Considerations
- No database migration or data backfill.
- No production compatibility burden because pre-production hard cutover is the standing policy.
- No Dokploy runtime change.
- No new `filament:assets` deployment step.
- Staging validation should run focused tests and browser smoke before promotion.
## Risk Controls
- Keep route list explicit and tested.
- Do not generalize classifier beyond audited need.
- Do not add Environment filter feature work unless a page already has product need and visible chip/clear behavior can be proven.
- Replace old tests that assert broken remembered fallback; do not keep them as compatibility coverage.
- Keep Spec 321 and Spec 322 out of scope.

View File

@ -0,0 +1,344 @@
# Feature Specification: Workspace-Owned Analysis Surface Registration & Shell Cutover
**Feature Branch**: `320-workspace-owned-analysis-surface-registration-shell-cutover`
**Created**: 2026-05-16
**Status**: Completed
**Input**: User supplied Spec 320 draft: workspace-owned analysis and library pages must be classified centrally, open with Workspace shell only, and stop inheriting remembered Environment shell context.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: Workspace-owned analysis and library surfaces such as Baselines, Baseline Snapshots, baseline detail/matrix pages, and several unregistered analysis pages can show an active Environment shell when opened from Environment navigation or remembered context.
- **Today's failure**: Operators can see `Workspace > Environment` shell on pages whose route and data model are workspace-owned, with no visible Environment filter chip and no route-level Environment ownership.
- **User-visible improvement**: Opening a workspace-owned analysis page from sidebar, global navigation, direct URL, reload, or browser history shows Workspace shell only. If an Environment focus is supported, it is explicit through canonical `environment_id` plus a visible chip.
- **Smallest enterprise-capable version**: Classify the exact workspace-owned analysis/library surfaces found by Spec 318, prevent remembered Environment shell fallback for them, update links/copy where needed, and add focused regression coverage.
- **Explicit non-goals**: No Baseline Compare changes except regression checks, no Alerts/Audit Log filter decision, no durable browser no-drift framework, no redesign, no new product feature, no baseline assignment semantic change, no migrations, no compatibility redirects, no legacy query aliases.
- **Permanent complexity imported**: Possible narrow classifier entries or a new `AdminSurfaceScope` value for workspace-owned analysis surfaces, plus targeted tests. No persisted entities, tables, enum/status families, broad registries, or cross-domain UI framework.
- **Why now**: Spec 318 identified workspace-owned baseline/analysis surfaces as the remaining opposite-side mismatch after Specs 314-317 stabilized workspace hubs and Spec 319 handles Environment-owned Baseline Compare.
- **Why not local**: Page-local copy or sidebar-only fixes would leave direct URLs, Livewire requests, reload/back-forward, and remembered-context shell restoration inconsistent.
- **Approval class**: Cleanup / Consolidation.
- **Red flags triggered**: New classifier semantics are possible, but the scope is bounded by existing audit evidence and does not create a new product taxonomy beyond shell-context ownership.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 12/12**
- **Decision**: approve.
## Spec Scope Fields *(mandatory)*
- **Scope**: workspace.
- **Primary Routes**:
- `/admin/baseline-profiles`
- `/admin/baseline-profiles/{record}`
- `/admin/baseline-profiles/{record}/edit`
- `/admin/baseline-profiles/{record}/compare-matrix`
- `/admin/baseline-snapshots`
- `/admin/baseline-snapshots/{record}`
- `/admin/findings/my-work`
- `/admin/findings/intake`
- `/admin/findings/hygiene`
- `/admin/cross-environment-compare`
- any additional unregistered workspace analysis surface confirmed from Spec 318 or repo inspection.
- **Data Ownership**: Existing workspace-owned Baseline Profile and Baseline Snapshot records, workspace analysis pages that aggregate across accessible Environments, and optional Environment filter state. No schema ownership change.
- **RBAC**: Workspace membership and existing workspace capabilities remain required. Environment entitlements still constrain row/data visibility where the existing page aggregates Environment-owned rows. UI visibility is not authorization.
For canonical-view specs:
- **Default filter behavior when tenant-context is active**: Clean workspace-owned analysis URLs must ignore active/remembered Environment shell context. They may retain data filters only when the page explicitly supports canonical `environment_id` and shows a visible filter chip.
- **Explicit entitlement checks preventing cross-tenant leakage**: Any `environment_id` filter must resolve only inside the current Workspace and only to an Environment visible to the actor. Cross-workspace or unauthorized IDs must not create shell context or leak Environment identity.
## Cross-Cutting / Shared Pattern Reuse *(mandatory)*
- **Cross-cutting feature?**: yes.
- **Interaction class(es)**: navigation, shell/context bar, breadcrumbs, URL/query state, Filament resource/page routing, filter chips, clear-filter actions, browser reload/history behavior.
- **Systems touched**: `AdminSurfaceScope`, `WorkspaceHubRegistry`, `WorkspaceSidebarNavigation`, `WorkspaceScopedEnvironmentRoutes`, `ManagedEnvironmentLinks`, `OperateHubShell`, `WorkspaceHubEnvironmentFilter`, `WorkspaceHubFilterStateResetter`, baseline resources/pages, findings analysis pages, cross-environment compare page, tests, and browser smoke artifacts.
- **Existing pattern(s) to extend**: Workspace hub environmentless shell contract from Specs 314-316 and hard-cut no-legacy policy from Spec 317.
- **Shared contract / presenter / builder / renderer to reuse**: Prefer existing `AdminSurfaceScope`, `WorkspaceHubRegistry` query cleaning, `WorkspaceHubEnvironmentFilter`/clear-filter behavior, and `OperateHubShell` shell resolution over page-local shell hacks.
- **Why the existing shared path is sufficient or insufficient**: Registered workspace hubs already force environmentless shell. The gap is that unregistered workspace analysis pages currently fall into generic `WorkspaceScoped`, which still allows remembered Environment restore.
- **Allowed deviation and why**: A distinct `workspace_owned_analysis_surface` classification is allowed only if adding these pages to `WorkspaceHubRegistry` would falsely make them first-class workspace hubs or filtered hub participants.
- **Consistency impact**: Sidebar, URL, shell, breadcrumb, page header, copy, filter chip, and data scope must agree on Workspace ownership.
- **Review focus**: Verify no remembered Environment, Filament tenant fallback, legacy Tenant query key, or `tableFilters` state creates active Environment shell on in-scope clean URLs.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, only because existing Baseline Profile/Baseline Compare Matrix actions can start baseline capture/compare work or link to existing runs.
- **Shared OperationRun UX contract/layer reused**: Existing `OperationUxPresenter`, `OpsUxBrowserEvents`, `OperationRunLinks`, services, jobs, and audit behavior must remain in place.
- **Delegated start/completion UX behaviors**: Existing queued toast, run link, browser event, tenant/workspace-safe URL resolution, authorization, and audit behavior remain delegated to current helpers/services.
- **Local surface-owned behavior that remains**: The pages keep their current initiation controls and filtering UI; this spec changes shell/scope classification, not operation semantics.
- **Queued DB-notification policy**: N/A - no new DB notification behavior.
- **Terminal notification path**: Existing central lifecycle behavior only.
- **Exception required?**: none.
## Provider Boundary / Platform Core Check
- **Shared provider/platform boundary touched?**: yes.
- **Boundary classification**: platform-core route/shell/scope contract; provider-specific Microsoft tenant identity remains provider-owned.
- **Seams affected**: shell resolution, navigation scope, route classification, query key handling, operator vocabulary (`Workspace`, `Environment`, `ManagedEnvironment`), and baseline analysis links.
- **Neutral platform terms preserved or introduced**: `Workspace`, `Environment`, `workspace-owned analysis surface`, `workspace hub`, `filtered workspace hub`, `Environment-owned page`.
- **Provider-specific semantics retained and why**: Existing Intune baseline details, compare strategy, and Graph-backed snapshot content remain provider-specific because they are current product implementation truth.
- **Why this does not deepen provider coupling accidentally**: The fix removes hidden Environment/provider tenant fallback from workspace-owned pages instead of adding provider ID routes or alias query support.
- **Follow-up path**: Spec 321 decides Alerts/Audit Log `environment_id` behavior. Spec 322 adds durable browser no-drift regression infrastructure.
## 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 |
|---|---:|---|---|---|---|---|
| Baselines / Baseline Profiles | yes | Existing Filament Resource | navigation, shell, breadcrumbs, actions | route, shell, page, copy | no | Workspace-owned baseline library. |
| Baseline Profile detail/edit | yes | Existing Filament Resource pages | breadcrumbs, header actions, related links | route, shell, record page | no | Workspace-owned record pages. |
| Baseline Compare Matrix | yes | Existing Filament Page + Blade view | analysis page, action links, OperationRun links | route, shell, query state | no | Workspace-owned analysis matrix, not Baseline Compare landing. |
| Baseline Snapshots | yes | Existing Filament Resource | navigation, shell, filters | route, shell, page, copy | no | Workspace-owned snapshot library/report. |
| My Findings / Intake / Hygiene | yes | Existing Filament Pages + tables | analysis queues, filters, clear actions | route, shell, URL query, table filters | no | Workspace-owned analysis pages with optional Environment filtering. |
| Cross-environment Compare | yes | Existing Filament Page + form | analysis page and environment selectors | route, shell, query state | no | Workspace-owned portfolio analysis. |
| Baseline Compare | yes | Existing Environment-owned page | regression only | route, shell | no | Must remain Environment-owned from Spec 319. |
## 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 |
|---|---|---|---|---|---|---|---|
| Baselines / Baseline Profiles | Secondary Context | Operator manages reusable baseline definitions | profile name, status, capture mode, snapshot truth, next step | assignments, compare matrix, capture run detail | Library/register, not Environment posture page | Workspace governance library | Removes false selected-Environment ownership. |
| Baseline Snapshots | Tertiary Evidence / Diagnostics | Operator inspects immutable snapshot evidence | baseline, captured time, outcome, coverage, next step | snapshot detail and related context | Evidence library, not active Environment dashboard | Workspace baseline evidence review | Keeps evidence scan workspace-owned. |
| Baseline Compare Matrix | Secondary Context | Operator reviews assigned-environment compare matrix for one baseline | matrix state, filters, visible assignments, compare readiness | run/finding/environment drilldowns | Workspace-owned analysis across assigned environments | Baseline profile detail to analysis | Clarifies that selected Environment shell is not the page owner. |
| My Findings / Intake / Hygiene | Primary/Secondary Decision Surfaces | Operator reviews assigned, intake, or hygiene finding work across visible environments | queue counts, finding state, Environment column/filter | finding detail | Workspace work queues with optional Environment filter | Governance inbox/workspace overview to queue | Environment becomes a filter, not shell owner. |
| Cross-environment Compare | Secondary Context | Operator selects source/target environments for portfolio comparison | source, target, policy type selection, preview/preflight state | environment detail links and operation links | Workspace portfolio analysis | Workspace portfolio flow | Avoids hidden current Environment changing a two-environment workflow. |
## 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 |
|---|---|---|---|---|---|---|---|
| Workspace-owned baseline library/snapshots | operator-MSP, support-platform | Workspace library/snapshot status and next step | snapshot fidelity, compare readiness, related runs | raw Graph payloads remain out of scope/default-hidden | Open profile/snapshot or capture/compare where already allowed | raw payload/debug detail | Shell states Workspace once; Environment appears only as data column/filter. |
| Findings analysis queues | operator-MSP, support-platform | queue state, finding severity/status, Environment column/filter | hygiene/intake reason, owner/assignee context | raw provider evidence remains on detail/evidence surfaces | Open finding, claim finding, or clear filter where allowed | support/raw evidence | Active filter chip and shell do not duplicate ownership. |
| Cross-environment Compare | operator-MSP, support-platform | source/target selection and preview readiness | preflight detail, operation/run links | raw policy payloads remain out of scope/default-hidden | Generate preflight where allowed | support/raw evidence | Source/target controls are page state, not shell context. |
## 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Baselines | List / Table / Bulk | Workspace-owned library resource | Open or create baseline profile | Existing clickable row | existing | Existing More/detail header | Existing archive/capture/compare confirmations stay as implemented | `/admin/baseline-profiles` | `/admin/baseline-profiles/{record}` | Workspace shell, no active Environment | Baselines | profile status, capture mode, snapshot truth | none |
| Baseline Snapshots | List / Table | Workspace-owned evidence report | Open snapshot | Existing clickable row | existing | Existing related action | N/A | `/admin/baseline-snapshots` | `/admin/baseline-snapshots/{record}` | Workspace shell, no active Environment | Baseline Snapshots | outcome, coverage, next step | none |
| Baseline Compare Matrix | Workflow / Page | Workspace-owned analysis page | Compare assigned environments or inspect matrix | Explicit controls/links | N/A | Existing header/actions | Existing high-impact compare action keeps confirmation | `/admin/baseline-profiles/{record}/compare-matrix` | N/A | Workspace shell, explicit source data in page | Compare matrix | baseline and matrix scope | none |
| Findings analysis queues | List / Table | Workspace-owned work queues/reports | Open or claim finding | Existing clickable row/action | existing | Existing header/table actions | Existing claim behavior remains authorized | `/admin/findings/my-work`, `/admin/findings/intake`, `/admin/findings/hygiene` | existing finding detail route | Workspace shell, optional Environment filter chip | Findings | queue scope, severity, status, Environment column | none |
| Cross-environment Compare | Workflow / Page | Workspace-owned portfolio analysis | Select source/target, generate preflight | Explicit form/actions | N/A | Header actions | Existing promotion preflight/execution confirmations remain | `/admin/cross-environment-compare` | N/A | Workspace shell, explicit source/target selectors | Cross-environment compare | selected environments and preview readiness | none |
| Baseline Compare | Workflow / Page | Environment-owned posture page | Regression only | Environment route | N/A | Existing | Existing Compare Now remains confirmed | N/A | `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare` | Workspace + Environment shell | Baseline Compare | selected Environment posture | 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Baselines | Workspace operator / manager | Manage reusable governance baseline definitions | Workspace-owned library | Which baseline should govern environments? | status, capture mode, snapshot truth, assignments count, next step | capture details, compare matrix, runs | lifecycle, data completeness, governance result | TenantPilot baseline library plus existing queued capture/compare | create/open/edit/capture/compare per current capability | archive/capture/compare remain confirmation/authorization governed |
| Baseline Snapshots | Workspace operator / support | Inspect immutable baseline evidence | Workspace-owned evidence report | Which baseline snapshots are usable? | captured time, outcome, coverage, next step | fidelity detail, related context | lifecycle, evidence completeness | TenantPilot immutable snapshot evidence | open snapshot/related record | none |
| Findings analysis queues | Workspace operator | Review assigned/intake/hygiene work across visible environments | Workspace-owned queues | What governance work needs attention? | finding, severity, status, Environment, queue reason | owner/assignee/hygiene diagnostics | workflow state, severity, SLA | Existing finding workflow only | open finding, claim finding, clear filter | existing mutations remain authorized |
| Cross-environment Compare | Workspace operator / manager | Compare/promote configuration across environments | Workspace-owned portfolio analysis | Which source/target comparison is valid? | selected source/target, preview/preflight readiness | preflight detail, run links | selection validity, execution readiness | Existing queued operation / Microsoft read-write behavior | generate preflight / execute if already implemented | existing execution action remains guarded |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no. The classification expresses existing product ownership; it does not create new domain truth.
- **New persisted entity/table/artifact?**: no.
- **New abstraction?**: possibly a narrow enum case or registry section if existing `WorkspaceHubRegistry` cannot represent workspace-owned analysis surfaces without falsely making them workspace hubs.
- **New enum/state/reason family?**: no business state. A technical surface-scope enum case is allowed only if it replaces ambiguous generic `WorkspaceScoped` behavior.
- **New cross-domain UI framework/taxonomy?**: no. This uses the existing shell/surface classification vocabulary.
- **Current operator problem**: Clean workspace-owned analysis URLs can show selected Environment shell, making page ownership and data scope misleading.
- **Existing structure is insufficient because**: Generic `AdminSurfaceScope::WorkspaceScoped` allows remembered Environment restore, while `WorkspaceHubRegistry` only covers first-class hubs and filtered hub behavior.
- **Narrowest correct implementation**: Add explicit classifier coverage for the audited workspace-owned analysis paths and make those paths force environmentless shell; use `environment_id` only where the page already supports a visible filter.
- **Ownership cost**: A small set of classifier/link/copy tests plus focused browser smoke. No schema, package, or product workflow cost.
- **Alternative intentionally rejected**: Add the pages blindly to `WorkspaceHubRegistry` if that would imply hub/filter contracts they do not support, or keep remembered fallback because it is convenient.
- **Release truth**: Current-release cleanup before production.
### Compatibility posture
This feature assumes pre-production hard cutover.
Backward compatibility, legacy aliases, remembered Environment fallback, redirect shims, migration shims, dual route models, and compatibility-specific tests are out of scope.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Unit for classifier/registry behavior; Feature/Livewire for page access, shell state, URL/query/filter behavior, and existing action regression; Browser for integrated shell/sidebar/reload/back-forward verification.
- **Validation lane(s)**: fast-feedback, confidence, browser.
- **Why this classification and these lanes are sufficient**: The defect is visible shell/context drift caused by classifier and navigation behavior; unit/feature tests prove the contract and browser smoke proves integrated UI state.
- **New or expanded test families**: Focused `global-context-shell` coverage for workspace-owned analysis pages. Browser additions remain explicit and bounded to Spec 320 flows.
- **Fixture / helper cost impact**: Reuse existing workspace/environment/member/baseline/finding fixtures. Do not introduce global expensive setup defaults.
- **Heavy-family visibility / justification**: Browser coverage is required because reload/back-forward/sidebar shell mismatch was found by browser audit. It must be named as Spec 320 browser smoke, not hidden inside fast lanes.
- **Special surface test profile**: `global-context-shell`.
- **Standard-native relief or required special coverage**: Existing native Filament pages keep normal UI shape; special coverage is shell/context and filter visibility only.
- **Reviewer handoff**: Verify in-scope clean URLs show Workspace shell only, explicit filtered URLs show visible chip where supported, legacy aliases do not create shell/filter state, and Baseline Compare remains Environment-owned.
- **Budget / baseline / trend impact**: none expected; if browser runtime grows materially, record in the active PR close-out.
- **Escalation needed**: document-in-feature.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage.
- **Planned validation commands**:
- `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=WorkspaceOwnedAnalysis`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=BaselineCompareEnvironmentRouteContract`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubEnvironmentFilterContract`
- focused Spec 320 browser smoke for Baselines, Baseline Snapshots, Baseline Compare Matrix, My Findings/Intake/Hygiene, Cross-environment Compare, Baseline Compare regression, and Decision Register regression.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Open Baseline Library Without Environment Shell (Priority: P1)
As a workspace operator, I can open Baselines and Baseline Snapshots from workspace or environment navigation and see Workspace shell only, so reusable baseline libraries do not appear owned by the last Environment I visited.
**Why this priority**: Baseline library ownership is the main known mismatch and directly affects governance trust.
**Independent Test**: Start from an active Environment context, open clean Baselines and Baseline Snapshots URLs, and assert the shell has Workspace only with no active Environment.
**Acceptance Scenarios**:
1. **Given** a remembered Environment exists, **When** the operator opens `/admin/baseline-profiles`, **Then** the shell shows Workspace only and no visible active Environment.
2. **Given** the operator navigates from Environment Dashboard sidebar to Baseline Snapshots, **When** `/admin/baseline-snapshots` opens, **Then** it is a clean workspace-owned page with no hidden Environment shell.
3. **Given** a valid baseline profile record, **When** view/edit/compare-matrix pages open by clean workspace URL, **Then** remembered Environment state does not become shell context.
---
### User Story 2 - Workspace Analysis Pages Do Not Inherit Environment Context (Priority: P1)
As a workspace operator, I can open My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare without the previous Environment owning the shell, so workspace analysis/work queues stay scope-honest.
**Why this priority**: Spec 318 found these unregistered workspace analysis pages inherit remembered Environment shell because they are not registered or classified.
**Independent Test**: With remembered Environment state present, open each clean URL and assert Workspace shell only; if `environment_id` is supported, assert it is a visible filter, not shell ownership.
**Acceptance Scenarios**:
1. **Given** an active Environment context, **When** the operator opens `/admin/findings/my-work`, `/admin/findings/intake`, or `/admin/findings/hygiene`, **Then** shell context remains Workspace only.
2. **Given** a valid `environment_id` filter is supported for a findings analysis page, **When** the page opens with `?environment_id=...`, **Then** shell remains Workspace only and a visible Environment filter chip/clear action exists.
3. **Given** the operator opens `/admin/cross-environment-compare`, **When** source/target selectors are empty or query-hydrated, **Then** shell is Workspace only and selected source/target environments are page state.
---
### User Story 3 - Legacy Query Aliases Cannot Recreate Hidden Environment Shell (Priority: P1)
As a security reviewer, I need legacy query aliases and Filament table filter query payloads to be ignored or stripped on workspace-owned analysis surfaces, so old Tenant/Environment parameters cannot silently resurrect shell context.
**Why this priority**: Specs 314-317 intentionally removed legacy Tenant context and hidden filter state.
**Independent Test**: Open each in-scope page with `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, and `tableFilters` payloads and assert no active Environment shell or hidden filter appears.
**Acceptance Scenarios**:
1. **Given** a clean workspace-owned analysis page, **When** the URL includes `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, or `tableFilters`, **Then** those aliases do not set shell Environment or filter state.
2. **Given** the page supports canonical `environment_id`, **When** invalid or cross-workspace `environment_id` is supplied, **Then** it is rejected/ignored without shell context or leakage.
---
### User Story 4 - Environment-Owned and Workspace Hub Regressions Remain Green (Priority: P2)
As a reviewer, I can verify Baseline Compare remains Environment-owned and Decision Register remains a filtered workspace hub, so Spec 320 does not undo Specs 314-319.
**Why this priority**: The fix targets the opposite side of Spec 319 and must not flatten Environment-owned pages into workspace pages.
**Independent Test**: Assert canonical Baseline Compare route shows Workspace + Environment shell, old workspace-style Baseline Compare URLs still do not render, and Decision Register clean/filtered contracts remain green.
**Acceptance Scenarios**:
1. **Given** a canonical Baseline Compare Environment route, **When** the page opens, **Then** Workspace + active Environment shell remains present.
2. **Given** `/admin/governance/decisions`, **When** opened clean or with `?environment_id=...`, **Then** it behaves exactly as Specs 314-316 require.
### Edge Cases
- A workspace-owned analysis page has no Environment selected or remembered.
- A remembered Environment belongs to the current Workspace.
- A remembered Environment belongs to a different Workspace.
- A valid `environment_id` is supplied on a page that supports explicit filtering.
- A valid `environment_id` is supplied on a page that does not support explicit filtering.
- Legacy query aliases are supplied individually and together.
- Browser reload/back/forward crosses Environment Dashboard, workspace-owned analysis pages, Baseline Compare, and Decision Register.
- Livewire update requests use referer-based surface classification and must not reintroduce remembered Environment shell.
## Functional Requirements
- **FR-001**: Baselines/Baseline Profiles MUST be explicitly classified as workspace-owned analysis/library surfaces unless implementation evidence proves a narrower Environment-owned split is required.
- **FR-002**: Baseline Snapshots MUST be explicitly classified as workspace-owned analysis/library surfaces unless implementation evidence proves a narrower Environment-owned split is required.
- **FR-003**: Baseline Profile view/edit pages and Baseline Compare Matrix MUST be classified so clean URLs force Workspace shell only.
- **FR-004**: My Findings, Findings Intake, Findings Hygiene, Cross-environment Compare, and any other Spec 318 unregistered workspace analysis pages confirmed in repo inspection MUST be registered/classified or explicitly excluded with evidence.
- **FR-005**: In-scope clean workspace-owned analysis URLs MUST NOT restore remembered Environment shell context.
- **FR-006**: In-scope clean URLs MUST open without requiring an active Environment context.
- **FR-007**: Workspace context MUST remain selected and valid; the fix MUST NOT clear Workspace state.
- **FR-008**: If a page supports Environment filtering, the only public filter key MUST be `environment_id`.
- **FR-009**: `environment_id` filtering MUST keep Workspace shell only and show a visible Environment filter chip or equivalent clear affordance.
- **FR-010**: `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, and `tableFilters` MUST NOT create active Environment shell or hidden Environment filter state on in-scope pages.
- **FR-011**: Cross-workspace or unauthorized `environment_id` MUST NOT leak Environment identity or become shell context.
- **FR-012**: Sidebar/global/workspace navigation entries for in-scope pages MUST emit clean workspace URLs unless they intentionally emit canonical `environment_id` filter URLs with visible chips.
- **FR-013**: Environment Dashboard links to workspace-owned analysis pages MUST use clean workspace URLs or explicit `environment_id` filter URLs; they MUST NOT carry active Environment shell ownership.
- **FR-014**: Breadcrumb/header/copy for workspace-owned analysis pages MUST not imply active Environment ownership as the primary scope.
- **FR-015**: Workspace-owned analysis pages MAY show Environment columns, badges, selectors, and filter labels as data/filter state.
- **FR-016**: Baseline Compare MUST remain Environment-owned according to Spec 319.
- **FR-017**: Decision Register, Governance Inbox, Operations, Finding Exceptions Queue, Provider Connections, Evidence Overview, Review Register, Customer Review Workspace, Workspace Settings, Manage Workspaces, and Managed Environments MUST retain their existing workspace hub contracts.
- **FR-018**: Alerts/Audit Log `environment_id` behavior MUST NOT be decided or changed except as regression context; that belongs to Spec 321.
- **FR-019**: No compatibility route, redirect shim, legacy alias support, migration, seeder, package, env var, queue, scheduler, storage, or deployment asset change may be introduced for this spec.
- **FR-020**: Browser verification screenshots SHOULD be saved under `specs/320-workspace-owned-analysis-surface-registration-shell-cutover/artifacts/screenshots/` when generated.
## Acceptance Criteria
### Surface Classification
- [x] Baselines/Baseline Profiles classified as workspace-owned or explicitly reclassified with evidence.
- [x] Baseline Snapshots classified as workspace-owned or explicitly reclassified with evidence.
- [x] Baseline Profile detail/edit/compare-matrix classified as workspace-owned analysis surfaces.
- [x] My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare classified or explicitly excluded with evidence.
- [x] No in-scope workspace-owned analysis page remains generic ambiguous `WorkspaceScoped` if that permits remembered Environment shell fallback.
- [x] Baseline Compare remains Environment-owned.
### Shell Context
- [x] Baselines clean URL shows Workspace shell only.
- [x] Baseline Snapshots clean URL shows Workspace shell only.
- [x] Baseline Profile detail/edit/compare-matrix clean URLs show Workspace shell only.
- [x] Findings analysis and Cross-environment Compare clean URLs show Workspace shell only.
- [x] Opening in-scope pages from Environment context cuts to Workspace shell only.
- [x] Reload and browser back/forward do not restore active Environment shell.
### URL / Query Contract
- [x] In-scope clean URLs do not require Environment context.
- [x] Legacy query aliases do not create shell/filter state.
- [x] Supported Environment filters use only `environment_id`.
- [x] Visible filter chip/clear behavior exists for pages that support `environment_id`.
- [x] Unsupported `environment_id` is ignored/stripped/rejected without hidden shell/data mismatch; chosen behavior is documented in implementation close-out.
### Regression
- [x] Spec 314 clean workspace hub entry remains green.
- [x] Spec 315 Environment CTA `environment_id` contract remains green.
- [x] Spec 316 clear filter contract remains green.
- [x] Spec 317 legacy Tenant cleanup remains green.
- [x] Spec 319 Baseline Compare Environment-owned route remains green.
- [x] Spec 318 workspace-owned analysis mismatch is resolved for in-scope surfaces.
## Out of Scope
- Baseline Compare implementation changes beyond regression checks.
- Alerts/Audit Log filter decision from Spec 321.
- Durable browser no-drift automation from Spec 322.
- New product features, new baseline assignment semantics, or baseline data model redesign.
- Migrations, seeders, data backfills, package changes, env vars, queues, scheduler, storage, or deployment asset changes.
- Legacy alias support, backwards compatibility, redirects, dual shell models, or remembered Environment fallback preservation.
## Source Evidence
- `specs/318-admin-surface-scope-shell-context-audit/audit-report.md` identifies the classifier gap and workspace-owned baseline/analysis mismatches.
- `specs/318-admin-surface-scope-shell-context-audit/surface-inventory.md` lists Baselines/Baseline Profiles, Baseline Snapshots, Baseline Compare Matrix, My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare as mismatches or unregistered workspace analysis pages.
- `specs/318-admin-surface-scope-shell-context-audit/mismatch-findings.md` records M2 and M4 as the direct targets.
- `specs/318-admin-surface-scope-shell-context-audit/recommended-fixes.md` recommends a Workspace-Owned Baseline Registry Contract and broader classifier coverage.
- `specs/319-environment-owned-surface-routing-shell-context-contract/spec.md` leaves workspace-owned baseline analysis surfaces explicitly to Spec 320.
## Assumptions
- Baseline Profiles and Baseline Snapshots are workspace-owned based on current `workspace_id` queries and Spec 318 evidence.
- Baseline Compare Matrix is workspace-owned analysis over assigned Environments, while Baseline Compare landing remains Environment-owned.
- Findings analysis pages are workspace-owned queues/reports with optional Environment filters, not Environment-owned pages.
- Cross-environment Compare is workspace-owned portfolio analysis because it explicitly selects source/target Environments.
- Current product is pre-production; hard cutover is correct.
## Risks
- Adding all in-scope pages to `WorkspaceHubRegistry` could accidentally imply filtered-hub behavior for pages that do not support chips/clear state.
- A new classifier enum case could become a broader taxonomy if not constrained to the audited route list.
- Existing tests may assert remembered Environment fallback and must be replaced, not preserved.
- Browser smoke can become too broad; keep it focused to Spec 318 evidence and required regressions.
## Open Questions
- Exact unsupported `environment_id` behavior for pages without filter support: ignore, strip, or reject. Implementation must choose per page and document the result.
- Whether Baseline Profile create page should be included in workspace-owned analysis classification if implementation finds it inherits Environment shell.
- Whether findings analysis pages should converge on canonical `environment_id` visible chip behavior in this spec or only prevent shell inheritance; if current filter UI is not chip-compatible, keep the fix narrow and document follow-up rather than adding feature scope.
## Follow-up Spec Candidates
- **321 - Alerts / Audit Log Environment Filter Contract Decision**: decide support vs rejection of `environment_id` with no half-state.
- **322 - Browser No-Drift Regression Guard**: durable browser coverage for shell/context/filter/reload/back-forward drift.

View File

@ -0,0 +1,151 @@
# Tasks: Workspace-Owned Analysis Surface Registration & Shell Cutover
**Input**: Design documents from `/specs/320-workspace-owned-analysis-surface-registration-shell-cutover/`
**Prerequisites**: `plan.md`, `spec.md`
**Tests**: Required. This is a runtime route/shell/query/navigation contract change.
## Test Governance Checklist
- [x] Lane assignment is named and is the narrowest sufficient proof for classifier, shell, query, reload/history, and regression 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 implementation starts from branch `320-workspace-owned-analysis-surface-registration-shell-cutover` and record any unrelated uncommitted files.
- [x] T002 Re-read `specs/318-admin-surface-scope-shell-context-audit/audit-report.md`, `surface-inventory.md`, `page-matrix.md`, `mismatch-findings.md`, and `recommended-fixes.md`.
- [x] T003 Re-read `specs/319-environment-owned-surface-routing-shell-context-contract/spec.md`, `plan.md`, and `tasks.md` as dependency context only.
- [x] T004 Confirm Laravel/Filament/Livewire/Pest versions through Laravel Boost `application_info`.
- [x] T005 Confirm no migration, seeder, package, env var, queue, scheduler, storage, or deployment asset change is required.
- [x] T006 Inventory current classifier behavior in `apps/platform/app/Support/Navigation/AdminSurfaceScope.php`.
- [x] T007 Inventory current workspace hub behavior in `apps/platform/app/Support/Navigation/WorkspaceHubRegistry.php` and `WorkspaceSidebarNavigation.php`.
- [x] T008 Inventory shell resolution in `apps/platform/app/Support/OperateHub/OperateHubShell.php`, including remembered Environment and query hint behavior.
- [x] T009 Inventory in-scope baseline routes/pages in `BaselineProfileResource`, `BaselineSnapshotResource`, and `BaselineCompareMatrix`.
- [x] T010 Inventory in-scope workspace analysis routes/pages in `MyFindingsInbox`, `FindingsIntakeQueue`, `FindingsHygieneReport`, and `CrossEnvironmentComparePage`.
- [x] T011 Search routes/navigation for any additional Spec 318 unregistered workspace analysis page still present and decide include/exclude with evidence.
- [x] T012 Identify any existing tests asserting remembered Environment fallback on in-scope pages and mark them for replacement.
## Phase 2: Tests First / Contract Coverage
**Purpose**: Add failing or alongside tests that define the new contract.
- [x] T013 Add/update `apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php` proving Baselines/Baseline Profiles paths are workspace-owned analysis or environmentless shell paths.
- [x] T014 Add/update `apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php` proving Baseline Snapshots paths are workspace-owned analysis or environmentless shell paths.
- [x] T015 Add/update `apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php` proving Baseline Profile detail/edit/compare-matrix paths do not allow remembered Environment restore.
- [x] T016 Add/update `apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php` proving My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare paths do not allow remembered Environment restore.
- [x] T017 Add/update `apps/platform/tests/Unit/Support/OperateHub/OperateHubShellResolutionTest.php` proving workspace-owned analysis clean URLs show Workspace shell only when a remembered Environment exists.
- [x] T018 Add/update `apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php` proving in-scope pages are not accidentally treated as full workspace hubs unless implementation intentionally registers them with hub behavior.
- [x] T019 Add/update tests proving clean Baselines and Baseline Snapshots URLs open without active Environment context.
- [x] T020 Add/update tests proving Baseline Profile view/edit/compare-matrix URLs open with Workspace shell only.
- [x] T021 Add/update tests proving My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare clean URLs open with Workspace shell only.
- [x] T022 Add/update tests proving remembered Environment state alone does not set shell context on in-scope pages.
- [x] T023 Add/update tests proving `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, and `tableFilters` do not create shell or filter state on in-scope pages.
- [x] T024 For pages that support canonical `environment_id`, add/update tests proving shell remains Workspace only and a visible chip/clear affordance exists.
- [x] T025 For pages that do not support canonical `environment_id`, add/update tests proving unsupported `environment_id` is ignored, stripped, or rejected without hidden shell/data mismatch.
- [x] T026 Add/update tests proving Environment Dashboard or Environment-origin links to in-scope workspace-owned analysis pages do not carry active Environment shell ownership.
- [x] T027 Keep/add Baseline Compare regression coverage proving its canonical route remains Environment-owned.
- [x] T028 Keep/add Decision Register regression coverage proving clean and filtered workspace hub behavior remains green.
- [x] T029 Keep/add Specs 314-317 regression coverage for clean workspace hub entry, Environment CTA `environment_id`, clear filter, and no legacy Tenant aliases.
- [x] T030 Add/update existing high-impact baseline action tests only as needed to prove capture/compare actions still keep confirmation, authorization, audit, and OperationRun UX after shell classification changes.
## Phase 3: Classification and Shell Cutover
**Purpose**: Implement the narrowest route/shell classification fix.
- [x] T031 Update `apps/platform/app/Support/Navigation/AdminSurfaceScope.php` to classify in-scope workspace-owned analysis paths explicitly.
- [x] T032 Ensure the chosen classification forces environmentless shell context for clean workspace-owned analysis URLs.
- [x] T033 Ensure the chosen classification does not allow remembered Environment restore.
- [x] T034 Ensure Livewire referer-based classification uses the same in-scope path behavior.
- [x] T035 If adding a new `AdminSurfaceScope` enum case, complete the proportionality note in implementation close-out and avoid using it outside audited routes.
- [x] T036 If registering any in-scope page in `WorkspaceHubRegistry`, verify it truly satisfies workspace hub/filter/clear semantics and add matching registry tests.
- [x] T037 Keep `WorkspaceHubRegistry::forbiddenQueryKeys()` and related query-cleaning behavior aligned with no legacy aliases.
- [x] T038 Do not add Baseline Compare to workspace hub or workspace-owned analysis classification.
- [x] T039 Do not alter Environment-bound route classification for required permissions, inventory, backups, evidence, reviews, stored reports, review packs, or other Spec 319 out-of-scope Environment pages.
## Phase 4: Baseline Surfaces
**Purpose**: Align baseline library/report pages with workspace-owned shell semantics.
- [x] T040 Update `apps/platform/app/Filament/Resources/BaselineProfileResource.php` only if needed so list/view/edit/create URLs and navigation do not rely on active Environment shell.
- [x] T041 Update Baseline Profile breadcrumbs/header/copy if any primary wording implies active Environment ownership.
- [x] T042 Update Baseline Profile related navigation links if they carry hidden Environment shell ownership or legacy query aliases.
- [x] T043 Update `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php` only if needed so the page shell is Workspace only while source Environment drilldowns remain explicit page links.
- [x] T044 Preserve Baseline Compare Matrix filter query keys such as `tenant_sort` only as page matrix state, not shell Environment ownership.
- [x] T045 Update `apps/platform/app/Filament/Resources/BaselineSnapshotResource.php` only if needed so list/view URLs and copy remain workspace snapshot library/report semantics.
- [x] T046 Preserve Baseline Profile and Baseline Snapshot global search disabled status unless implementation deliberately verifies and tests safe View/Edit pages.
- [x] T047 Preserve existing baseline archive/capture/compare action confirmation, authorization, notifications, audit, and OperationRun behavior.
## Phase 5: Findings and Portfolio Analysis Surfaces
**Purpose**: Align unregistered workspace analysis pages found by Spec 318.
- [x] T048 Update `apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php` only if needed so clean URL shell is Workspace only.
- [x] T049 Update `apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php` only if needed so clean URL shell is Workspace only.
- [x] T050 Update `apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php` only if needed so clean URL shell is Workspace only.
- [x] T051 Replace `tenant` query prefilter handling on findings analysis pages with canonical `environment_id` if this is already intended product behavior and can show visible filter/clear affordance.
- [x] T052 If findings analysis pages cannot safely migrate filter behavior in this slice, prevent shell inheritance and document filter-key follow-up rather than adding half-state support.
- [x] T053 Update `apps/platform/app/Filament/Pages/CrossEnvironmentComparePage.php` only if needed so source/target Environment selectors remain page state and shell is Workspace only.
- [x] T054 Ensure Cross-environment Compare direct URL, query-hydrated URL, and reload do not restore remembered Environment shell.
- [x] T055 Preserve existing promotion/preflight authorization, confirmation, audit, OperationRun links, and provider-boundary behavior.
## Phase 6: Navigation, Links, Copy, and Legacy Aliases
**Purpose**: Make visible navigation agree with page ownership.
- [x] T056 Update `WorkspaceSidebarNavigation` or related navigation builders only if in-scope pages are present there and currently emit ambiguous URLs.
- [x] T057 Update `ManagedEnvironmentLinks` only if Environment-origin cards/actions link to workspace-owned analysis pages with hidden shell ownership.
- [x] T058 Ensure sidebar/global/workspace entry to Baselines, Baseline Snapshots, baseline matrix, findings analysis pages, and Cross-environment Compare uses clean workspace URLs unless canonical `environment_id` filter is intentionally supported.
- [x] T059 Ensure Environment Dashboard links to in-scope workspace-owned analysis pages use clean workspace URLs or explicit `environment_id` filter URLs with visible chip/clear behavior.
- [x] T060 Remove or replace user-facing copy that says `this environment`, `current environment`, or similar primary ownership wording on in-scope workspace-owned analysis pages.
- [x] T061 Keep Environment columns, Environment badges, source/target selectors, and Environment filters where they are data/filter state rather than shell ownership.
- [x] T062 Ensure no in-scope page starts accepting `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, or `tableFilters` as public Environment filter aliases.
## Phase 7: Browser Verification
**Purpose**: Prove visible route/shell/copy behavior.
- [x] T063 Start local platform stack using Sail or the repo platform dev command.
- [x] T064 Browser Flow A: Workspace Overview -> Baselines; verify Workspace shell only, no active Environment, workspace/library wording.
- [x] T065 Save Flow A screenshot to `specs/320-workspace-owned-analysis-surface-registration-shell-cutover/artifacts/screenshots/workspace-origin--baselines.png`.
- [x] T066 Browser Flow B: Environment Dashboard -> Baselines through sidebar/global/navigation/card; verify shell cuts to Workspace only.
- [x] T067 Save Flow B screenshot to `artifacts/screenshots/environment-origin--baselines.png`.
- [x] T068 Browser Flow C: reload Baselines and verify active Environment shell does not return.
- [x] T069 Save Flow C screenshot to `artifacts/screenshots/baselines--after-reload.png`.
- [x] T070 Repeat workspace origin, environment origin, and reload flows for Baseline Snapshots.
- [x] T071 Save Baseline Snapshots screenshots using `workspace-origin--baseline-snapshots.png`, `environment-origin--baseline-snapshots.png`, and `baseline-snapshots--after-reload.png`.
- [x] T072 Browser verify Baseline Compare Matrix direct/reload/back-forward behavior if local fixture has a baseline profile.
- [x] T073 Browser verify My Findings, Findings Intake, Findings Hygiene, and Cross-environment Compare clean URLs from remembered Environment context.
- [x] T074 Browser verify Baseline Compare remains Environment-owned.
- [x] T075 Save Baseline Compare regression screenshot to `artifacts/screenshots/baseline-compare--regression-environment-owned.png`.
- [x] T076 Browser verify Decision Register clean and filtered workspace hub regressions.
- [x] T077 Save Decision Register regression screenshot to `artifacts/screenshots/decision-register--regression-workspace-hub.png`.
- [x] T078 If browser setup or fixture data blocks any flow, document the exact blocker and alternate proof in the implementation close-out.
> Browser coverage note: Matrix and Baseline Compare browser flows were partially blocked by local Spec 180 fixture/capability state. The local fixture has no baseline profile for a matrix browser flow, and the environment-owned Baseline Compare browser route is intentionally capability-denied. The environment-owned Baseline Compare contract and matrix route behavior are covered by focused Pest route/RBAC tests. No runtime gap remains in Spec 320 scope.
## Phase 8: Final Validation and Close-Out
**Purpose**: Complete implementation proof without broad rebaseline.
- [x] T079 Run `git diff --check`.
- [x] T080 Run the focused Pest commands listed in `plan.md`.
- [x] T081 Run formatting with the repo-standard Pint command for touched PHP files.
- [x] T082 Review `git diff --stat` and confirm only in-scope runtime/test/spec artifacts changed.
- [x] T083 Confirm no migrations, seeders, package files, env files, queue/scheduler/storage config, or deployment asset files changed.
- [x] T084 Confirm no backwards compatibility layer, redirect shim, dual route model, or legacy query alias support was introduced.
- [x] T085 Prepare final implementation report with changed behavior, workspace-owned analysis surfaces classified, surfaces registered, files changed, tests, browser verification, screenshots path, follow-ups 321/322, and any unrelated residual failures.
- [x] T086 Include the Filament v5 output contract in the final report: Livewire v4.0+ compliance, provider registration location, global search status, destructive/high-impact actions, asset strategy, and testing plan/results.
## Explicit Non-Tasks
- [x] NT001 Do not implement Alerts/Audit Log filter behavior; leave it to Spec 321.
- [x] NT002 Do not build durable browser no-drift infrastructure; leave it to Spec 322.
- [x] NT003 Do not change Baseline Compare except regression coverage needed to prove Spec 319 remains intact.
- [x] NT004 Do not add migrations, seeders, packages, env vars, queues, scheduler, storage, or deployment asset changes.
- [x] NT005 Do not add legacy `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, or `tableFilters` aliases.
- [x] NT006 Do not preserve remembered Environment fallback for workspace-owned analysis clean URLs.