# Implementation Plan: Spec 338 - Workspace / Environment Resource Scope Contract - Branch: `338-workspace-environment-resource-scope-contract` - Date: 2026-05-30 - Spec: `specs/338-workspace-environment-resource-scope-contract/spec.md` - Input: User-provided Spec 338 draft + repo inspection for link/query seams. ## Summary Harden TenantPilot’s resource scope contract by tightening the canonical deep-link and query contract for workspace hubs and by eliminating first-party helper outputs that encode Filament internals (`tableFilters[...]`) as a product-level URL contract. This is contract-first and targeted: - `OperationRunLinks::index(..., operationType: ...)` must stop emitting `tableFilters[type][value]`. - Evidence scope special casing under `/admin/evidence/*` must be either proven real and contractual, or removed as stale ambiguity. - Environment-owned sidebar navigation must keep environment-owned entries primary and move workspace-wide/admin links into explicitly labeled cross-scope groups. - Baseline ownership/navigation is regression-only (Spec 320 already completed; do not reopen). ## Technical Context - Language/Version: PHP 8.4.15, Laravel 12.52.x. - Primary Dependencies: Filament 5.2.x, Livewire 4.1.x, Pest 4.x, Tailwind CSS 4.x. - Storage: PostgreSQL; no schema change expected. - Testing: Pest Feature tests + minimal browser smoke only if navigation presentation is materially affected. - Validation Lanes: fast-feedback (Feature) + browser (smoke, scoped). - Target Platform: Sail locally; Dokploy/container deployment posture unchanged. - Project Type: Laravel monolith under `apps/platform`. - Constraints: No new persisted truth, migrations, packages, env vars, queue/scheduler changes, or route architecture rewrite. ## UI / Surface Guardrail Plan - **Guardrail scope**: changed existing operator-facing scope/link behavior (navigation + deep links). - **Affected surfaces**: - Workspace hub links to Operations (`OperationRunLinks` and any `CanonicalNavigationContext` filter payload usage). - Evidence Overview hub + “clear environment context” redirect behavior. - Environment → workspace hub “filtered” links (`environment_id` must remain canonical). - Environment sidebar grouping for workspace-wide/admin links. - **Native vs custom**: native Filament + existing project navigation helpers; no custom UI framework. - **Shared-family relevance**: navigation entry points, scope presentation, deep links, hub filtering, OperationRun “view in collection” links. - **State layers in scope**: shell scope (route-owned), URL query contract, local table filter state (internal translation only). - **Handling modes**: review-mandatory. - **Required tests / smoke**: - Feature tests for URL contract + helper output. - Optional minimal browser smoke when sidebar/scope presentation changes are user-visible. - **UI/Productization coverage**: no new routes/pages expected; capture screenshots only when needed to prove a scope regression fix. ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes. - **Systems touched (expected)**: - `apps/platform/app/Support/OperationRunLinks.php` - `apps/platform/app/Support/Navigation/CanonicalNavigationContext.php` - `apps/platform/app/Support/Navigation/AdminSurfaceScope.php` - `apps/platform/app/Support/Navigation/WorkspaceHubNavigation.php` - `apps/platform/app/Http/Controllers/ClearEnvironmentContextController.php` - `apps/platform/app/Support/Navigation/WorkspaceHubRegistry.php` - `apps/platform/app/Support/Navigation/WorkspaceSidebarNavigation.php` - **New abstraction introduced?**: `WorkspaceHubNavigation`, a narrow helper for environment-surface hub grouping and explicit `environment_id` URL carry. - **Shared abstractions reused**: existing `AdminSurfaceScope` + hub registry + navigation context; do not create a second taxonomy framework. - **Bounded deviation**: if Filament requires `tableFilters` internally, keep it internal (page-level translation) and keep first-party helper output contract stable. ## OperationRun UX Impact Link semantics only (no new OperationRun types, no lifecycle changes): - Stop emitting Filament internals as deep-link contract for operation type filtering. - Decide between: 1) `operation_type=` accepted by Operations page and mapped to internal table state, or 2) removing operation-type deep-linking entirely if safe mapping is not feasible without bloat. ## Implementation Approach ### Phase 1 — Repo truth + failing tests first - Inventory current first-party helper outputs and navigation contexts that emit: - `tableFilters[...]` (confirmed in `OperationRunLinks`; re-check `CanonicalNavigationContext` usage and call sites) - legacy `/admin/evidence/*` special casing branches (`AdminSurfaceScope`, `ClearEnvironmentContextController`) - Add failing tests that lock the desired contract: - `OperationRunLinks::index(..., operationType: ...)` must not contain `tableFilters`. - Evidence Overview is workspace hub; any `/admin/evidence/*` environment-scope handling is either intentional + tested or removed. ### Phase 2 — OperationRunLinks query contract - Change `OperationRunLinks::index`: - replace `tableFilters[type][value]` emission with a stable query key (`operation_type`) or remove operation-type deep linking. - Update the Operations page boundary to translate `operation_type` into internal table state where needed (keep `environment_id` canonical). ### Phase 3 — Navigation context payload hygiene - Re-check `CanonicalNavigationContext::toQuery()` usage: - prefer keeping navigation metadata under `nav[...]` only, - avoid emitting additional top-level filter payload that encodes `tableFilters` for hub filtering when `environment_id` is sufficient. - Adjust the specific call sites (e.g. RelatedNavigationResolver contexts) that currently inject `tableFilters[managed_environment_id]` into query strings when linking to Operations. ### Phase 4 — Evidence scope special casing - Verify actual route inventory for `/admin/evidence/*` beyond overview. - Remove stale classification or redirect rules only when route inventory proves they are not real, or explicitly document + test the remaining route family if it is still reachable. ### Phase 5 — Validation and regression posture - Split Environment sidebar IA: - keep environment-owned resources in their domain groups, - move workspace hub entries into “Workspace-wide” on environment pages, - move workspace configuration/admin entries into “Workspace admin” on environment pages, - preserve explicit `environment_id` only for workspace hubs that already accept that filter. Run narrow tests first: - `cd apps/platform && ./vendor/bin/sail artisan test --compact ` - `cd apps/platform && ./vendor/bin/sail pint --dirty --format agent` - `git diff --check` Run minimal browser smoke only if link/scope changes are user-visible in navigation: - `cd apps/platform && php vendor/bin/pest tests/Browser --filter=Spec338 --compact` ## Deployment / Ops Impact - Migrations: none expected. - Env vars: none expected. - Queues/scheduler: none expected. - Filament assets: no new registered assets expected; `filament:assets` posture unchanged.