---
id: STATE-ACC-TRX-013
title: Month-end aging snapshot run
object: DOM-ACC-TRX-005
product_area: accounting
module: transactions
revision: 1
status: draft
design_intent: confirmed
origin: >-
  The snapshot architecture approved by Andrew on 2026-10-15, as adopted in
  services-accounting:docs/snapshots/ar-aging.md#The A/R Aging Snapshot
  (`accounting.ar_aging`)
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: not planned
    meaning: No capture version has been scheduled for this workspace and month.
  - name: planned
    meaning: The run ledger records a capture awaiting admission.
  - name: queued
    meaning: Admission has reserved execution capacity.
  - name: running
    meaning: Capture is generating results in one transaction or ordered chunks.
  - name: published
    meaning: Validated results are published as the current version.
  - name: superseded
    meaning: A later publication replaced this version while retaining its rows.
  - name: failed
    meaning: >-
      An attempt failed and may retry; rollback evidence covers
      single-transaction capture.
transitions:
  - from: not planned
    to: planned
    trigger: schedule the closed month
    guard: >-
      The month must be two hours past its workspace end and lack an initial
      planned version.
    side_effects:
      - save the run with exclusive month end as knowledge cutoff
    who: Snapshot planning.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/runs/snapshot-run-workflow.ts;
      services-accounting:modules/transactions/aws/snapshots.module.ts;
      services-accounting:modules/transactions/tests/integration/services/runs/ar-aging-pipeline.integration.spec.js#it
      plans nothing twice: a second pass creates no run, and the ledger holds
      one run per tenant
  - from: planned
    to: queued
    trigger: admit scheduled capture
    guard: Run and backfill capacity must permit admission.
    side_effects:
      - submit its durable execution job
    who: Snapshot dispatch.
    provenance: documented
    source: 'services-accounting:docs/snapshots/ar-aging.md#Operating it'
  - from: queued
    to: running
    trigger: start admitted capture
    guard: The run must have been admitted.
    side_effects:
      - start result generation
    who: The snapshot state machine.
    provenance: documented
    source: 'services-accounting:docs/snapshots/ar-aging.md#Operating it'
  - from: running
    to: published
    trigger: publish validated results
    guard: 'Item totals, financial facts, bucket assignments and signs must reconcile.'
    side_effects:
      - make the version current at publication
    who: Snapshot execution.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/runs/snapshot-run-workflow.ts;
      services-accounting:modules/transactions/aws/snapshots.module.ts;
      services-accounting:modules/transactions/tests/integration/services/runs/ar-aging-pipeline.integration.spec.js#it
      keeps version 1’s rows as they were captured, readable by its run
  - from: running
    to: failed
    trigger: record capture failure
    guard: Generation or a validation check fails.
    side_effects:
      - roll back single-transaction capture with chunk cleanup unproved
      - retain check results
      - 'schedule retry delays of fifteen minutes, one hour and four hours'
    who: Snapshot execution.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/runs/snapshot-run-workflow.ts;
      services-accounting:modules/transactions/aws/snapshots.module.ts;
      services-accounting:modules/transactions/tests/integration/services/runs/ar-aging-pipeline.integration.spec.js#it
      leaves none of the failed run’s rows behind: its transaction rolled back
  - from: failed
    to: queued
    trigger: retry an eligible attempt
    guard: Back-off must be due with fewer than four failures.
    side_effects:
      - admit the next attempt
    who: Snapshot dispatch.
    provenance: documented
    source: 'services-accounting:docs/snapshots/ar-aging.md#Validation'
  - from: published
    to: superseded
    trigger: replace the current version
    guard: A subsequent eligible run must publish.
    side_effects:
      - make the new version current and retain this run
    who: Snapshot publication following a rerun or backfill request.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/runs/snapshot-run-workflow.ts;
      services-accounting:modules/transactions/aws/snapshots.module.ts;
      services-accounting:modules/transactions/tests/integration/services/runs/ar-aging-pipeline.integration.spec.js#it
      keeps version 1’s rows as they were captured, readable by its run
invalid:
  - from: published
    to: running
    why: Publication is immutable; generating again requires another version.
  - from: failed
    to: published
    why: Only a successful later attempt can publish after a failed attempt.
concurrency: >-
  A workspace and month permit one active capture. Version-aware reads reject
  cursors invalidated by newer publication.
invariants:
  - INV-DOM-ACC-TRX-005-09
  - INV-DOM-ACC-TRX-005-10
  - INV-DOM-ACC-TRX-005-11
  - INV-DOM-ACC-TRX-005-12
evidence:
  - >-
    services-accounting at f47cb263: docs/snapshots/ar-aging.md;
    services/runs/snapshot-run-workflow.ts, nudge-dispatcher.ts;
    aws/snapshots.module.ts, logic/snapshot-run.step-function.ts;
    db/ar-snapshot-*.ts.
---
# Month-end aging snapshot run

## The Lifecycle

The run model describes planning, durable execution and publication rather than invoice state. Superseded captures retain their original results for historical reads.

```mermaid
stateDiagram-v2
  direction LR
  state "not planned" as s0
  state "planned" as s1
  state "queued" as s2
  state "running" as s3
  state "published" as s4
  state "superseded" as s5
  state "failed" as s6
  s0 --> s1 : schedule the closed month
  s1 --> s2 : admit scheduled capture
  s2 --> s3 : start admitted capture
  s3 --> s4 : publish validated results
  s3 --> s6 : record capture failure
  s6 --> s2 : retry an eligible attempt
  s4 --> s5 : replace the current version
```

## Transitions Explained

The transition rows identify the initiating action, eligibility checks, resulting records and responsible caller. They retain the pinned evidence for each path.

## What Cannot Happen

The invalid-transition rows describe final-state protection and unsupported paths; command eligibility remains distinct from the declared state vocabulary.

## Concurrency and Timing

A workspace and month permit one active capture. Version-aware reads reject cursors invalidated by newer publication. Planning runs every fifteen minutes, dispatch every minute, and initial capture waits two hours after month end. A lost execution without its failure step has no job reconciler.
