---
id: WF-ACC-CLS-001
title: Close a month
trigger: >-
  A controller confirms the current month in the close dialog, or an authorized
  client submits the close request.
product_area: accounting
module: close
revision: 1
status: draft
design_intent: confirmed
origin: >-
  services-accounting:docs/milestones/plans/ACCT-M4-plan.md#4a rows 73 and 74
  (class A: close runs as a job with six steps; plan approved 2026-10-21, D12);
  docs/fiscal-close/PHASE-2-PLAN.md#4.1 (Snapshot Architecture agreement,
  2026-10-16); DEF-ACT-0819 (ACCT-M4 WP-K, 2026-10-21). The wait for an active
  aging run contradicts D8 and remains observed only.
implementation_evidence: partially-observed
inference: []
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-ACC-CLS-008
  - CAP-ACC-CLS-009
  - CAP-ACC-CLS-010
  - CAP-ACC-CLS-014
  - CAP-ACC-CLS-015
  - CAP-ACC-CLS-003
moves:
  - STATE-ACC-CLS-001
  - STATE-ACC-CLS-002
crosses:
  - SVC-ACC-CLS-fiscal-close
idempotency: >-
  Each opening of the dialog reserves a key. Replay compares kind, period, note
  and acknowledgements but omits the requester; identical content returns the
  stored run, changed content is refused, and an insertion race asks the caller
  to replay. Financial-step output supports replay without duplicate
  recognition. Calendar generation can commit before its output and be
  revisited.
concurrency: >-
  The active-run index protects each month. Changing tasks take its row
  exclusively while posting takes it shared; existing-output replay can bypass
  the lock. Sequence checks leave neighbouring months unlocked. Aging keys cover
  a workspace’s calendar month across units, and roll-forward has separate
  transactions.
hops:
  - 'n': 1
    actor_or_service: Console close dialog
    action: >-
      Refresh readiness, collect warning acknowledgements and an optional note,
      reserve the dialog’s command key, and require the month label before
      submission.
    anchor: 'react-console:components/accounting/close/close-dialog.tsx:CloseDialog'
    seam: sync
    on_failure: >-
      The dialog disables confirmation for unusable readiness and displays a
      refused request.
    left_inconsistent: No request data is committed at this point.
    observer: The submitting controller sees the dialog result.
    provenance: observed
    source: 'react-console:components/accounting/close/close-dialog.tsx:CloseDialog'
  - 'n': 2
    actor_or_service: Close route (configuration function)
    action: >-
      The route checks close permission and validates the trimmed key, note and
      warning list, rejecting extra properties.
    anchor: >-
      services-accounting:modules/fiscal-close/aws/api/fiscal-period-close.routes.ts:fiscalPeriodCloseRoutes
    seam: sync
    on_failure: Permission and request-schema failures return a refusal to the caller.
    left_inconsistent: The route has not written a run.
    observer: The calling client receives the error.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/e2e/fiscal-close-runs.e2e.spec.js#refuses
      a close without an idempotency key with 400
  - 'n': 3
    actor_or_service: Close request (one transaction)
    action: >-
      Read the period and replay key, validate availability and sequence, then
      save a queued close-kind run with its first job.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/close-run-actions.ts:requestPeriodClose
    seam: sync
    on_failure: >-
      Any transactional error rolls back; conflicts identify active work,
      changed command content or an insertion race.
    left_inconsistent: No partial request or job remains after rollback.
    observer: The request response identifies acceptance or refusal.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#answers
      the same run for the same key, created once
  - 'n': 4
    actor_or_service: Platform durable jobs
    action: >-
      Start the committed job through the registered durable workflow; failed
      starts are delegated to Platform’s reconciler.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-jobs.ts:startRunJob
    seam: state_machine
    on_failure: >-
      The module invokes the leave-to-reconciler start helper; eventual delivery
      is delegated.
    left_inconsistent: The queued run blocks another request while the month remains open.
    observer: The run view reports queued; the module emits no customer alert.
    provenance: observed
    source: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-jobs.ts:startRunJob
  - 'n': 5
    actor_or_service: 'Close job, lock step'
    action: >-
      Take the month’s exclusive lock, recheck open state and prior months, then
      store closing state, the run’s running status and lock output.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:lockForClose
    seam: state_machine
    on_failure: >-
      A domain refusal ends the task without transient retries; other errors use
      the declared task retry policy.
    left_inconsistent: A rolled-back lock transaction changes neither the month nor its run.
    observer: Step history and the run outcome expose failure.
    provenance: observed
    source: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:lockForClose
  - 'n': 6
    actor_or_service: 'Close job, re-check step'
    action: >-
      Calculate and retain readiness inside the period lock; blockers release
      the month and fail the run, while usable readiness records completion and
      warning references.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:revalidate
    seam: state_machine
    on_failure: >-
      A blocked calculation is committed as refusal; transient transactional
      errors roll back and retry. A later history append can fail after the
      calculation committed.
    left_inconsistent: >-
      The evaluation, period release and run failure commit together; their job
      event can be missing.
    observer: >-
      The retained readiness, step outcome and job history provide the available
      evidence.
    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
  - 'n': 7
    actor_or_service: 'Close job, recognition step, through Batch management'
    action: >-
      Recognize entries due by month end and post the deferred revenue batch
      under the close key, retaining its output; record a skip if nothing is
      due.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:recognizeRevenue
    seam: state_machine
    on_failure: >-
      Domain refusal retains the protected month for recovery; transient
      failures retry. An event failure after a skipped result does not undo that
      result.
    left_inconsistent: >-
      Committed revenue can remain in a closing month until forward recovery
      finishes.
    observer: 'The step output links its batch, and failure appears on the run.'
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/revenue-recognition.integration.spec.js#posts
      July's deferred revenue system batch, named and dated as 1.0 did
  - 'n': 8
    actor_or_service: 'Close job, cost of goods sold step'
    action: Store the cost of goods sold step as skipped with its explanation.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:bookCogs
    seam: state_machine
    on_failure: >-
      Task retries apply; its history append is a separate transaction after the
      skip output.
    left_inconsistent: The domain skip may be retained without its history event.
    observer: The output and any appended skip event explain the outcome.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#records
      why revenue recognition and COGS were skipped
  - 'n': 9
    actor_or_service: 'Close job, finalization step, with the Platform snapshot library'
    action: >-
      Commit the closed month, requester attribution, snapshot, accepted aging
      request and successful run together; an active aging run rolls back and
      requests another bounded attempt.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:finalizeClose
    seam: state_machine
    on_failure: >-
      Settled finalization failure releases a month without completed
      recognition; otherwise the month remains protected for retry.
    left_inconsistent: >-
      A close with recognized revenue can remain closing while finalization is
      unresolved.
    observer: >-
      Waiting events and the recorded failure explain available progress,
      subject to event-delivery gaps.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#fails
      the run on finalize, not retryable, saying the period was given back
      (DEF-ACT-0819)
  - 'n': 10
    actor_or_service: 'Close job, roll-forward step, through Books'
    action: >-
      Generate missing future years through Books one year per transaction,
      stopping at a validation refusal and recording output after generation
      finishes.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-job-tasks.ts:rollForward
    seam: state_machine
    on_failure: >-
      Transient retries can repeat the task. Exhaustion fails its job without
      changing the already successful run.
    left_inconsistent: >-
      Committed years may lack the task’s final output, and replay can generate
      remaining years.
    observer: >-
      The job step and infrastructure alarms show failure; successful-run
      polling has already stopped.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#rolls
      the fiscal years forward to three open years after September 2026
  - 'n': 11
    actor_or_service: 'Close job failure task, or the Platform reconciler'
    action: >-
      Apply the current job’s failure consequence, recording failure and
      releasing a finalization-only failure without completed recognition.
    anchor: >-
      services-accounting:modules/fiscal-close/services/jobs/fiscal-close-jobs.ts:applyRunFailure
    seam: state_machine
    on_failure: >-
      The failure task has its declared retry policy; reconciler delivery
      remains delegated beyond the verified module.
    left_inconsistent: >-
      Other retained failures keep their month protected; pre-lock run reads
      create a recovery race concern.
    observer: The run view and infrastructure execution alarms expose what was recorded.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-job.integration.spec.js#leaves
      the run failed and the period closing, held by it
branches:
  - Closing the final month creates a year-close run and adds the year checks.
  - Skipped recognition permits period release if finalization later fails.
waits:
  - >-
    An active aging run causes another finalization attempt after sixty seconds,
    bounded by sixty repeat iterations in the workflow definition.
evidence:
  - >-
    services-accounting at f47cb263: modules/fiscal-close/services/jobs/*.ts;
    services/ports/*.ts; aws/api/fiscal-period-close.routes.ts;
    aws/logic/fiscal-close.step-functions.ts; tests/aws/fiscal-close.synth.ts;
    docs/fiscal-close/PHASE-2-PLAN.md §4, §4.1;
    docs/migrations/fiscal-close/README.md (FC2-4).
  - >-
    react-console at baf6dae3: components/accounting/close/close-dialog.tsx,
    close-run-view.tsx.
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-005, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-LOG-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]
---

# Close a month

## What It Does

A confirmed close becomes a recorded request followed by durable execution. The job protects the month, evaluates its current readiness, recognizes due revenue, captures closure evidence and requests aging. Calendar maintenance follows the successful close.

## The Flow

```mermaid
sequenceDiagram
  actor Staff
  participant API as Close service
  participant Job as Durable close
  participant Revenue as Batch management
  participant Aging
  participant Books
  Staff->>API: submit confirmed close
  API->>API: commit request and job
  API->>Job: start after commit
  Job->>Job: take month and revalidate
  alt required work remains
    Job->>Job: release month and fail run
  else ready
    Job->>Revenue: recognize and post due revenue
    Job->>Job: record skipped cost of goods sold
    Job->>Aging: request version, with bounded active-run waits
    Job->>Job: commit closure, evidence and success
    Job->>Books: top up future calendar
  end
```

## When Something Fails

Before acceptance, a refusal rolls back the request. After commit, a start failure leaves a queued run for the delegated reconciler. Revalidation blockers release the month and require another request after correction. Committed recognition requires forward recovery: finish the close, then reopen to reverse it.

Finalization retries while an aging run is active, within a finite repeat limit. Exhaustion fails the job and releases the month only if recognition has not completed. Calendar generation may partly commit and fail after the run succeeds. Several history events are appended separately from domain commits, so an event failure can leave committed work without its expected history entry.

## Open Design Questions

The approved direction requires a close that does not wait for aging and service-resolved closer names. Requester identity must join replay comparison. Product’s authority and notification questions remain open; calendar-sequence races and failure delivery need proof.
