---
id: WF-ACC-BAT-003
title: Roll a recurring stream and post the batch it replaces
trigger: The scheduler delivers a recurring-stream roll occurrence.
product_area: accounting
module: batches
revision: 1
status: draft
design_intent: confirmed
origin: >-
  D13 (Andrew, 2026-10-16, automatic posting) and the roll contract recorded at
  services-accounting:docs/accounting/recurring-batches.md#The roll contract
implementation_evidence: observed
inference: []
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
implements:
  - CAP-ACC-BAT-010
  - CAP-ACC-BAT-013
  - CAP-ACC-BAT-009
moves:
  - STATE-ACC-BAT-001
  - STATE-ACC-BAT-002
  - STATE-ACC-BAT-003
crosses:
  - SVC-ACC-BAT-batches
idempotency: >-
  The stream occurrence key identifies the newly opened batch; duplicate
  delivery returns that batch. The run key identifies predecessor posting
  submission.
concurrency: >-
  The roller serializes stream changes with a row lock. Occurrence uniqueness
  prevents duplicate creation for one occurrence; imported dispatch behavior is
  documented rather than executed here.
hops:
  - 'n': 1
    actor_or_service: Accounting schedule consumer
    action: Invoke roll work for the stream occurrence in its workspace.
    anchor: >-
      services-accounting:modules/batches/services/roll/recurring-batch-roll.ts:recurringBatchRollWork
    seam: schedule
    on_failure: >-
      Documented refusal handling accumulates failed runs toward pause; the
      consumer retries other delivery errors.
    left_inconsistent: The existing intake pointer remains until a successful commit.
    observer: Scheduling history and API pause metadata
    provenance: documented
    source: 'services-accounting:docs/accounting/recurring-batches.md#The roll'
  - 'n': 2
    actor_or_service: Roll planning
    action: >-
      Read active stream configuration, derive the local occurrence date, render
      templates and locate fiscal placement.
    anchor: >-
      services-accounting:modules/batches/services/roll/recurring-batch-roll.ts:planFor
    seam: sync
    on_failure: Planning refusal records its explanation without generating a collection.
    left_inconsistent: The prior intake collection remains current.
    observer: The scheduling run record
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/roll/batch-roller.integration.spec.js#opens
      the new batch, dated the occurrence’s local date in Denver, and points the
      recurring batch at it
  - 'n': 3
    actor_or_service: Recurring batch roller
    action: >-
      Open the successor, repoint intake and attempt predecessor deletion or
      Open-state close; retain other predecessors.
    anchor: >-
      services-accounting:modules/batches/services/roll/batch-roller.ts:BatchesRecurringBatchRoller.roll
    seam: sync
    on_failure: A transactional error undoes the entire roll.
    left_inconsistent: Neither the new collection nor repoint survives rollback.
    observer: The scheduling run record
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/roll/batch-roller.integration.spec.js#leaves
      nothing: no new batch, the first batch still the active one, open
  - 'n': 4
    actor_or_service: Automatic posting
    action: >-
      Prepare posting for a retained Closed predecessor, including one already
      Closed before the roll.
    anchor: >-
      services-accounting:modules/batches/services/auto-post/automatic-posting.ts:prepareAutomaticPosting
    seam: sync
    on_failure: >-
      Recognized preparation refusal is retained in the run outcome; unexpected
      errors abort the roll.
    left_inconsistent: The Closed predecessor may have no prepared job.
    observer: Run outcome and enabled attention
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/roll/batch-roller.integration.spec.js#posts
      the previous batch the roll leaves closed: one posting job of it, named in
      the run’s outcome
  - 'n': 5
    actor_or_service: Database
    action: >-
      Commit successor, pointer, predecessor disposition and any prepared job
      together.
    anchor: >-
      services-accounting:modules/batches/services/roll/recurring-batch-roll.ts:recurringBatchRollWork
    seam: sync
    on_failure: Commit failure prevents the roll changes.
    left_inconsistent: The previous pointer and collections remain as before.
    observer: The scheduling run record
    provenance: test
    source: >-
      services-accounting:modules/batches/tests/integration/services/roll/batch-roller.integration.spec.js#opens
      nothing for the same occurrence delivered again, and answers the same
      batch
  - 'n': 6
    actor_or_service: Automatic posting
    action: >-
      Attempt the prepared workflow start after commit and return its identity
      in the outcome.
    anchor: >-
      services-accounting:modules/batches/services/auto-post/automatic-posting.ts:startAutomaticPosting
    seam: state_machine
    on_failure: A logged start failure has no registered reconciler.
    left_inconsistent: A Closed predecessor can wait beside a Pending job.
    observer: Platform jobs and enabled attention
    provenance: observed
    source: >-
      services-accounting:modules/batches/services/jobs/batch-posting-jobs.ts:startBatchJob
branches:
  - Missing or inactive streams produce a skipped outcome.
  - >-
    Removing an unused predecessor frees its name and does not request its
    posting.
  - >-
    An already Closed predecessor can receive a posting request; locked, system
    and other retained states do not establish a successful automatic
    submission.
waits:
  - Configured occurrences are validated to be at least twenty-four hours apart.
evidence:
  - >-
    services-accounting at f47cb263: modules/batches/services/roll/**;
    services/auto-post/automatic-posting.ts; services/persistence/batches.ts
    (findRolledBatch, insertRolledBatch, closeOrDeleteRolledOutBatch);
    services/persistence/recurring-batches.ts;
    docs/accounting/recurring-batches.md
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-DAT-004, 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]
---

# Roll a recurring stream and post the batch it replaces

## What It Does

A scheduled roll gives intake a successor before retiring its predecessor. The roll and posting submission share a commit; predecessor accounting then runs as a durable job. A retained Closed predecessor is eligible for preparation even when this roll did not close it.

## The Flow

```mermaid
flowchart LR
  O[Occurrence] --> P[Plan successor]
  P --> R[Open and repoint intake]
  R --> C[Retire predecessor]
  C --> J[Prepare eligible posting]
  J --> D[Commit]
  D --> S[Attempt workflow start]
```

## When Something Fails

Planning refusal creates no successor. Transaction rollback preserves the prior intake pointer. A preparation refusal can commit the new intake while retaining unfinished predecessor accounting, and failed post-commit start can leave the job Pending.

## Open Design Questions

Reactivation does not use the same predecessor-posting policy. Product must resolve that difference and provide recovery for prepared jobs whose workflow never starts.
