---
id: STATE-ACC-CLS-002
title: Close run lifecycle
object: DOM-ACC-CLS-003
product_area: accounting
module: close
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: queued
    meaning: >-
      The request and first job are committed, but execution has not taken the
      month; a close’s month remains open and a reopen’s remains closed.
  - name: running
    meaning: The first lock task or a retry has moved the run into execution.
  - name: failed
    meaning: >-
      Execution stopped and the run retains a finish time plus failure details
      when those were recorded.
  - name: succeeded
    meaning: >-
      The requested month change committed; roll-forward can still fail after a
      close has succeeded.
  - name: cancelled
    meaning: >-
      A caller ended the eligible queued or failed request; this outcome is
      final.
transitions:
  - from: queued
    to: running
    trigger: The first job takes its target month.
    guard: The sequence and target-state checks pass and the run is queued.
    side_effects:
      - The target month moves into closing or reopening.
    who: Accounting Close Job
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/persistence/period-close-runs.integration.spec.js#moves
      queued → running, recording the job’s id
  - from: running
    to: succeeded
    trigger: The close or reopen finalization task commits.
    guard: >-
      The task verifies its required period state and updates a running run;
      reopen checks the state without matching a holding-run identifier.
    side_effects:
      - The run receives its finish time.
      - 'A close commits its closed month, evidence and aging request together.'
      - A reopen commits an open month and incremented reopen count.
    who: Accounting Close Job
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#ends
      the run succeeded and finished
  - from: running
    to: failed
    trigger: >-
      Revalidation refuses, execution errors settle as failed, or finalization
      exhausts its aging wait.
    guard: >-
      Failure handling accepts the reported job as current before waiting on the
      period lock; revalidation can fail the run within its own transaction.
    side_effects:
      - 'Failure details identify the step, summary and retry eligibility.'
      - A blocking revalidation opens the month in the same transaction.
      - A finalization failure releases a month without completed recognition.
      - Other retained failures leave closing or reopening in place.
    who: 'Accounting Close Job (its failure task, or the Platform reconciler)'
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#ends
      the run failed at revalidate, not retryable
  - from: queued
    to: failed
    trigger: >-
      A job fails while queued, before its lock has taken the month; failure
      handling passes through running before failing it.
    guard: The reported job matches the run’s current job when read before the lock.
    side_effects:
      - >-
        If no month is retained, retry is unavailable and the target’s existing
        state remains unchanged.
    who: Accounting Close Job
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/reopen-job.integration.spec.js#refuses
      a reason under ten characters in the job’s lock, the period left closed
  - from: failed
    to: running
    trigger: A caller requests recovery of a failed run.
    guard: >-
      The run remains failed, retains the protected month and the acting caller
      holds its kind’s action grant.
    side_effects:
      - >-
        A new job references the failed job, preserves settled work and starts
        after commit.
      - >-
        The failure output and finish time are cleared as the run returns to
        running.
    who: Month-End Closer
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/reopen-job.integration.spec.js#finishes
      the reopen on retry
  - from: queued
    to: cancelled
    trigger: A caller cancels before the job takes the month.
    guard: The caller holds the matching action grant and the run is queued.
    side_effects:
      - >-
        Cancellation records its actor and outcome in job history without moving
        an untouched month.
      - A later job invocation refuses the cancelled run.
    who: Month-End Closer
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/persistence/period-close-runs.integration.spec.js#moves
      queued → cancelled, recording when it finished
  - from: failed
    to: cancelled
    trigger: A caller cancels a stopped run.
    guard: >-
      The caller holds the matching grant; recognized or reversed revenue has
      not completed on a retained period.
    side_effects:
      - An eligible retained close returns its month to open.
      - An eligible retained reopen restores the earlier closed attribution.
      - The job history records the cancelling caller and the period effect.
    who: Month-End Closer
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#leaves
      the period open with no run holding it (finalize had already given it
      back)
invalid:
  - from: succeeded
    to: running
    why: A completed close is corrected through a separate reopen request.
  - from: cancelled
    to: running
    why: Cancelled requests cannot resume; another action needs a fresh request.
  - from: running
    to: cancelled
    why: >-
      The cancellation service rejects running work even though the low-level
      transition table permits the move.
  - from: failed
    to: succeeded
    why: Recovery must put the failed run back into running before finalization.
  - from: queued
    to: succeeded
    why: A queued request must run before it can succeed.
concurrency: >-
  The active-run index allows one queued or running request per month. Changing
  tasks and recovery share its period lock, but failure handling retains a run
  value read before that lock. Competing command keys are constrained in the
  database, and a lost insertion race returns a refusal instructing the caller
  to replay. Different month rows do not serialize calendar sequence.
invariants:
  - INV-DOM-ACC-CLS-003-01
  - INV-DOM-ACC-CLS-003-02
  - INV-DOM-ACC-CLS-003-03
  - INV-DOM-ACC-CLS-003-08
  - INV-DOM-ACC-CLS-003-11
evidence:
  - >-
    services-accounting at f47cb263:
    modules/fiscal-close/services/rules/transitions.ts:RUN_STATUS_TRANSITIONS;
    services/persistence/period-close-runs.ts;
    services/jobs/close-run-actions.ts, fiscal-close-jobs.ts:applyRunFailure,
    periodHeldBy; db/period-close-runs.table.ts.
  - >-
    react-console at baf6dae3: components/accounting/close/close-run-view.tsx
    (FailurePanel, CancelCloseDialog, CancelledPanel).
---

# Close run lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> queued : save request and job
  queued --> running : take month
  queued --> cancelled : cancel waiting request
  queued --> failed : job fails before taking month
  running --> succeeded : finalize month
  running --> failed : refused or exhausted execution
  failed --> running : retry retained month
  failed --> cancelled : cancel eligible stopped work
  succeeded --> [*]
  cancelled --> [*]
```

## Transitions Explained

Acceptance saves one queued request with its job. Locking starts it, and finalization records success. A queued failure is represented to callers as failed although the writer moves it through running in the same failure transaction.

A failed run can resume only while it retains its month. Retry adds a linked job and clears the failure. A released month needs a new request. Cancellation is available through the service for queued or failed runs, subject to the committed revenue checks; the console exposes it only after failure.

## What Cannot Happen

Succeeded and cancelled runs have no outgoing writer transitions. The recovery service also rejects cancellation during running work and retries after release. Only the newest job is intended to settle failure, but the pre-lock run read needs concurrency verification to establish that intent under a race.

## Concurrency and Timing

The period row is shared by changing tasks and recovery. Retry and cancellation refresh the run after acquiring it; failure handling does not. Jobs left queued and retained failures have no module expiry. A successful close stops active polling even while its final calendar-maintenance task remains unfinished.
