## Summary - implement Spec 224 findings notifications and escalation v1 on top of the existing alerts and Filament database notification infrastructure - add finding assignment, reopen, due soon, and overdue event handling with direct recipient routing, dedupe, and optional external alert fan-out - extend alert rule and alert delivery surfaces plus add the Spec 224 planning bundle and candidate-list promotion cleanup ## Validation - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsNotificationEventTest.php tests/Feature/Findings/FindingsNotificationRoutingTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Alerts/FindingsAlertRuleIntegrationTest.php tests/Feature/Alerts/SlaDueAlertTest.php tests/Feature/Notifications/FindingNotificationLinkTest.php` ## Filament / Platform Notes - Livewire v4.0+ compliance is preserved - provider registration remains unchanged in `apps/platform/bootstrap/providers.php` - no globally searchable resource behavior changed in this feature - no new destructive action was introduced - asset strategy is unchanged and the existing `cd apps/platform && php artisan filament:assets` deploy step remains sufficient ## Manual Smoke Note - integrated-browser smoke testing confirmed the new alert rule event options, notification drawer entries, alert delivery history row, and tenant finding detail route on the active Sail host - local notification deep links currently resolve from `APP_URL`, so a local `localhost` vs `127.0.0.1:8081` host mismatch can break the browser session if the app is opened on a different host/port combination Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #261
253 lines
24 KiB
Markdown
253 lines
24 KiB
Markdown
# Implementation Plan: Findings Notifications & Escalation v1
|
|
|
|
**Branch**: `224-findings-notifications-escalation` | **Date**: 2026-04-22 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/spec.md`
|
|
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/spec.md`
|
|
|
|
**Note**: This plan keeps the work inside the existing findings workflow, workspace alerting, and Filament database-notification primitives. The intended implementation adds four finding event types, one narrow finding-notification service, one database notification class, and focused extensions to the existing alert evaluation and alert-management surfaces. It does not add a new table, a notification center, a preference system, a second findings queue, or a generic workflow engine.
|
|
|
|
## Summary
|
|
|
|
Extend the existing workspace Alerts event vocabulary with `findings.assigned`, `findings.reopened`, `findings.due_soon`, and `findings.overdue`, then add a narrow `FindingNotificationService` that sends one entitlement-safe direct database notification to the currently responsible operator and forwards the same event into `AlertDispatchService` for optional external copies. Emit assignment and system-reopen events from `FindingWorkflowService` after committed mutations, evaluate due-soon and overdue windows inside the existing `EvaluateAlertsJob` cadence, reuse the existing `notifications` table and Filament database-notification drawer for direct delivery, and keep finding follow-up on the existing tenant finding detail route.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade
|
|
**Primary Dependencies**: Laravel notifications (`database` channel), Filament database notifications, `Finding`, `FindingWorkflowService`, `FindingSlaPolicy`, `AlertRule`, `AlertDelivery`, `AlertDispatchService`, `EvaluateAlertsJob`, `CapabilityResolver`, `WorkspaceContext`, `TenantMembership`, `FindingResource`
|
|
**Storage**: PostgreSQL via existing `findings`, `alert_rules`, `alert_deliveries`, `notifications`, `tenant_memberships`, and `audit_logs`; no schema changes planned
|
|
**Testing**: Pest v4 feature tests with Filament/Livewire assertions and notification-payload checks
|
|
**Validation Lanes**: fast-feedback, confidence
|
|
**Target Platform**: Dockerized Laravel web application via Sail locally and Linux containers in deployment
|
|
**Project Type**: Laravel monolith inside the `wt-plattform` monorepo
|
|
**Performance Goals**: Keep direct notification dispatch and external alert-copy generation DB-backed and queue-safe, avoid N+1 tenant or recipient lookups, and keep due-window scans bounded to workspace-scoped open findings with due-date indexes already present
|
|
**Constraints**: No new persisted notification-truth table, no notification-preference center, no new capability family, no hidden-tenant leakage, no manual-reopen notification in v1, no repeated overdue spam within the same due cycle, and no new frontend assets
|
|
**Scale/Scope**: Four new event types, one narrow finding-notification service, one new database notification class, extensions to two existing alert resources, one extension to the existing alerts evaluation job, and four focused feature suites
|
|
|
|
## UI / Surface Guardrail Plan
|
|
|
|
- **Guardrail scope**: changed surfaces
|
|
- **Native vs custom classification summary**: native Filament resources and existing database-notification primitives only
|
|
- **Shared-family relevance**: workspace alert configuration and delivery-history family, existing admin and tenant database-notification family, existing tenant finding detail family
|
|
- **State layers in scope**: shell, detail
|
|
- **Handling modes by drift class or surface**: review-mandatory
|
|
- **Repository-signal treatment**: review-mandatory
|
|
- **Special surface test profiles**: standard-native-filament, global-context-shell
|
|
- **Required tests or manual smoke**: functional-core, state-contract
|
|
- **Exception path and spread control**: none; the feature extends existing alert resources and notification primitives rather than creating a new page family
|
|
- **Active feature PR close-out entry**: Guardrail
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Passed before Phase 0 research. Re-check after Phase 1 design.*
|
|
|
|
| Principle | Pre-Research | Post-Design | Notes |
|
|
|-----------|--------------|-------------|-------|
|
|
| Inventory-first / snapshots-second | PASS | PASS | All event production stays derived from existing `Finding` lifecycle, ownership, severity, and due-date truth; notifications and alert deliveries remain delivery artifacts only |
|
|
| Read/write separation | PASS | PASS | The feature adds no new operator mutation surface; assignment and reopen writes stay inside existing workflow actions, while due reminders remain scheduled evaluation side effects |
|
|
| Graph contract path | PASS | PASS | No Microsoft Graph call paths or contract-registry changes are introduced |
|
|
| Deterministic capabilities / RBAC-UX | PASS | PASS | Workspace-scoped alert configuration remains capability-gated, direct recipients are re-validated against current tenant membership plus findings-view capability at send time, non-members remain `404`, and in-scope capability failures remain `403` |
|
|
| Workspace / tenant isolation | PASS | PASS | Alert rules and alert deliveries stay workspace-scoped, while direct notifications always deep-link to tenant-scoped finding detail and must not expose hidden tenant data |
|
|
| Run observability / Ops-UX | PASS | PASS | Scheduled due-event evaluation stays inside the existing `alerts.evaluate` cadence; no `OperationRun` notification semantics are changed and no queued/running DB notifications are introduced |
|
|
| Proportionality / no premature abstraction | PASS | PASS | The plan allows one narrow `FindingNotificationService` because direct-recipient resolution, dedupe, payload composition, and external alert-copy forwarding would otherwise be duplicated across workflow mutations and scheduled due evaluation |
|
|
| Persisted truth / few layers | PASS | PASS | No new table or persisted workflow state is added; existing `notifications` JSONB and `alert_deliveries` rows remain the only delivery artifacts |
|
|
| Behavioral state discipline | PASS | PASS | The four new values are delivery event types, not a new finding lifecycle or responsibility taxonomy |
|
|
| Filament-native UI (UI-FIL-001) | PASS | PASS | Alert rules and alert deliveries remain native Filament resources; direct notifications use existing Filament database-notification payloads |
|
|
| Decision-first / action-surface contract | PASS | PASS | Notifications stay as secondary drill-in entry points, alert rules remain config-first, and alert deliveries remain read-only diagnostics |
|
|
| Test governance (TEST-GOV-001) | PASS | PASS | Proof stays in focused feature suites for event production, routing, alert-rule integration, and deep-link safety, with no browser or heavy-governance expansion |
|
|
| Filament v5 / Livewire v4 compliance | PASS | PASS | The feature uses existing Filament v5 resources and Livewire v4-compatible database notifications only |
|
|
| Provider registration / global search / assets | PASS | PASS | Panel providers already live in `apps/platform/bootstrap/providers.php`; no globally searchable resource is added or changed; no new assets are required, so the existing deploy `filament:assets` step remains unchanged |
|
|
|
|
## Test Governance Check
|
|
|
|
- **Test purpose / classification by changed surface**: `Feature` for finding-event production, direct-recipient routing, alert-rule UI integration, and deep-link authorization behavior
|
|
- **Affected validation lanes**: `fast-feedback`, `confidence`
|
|
- **Why this lane mix is the narrowest sufficient proof**: The main risk is integrated workflow behavior: which event fires, who gets it, whether direct delivery leaks scope, whether external copies remain optional, and whether alert-management surfaces expose the new event types coherently. Focused feature tests prove that without adding unit-only abstractions or browser cost.
|
|
- **Narrowest proving command(s)**:
|
|
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsNotificationEventTest.php tests/Feature/Findings/FindingsNotificationRoutingTest.php`
|
|
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Alerts/FindingsAlertRuleIntegrationTest.php tests/Feature/Alerts/SlaDueAlertTest.php tests/Feature/Notifications/FindingNotificationLinkTest.php`
|
|
- **Fixture / helper / factory / seed / context cost risks**: Moderate. Tests need workspace and tenant context, current and removed memberships, owner-versus-assignee combinations, existing alert rules and destinations, notification-table assertions, and time-travel across due windows.
|
|
- **Expensive defaults or shared helper growth introduced?**: no; any notification-specific helper should stay local to the new tests and reuse existing `createUserWithTenant(...)`, `Finding::factory()`, and alert destination factories
|
|
- **Heavy-family additions, promotions, or visibility changes**: none
|
|
- **Surface-class relief / special coverage rule**: `standard-native-filament` for alert resources and `global-context-shell` for database notifications that bridge admin shell context to tenant finding detail
|
|
- **Closing validation and reviewer handoff**: Reviewers should rely on the exact commands above and verify that owner-only changes do not emit assignment notifications, manual reopen still emits nothing, due-soon and overdue stay one-per-due-cycle, same-user owner/assignee resolution creates one notification, direct delivery suppresses when entitlement is lost, and external alert copies only appear when a matching alert rule exists.
|
|
- **Budget / baseline / trend follow-up**: none
|
|
- **Review-stop questions**: Did the implementation introduce new persistence, a preference layer, or a generic workflow-notification engine? Did any path leak hidden tenant information in the notification title, body, or action URL? Did due evaluation widen beyond the existing alert-evaluation cadence without need? Did alert-rule resource changes stay native and read clearly?
|
|
- **Escalation path**: document-in-feature unless a second delivery abstraction, a preference center, or a new findings-specific notification surface is proposed, in which case split or follow up with a dedicated spec
|
|
- **Active feature PR close-out entry**: Guardrail
|
|
- **Why no dedicated follow-up spec is needed**: This feature remains bounded to four concrete event types and one direct-delivery contract built on existing infrastructure
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/224-findings-notifications-escalation/
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ └── findings-notifications-escalation.logical.openapi.yaml
|
|
├── checklists/
|
|
│ └── requirements.md
|
|
└── tasks.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
apps/platform/
|
|
├── app/
|
|
│ ├── Filament/
|
|
│ │ └── Resources/
|
|
│ │ ├── AlertDeliveryResource.php
|
|
│ │ └── AlertRuleResource.php
|
|
│ ├── Jobs/
|
|
│ │ └── Alerts/
|
|
│ │ └── EvaluateAlertsJob.php
|
|
│ ├── Models/
|
|
│ │ └── AlertRule.php
|
|
│ ├── Notifications/
|
|
│ │ └── Findings/
|
|
│ │ └── FindingEventNotification.php
|
|
│ └── Services/
|
|
│ ├── Alerts/
|
|
│ │ └── AlertDispatchService.php
|
|
│ └── Findings/
|
|
│ ├── FindingNotificationService.php
|
|
│ ├── FindingSlaPolicy.php
|
|
│ └── FindingWorkflowService.php
|
|
├── database/
|
|
│ └── factories/
|
|
│ └── FindingFactory.php
|
|
└── tests/
|
|
└── Feature/
|
|
├── Alerts/
|
|
│ └── FindingsAlertRuleIntegrationTest.php
|
|
├── Findings/
|
|
│ ├── FindingsNotificationEventTest.php
|
|
│ └── FindingsNotificationRoutingTest.php
|
|
└── Notifications/
|
|
└── FindingNotificationLinkTest.php
|
|
```
|
|
|
|
**Structure Decision**: Standard Laravel monolith. The feature stays inside existing finding workflow, alerting, and notification seams. No new base directory, panel, or persisted model is required.
|
|
|
|
## Complexity Tracking
|
|
|
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
|-----------|------------|-------------------------------------|
|
|
| none | — | — |
|
|
|
|
## Proportionality Review
|
|
|
|
- **Current operator problem**: Finding assignment, automatic reopen, and due-state changes remain silent unless operators keep polling findings pages.
|
|
- **Existing structure is insufficient because**: `FindingWorkflowService` knows about ownership changes but not delivery, `EvaluateAlertsJob` knows about workspace alert events but not direct responsible-user delivery, and the existing alert-rule UI cannot express these finding-specific workflow events yet.
|
|
- **Narrowest correct implementation**: Add four event types, extend the existing alert-rule and delivery viewer labels, create one narrow `FindingNotificationService` to unify recipient resolution plus direct and external delivery, and emit events only from existing workflow and alert-evaluation seams.
|
|
- **Ownership cost created**: One service, one notification class, incremental logic in `FindingWorkflowService` and `EvaluateAlertsJob`, and four focused feature suites.
|
|
- **Alternative intentionally rejected**: A new `FindingNotificationDelivery` table or generic workflow-notification engine. Both add persistence or framework complexity that current-release truth does not require because existing `notifications` and `alert_deliveries` already capture delivery artifacts.
|
|
- **Release truth**: Current-release truth. The feature closes an existing workflow loop now rather than preparing a later escalation framework.
|
|
|
|
## Phase 0 Research
|
|
|
|
Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/research.md`.
|
|
|
|
Key decisions:
|
|
|
|
- Add the four new finding event types to the existing `AlertRule` constant registry and `AlertRuleResource::eventTypeOptions()` instead of creating a second event catalog.
|
|
- Reuse existing Filament database notifications on the admin panel rather than building a findings-specific notification surface or page.
|
|
- Emit `findings.assigned` and `findings.reopened` from `FindingWorkflowService` after the write transaction commits so delivery does not race an uncommitted finding state.
|
|
- Evaluate `findings.due_soon` and `findings.overdue` inside the existing `EvaluateAlertsJob` workspace cadence rather than adding a second scheduler, command, or `OperationRun` family.
|
|
- Use the existing `notifications` table `data` payload to store a finding-event fingerprint for direct-delivery dedupe; do not add a new persistence model.
|
|
- Reuse `FindingResource::getUrl(..., panel: 'tenant', tenant: $tenant)` for notification deep links and re-check entitlement before sending any direct notification.
|
|
|
|
## Phase 1 Design
|
|
|
|
Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/`:
|
|
|
|
- `research.md`: event-registry, delivery, dedupe, and evaluation-seam decisions
|
|
- `data-model.md`: existing entities plus the derived finding-event and direct-notification payload models
|
|
- `contracts/findings-notifications-escalation.logical.openapi.yaml`: internal logical contract for finding-event dispatch, alert-rule event exposure, alert-delivery viewing, and finding deep-link payloads
|
|
- `quickstart.md`: focused validation workflow for implementation and review
|
|
|
|
Design decisions:
|
|
|
|
- No schema migration is required; direct notifications use the existing `notifications` table and external copies use existing `alert_deliveries` rows.
|
|
- The canonical new seam is one narrow `FindingNotificationService`, not a reusable workflow-notification framework.
|
|
- Direct-recipient dedupe uses a fingerprint stored in the existing database notification payload; due-cycle reset keys are derived from the current `due_at` value.
|
|
- Alert rules remain optional for external copies; direct responsible-user notifications do not depend on any matching alert rule.
|
|
- Existing tenant finding detail remains the only follow-up surface, and current `404` versus `403` route behavior remains authoritative at open time.
|
|
|
|
## Phase 1 Agent Context Update
|
|
|
|
Run:
|
|
|
|
- `.specify/scripts/bash/update-agent-context.sh copilot`
|
|
|
|
## Constitution Check — Post-Design Re-evaluation
|
|
|
|
- PASS — the design remains inside current findings, alerts, and database-notification seams with no new persistence, no Graph work, no new capability family, and no new frontend assets.
|
|
- PASS — Livewire v4.0+ and Filament v5 constraints remain satisfied, panel provider registration stays in `apps/platform/bootstrap/providers.php`, no globally searchable resource behavior changes, and no new destructive action path is introduced.
|
|
|
|
## Implementation Strategy
|
|
|
|
### Phase A — Extend the existing alert-event vocabulary and operator labels
|
|
|
|
**Goal**: Teach existing alert-management surfaces about the four new finding workflow events.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| A.1 | `apps/platform/app/Models/AlertRule.php` | Add the four new `EVENT_FINDINGS_*` constants alongside the existing alert event types |
|
|
| A.2 | `apps/platform/app/Filament/Resources/AlertRuleResource.php` | Extend `eventTypeOptions()` and `eventTypeLabel()` with operator-facing labels for assignment, reopened, due soon, and overdue events |
|
|
| A.3 | `apps/platform/app/Filament/Resources/AlertDeliveryResource.php` | Ensure list, view, and filter surfaces continue to render the new event labels cleanly through the existing event-type label seam without adding a new delivery viewer |
|
|
|
|
### Phase B — Add one narrow finding-notification delivery seam on existing primitives
|
|
|
|
**Goal**: Send one entitlement-safe direct database notification and one optional external alert copy from the same event envelope.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| B.1 | `apps/platform/app/Services/Findings/FindingNotificationService.php` | Add a focused service that builds finding-event payloads, resolves the direct recipient by the spec precedence rules, checks current tenant entitlement plus findings-view capability, computes a delivery fingerprint, suppresses duplicates by querying existing database notifications, and forwards the same event array to `AlertDispatchService` for external copies |
|
|
| B.2 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Add a database notification class that returns Filament notification payloads with tenant-safe title/body copy, recipient-reason copy, one finding-detail action URL, and embedded metadata including the event type and fingerprint |
|
|
| B.3 | `apps/platform/app/Services/Alerts/AlertDispatchService.php` | Keep the existing workspace alert-copy path intact and only absorb any payload-shape normalization needed for the new finding event titles, body copy, and metadata |
|
|
|
|
### Phase C — Emit assignment and automatic-reopen notifications from existing finding workflow mutations
|
|
|
|
**Goal**: Turn current write seams into finding-event producers without changing workflow truth.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| C.1 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | After committed `assign(...)` mutations, compare before and after owner and assignee state, suppress no-op, owner-only, and assignee-clear transitions, and dispatch `findings.assigned` only when a new assignee is set on an open finding |
|
|
| C.2 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | After committed `reopenBySystem(...)` mutations, dispatch `findings.reopened` using the refreshed finding state; keep manual `reopen(...)` out of scope for v1 delivery |
|
|
| C.3 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` and `apps/platform/database/factories/FindingFactory.php` | Preserve existing due-date reset and reopen semantics so notification due-cycle logic stays derived from the recalculated `due_at` value, with reopen metadata remaining explanatory rather than becoming a second cycle key |
|
|
|
|
### Phase D — Evaluate due-soon and overdue events inside the existing alerts evaluation cadence
|
|
|
|
**Goal**: Keep scheduled due reminders and escalations inside the already-established workspace alert window.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| D.1 | `apps/platform/app/Jobs/Alerts/EvaluateAlertsJob.php` | Add finding-level due-soon and overdue candidate queries over workspace-scoped open findings with `due_at`, using the existing window semantics and a fixed v1 due-soon horizon of 24 hours |
|
|
| D.2 | `apps/platform/app/Jobs/Alerts/EvaluateAlertsJob.php` | For each candidate finding event, call `FindingNotificationService` so direct delivery and optional external alert copies stay consistent and no second dispatch pipeline appears |
|
|
| D.3 | `apps/platform/app/Services/Findings/FindingNotificationService.php` | Define due-cycle fingerprints from the current `due_at` value so due-soon and overdue notifications emit once per cycle and reset only when `due_at` is recalculated by existing lifecycle semantics |
|
|
|
|
### Phase E — Preserve tenant-safe deep links and existing notification shell behavior
|
|
|
|
**Goal**: Reuse the current notification drawer and finding detail route without widening visibility.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| E.1 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Build finding action URLs with `FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant)` so links target the tenant panel explicitly |
|
|
| E.2 | Existing admin panel notification configuration | Rely on the already-configured `->databaseNotifications()` behavior in `AdminPanelProvider`; do not add polling, a new page, or a new notification surface |
|
|
| E.3 | Existing finding detail route behavior | Keep current route authorization authoritative so an operator who lost access after send time still receives the existing `404` or `403` outcome instead of leaked detail |
|
|
|
|
### Phase F — Protect event truth, routing, and link safety with focused regression coverage
|
|
|
|
**Goal**: Lock down event production, recipient precedence, alert-rule integration, and tenant-safe finding drilldown.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| F.1 | `apps/platform/tests/Feature/Findings/FindingsNotificationEventTest.php` | Cover assignment-event production, system-reopen event production, rapid reassignment and repeated automatic-reopen dedupe, due-soon and overdue evaluation windows, terminal-finding suppression, and one-per-due-cycle behavior |
|
|
| F.2 | `apps/platform/tests/Feature/Findings/FindingsNotificationRoutingTest.php` | Cover recipient precedence, same-user owner and assignee dedupe, owner-only assignment suppression, entitlement-loss suppression, and no direct delivery without a current eligible recipient |
|
|
| F.3 | `apps/platform/tests/Feature/Alerts/FindingsAlertRuleIntegrationTest.php` and `apps/platform/tests/Feature/Alerts/SlaDueAlertTest.php` | Cover alert-rule event-type option exposure, `ALERTS_VIEW` read versus `ALERTS_MANAGE` mutation boundaries, inherited Alerts v1 external-copy behavior including minimum severity, tenant scoping, cooldown, quiet hours, and dedupe, delivery-history labels and filters, the rule-free case where direct personal delivery still occurs, and non-regression of existing aggregate `sla_due` behavior |
|
|
| F.4 | `apps/platform/tests/Feature/Notifications/FindingNotificationLinkTest.php` | Cover database notification payload shape, explicit tenant-panel URLs, and finding-detail open behavior with correct tenant-safe `404` and `403` semantics |
|
|
| F.5 | `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` plus the focused Pest commands above | Run formatting and the narrowest proving suites before closing implementation | |