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
16 KiB
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 inapp/Support/OperateHub/OperateHubShell.php: change'Scope: Tenant — '.$activeTenant->nameto'Filtered by tenant: '.$activeTenant->nameand'Scope: Workspace — all tenants'to'All tenants'(line 23–33)
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 allassertSee('Scope: Workspace — all tenants')withassertSee('All tenants')and allassertSee('Scope: Tenant')patterns withassertSee('Filtered by tenant:'). Also addassertDontSee('Scope: Tenant')andassertDontSee('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()inapp/Filament/Widgets/Alerts/AlertsKpiHeader.php: replaceFilament::getTenant()withapp(OperateHubShell::class)->activeEntitledTenant(request())and update the null-check to useinstanceof Tenant(line 101–118). Adduse App\Support\OperateHub\OperateHubShell;anduse 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 filetests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php): (a) test that when tenant-context is set only viaWorkspaceContext::LAST_TENANT_IDS_SESSION_KEYsession (Filament::getTenant() returns null), the KPI widget's delivery query includes awhere tenant_id = ?clause matching the fallback tenant; (b) test that when tenant-context is set viaFilament::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_idfilter (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 fromgetHeaderActions()inapp/Filament/Resources/AlertRuleResource/Pages/ListAlertRules.php. Keep only theCreateAction::make(). Remove unusedOperateHubShellimport if no other references remain. - T011 [P] [US3] Remove
...app(OperateHubShell::class)->headerActions(...)spread fromgetHeaderActions()inapp/Filament/Resources/AlertDestinationResource/Pages/ListAlertDestinations.php. Keep only theCreateAction::make(). Remove unusedOperateHubShellimport if no other references remain.
Tests for User Story 3
- T012 [US3] Add test: visit
/admin/alert-ruleslist page with tenant-context active (via Filament tenant or lastTenantId fallback), assertassertDontSee('Filtered by tenant')andassertDontSee('Scope: Tenant')andassertDontSee('Scope: Workspace'). - T013 [US3] Add test: visit
/admin/alert-destinationslist 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.phpform method: add explicit->label('Applies to tenants')toSelect::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.phpform method: changeSelect::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.phpif 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.phpform method: adduse Filament\Schemas\Components\Section;import (if not already present). Wrap Name, Enabled, Event type, Minimum severity fields inSection::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 T001–T024 are complete and PR scope permits. Skip entirely if budget exceeded.
Implementation for User Story 6
- T025 [US6] In
app/Filament/Resources/AlertRuleResource.phpform method: add a form action button on thetenant_allowlistfield (or as a suffix action) that resolvesOperateHubShell::activeEntitledTenant(request())and appends its ID to thetenant_allowlistfield state. Button visible only when tenant-context is active ANDtenant_scope_modeisallowlist.
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.phpvsListAlertDestinations.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)
- Complete Phase 1: Setup (verify baseline)
- Complete Phase 2: Foundational (T003 —
scopeLabel()copy change) - Complete Phase 3: US1 (update test assertions)
- Complete Phase 4: US2 (KPI bugfix + tests)
- STOP and VALIDATE: All Monitoring pages show correct indicators + KPIs match
- Deploy/demo if ready — highest-value changes shipped
Incremental Delivery
- T003 foundational → scopeLabel copy changed
- US1 test updates → existing tests green with new copy (MVP ready)
- US2 KPI bugfix → data consistency fixed (full P1 delivered)
- US3 Manage pages → no false indicators on workspace-owned pages (P2a)
- US4 form labels → targeting semantics in AlertRule form (P2b)
- US5 form sections → improved form organization (P3a)
- US6 convenience button → optional enhancement (P3b, skip if budget exceeded)
- Phase 9 polish → Pint + full suite + grep verification
Single Developer Strategy (Recommended)
Sequential execution in priority order:
- Phase 1 → Phase 2 → Phase 3 (US1) → Phase 4 (US2) → run full suite
- Phase 5 (US3) → Phase 6 (US4) → Phase 7 (US5) → run full suite
- 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\Sectionis the correct v5 import (confirmed in 11 existing files)- US6 is explicitly optional — skip without guilt if PR is already large