TenantAtlas/specs/103-ia-scope-filter-semantics/tasks.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

16 KiB
Raw Blame History

Tasks: IA Semantics — Scope vs Filter vs Targeting (Monitoring + Manage)

Input: Design documents from /specs/103-ia-scope-filter-semantics/ Prerequisites: plan.md (loaded), spec.md (loaded), research.md (loaded), data-model.md (loaded), quickstart.md (loaded)

Tests: Required (Pest feature tests). This feature changes runtime behavior (copy, query filtering, form layout). Operations: N/A — no long-running/remote/queued/scheduled work introduced. RBAC: N/A — no authorization changes. Existing RBAC enforcement unchanged. Filament UI Action Surfaces: Manage pages have header actions removed (informational indicators, not mutation actions). No new actions added. Action surface contract satisfied. Filament UI UX-001: AlertRule form adds Section grouping (conforming to "all fields inside Sections/Cards"). No new pages or custom layouts. Badges: N/A — no status badges added or changed.

Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependencies)
  • [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
  • Include exact file paths in descriptions

Phase 1: Setup

Purpose: No project setup needed — existing Laravel monolith. This phase validates prerequisites only.

  • T001 Verify Sail is running and database is migrated (vendor/bin/sail up -d && vendor/bin/sail artisan migrate)
  • T002 Run existing test suite baseline (vendor/bin/sail artisan test --compact) to confirm green before changes

Checkpoint: Baseline green — changes can begin.


Phase 2: Foundational (Blocking Prerequisites)

Purpose: The OperateHubShell::scopeLabel() change is the single foundation all other stories depend on. It must be updated first because US2 tests need the new copy, US3 verifies its absence, and US1 tests assert it directly.

⚠️ CRITICAL: US2/US3/US4/US5 tests reference the new indicator copy. This phase must complete first.

  • T003 Update scopeLabel() return values in app/Support/OperateHub/OperateHubShell.php: change 'Scope: Tenant — '.$activeTenant->name to 'Filtered by tenant: '.$activeTenant->name and 'Scope: Workspace — all tenants' to 'All tenants' (line 2333)

Checkpoint: scopeLabel() emits new copy. All Monitoring pages that call scopeLabel() or headerActions() now show the new labels automatically. No tests pass yet — existing test assertions reference old copy.


Phase 3: User Story 1 — Monitoring Page Tenant Indicator (Priority: P1) 🎯 MVP

Goal: Monitoring canonical pages display "Filtered by tenant: {name}" or "All tenants" instead of "Scope: …" labels.

Independent Test: Visit any Monitoring page with/without tenant-context and assert the indicator text.

Tests for User Story 1

  • T004 [US1] Update existing assertions in tests/Feature/OpsUx/OperateHubShellTest.php: replace all assertSee('Scope: Workspace — all tenants') with assertSee('All tenants') and all assertSee('Scope: Tenant') patterns with assertSee('Filtered by tenant:'). Also add assertDontSee('Scope: Tenant') and assertDontSee('Scope: Workspace') to existing test cases.
  • T005 [US1] Run OperateHubShell tests to confirm they pass with new copy: vendor/bin/sail artisan test --compact --filter=OperateHubShell

Checkpoint: US1 complete — Monitoring indicator shows correct copy. FR-001, FR-002, FR-003 satisfied.


Phase 4: User Story 2 — Alerts KPI Widget Tenant Resolution Bugfix (Priority: P1)

Goal: AlertsKpiHeader::deliveriesQueryForViewer() uses OperateHubShell::activeEntitledTenant() instead of Filament::getTenant(), so KPI numbers always match the indicator banner.

Independent Test: Set tenant-context via lastTenantId session fallback only, render KPI widget, assert query includes tenant_id filter.

Implementation for User Story 2

  • T006 [US2] Fix deliveriesQueryForViewer() in app/Filament/Widgets/Alerts/AlertsKpiHeader.php: replace Filament::getTenant() with app(OperateHubShell::class)->activeEntitledTenant(request()) and update the null-check to use instanceof Tenant (line 101118). Add use App\Support\OperateHub\OperateHubShell; and use App\Models\Tenant; imports if not already present.

Tests for User Story 2

  • T007 [US2] Add test in tests/Feature/OpsUx/OperateHubShellTest.php (or new test file tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php): (a) test that when tenant-context is set only via WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY session (Filament::getTenant() returns null), the KPI widget's delivery query includes a where tenant_id = ? clause matching the fallback tenant; (b) test that when tenant-context is set via Filament::setTenant(), the KPI widget's delivery query filters by that tenant's ID (regression guard for US2 acceptance scenario 2).
  • T008 [US2] Add test: when no tenant-context is active, the KPI widget's delivery query does NOT include a tenant_id filter (workspace-wide counts).
  • T009 [US2] Run KPI tests: vendor/bin/sail artisan test --compact --filter=AlertsKpiHeader

Checkpoint: US2 complete — KPI numbers are consistent with indicator banner. FR-004, FR-005 satisfied.


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

Goal: Alert Rules list and Alert Destinations list no longer show any tenant indicator/banner.

Independent Test: Visit /admin/alert-rules with tenant-context active, assert absence of "Filtered by tenant" text.

Implementation for User Story 3

  • T010 [P] [US3] Remove ...app(OperateHubShell::class)->headerActions(...) spread from getHeaderActions() in app/Filament/Resources/AlertRuleResource/Pages/ListAlertRules.php. Keep only the CreateAction::make(). Remove unused OperateHubShell import if no other references remain.
  • T011 [P] [US3] Remove ...app(OperateHubShell::class)->headerActions(...) spread from getHeaderActions() in app/Filament/Resources/AlertDestinationResource/Pages/ListAlertDestinations.php. Keep only the CreateAction::make(). Remove unused OperateHubShell import if no other references remain.

Tests for User Story 3

  • T012 [US3] Add test: visit /admin/alert-rules list page with tenant-context active (via Filament tenant or lastTenantId fallback), assert assertDontSee('Filtered by tenant') and assertDontSee('Scope: Tenant') and assertDontSee('Scope: Workspace').
  • T013 [US3] Add test: visit /admin/alert-destinations list page with tenant-context active, assert absence of any tenant indicator text.
  • T014 [US3] Run Manage page tests: vendor/bin/sail artisan test --compact --filter="ListAlertRules|ListAlertDestinations|ManagePage"

Checkpoint: US3 complete — Manage pages show no tenant indicator. FR-006 satisfied.


Phase 6: User Story 4 — AlertRule Form Labels (Priority: P2)

Goal: AlertRule edit form uses targeting semantics: "Applies to tenants" with options "All tenants" / "Selected tenants", helper texts, and "Selected tenants" field label.

Independent Test: Visit AlertRule edit page, assert new labels/helper texts appear and old labels are absent.

Implementation for User Story 4

  • T015 [US4] In app/Filament/Resources/AlertRuleResource.php form method: add explicit ->label('Applies to tenants') to Select::make('tenant_scope_mode'), change option display from 'Allowlist' to 'Selected tenants', add ->helperText('This rule is workspace-wide. Use this to limit where it applies.').
  • T016 [US4] In app/Filament/Resources/AlertRuleResource.php form method: change Select::make('tenant_allowlist')->label('Tenant allowlist') to ->label('Selected tenants'), add ->helperText('Only these tenants will trigger this rule.').

Tests for User Story 4

  • T017 [US4] Add test: visit AlertRule edit form, assertSee('Applies to tenants'), assertSee('This rule is workspace-wide'), assertDontSee('Tenant scope mode'), assertDontSee('Tenant allowlist').
  • T018 [US4] Update any existing label-dependent assertions in tests/Feature/Filament/Alerts/AlertRuleCrudTest.php if they reference old "Tenant scope mode" or "Tenant allowlist" labels.
  • T019 [US4] Run AlertRule form tests: vendor/bin/sail artisan test --compact --filter=AlertRuleCrud

Checkpoint: US4 complete — AlertRule form uses targeting semantics. FR-007, FR-008, FR-009, FR-010 satisfied.


Phase 7: User Story 5 — AlertRule Form Sections (Priority: P3)

Goal: AlertRule edit form fields are grouped into three sections: "Rule", "Applies to", "Delivery".

Independent Test: Load the edit form and assert the presence of three section headings.

Implementation for User Story 5

  • T020 [US5] In app/Filament/Resources/AlertRuleResource.php form method: add use Filament\Schemas\Components\Section; import (if not already present). Wrap Name, Enabled, Event type, Minimum severity fields in Section::make('Rule').
  • T021 [US5] Wrap tenant_scope_mode and tenant_allowlist fields in Section::make('Applies to').
  • T022 [US5] Wrap Cooldown, Quiet hours, Destinations fields in Section::make('Delivery').

Tests for User Story 5

  • T023 [US5] Add test: visit AlertRule edit form, assertSee('Rule') (section heading), assertSee('Applies to') (section heading), assertSee('Delivery') (section heading).
  • T024 [US5] Run section tests: vendor/bin/sail artisan test --compact --filter=AlertRuleCrud

Checkpoint: US5 complete — AlertRule form is organized in sections. FR-011 satisfied.


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

Goal: When editing an AlertRule with tenant-context active and "Selected tenants" mode visible, an action button "Add current tenant to selected tenants" appears.

Independent Test: Render form with tenant-context + allowlist visible, click button, assert tenant is added.

⚠️ NOTE: Only implement if T001T024 are complete and PR scope permits. Skip entirely if budget exceeded.

Implementation for User Story 6

  • T025 [US6] In app/Filament/Resources/AlertRuleResource.php form method: add a form action button on the tenant_allowlist field (or as a suffix action) that resolves OperateHubShell::activeEntitledTenant(request()) and appends its ID to the tenant_allowlist field state. Button visible only when tenant-context is active AND tenant_scope_mode is allowlist.

Tests for User Story 6

  • T026 [US6] Add test: render AlertRule edit form with tenant-context active + allowlist mode, click "Add current tenant to selected tenants", assert the tenant ID is added to the field state.
  • T027 [US6] Add test: render AlertRule edit form with no tenant-context (or "All tenants" mode), assert the "Add current tenant" button is NOT visible.
  • T028 [US6] Run convenience button tests: vendor/bin/sail artisan test --compact --filter=AlertRuleCrud

Checkpoint: US6 complete (if implemented). Optional enhancement delivered.


Phase 9: Polish & Cross-Cutting Concerns

Purpose: Final validation, formatting, and full-suite regression check.

  • T029 Run Pint code formatter: vendor/bin/sail bin pint --dirty
  • T030 Run full test suite to confirm zero regressions: vendor/bin/sail artisan test --compact
  • T031 Verify no remaining instances of "Scope: Tenant" or "Scope: Workspace" in source code: grep -r "Scope: Tenant\|Scope: Workspace" app/ tests/ should return zero matches (excluding comments/docs)

Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies — start immediately
  • Foundational (Phase 2, T003): Depends on Phase 1 — BLOCKS all user stories
  • US1 (Phase 3): Depends on Phase 2 (T003) — test-only phase, updates existing assertions
  • US2 (Phase 4): Depends on Phase 2 (T003) — can run in parallel with US1
  • US3 (Phase 5): Depends on Phase 2 (T003) — can run in parallel with US1/US2
  • US4 (Phase 6): No dependency on other user stories — can run in parallel with US1/US2/US3
  • US5 (Phase 7): Depends on US4 (T015/T016) completion — same file, must sequence after US4
  • US6 (Phase 8): Depends on US4 + US5 — same file, must sequence after US5
  • Polish (Phase 9): Depends on all desired user stories being complete

User Story Dependencies

  • US1 (P1): Independent after foundational phase. Can start immediately.
  • US2 (P1): Independent after foundational phase. Can run in parallel with US1.
  • US3 (P2): Independent. T010 and T011 are parallel (different files).
  • US4 (P2): Independent. T015 and T016 edit the same file sequentially.
  • US5 (P3): Depends on US4 (same file — AlertRuleResource.php).
  • US6 (P3, Optional): Depends on US5 (same file — AlertRuleResource.php).

Within Each User Story

  • Implementation before tests (tests validate the implementation)
  • Exception: US1 is test-only (updates existing assertions for the T003 foundational change)
  • Story-specific tests run immediately after implementation

Parallel Opportunities

  • After Phase 2: US1 (test updates) + US2 (KPI bugfix) + US3 (Manage page cleanup) can all run in parallel — different files, no dependencies.
  • Within US3: T010 and T011 are parallel — different files (ListAlertRules.php vs ListAlertDestinations.php).
  • US4 must precede US5/US6 — all edit AlertRuleResource.php.

Parallel Example: After Foundational Phase

# These three can launch simultaneously after T003 completes:

# Agent A: US1 — Update test assertions
Task T004: Update OperateHubShellTest.php assertions (tests/Feature/OpsUx/)
Task T005: Run OperateHubShell tests

# Agent B: US2 — Fix KPI bug
Task T006: Fix deliveriesQueryForViewer() (app/Filament/Widgets/Alerts/)
Task T007-T009: Add + run KPI tests

# Agent C: US3 — Clean Manage pages
Task T010: Remove header spread from ListAlertRules.php
Task T011: Remove header spread from ListAlertDestinations.php (parallel with T010)
Task T012-T014: Add + run Manage page tests

Implementation Strategy

MVP First (US1 + US2 Only)

  1. Complete Phase 1: Setup (verify baseline)
  2. Complete Phase 2: Foundational (T003 — scopeLabel() copy change)
  3. Complete Phase 3: US1 (update test assertions)
  4. Complete Phase 4: US2 (KPI bugfix + tests)
  5. STOP and VALIDATE: All Monitoring pages show correct indicators + KPIs match
  6. Deploy/demo if ready — highest-value changes shipped

Incremental Delivery

  1. T003 foundational → scopeLabel copy changed
  2. US1 test updates → existing tests green with new copy (MVP ready)
  3. US2 KPI bugfix → data consistency fixed (full P1 delivered)
  4. US3 Manage pages → no false indicators on workspace-owned pages (P2a)
  5. US4 form labels → targeting semantics in AlertRule form (P2b)
  6. US5 form sections → improved form organization (P3a)
  7. US6 convenience button → optional enhancement (P3b, skip if budget exceeded)
  8. Phase 9 polish → Pint + full suite + grep verification

Sequential execution in priority order:

  1. Phase 1 → Phase 2 → Phase 3 (US1) → Phase 4 (US2) → run full suite
  2. Phase 5 (US3) → Phase 6 (US4) → Phase 7 (US5) → run full suite
  3. Phase 8 (US6, optional) → Phase 9 (polish)

Notes

  • No schema changes — zero migrations needed
  • No new files created (except potentially one new test file for US2 KPI tests)
  • The scopeLabel() method name is NOT renamed — only the return values change (per spec)
  • DB column values for tenant_scope_mode (all, allowlist) are UNCHANGED — only display labels
  • Filament\Schemas\Components\Section is the correct v5 import (confirmed in 11 existing files)
  • US6 is explicitly optional — skip without guilt if PR is already large