---
id: WF-ACC-BKS-008
title: 'Claim, Deliver and Record Scheduled Work'
trigger: The minute rule invokes Accounting’s dispatcher.
product_area: accounting
module: books
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
inference: []
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
implements:
  - CAP-ACC-BKS-018
  - CAP-ACC-BKS-010
moves:
  - STATE-ACC-BKS-004
crosses: []
idempotency: >-
  A unique schedule-occurrence key identifies one run row. Terminal or missing
  runs do no work on delivery; queued and running rows can execute again. Owning
  handlers must supply their own repeat safety.
concurrency: >-
  The dispatcher has reserved concurrency one and skips locked schedule rows.
  The consumer event source permits two concurrent invocations, without
  preventing duplicate messages from entering the same unfinished handler.
hops:
  - 'n': 1
    actor_or_service: Platform scheduler rule
    action: Invoke the dispatcher on the fixed minute cadence.
    anchor: >-
      services-accounting:modules/maintenance/aws/scheduling.module.ts:schedulingModule
    seam: schedule
    on_failure: >-
      A later minute can try again; deployed silence monitoring detects missing
      dispatcher activity.
    left_inconsistent: Due work may wait beyond its intended minute.
    observer: Operations staff through dispatcher silence monitoring
    provenance: observed
    source: >-
      services-accounting:modules/maintenance/aws/scheduling.module.ts:schedulingModule
  - 'n': 2
    actor_or_service: Schedule dispatcher
    action: >-
      Claim at most two hundred due schedules or explicitly released runs;
      select the latest due occurrence, record skipped backlog and advance
      future timing atomically.
    anchor: 'services-accounting:modules/maintenance/aws/lambda/DISPATCHER.ts:handler'
    seam: sync
    on_failure: Roll back the claim transaction so eligible timing remains due.
    left_inconsistent: No claim or timing change commits from the failed transaction.
    observer: dispatcher logs
    provenance: test
    source: >-
      services-accounting:modules/maintenance/aws/lambda/DISPATCHER.ts:handler;
      services-accounting:package-lock.json
  - 'n': 3
    actor_or_service: Schedule dispatcher
    action: Publish one queue message for each committed claimed run.
    anchor: 'services-accounting:modules/maintenance/aws/lambda/DISPATCHER.ts:handler'
    seam: queue
    on_failure: >-
      Release explicitly refused messages for a later pass; a crash before
      publication or release can strand a committed offered run.
    left_inconsistent: >-
      A run can remain queued and marked offered without a corresponding
      message.
    observer: dispatcher logs; queue age alarms
    provenance: documented
    source: >-
      services-accounting:modules/maintenance/aws/lambda/DISPATCHER.ts:handler;
      services-accounting:package-lock.json
  - 'n': 4
    actor_or_service: Schedule consumer
    action: >-
      Enter the run’s workspace and execute its registered work kind; terminal
      or absent runs are acknowledged without execution.
    anchor: 'services-accounting:modules/maintenance/aws/lambda/CONSUMER.ts:handler'
    seam: queue
    on_failure: >-
      Settle known refusals and unknown work kinds immediately; unexpected
      errors request redelivery, with final-receive acknowledgement only after
      failure is recorded.
    left_inconsistent: Previously committed handler effects are not undone by the scheduler.
    observer: Recorded execution history and the latest schedule summary
    provenance: test
    source: >-
      services-accounting:modules/maintenance/tests/integration/services/scheduling/schedules-end-to-end.integration.spec.js#records
      the run succeeded, with the batch it opened, the recurring batch’s active
      batch, and the whole plan
  - 'n': 5
    actor_or_service: Batch module (owning handler)
    action: >-
      A roll or automatic close can submit the closed batch’s posting job
      through the owning batch handler.
    anchor: >-
      services-accounting:modules/maintenance/aws/scheduling.module.ts:schedulingModule
    seam: state_machine
    on_failure: >-
      The batch handler and posting job apply their own refusal and recovery
      behavior.
    left_inconsistent: A batch whose posting submission failed can remain closed and unposted.
    observer: the batch's posting job status
    provenance: documented
    source: >-
      services-accounting:modules/maintenance/aws/scheduling.module.ts:schedulingModule;
      services-accounting:modules/batches/services/auto-post/automatic-posting.ts:startAutomaticPosting
  - 'n': 6
    actor_or_service: Schedule consumer
    action: >-
      Record the terminal result once, update the failure count and disable an
      enabled schedule after five consecutive recorded failures.
    anchor: 'services-accounting:modules/maintenance/aws/lambda/CONSUMER.ts:handler'
    seam: sync
    on_failure: >-
      A failure to record can redeliver the message and repeat unfinished
      handler effects.
    left_inconsistent: A completed effect can still have an unfinished history row.
    observer: Execution history and the schedule details returned by its owner
    provenance: test
    source: >-
      services-accounting:modules/maintenance/tests/integration/services/scheduling/schedules-end-to-end.integration.spec.js#pauses
      the schedule after the fifth failure in a row
  - 'n': 7
    actor_or_service: Dead-letter queue
    action: >-
      Retain malformed messages or failure-recording errors that exhaust queue
      delivery.
    anchor: >-
      services-accounting:modules/maintenance/aws/scheduling.module.ts:schedulingModule
    seam: queue
    on_failure: No automatic business recovery is established by dead-letter storage.
    left_inconsistent: >-
      Malformed delivery may have no run identity; unrecorded failure can leave
      a run unfinished.
    observer: production queue-age alarms
    provenance: observed
    source: >-
      services-accounting:modules/maintenance/aws/scheduling.module.ts:schedulingModule
branches:
  - condition: Attempt a roll outside calendar coverage
    path: >-
      The handler refuses the uncovered date and records failure rather than
      generating calendar.
  - condition: Encounter the tested spring clock gap
    path: >-
      The scheduled 2:30 AM occurrence shifts to 3:30 AM and retains its local
      occurrence date.
waits:
  - Minute cadence and a bounded pass can delay a due claim.
  - Consumer capacity can leave messages waiting in the queue.
evidence:
  - >-
    services-accounting at f47cb263:
    modules/maintenance/aws/scheduling.module.ts,
    aws/logic/schedule-workers.lambda.ts, aws/lambda/DISPATCHER.ts, CONSUMER.ts;
    services/scheduled-work/accounting-scheduled-work.ts; db/schedules.table.ts
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-AVL-001, TC-AVL-004, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005]
---

# Claim, Deliver and Record Scheduled Work

## What It Does

Accounting coordinates recurring timing through schedule rows, one dispatcher and a queue consumer. The dispatcher chooses the latest due occurrence and can skip older backlog. Registered work covers recurring rolls, automatic close, Reconciliation sweeps and recurring Billing preparation. History records execution results and pause reasons.

## The Flow

```mermaid
flowchart TD
  minute["Minute dispatch"] --> claim["Commit a bounded claim and advance timing"]
  claim --> publish["Offer run messages"]
  publish -->|accepted| queue["Schedule queue"]
  publish -->|explicit refusal| release["Release for a later pass"]
  publish -->|interrupted| stranded["Offered run may remain undelivered"]
  queue --> consume["Start an unfinished run"]
  consume -->|terminal or absent| ack["Acknowledge without work"]
  consume -->|queued or running| handler["Invoke owning work; duplicate execution remains possible"]
  handler -->|roll or close| posting["Prepare posting and attempt its post-commit start"]
  handler --> finish["Record success, skip or expected refusal"]
  posting --> finish
  handler -->|unexpected fault| retry["Request redelivery until final failure is recorded"]
  retry -->|another receive| consume
  retry -->|recorded final failure| finish
  finish --> ack
  finish --> summary["Update history and consecutive failures"]
  summary -->|fifth failure while enabled| pause["Pause future scheduling"]
  queue -->|malformed or unrecordable exhausted failure| dead["Dead-letter queue"]
```

## When Something Fails

Claim rollback leaves timing due. Explicit publish refusals are released, but an interruption between claim and publication can leave an offered run without delivery. Terminal work is acknowledged; unfinished work may execute again, including concurrently on duplicate delivery. Expected refusals settle immediately, while unexpected errors retry. On the final receive the consumer records failure and acknowledges it; malformed messages and failures that cannot be recorded can reach the dead-letter queue.

The scheduler does not compensate handler effects. Failure counts pause an enabled schedule after five recorded failures, with no attention notice. An invalid saved definition also pauses. Handler repeat safety and recovery of a closed unposted batch remain the owning module’s concerns.

## Open Design Questions

Implementation preparation needs a delivery-recovery decision for stranded claims and an explicit idempotency contract for each handler. Billing’s scheduling ownership and pause notification also remain open.
