---
id: WF-ACC-CLS-006
title: Keep ordinary postings out of a protected month
trigger: >-
  An accounting posting or reversal path checks whether its target month accepts
  the operation.
product_area: accounting
module: close
revision: 1
status: draft
design_intent: confirmed
origin: >-
  services-accounting:docs/milestones/plans/ACCT-M4-plan.md#4a row 74 (class A;
  plan approved 2026-10-21); batch posting amendment A2 (2026-10-16);
  reconciliation architecture §15.4 (INV-16)
implementation_evidence: observed
inference: []
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-ACC-CLS-003
moves:
  - STATE-ACC-CLS-001
crosses:
  - SVC-ACC-CLS-fiscal-close
idempotency: >-
  The guard does not assign a command identity. Each posting path owns replay
  behavior; a rejected operation can be attempted again with an eligible
  open-period date.
concurrency: >-
  Shared posting locks and exclusive close locks order work on one month.
  Multiple postings can hold a shared lock together; the later close waits for
  them.
hops:
  - 'n': 1
    actor_or_service: Batch management posting path
    action: >-
      In the posting transaction, acquire the period’s shared lock and apply the
      operation-specific state rule after any required batch locks.
    anchor: >-
      services-accounting:modules/batches/services/posting-period-guard.ts:assertPeriodAllows
    seam: sync
    on_failure: >-
      Reject ordinary work outside open state; system posting and reversal use
      their restricted state windows.
    left_inconsistent: Rejected posting work rolls back with its transaction.
    observer: The posting caller receives the guard refusal.
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/posting-period-guard.ts:POSTING_PERIOD_RULES
  - 'n': 2
    actor_or_service: Close job step holding the month
    action: >-
      Wait for any shared posting holder before acquiring the close step’s
      exclusive month lock and changing state.
    anchor: >-
      services-accounting:modules/fiscal-close/services/persistence/fiscal-periods.ts:lockPeriodForUpdate
    seam: sync
    on_failure: >-
      The declared task timeout and retry policy apply to an excessive lock
      wait.
    left_inconsistent: >-
      The month retains its prior state while waiting, which depends on the
      step.
    observer: Execution history can show a running step.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-concurrency.integration.spec.js#waits
      on the period row while the FOR SHARE is held
  - 'n': 3
    actor_or_service: Reconciliation late effects
    action: >-
      For a protected source month, find and lock a later open period, date the
      eligible effect on its first day and retain the late marker.
    anchor: >-
      services-accounting:modules/reconciliation/services/persistence/fiscal-period-locks.ts:lockFirstOpenPeriodAfter
    seam: sync
    on_failure: The writable-period service refuses when no later open month exists.
    left_inconsistent: The failed transaction applies no reconciliation effect.
    observer: The reconciliation caller receives the refusal.
    provenance: test
    source: >-
      services-accounting:modules/fiscal-close/tests/integration/services/jobs/close-concurrency.integration.spec.js#posts
      a reconciliation dated in it on the first open period, October 1, flagged
      late after close
evidence:
  - >-
    services-accounting at f47cb263:
    modules/batches/services/posting-period-guard.ts;
    modules/reconciliation/services/persistence/fiscal-period-locks.ts;
    modules/reconciliation/services/close-readiness/assess-period.ts:assertPeriodWritable;
    modules/fiscal-close/services/persistence/fiscal-periods.ts;
    docs/fiscal-close/PHASE-2-PLAN.md §5.
controls: [TC-IAM-001, TC-IAM-005, TC-IAM-006, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-LOG-002, TC-LOG-003, TC-LOG-005, TC-PI-002, TC-PI-003]
---

# Keep ordinary postings out of a protected month

## What It Does

Posting protection makes the month’s state visible to the modules that apply accounting work. Ordinary posting and unposting require open state. Close-owned recognition and reversal use the restricted system rules, while eligible late reconciliation moves to a following open month.

## The Flow

```mermaid
sequenceDiagram
  participant Posting
  participant Month as Period row
  participant Close
  Posting->>Month: acquire shared lock
  Close->>Month: request exclusive lock and wait
  Posting->>Posting: verify open and apply work
  Posting->>Month: commit and release
  Close->>Month: protect month and commit
  Posting->>Month: acquire shared lock
  Posting->>Posting: see protected state and refuse
```

## When Something Fails

An operation rejected by the guard rolls back its posting transaction. A posting that already holds the shared month lock can finish before a close takes its exclusive lock. Reconciliation cannot redirect work when no later open month is available. The cited batch-creation test also refuses a protected month; it does not prove ordinary posting refusal by itself.

## Open Design Questions

The proposed database guard on ledger dates is not established as built. The earlier planning finding about batch creation conflicts with the pinned placement guard and needs correction by the planning owner.
