# Implementation Plan: IA Semantics — Scope vs Filter vs Targeting (Monitoring + Manage)

**Branch**: `103-ia-scope-filter-semantics` | **Date**: 2026-02-20 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/103-ia-scope-filter-semantics/spec.md`

## Summary

Replace "Scope: …" wording with "Filtered by tenant: …" / "All tenants" on Monitoring canonical pages, fix the `AlertsKpiHeader` tenant-resolution bug that causes KPI numbers to diverge from the indicator when lastTenantId fallback is active, remove the semantically incorrect tenant indicator from workspace-owned Manage pages (Alert Rules, Alert Destinations), and relabel + restructure the AlertRule edit form to use targeting semantics instead of scope semantics.

**Technical approach**: Update `OperateHubShell::scopeLabel()` (single source of truth for indicator copy), fix `AlertsKpiHeader::deliveriesQueryForViewer()` to call `OperateHubShell::activeEntitledTenant()` instead of `Filament::getTenant()`, remove header-action spreads from Manage list pages, and refactor `AlertRuleResource::form()` labels/sections.

## Technical Context

**Language/Version**: PHP 8.4 (Laravel 12)
**Primary Dependencies**: Filament v5, Livewire v4, `OperateHubShell` support class
**Storage**: PostgreSQL — no schema changes
**Testing**: Pest v4 (feature tests)
**Target Platform**: Web (Laravel Sail / Dokploy)
**Project Type**: Web application (Laravel monolith)
**Performance Goals**: N/A — copy + bugfix, no performance-sensitive changes
**Constraints**: DB-only renders on Monitoring pages (no Graph/HTTP calls)
**Scale/Scope**: 5 source files modified, 1–2 test files updated/created

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- [x] **Inventory-first**: N/A — no inventory data touched.
- [x] **Read/write separation**: No new writes introduced. The KPI bugfix corrects an existing read query filter — no preview/confirmation/audit needed.
- [x] **Graph contract path**: No Graph calls introduced or modified. All changes are DB-only.
- [x] **Deterministic capabilities**: No capability changes. Existing RBAC enforcement unchanged.
- [x] **RBAC-UX (two planes)**: No routing changes. All affected pages remain on `/admin` plane. No cross-plane routing introduced.
- [x] **Workspace isolation**: No workspace isolation changes. Monitoring pages remain workspace-context. Manage pages remain workspace-owned.
- [x] **RBAC-UX (destructive actions)**: No destructive actions added. Removal of OperateHubShell header actions from Manage pages removes informational indicators, not mutation actions.
- [x] **RBAC-UX (global search)**: No global search changes.
- [x] **Tenant isolation**: The bugfix *improves* tenant isolation by ensuring KPI queries respect the resolved tenant context. No cross-tenant views introduced.
- [x] **Run observability**: No long-running/remote/queued work introduced. All changes are synchronous UI/copy.
- [x] **Automation**: N/A — no queued/scheduled ops.
- [x] **Data minimization**: N/A — no new data storage.
- [x] **Badge semantics (BADGE-001)**: No status badges added or changed. The "Filtered by tenant" indicator is informational copy, not a status badge.
- [x] **Filament UI Action Surface Contract**: See UI Action Matrix in spec.md. No new actions. Removed header actions on Manage pages were informational. Existing destructive actions unchanged.
- [x] **Filament UI UX-001**: AlertRule form adds Section grouping (conforming to "all fields inside Sections/Cards"). No new pages.

**Post-Phase 1 re-check**: All gates remain satisfied. No design decisions introduced violations.

## Project Structure

### Documentation (this feature)

```text
specs/103-ia-scope-filter-semantics/
├── plan.md              # This file
├── spec.md              # Feature specification
├── research.md          # Phase 0 output — codebase research findings
├── data-model.md        # Phase 1 output — no schema changes documented
├── quickstart.md        # Phase 1 output — files to touch + verification commands
├── checklists/
│   └── requirements.md  # Spec quality checklist (from /speckit.spec)
└── tasks.md             # Phase 2 output (NOT created by /speckit.plan)
```

### Source Code (files to modify)

```text
app/
├── Support/OperateHub/
│   └── OperateHubShell.php             # T1: Update scopeLabel() copy
├── Filament/
│   ├── Widgets/Alerts/
│   │   └── AlertsKpiHeader.php         # T2: Fix deliveriesQueryForViewer() bug
│   ├── Resources/
│   │   ├── AlertRuleResource.php       # T4/T5: Relabel form + add Sections
│   │   └── AlertRuleResource/Pages/
│   │       └── ListAlertRules.php      # T3: Remove OperateHubShell header spread
│   └── Resources/AlertDestinationResource/Pages/
│       └── ListAlertDestinations.php   # T3: Remove OperateHubShell header spread

tests/
├── Feature/OpsUx/
│   └── OperateHubShellTest.php         # T6: Update existing assertions for new copy
└── Feature/Filament/Alerts/
    └── AlertRuleCrudTest.php           # T6: Verify/update label-dependent assertions
```

**Structure Decision**: Existing Laravel monolith structure. All modifications are to existing files. No new directories or structural changes.

## Implementation Tasks

### T1 — Update `OperateHubShell::scopeLabel()` Copy (FR-001, FR-002, FR-003)

**File**: `app/Support/OperateHub/OperateHubShell.php` (line 23–33)

**Current**:
```php
public function scopeLabel(?Request $request = null): string
{
    $activeTenant = $this->activeEntitledTenant($request);
    if ($activeTenant instanceof Tenant) {
        return 'Scope: Tenant — '.$activeTenant->name;
    }
    return 'Scope: Workspace — all tenants';
}
```

**Target**:
```php
public function scopeLabel(?Request $request = null): string
{
    $activeTenant = $this->activeEntitledTenant($request);
    if ($activeTenant instanceof Tenant) {
        return 'Filtered by tenant: '.$activeTenant->name;
    }
    return 'All tenants';
}
```

**Propagation**: This single change propagates to all consumers:
- `headerActions()` within `OperateHubShell` itself (L61)
- `Operations.php` (L66) — uses `$operateHubShell->scopeLabel(request())` directly
- `TenantlessOperationRunViewer.php` (L47) — uses `$operateHubShell->scopeLabel(request())` directly
- `AuditLog.php`, `Alerts.php`, `ListAlertDeliveries.php` — via `headerActions()`

**FR mapping**: FR-001 (filtered indicator), FR-002 (all-tenants indicator), FR-003 (no "Scope:" text), FR-014 (DB-only renders unchanged).

---

### T2 — Fix `AlertsKpiHeader::deliveriesQueryForViewer()` Bug (FR-004, FR-005)

**File**: `app/Filament/Widgets/Alerts/AlertsKpiHeader.php` (line 101–118)

**Current** (buggy):
```php
protected function deliveriesQueryForViewer(): Builder
{
    $query = AlertDelivery::query()->where(...);
    $tenant = Filament::getTenant();  // ← BUG: null when lastTenantId fallback
    if ($tenant) {
        $query->where('tenant_id', $tenant->id);
    }
    return $query;
}
```

**Target**:
```php
protected function deliveriesQueryForViewer(): Builder
{
    $query = AlertDelivery::query()->where(...);
    $tenant = app(OperateHubShell::class)->activeEntitledTenant(request());
    if ($tenant instanceof Tenant) {
        $query->where('tenant_id', $tenant->id);
    }
    return $query;
}
```

**FR mapping**: FR-004 (consistent tenant resolution), FR-005 (KPI filters by resolved tenant).

---

### T3 — Remove OperateHubShell Header Actions from Manage Pages (FR-006)

**Files**:
- `app/Filament/Resources/AlertRuleResource/Pages/ListAlertRules.php` (L23–37)
- `app/Filament/Resources/AlertDestinationResource/Pages/ListAlertDestinations.php` (L17–30)

**Change**: Remove `...app(OperateHubShell::class)->headerActions(...)` from `getHeaderActions()`. Keep only the `CreateAction::make()`.

**FR mapping**: FR-006 (no tenant indicator on Manage pages).

---

### T4 — Relabel AlertRule Form Fields (FR-007, FR-008, FR-009, FR-010)

**File**: `app/Filament/Resources/AlertRuleResource.php` (form method, ~L155+)

**Changes**:
| Current | Target |
|---------|--------|
| `Select::make('tenant_scope_mode')` label (auto-generated "Tenant scope mode") | Explicit `->label('Applies to tenants')` |
| Option `'Allowlist'` | `'Selected tenants'` |
| `Select::make('tenant_allowlist')->label('Tenant allowlist')` | `->label('Selected tenants')` |
| No helper texts | `->helperText('This rule is workspace-wide. Use this to limit where it applies.')` on tenant_scope_mode |
| No helper texts | `->helperText('Only these tenants will trigger this rule.')` on tenant_allowlist |

**DB values**: `all` / `allowlist` constants UNCHANGED. Only display labels change.

**FR mapping**: FR-007, FR-008, FR-009, FR-010, FR-012 (no domain behavior changes), FR-013 (no schema changes).

---

### T5 — Add Section Grouping to AlertRule Form (FR-011)

**File**: `app/Filament/Resources/AlertRuleResource.php` (form method)

**Change**: Wrap existing flat fields into three `Filament\Schemas\Components\Section` groups:

1. **"Rule"** section: Name, Enabled, Event type, Minimum severity
2. **"Applies to"** section: Applies to tenants (tenant_scope_mode), Selected tenants (tenant_allowlist, conditional)
3. **"Delivery"** section: Cooldown, Quiet hours, Destinations

**Import**: `use Filament\Schemas\Components\Section;` (verified in 11 existing files).

**FR mapping**: FR-011.

---

### T6 — Update Tests (all FRs)

**Files to update**:
- `tests/Feature/OpsUx/OperateHubShellTest.php` — update all `assertSee('Scope: Workspace — all tenants')` → `assertSee('All tenants')` and all `assertSee('Scope: Tenant — ')` → `assertSee('Filtered by tenant: ')`
- `tests/Feature/Filament/Alerts/AlertRuleCrudTest.php` — check for label-dependent assertions, update if needed

**New test coverage** (add to existing test files or create new):
- KPI consistency: Set tenant-context via lastTenantId only → assert `deliveriesQueryForViewer()` includes `where tenant_id = X`
- Manage pages: Visit `/admin/alert-rules` with tenant-context → `assertDontSee('Filtered by tenant')` and `assertDontSee('Scope: Tenant')`
- Form labels: Visit AlertRule edit page → `assertSee('Applies to tenants')`, `assertDontSee('Tenant scope mode')`
- Section headings: Visit AlertRule edit page → `assertSee('Rule')`, `assertSee('Applies to')`, `assertSee('Delivery')`

---

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

**File**: `app/Filament/Resources/AlertRuleResource.php`

**Change**: Add a form action button visible when tenant-context is active AND "Selected tenants" mode is visible. On click, append current tenant to `tenant_allowlist` field state.

**Note**: Only implement if T1–T6 are complete and PR remains small. Skip if scope exceeds budget.

## Filament v5 Compliance (Agent Output Contract)

1. **Livewire v4.0+ compliance**: All code targets Livewire v4.0+. No v3 references.
2. **Provider registration**: No new providers. Existing panel provider in `bootstrap/providers.php` unchanged.
3. **Globally searchable resources**: AlertRuleResource has `$isScopedToTenant = false` — global search not applicable to workspace-owned resources. No changes to global search behavior.
4. **Destructive actions**: No destructive actions added or modified. Existing destructive actions on these pages retain `->requiresConfirmation()`.
5. **Asset strategy**: No new assets registered. No changes to `filament:assets` deployment.
6. **Testing plan**: OperateHubShellTest updated for new copy. AlertRuleCrudTest verified/updated. New test assertions for KPI consistency, Manage page indicator suppression, and form labels/sections.

## Complexity Tracking

> No constitution violations. No complexity justifications needed.
