TenantAtlas/specs/103-ia-scope-filter-semantics/spec.md
ahmido d32b2115a8 Spec 103: IA semantics (scope vs filter vs targeting) + UI polish (#126)
Implements Spec 103 (IA semantics: Scope vs Filter vs Targeting) across Monitoring + Manage.

Changes
- Monitoring tenant indicator copy: “All tenants” / “Filtered by tenant: …”
- Alerts KPI header resolves tenant via OperateHubShell::activeEntitledTenant() for consistency
- Manage list pages (Alert Rules / Destinations) no longer show tenant indicator
- AlertRule form uses targeting semantics + sections (Rule / Applies to / Delivery)
- Additional UI polish: resource sections, tenant view widgets layout, RBAC progressive disclosure (“Not configured” when empty)

Notes
- US6 (“Add current tenant” convenience button) intentionally skipped (optional P3).

Testing
- CI=1 vendor/bin/sail artisan test tests/Feature/TenantRBAC/ tests/Feature/Onboarding/OnboardingIdentifyTenantTest.php
- vendor/bin/sail bin pint --dirty --format agent

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #126
2026-02-21 00:28:15 +00:00

19 KiB

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.