# Feature Specification: Alerts / Audit Log Environment Filter Contract Decision **Feature Branch**: `321-alerts-audit-log-environment-filter-contract-decision` **Created**: 2026-05-17 **Status**: Draft **Input**: User supplied Spec 321 draft: "Alerts / Audit Log Environment Filter Contract Decision" **Type**: Product contract decision / runtime hardening / filter consistency **Runtime Posture**: Hard cutover. No backwards compatibility. No legacy alias support. ## Dependencies - Spec 313: Full Workspace / Environment Context Browser Verification Audit - Spec 314: Workspace Hub Navigation Context Contract - Spec 315: Environment CTA Explicit Filter Contract - Spec 316: Workspace Hub Clear Filter Contract - Spec 317: Legacy Tenant / Environment Context Cleanup - Spec 318: Admin Surface Scope & Shell Context Audit - Spec 319: Environment-Owned Surface Routing & Shell Context Contract - Spec 320: Workspace-Owned Analysis Surface Registration & Shell Cutover ## Spec Candidate Check **Candidate Source**: Direct user-provided manual promotion, based on Spec 318 mismatch findings. **Completed-Spec Guardrail**: Specs 313 through 320 were inspected as completed context and remain unchanged by this preparation. Spec 321 addresses the unresolved Alerts / Audit Log contract gap explicitly called out by Spec 318. **Score**: 9/10. **Why now**: - Spec 318 identified Alerts and Audit Log as the remaining ambiguous workspace-hub filter surfaces. - Specs 314 through 320 already established the shared workspace hub, environment filter, clear filter, legacy cleanup, and shell ownership contracts this work must reuse. - The repo data model supports a hard decision now: - `alert_deliveries.managed_environment_id` exists and is workspace constrained. - `audit_logs.managed_environment_id` exists, is indexed, and is workspace constrained. **Alternatives deferred**: - Spec 322 durable browser no-drift regression coverage is explicitly out of scope. - Broad redesign of alerts, audit logging, notification routing, or evidence flows is out of scope. - New persisted entities, packages, migrations, or compatibility redirects are out of scope. ## Summary Resolve the remaining Alerts / Audit Log environment-filter contract gap found by Spec 318. TenantPilot has two primary admin context contracts: - Workspace hubs use a Workspace-only shell. They may support an explicit, visible, clearable `environment_id` filter. - Environment-owned pages require an Environment route or context. They show Workspace + Environment shell ownership and do not use workspace-hub-style `environment_id` access. Alerts and Audit Log are governance and observability surfaces. They stay Workspace-owned surfaces. This spec decides and prepares the implementation contract for optional canonical Environment filtering on those surfaces. ## Product Decision The chosen contracts are: | Surface | Chosen contract | | --- | --- | | Alerts overview / Alert Center | `environment_filterable_workspace_hub` | | Alert Deliveries | `environment_filterable_workspace_hub` | | Alert Rules | `configuration_workspace_surface` | | Alert Destinations | `configuration_workspace_surface` | | Audit Log | `environment_filterable_workspace_hub` | | Audit event detail state | Same `environment_filterable_workspace_hub` surface, not a separate Environment-owned page | Decision details are documented in: ```text specs/321-alerts-audit-log-environment-filter-contract-decision/decision.md ``` ## Hard Cutover Policy There is no production data or production environment to preserve. The implementation must not introduce compatibility behavior. Only this Environment filter query key is canonical: ```text environment_id ``` These inputs must not create Environment filter state: ```text tenant tenant_id managed_environment_id environment tenant_scope tableFilters as URL source remembered Environment Filament::getTenant() getTenant() ``` No compatibility redirect, alias support, remembered fallback, hidden fallback, or dual contract is allowed. ## Spec Scope Fields **Primary users**: Workspace operators, security/governance admins, support/admin users reviewing operational signal and auditability. **Primary surfaces**: - `GET /admin/alerts` - `GET /admin/alerts/alert-deliveries` - `GET /admin/alerts/alert-rules` - `GET /admin/alerts/alert-destinations` - `GET /admin/audit-log` **Related surfaces to inspect during implementation**: - Environment Dashboard CTAs and widgets - Alert widgets/cards and alert KPI header - Notification or alert link helpers - Audit support links - OperationRun and ManagedEnvironment link helpers - Audit event selection/detail state **Persistence impact**: No migrations expected. Existing `managed_environment_id` columns provide the reliable attribution needed for the chosen filterable surfaces. **Runtime impact**: Query, navigation, chip, clear behavior, and test/browser coverage only. **Out of scope**: - Alert type redesign - Audit schema redesign - New alert rules or delivery engines - Provider-specific tenant concepts - Spec 322 durable browser no-drift infrastructure - Broad rebaseline of existing browser artifacts ## Current Repo Truth Discovery found these relevant seams: - `WorkspaceHubRegistry` already registers `audit_log`, `alerts`, `alert_deliveries`, `alert_rules`, and `alert_destinations` as workspace hub entries. - `WorkspaceHubEnvironmentFilter` already resolves canonical `environment_id`, constrains by current workspace, checks current user Environment access, and rejects cross-workspace or unauthorized Environment IDs with 404 behavior. - `WorkspaceHubFilterStateResetter` and `ClearsWorkspaceHubEnvironmentFilterState` already provide shared clear behavior for stale query/table/session filter state. - `resources/views/filament/partials/workspace-hub-environment-filter-chip.blade.php` already provides a shared visible filter chip. - `AlertDeliveryResource` is table-backed and has reliable `managed_environment_id` attribution. - `AlertRuleResource` and `AlertDestinationResource` are workspace configuration resources and should not become environment-filtered. - `AuditLog` has a table filter for `managed_environment_id`, but Spec 318 found canonical `environment_id` direct URLs currently lack a visible chip and full contract behavior. - `audit_logs.managed_environment_id` exists, is indexed, and is a reliable attribution column. ## User Scenarios & Testing ### User Story 1: Workspace operator filters Alerts by Environment As a workspace operator, I can open Alerts cleanly for all environments or open Alerts with `?environment_id={id}` to focus on one Managed Environment without changing shell ownership. **Independent Test**: Open `/admin/alerts` and `/admin/alerts?environment_id={validEnvironmentId}`. Verify clean state is workspace-wide, filtered state shows the shared chip, the shell remains Workspace-only, clear returns to clean URL, and no legacy query alias creates filter state. **Acceptance Scenarios**: 1. Given a Workspace has multiple Managed Environments and alert delivery data, when the operator opens the clean Alerts URL, then the page shows Workspace-only shell and all-environment copy. 2. Given a valid Environment ID in the current Workspace, when the operator opens Alerts with `environment_id`, then the page shows `Environment filter: {environment name}` and Environment-scoped signal where the data model supports it. 3. Given the operator clears the filter, when the page reloads, then the URL is clean and the Environment chip does not return. ### User Story 2: Workspace operator filters Alert Deliveries by Environment As a workspace operator, I can use Alert Deliveries as the table-backed alert signal surface and apply the same canonical `environment_id` filter contract. **Independent Test**: Open `/admin/alerts/alert-deliveries?environment_id={validEnvironmentId}`. Verify visible chip, filtered rows, clear behavior, and no Environment shell ownership. **Acceptance Scenarios**: 1. Given deliveries exist for two environments, when the filtered URL is opened for one Environment, then only deliveries for that Environment are shown. 2. Given alert rules or destinations are opened with `environment_id`, then those configuration surfaces do not create an Environment chip or Environment filter state. ### User Story 3: Governance admin filters Audit Log by Environment As a governance admin, I can open Audit Log cleanly for workspace-wide events or explicitly filter it by Environment when audit entries carry reliable Environment attribution. **Independent Test**: Open `/admin/audit-log` and `/admin/audit-log?environment_id={validEnvironmentId}`. Verify clean state is workspace-wide, filtered state shows the chip, rows are filtered by `audit_logs.managed_environment_id`, selected event detail remains consistent with the filter, and clear is reload-safe. **Acceptance Scenarios**: 1. Given audit logs exist for two environments, when the filtered URL is opened, then only matching Environment-attributed audit rows appear. 2. Given an audit event detail is selected while an Environment filter is active, when the event does not belong to that Environment, then it is not shown as the selected detail. 3. Given an audit row has no Environment attribution, when an Environment filter is active, then the row is excluded from the filtered results. ### User Story 4: Environment Dashboard CTAs follow the contract As an operator starting from an Environment-owned page, I can use CTAs that either pass canonical `environment_id` to filterable workspace hubs or use clean workspace links for configuration surfaces. **Independent Test**: Inspect and browser-test Environment Dashboard or related Environment CTAs. Verify Alerts, Alert Deliveries, and Audit Log CTAs use `environment_id` when they claim Environment focus, while Alert Rules and Alert Destinations do not receive Environment filter params. ## Edge Cases - `environment_id` belongs to another Workspace: reject with 404 / safe no-access and do not switch Workspace. - `environment_id` belongs to current Workspace but current user lacks access: reject with 404 / safe no-access. - `environment_id` is malformed or missing: no filter state should be created. - Legacy aliases appear with or without `environment_id`: only canonical `environment_id` may control filter state. - Stale `tableFilters`, `tableDeferredFilters`, persisted Filament/session state, or Livewire state exists: clear and clean entry must neutralize stale Environment-like state. - Browser back/forward after filter and clear must not create mismatched URL/chip/data state. - Alert Rules and Alert Destinations must stay workspace configuration surfaces even though rule configuration may contain tenant/environment targeting semantics. - Audit events with null `managed_environment_id` remain visible in workspace-wide Audit Log but are excluded from Environment-filtered Audit Log. ## Requirements ### Functional Requirements - **FR-001**: The implementation MUST preserve Workspace-only shell ownership for Alerts, Alert Deliveries, Alert Rules, Alert Destinations, and Audit Log. - **FR-002**: Alerts overview MUST support clean workspace-wide URL `/admin/alerts` and filtered URL `/admin/alerts?environment_id={id}`. - **FR-003**: Alert Deliveries MUST support clean workspace-wide URL `/admin/alerts/alert-deliveries` and filtered URL `/admin/alerts/alert-deliveries?environment_id={id}`. - **FR-004**: Audit Log MUST support clean workspace-wide URL `/admin/audit-log` and filtered URL `/admin/audit-log?environment_id={id}`. - **FR-005**: Alert Rules and Alert Destinations MUST remain workspace configuration surfaces and MUST NOT create Environment filter state from `environment_id`. - **FR-006**: Filterable surfaces MUST resolve `environment_id` through the shared `WorkspaceHubEnvironmentFilter` or an equivalent shared adapter that preserves workspace and user access checks. - **FR-007**: Filterable surfaces MUST show the shared visible Environment filter chip when a valid `environment_id` filter is active. - **FR-008**: The chip clear action MUST reuse the Spec 316 shared clear/reset behavior and remove URL, Livewire, Filament table, deferred table, and persisted session Environment-like state. - **FR-009**: Sidebar and global workspace hub entries MUST generate clean URLs without Environment query params. - **FR-010**: Environment-owned CTAs to filterable surfaces MUST use canonical `environment_id` if they intend to preserve Environment focus. - **FR-011**: Environment-owned CTAs to Alert Rules or Alert Destinations MUST use clean workspace links or be omitted if an Environment-focused link would be misleading. - **FR-012**: Legacy aliases `tenant`, `tenant_id`, `managed_environment_id`, `environment`, `tenant_scope`, and `tableFilters` as URL source MUST NOT create Environment filter state. - **FR-013**: Cross-workspace or unauthorized Environment IDs MUST be rejected with safe no-access behavior and MUST NOT leak data or switch Workspace. - **FR-014**: Alert Deliveries filtering MUST use reliable `managed_environment_id` attribution only. - **FR-015**: Audit Log filtering MUST use reliable `audit_logs.managed_environment_id` attribution only. - **FR-016**: The implementation MUST NOT infer Environment attribution from labels, descriptions, actor/session context, remembered Environment, provider tenant external IDs, or arbitrary JSON text. - **FR-017**: Audit event detail/selection state MUST stay consistent with any active `environment_id` filter. - **FR-018**: Alerts overview KPI or summary data MUST either apply the active Environment filter where it uses environment-attributable data or clearly avoid implying Environment-specific counts for non-attributable configuration data. - **FR-019**: Workspace-wide copy MUST say all environments, workspace-wide, all alerts, or all events as appropriate when no filter is active. - **FR-020**: Filtered copy MUST use `Environment filter: {environment name}` through the shared chip pattern. - **FR-021**: No migration, seeder, package, environment variable, queue, scheduler, storage, or deployment asset change is expected or allowed unless the spec is updated before implementation. - **FR-022**: No backwards compatibility layer, legacy query alias support, compatibility redirect, or dual contract is allowed. ### Non-Functional Requirements - **NFR-001**: Workspace and Environment isolation MUST remain enforceable in queries and authorization. - **NFR-002**: Data filtering MUST be reload-safe, shareable by URL, and safe across browser back/forward where covered by browser verification. - **NFR-003**: The implementation MUST reuse existing shared navigation/filter/reset/chip seams before introducing any new abstraction. - **NFR-004**: Filament v5 and Livewire v4 patterns MUST be used. No Filament v3/v4 APIs or Livewire v3 references are allowed. - **NFR-005**: Tests MUST cover both visible UI state and actual data scope for filterable surfaces. ## UI / Surface Guardrail Impact ### Decision-First Surface Role - Alerts overview: Workspace-owned operational signal hub with optional explicit Environment filter. - Alert Deliveries: Workspace-owned table-backed operational signal hub with optional explicit Environment filter. - Alert Rules: Workspace-level configuration surface. - Alert Destinations: Workspace-level configuration surface. - Audit Log: Workspace-owned auditability surface with optional explicit Environment filter. ### Audience-Aware Disclosure Filtered state must be visible and unambiguous. Operators must never have to infer from URL alone whether the page is Environment-filtered. ### UI/UX Surface Classification This is an operator/admin UX change to existing Filament pages and resources. It is not a marketing, website, or public-facing surface. ### Operator Surface Contract - Use existing Filament table/page structure. - Use the shared workspace hub Environment filter chip where possible. - Do not introduce custom visual systems, new card-heavy layouts, or marketing copy. - Do not publish Filament internal views. ## Cross-Cutting / Shared Pattern Reuse Implementation must inspect and reuse these existing seams before adding code: - `WorkspaceHubRegistry` - `AdminSurfaceScope` - `WorkspaceSidebarNavigation` - `WorkspaceHubEnvironmentFilter` - `WorkspaceHubFilterStateResetter` - `ClearsWorkspaceHubEnvironmentFilterState` - `workspace-hub-environment-filter-chip` partial - Existing page state contract helpers - Existing audit and alert resource query scopes Any new helper must be justified by reducing duplication across Alerts and Audit Log without weakening existing contracts from Specs 314 through 320. ## OperationRun UX Impact No new operation start, operation run detail, or operation lifecycle behavior is introduced. Existing OperationRun audit or navigation links must only be updated if they currently emit non-canonical Environment parameters or imply a filtered Alerts/Audit destination without using `environment_id`. ## Provider Boundary This spec is provider-neutral. It must not introduce Microsoft Graph tenant concepts, provider tenant aliases, or provider-specific Environment inference. The only Environment filter key is internal canonical `environment_id`. ## Proportionality Review This spec introduces no new persisted entity, enum/status family, taxonomy, or framework. It updates the product contract and prepares implementation for existing surfaces using existing columns and shared navigation/filter abstractions. Therefore the constitution proportionality threshold for new persisted abstractions is not triggered. If implementation discovers that a new persisted attribute or abstraction is required, work must stop and this spec/plan/tasks must be updated before runtime changes continue. ## Testing Requirements Required tests: - `it('documents_alerts_and_audit_log_filter_contract_decisions')` - `it('alerts_support_environment_id_filter_with_visible_chip_and_clear')` - `it('alert_deliveries_support_environment_id_filter_with_visible_chip_and_clear')` - `it('audit_log_supports_environment_id_filter_with_visible_chip_and_clear')` - `it('alerts_and_audit_log_do_not_accept_legacy_environment_query_aliases')` - `it('alerts_and_audit_log_reject_cross_workspace_environment_filters')` - `it('alerts_and_audit_log_sidebar_entry_is_workspace_wide')` - `it('environment_ctas_to_alerts_and_audit_log_use_environment_id')` - `it('alert_configuration_surfaces_do_not_emit_environment_filters')` Regression lanes: - Workspace hub registry and clean navigation tests from Spec 314. - Environment CTA explicit filter tests from Spec 315. - Clear filter contract tests from Spec 316. - Legacy tenant/environment cleanup tests from Spec 317. - Baseline Compare Environment-owned tests from Spec 319. - Workspace-owned analysis shell tests from Spec 320. ## Browser Verification Required Perform focused browser verification after runtime implementation: 1. Open Alerts clean URL and verify Workspace-only shell, workspace-wide copy, and no chip. 2. Open Alerts with `?environment_id={id}` and verify chip, Workspace-only shell, and aligned filtered signal. 3. Clear Alerts filter, reload, and verify clean workspace-wide state. 4. Open Alert Deliveries with `?environment_id={id}` and verify chip, filtered rows, clear, and reload safety. 5. Open Audit Log clean URL and verify Workspace-only shell, workspace-wide copy, and no chip. 6. Open Audit Log with `?environment_id={id}` and verify chip, filtered rows, selected event consistency, clear, and reload safety. 7. Verify Environment Dashboard CTAs use `environment_id` only for filterable destinations and clean links for configuration destinations. 8. Verify browser back/forward after filter and clear does not create URL/chip/data mismatch. Screenshots, when captured, should be saved under: ```text specs/321-alerts-audit-log-environment-filter-contract-decision/artifacts/screenshots/ ``` Suggested names: ```text alerts--clean.png alerts--filtered.png alerts--after-clear.png alerts--after-reload.png alert-deliveries--filtered.png audit-log--clean.png audit-log--filtered.png audit-log--after-clear.png audit-log--after-reload.png environment-cta--alerts.png environment-cta--audit-log.png ``` ## Acceptance Criteria ### Decision - [ ] `decision.md` exists. - [ ] Alerts have final contract `environment_filterable_workspace_hub`. - [ ] Alert Deliveries have final contract `environment_filterable_workspace_hub`. - [ ] Alert Rules have final contract `configuration_workspace_surface`. - [ ] Alert Destinations have final contract `configuration_workspace_surface`. - [ ] Audit Log has final contract `environment_filterable_workspace_hub`. - [ ] No Alerts/Audit surface remains ambiguous. ### URL / Query - [ ] Clean URLs open workspace-wide. - [ ] Sidebar/global URLs contain no Environment params. - [ ] Only `environment_id` is accepted for filterable surfaces. - [ ] Legacy query aliases are not accepted. - [ ] Cross-workspace Environment IDs are rejected. ### Shell / UI - [ ] Alerts shell is Workspace-only. - [ ] Alert Deliveries shell is Workspace-only. - [ ] Alert Rules and Alert Destinations shell remains Workspace-only configuration. - [ ] Audit Log shell is Workspace-only. - [ ] Filtered state uses visible Environment chip. - [ ] Workspace-wide state does not show Environment chip. - [ ] No active Environment shell ownership appears. ### Data Scope - [ ] Alert Deliveries are filtered by reliable `managed_environment_id`. - [ ] Alerts overview Environment-attributable signal is filtered or non-attributable configuration counts are clearly not implied to be filtered. - [ ] Audit Log is filtered by reliable `audit_logs.managed_environment_id`. - [ ] No remembered Environment fallback applies. - [ ] No Filament tenant fallback applies. - [ ] No legacy table filter resurrects Environment scope. ### Clear / Reload - [ ] Clear removes `environment_id`. - [ ] Clear removes visible chip. - [ ] Clear neutralizes stale table/session state. - [ ] Reload after clear stays workspace-wide. - [ ] Browser back/forward does not create mismatch where covered. ### CTAs - [ ] Environment CTAs to Alerts, Alert Deliveries, or Audit Log use `environment_id` when preserving Environment focus. - [ ] Environment CTAs to Alert Rules or Alert Destinations do not emit Environment filters. - [ ] No CTA emits legacy params. ### Regression - [ ] Spec 314 clean workspace hub entry remains green. - [ ] Spec 315 `environment_id` contract remains green. - [ ] Spec 316 clear filter remains green. - [ ] Spec 317 legacy cleanup remains green. - [ ] Spec 319 Baseline Compare remains Environment-owned. - [ ] Spec 320 workspace-owned analysis remains Workspace-only. ## Success Criteria - **SC-001**: A valid filtered Alerts or Audit Log URL visibly shows exactly one active Environment chip and Workspace-only shell. - **SC-002**: A clean Alerts or Audit Log URL never inherits remembered Environment state. - **SC-003**: Legacy query aliases do not change data, chip, shell, or clear state. - **SC-004**: Cross-workspace Environment IDs never leak data. - **SC-005**: Clear returns the page to a shareable, reload-safe clean URL. ## Assumptions - The app remains pre-production; hard cutover is acceptable. - Existing `managed_environment_id` columns are the reliable attribution source for chosen filterable surfaces. - No migration is needed. - Alert Rules and Alert Destinations remain workspace-level configuration, even when individual rule configuration can target tenants/environments. ## Risks - Existing persisted Filament table filter state may conflict with canonical URL filter state if not explicitly reset. - Alerts overview combines configuration and delivery data, so implementation must avoid implying that workspace-level configuration counts are Environment-filtered unless they truly are. - Audit event detail selection could show a stale event outside the active Environment filter if selection state is not reconciled. ## Follow-Up Spec 322 should add durable browser no-drift regression coverage for all context contracts after Spec 321 runtime implementation is complete. ## Required Final Report For Implementation When Spec 321 runtime implementation completes, report: ```text Spec 321 completed. Chosen contracts: - Alerts: environment_filterable_workspace_hub - Alert Deliveries: environment_filterable_workspace_hub - Alert Rules: configuration_workspace_surface - Alert Destinations: configuration_workspace_surface - Audit Log: environment_filterable_workspace_hub Changed behavior: ... Decision artifact: specs/321-alerts-audit-log-environment-filter-contract-decision/decision.md Files changed: ... Tests: - command: - result: Browser verification: ... Remaining follow-up: - 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. ```