---
id: STATE-ACC-BAT-001
title: Batch lifecycle
object: DOM-ACC-BAT-001
product_area: accounting
module: batches
revision: 1
status: draft
design_intent: confirmed
origin: >-
  Andrew's UI brief of 2026-10-15 (status only through named actions), recorded
  at
  services-accounting:docs/migrations/transaction-processing/batches/README.md
  ("Status as actions"); D13 (Andrew, 2026-10-16, automatic posting); Reporting
  D7 (Andrew, 2026-10-21, unposting returns the batch to Open)
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: open
    meaning: >-
      Available for intake and permitted corrections; ordinary persistence
      allows automatic-close configuration in this state.
  - name: closed
    meaning: Intake has ended and accounting completion is waiting.
  - name: verified
    meaning: Retained readiness vocabulary with no established native producer.
  - name: posting
    meaning: >-
      The engine working state, entered and completed inside the same
      transaction.
  - name: posted
    meaning: >-
      A current ledger generation exists and document batch movement is
      protected.
  - name: downloaded
    meaning: >-
      Retained external-delivery vocabulary without an implemented native
      producer.
  - name: error
    meaning: >-
      Document generation failed in the latest attempt; eligible intake remains
      possible.
  - name: archived
    meaning: A retired collection whose ordinary details are protected from editing.
  - name: deleted
    meaning: >-
      The collection row has been removed, with restrictive references checked
      first.
transitions:
  - from: open
    to: closed
    trigger: >-
      A manual close, due automatic close or scheduled predecessor retirement
      runs.
    guard: >-
      An ordinary Open batch passes the applicable update lock and system-type
      checks.
    side_effects:
      - >-
        Clear the close time; manual close removes the schedule, while scheduled
        close retains its completed run schedule.
      - Publish posting attention only when enabled.
      - Scheduled work also prepares a posting job and starts it after commit.
    who: Finance staff with update permission or scheduled Accounting work
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batches.integration.spec.js#removes
      the schedule and the time when the batch is closed
  - from: open
    to: posting
    trigger: Execution begins for an Open batch.
    guard: >-
      The requested batch passes status, edit-lock, system-type and open-period
      checks.
    side_effects:
      - Remove errors carried from an earlier attempt.
    who: The posting execution task
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/engine/post-batches.ts:postBatches
  - from: closed
    to: posting
    trigger: Execution begins for a Closed batch.
    guard: The same ordinary posting checks apply.
    side_effects:
      - Remove errors carried from an earlier attempt.
    who: The posting execution task
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/engine/post-batches.ts:postBatches
  - from: error
    to: posting
    trigger: Execution retries an Error batch after its cause is corrected.
    guard: The batch passes the ordinary posting checks.
    side_effects:
      - Discard previous errors before collecting this attempt outcomes.
    who: The posting execution task
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/unit/rules/batch-repost-rules.spec.js#admits
      an open, a closed and an errored batch
  - from: verified
    to: posting
    trigger: A directly submitted job executes a Verified batch.
    guard: The engine admits this carried state; other posting checks still apply.
    side_effects:
      - Clear carried errors during the posting check.
    who: The posting execution task
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/rules/batch-posting-rules.ts:ensureBatchesCanBePosted
  - from: posting
    to: posted
    trigger: Generation reaches a successful outcome.
    guard: >-
      Documents produced valid accounting and the generation has no failed
      dependency.
    side_effects:
      - >-
        Persist generation history, ledger lines, revenue plans and resolved
        document snapshots.
      - >-
        Retain recorded amounts, counts, dependencies, completion time and job
        reference.
      - Resolve enabled posting attention as posted.
    who: The posting execution task
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/posting/rebuilt-posting-edges.integration.spec.js#posts
      January, puts April in error naming the application whose credit's batch
      is not posted, and May in error as April's dependent
  - from: posting
    to: error
    trigger: Document generation or dependency resolution produces a batch failure.
    guard: No additional transition guard applies.
    side_effects:
      - >-
        Retain the document failures while omitting ledger and snapshot writes
        for that batch.
      - Refresh enabled attention for the waiting collection.
    who: The posting execution task
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/posting/rebuilt-posting-edges.integration.spec.js#posts
      January, puts April in error naming the application whose credit's batch
      is not posted, and May in error as April's dependent
  - from: posting
    to: deleted
    trigger: Generation identifies an empty collection.
    guard: Deletion must still satisfy restrictive database references.
    side_effects:
      - Resolve enabled attention for the removed collection.
    who: The posting execution task
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/posting/rebuilt-posting-edges.integration.spec.js#posts
      February with its one entry, and June is gone (RS-1116)
  - from: posted
    to: open
    trigger: An unposting request or permitted Unpost action executes.
    guard: >-
      The ordinary batch passes update-lock and period checks, with no
      successfully read Posted or Downloaded dependent outside the request.
    side_effects:
      - Remove current ledger rows and retire Open revenue plans.
      - Mark the generation reversed by the recovery job.
      - 'Clear generation statistics, amounts, dependencies and completion time.'
      - Resolve enabled attention; later posting creates a new generation.
    who: An administrator using unposting permission through a recovery job
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/e2e/batch-postings.e2e.spec.js#unposts
      a posted batch as a durable job: the batch is open again and its entries
      are deleted
  - from: downloaded
    to: open
    trigger: An unposting job executes a Downloaded batch.
    guard: The ordinary unposting checks apply.
    side_effects:
      - Perform the same generation retirement as for Posted.
    who: The unposting execution task
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/rules/batch-rules.ts:assertBatchCanBeUnposted
  - from: posting
    to: open
    trigger: Recovery executes for a stale Posting row.
    guard: >-
      The row has remained unchanged for more than an hour and passes the
      ordinary unposting checks.
    side_effects:
      - Retire any existing current generation and restore intake.
    who: The unposting execution task
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/rules/batch-rules.ts:BATCH_STUCK_POSTING_MINUTES
  - from: closed
    to: open
    trigger: The caller invokes Reopen on a Closed batch.
    guard: Update-lock and system-type checks permit the command.
    side_effects:
      - Resolve enabled attention as reopened.
    who: A finance user with update permission
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batch-attention-items.integration.spec.js#resolves
      a reopened batch reopened, and its next close opens the second generation
  - from: verified
    to: open
    trigger: The caller invokes Reopen on a Verified batch.
    guard: The ordinary reopen checks apply.
    side_effects:
      - Resolve enabled attention as reopened.
    who: A finance user with update permission
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/rules/batch-rules.ts:BATCH_REOPENABLE_STATUSES
  - from: error
    to: open
    trigger: The caller invokes Reopen on an Error batch.
    guard: The ordinary reopen checks apply.
    side_effects:
      - Erase the recorded failures.
      - Resolve enabled attention as reopened.
    who: A finance user with update permission
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batches.integration.spec.js#clears
      the errors of an errored batch on reopen, so the open batch carries none
      (journey ACT-ACC-0521)
  - from: open
    to: archived
    trigger: The caller invokes Archive on an Open batch.
    guard: Update-lock and system-type checks permit archiving.
    side_effects:
      - Remove automatic-close configuration and its schedule.
    who: A finance user with update permission
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batches.integration.spec.js#archives
      an open batch and a closed one, and unarchives to open
  - from: closed
    to: archived
    trigger: The caller invokes Archive on a Closed batch.
    guard: The ordinary archive checks apply.
    side_effects:
      - Resolve enabled attention as archived.
    who: A finance user with update permission
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batches.integration.spec.js#archives
      an open batch and a closed one, and unarchives to open
  - from: archived
    to: open
    trigger: The caller invokes Unarchive.
    guard: Update-lock and system-type checks permit restoration.
    side_effects:
      - Restore Open rather than the pre-archive state.
    who: A finance user with update permission
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batches.integration.spec.js#archives
      an open batch and a closed one, and unarchives to open
  - from: open
    to: deleted
    trigger: A delete request or eligible roll retires an unused Open batch.
    guard: >-
      No protection lock, system type or restrictive document, posting or ledger
      reference remains.
    side_effects:
      - Remove the automatic-close schedule and resolve enabled attention.
      - Clear any stream intake pointer to the removed batch.
    who: A caller with delete permission or the scheduled roller
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/persistence/batches.integration.spec.js#deletes
      the batch a recurring batch names as its active batch and clears the
      reference (DEF-ACT-0012)
  - from: closed
    to: deleted
    trigger: The caller deletes an unused Closed batch.
    guard: The ordinary delete checks apply.
    side_effects:
      - Resolve enabled attention for the deleted record.
    who: A caller with delete permission
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/rules/batch-rules.ts:assertBatchCanBeDeleted
  - from: verified
    to: deleted
    trigger: The caller requests deletion of the Verified collection.
    guard: >-
      Delete rejects Posted and Downloaded; this state still requires no lock,
      no system type and no restrictive references.
    side_effects:
      - >-
        Remove the close schedule, resolve enabled attention and clear any
        stream intake reference.
    who: A caller with delete permission
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/persistence/batches.ts:deleteBatch;
      services-accounting:modules/batches/services/rules/batch-rules.ts:assertBatchCanBeDeleted
  - from: error
    to: deleted
    trigger: The caller requests deletion of the Error collection.
    guard: >-
      Delete rejects Posted and Downloaded; this state still requires no lock,
      no system type and no restrictive references.
    side_effects:
      - >-
        Remove the close schedule, resolve enabled attention and clear any
        stream intake reference.
    who: A caller with delete permission
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/persistence/batches.ts:deleteBatch;
      services-accounting:modules/batches/services/rules/batch-rules.ts:assertBatchCanBeDeleted
  - from: archived
    to: deleted
    trigger: The caller requests deletion of the Archived collection.
    guard: >-
      Delete rejects Posted and Downloaded; this state still requires no lock,
      no system type and no restrictive references.
    side_effects:
      - >-
        Remove the close schedule, resolve enabled attention and clear any
        stream intake reference.
    who: A caller with delete permission
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/persistence/batches.ts:deleteBatch;
      services-accounting:modules/batches/services/rules/batch-rules.ts:assertBatchCanBeDeleted
  - from: posting
    to: deleted
    trigger: The caller requests deletion of the Posting collection.
    guard: >-
      Delete rejects Posted and Downloaded; this state still requires no lock,
      no system type and no restrictive references.
    side_effects:
      - >-
        Remove the close schedule, resolve enabled attention and clear any
        stream intake reference.
    who: A caller with delete permission
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/persistence/batches.ts:deleteBatch;
      services-accounting:modules/batches/services/rules/batch-rules.ts:assertBatchCanBeDeleted
invalid:
  - from: posted
    to: closed
    why: Closed is not an unposting outcome; ordinary recovery restores Open.
  - from: posted
    to: archived
    why: Archiving is defined only for Open and Closed collections.
  - from: posted
    to: deleted
    why: >-
      Deletion rejects completed ordinary states and remains subject to posting
      and ledger references.
  - from: archived
    to: closed
    why: 'Restoration from archive produces Open, not a previous intake state.'
  - from: error
    to: closed
    why: Closing requires the current state to be Open.
  - from: posting
    to: closed
    why: The Close command does not accept a Posting row.
  - from: open
    to: posted
    why: >-
      Ordinary HTTP commands do not directly assign Posted; posting generation
      produces that outcome, and fiscal accounting has a separate system path.
invariants:
  - INV-DOM-ACC-BAT-001-05
  - INV-DOM-ACC-BAT-001-06
  - INV-DOM-ACC-BAT-001-07
  - INV-DOM-ACC-BAT-003-01
concurrency: >-
  Lifecycle persistence locks the existing row before checking and writing. Job
  submission and execution lock requested rows in identity order; shared period
  locks coordinate posting with fiscal close. An edit checks optimistic
  concurrency only when the caller supplies a version.
evidence:
  - >-
    services-accounting at f47cb263:
    modules/batches/services/rules/batch-rules.ts, batch-posting-rules.ts,
    batch-attention-rules.ts; services/persistence/batches.ts;
    services/actions/batch-actions.ts; services/engine/post-batches.ts,
    unpost-batches.ts; services/close/system-batches.ts; db/batches.table.ts
---

# Batch Lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> open: ordinary HTTP create
  open --> closed: finish intake
  open --> archived: retire
  closed --> archived: retire
  archived --> open: restore
  closed --> open: reopen
  verified --> open: reopen
  error --> open: reopen
  open --> posting: execute accounting
  closed --> posting: execute accounting
  verified --> posting: direct job
  error --> posting: another attempt
  posting --> posted: valid generation
  posting --> error: generation refusal
  posting --> deleted: empty generation
  posted --> open: ordinary recovery
  downloaded --> open: ordinary recovery
  posting --> open: stale-row recovery
  open --> deleted: permitted removal
  closed --> deleted: permitted removal
  verified --> deleted: permitted removal
  error --> deleted: permitted removal
  archived --> deleted: permitted removal
  posting --> deleted: permitted removal
  deleted --> [*]
```

This diagram describes ordinary collections. Fiscal accounting writes system collections directly as Posted and restores them through its separate reopening path. Lower-level creation also supports Archived.

## Transitions Explained

The transition rows specify commands, generation outcomes and retirement, including their distinct guards and side effects. Scheduled close retains the schedule whose run is completing; it does not use the same schedule-removal behavior as manual close. Attention effects depend on the deployment switch.

## What Cannot Happen

The invalid rows describe refused commands rather than assuming every stored state has a native producer. Carried states remain accepted input to selected recovery paths. The deletion service has a broader status boundary than the ordinary action menu.

## Concurrency and Timing

Posting is transient within the native accounting transaction, so another reader does not observe that intermediate state. Stale-row thresholds remain relevant to migrated or otherwise retained rows. Job state and batch state must be read separately when settlement fails.
