---
id: CAP-ACC-REC-014
title: Carry reconciliation accounting through operational posting
feature: FEAT-ACC-REC-03
product_area: accounting
module: reconciliation
revision: 2
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - react-console acceptance inventory CAP-ACC-REC-014
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
intent_sources:
  - 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D7'
  - 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D2'
  - >-
    services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§4: the architecture §6 invariants hold;
    INV-11, INV-12, INV-16)
  - >-
    services-accounting:docs/reconciliation/brief.md#Accounting (Andrew’s product brief, 2026-10-15):
    reconciliation must not become a second ledger
purpose: The system needs reconciliation’s accounting effects to use the established batch-posting path.
outcomes:
  - Distinct effect keys protect each requested accounting movement.
  - Staff can distinguish current posted entries from pending requests.
concepts:
  - CON-ACC-REC-003
actors:
  - ACT-ACC-011
  - ACT-ACC-018
surfaces:
  - job
preconditions: []
postconditions: []
permissions: []
rules:
  - id: BR-CAP-ACC-REC-014-01
    proved_by: [ATS-ACC-REC-007-11, ATS-ACC-REC-007-12, ATS-ACC-REC-014-01, ATS-ACC-REC-014-10, ATS-ACC-REC-014-14, TST-ACC-REC-067]
    statement: >-
      Ledger requests join the operational batch selected by the unit’s default assignment for that merchant,
      and fail without writes when the batch cannot accept work or its period is closed.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      a unit with no open batch for its requests (400, `no_open_batch`), writing nothing;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#answers
      the request as stored: in the unit’s open batch, on its date, the document date kept, its lines with
      their roles;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#assigns
      the batch the default-batch rule gives the unit and merchant account;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#asks
      for no batch when there is no plan: a unit with none answers nothing, not a refusal
    origin: 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D7'
  - id: BR-CAP-ACC-REC-014-02
    proved_by: [ATS-ACC-REC-007-11, ATS-ACC-REC-007-12, ATS-ACC-REC-014-01, ATS-ACC-REC-014-10, ATS-ACC-REC-014-14, TST-ACC-REC-067]
    statement: >-
      Request accounting dates follow the operational batch, document dates preserve the original effect and
      lines distinguish bank, clearing, fee, reserve, adjustment and write-off roles.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#answers
      the request as stored: in the unit’s open batch, on its date, the document date kept, its lines with
      their roles;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#assigns
      the batch the default-batch rule gives the unit and merchant account;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      a unit with no open batch for its requests (400, `no_open_batch`), writing nothing;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#asks
      for no batch when there is no plan: a unit with none answers nothing, not a refusal
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§4: the architecture §6 invariants hold;
      INV-16)
  - id: BR-CAP-ACC-REC-014-03
    proved_by: [ATS-ACC-REC-007-11, ATS-ACC-REC-007-12, ATS-ACC-REC-014-01, ATS-ACC-REC-014-10, ATS-ACC-REC-014-14, TST-ACC-REC-067]
    statement: 'An effect generating no posting plan needs no operational batch, including a zero transfer without fees.'
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#asks
      for no batch when there is no plan: a unit with none answers nothing, not a refusal;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#answers
      the request as stored: in the unit’s open batch, on its date, the document date kept, its lines with
      their roles;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#assigns
      the batch the default-batch rule gives the unit and merchant account;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      a unit with no open batch for its requests (400, `no_open_batch`), writing nothing
    origin: none
  - id: BR-CAP-ACC-REC-014-04
    proved_by: [ATS-ACC-REC-014-02, TST-ACC-REC-044, TST-ACC-REC-045]
    statement: >-
      Requests are append-only, remain within one unit, balance by currency and contain one to twenty lines
      with exactly one debit or credit side each.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-postings.table.integration.spec.js#refuses
      at commit a posting whose debits and credits differ in USD (RHF36);
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-postings.table.integration.spec.js#commits
      a balanced posting with its two lines (premise of the refusals);
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-postings.table.integration.spec.js#refuses
      at commit a posting with no lines (RHF36);
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-posting-lines.table.integration.spec.js#refuses
      position 21, so a posting has at most 20 lines;
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-posting-lines.table.integration.spec.js#refuses
      a line with both a debit and a credit;
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-postings.table.integration.spec.js#refuses
      the owner an UPDATE, by the append-only trigger (RHF32);
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-posting-lines.table.integration.spec.js#refuses
      the owner a DELETE outside a tenant purge, by the append-only trigger (RHF32)
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§4: the architecture §6 invariants hold;
      INV-11)
  - id: BR-CAP-ACC-REC-014-05
    proved_by: [ATS-ACC-REC-014-03, TST-ACC-REC-045, TST-ACC-REC-091]
    statement: >-
      Each accounting effect uses a workspace-unique key that distinguishes finalization, reversal, write-off
      and successive item recognitions.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-postings.table.integration.spec.js#refuses
      an event key the tenant already has;
      services-accounting:modules/reconciliation/tests/unit/rules/posting-plans.spec.js#appends :reverse to a
      recognition’s key;
      services-accounting:modules/reconciliation/tests/unit/rules/posting-plans.spec.js#numbers a second
      recognition after a reversal
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§4: the architecture §6 invariants hold;
      INV-11)
  - id: BR-CAP-ACC-REC-014-06
    proved_by: [ATS-ACC-REC-014-04, TST-ACC-REC-091]
    statement: >-
      Bank and clearing lines use their bank and merchant ledger accounts, while fees, reserves and write-offs
      resolve the unit’s purpose defaults.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/unit/rules/posting-plans.spec.js#takes a fee’s
      counter-account from processor_fee_expense;
      services-accounting:modules/reconciliation/tests/unit/rules/posting-plans.spec.js#takes a reserve
      release’s counter-account from processor_reserve_receivable;
      services-accounting:modules/reconciliation/tests/unit/rules/posting-plans.spec.js#debits the bank and
      credits clearing for a positive payout;
      services-accounting:modules/reconciliation/tests/unit/rules/posting-plans.spec.js#debits the write-off
      account and credits clearing for a positive item never paid out
    origin: 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D2'
  - id: BR-CAP-ACC-REC-014-07
    proved_by: [ATS-ACC-REC-010-13, ATS-ACC-REC-014-05, ATS-ACC-REC-014-11, ATS-ACC-REC-014-12, TST-ACC-REC-051, TST-ACC-REC-068]
    statement: >-
      Posted status is calculated from an unreversed batch posting containing the request’s reconciliation
      entry; reversing that posting makes the request pending again.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#is
      posted when an unreversed batch posting holds its reconciliation entry;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#is
      not posted when the batch posting holding its entry was reversed;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#is
      not posted by an entry of another transaction type naming its id;
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#writes
      the transfer, the fee and the reserve as balanced reconciliation entries on the accounts of the example;
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#deletes
      the entries, empties the clearing account and makes the three requests pending again;
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#writes
      the same entries when the batch is posted again
    origin: 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D7'
  - id: BR-CAP-ACC-REC-014-08
    proved_by: [ATS-ACC-REC-010-13, ATS-ACC-REC-014-05, ATS-ACC-REC-014-09, ATS-ACC-REC-014-11, ATS-ACC-REC-014-12, ATS-ACC-REC-014-13, TST-ACC-REC-021, TST-ACC-REC-051, TST-ACC-REC-068]
    statement: >-
      Operational batch posting creates the reconciliation entries, one per request, and unposting removes
      those entries; reconciliation does not post directly.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#writes
      the transfer, the fee and the reserve as balanced reconciliation entries on the accounts of the example;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#is
      posted when an unreversed batch posting holds its reconciliation entry;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#is
      not posted when the batch posting holding its entry was reversed;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#is
      not posted by an entry of another transaction type naming its id;
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#deletes
      the entries, empties the clearing account and makes the three requests pending again;
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#writes
      the same entries when the batch is posted again;
      react-console:acceptance/accounting/playwright/journeys/reconciliation-close/ACT-ACC-0627.spec.ts#ACT-ACC-0627
    origin: 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D7'
  - id: BR-CAP-ACC-REC-014-09
    proved_by: [ATS-ACC-REC-014-06, TST-ACC-REC-067]
    statement: >-
      Posting requests support two-decimal currencies only and reject other exponents without writes until
      batch posting supports minor units.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      a currency whose exponent is not 2 (409, `unsupported-currency-exponent`), writing nothing;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      a three-decimal currency the same way
    origin: none
  - id: BR-CAP-ACC-REC-014-10
    proved_by: [ATS-ACC-REC-014-07, TST-ACC-REC-068]
    statement: >-
      Pending batch requests are read in creation order with event-key tie breaking and pages capped at five
      hundred.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#answers
      the batch’s requests not posted, oldest written first, then by event key;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#refuses
      a limit of 0;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.queries.integration.spec.js#refuses
      a limit above 500
    origin: none
  - id: BR-CAP-ACC-REC-014-11
    proved_by: [ATS-ACC-REC-014-08, TST-ACC-REC-045, TST-ACC-REC-067]
    statement: >-
      Request creation requires the causing command’s transaction, and its operational batch cannot be deleted
      while the request refers to it.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      to record outside the command’s transaction;
      services-accounting:modules/reconciliation/tests/integration/db/reconciliation-postings.table.integration.spec.js#is
      not deleted while the posting rides in it
    origin: none
scenarios:
  - id: SC-CAP-ACC-REC-014-01
    given: Three approved requests are assigned to an open operational batch.
    when: Post that batch.
    then: Three balanced reconciliation entries leave no request pending.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#leaves
      no request pending: each is posted
  - id: SC-CAP-ACC-REC-014-02
    given: Those operational entries are posted.
    when: Unpost the batch.
    then: Entry removal makes the requests pending and reverses the example’s clearing effect.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/batch-lifecycle/finalize-posts-to-subledger.integration.spec.js#deletes
      the entries, empties the clearing account and makes the three requests pending again
edge_cases:
  - id: SC-CAP-ACC-REC-014-03
    given: The merchant settles in yen.
    when: Create a required posting request.
    then: Unsupported exponent refusal leaves no written request.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/reconciliation-ledger-postings.integration.spec.js#refuses
      a currency whose exponent is not 2 (409, `unsupported-currency-exponent`), writing nothing
constraints: []
depends_on: []
related:
  - CAP-ACC-REC-007
  - CAP-ACC-REC-011
  - CAP-ACC-REC-013
  - CAP-ACC-REC-018
journeys:
  - JNY-ACC-REC-003
open_questions: []
evidence:
  - >-
    Examined supporting service material: services/persistence/reconciliation-ledger-postings.ts
    (recordPostings, listPendingPostings, listPostingsInBatches, loadPosting), rules/posting-plans.ts,
    persistence/fiscal-period-locks.ts; db/reconciliation-postings.table.ts; tests integration
    reconciliation-ledger-postings*, finalize-posts-to-subledger, db reconciliation-postings and posting-lines
    table specs, unit posting-plans; modules/reconciliation/README.md (Ledger requests ride in batches);
    docs/reconciliation/architecture.md §16.
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-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-DAT-004, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-003, TC-PI-005]
exercised_by: [FAS-ACC-REC-03-01, FAS-ACC-REC-03-02, FAS-ACC-REC-03-03, FAS-ACC-REC-03-05]
suites: [TST-ACC-REC-021, TST-ACC-REC-044, TST-ACC-REC-045, TST-ACC-REC-051, TST-ACC-REC-067, TST-ACC-REC-068, TST-ACC-REC-091]
---

# Carry reconciliation accounting through operational posting

## Purpose

The system needs reconciliation’s accounting effects to use the established batch-posting path.

## How It Works

Finalization, reversal or write-off produces an accounting request. Rhythm assigns the request to the operational batch selected by accounting policy. Batch posting creates its ledger entries, while reconciliation derives whether those entries are currently posted. Unposting makes the request pending again rather than rewriting its financial history. Missing batches, closed periods or unsupported currency exponents stop request creation.

## Relationships

Reconciliation decisions produce requests, Books supplies accounts, Batch Management posts them and readiness reports pending accounting.
