# Implementation Plan: Environment CTA Explicit Filter Contract **Branch**: `315-environment-cta-explicit-filter-contract` | **Date**: 2026-05-16 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/315-environment-cta-explicit-filter-contract/spec.md) **Input**: Feature specification from `/specs/315-environment-cta-explicit-filter-contract/spec.md` **Preparation status**: Specification artifacts only. No runtime implementation has been performed by this preparation step. ## Summary Spec 315 hard-cuts Environment-owned CTA links into workspace hubs to one canonical explicit filter contract: ```text Environment Dashboard / Environment-owned CTA -> Workspace Hub ?environment_id={managed_environment_id} ``` Workspace hubs remain workspace-scoped. `environment_id` is resolved only inside the selected Workspace, represented as a visible page-level Environment filter chip, applied to data where valid, and removable through a clean hub URL. Legacy query keys such as `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, `environment`, and `tableFilters` are not accepted as valid Environment CTA filter state. ## 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 for this spec **Testing**: Pest 4.3.1 / PHPUnit 12.4.4; focused browser smoke where applicable **Validation Lanes**: fast-feedback, confidence for existing related coverage, browser for focused rendered-state verification **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. Environment filter resolution is one workspace-scoped Managed Environment lookup per relevant request/page mount and normal query narrowing thereafter. **Constraints**: No migrations, seeders, new packages, env vars, queues, scheduler, or storage changes. No backward compatibility layer or dual-param support. **Scale/Scope**: Cross-cutting runtime contract across critical workspace hubs and shared Environment-owned CTA/link helpers. ## UI / Surface Guardrail Plan - **Guardrail scope**: Changed operator-facing surfaces for visible Environment filter state and CTA query contracts. - **Native vs custom classification summary**: Native Filament/Livewire pages/resources with a shared Blade partial or existing shared primitive for a small chip. No redesign. - **Shared-family relevance**: Navigation entry points, dashboard CTAs, scope signals, filter summaries, and clean clear links. - **State layers in scope**: URL query, page state, table/list query state, rendered page header/filter chip, shell context. Full persisted/session/deferred clear semantics are deferred to Spec 316. - **Audience modes in scope**: Operator-MSP and support-platform. Customer-read-only applies only where Customer Review Workspace currently exposes a customer-safe workspace surface. - **Decision/diagnostic/raw hierarchy plan**: Filter truth is default-visible; existing diagnostics/details remain on demand. - **Raw/support gating plan**: No raw evidence exposure changes. - **One-primary-action / duplicate-truth control**: The visible Environment filter chip is the single page-level truth for explicit Environment filter state; shell remains Workspace. - **Handling modes by drift class or surface**: Hard-stop for legacy alias acceptance in Environment CTA filter behavior; review-mandatory for any page-specific exception. - **Repository-signal treatment**: Contract tests and browser screenshots are required evidence. - **Special surface test profiles**: global-context-shell, monitoring-state-page, standard-native-filament. - **Required tests or manual smoke**: Functional-core URL/filter tests, state-contract tests, sidebar regression tests, and focused browser smoke. - **Exception path and spread control**: Any page discovered not to be workspace-owned or Environment-filterable must be documented as excluded instead of receiving a partial local exception. - **Active feature PR close-out entry**: Guardrail and Smoke Coverage. ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes. - **Systems touched**: `WorkspaceHubRegistry`, `WorkspaceSidebarNavigation`, `ManagedEnvironmentLinks`, `OperationRunLinks`, `CanonicalAdminTenantFilterState` only if immediate clean-entry correctness requires it, Filament pages/resources for critical hubs, shared views under `resources/views/filament`, and related tests. - **Shared abstractions reused**: `WorkspaceHubRegistry` for known hub paths and clean hub URL generation; existing page/table query patterns; existing workspace and Managed Environment models/factories. - **New abstraction introduced? why?**: A narrow `WorkspaceHubEnvironmentFilter` resolver/helper is expected because multiple pages currently parse different keys and identifier types. - **Why the existing abstraction was sufficient or insufficient**: Spec 314 registry is sufficient for clean sidebar/global entry but intentionally treats Environment-like query params as forbidden for that clean-entry contract. Spec 315 needs a separate valid explicit CTA filter path. - **Bounded deviation / spread control**: The resolver reads only `environment_id`, validates only inside current Workspace, and does not become a shell/context manager or compatibility adapter. ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes, link semantics only. - **Central contract reused**: Existing `OperationRunLinks` URL helper. - **Delegated UX behaviors**: Tenant/workspace-safe URL resolution for OperationRun links into workspace hubs. - **Surface-owned behavior kept local**: OperationRun start, completion, status, notifications, and artifact behavior remain unchanged. - **Queued DB-notification policy**: N/A. - **Terminal notification path**: N/A. - **Exception path**: none. ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: yes. - **Provider-owned seams**: Existing provider connection data and provider external tenant IDs stay provider-adjacent model data. - **Platform-core seams**: Workspace hub query contract, visible scope wording, Environment filter resolver, and CTA URL generation. - **Neutral platform terms / contracts preserved**: `Workspace`, `Environment`, `Environment filter`, `environment_id`. - **Retained provider-specific semantics and why**: Existing Microsoft/Intune provider connection internals remain as-is. They must not become the CTA filter identifier. - **Bounded extraction or follow-up path**: Broader tenant/environment naming cleanup is deferred to Spec 317. ## Constitution Check *GATE: Must pass before implementation. Re-check after runtime changes.* - Inventory-first: no Graph inventory semantics change. - Read/write separation: no Graph writes or destructive operations are added. - Graph contract path: no Graph calls are introduced. - Deterministic capabilities: existing capability checks remain; new tests must assert workspace/environment access boundaries. - RBAC-UX: workspace hub authorization remains workspace/capability-based. Cross-workspace Environment IDs are 404/safe no-access; member-missing-capability remains existing 403 behavior. - Workspace isolation: Environment filter resolution is explicitly scoped to current Workspace and must not switch workspace. - Tenant isolation: no cross-Workspace/Environment leakage; no provider external tenant ID lookup for CTA filter state. - Run observability: no new OperationRun lifecycle behavior. URL helpers only. - Test governance (TEST-GOV-001): lane, fixture cost, heavy-family browser coverage, and reviewer handoff are explicit in this plan and tasks. - Proportionality (PROP-001): new resolver/helper is justified by current-release cross-page drift and at least two concrete surfaces. - No premature abstraction (ABSTR-001): the resolver is bounded to one query key and does not become a generalized context framework. - Persisted truth (PERSIST-001): no persisted truth is added. - Behavioral state (STATE-001): no new state/status family is added. - UI semantics (UI-SEM-001): visible chip is direct domain-to-UI mapping, not a new taxonomy. - Shared pattern first (XCUT-001): extend Spec 314 hub registry and existing visible filter summaries before local patterns. - Provider boundary (PROV-001): platform CTA filter uses Managed Environment database ID, not provider external ID. - V1 explicitness / few layers: direct hard cutover, small helper, page-local data query application where appropriate. - Spec discipline / bloat check: explicit non-goals, follow-up specs 316/317/318, and no compatibility seam. - Filament-native UI (UI-FIL-001): use native Filament/Livewire and shared partials; no ad-hoc styling system. - UI/UX scope, truth, and naming: scope signals must distinguish Workspace shell from explicit Environment page filter. - UI naming: user-facing wording uses `Environment`, not `Tenant`, for the filter chip. ## Test Governance Check - **Test purpose / classification by changed surface**: Unit/Feature for resolver and page contracts; Browser for integrated shell/header/chip URL flows. - **Affected validation lanes**: fast-feedback, confidence for existing related regressions, browser for focused smoke. - **Why this lane mix is the narrowest sufficient proof**: Unit/feature tests prove URL key, resolver, data scope, and clear-link behavior. Browser tests are required because the defect is also visible UI/shell truth. - **Narrowest proving command(s)**: - `cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubEnvironmentFilter` - `cd apps/platform && ./vendor/bin/sail artisan test --filter=EnvironmentCta` - `cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHub` - focused browser smoke command or manual browser verification documented in close-out - **Fixture / helper / factory / seed / context cost risks**: Existing Workspace, ManagedEnvironment, membership/capability, operation/review/evidence/finding/provider connection factories may need small focused setup. Avoid broad global seeders. - **Expensive defaults or shared helper growth introduced?**: no; browser smoke remains explicit. - **Heavy-family additions, promotions, or visibility changes**: Focused Spec 315 browser smoke only. - **Surface-class relief / special coverage rule**: Native Filament page/resource tests for pages/resources; browser smoke for rendered shell/context. - **Closing validation and reviewer handoff**: Reviewers must verify test commands, screenshot artifacts, no legacy alias acceptance, no compatibility adapter, and no scope drift from Spec 314. - **Budget / baseline / trend follow-up**: none expected. - **Review-stop questions**: Does any page still parse `tenant` or `managed_environment_id` as CTA filter? Does any CTA emit a legacy key? Does any filtered hub set shell Environment ownership? Does any clean sidebar link retain filter state? - **Escalation path**: Follow-up Spec 316 only if implementation reveals broader persisted clear-state behavior is required for correctness beyond clean URL entry. - **Active feature PR close-out entry**: Guardrail and Smoke Coverage. - **Why no dedicated follow-up spec is needed**: The canonical CTA filter contract is the dedicated follow-up to Spec 314. Clear internals, legacy cleanup, and durable browser guard infrastructure already have follow-up specs 316, 317, and 318. ## Project Structure ### Documentation (this feature) ```text specs/315-environment-cta-explicit-filter-contract/ |-- spec.md |-- plan.md |-- tasks.md |-- checklists/ | `-- requirements.md `-- artifacts/ `-- screenshots/ # created during implementation/browser verification if useful ``` No `research.md`, `data-model.md`, `quickstart.md`, or `contracts/` artifact is required for preparation because this feature introduces no data model, external API contract, or new workflow API. Runtime implementation may add notes only if a classification/exclusion needs durable documentation inside this spec folder. ### Source Code (repository root) Likely runtime files to inspect or update during implementation: ```text apps/platform/app/Support/Navigation/WorkspaceHubRegistry.php apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php apps/platform/app/Support/Navigation/WorkspaceSidebarNavigation.php apps/platform/app/Support/ManagedEnvironmentLinks.php apps/platform/app/Support/Operations/OperationRunLinks.php apps/platform/app/Support/Filament/CanonicalAdminTenantFilterState.php apps/platform/app/Filament/Pages/Monitoring/Operations.php apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php apps/platform/app/Filament/Pages/Governance/DecisionRegister.php apps/platform/app/Filament/Pages/Governance/FindingExceptionsQueue.php apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php apps/platform/app/Filament/Resources/ProviderConnectionResource.php apps/platform/resources/views/filament/ apps/platform/tests/Feature/ apps/platform/tests/Browser/ ``` Potential classification-only inspection areas: ```text apps/platform/app/Filament/Pages/Monitoring/AuditLog.php apps/platform/app/Filament/Resources/Alert* apps/platform/app/Filament/Pages or Resources for Reports / Stored Reports apps/platform/app/Filament/Pages or Resources for Support Requests ``` **Structure Decision**: Laravel/Filament platform app under `apps/platform`; new source files, if any, stay in existing `app/Support` and `resources/views/filament` locations. Tests stay in existing Pest feature/browser test directories. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |---|---|---| | New resolver/helper abstraction | Environment CTA filtering spans many workspace hubs and currently has divergent key/identifier semantics | Page-local parsing or dual-param adapters would preserve drift and violate the hard-cutover policy | | Shared visible chip partial or primitive | Filter state must be visibly consistent across hubs | Duplicated per-page chip markup would increase scope wording drift | ## Phase 0: Discovery Completed During Preparation Relevant repository facts discovered before authoring this plan: - Spec 313 is an audit-only baseline and explicitly recommends Spec 315 for Environment CTA explicit filter standardization. - Spec 314 has completed tasks and latest local history indicates the workspace hub navigation context contract was merged before this branch. - `WorkspaceHubRegistry` currently centralizes hub paths and clean URL behavior from Spec 314. - `ManagedEnvironmentLinks` and `OperationRunLinks` currently have Environment-owned link helpers that use legacy `managed_environment_id` semantics in some cases. - Operations currently has `managed_environment_id`, `tenant_scope`, and table filter state related to Environment prefiltering. - Governance Inbox and Decision Register currently parse `managed_environment_id` and `tenant` and render a visible ManagedEnvironment filter summary. - Finding Exceptions Queue currently uses `tenant` query state and `managed_environment_id` table filter naming. - Evidence Overview and Customer Review Workspace currently have Environment-like query/filter behavior that must hard-cut to `environment_id`. - Provider Connections currently uses `managed_environment_id` query behavior around provider/external identifiers; implementation must avoid provider external tenant ID as canonical CTA filter key. ## Technical Approach ### 1. Canonical resolver Add or reuse a narrow helper such as: ```text apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php ``` Responsibilities: - read only `environment_id` - validate scalar/integer-like value safely - resolve `ManagedEnvironment` by primary key inside the current Workspace - return null/no filter when `environment_id` is absent - abort with 404 or existing safe no-access when `environment_id` is present but invalid or cross-workspace - expose display name, ID, clear URL, and query application helpers - never read legacy params, remembered Environment, provider external tenant ID, slug, or `Filament::getTenant()` ### 2. Workspace hub registry alignment Keep Spec 314 clean-entry behavior. Add a safe way for Environment-owned CTA URL builders and pages to generate or consume an explicit `environment_id` filter without weakening sidebar/global clean URL checks. Review point: - `environment_id` may remain forbidden for clean sidebar/global entry while being allowed for explicit CTA filter entry through the resolver. The implementation must keep those two modes separate. ### 3. CTA URL hard cutover Update Environment-owned link helpers and CTAs: - Environment Dashboard cards/header actions - Environment Governance Overview cards - Provider readiness/onboarding links - Required permissions and permission posture links - Recent Operations and OperationRun links - ManagedEnvironment link helpers - Review, evidence, support, report, provider connection helpers where they target workspace hubs Output must use: ```text ?environment_id={managed_environment_id} ``` and must not output: ```text tenant tenant_id managed_environment_id tenant_scope environment tableFilters ``` ### 4. Visible filter chip Create or reuse a shared partial/primitive such as: ```text apps/platform/resources/views/filament/partials/workspace-hub-environment-filter-chip.blade.php ``` Required behavior: - render only for a valid `environment_id` filter - show `Environment filter: {environment display name}` or equivalent - include `Clear filter` - point clear link to the clean workspace hub URL - use calm enterprise styling consistent with existing Filament/Blade patterns - avoid `Tenant`, `current tenant`, or active shell Environment wording ### 5. Apply per workspace hub For each critical hub: - read only the shared resolver - apply filter only if valid - render shared chip - update header/scope text to avoid misleading `All environments` - keep clean URL workspace-wide - keep shell Workspace-scoped - add/update tests proving canonical behavior and legacy param rejection ### 6. Classify conditional hubs Audit Log, Alerts, Reports/Stored Reports, and Support Requests must be classified: - if workspace-owned and Environment-filterable: implement the same contract - if not Environment-filterable: do not pass Environment filters through CTAs and document exclusion in implementation close-out ## Data Model No data model changes. No migrations, seeders, backfills, stored URL migrations, compatibility transforms, retention changes, queues, scheduler, or storage volume changes. ## Security and RBAC - Existing page authorization remains the access gate. - Environment filter narrows visible data; it never grants access. - Cross-workspace Environment IDs must return 404 or safe no-access. - No shell context switch or implicit workspace switch is allowed. - No provider external tenant ID lookup is allowed for this CTA filter. - No secrets or Graph payload changes. ## Deployment / Operations - No new environment variables. - No migrations. - No queues or scheduler changes. - No storage persistence or volume changes. - No package changes. - No Dokploy-specific deployment changes expected. - If implementation registers Filament assets, deployment must include `cd apps/platform && php artisan filament:assets`; no registered assets are expected for the planned shared chip. ## Browser Verification Plan Use the in-app browser or existing browser test tooling after runtime changes. Critical hubs: - Operations - Governance Inbox - Decision Register - Finding Exceptions Queue - Provider Connections - Evidence - Reviews - Customer Reviews Flows: 1. Environment Dashboard CTA -> filtered workspace hub: URL has `environment_id`, no legacy params, workspace shell, visible chip, no misleading primary `All environments`, clear target is clean. 2. Clean sidebar/global regression: URL has no `environment_id` or legacy params, no chip, workspace-wide state. 3. Clear link smoke: clicking clear navigates to clean URL, chip disappears, workspace-wide state is visible. Screenshots may be saved under: ```text specs/315-environment-cta-explicit-filter-contract/artifacts/screenshots/ ``` ## Implementation Notes - Use direct hard cuts. - Do not add compatibility middleware or adapter layers. - Do not update tests by broad rebaseline; only change tests that asserted broken legacy behavior. - Avoid deep clear-filter work unless immediate clean URL correctness breaks; move deeper clearing to Spec 316. - Document any intentional page exclusions in the final implementation report.