## Summary - Implement environment filtering across Filament alerts and audit log pages, widgets, and support builders. - Add a feature test covering the alerts/audit environment filter contract. - Add the supporting specification and planning artifacts under `specs/`. ## Testing - Not run in this step. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #378
25 KiB
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_idexists and is workspace constrained.audit_logs.managed_environment_idexists, 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_idfilter. - Environment-owned pages require an Environment route or context. They show Workspace + Environment shell ownership and do not use workspace-hub-style
environment_idaccess.
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:
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:
environment_id
These inputs must not create Environment filter state:
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/alertsGET /admin/alerts/alert-deliveriesGET /admin/alerts/alert-rulesGET /admin/alerts/alert-destinationsGET /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:
WorkspaceHubRegistryalready registersaudit_log,alerts,alert_deliveries,alert_rules, andalert_destinationsas workspace hub entries.WorkspaceHubEnvironmentFilteralready resolves canonicalenvironment_id, constrains by current workspace, checks current user Environment access, and rejects cross-workspace or unauthorized Environment IDs with 404 behavior.WorkspaceHubFilterStateResetterandClearsWorkspaceHubEnvironmentFilterStatealready provide shared clear behavior for stale query/table/session filter state.resources/views/filament/partials/workspace-hub-environment-filter-chip.blade.phpalready provides a shared visible filter chip.AlertDeliveryResourceis table-backed and has reliablemanaged_environment_idattribution.AlertRuleResourceandAlertDestinationResourceare workspace configuration resources and should not become environment-filtered.AuditLoghas a table filter formanaged_environment_id, but Spec 318 found canonicalenvironment_iddirect URLs currently lack a visible chip and full contract behavior.audit_logs.managed_environment_idexists, 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:
- 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.
- Given a valid Environment ID in the current Workspace, when the operator opens Alerts with
environment_id, then the page showsEnvironment filter: {environment name}and Environment-scoped signal where the data model supports it. - 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:
- Given deliveries exist for two environments, when the filtered URL is opened for one Environment, then only deliveries for that Environment are shown.
- 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:
- Given audit logs exist for two environments, when the filtered URL is opened, then only matching Environment-attributed audit rows appear.
- 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.
- 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_idbelongs to another Workspace: reject with 404 / safe no-access and do not switch Workspace.environment_idbelongs to current Workspace but current user lacks access: reject with 404 / safe no-access.environment_idis malformed or missing: no filter state should be created.- Legacy aliases appear with or without
environment_id: only canonicalenvironment_idmay 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_idremain 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/alertsand filtered URL/admin/alerts?environment_id={id}. - FR-003: Alert Deliveries MUST support clean workspace-wide URL
/admin/alerts/alert-deliveriesand filtered URL/admin/alerts/alert-deliveries?environment_id={id}. - FR-004: Audit Log MUST support clean workspace-wide URL
/admin/audit-logand 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_idthrough the sharedWorkspaceHubEnvironmentFilteror 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_idfilter 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_idif 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, andtableFiltersas 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_idattribution only. - FR-015: Audit Log filtering MUST use reliable
audit_logs.managed_environment_idattribution 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_idfilter. - 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:
WorkspaceHubRegistryAdminSurfaceScopeWorkspaceSidebarNavigationWorkspaceHubEnvironmentFilterWorkspaceHubFilterStateResetterClearsWorkspaceHubEnvironmentFilterStateworkspace-hub-environment-filter-chippartial- 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:
- Open Alerts clean URL and verify Workspace-only shell, workspace-wide copy, and no chip.
- Open Alerts with
?environment_id={id}and verify chip, Workspace-only shell, and aligned filtered signal. - Clear Alerts filter, reload, and verify clean workspace-wide state.
- Open Alert Deliveries with
?environment_id={id}and verify chip, filtered rows, clear, and reload safety. - Open Audit Log clean URL and verify Workspace-only shell, workspace-wide copy, and no chip.
- Open Audit Log with
?environment_id={id}and verify chip, filtered rows, selected event consistency, clear, and reload safety. - Verify Environment Dashboard CTAs use
environment_idonly for filterable destinations and clean links for configuration destinations. - Verify browser back/forward after filter and clear does not create URL/chip/data mismatch.
Screenshots, when captured, should be saved under:
specs/321-alerts-audit-log-environment-filter-contract-decision/artifacts/screenshots/
Suggested names:
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.mdexists.- 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_idis 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_idwhen 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_idcontract 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_idcolumns 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:
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.