Skip to content

CAP-ACC-BAT-017Protect ledger balance, immutability and workspace isolation

Markdown

CAP-ACC-BAT-017

Protect ledger balance, immutability and workspace isolation

Markdown
  • DocumentDraft
  • Product intentProposed
  • ImplementationObserved

KindCapabilityProduct areaAccountingModuleBatch ManagementRevision1

Read atservices-accounting f47cb263

The Formal Contract

The narrative above explains; the sections below are authoritative — the rules, scenarios and tests this document binds.

Outcomes
Ledger protections refuse invalid writes and restrict deletion to the guarded recovery and purge paths.
Surfaces
job
Constraints
Platform coordinates workspace deletion and this module contributes ledger purge.
Intent sources
services-accounting:docs/architecture/accounting-2.0/02-decisions.md#A-1services-accounting:docs/architecture/accounting-2.0/02-decisions.md#D-04services-accounting:docs/accounting/batches.md#Unposting deletes the entries (Andrew, Reporting D7, 2026-10-21)services-accounting:docs/migrations/batch-posting/README.md#Merge readiness (Andrew's D9 of 2026-10-15: tenant deletion removes the posted ledger)

Controls

18 controls apply, from the corpus control catalogue.

TC-IAM-001 TC-IAM-005 TC-IAM-006 TC-AZ-001 TC-AZ-002 TC-AZ-003 TC-AZ-004 TC-AZ-005 TC-TEN-001 TC-TEN-002 TC-TEN-003 TC-TEN-004 TC-DAT-004 TC-LOG-001 TC-LOG-002 TC-LOG-003 TC-LOG-005 TC-PI-005

Business Rules

8 rules, 8 to be proved before approval. A rule's product intent is derived from its origin: 1 confirmed by a product decision, the rest inheriting this capability's proposed.

IdRuleKindEnforced atProvenance
01must prove
Proposed
Every stored entry needs at least two lines and equal debits and credits in base currency and each transaction currency, with the database checking balance at commit.
invariantdatabase, servicetest
services-accounting:modules/batches/tests/integration/services/persistence/subledger-entries.integration.spec.js#refuses an entry whose debits and credits differ, at commit, by the balance constraint
02must prove
Confirmed
Amounts are nonnegative whole minor units with their direction expressed as debit or credit.
invariantdatabasetest
services-accounting:modules/batches/tests/integration/services/persistence/ledger-minor-units.integration.spec.js#converts each line with its unit's base exponent and its transaction currency's, and each schedule entry with its schedule's
03must prove
Proposed
Stored headers and lines reject updates even from their owner, and the application cannot delete them directly.
invariantdatabasetest
services-accounting:modules/batches/tests/integration/services/persistence/subledger-entries.integration.spec.js#cannot be updated even by the table owner: the append-only trigger refuses it, naming the table
04must prove
Proposed
Removal is restricted to guarded deletion of a current ordinary posting within its workspace or workspace purge.
origin: services-accounting:docs/accounting/batches.md#Unposting deletes the entries (Andrew, Reporting D7, 2026-10-21)
invariantdatabasetest
services-accounting:modules/batches/tests/integration/services/posting/unpost-deletes-entries.integration.spec.js#is the only way to delete a ledger row: the application role is refused a direct delete
05must prove
Proposed
Each line account must belong to its entry’s business unit.
invariantdatabasetest
services-accounting:modules/batches/tests/integration/services/persistence/subledger-entries.integration.spec.js#refuses a line whose G/L account belongs to another business unit, by the same-unit foreign key
06must prove
Proposed
A posting cannot contain duplicate entries for one document or schedule entry, and an original entry permits only one reversal referencing that workspace’s entry.
invariantdatabasetest
services-accounting:modules/batches/tests/integration/services/persistence/subledger-entries.integration.spec.js#refuses a second entry of the same transaction in the same posting, by the no-duplicates key
07must prove
Proposed
During workspace deletion, the ledger participant purges ledger, postings, schedules and jobs before batches and configuration and refuses purge while the workspace remains live.
origin: services-accounting:docs/migrations/batch-posting/README.md#Merge readiness (Andrew's D9 of 2026-10-15: tenant deletion removes the posted ledger)
lifecycledatabase, servicetest
services-accounting:modules/batches/tests/integration/services/lifecycle/ledger-purge.integration.spec.js#removes every batch posting table’s rows, the ledger’s first among them, and answers done with nothing left
08must prove
Proposed
Workspace isolation prevents reading another workspace’s ledger.
invariantdatabasetest
services-accounting:modules/batches/tests/integration/services/posting/rebuilt-documents.integration.spec.js#answers another tenant none of the entries, by batch or by transaction (the row-level policy)

Scenarios

IdGivenWhenThenProvenance
01A ledger entry is stored under a posting.An application or the table owner changes its amount.The update is refused and the stored accounting remains intact.test
services-accounting:modules/batches/tests/integration/services/persistence/subledger-entries.integration.spec.js#cannot be updated even by the table owner: the append-only trigger refuses it, naming the table
02Platform is deleting a workspace containing posting history.The ledger participant purges and configuration purge follows.No rows for that workspace remain in the examined accounting tables.test
services-accounting:modules/batches/tests/integration/services/lifecycle/ledger-purge.integration.spec.js#lets configuration be purged once the posting tables are gone, leaving no row of the tenant in any tenant table

The Markdown files under specs/ in rhythmsoftware/rhythm-v2-docs are the canonical source of truth. This reader is a generated, read-only projection of them.