---
id: STATE-ACC-CLS-001
title: Fiscal period closing lifecycle
object: DOM-ACC-CLS-001
product_area: accounting
module: close
revision: 1
status: draft
design_intent: confirmed
origin: >-
  services-accounting:docs/milestones/plans/ACCT-M4-plan.md#4a rows 74 and 75
  (class A; plan approved 2026-10-21); the release and cancellation moves and
  the finalization release (DEF-ACT-0819, ACCT-M4 WP-K, 2026-10-21) are
  implementation departures recorded in docs/migrations/fiscal-close/README.md
  and remain proposed
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: open
    meaning: The month accepts ordinary posting and has no close holding it.
  - name: closing
    meaning: >-
      A close protects the month from ordinary posting while its recognition
      step can still post.
  - name: closed
    meaning: >-
      The month retains its close attribution and blocks ordinary posting;
      system reversal remains allowed and late reconciliation moves forward.
  - name: reopening
    meaning: >-
      A reopen protects the month during reversal; current close attribution has
      been cleared.
transitions:
  - from: open
    to: closing
    trigger: Execution reaches the close’s lock task.
    guard: >-
      The run is queued or running, the target month is open and every earlier
      month is closed when the task reads the calendar under the target month’s
      exclusive lock.
    side_effects:
      - The month stores the close run reference.
      - The run starts running and retains the lock timestamp.
    who: 'Accounting Close Job, for the requesting Month-End Closer'
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/persistence/fiscal-periods.integration.spec.js#moves
      open → closing, naming the run that holds it
  - from: closing
    to: closed
    trigger: Execution reaches close finalization.
    guard: >-
      The run retains the closing month and has stored readiness; its aging
      request must be accepted before the transaction can commit.
    side_effects:
      - 'The month stores requester attribution, close time and run reference.'
      - The database derives year closure when all its months are closed.
      - >-
        The same transaction writes close evidence and requests an aging
        version.
      - The run finishes successfully.
    who: Accounting Close Job
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#closes
      the period, by the requester, naming the run
  - from: closing
    to: open
    trigger: >-
      Revalidation blocks, finalization fails without committed recognition, or
      an eligible cancellation releases the close.
    guard: >-
      The close retains the month; cancellation and failure release require no
      completed recognition result.
    side_effects:
      - The month’s run backlink is removed.
      - The run finishes failed without retry eligibility or finishes cancelled.
      - >-
        Blocking evidence is retained on refusal; cancellation records the
        acting person in job history.
    who: 'Accounting Close Job, or a Month-End Closer with the close grant'
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#returns
      the period to open with no run holding it
  - from: closed
    to: reopening
    trigger: Execution reaches the reopen’s lock task.
    guard: >-
      The run is queued or running, its reason has ten characters, and the
      target is closed with every later month open when checked.
    side_effects:
      - >-
        The prior closer and time are cleared while the original close reference
        remains.
      - The run retains prior attribution so cancellation can restore it.
      - The database derives an open fiscal year again.
    who: 'Accounting Close Job, for the requesting Month-End Closer'
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/persistence/fiscal-periods.integration.spec.js#moves
      closed → reopening, clearing the closer and keeping the run it undoes
  - from: reopening
    to: open
    trigger: Execution reaches reopen finalization.
    guard: >-
      The task checks reopening state and moves the run from running; it does
      not compare a holding reopen identifier on the period.
    side_effects:
      - Close attribution and the run backlink are removed.
      - The period’s reopen count increases by one.
      - >-
        The run succeeds with the period change; a later history transaction
        records the requester and reason.
    who: Accounting Close Job
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/reopen-job.integration.spec.js#opens
      the period and counts the reopen
  - from: reopening
    to: closed
    trigger: A caller cancels a queued or failed reopen that has taken the month.
    guard: >-
      The reopen has its lock output, the month is reopening and no completed
      revenue reversal has been recorded.
    side_effects:
      - 'The original closer, close time and close reference are restored.'
      - The run finishes cancelled.
    who: Month-End Closer with the reopen grant
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/persistence/fiscal-periods.integration.spec.js#moves
      reopening → closed when the reopen fails, with the closer and the time it
      is given
invalid:
  - from: open
    to: closed
    why: Closing must pass through closing state before finalization.
  - from: closed
    to: open
    why: Reopening must pass through reopening state before opening the month.
  - from: closing
    to: reopening
    why: A reopen cannot take a month held in closing state.
  - from: reopening
    to: closing
    why: A close cannot take a month held in reopening state.
  - from: open
    to: reopening
    why: Reopen locking requires the target month to be closed.
  - from: closed
    to: closing
    why: Another close cannot start until the month has returned to open.
concurrency: >-
  Tasks that change a month, retry and cancellation lock its row exclusively;
  postings use a shared lock. Existing-output replays can return before locking.
  Calendar sequence reads leave neighbouring periods unlocked. Failure handling
  reads its run before waiting on the period, whereas retry and cancellation
  refresh the run inside the lock.
invariants:
  - INV-DOM-ACC-CLS-001-01
  - INV-DOM-ACC-CLS-001-02
  - INV-DOM-ACC-CLS-001-03
  - INV-DOM-ACC-CLS-001-04
  - INV-DOM-ACC-CLS-001-05
  - INV-DOM-ACC-CLS-001-06
  - INV-DOM-ACC-CLS-001-07
  - INV-DOM-ACC-CLS-001-09
evidence:
  - >-
    services-accounting at f47cb263:
    modules/fiscal-close/services/rules/transitions.ts;
    services/persistence/fiscal-periods.ts;
    services/jobs/fiscal-close-job-tasks.ts, fiscal-close-jobs.ts,
    close-run-actions.ts; docs/architecture/accounting-2.0/05-lifecycles.md §8.
---

# Fiscal period closing lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> open
  open --> closing : close takes the month
  closing --> closed : close commits evidence
  closing --> open : refusal or eligible release
  closed --> reopening : reopen takes the month
  reopening --> open : reopen completes
  reopening --> closed : eligible cancellation
```

## Transitions Explained

A close first protects the month, then rechecks readiness and performs recognition before finalization. Finalization commits the closed state with retained evidence and the aging request. Revalidation refusal releases the month. A finalization failure can also release it when recognition has not committed.

A reopen preserves prior attribution in its run, clears it from the period, reverses recorded revenue and returns the month to open. Eligible cancellation restores the previous closed state. Other failures retain the protected working state for recovery.

## What Cannot Happen

The writer rejects direct open-to-closed and closed-to-open changes, crossovers between the working states, unchanged-state requests and stale expected states. Sequence validation also rejects a close with an earlier outstanding month or a reopen with a later non-open month. Those checks do not establish race prevention across different period rows.

## Concurrency and Timing

The target period’s row orders changing tasks against postings into that month. Lock waits and task retries have finite execution limits, but the resulting closing or reopening state has no automatic expiry. A failed or unstarted run can therefore require a person to discover and recover it.
