feat(workflow): document-wait task + 30-day timeout cancellation (S-10a, closes #102) #105

Merged
not merged 23 commits from feat/102-document-wait-timeout into main 2026-07-20 09:42:03 +00:00
Showing only changes of commit 07139324a3 - Show all commits
@@ -12,9 +12,11 @@ namespace Big.Application;
public sealed class ExpireRegistrationWorker(IRegistrationStore store)
{
/// <summary>
/// Process the job. Idempotent: a redelivered job whose registration is already VERLOPEN is a
/// no-op — not persisted again (§8.6, at-least-once delivery). An unknown registration is an error:
/// it throws, leaving the job un-completed for Flowable to redeliver.
/// Process the job. Idempotent and tolerant of races (§8.6, at-least-once delivery): a job whose
/// registration is already resolved — a redelivered expiry (VERLOPEN), or one withdrawn/decided
/// while it waited (INGETROKKEN/INGESCHREVEN/AFGEWEZEN) — is a no-op, so the job still completes
/// rather than throwing into a redelivery loop. Only a still-open registration is expired. An
/// unknown registration is an error: it throws, leaving the job un-completed for Flowable to redeliver.
/// </summary>
public async Task HandleAsync(RegistratieVerlopenJob job, CancellationToken ct = default)
{
@@ -24,8 +26,9 @@ public sealed class ExpireRegistrationWorker(IRegistrationStore store)
?? throw new InvalidOperationException(
$"No registration {job.RegistrationId} for RegistratieVerlopen job {job.JobId}.");
// A redelivered job whose registration is already VERLOPEN completes without persisting again.
if (registration.Status == RegistrationStatus.Verlopen)
// Only a still-open registration lapses; an already-resolved one (expired, or withdrawn/decided
// while it waited) is left untouched so the job can complete without violating the aggregate.
if (registration.Status is not (RegistrationStatus.Ingediend or RegistrationStatus.InBehandeling))
return;
registration.Expire();