# Feature Specification: Environment CTA Explicit Filter Contract

**Feature Branch**: `315-environment-cta-explicit-filter-contract`  
**Created**: 2026-05-16  
**Status**: Draft  
**Input**: User supplied Spec 315 draft for a hard cutover from legacy Environment CTA query aliases to canonical `environment_id` filters on workspace hubs.

## Spec Candidate Check *(mandatory - SPEC-GATE-001)*

- **Problem**: Environment-owned CTAs into workspace hubs can currently pass Environment focus through inconsistent query keys, hidden table state, or shell context, so operators cannot reliably tell whether a workspace hub is showing all environments or one explicit Environment filter.
- **Today's failure**: A user can enter a workspace hub from an Environment Dashboard and see data filtered by one Environment while the URL, header, shell, or filter UI implies workspace-wide scope. Legacy keys such as `tenant`, `managed_environment_id`, `tenant_scope`, and table filter structures also allow future drift.
- **User-visible improvement**: Environment Dashboard and Environment-owned CTA entry to workspace hubs uses one visible, explicit Environment filter. The URL, header/filter chip, data scope, and clear link all agree.
- **Smallest enterprise-capable version**: Introduce one request-scoped resolver for `environment_id`, one shared visible chip pattern, and update the in-scope workspace hubs and Environment-owned CTA URL builders to use that contract.
- **Explicit non-goals**: No universal clear-filter internals, no broad legacy tenant naming cleanup, no compatibility aliases, no migrations, no new runtime feature, no redesign, and no conversion of workspace hubs into Environment pages.
- **Permanent complexity imported**: One narrow Environment filter resolver/helper, one shared filter chip partial or existing shared primitive usage, focused feature/browser tests, and updated CTA URL helpers. No persisted entity, enum, status family, queue, scheduler, package, or environment variable is introduced.
- **Why now**: Spec 314 completed the sidebar/global clean-entry half of the navigation contract. This spec closes the complementary Environment CTA path before more workspace hubs and CTAs accumulate incompatible scope semantics.
- **Why not local**: The behavior spans Operations, Governance Inbox, Decision Register, Finding Exceptions, Provider Connections, Evidence, Reviews, Customer Reviews, and shared link helpers. Page-local fixes would recreate the exact drift this spec is intended to stop.
- **Approval class**: Core Enterprise.
- **Red flags triggered**: Cross-cutting navigation/filter behavior and a new resolver/helper. Defense: the helper is bounded to one canonical query key and at least two concrete current surfaces already require identical semantics.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
- **Decision**: approve.

## Spec Scope Fields *(mandatory)*

- **Scope**: canonical-view.
- **Primary Routes**: Workspace hub routes for Operations, Governance Inbox, Decision Register, Finding Exceptions Queue, Provider Connections / Integrations, Evidence Overview, Reviews / Review Register, Customer Review Workspace, and any Audit Log, Alerts, Reports, Stored Reports, or Support Requests workspace hub route that is confirmed Environment-filterable during implementation.
- **Data Ownership**: Workspace hubs remain workspace-owned pages. Managed Environment is a workspace-owned secondary scope used only as an explicit filter. Existing rows that belong to `managed_environment_id` or equivalent Environment scope are narrowed only when a valid `environment_id` filter is present.
- **RBAC**: Existing workspace membership and page capability checks remain authoritative. Environment filter resolution must confirm the selected Environment belongs to the current Workspace and that the user is allowed to access it. Cross-workspace Environment IDs return 404 or the existing safe no-access convention without leaking existence.

For canonical-view specs:

- **Default filter behavior when tenant-context is active**: Clean sidebar/global workspace hub entry is workspace-wide as defined by Spec 314. Active shell Environment, remembered Environment, Filament tenant fallback, session filters, or legacy query keys must not create an Environment filter on clean workspace hub entry.
- **Explicit entitlement checks preventing cross-tenant leakage**: `environment_id` is resolved as `ManagedEnvironment::query()->whereKey($environment_id)->where('workspace_id', $currentWorkspace->id)`. No lookup by provider external tenant ID, slug, remembered Environment, or `Filament::getTenant()` is valid for this contract.

## Cross-Cutting / Shared Pattern Reuse *(mandatory)*

- **Cross-cutting feature?**: yes.
- **Interaction class(es)**: navigation entry points, dashboard CTA links, header/scope wording, list/table filters, visible filter chips, clean clear links, and browser smoke verification.
- **Systems touched**: `WorkspaceHubRegistry`, workspace sidebar/global navigation contracts from Spec 314, `ManagedEnvironmentLinks`, `OperationRunLinks`, workspace hub Filament pages/resources, page Blade views, and relevant tests.
- **Existing pattern(s) to extend**: Spec 314 clean workspace hub registry and URL-cleaning behavior; current page-level visible filter summaries in Governance Inbox, Decision Register, and Customer Review Workspace; existing Filament page/table query patterns.
- **Shared contract / presenter / builder / renderer to reuse**: Reuse `WorkspaceHubRegistry` for clean hub URL generation and add or reuse a narrow `WorkspaceHubEnvironmentFilter` resolver plus one shared visible filter chip partial/primitive.
- **Why the existing shared path is sufficient or insufficient**: Spec 314 defines clean sidebar/global entry and forbidden query detection. It intentionally does not define valid Environment CTA filter resolution or visible filtered-state rendering, so a small complementary resolver is needed.
- **Allowed deviation and why**: Page-specific data filtering may stay local where each hub uses different query/table/in-memory data sources, but every page must consume the same canonical filter state and shared visible chip wording.
- **Consistency impact**: Query key, identifier type, workspace validation, chip wording, clear link target, shell behavior, and legacy key rejection must stay aligned across all in-scope hubs.
- **Review focus**: Verify no page adds dual-param support, no CTA emits legacy params, and no workspace hub uses remembered Environment or Filament tenant state as data scope.

## OperationRun UX Impact *(mandatory)*

- **Touches OperationRun start/completion/link UX?**: yes, link semantics only.
- **Shared OperationRun UX contract/layer reused**: Existing `OperationRunLinks` where it links Environment-owned surfaces to Operations or related workspace hubs.
- **Delegated start/completion UX behaviors**: Tenant/workspace-safe URL resolution only. No queued toast, run start, completion, terminal notification, or artifact behavior changes.
- **Local surface-owned behavior that remains**: Operation initiation inputs and run lifecycle behavior remain unchanged.
- **Queued DB-notification policy**: N/A.
- **Terminal notification path**: N/A.
- **Exception required?**: none.

## Provider Boundary / Platform Core Check *(mandatory)*

- **Shared provider/platform boundary touched?**: yes.
- **Boundary classification**: mixed.
- **Seams affected**: Provider Connections filter query keys, provider-adjacent Environment identifiers, workspace hub URL generation, and operator-facing scope wording.
- **Neutral platform terms preserved or introduced**: `Workspace`, `Environment`, `Workspace hub`, `Environment filter`, `Clear filter`, `environment_id`.
- **Provider-specific semantics retained and why**: Existing provider connection records and Managed Environment backing data stay unchanged. Provider external tenant IDs remain model data but are not valid CTA filter identifiers.
- **Why this does not deepen provider coupling accidentally**: The canonical CTA filter uses the platform Managed Environment database identifier and workspace ownership, not Microsoft tenant IDs or provider-specific slugs.
- **Follow-up path**: Spec 317 will handle broader legacy Tenant / Environment cleanup.

## UI / Surface Guardrail Impact *(mandatory)*

| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Environment Dashboard and Environment-owned CTA links | yes | Existing Filament/Blade actions and link helpers | navigation, dashboard CTAs | URL-query | no | Link target query key changes to `environment_id` |
| Workspace hub header/filter area | yes | Native Filament page plus shared Blade partial or existing primitive | scope signals, filter summary | page, URL-query, table/list state | no | Adds visible Environment filter chip when active |
| Operations | yes | Existing Filament page/table | monitoring/state page | URL-query, page state, table query | no | Filter chip and canonical query support only |
| Governance Inbox | yes | Existing Filament page/table | governance queue | URL-query, page state, table query | no | Hard-cut existing visible filter to `environment_id` |
| Decision Register | yes | Existing Filament page/table | governance decision register | URL-query, page state, table query | no | Clean and filtered URLs both supported |
| Finding Exceptions Queue | yes | Existing Filament page/table | exception queue | URL-query, page state, table query | no | Legacy `tenant` query no longer accepted |
| Provider Connections / Integrations | yes | Existing Filament resource/list | provider connections | URL-query, resource/list query | no | No provider external tenant ID as CTA filter key |
| Evidence Overview | yes | Existing Filament page/list | evidence viewer | URL-query, page state, in-memory/list filtering | no | Document if rows remain in-memory filtered |
| Reviews / Review Register | yes | Existing Filament page/table | review workflow | URL-query, table query | no | Explicit Environment chip and clean clear link |
| Customer Review Workspace | yes | Existing Filament page/table | customer-safe review workspace | URL-query, table query | no | Remains workspace-scoped, not Environment-owned |
| Audit Log / Alerts / Reports / Support Requests classification | maybe | Existing pages/resources | audit/alerts/reports/support | URL-query where supported | no | Only retrofit if confirmed workspace-owned and Environment-filterable |

## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*

| 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 |
|---|---|---|---|---|---|---|---|
| Workspace hubs opened from Environment CTA | Secondary Context | Decide whether the listed workspace data is intentionally narrowed to one Environment | Workspace name, page title, Environment filter chip, clear filter link, filtered records | Existing record details, table filters, and row actions | The hub remains the decision/workspace surface; the chip makes the inherited Environment focus truthful | Aligns Environment Dashboard drilldowns with workspace hub workflows | Removes hidden scope reconstruction from URL/table/session state |
| Clean sidebar/global workspace hub entry | Secondary Context | Decide from workspace-wide data after navigating globally | Workspace-wide header/scope, no Environment chip, clean URL | Existing filters and details | This is the neutral workspace-wide entry from Spec 314 | Keeps sidebar/global behavior stable | Prevents stale Environment context from surprising operators |

## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)*

| 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 hub Environment filter chip | operator-MSP, support-platform, customer-read-only where Customer Review Workspace applies | `Environment filter: {display name}` and `Clear filter` | Existing table/list filters and row details | Existing support/raw views, if any | Clear filter or inspect rows | Raw query/table/session internals | The chip is the single visible truth for explicit Environment filter state |

## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*

| 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Operations | List / Table / Bulk | Monitoring/state page | Inspect operation or clear Environment filter | Existing operations inspect pattern | existing | Existing table/page actions | Existing placement; no destructive changes | `/admin/workspaces/{workspace}/operations` | existing | Workspace plus Environment filter chip when active | Operations / Operation | Whether results are workspace-wide or Environment-filtered | none |
| Governance Inbox | List / Table / Bulk | Queue | Inspect governance item or clear Environment filter | Existing inbox inspect pattern | existing | Existing table/page actions | Existing placement; no destructive changes | existing workspace hub URL | existing | Workspace plus Environment filter chip when active | Governance Inbox | Filtered Environment state | none |
| Decision Register | List / Table / Bulk | Register | Inspect decision or clear Environment filter | Existing decision inspect pattern | existing | Existing table/page actions | Existing placement; no destructive changes | existing workspace hub URL | existing | Workspace plus Environment filter chip when active | Decision Register | Filtered Environment state | none |
| Finding Exceptions Queue | List / Table / Bulk | Queue | Inspect exception or clear Environment filter | Existing exception inspect pattern | existing | Existing table/page actions | Existing placement; no destructive changes | existing workspace hub URL | existing | Workspace plus Environment filter chip when active | Finding Exceptions | Filtered Environment state | none |
| Provider Connections | List / Table / Bulk | Registry | Inspect connection or clear Environment filter | Existing provider connection inspect pattern | existing | Existing table/resource actions | Existing placement; no destructive changes | existing workspace hub URL | existing | Workspace plus Environment filter chip when active | Provider Connections | Filtered Environment state | none |
| Evidence Overview | List / Table / Bulk | Evidence viewer | Inspect evidence or clear Environment filter | Existing evidence inspect pattern | existing | Existing page actions | Existing placement; no destructive changes | existing workspace hub URL | existing | Workspace plus Environment filter chip when active | Evidence | Filtered Environment state | none |
| Reviews / Customer Reviews | List / Table / Bulk | Review workspace | Inspect review or clear Environment filter | Existing review inspect pattern | existing | Existing page/table actions | Existing placement; no destructive changes | existing workspace hub URL | existing | Workspace plus Environment filter chip when active | Reviews | Filtered Environment state | none |

## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*

| 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Environment-filtered workspace hub | TenantPilot operator | Confirm whether the workspace hub is intentionally narrowed to one Environment, inspect rows, or clear the filter | Existing list/table/page | Am I looking at workspace-wide data or one Environment? | Workspace context, Environment filter chip, clear link, filtered rows | Existing detailed diagnostics on each page | Existing page-specific dimensions only | TenantPilot UI navigation/filter state only | Clear filter, inspect rows | None added or changed |

## Proportionality Review *(mandatory when structural complexity is introduced)*

- **New source of truth?**: no.
- **New persisted entity/table/artifact?**: no.
- **New abstraction?**: yes, a narrow request-scoped Environment filter resolver/helper is expected.
- **New enum/state/reason family?**: no.
- **New cross-domain UI framework/taxonomy?**: no.
- **Current operator problem**: Operators cannot trust visible workspace hub scope after Environment-owned CTA entry because data filters, shell context, URL params, and visible labels can diverge.
- **Existing structure is insufficient because**: `WorkspaceHubRegistry` protects clean sidebar/global entry, but it deliberately treats Environment-like query params as forbidden for clean entry and does not resolve a valid explicit Environment CTA filter. Page-local parsers currently disagree on key names, identifier type, and visible state.
- **Narrowest correct implementation**: One resolver reads only `environment_id`, validates it inside the current Workspace, and exposes display/apply/clear-url helpers. One shared chip or existing shared primitive renders the visible filter. Page-specific query application remains local.
- **Ownership cost**: Tests must cover resolver behavior, CTA URL generation, legacy param rejection, cross-workspace rejection, shell context, clear-link targets, and browser smoke flows. Reviewers must check every touched hub stays aligned.
- **Alternative intentionally rejected**: Keeping page-local parsing and only renaming a few CTAs would preserve hidden drift. A compatibility adapter accepting old and new keys is explicitly rejected because this is a hard cutover.
- **Release truth**: Current-release truth. This fixes observed system behavior after Spec 313/314, not speculative future architecture.

### Compatibility posture

This feature assumes a pre-production environment.

Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope. Canonical replacement is required.

## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*

- **Test purpose / classification**: Unit/Feature for resolver, URL helper, and page contracts; Browser for focused critical hub smoke flows.
- **Validation lane(s)**: fast-feedback for focused Pest tests; browser for Environment CTA, clean sidebar, and clear-link smoke verification.
- **Why this classification and these lanes are sufficient**: The risk is request/URL state, workspace isolation, visible scope truth, and cross-page navigation. Focused feature tests prove deterministic contracts, while browser smoke verifies Filament/Livewire rendered state and shell behavior.
- **New or expanded test families**: Add Spec 315 contract tests for CTA URL generation, workspace hub `environment_id` acceptance, legacy param rejection, cross-workspace rejection, shell context, primary-scope wording, clear links, sidebar regression, and Decision Register clean/filtered access.
- **Fixture / helper cost impact**: Uses existing Workspace, Managed Environment, membership/capability, and page factories/helpers. New helpers must be opt-in and local to this feature.
- **Heavy-family visibility / justification**: Browser smoke is required because the defect includes rendered shell/header/chip truth, not only query filtering. Keep it focused to the critical hub flows named in this spec.
- **Special surface test profile**: global-context-shell and monitoring-state-page.
- **Standard-native relief or required special coverage**: Standard Filament feature tests are sufficient for page loading, query state, filter chip text, and clear links; browser smoke handles integrated UI truth.
- **Reviewer handoff**: Reviewers should verify the exact commands in `tasks.md`, ensure no legacy param acceptance remains for the explicit CTA filter contract, and confirm browser screenshots for critical hubs.
- **Budget / baseline / trend impact**: No expected long-term budget increase beyond focused Spec 315 tests.
- **Escalation needed**: none unless implementation discovers a hub requires broad persisted filter clearing; that belongs in Spec 316.
- **Active feature PR close-out entry**: Guardrail and Smoke Coverage.
- **Planned validation commands**: Focused Pest commands for Spec 315 feature tests, existing Spec 314 regression tests, and a focused browser smoke command or manual browser verification if browser automation cannot be made deterministic.

## User Scenarios & Testing *(mandatory)*

### User Story 1 - Environment CTA opens a visible filtered workspace hub (Priority: P1)

An operator starts on an Environment Dashboard and opens a workspace hub CTA. The destination remains a workspace hub, but the Environment focus is explicit in the URL, visible on the page, and applied to the data.

**Why this priority**: This is the primary drift that Spec 315 fixes.

**Independent Test**: Open each critical workspace hub with `?environment_id={environment.id}` from an Environment-owned link and assert the page loads, shows the Environment filter chip, filters data to that Environment, and offers a clean clear link.

**Acceptance Scenarios**:

1. **Given** Workspace A has Environment A and the user can access Workspace A, **When** the user clicks an Environment Dashboard CTA to Operations, **Then** the URL contains `environment_id={Environment A database id}`, the page shows `Environment filter: {Environment A display name}`, and operations are filtered to Environment A.
2. **Given** a filtered workspace hub, **When** the user reviews the shell/context bar, **Then** the shell remains workspace-scoped and does not treat the hub as Environment-owned.
3. **Given** a filtered workspace hub, **When** the user sees page scope text, **Then** the primary scope does not misleadingly claim `All environments`.

---

### User Story 2 - Legacy Environment CTA params are ignored (Priority: P1)

An operator or stale link opens a workspace hub with legacy query keys. The page must not treat those keys as a valid Environment CTA filter.

**Why this priority**: The spec is a hard cutover. Accepting aliases would preserve hidden ambiguity.

**Independent Test**: For each critical hub, open URLs with `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, `environment`, and `tableFilters` without `environment_id`; assert no valid Environment chip appears and data remains workspace-wide or the app safely normalizes according to existing conventions.

**Acceptance Scenarios**:

1. **Given** a workspace hub URL includes `managed_environment_id=123` but no `environment_id`, **When** the page loads, **Then** no Environment filter chip is shown and the page does not filter as a valid Environment CTA filter.
2. **Given** a workspace hub URL includes `tenant=123` but no `environment_id`, **When** the page loads, **Then** the page remains workspace-wide or safely normalizes without using the legacy key.

---

### User Story 3 - Cross-workspace Environment IDs are rejected (Priority: P1)

An operator opens Workspace A hub with an Environment ID from Workspace B. The app must not leak data or switch workspace context.

**Why this priority**: Workspace isolation is a core constitution requirement.

**Independent Test**: Seed Workspace A, Workspace B, Environment B, and accessible user context for Workspace A. Open Workspace A hub with `?environment_id={Environment B id}` and assert 404 or the existing safe no-access behavior.

**Acceptance Scenarios**:

1. **Given** Environment B belongs to Workspace B, **When** the user opens Workspace A Operations with `environment_id={Environment B id}`, **Then** the app returns 404 or safe no-access, does not show Environment B, and does not switch workspace.

---

### User Story 4 - Clean sidebar/global entry remains workspace-wide (Priority: P2)

An operator moves from an Environment-owned surface to a workspace hub via sidebar or global navigation. The destination remains clean and workspace-wide.

**Why this priority**: This preserves Spec 314 while introducing Environment CTA filters.

**Independent Test**: After visiting an Environment Dashboard or filtered hub, open each critical hub through sidebar/global navigation and assert no `environment_id` or legacy Environment-like query params are present, no filter chip is visible, and workspace-wide data appears.

**Acceptance Scenarios**:

1. **Given** the user recently opened Operations with `environment_id`, **When** the user clicks the sidebar Operations entry, **Then** the URL is the clean Operations hub URL and no Environment filter chip is visible.

---

### User Story 5 - Clear filter returns to the clean hub URL (Priority: P2)

An operator on a filtered workspace hub can remove the Environment filter through a visible clear action.

**Why this priority**: Operators need an obvious exit from filtered state. Full persisted-state clear internals are intentionally deferred to Spec 316.

**Independent Test**: Open each critical hub with `environment_id`, assert the clear link exists, inspect the link target, click it in browser smoke, and assert the URL no longer contains `environment_id` or legacy params.

**Acceptance Scenarios**:

1. **Given** Governance Inbox is filtered by Environment A, **When** the user clicks `Clear filter`, **Then** the browser navigates to the clean Governance Inbox URL without `environment_id`.

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**: The only accepted Environment CTA filter query key for workspace hubs MUST be `environment_id`.
- **FR-002**: Workspace hubs MUST NOT treat `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, `environment`, or `tableFilters` as valid Environment CTA filter state.
- **FR-003**: `environment_id` MUST be the Managed Environment database identifier used by the current application model, not provider external tenant ID, slug, or display name.
- **FR-004**: Environment filter resolution MUST query the current Workspace only and reject Environment IDs outside that Workspace with 404 or the existing safe no-access behavior.
- **FR-005**: Environment filter resolution MUST NOT use `Filament::getTenant()`, remembered Environment state, session fallback, provider external tenant ID lookup, or workspace switching.
- **FR-006**: Environment-owned CTAs that intentionally open workspace hubs with Environment focus MUST emit exactly `environment_id={managed_environment_id}` for that filter and MUST NOT emit legacy Environment filter params.
- **FR-007**: A shared resolver/helper MUST expose the valid Environment filter state to pages in a way that supports `hasFilter()`, `environment()`, `environmentId()`, display name access, clear URL generation, and query application.
- **FR-008**: Each in-scope workspace hub that accepts `environment_id` MUST render a visible Environment filter chip or summary when the filter is valid.
- **FR-009**: The visible filter state MUST include the Environment display name and a clear link/action to the clean workspace hub URL.
- **FR-010**: Filtered workspace hubs MUST NOT primarily show `All environments` wording while the explicit Environment filter is active.
- **FR-011**: Filtered workspace hubs MUST keep shell/context ownership at the Workspace level and MUST NOT set active shell Environment ownership.
- **FR-012**: If `environment_id` is valid, in-scope workspace hub data MUST be filtered to that Environment.
- **FR-013**: If `environment_id` is absent, in-scope workspace hubs MUST be workspace-wide and MUST NOT apply remembered Environment state as a filter.
- **FR-014**: Clean sidebar/global workspace hub URLs from Spec 314 MUST remain clean, workspace-wide, and free of Environment query params.
- **FR-015**: The clear link target MUST remove `environment_id`, MUST emit no legacy Environment filter params, and MUST point to the clean workspace hub URL.
- **FR-016**: Operations MUST support the canonical `environment_id` filter, visible chip, filtered data, clean clear link, and clean sidebar regression.
- **FR-017**: Governance Inbox MUST hard-cut its existing Environment filter behavior to `environment_id` only.
- **FR-018**: Decision Register MUST support both clean workspace URL and `environment_id` filtered URL without requiring the filter for access.
- **FR-019**: Finding Exceptions Queue MUST replace legacy Environment CTA query behavior with `environment_id` only.
- **FR-020**: Provider Connections / Integrations MUST use `environment_id` for explicit Environment CTA filters and MUST NOT use provider external tenant ID as the filter key.
- **FR-021**: Evidence Overview MUST support canonical Environment filtering and document whether filtering is query-backed or in-memory.
- **FR-022**: Reviews and Customer Review Workspace MUST support canonical Environment filtering while remaining workspace-scoped.
- **FR-023**: Audit Log, Alerts, Reports, Stored Reports, and Support Requests MUST be classified during implementation; if Environment-filterable workspace hubs, they must follow this contract, otherwise Environment-owned CTAs must not pass an Environment filter to them.
- **FR-024**: No compatibility layer, dual-param support, legacy alias support, stored URL migration, seeders, migrations, packages, environment variables, queues, scheduler changes, or storage changes may be introduced for this spec.

### Non-Functional Requirements

- **NFR-001**: The implementation MUST preserve workspace isolation and avoid leaking whether cross-workspace Environment IDs exist.
- **NFR-002**: The implementation MUST use native Filament v5 / Livewire v4 patterns and existing shared primitives before local Blade/Tailwind customization.
- **NFR-003**: Any new shared helper MUST stay narrow and replace duplicated page-local query parsing rather than becoming a broader context framework.
- **NFR-004**: Browser verification MUST include Environment CTA flow, clean sidebar regression, and clear-link smoke for the critical hubs.

## Key Entities *(include if feature involves data)*

- **Workspace**: Primary operating scope and owner of workspace hubs.
- **ManagedEnvironment**: Secondary operational scope inside a Workspace. Used by this spec only as an explicit filter when a valid `environment_id` is present.
- **WorkspaceHubEnvironmentFilter**: Proposed request-scoped helper/resolver. It is not persisted and is not a new source of truth.
- **WorkspaceHubRegistry**: Existing workspace hub registry/URL helper from Spec 314. It remains responsible for clean hub URL generation and must align with the explicit filter path.

## In Scope

- Canonical query key `environment_id`.
- Workspace-scoped Managed Environment resolution.
- Environment-owned CTA URL generation to workspace hubs.
- Visible Environment filter chip/summary and clean clear link.
- Data filtering for in-scope hubs when the filter is valid.
- Legacy param rejection for Environment CTA filter behavior.
- Regression coverage that sidebar/global entry remains clean and workspace-wide.
- Focused browser verification and screenshots where useful.

## Out of Scope

- Full universal clear-filter internals across Livewire properties, Filament table filters, deferred filters, persisted/session filters, and back/forward state. This belongs to Spec 316.
- Broad removal of all legacy `tenant` naming or old query handling unrelated to Environment CTA filters. This belongs to Spec 317.
- Durable browser regression guard infrastructure. This belongs to Spec 318.
- Redesigning workspace hubs.
- Changing page product scope.
- Making workspace hubs Environment-owned.
- Making the sidebar dynamic based on active Environment.
- New migrations, seeders, data backfills, packages, environment variables, queues, scheduler tasks, storage volumes, or production compatibility transforms.

## Page-Specific Requirements

### Operations

- Accept only `environment_id` for explicit Environment CTA filter.
- Show visible Environment filter chip.
- Filter operations to the selected Environment.
- Avoid `managed_environment_id`, `tenant_scope`, and misleading `All environments` wording while filtered.
- Clear link points to the clean Operations URL.

### Governance Inbox

- Hard-cut existing Environment filtering to `environment_id`.
- Remove `tenant` and `managed_environment_id` acceptance for this CTA filter contract.
- Keep clean URL workspace-wide.

### Decision Register

- Keep clean workspace URL accessible.
- Support filtered URL with visible chip.
- Do not return 403 merely because the filter is absent.
- Do not accept legacy Environment query aliases.

### Finding Exceptions Queue

- Accept only `environment_id`.
- Do not use remembered Environment as data scope.
- Show visible chip and clean clear link.
- Keep clean URL workspace-wide.

### Provider Connections / Integrations

- Accept only `environment_id` for explicit Environment CTA filters.
- Do not use provider external tenant ID as the CTA filter key.
- Do not treat explicit Environment filtering as shell Environment ownership.
- Keep sidebar/global behavior workspace-wide.

### Evidence Overview

- Accept only `environment_id`.
- Show visible chip and clean clear link.
- Filter rows/search/list consistently with the chip, whether query-backed or in-memory.

### Reviews and Customer Review Workspace

- Accept only `environment_id`.
- Show visible chip and clear link.
- Keep the customer-safe workspace experience workspace-scoped.
- Do not convert Customer Review Workspace into an Environment page.

### Audit Log / Alerts / Reports / Support Requests

- Classify each during implementation.
- If workspace-owned and Environment-filterable, implement `environment_id`, visible chip, and filtered data.
- If not Environment-filterable, do not send Environment CTA filters to those pages and document the exclusion in the implementation close-out.

## Required Tests

- **CTA URL Contract**: Environment-owned CTAs to critical workspace hubs generate `environment_id` and never legacy keys.
- **Workspace Hub Accepts Canonical Filter**: Critical hubs load with `environment_id`, show chip, filter data where seeded rows prove it, and expose clean clear link.
- **Legacy Params Do Not Apply Filter**: Critical hubs ignore `tenant`, `tenant_id`, `managed_environment_id`, `tenant_scope`, `environment`, and `tableFilters` as explicit Environment filter state.
- **Invalid Environment ID Is Workspace-Scoped**: Cross-workspace IDs return 404 or safe no-access without leakage or workspace switch.
- **Shell Does Not Become Environment-Owned**: Filtered hubs keep Workspace shell context and show Environment only as page-level filter.
- **Filtered Header Does Not Say All Environments**: At least Operations, Provider Connections, Customer Reviews, and Finding Exceptions Queue avoid misleading primary scope wording.
- **Clear Link Points To Clean Hub URL**: Critical hubs link back to clean workspace hub URLs without `environment_id` or legacy params.
- **Sidebar Contract Still Holds**: Spec 314 clean sidebar/global entry remains clean and workspace-wide.
- **Decision Register Filter Contract**: Clean and filtered Decision Register URLs both work, with no legacy query accepted.

## Browser Verification Required

Critical pages:

- Operations
- Governance Inbox
- Decision Register
- Finding Exceptions Queue
- Provider Connections
- Evidence
- Reviews
- Customer Reviews

Required flows:

1. **Environment Dashboard CTA**: Open Environment Dashboard, click CTA, verify `environment_id`, no legacy params, workspace shell, visible chip, no misleading `All environments`, clean clear target, and capture screenshot.
2. **Clean Sidebar Regression**: From Environment context, click sidebar/global entry to the same hub, verify clean URL, no chip, workspace-wide state, and capture screenshot where useful.
3. **Clear Link Smoke**: From filtered page, click clear link, verify clean URL, chip disappears, and page is workspace-wide. Do not assert full persisted-state cleanup beyond immediate visible correctness.

Suggested screenshots path:

```text
specs/315-environment-cta-explicit-filter-contract/artifacts/screenshots/
```

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: Every in-scope Environment-owned CTA to a workspace hub emits `environment_id` and no legacy Environment filter query params.
- **SC-002**: Every critical workspace hub accepts valid `environment_id`, shows a visible Environment filter chip, filters data to that Environment, and exposes a clean clear link.
- **SC-003**: Every critical workspace hub ignores legacy Environment CTA params as filter state.
- **SC-004**: Cross-workspace Environment IDs are rejected without data leakage.
- **SC-005**: Clean sidebar/global workspace hub entry remains clean and workspace-wide after visiting filtered hubs.
- **SC-006**: Required focused Pest tests and browser verification pass.

## Assumptions

- Spec 314 is complete and its clean sidebar/global workspace hub behavior is the baseline.
- There is no production data or production environment to preserve, so hard cutover is acceptable.
- Existing Workspace and Managed Environment models/factories can support the required tests without new persistence.
- Existing authorization conventions already distinguish non-member/not-found and member-missing-capability behavior.

## Risks

- **Persisted Filament filter state can reapply after clear**: Spec 315 only requires clean hub URL behavior and immediate visible correctness; full clear semantics belong to Spec 316.
- **Provider Connections currently mixes external IDs and Managed Environment identifiers**: Implementation must be explicit that CTA filtering uses database `environment_id`.
- **Large surface area**: Tests and shared resolver/chip are mandatory to prevent another page-local drift cycle.

## Required Final Implementation Report

When implementation is complete, report:

```text
Spec 315 completed.

Changed behavior:
...

Canonical filter:
environment_id

Files changed:
...

Tests:
- command:
- result:

Browser verification:
...

Remaining follow-ups:
- 316:
- 317:
- 318:

No migrations were created.
No seeders were changed.
No packages, env vars, queues, scheduler, or storage changes were made.
No backwards compatibility layer was introduced.
No legacy query alias support was preserved for Environment CTA filtering.
```

Also include:

- list of hubs supporting `environment_id`
- list of CTAs updated
- list of legacy params removed or ignored
- pages intentionally excluded
- clear-filter limitations deferred to Spec 316

## Follow-Up Specs

- **316 - Workspace Hub Clear Filter Contract**: Full clearing across URL query, Livewire state, Filament table filters, deferred filters, persisted/session state, visible chips, reload safety, and back/forward stale state.
- **317 - Legacy Tenant / Environment Context Cleanup**: Remove/quarantine old `tenant` aliases, old `managed_environment_id` query handling where no longer valid, workspace hub `Filament::getTenant()` usage, remembered Environment as data boundary, stale tenant naming, and compatibility seams.
- **318 - Browser Regression Coverage / No-Drift Guard**: Durable browser/regression coverage for sidebar/global versus Environment CTA behavior.

## Filament v5 Output Contract

1. **Livewire v4.0+ compliance**: This spec targets the current app stack: Filament v5 with Livewire v4. No Livewire v3 APIs or assumptions are allowed.
2. **Provider registration location**: No new Filament panel provider is expected. If implementation discovers provider registration work, Laravel 12 requires panel providers in `apps/platform/bootstrap/providers.php`, not `bootstrap/app.php`.
3. **Global search**: No resource is made globally searchable by this spec. Existing globally searchable resources must still have Edit/View pages; Provider Connections currently remains disabled for global search unless a separate spec changes it.
4. **Destructive actions**: No destructive actions are added or changed. If any implementation path touches a destructive action, it must use `->action(...)`, `->requiresConfirmation()`, and existing authorization policy checks.
5. **Asset strategy**: No new heavy assets are expected. If a shared Blade partial uses existing CSS/classes only, no asset deployment change is needed. If Filament assets are registered, deployment must include `cd apps/platform && php artisan filament:assets`.
6. **Testing plan**: Cover Filament pages/resources as Livewire components or feature routes following current Pest conventions. Mutating actions are not part of this spec; URL, filter, shell, and visible-chip contracts are the required test surface.
