TenantAtlas/specs/321-alerts-audit-log-environment-filter-contract-decision/spec.md
ahmido d879c61204 feat: implement environment filtering for alerts and audit logs (#378)
## 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
2026-05-17 00:27:27 +00:00

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_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:

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/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:

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.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:

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.