# Event & queue consistency audit

**Scope:** Laravel domain `event()` usage, `Event::listen` registration, job dispatch patterns, integration webhooks, dead-letter behavior.

---

## Executive summary

The platform uses **two “event planes”**:

1. **In-process Laravel events** (HR, Payroll, Exports, Tenancy) registered centrally in `AppServiceProvider` — **tight coupling** to listener classes but predictable.
2. **Integration events / webhooks** (`integration_events`, `DeliverWebhookJob`) — **separate** contract from domain events; versioning via topic strings (`*.v1`).

**Main risks:** synchronous listeners doing **notifications inside DB transactions** (Leave), **centralized listener registration** scaling cost, and **mixed dispatch styles** (`event()` vs `Event::dispatch()`).

---

## Event naming & versioning

| Layer | Convention | Consistency |
|-------|--------------|-------------|
| Domain events | `LeaveRequestHrApproved`, `PayrollPeriodFinalized`, … | Consistent **PascalCase + past tense** |
| Integration catalog | `employee.created.v1` style topics | Documented in `docs/integrations/event-catalog.md` + governance |

**Gap:** Not all domain occurrences emit integration events yet — integration plane is **partially wired** (by design during rollout).

---

## Listener registration (centralization)

`AppServiceProvider::boot()` wires many `Event::listen` lines explicitly.

**Pros:** Easy to grep; obvious ordering.

**Cons:** **Hidden coupling** — modules do not “own” their listener registration inside module service providers; onboarding requires knowing global wiring.

**Recommendation:** Long-term, migrate to **module `ServiceProvider`s** (or discovery conventions) to keep change blast radius local — **medium** priority.

---

## Retry behavior

| Mechanism | Pattern | Notes |
|-----------|---------|------|
| Queue jobs | Laravel retries / `release()` | Webhook job uses **`release(delay)`** with `WebhookRetryPolicy` — **good** |
| Biometric processing | `ProcessBiometricLogsJob` | Dispatched **after** DB transaction in ingest service — **good** |
| Exports | `ExportProcessorJob` | Standard dispatch after create |

**Risk:** Any job that mutates money or attendance must document **idempotency** — spot-check job handlers for “double run” safety.

---

## Queue safety patterns (observed)

- **Tenant context:** Jobs like `ProcessBiometricLogsJob` accept `tenantId` and (per prior design) should set `TenantContext` in `handle` — **verify** each new job follows the same template.
- **Locks:** Payroll snapshot generation uses `Cache::lock` — good **single-flight** guard.

---

## Dead-letter handling (integrations)

Webhook deliveries support **`dead_letter`** status, timestamps, and ops commands (`ops:webhooks:retry-failed`, inspect).

**Consistency:** Stronger than many Laravel-only apps; **align** domain exports / payroll jobs with similar **observable failure** patterns where business-critical.

---

## Action items (prioritized)

1. **High:** Ensure leave (and similar) notifications use **after-commit** semantics — avoids irreversible side effects on rolled-back transactions.
2. **Medium:** Document per-job **idempotency contract** in job class docblock.
3. **Medium:** Reduce `AppServiceProvider` listener wall — module providers.
4. **Low:** Normalize `event()` vs `Event::dispatch()` style for readability only.

---

## References

- `app/Providers/AppServiceProvider.php`
- `docs/audits/transaction-boundary-audit.md`
- `docs/governance/event-contracts.md`
