# Feature Specification: IA Semantics — Scope vs Filter vs Targeting (Monitoring + Manage)

**Feature Branch**: `103-ia-scope-filter-semantics`  
**Created**: 2026-02-20  
**Status**: Draft  
**Input**: User description: "IA Semantics: Scope vs Filter vs Targeting — Monitoring canonical views use 'Scope' wording but mean 'Filter'; workspace-owned Manage pages incorrectly show tenant indicators; Alerts KPI widget has inconsistent tenant resolution; AlertRule form labels mix scope and targeting semantics."

## Spec Scope Fields *(mandatory)*

- **Scope**: canonical-view (Monitoring pages) + workspace (Manage pages)
- **Primary Routes**:
  - `/admin/operations` (Monitoring → Operations)
  - `/admin/alert-deliveries` (Alerts → Alert Deliveries)
  - `/admin/audit-log` (Monitoring → Audit Log)
  - `/admin/alerts` (Monitoring → Alerts Overview — Landing + KPI Widget)
  - `/admin/alert-rules` (Alerts → Alert Rules — Manage)
  - `/admin/alert-destinations` (Alerts → Alert Destinations — Manage)
  - `/admin/alert-rules/{record}/edit` (AlertRule Edit Form)
- **Data Ownership**:
  - Monitoring pages: canonical views across workspace-level data, filtered by tenant-context
  - Alert Rules / Alert Destinations / Baselines: workspace-owned records (tenantless configuration)
  - Alert Rule `tenant_allowlist`: workspace-owned rule targeting selected tenants
- **RBAC**: Existing RBAC checks remain unchanged. No new capabilities introduced. Existing membership + capability enforcement is preserved.

For canonical-view specs, the spec MUST define:

- **Default filter behavior when tenant-context is active**: Monitoring canonical pages prefilter query results to the active tenant resolved via `OperateHubShell::activeEntitledTenant(request())` (Filament tenant + lastTenantId session fallback). If no tenant-context is active, all workspace-level data is shown unfiltered.
- **Explicit entitlement checks preventing cross-tenant leakage**: Existing entitlement checks in `OperateHubShell::activeEntitledTenant()` verify the user is entitled to the resolved tenant. No changes to entitlement logic.

## User Scenarios & Testing *(mandatory)*

### User Story 1 — Monitoring Page Tenant Indicator Shows "Filtered by tenant" Instead of "Scope" (Priority: P1)

An admin navigates to a Monitoring canonical page (Operations, Alert Deliveries, Audit Log, or Alerts Overview) while a tenant-context is active. The page indicator reads **"Filtered by tenant: Contoso"** instead of the old "Scope: Tenant — Contoso". When no tenant-context is active, the indicator reads **"All tenants"** instead of "Scope: Workspace — all tenants".

**Why this priority**: This is the core IA semantics fix. The "Scope" label implies ownership, misleading admins into thinking data belongs to the tenant rather than being filtered. Fixing the copy on all Monitoring pages is the highest-value change.

**Independent Test**: Can be fully tested by visiting any Monitoring page with and without tenant-context and asserting the indicator text. Delivers correct mental model of "filter" semantics.

**Acceptance Scenarios**:

1. **Given** a tenant-context is active (via Filament tenant or lastTenantId fallback), **When** the admin visits `/admin/operations`, **Then** the page shows "Filtered by tenant: {TenantName}" and does NOT show "Scope: Tenant".
2. **Given** no tenant-context is active, **When** the admin visits `/admin/operations`, **Then** the page shows "All tenants" and does NOT show "Scope: Workspace".
3. **Given** a tenant-context is active, **When** the admin visits `/admin/alert-deliveries`, **Then** the page shows "Filtered by tenant: {TenantName}".
4. **Given** a tenant-context is active, **When** the admin visits `/admin/alerts` (Alerts Overview), **Then** the indicator reads "Filtered by tenant: {TenantName}".

---

### User Story 2 — Alerts KPI Widget Uses Consistent Tenant Resolution (Bugfix) (Priority: P1)

An admin has tenant-context active via the lastTenantId session fallback (Filament::getTenant() returns null). The Monitoring Alerts Overview page banner shows "Filtered by tenant: Contoso" but the KPI numbers show workspace-wide counts — a data inconsistency. After the fix, the KPI widget's query MUST use the same tenant resolution as the indicator so banner and numbers always agree.

**Why this priority**: This is a correctness bug. Misleading KPI numbers undermine trust in the monitoring dashboard. Co-equal P1 with the copy fix because the two are interdependent: fixing the copy without fixing the filter makes the inconsistency more visible.

**Independent Test**: Can be tested by setting tenant-context via session fallback only (no Filament::setTenant), rendering the KPI widget, and asserting the underlying query includes the correct tenant_id filter.

**Acceptance Scenarios**:

1. **Given** tenant-context is set only via lastTenantId fallback (Filament::getTenant() returns null), **When** the Alerts KPI widget renders, **Then** the KPI query filters by the fallback tenant's ID.
2. **Given** tenant-context is set via Filament::setTenant(), **When** the Alerts KPI widget renders, **Then** the KPI query filters by that tenant's ID.
3. **Given** no tenant-context is active, **When** the Alerts KPI widget renders, **Then** no tenant filter is applied (workspace-wide counts).

---

### User Story 3 — Manage Pages Suppress Tenant Indicator (Priority: P2)

An admin navigates to the Alert Rules list or Alert Destinations list (workspace-owned Manage pages). These pages MUST NOT show any tenant indicator/banner because the records are workspace-owned configuration — showing "Filtered by tenant" would be semantically wrong.

**Why this priority**: After fixing the Monitoring indicator copy, the next most confusing pattern is Manage pages that incorrectly display a tenant indicator. Removing it reinforces the ownership vs filter distinction.

**Independent Test**: Can be tested by visiting Alert Rules list with tenant-context active and asserting the absence of any "Filtered by tenant" or "Scope: Tenant" text.

**Acceptance Scenarios**:

1. **Given** a tenant-context is active, **When** the admin visits `/admin/alert-rules`, **Then** the page does NOT show "Filtered by tenant" nor "Scope: Tenant" nor any tenant indicator.
2. **Given** a tenant-context is active, **When** the admin visits `/admin/alert-destinations`, **Then** the page does NOT show any tenant indicator.

---

### User Story 4 — AlertRule Form Labels Use Targeting Semantics (Priority: P2)

An admin edits an Alert Rule. The form field previously labeled "Tenant scope mode" now reads **"Applies to tenants"** with options "All tenants" / "Selected tenants". The "Tenant allowlist" field now reads **"Selected tenants"** (conditionally visible). Helper texts communicate targeting clearly.

**Why this priority**: Completes the IA semantics cleanup by fixing the last "scope" references in the AlertRule form. Lower priority than Monitoring because it affects fewer interactions.

**Independent Test**: Can be tested by visiting any AlertRule edit page and asserting the new labels/helper texts appear.

**Acceptance Scenarios**:

1. **Given** the admin opens an AlertRule edit form, **Then** the form shows a field labeled "Applies to tenants" with helper text "This rule is workspace-wide. Use this to limit where it applies."
2. **Given** the admin selects "Selected tenants" in "Applies to tenants", **Then** a field labeled "Selected tenants" appears with helper text "Only these tenants will trigger this rule."
3. **Given** the admin opens an AlertRule edit form, **Then** the form does NOT show "Tenant scope mode" or "Tenant allowlist" labels.

---

### User Story 5 — AlertRule Form Organized in Sections (Priority: P3)

An admin edits an Alert Rule. The form is grouped into three clear sections: **Rule** (Name, Enabled, Event type, Minimum severity), **Applies to** (tenant targeting fields), and **Delivery** (Cooldown, Quiet hours, Destinations).

**Why this priority**: Layout improvement that aids form comprehension. Lower priority because the current flat layout is functional, just less organized.

**Independent Test**: Can be tested by loading the edit form and asserting the presence of the three section headings.

**Acceptance Scenarios**:

1. **Given** the admin opens an AlertRule edit form, **Then** the form contains a section titled "Rule" with fields: Name, Enabled, Event type, Minimum severity.
2. **Given** the admin opens an AlertRule edit form, **Then** the form contains a section titled "Applies to" with fields: Applies to tenants, Selected tenants (conditional).
3. **Given** the admin opens an AlertRule edit form, **Then** the form contains a section titled "Delivery" with fields: Cooldown, Quiet hours, Destinations.

---

### User Story 6 — Optional: "Add Current Tenant" Convenience Button (Priority: P3, Optional)

When an admin edits an Alert Rule with tenant-context active and "Selected tenants" mode visible, an optional action button **"Add current tenant to selected tenants"** appears (no auto-prefill, explicit click required).

**Why this priority**: Pure convenience enhancement. Only implemented if core stories are complete and PR remains small.

**Independent Test**: Can be tested by rendering the form with tenant-context active and allowlist visible, clicking the button, and asserting the current tenant is added to the selected list.

**Acceptance Scenarios**:

1. **Given** tenant-context is active AND "Selected tenants" mode is visible, **When** the admin clicks "Add current tenant to selected tenants", **Then** the currently active tenant is added to the selected tenants field.
2. **Given** no tenant-context is active OR "All tenants" mode is selected, **Then** the "Add current tenant" button is NOT visible.

---

### Edge Cases

- What happens when the lastTenantId session value references a tenant the user is no longer entitled to? — The existing `activeEntitledTenant()` method performs entitlement checks; if the user is not entitled, it returns null and the page shows "All tenants" (workspace-wide). No change to this behavior.
- What happens when `Filament::getTenant()` and `lastTenantId` resolve to different tenants? — `activeEntitledTenant()` gives priority to `Filament::getTenant()`; the fallback only activates when Filament tenant is null. No change.
- What happens on a Manage page if the user navigates directly with a tenant in the URL? — Manage pages do not query by tenant, and the indicator is suppressed. No data inconsistency arises; the page simply shows all workspace-owned records.
- What happens on the Alerts Overview page with no alert rules configured? — KPI widget shows zeros for all metrics, regardless of tenant filter. Existing empty-state behavior is preserved.

## Requirements *(mandatory)*

**Constitution alignment (required):** This feature introduces no Microsoft Graph calls, no new write/change behavior, no queued/scheduled work. It is a UI copy + query filter consistency fix. No contract registry updates needed. The KPI bugfix is a DB-only query change that corrects an existing filter to match the indicator — this is a correctness fix, not a new behavior. No `OperationRun` or `AuditLog` changes required.

**Constitution alignment (RBAC-UX):** This feature does not introduce or change authorization behavior. All existing RBAC enforcement remains unchanged. No new capabilities, no new gates/policies, no changes to 404/403 semantics.

**Constitution alignment (OPS-EX-AUTH-001):** Not applicable — no outbound HTTP calls introduced.

**Constitution alignment (BADGE-001):** This feature does not add or change any status badges. The "Filtered by tenant" indicator is informational copy, not a status badge.

**Constitution alignment (Filament Action Surfaces):** This feature modifies Filament pages (copy, layout, conditional rendering). No new actions are added. Existing actions (header actions on Manage pages) are removed from pages where they were semantically incorrect. The action surface contract is satisfied — see UI Action Matrix below.

**Constitution alignment (UX-001 — Layout & Information Architecture):** The AlertRule form changes add Section grouping (conforming to UX-001 "all fields inside Sections/Cards"). Monitoring pages receive copy changes only. No new pages or custom layouts are introduced.

**UX-001 exemption (Main/Aside layout):** UX-001 requires Create/Edit forms to default to a Main/Aside 3-column grid. The AlertRule edit form does not adopt Main/Aside layout in this feature because the scope is limited to copy/label fixes and Section grouping. A full layout refactor is deferred to a dedicated UX spec.

### Functional Requirements

- **FR-001**: Monitoring canonical pages MUST display "Filtered by tenant: {TenantName}" when tenant-context is active (resolved via Filament tenant OR lastTenantId session fallback).
- **FR-002**: Monitoring canonical pages MUST display "All tenants" when no tenant-context is active.
- **FR-003**: Monitoring canonical pages MUST NOT display "Scope: Tenant" or "Scope: Workspace" labels.
- **FR-004**: The Alerts KPI delivery widget (`AlertsKpiHeader`) MUST resolve the active tenant using the same mechanism as the indicator (`activeEntitledTenant()` including lastTenantId fallback). Other Monitoring widgets that display workspace-owned counts (e.g., AlertRule/AlertDestination totals) are correctly unfiltered and are not affected by this requirement.
- **FR-005**: The Alerts KPI widget MUST filter by the resolved tenant when one is active, and show workspace-wide counts when no tenant is active.
- **FR-006**: Workspace-owned Manage pages (Alert Rules list, Alert Destinations list) MUST NOT display any tenant indicator/banner.
- **FR-007**: The AlertRule edit form MUST display the field label "Applies to tenants" (replacing "Tenant scope mode") with options "All tenants" and "Selected tenants".
- **FR-008**: The AlertRule edit form MUST display the field label "Selected tenants" (replacing "Tenant allowlist") when "Selected tenants" mode is active.
- **FR-009**: The AlertRule edit form MUST show helper text "This rule is workspace-wide. Use this to limit where it applies." for the "Applies to tenants" field.
- **FR-010**: The AlertRule edit form MUST show helper text "Only these tenants will trigger this rule." for the "Selected tenants" field.
- **FR-011**: The AlertRule edit form MUST group fields into three sections: "Rule", "Applies to", and "Delivery".
- **FR-012**: No domain behavior changes MUST be introduced except the KPI tenant resolution consistency fix (FR-004/FR-005).
- **FR-013**: No database schema changes, no new tables, no new enums.
- **FR-014**: All monitoring page renders MUST remain DB-only (no Graph/HTTP calls introduced).

## UI Action Matrix *(mandatory when Filament is changed)*

| Surface           | Location                             | Header Actions                                    | Inspect Affordance | Row Actions | Bulk Actions | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions                                                               |
| ----------------- | ------------------------------------ | ------------------------------------------------- | ------------------ | ----------- | ------------ | ------------------- | ------------------- | ----------------------- | ---------- | -------------------------------------------------------------------------------- |
| ListAlertRules    | app/Filament/…/ListAlertRules.php    | **Removed**: OperateHubShell header actions        | Unchanged          | Unchanged   | Unchanged    | Unchanged           | N/A                 | N/A                     | No         | Manage page — tenantless config; indicator was semantically incorrect             |
| ListAlertDests    | app/Filament/…/ListAlertDestinations | **Removed**: OperateHubShell header actions        | Unchanged          | Unchanged   | Unchanged    | Unchanged           | N/A                 | N/A                     | No         | Manage page — tenantless config; indicator was semantically incorrect             |
| AlertRuleResource | app/Filament/…/AlertRuleResource.php | Unchanged                                         | Unchanged          | Unchanged   | Unchanged    | Unchanged           | Unchanged           | Unchanged (Save+Cancel) | No         | Form copy + section layout change only; no action changes                         |
| Monitoring pages  | Operations / AlertDeliveries / Alerts | **Changed**: Copy from "Scope: …" to "Filtered…" | Unchanged          | Unchanged   | Unchanged    | Unchanged           | N/A                 | N/A                     | No         | Header action label change; "Show all tenants" / "Back to" actions unchanged      |

**Action Surface Contract**: Satisfied. No new actions introduced. Existing destructive actions (if any on these pages) are unchanged. The removed header actions on Manage pages were informational indicators, not mutation actions.

### Key Entities

No new entities. Existing entities affected by copy/layout changes only:

- **Alert Rule**: Workspace-owned rule with `tenant_scope_mode` (persisted value unchanged) and `tenant_allowlist` (persisted value unchanged). Only display labels and helper texts change.
- **Monitoring Indicator**: Not a persisted entity — runtime UI element rendered by `OperateHubShell::scopeLabel()` based on resolved tenant-context.

## Assumptions

- `OperateHubShell::scopeLabel()` is the single source of truth for the Monitoring indicator copy. Updating this method propagates the label change to all Monitoring canonical pages that use it.
- `OperateHubShell::activeEntitledTenant(request())` correctly performs entitlement checks and returns null for non-entitled tenants.
- The AlertRule form's `tenant_scope_mode` and `tenant_allowlist` database column names are NOT changed — only the display labels in the form.
- Baselines list page is already banner-free and requires no changes.
- The "Show all tenants" and "Back to {Tenant}" action labels remain unchanged (they do not use "Scope" wording).
- The Audit Log page uses the same `OperateHubShell` indicator mechanism as other Monitoring pages and will automatically receive the copy change.

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: Zero instances of "Scope: Tenant" or "Scope: Workspace" visible on any Monitoring canonical page — verified by automated tests.
- **SC-002**: When the Monitoring indicator shows "Filtered by tenant: X", 100% of KPI widgets on the same page filter their queries by tenant X — verified by query assertion tests.
- **SC-003**: Zero instances of tenant indicator/banner on workspace-owned Manage pages (Alert Rules list, Alert Destinations list) — verified by automated tests.
- **SC-004**: Admin can identify within 5 seconds whether a Monitoring page is showing tenant-filtered data or workspace-wide data, based on the indicator copy alone.
- **SC-005**: AlertRule form contains three clearly labeled sections, allowing admin to locate the targeting configuration in under 3 seconds.
- **SC-006**: Full test suite passes with zero regressions.
