# Jobs & Queues Audit

> Auto-generated on 2026-03-16

## Table of Contents

- [1. Executive Summary](#1-executive-summary)
- [2. Queue Infrastructure](#2-queue-infrastructure)
- [3. Job Catalog](#3-job-catalog)
- [4. Dispatch Map](#4-dispatch-map)
- [5. Scheduled Tasks](#5-scheduled-tasks)
- [6. Event-Driven Listeners](#6-event-driven-listeners)
- [7. Queued Notifications & Mailables](#7-queued-notifications--mailables)
- [8. Findings & Recommendations](#8-findings--recommendations)
- [9. Recommended Action Plan](#9-recommended-action-plan)
- [10. Appendix](#10-appendix)

## 1. Executive Summary

- Total queued job classes discovered: 11
- Total queued listeners discovered: 1
- Queued notifications discovered: 0
- Queued mailables discovered: 0
- Queue connections configured: 6 (`sync`, `database`, `beanstalkd`, `sqs`, `redis`, `redis-long-processes`)
- Queue connections actively targeted by application code: default connection, `redis` (`activity-logs`), and `redis-long-processes` (`long-running`)
- Direct dispatch / batch / chain call sites matched in application code: 119
- Critical findings: 3

Top 3 priorities:

1. Stop dispatching queue work before database commits in payment flows.
2. Activate and right-size Horizon workers for `redis-long-processes` / `long-running`.
3. Align worker timeouts, job timeouts, and monitoring so long-running jobs do not die silently.

## 2. Queue Infrastructure

### Connection type and configuration

- `config/queue.php` uses `env('QUEUE_CONNECTION', 'sync')` as the default connection (`config/queue.php:16`).
- The audited `.env` currently sets `QUEUE_CONNECTION=sync` (`.env:21`). In this environment, any dispatch that does not explicitly override connection runs synchronously.
- `.env.example` expects `QUEUE_CONNECTION=redis` (`.env.example:21`), which matches the presence of Horizon and Redis queue configuration.
- Configured queue connections:
  - `sync`
  - `database` using `jobs` table, `retry_after=90`, `after_commit=false`
  - `beanstalkd`, `retry_after=90`, `after_commit=false`
  - `sqs`, `after_commit=false`
  - `redis`, queue `env('REDIS_QUEUE', 'default')`, `retry_after=150`, `after_commit=false`
  - `redis-long-processes`, queue `long-running`, `retry_after=7400`, no explicit `after_commit` flag (Laravel default behavior still does not give this app after-commit safety)
- Failed jobs are stored in the database with `database-uuids` driver and `failed_jobs` table (`config/queue.php:95-99`).
- Queue-related tables present in migrations:
  - `database/migrations/2019_08_19_000000_create_failed_jobs_table.php`
  - `database/migrations/2023_03_19_171854_create_jobs_table.php`
  - `database/migrations/2024_08_07_222150_create_job_batches_table.php`

### Worker / Horizon setup

- Horizon is installed and registered (`config/app.php:180`, `app/Providers/HorizonServiceProvider.php`).
- Horizon dashboard path is `horizon` and uses `web` middleware (`config/horizon.php:31`, `config/horizon.php:73`).
- Horizon access is gated to `admin@admin.com` in non-local environments (`app/Providers/HorizonServiceProvider.php:23-29`).
- `supervisor-1` is configured for `redis` queues `default` and `activity-logs` with `tries=1` and `timeout=60` in defaults, overridden to `timeout=90` in `production` and `local` (`config/horizon.php:182-195`, `config/horizon.php:207-228`).
- `supervisor-long-running` is defined for `redis-long-processes` / `long-running`, `tries=2`, `timeout=1800` (`config/horizon.php:197-204`), but it is not declared under either active environment block (`config/horizon.php:207-229`).
- No Supervisor or systemd unit files were found in the repository. The only documented worker startup path is manual `php artisan queue:work` / `php artisan horizon` guidance in `README.md:130-135`, `README.md:243-248`, and `README.md:305-308`.

### Environment variables

- `.env`
  - `QUEUE_CONNECTION=sync`
  - `CACHE_DRIVER=file`
  - `REDIS_CLIENT=predis`
  - `REDIS_HOST=redis`
  - `REDIS_PORT=6379`
- `.env.example`
  - `QUEUE_CONNECTION=redis`
  - `CACHE_DRIVER=file`
  - `REDIS_CLIENT` not set there, but Redis host defaults to `127.0.0.1`
  - `REDIS_HOST=127.0.0.1`
  - `REDIS_PORT=6379`
- No SQS queue is currently active in the audited environment; SQS / AWS keys are present as placeholders only.

## 3. Job Catalog

`Default` below means the class does not define `$queue` / `$connection`; the dispatch site or app default decides where it runs.

| Job Class | Queue | Connection | Tries | Timeout | Unique | Encrypted | Has failed() | Tested |
|-----------|-------|------------|-------|---------|--------|-----------|-------------|--------|
| `App\Jobs\ClearUserPhoneCodesJob` | Default | Default | Worker default | - | No | No | No | No |
| `App\Jobs\ClearUserTagsJob` | Default | Default | Worker default | - | No | No | No | No |
| `App\Jobs\DeleteUserJob` | Default | Default | Worker default | - | No | No | No | No |
| `App\Jobs\SendBarrierPhoneMessageJob` | Default / caller-defined | Default / caller-defined | 2 | 120 | No | No | No | No |
| `App\Jobs\SendSmsJob` | Default or `long-running` | Default or `redis-long-processes` | Worker default | 120 | No | No | No | Yes |
| `App\Jobs\SendTurnOFFSmsToDomophoneJob` | Default / caller-defined | Default / caller-defined | 2 | 120 | No | No | No | No |
| `App\Jobs\SendTurnOnSmsToDomophoneJob` | Default / caller-defined | Default / caller-defined | 2 | 120 | No | No | No | No |
| `App\Jobs\ToggleCoTagStatus` | Default | Default | Worker default | 3200 | No | No | Yes | No |
| `App\Jobs\TurnOffTagJob` | Default | Default | 3 | 120 | No | No | No | No |
| `App\Jobs\TurnOffTagJobForSelectedUsers` | `long-running` when queued by cron | `redis-long-processes` when queued by cron | 2 | 7200 | No | No | No | No |
| `App\Jobs\TurnOnTagJob` | Default | Default | 3 | 120 | No | No | No | No |

Notes:

- No job class implements `ShouldBeUnique`, `ShouldBeUniqueUntilProcessing`, or `ShouldBeEncrypted`.
- No job class defines a `middleware()` method.
- `Batchable` is used by `TurnOnTagJob`, `TurnOffTagJob`, `TurnOffTagJobForSelectedUsers`, `SendTurnOnSmsToDomophoneJob`, `SendTurnOFFSmsToDomophoneJob`, and `SendBarrierPhoneMessageJob`.
- `InteractsWithQueue` and `SerializesModels` are used by all queued job classes.
- Direct job-specific queue assertions exist only for `SendSmsJob` via `tests/Feature/OnlinePaymentConfirmationSmsTest.php` and `tests/Feature/SmsNotificationQueueDispatchTest.php`.

## 4. Dispatch Map

| Job | Primary dispatch sources | Mode | Connection / queue notes | Transaction risk |
|-----|---------------------------|------|---------------------------|------------------|
| `SendSmsJob` | `app/Services/PaymentConfirmationSmsService.php`, `app/Services/SmsNotificationQueueService.php`, `app/Http/Controllers/BlocksController.php`, `app/Http/Controllers/MessageController.php`, `app/Console/Commands/SendBlockArbitraryMessage.php` | Async helper / static dispatch | Many bulk paths override to `redis-long-processes` / `long-running`; payment receipt dispatch uses default connection | No direct transaction at dispatch site when called through `PaymentConfirmationSmsService`; other bulk loops are outside transactions |
| `TurnOnTagJob` | `CourierController`, `UsersController`, `TurnOnBlockTags` command, `CronController::statusManualOn`, payment controllers (`TbcController`, `BOGController`, `LibertyController`) | Async dispatch; included in batches | Usually default connection / queue | Yes in payment controllers (`TbcController:217`, `BOGController:254`, `BOGController:281`,  `LibertyController:230`, `LibertyController:243`) |
| `TurnOffTagJob` | `CourierController`, `UsersController`, `CheckUserEligibility` command, `CronController::processUsersWithCheckDate`, `CronController::statusManualOff` | Async dispatch; included in batches | Usually default connection / queue | No transaction found at dispatch sites |
| `TurnOffTagJobForSelectedUsers` | `CronController::processTagStatus` | Batched | Explicitly routed to `redis-long-processes` / `long-running` | No |
| `SendTurnOnSmsToDomophoneJob` | `UsersController`, `UserAccountService`, `CoworkerController`, payment controllers, nested fan-out from `SendBarrierPhoneMessageJob` | Async dispatch, batch item, chain item | Mixed: many default dispatches, some explicit `long-running` batch contexts | Yes in payment controllers (`TbcController`, `BOGController`, `LibertyController`) |
| `SendTurnOFFSmsToDomophoneJob` | `UsersController`, `UserAccountService`, `CoworkerController`, `CheckUserEligibility`, `CronController`, nested fan-out from `SendBarrierPhoneMessageJob` | Async dispatch, batch item, chain item | Mixed default and long-running contexts | No transaction found at dispatch sites |
| `SendBarrierPhoneMessageJob` | `UsersController::refreshUserPhone` | Async dispatch | Default connection / queue | No |
| `ToggleCoTagStatus` | `CoTagController`, `CoworkerController` | Async dispatch and one synchronous `dispatchSync()` in `CoTagController:74` | Default connection / queue | No |
| `ClearUserPhoneCodesJob` | `CoworkerController::destroy` | Chain item | Default connection / queue | No |
| `ClearUserTagsJob` | `CoworkerController::destroy` | Chain item | Default connection / queue | No |
| `DeleteUserJob` | `CoworkerController::destroy` | Chain item | Default connection / queue | No |

Additional dispatch characteristics:

- No use of `delay()`, `later()`, `dispatchAfterResponse()`, `afterResponse()`, or `afterCommit()` was found.
- No queued closures or `Bus::dispatch()` closure payloads were found.
- Bulk dispatch-in-loop patterns are common in `MessageController`, `BlocksController`, `UsersController`, `UserAccountService`, and `CoworkerController`.

## 5. Scheduled Tasks

| Scheduled task | Definition | Cron expression | Overlap protection | Single-server |
|----------------|------------|-----------------|--------------------|---------------|
| `tbilisi-group:create-utility-bills-for-blocks` | `app/Console/Kernel.php:28` | `0 5 1 * *` | No | No |
| `tbilisi-group:check-user-eligibility` | `app/Console/Kernel.php:29` | `0 4 * * *` | No | No |
| `tbilisi-group:sms-notifications-dispatch` | `app/Console/Kernel.php:30-32` | `0 9 * * *` | `withoutOverlapping(30)` | No |
| `tbilisi-group:monthly-export` | `app/Console/Kernel.php:33` | `0 0 1 * *` (`monthly()`) | No | No |
| `tags:export` | `app/Console/Kernel.php:34` | `0 0 1 * *` (`monthly()`) | No | No |
| `activity-logs:archive --days=365` | `app/Console/Kernel.php:37-39` | `30 2 * * *` | No | No |
| `audit:prune` | `app/Console/Kernel.php:42-45` | `0 2 * * 0` | No | No |
| `Spatie\Health\Commands\RunHealthChecksCommand` | `app/Console/Kernel.php:47` | `*/15 * * * *` | No | No |
| `Spatie\Health\Commands\ScheduleCheckHeartbeatCommand` | `app/Console/Kernel.php:48` | `*/15 * * * *` | No | No |

Missing scheduled queue operations:

- `horizon:snapshot` is not scheduled.
- `queue:monitor` is not scheduled.
- No scheduled task uses `onOneServer()`.

## 6. Event-Driven Listeners

| Listener | Events | Queue | Connection | Tries | Timeout | Middleware | Has failed() | Tested |
|----------|--------|-------|------------|-------|---------|------------|--------------|--------|
| `App\Listeners\LogActivityListener` | `UserUpdated`, `BillingStatusChanged`, `TagChanged`, `PhoneNumbersChanged`, `BillingCharged`, `BlockUpdated` | `activity-logs` | `redis` | Worker default (`supervisor-1` tries=1) | Worker default (`supervisor-1` timeout=60/90) | None | No | No |

Listener notes:

- Registered in `app/Providers/EventServiceProvider.php:24-46`.
- Trigger sites are spread across `UsersController`, `ActionController`, `BlocksController`, `TagController`, `CoTagController`, `UserAccountService`, and `BalanceBillingService`.
- The listener has no `InteractsWithQueue`, `Queueable`, `SerializesModels`, `middleware()`, `failed()`, `$tries`, `$maxExceptions`, `$backoff`, or `$timeout`.

## 7. Queued Notifications & Mailables

### Queued notifications

No issues found. No `app/Notifications` directory exists, and no queued notification class was found.

### Queued mailables

No issues found. No Mailable class implements `ShouldQueue`.

Observed non-queued mailables sent synchronously:

| Mailable | Sent from | Queue / channel configuration |
|----------|-----------|-------------------------------|
| `App\Mail\PhonesTurnedOnNotification` | `app/Http/Controllers/BlocksPhonesController.php:91` | Synchronous `Mail::send()` |
| `App\Mail\PhonesTurnedOffNotification` | `app/Http/Controllers/BlocksPhonesController.php:140` | Synchronous `Mail::send()` |
| `App\Mail\SendPhoneTurnOffCompletionEmail` | `app/Http/Controllers/CronController.php:153` | Synchronous `Mail::send()` |
| `App\Mail\SendTagsTurnOffCompletionEmail` | `app/Http/Controllers/CronController.php:202` | Synchronous `Mail::send()` |
| `App\Mail\TagOperationNotification` | `app/Http/Controllers/CronController.php:317`, `app/Http/Controllers/CronController.php:341` | Synchronous `Mail::send()` |
| `App\Mail\TagsRefreshCompletionEmail` | `app/Http/Controllers/UsersController.php:891` | Synchronous `Mail::send()` |

## 8. Findings & Recommendations

### 🔴 Critical

#### 1. Jobs are dispatched before database commit in payment flows, and queue connections are not after-commit safe

- **What**: Payment controllers dispatch jobs while a DB transaction is still open.
- **Where**: `app/Http/Controllers/TbcController.php:217-235`, `app/Http/Controllers/BOGController.php:254-285`, `app/Http/Controllers/LibertyController.php:230-247`, plus queue connections with `after_commit=false` in `config/queue.php:42`, `config/queue.php:51`, `config/queue.php:62`, `config/queue.php:71`.
- **Why it matters**: A queue worker can observe stale state or begin device / tag operations before the transaction is committed. If a transaction rolls back after dispatch, queued side effects may still run.
- **Fix**:

```php
TurnOnTagJob::dispatch($user)->afterCommit();
SendTurnOnSmsToDomophoneJob::dispatch(new PhoneService(), $user)->afterCommit();
```

```php
// config/queue.php
'redis' => [
    // ...
    'after_commit' => true,
],
'redis-long-processes' => [
    // ...
    'after_commit' => true,
],
```

#### 2. The `long-running` queue has a Horizon supervisor definition, but it is not enabled for any active environment

- **What**: `supervisor-long-running` exists only in Horizon defaults and is not listed under `production` or `local` environments.
- **Where**: `config/horizon.php:197-204`, `config/horizon.php:207-229`.
- **Why it matters**: Jobs explicitly routed to `redis-long-processes` / `long-running` may never be processed by Horizon even though controllers and services dispatch heavily to that queue.
- **Fix**:

```php
// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-1' => [
            'maxProcesses' => 1,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
            'balance' => 'simple',
            'processes' => 1,
            'timeout' => 90,
        ],
        'supervisor-long-running' => [
            'connection' => 'redis-long-processes',
            'queue' => ['long-running'],
            'balance' => 'simple',
            'processes' => 3,
            'tries' => 2,
            'timeout' => 7400,
        ],
    ],
];
```

#### 3. Worker timeouts are shorter than multiple job timeouts, so workers can kill jobs early

- **What**: Several jobs declare a higher `$timeout` than the Horizon worker that runs them.
- **Where**: `config/horizon.php:193`, `config/horizon.php:203`, `app/Jobs/SendSmsJob.php:21`, `app/Jobs/TurnOnTagJob.php:22-24`, `app/Jobs/TurnOffTagJob.php:22-24`, `app/Jobs/ToggleCoTagStatus.php:23`, `app/Jobs/TurnOffTagJobForSelectedUsers.php:24-29`.
- **Why it matters**: Horizon will terminate the worker before the job's declared timeout is reached. The highest-risk mismatch is `TurnOffTagJobForSelectedUsers` (`timeout=7200`) on a worker configured for `timeout=1800`.
- **Fix**:

```php
// config/horizon.php
'defaults' => [
    'supervisor-1' => [
        // keep this above the longest job allowed on the default queue
        'timeout' => 180,
    ],
    'supervisor-long-running' => [
        // keep this above TurnOffTagJobForSelectedUsers::$timeout
        'timeout' => 7400,
    ],
],
```

```php
// Alternative: move the heavy job to a dedicated queue and lower its timeout
public $timeout = 900;
```

### 🟡 Warning

#### 4. The audited environment defaults to synchronous processing for most dispatches

- **What**: `.env` sets `QUEUE_CONNECTION=sync`.
- **Where**: `.env:21`.
- **Why it matters**: Default-dispatched jobs, chains, and batches run inline in this environment, eliminating retries, failed job capture, queue back-pressure, and Horizon visibility.
- **Fix**:

```env
QUEUE_CONNECTION=redis
```

Use `sync` only in explicitly local or test contexts, and keep bulk or long-running work on Redis-backed queues.

#### 5. There is no global failure hook, dead-letter strategy, or queue-depth monitoring

- **What**: Failed jobs go to `failed_jobs`, but there is no `Queue::failing()` hook, `queue:monitor` schedule, `QueueBusy` listener, or Horizon notification routing.
- **Where**: `config/queue.php:95-99`, `app/Providers/AppServiceProvider.php`, `app/Providers/EventServiceProvider.php`, `app/Console/Kernel.php`.
- **Why it matters**: Jobs can fail or backlog silently unless somebody manually checks Horizon or the database.
- **Fix**:

```php
// app/Providers/AppServiceProvider.php
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    Queue::failing(function (JobFailed $event): void {
        logger()->error('Queued job failed', [
            'connection' => $event->connectionName,
            'job' => $event->job->resolveName(),
            'exception' => $event->exception->getMessage(),
        ]);
    });
}
```

```php
// app/Console/Kernel.php
$schedule->command('queue:monitor redis:default,redis:activity-logs,redis-long-processes:long-running --max=100')
    ->everyMinute();
```

#### 6. Horizon metrics and long-wait alerts are only partially configured

- **What**: `horizon:snapshot` is not scheduled, and `config/horizon.php` defines wait thresholds only for `redis:default`.
- **Where**: `config/horizon.php:86-88`, `app/Console/Kernel.php`.
- **Why it matters**: The Horizon dashboard will not have reliable metrics history, and queue pressure on `activity-logs` or `long-running` will not trigger long-wait detection.
- **Fix**:

```php
// app/Console/Kernel.php
$schedule->command('horizon:snapshot')->everyFiveMinutes();
```

```php
// config/horizon.php
'waits' => [
    'redis:default' => 60,
    'redis:activity-logs' => 60,
    'redis-long-processes:long-running' => 300,
],
```

#### 7. Network-heavy jobs do not use queue middleware for overlap control, throttling, or exception backoff

- **What**: No job or queued listener defines `middleware()`.
- **Where**: `app/Jobs/*.php`, `app/Listeners/LogActivityListener.php`.
- **Why it matters**: Device and SMS operations can overlap for the same user or device, retry aggressively, and create duplicated side effects under contention.
- **Fix**:

```php
use Illuminate\Queue\Middleware\ThrottlesExceptions;
use Illuminate\Queue\Middleware\WithoutOverlapping;

public function middleware(): array
{
    return [
        (new WithoutOverlapping('domophone-user-'.$this->user->id))->expireAfter(300),
        new ThrottlesExceptions(10, 300),
    ];
}
```

#### 8. Several jobs serialize service objects or large user collections instead of resolving dependencies at runtime

- **What**: `PhoneService` instances are passed into queued constructors, and `TurnOffTagJobForSelectedUsers` serializes chunks of user models.
- **Where**: `app/Jobs/SendTurnOnSmsToDomophoneJob.php:44-60`, `app/Jobs/SendTurnOFFSmsToDomophoneJob.php:40-55`, `app/Jobs/SendBarrierPhoneMessageJob.php:30-39`, `app/Jobs/TurnOffTagJobForSelectedUsers.php:35-38`.
- **Why it matters**: Queue payloads get larger, model snapshots grow stale, and serialization becomes harder to reason about. Constructor-injected services also fight Laravel's normal dependency injection pattern.
- **Fix**:

```php
class SendTurnOnSmsToDomophoneJob implements ShouldQueue
{
    public function __construct(
        public int $userId,
        public string $phoneOrUser = 'user',
        public ?string $phone = null,
    ) {}

    public function handle(PhoneService $phoneService): void
    {
        $user = User::findOrFail($this->userId);
        // ...
    }
}
```

#### 9. Bulk SMS and device operations are often dispatched one-by-one inside loops

- **What**: Several controllers and services dispatch jobs in tight loops instead of batching or chunking at a higher level.
- **Where**: `app/Http/Controllers/MessageController.php:279-311`, `app/Http/Controllers/BlocksController.php:818`, `app/Http/Controllers/BlocksController.php:868-870`, `app/Console/Commands/SendBlockArbitraryMessage.php:44`, `app/Http/Controllers/CoworkerController.php:99`, `app/Jobs/SendBarrierPhoneMessageJob.php:100-103`.
- **Why it matters**: Request time increases, dispatch overhead rises, and observability degrades because related work is scattered across many independent jobs.
- **Fix**:

```php
$jobs = collect($recipients)
    ->chunk(100)
    ->map(fn ($chunk) => new SendBulkSmsChunkJob($chunk->pluck('id')->all(), $text));

Bus::batch($jobs)->name('bulk-sms')->dispatch();
```

#### 10. Sensitive SMS content and phone codes are queued without encryption

- **What**: `SendSmsJob` payloads can contain account numbers and balance information; domophone jobs carry phone numbers and device codes.
- **Where**: `app/Jobs/SendSmsJob.php:31-37`, `app/Jobs/SendTurnOnSmsToDomophoneJob.php:44-60`, `app/Jobs/SendTurnOFFSmsToDomophoneJob.php:40-55`.
- **Why it matters**: Payloads may be readable in Redis, Horizon, or `failed_jobs`, which is unnecessary exposure for subscriber data and access-control commands.
- **Fix**:

```php
use Illuminate\Contracts\Queue\ShouldBeEncrypted;

class SendSmsJob implements ShouldQueue, ShouldBeEncrypted
{
    // ...
}
```

Apply the same pattern to domophone jobs that carry device-operation data.

#### 11. Direct queue coverage is sparse: only `SendSmsJob` has explicit queue assertions

- **What**: Tests assert `SendSmsJob` dispatch behavior, but no job-specific test references were found for the other 10 queued jobs or the queued listener.
- **Where**: `tests/Feature/OnlinePaymentConfirmationSmsTest.php`, `tests/Feature/SmsNotificationQueueDispatchTest.php`; no direct job test references for the remaining jobs in `tests/`.
- **Why it matters**: Queue regressions in tag toggling, domophone messaging, chained deletion, and queued activity logging can slip in without automated detection.
- **Fix**:

```php
public function test_status_manual_off_batches_tag_jobs(): void
{
    Bus::fake();

    $this->post(route('...'));

    Bus::assertBatched(function ($batch) {
        return collect($batch->jobs)->contains(fn ($job) => $job instanceof TurnOffTagJob);
    });
}
```

#### 12. cURL timeouts are set to zero inside SMS-related execution paths

- **What**: Both `SendSmsJob` and `PhoneService` set `CURLOPT_TIMEOUT => 0`.
- **Where**: `app/Jobs/SendSmsJob.php:97-103`, `app/Services/PhoneService.php:43-49`.
- **Why it matters**: The remote request can block indefinitely until the queue worker timeout or process manager kills the worker, reducing throughput and increasing retry ambiguity.
- **Fix**:

```php
curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_FOLLOWLOCATION => true,
]);
```

### 🟢 Suggestions

#### 13. Add Horizon tags for better queue observability

- **What**: No queued job or listener defines `tags()`.
- **Where**: `app/Jobs/*.php`, `app/Listeners/LogActivityListener.php`.
- **Why it matters**: Tagging by user, block, or operation makes Horizon much easier to use when investigating failures or long wait times.
- **Fix**:

```php
public function tags(): array
{
    return ['user:'.$this->user->id, 'job:turn-on-tag'];
}
```

#### 14. Batchable jobs do not check for batch cancellation

- **What**: Jobs using `Batchable` do not stop early when their batch is cancelled.
- **Where**: `app/Jobs/TurnOnTagJob.php`, `app/Jobs/TurnOffTagJob.php`, `app/Jobs/TurnOffTagJobForSelectedUsers.php`, `app/Jobs/SendTurnOnSmsToDomophoneJob.php`, `app/Jobs/SendTurnOFFSmsToDomophoneJob.php`, `app/Jobs/SendBarrierPhoneMessageJob.php`.
- **Why it matters**: Cancelled batches can continue making external device changes.
- **Fix**:

```php
public function handle(TagService $service): void
{
    if ($this->batch()?->cancelled()) {
        return;
    }

    $service->activateUserTags($this->user);
}
```

#### 15. `dispatchSync()` is used in a user-facing controller path for coworker tag removal

- **What**: `CoTagController::destroy()` uses synchronous queue dispatch.
- **Where**: `app/Http/Controllers/CoTagController.php:74`.
- **Why it matters**: The HTTP request waits on the full tag deactivation workflow and any remote device latency.
- **Fix**:

```php
Bus::chain([
    new ToggleCoTagStatus($user, $tag, 'off'),
    new DeleteCoworkerTagRecordJob($tag->id),
])->dispatch();
```

#### 16. Domophone jobs are large orchestration jobs with branching logic and `sleep()` calls

- **What**: `SendTurnOnSmsToDomophoneJob`, `SendTurnOFFSmsToDomophoneJob`, and `SendBarrierPhoneMessageJob` contain large per-model branches, nested loops, and multiple `sleep()` calls.
- **Where**: `app/Jobs/SendTurnOnSmsToDomophoneJob.php:63-250`, `app/Jobs/SendTurnOFFSmsToDomophoneJob.php:57-220`, `app/Jobs/SendBarrierPhoneMessageJob.php:41-108`.
- **Why it matters**: These jobs are hard to test, hard to tune, and consume worker time inefficiently.
- **Fix**:

```php
// Split by responsibility
new QueueK6PhoneRefreshJob($userId);
new QueueRtu5024PhoneRefreshJob($userId, $phone);
new QueueBarrierPhoneRefreshJob($userId, $phone, $shouldTurnOn);
```

## 9. Recommended Action Plan

1. Enable `afterCommit` behavior for queued work triggered from payment transactions, then update the four payment controllers to dispatch after commit.
2. Add `supervisor-long-running` to active Horizon environments and align its timeout above the heaviest job it is expected to run.
3. Reconcile Horizon worker timeouts with job-level `$timeout` values on both default and long-running queues.
4. Decide the intended non-test default queue driver, and set deployed environments to `redis` if asynchronous processing is required.
5. Add queue monitoring: `horizon:snapshot`, `queue:monitor`, and a global `Queue::failing()` hook.
6. Add middleware (`WithoutOverlapping`, throttling) to domophone and SMS jobs that touch the same subscribers or devices.
7. Slim job payloads by passing IDs / scalar inputs and resolving `PhoneService` / models inside `handle()`.
8. Add `ShouldBeEncrypted` to jobs carrying subscriber balance or access-control payloads.
9. Add direct queue tests for at least: payment-triggered tag/phone jobs, coworker deletion chain, cron status batches, and queued activity logging.
10. Add Horizon `tags()` methods and batch cancellation checks to improve operability.

## 10. Appendix

### Job definition details

#### `App\Jobs\ClearUserPhoneCodesJob`

- Path: `app/Jobs/ClearUserPhoneCodesJob.php`
- Traits: `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: no `$tries`, `$maxExceptions`, `$backoff`, or `$timeout`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\ClearUserTagsJob`

- Path: `app/Jobs/ClearUserTagsJob.php`
- Traits: `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: no `$tries`, `$maxExceptions`, `$backoff`, or `$timeout`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\DeleteUserJob`

- Path: `app/Jobs/DeleteUserJob.php`
- Traits: `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: no `$tries`, `$maxExceptions`, `$backoff`, or `$timeout`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\SendBarrierPhoneMessageJob`

- Path: `app/Jobs/SendBarrierPhoneMessageJob.php`
- Traits: `Batchable`, `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class; nested child jobs are dispatched from inside `handle()`
- Retry config: `$tries=2`, `$timeout=120`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\SendSmsJob`

- Path: `app/Jobs/SendSmsJob.php`
- Traits: `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class; frequently overridden to `redis-long-processes` / `long-running`
- Retry config: `$timeout=120`; no job-level `$tries`, `$maxExceptions`, or `$backoff`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\SendTurnOFFSmsToDomophoneJob`

- Path: `app/Jobs/SendTurnOFFSmsToDomophoneJob.php`
- Traits: `Batchable`, `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: `$tries=2`, `$timeout=120`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\SendTurnOnSmsToDomophoneJob`

- Path: `app/Jobs/SendTurnOnSmsToDomophoneJob.php`
- Traits: `Batchable`, `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: `$tries=2`, `$timeout=120`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\ToggleCoTagStatus`

- Path: `app/Jobs/ToggleCoTagStatus.php`
- Traits: `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: `$timeout=3200`; no job-level `$tries`, `$maxExceptions`, or `$backoff`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: `failed(Exception $exception)`

#### `App\Jobs\TurnOffTagJob`

- Path: `app/Jobs/TurnOffTagJob.php`
- Traits: `Batchable`, `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: `$tries=3`, `$timeout=120`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\TurnOffTagJobForSelectedUsers`

- Path: `app/Jobs/TurnOffTagJobForSelectedUsers.php`
- Traits: `Batchable`, `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class; routed to `redis-long-processes` / `long-running` in `CronController`
- Retry config: `$tries=2`, `$timeout=7200`, `$failOnTimeout=true`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Jobs\TurnOnTagJob`

- Path: `app/Jobs/TurnOnTagJob.php`
- Traits: `Batchable`, `Dispatchable`, `InteractsWithQueue`, `Queueable`, `SerializesModels`
- Queue / connection: not defined on class
- Retry config: `$tries=3`, `$timeout=120`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none

#### `App\Listeners\LogActivityListener`

- Path: `app/Listeners/LogActivityListener.php`
- Connection / queue: `$connection='redis'`, `$queue='activity-logs'`
- Retry config: no `$tries`, `$maxExceptions`, `$backoff`, or `$timeout`
- Uniqueness / encryption: none
- Middleware: none
- Failure handler: none
- Methods mapped from events: `handleUserUpdated`, `handleBillingStatusChanged`, `handleTagChanged`, `handlePhoneNumbersChanged`, `handleBillingCharged`, `handleBlockUpdated`

### Full list of dispatch sites (raw grep)

```text
app/Services/PaymentConfirmationSmsService.php:37:            SendSmsJob::dispatch($user->id, $text);
app/Jobs/SendBarrierPhoneMessageJob.php:101:                    dispatch(new SendTurnOnSmsToDomophoneJob($this->phoneService, $user, 'phone', $phone, null, 'yes', SendTurnOnSmsToDomophoneJob::BARRIER_OPERATION));
app/Jobs/SendBarrierPhoneMessageJob.php:103:                    dispatch(new SendTurnOFFSmsToDomophoneJob($this->phoneService, $user, 'phone', $code->code, null, SendTurnOFFSmsToDomophoneJob::BARRIER_OPERATION));
app/Services/UserAccountService.php:88:                            dispatch(new SendTurnOnSmsToDomophoneJob(
app/Services/UserAccountService.php:129:                            dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user, 'phone', $oldCodeValue));
app/Services/UserAccountService.php:144:                                Bus::chain([
app/Services/UserAccountService.php:146:                                ])->dispatch();
app/Services/UserAccountService.php:148:                                Bus::chain([
app/Services/UserAccountService.php:151:                                ])->dispatch();
app/Services/UserAccountService.php:154:                            dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user, 'phone', $oldCode));
app/Services/UserAccountService.php:175:                                dispatch(new SendTurnOnSmsToDomophoneJob(
app/Services/UserAccountService.php:222:                                dispatch(new SendTurnOnSmsToDomophoneJob(
app/Services/UserAccountService.php:274:                                Bus::batch([
app/Services/UserAccountService.php:282:                                })->dispatch();
app/Services/UserAccountService.php:284:                                dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user, 'phone', $codeValue));
app/Services/UserAccountService.php:319:                dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Services/UserAccountService.php:419:                    dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user, 'phone', $oldCodeRecord->code, null, SendTurnOFFSmsToDomophoneJob::BARRIER_OPERATION));
app/Services/UserAccountService.php:465:                dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'phone', $phone, null, 'yes', SendTurnOnSmsToDomophoneJob::BARRIER_OPERATION));
app/Services/UserAccountService.php:477:                dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user, 'phone', $code->code, null, SendTurnOFFSmsToDomophoneJob::BARRIER_OPERATION));
app/Services/UserAccountService.php:601:                    dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'phone', $phone));
app/Services/SmsNotificationQueueService.php:109:                    SendSmsJob::dispatch($user->id, $messageText)
app/Console/Commands/TurnOnBlockTags.php:52:                TurnOnTagJob::dispatch($user);
app/Console/Commands/SendBlockArbitraryMessage.php:44:                        dispatch(new SendSmsJob($user->id, $message, $phoneNumber));
app/Console/Commands/CheckUserEligibility.php:83:                    dispatch(new TurnOffTagJob($user));
app/Console/Commands/CheckUserEligibility.php:86:                    dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/CourierController.php:67:                dispatch(new TurnOnTagJob($user));
app/Http/Controllers/CourierController.php:71:                        dispatch(new TurnOnTagJob($user));
app/Http/Controllers/CourierController.php:73:                        dispatch(new TurnOffTagJob($user));
app/Http/Controllers/CourierController.php:76:                    dispatch(new TurnOffTagJob($user));
app/Http/Controllers/CourierController.php:80:            dispatch(new TurnOnTagJob($user));
app/Http/Controllers/CourierController.php:82:            dispatch(new TurnOffTagJob($user));
app/Http/Controllers/CronController.php:146:            Bus::batch($phoneJobs)
app/Http/Controllers/CronController.php:158:                ->dispatch();
app/Http/Controllers/CronController.php:196:            Bus::batch($tagJobs)
app/Http/Controllers/CronController.php:208:                ->dispatch();
app/Http/Controllers/CronController.php:284:                            dispatch(new TurnOffTagJob($user));
app/Http/Controllers/CronController.php:289:                            dispatch(new SendTurnOFFSmsToDomophoneJob(
app/Http/Controllers/CronController.php:315:        Bus::batch($jobs)->then(function (Batch $batch) use ($block) {
app/Http/Controllers/CronController.php:320:        })->dispatch();
app/Http/Controllers/CronController.php:339:        Bus::batch($jobs)->then(function (Batch $batch) use ($block) {
app/Http/Controllers/CronController.php:344:        })->dispatch();
app/Http/Controllers/CoTagController.php:35:            ToggleCoTagStatus::dispatch($coworker, $tag, 'on');
app/Http/Controllers/CoTagController.php:61:            ToggleCoTagStatus::dispatch($coTag->user, $coTag, 'on');
app/Http/Controllers/CoTagController.php:74:        ToggleCoTagStatus::dispatchSync($user, $tag, 'off');
app/Http/Controllers/CoTagController.php:86:        ToggleCoTagStatus::dispatch($tag->user, $tag, 'off');
app/Http/Controllers/CoTagController.php:93:        ToggleCoTagStatus::dispatch($tag->user, $tag, 'on');
app/Http/Controllers/BlocksPhonesController.php:89:        Bus::batch($jobs)->then(function (Batch $batch) use ($block) {
app/Http/Controllers/BlocksPhonesController.php:95:        })->dispatch();
app/Http/Controllers/BlocksPhonesController.php:138:        Bus::batch($jobs)->then(function (Batch $batch) use ($block) {
app/Http/Controllers/BlocksPhonesController.php:143:        })->dispatch();
app/Http/Controllers/LibertyController.php:230:                    dispatch(new TurnOnTagJob($user));
app/Http/Controllers/LibertyController.php:236:                    dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'user', null, null, 'no'));
app/Http/Controllers/LibertyController.php:243:                            dispatch(new TurnOnTagJob($user));
app/Http/Controllers/LibertyController.php:247:                            dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/CoworkerController.php:99:                    dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $coworker, 'user', null, [...$block->entrances], 'no'));
app/Http/Controllers/CoworkerController.php:170:                Bus::batch($phoneJobs)->then(function (Batch $batch) use ($coworker) {
app/Http/Controllers/CoworkerController.php:174:                })->dispatch();
app/Http/Controllers/CoworkerController.php:184:                Bus::batch($phoneJobs)->then(function (Batch $batch) {
app/Http/Controllers/CoworkerController.php:188:                })->dispatch();
app/Http/Controllers/CoworkerController.php:198:                Bus::batch($phoneJobs)->then(function (Batch $batch) {
app/Http/Controllers/CoworkerController.php:202:                })->dispatch();
app/Http/Controllers/CoworkerController.php:210:                        dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $coworker, 'user', null, [...$block->entrances], 'no'));
app/Http/Controllers/CoworkerController.php:217:                        dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $coworker, 'user', null, [...$block->entrances]));
app/Http/Controllers/CoworkerController.php:251:        Bus::chain($chain)->dispatch();
app/Http/Controllers/CoworkerController.php:261:            dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $coworker, 'user', null, [...$block->entrances], 'yes'));
app/Http/Controllers/CoworkerController.php:271:            dispatch(new ToggleCoTagStatus($coworker, $tag, 'on'));
app/Http/Controllers/CoworkerController.php:281:            dispatch(new ToggleCoTagStatus($coworker, $tag, 'on', $blocks));
app/Http/Controllers/CoworkerController.php:290:            dispatch(new ToggleCoTagStatus($coworker, $tag, 'off', $blocks));
app/Http/Controllers/BlocksController.php:818:                    SendSmsJob::dispatch($user->id, $message);
app/Http/Controllers/BlocksController.php:868:                    dispatch((new SendSmsJob($user->id, $text))
app/Http/Controllers/TbcController.php:217:                    dispatch(new TurnOnTagJob($user));
app/Http/Controllers/TbcController.php:223:                    dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'user', null, null, 'no'));
app/Http/Controllers/TbcController.php:230:                            dispatch(new TurnOnTagJob($user));
app/Http/Controllers/TbcController.php:234:                            dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/MessageController.php:132:        SendSmsJob::dispatch($user->id, $text);
app/Http/Controllers/MessageController.php:281:                            dispatch(new SendSmsJob($item->id, $text, $phoneNumber, Message::TYPE_CUSTOM))
app/Http/Controllers/MessageController.php:295:                        dispatch(new SendSmsJob($item->id, $text, $contact, Message::TYPE_CUSTOM))
app/Http/Controllers/MessageController.php:301:                    dispatch(new SendSmsJob($item->id, $text, $destination, Message::TYPE_CUSTOM))
app/Http/Controllers/MessageController.php:309:            dispatch(new SendSmsJob(null, $request->content, $phone, Message::TYPE_CUSTOM))
app/Http/Controllers/BOGController.php:254:                        dispatch(new TurnOnTagJob($user));
app/Http/Controllers/BOGController.php:260:                            dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'user', null, null, 'no'));
app/Http/Controllers/BOGController.php:281:                                dispatch(new TurnOnTagJob($user));
app/Http/Controllers/BOGController.php:285:                                dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:581:                Bus::batch([
app/Http/Controllers/UsersController.php:589:                })->dispatch();
app/Http/Controllers/UsersController.php:598:                dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'user', null, [$newEntrance]));
app/Http/Controllers/UsersController.php:622:                    dispatch(new SendTurnOffSmsToDomophoneJob(new PhoneService, $user, 'phone', $oldCode->code));
app/Http/Controllers/UsersController.php:670:                        dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'phone', $code->phone));
app/Http/Controllers/UsersController.php:675:                    dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user, 'phone'));
app/Http/Controllers/UsersController.php:741:                dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:755:                dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:764:                dispatch(new TurnOnTagJob($user));
app/Http/Controllers/UsersController.php:766:                    dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:769:                dispatch(new TurnOffTagJob($user));
app/Http/Controllers/UsersController.php:771:                    dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:778:            dispatch(new TurnOnTagJob($user));
app/Http/Controllers/UsersController.php:780:            dispatch(new TurnOffTagJob($user));
app/Http/Controllers/UsersController.php:786:                dispatch(new SendTurnOnSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:791:                dispatch(new SendTurnOFFSmsToDomophoneJob(new PhoneService, $user));
app/Http/Controllers/UsersController.php:888:        Bus::batch($jobs)
app/Http/Controllers/UsersController.php:897:            ->dispatch();
app/Http/Controllers/UsersController.php:941:                dispatch(new TurnOnTagJob($user));
app/Http/Controllers/UsersController.php:947:                        dispatch(new TurnOnTagJob($user));
app/Http/Controllers/UsersController.php:950:                        dispatch(new TurnOffTagJob($user));
app/Http/Controllers/UsersController.php:954:                    dispatch(new TurnOffTagJob($user));
app/Http/Controllers/UsersController.php:959:            dispatch(new TurnOnTagJob($user));
app/Http/Controllers/UsersController.php:971:            dispatch(new TurnOffTagJob($user));
app/Http/Controllers/UsersController.php:982:                dispatch(new SendBarrierPhoneMessageJob(new PhoneService, $user, true));
app/Http/Controllers/UsersController.php:984:                dispatch(new SendBarrierPhoneMessageJob(new PhoneService, $user, true));
app/Http/Controllers/UsersController.php:986:                dispatch(new SendBarrierPhoneMessageJob(new PhoneService, $user, false));
app/Http/Controllers/UsersController.php:998:                    dispatch(new SendTurnOnSmsToDomophoneJob(
app/Http/Controllers/UsersController.php:1008:                            dispatch(new SendTurnOnSmsToDomophoneJob(
app/Http/Controllers/UsersController.php:1016:                            dispatch(new SendTurnOFFSmsToDomophoneJob(
app/Http/Controllers/UsersController.php:1025:                        dispatch(new SendTurnOFFSmsToDomophoneJob(
app/Http/Controllers/UsersController.php:1035:                dispatch(new SendTurnOnSmsToDomophoneJob(
app/Http/Controllers/UsersController.php:1043:                dispatch(new SendTurnOFFSmsToDomophoneJob(
app/Http/Controllers/UsersController.php:1273:            dispatch((new SendSmsJob($user->id, $text))
```

### Event dispatch sites that feed the queued listener

```text
app/Services/BalanceBillingService.php:93:        event(new BillingCharged($user, 'subscription', (float) $subscriptionFee, (float) $oldBalance, (float) $user->balance));
app/Services/BalanceBillingService.php:164:            event(new BillingCharged($user, 'tag', (float) $tagCharges, (float) $oldBalance, (float) $user->balance, [
app/Services/BalanceBillingService.php:257:            event(new BillingCharged($user, 'phone', (float) $phoneCharges, (float) $oldBalance, (float) $user->balance, [
app/Http/Controllers/TagController.php:52:                event(new TagChanged($user, $causerId, 'added', ['tag' => $tag]));
app/Http/Controllers/TagController.php:86:                event(new TagChanged($tag->user, $causerId, 'modified', [
app/Http/Controllers/TagController.php:135:            event(new TagChanged($user, $causerId, 'removed', ['tag_id' => $tagId]));
app/Services/UserAccountService.php:354:            event(new PhoneNumbersChanged($user, $causerId, $added, $removed, $changed));
app/Http/Controllers/BlocksController.php:390:            event(new BlockUpdated($block, $causerId, 'balance', [
app/Http/Controllers/BlocksController.php:398:            event(new BillingStatusChanged(
app/Http/Controllers/BlocksController.php:443:                    event(new BlockUpdated($block, $causerId, 'additional_manager_removed', [
app/Http/Controllers/BlocksController.php:457:            event(new BlockUpdated($block, $causerId, 'chairman_change', [
app/Http/Controllers/BlocksController.php:468:                    event(new BlockUpdated($block, $causerId, 'additional_manager_added', [
app/Http/Controllers/ActionController.php:39:            event(new UserUpdated($user, $causerId, 'amount', [
app/Http/Controllers/ActionController.php:96:            event(new UserUpdated($user, $causerId, 'balance', [
app/Http/Controllers/ActionController.php:126:            event(new UserUpdated($user, $causerId, 'amount', [
app/Http/Controllers/CoTagController.php:34:            event(new TagChanged($coworker, $causerId, 'added', ['tag' => $tag]));
app/Http/Controllers/CoTagController.php:60:            event(new TagChanged($coTag->user, $causerId, 'updated', ['tag' => $coTag]));
app/Http/Controllers/CoTagController.php:78:            event(new TagChanged($user, $causerId, 'removed', ['tag_id' => $tagId]));
app/Http/Controllers/UsersController.php:801:            event(new UserUpdated($user, $causerId, 'name', [
app/Http/Controllers/UsersController.php:807:            event(new UserUpdated($user, $causerId, 'phone', [
app/Http/Controllers/UsersController.php:813:            event(new UserUpdated($user, $causerId, 'balance', [
app/Http/Controllers/UsersController.php:820:            event(new UserUpdated($user, $causerId, 'phone_balance', [
app/Http/Controllers/UsersController.php:826:            event(new UserUpdated($user, $causerId, 'tag_debt', [
app/Http/Controllers/UsersController.php:832:            event(new UserUpdated($user, $causerId, 'amount', [
app/Http/Controllers/UsersController.php:838:            event(new BillingStatusChanged(
app/Http/Controllers/UsersController.php:845:            event(new BillingStatusChanged(
```

### Raw configuration dumps

#### `config/queue.php` excerpt

```php
    'default' => env('QUEUE_CONNECTION', 'sync'),

    /*
    |--------------------------------------------------------------------------
    | Queue Connections
    |--------------------------------------------------------------------------
    |
    | Here you may configure the connection information for each server that
    | is used by your application. A default configuration has been added
    | for each back-end shipped with Laravel. You are free to add more.
    |
    | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
    |
    */

    'connections' => [

        'sync' => [
            'driver' => 'sync',
        ],

        'database' => [
            'driver' => 'database',
            'table' => 'jobs',
            'queue' => 'default',
            'retry_after' => 90,
            'after_commit' => false,
        ],

        'beanstalkd' => [
            'driver' => 'beanstalkd',
            'host' => 'localhost',
            'queue' => 'default',
            'retry_after' => 90,
            'block_for' => 0,
            'after_commit' => false,
        ],

        'sqs' => [
            'driver' => 'sqs',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
            'queue' => env('SQS_QUEUE', 'default'),
            'suffix' => env('SQS_SUFFIX'),
            'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
            'after_commit' => false,
        ],

        'redis' => [
            'driver' => 'redis',
            'connection' => 'default',
            'queue' => env('REDIS_QUEUE', 'default'),
            'retry_after' => 150,
            'block_for' => null,
            'after_commit' => false,
        ],

        'redis-long-processes' => [
            'driver' => 'redis',
            'connection' => 'default',
            'queue' => 'long-running',
            'retry_after' => 7400,
            'block_for' => null,
        ],

    ],

    /*
    |--------------------------------------------------------------------------
    | Failed Queue Jobs
    |--------------------------------------------------------------------------
    |
    | These options configure the behavior of failed queue job logging so you
    | can control which database and table are used to store the jobs that
    | have failed. You may change them to any database / table you wish.
    |
    */

    'failed' => [
        'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
        'database' => env('DB_CONNECTION', 'mysql'),
        'table' => 'failed_jobs',
    ],
```

#### `config/horizon.php` excerpt

```php
    'domain' => env('HORIZON_DOMAIN'),

    /*
    |--------------------------------------------------------------------------
    | Horizon Path
    |--------------------------------------------------------------------------
    |
    | This is the URI path where Horizon will be accessible from. Feel free
    | to change this path to anything you like. Note that the URI will not
    | affect the paths of its internal API that aren't exposed to users.
    |
    */

    'path' => env('HORIZON_PATH', 'horizon'),

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Connection
    |--------------------------------------------------------------------------
    |
    | This is the name of the Redis connection where Horizon will store the
    | meta information required for it to function. It includes the list
    | of supervisors, failed jobs, job metrics, and other information.
    |
    */

    'use' => 'default',

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Prefix
    |--------------------------------------------------------------------------
    |
    | This prefix will be used when storing all Horizon data in Redis. You
    | may modify the prefix when you are running multiple installations
    | of Horizon on the same server so that they don't have problems.
    |
    */

    'prefix' => env(
        'HORIZON_PREFIX',
        Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
    ),

    /*
    |--------------------------------------------------------------------------
    | Horizon Route Middleware
    |--------------------------------------------------------------------------
    |
    | These middleware will get attached onto each Horizon route, giving you
    | the chance to add your own middleware to this list or change any of
    | the existing middleware. Or, you can simply stick with this list.
    |
    */

    'middleware' => ['web'],

    /*
    |--------------------------------------------------------------------------
    | Queue Wait Time Thresholds
    |--------------------------------------------------------------------------
    |
    | This option allows you to configure when the LongWaitDetected event
    | will be fired. Every connection / queue combination may have its
    | own, unique threshold (in seconds) before this event is fired.
    |
    */

    'waits' => [
        'redis:default' => 60,
    ],

    /*
    |--------------------------------------------------------------------------
    | Job Trimming Times
    |--------------------------------------------------------------------------
    |
    | Here you can configure for how long (in minutes) you desire Horizon to
    | persist the recent and failed jobs. Typically, recent jobs are kept
    | for one hour while all failed jobs are stored for an entire week.
    |
    */

    'trim' => [
        'recent' => 60,
        'pending' => 60,
        'completed' => 60,
        'recent_failed' => 10080,
        'failed' => 10080,
        'monitored' => 10080,
    ],

    /*
    |--------------------------------------------------------------------------
    | Silenced Jobs
    |--------------------------------------------------------------------------
    |
    | Silencing a job will instruct Horizon to not place the job in the list
    | of completed jobs within the Horizon dashboard. This setting may be
    | used to fully remove any noisy jobs from the completed jobs list.
    |
    */

    'silenced' => [
        // App\Jobs\ExampleJob::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Metrics
    |--------------------------------------------------------------------------
    |
    | Here you can configure how many snapshots should be kept to display in
    | the metrics graph. This will get used in combination with Horizon's
    | `horizon:snapshot` schedule to define how long to retain metrics.
    |
    */

    'metrics' => [
        'trim_snapshots' => [
            'job' => 24,
            'queue' => 24,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Fast Termination
    |--------------------------------------------------------------------------
    |
    | When this option is enabled, Horizon's "terminate" command will not
    | wait on all of the workers to terminate unless the --wait option
    | is provided. Fast termination can shorten deployment delay by
    | allowing a new instance of Horizon to start while the last
    | instance will continue to terminate each of its workers.
    |
    */

    'fast_termination' => false,

    /*
    |--------------------------------------------------------------------------
    | Memory Limit (MB)
    |--------------------------------------------------------------------------
    |
    | This value describes the maximum amount of memory the Horizon master
    | supervisor may consume before it is terminated and restarted. For
    | configuring these limits on your workers, see the next section.
    |
    */

    'memory_limit' => 64,

    /*
    |--------------------------------------------------------------------------
    | Queue Worker Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may define the queue worker settings used by your application
    | in all environments. These supervisors and settings handle all your
    | queued jobs and will be provisioned by Horizon during deployment.
    |
    */

    'defaults' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'activity-logs'],
            'balance' => 'auto',
            'autoScalingStrategy' => 'time',
            'maxProcesses' => 1,
            'maxTime' => 0,
            'maxJobs' => 0,
            'memory' => 128,
            'tries' => 1,
            'timeout' => 60,
            'nice' => 0,
        ],

        'supervisor-long-running' => [
            'connection' => 'redis-long-processes',
            'queue' => 'long-running',
            'balance' => 'simple',
            'processes' => 3,
            'tries' => 2,
            'timeout' => 1800,
        ],
    ],

    'environments' => [
        'production' => [
            'supervisor-1' => [
                'maxProcesses' => 1,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
                'balance' => 'simple',
                'processes' => 1,
                'timeout' => 90,
            ],
        ],

        'local' => [
            'supervisor-1' => [
                'maxProcesses' => 1,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
                'balance' => 'simple',
                'processes' => 1,
                'timeout' => 90,
            ],
        ],
    ],
```

#### Queue-related `.env` values

```env
17:BROADCAST_DRIVER=log
18:CACHE_DRIVER=file
19:REDIS_CLIENT=predis
21:QUEUE_CONNECTION=sync
27:REDIS_HOST=redis
28:REDIS_PASSWORD=null
29:REDIS_PORT=6379
40:AWS_ACCESS_KEY_ID=
41:AWS_SECRET_ACCESS_KEY=
42:AWS_DEFAULT_REGION=us-east-1
43:AWS_BUCKET=
44:AWS_USE_PATH_STYLE_ENDPOINT=false
```

#### Queue-related `.env.example` values

```env
18:BROADCAST_DRIVER=log
19:CACHE_DRIVER=file
21:QUEUE_CONNECTION=redis
27:REDIS_HOST=127.0.0.1
28:REDIS_PASSWORD=null
29:REDIS_PORT=6379
40:AWS_ACCESS_KEY_ID=
41:AWS_SECRET_ACCESS_KEY=
42:AWS_DEFAULT_REGION=us-east-1
43:AWS_BUCKET=
44:AWS_USE_PATH_STYLE_ENDPOINT=false
```
