Alert and record a lost recurring-ticket occurrence on creation failure (#88)

RecurringTicketModel::claimForRun() deliberately advances next_run_at
before TicketModel::createTicket() runs, to prevent duplicate-ticket
floods if creation fails partway and the cron retries. The tradeoff:
if createTicket() then fails, that specific occurrence is gone forever
with no record anywhere an admin would normally look — the catch
block only wrote a line to stdout/the cron log.

Added recordMissedOccurrence(), called from both the "createTicket()
returned success:false" branch and the exception catch, which writes
an audit_log entry (entity_type='recurring_ticket', action_type='error')
and fires a new NotificationHelper::sendSystemAlert() — a generic
operational alert (unlike the ticket-specific notification methods,
it has no associated ticket) sent to the shared MATRIX_NOTIFY_USERS
list regardless of any per-event toggle, so a silently-skipped
recurring ticket surfaces immediately instead of requiring someone to
grep cron logs.

Verified against real MariaDB and a real webhook-capturing server:
calling the recorder writes the audit_log row with the failure reason
and schedule details, and fires the Matrix alert with the same
information.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lhz7pGMaoTfL5sdYS5XiKv
This commit is contained in:
2026-09-11 14:28:31 -04:00
co-authored by Claude Sonnet 5
parent 86ef91abcb
commit a4828c1b7b
2 changed files with 56 additions and 1 deletions
+17
View File
@@ -59,6 +59,23 @@ class NotificationHelper
// ─── Public event methods ─────────────────────────────────────────────────
/**
* Generic operational alert with no associated ticket (e.g. a recurring
* schedule whose ticket creation failed after its next_run_at was
* already advanced, so the missed occurrence has no other record an
* admin would normally see). Always sent to the shared
* MATRIX_NOTIFY_USERS list, regardless of any per-event notify toggle.
*/
public static function sendSystemAlert(string $message, array $context = []): void
{
self::fire(array_merge([
'event' => 'system_alert',
'message' => $message,
], $context, [
'notify_users' => self::notifyUsers(),
]));
}
/**
* New ticket created (manual or automated/API).
*