ENGINE 01
SFS-E01-CLS · CLOSE AUTOMATION · REV 2026-07-20 · PRODUCTION
PRODUCTION = runnable end-to-end, CI-backed, full test suite passing. All data fictional and seeded.
Month-End Close
A deterministic month-end close that cannot silently go wrong — ten controls, proven
The engine runs a full month-end close on a fictional three-entity group: it computes nine classes of recurring journal entry (prepaid amortization, depreciation, deferred rent and CAM, fixed-fee and management-fee accruals, note interest, G&A and insurance allocations, and exact-route postage), ties every backing schedule back to the ledger, and writes a JE register, an updated trial balance, and a close report. Before the close counts as clean, ten independent Close Sentinel controls re-check it for the ways a month-end silently goes wrong — a missed accrual, a one-sided intercompany entry, an asset depreciating past its life — and a CRITICAL finding blocks the close and fails the run's exit code. All money is integer cents so tie-outs are exact, and every run is seeded and reproducible.
A controller cannot take "the close is clean" on faith. Every figure on this page re-derives from the public repository by running the commands shown — detection is proven, not asserted.
Architecture
functional block stack · static overviewSelect a layer
Each layer is an independent stage of the close, stacked from the pipeline gate down to the seeded data. Select a layer for its plain-terms and engineering detail.
Key specifications
at a glanceWhat it does for you
plain termsMonth-end close at a large entity group is a recurring operational burden: many interdependent recurring journal entries, each with its own logic, spread across multiple operating entities and separate accounting databases. Run from memory or a static spreadsheet, errors accumulate and the close extends for days. The failures that hurt most are the silent ones — a recurring accrual that quietly stops posting, a fully depreciated asset that keeps depreciating, an allocation driven by a cumulative balance instead of the month's activity. Each looks exactly like a clean close until someone finds it quarters later.
A clean March close7. Running the close for 2026-03 posts all nine recurring entries balanced, ties every schedule to the GL (prepaid 8,100.00, fixed-fee 62,250.00, insurance 10,950.00, and postage account 1460 clearing to 0.00 in each entity), lands the trial balance at Dr 3,603,311.50 = Cr 3,603,311.50, and Sentinel reports zero findings — close status CLEAN, exit 0.
Prove the controls with injected faults8. The guardrail demo first runs a clean baseline (zero findings), then injects twelve classic close errors one at a time — an unbalanced opening, an ended asset still depreciating, a one-sided intercompany entry, a missing accrual, a rounding drift, a single-cent shadow tamper, a rewritten locked period, and more — and asserts each is caught by exactly the control designed for it. Verdict: ALL GUARDRAILS HELD (12 faults, baseline clean), exit 0.
Autonomous remediation of a drifted close9. Given a close contaminated with a drift profile (19 initial findings), the Autonomous Close Loop resyncs four drifted categories from the seeded sub-ledger of record over four turns (criticals fall 18 -> 1), quarantines the tampered locked prior period it has no authority to overwrite, and posts the current period on its own authority — verdict AUTO-POSTED (PARTIAL), exit 0, with a byte-identical settled register.
Functional block diagram
engineering · each block links to its sourcePlain terms
- Seeded dataset. Fictional 3-entity group + sub-ledgers
- Integer-cent math. Exact splits, no penny lost
- Recurring-entry engine. Computes & posts 9 recurring JEs
- Ledger + balance control. Debits must equal credits, per entity too
- Read-only preflight family. 13 validation-only checkers (PASS / NEEDS REVIEW)
- Schedule to GL tie-out. Each schedule must reconcile to its account
- Shadow recompute (C9). A second, independent computation of every amount
- Close Sentinel C1-C10. Ten controls block a silent close error
- Register / TB / report. Writes JE register, trial balance, close report
- Autonomous close loop. Auto-remediates in-period drift, quarantines what it cannot certify
- Verdict + exit-code gate. Clean close => exit 0; otherwise non-zero
Engineering
- Seeded dataset. generate_dataset(period, seed=2026): entities, opening TB, prepaids, assets, leases, notes, insurance, postage.
- Integer-cent math. split_evenly, allocate_by_ratio (largest-remainder; weights sum to 10000 bps).
- Recurring-entry engine. CloseEngine.run(): refuse invalid splits/postage, post 9 categories, balance gate per entity.
- Ledger + balance control. Ledger.post() raises OutOfTieError on aggregate or per-entity imbalance.
- Read-only preflight family. recurring_register, tb_package, group_operations, project_job_cost, personal_property_tax, payroll_recovery, headcount, je_package, budget_variance, cash_draw, trial_balance, audit_pbc, consolidation — never post.
- Schedule to GL tie-out. _tie_out() -> TieResult per schedule; prepaid ties to acct 1400, fixed-fee to 2350, insurance to 1450, postage to 1460.
- Shadow recompute (C9). sentinel/shadow.py re-derives (entity, category, account) -> (dr, cr) importing nothing from the engine; the posted register must match to the cent.
- Close Sentinel C1-C10. run_sentinel(dataset, result) -> SentinelReport; any CRITICAL => not clean.
- Register / TB / report. write_outputs(): je_register.md/.json, trial_balance.md/.json, schedules.json, close_report.md, sentinel.json.
- Autonomous close loop. autonomous_close_loop(): observe -> detect -> remediate -> re-verify -> gate; resync the earliest drifted category from the seeded source of record; verdict AUTO-POSTED / AUTO-POSTED (PARTIAL) / HALTED. Deterministic and offline — no model calls.
- Verdict + exit-code gate. cli.main() and loop.verdict_exit_code(); a CRITICAL finding, a refused entry, or a failed tie => exit 1.
Instruction set
every public command| Command | Operation | Output | Exit | Artifacts |
|---|---|---|---|---|
python -m close_engine.generate --period 2026-03 --seed 2026 | Inspect the seeded synthetic dataset | Prints the fictional entity group and sub-ledgers | 0 | none (stdout only) |
python -m close_engine --period 2026-03 --out ./output | Run a full month-end close (Sentinel on by default) | Tie-out summary + 'Close status: CLEAN' | 0 clean; 1 if refused / failed-tie / unbalanced / CRITICAL | output/je_register.md+.json, trial_balance.md+.json, schedules.json, close_report.md, sentinel.json |
python run.py --period 2026-03 --out ./output | Same close via the convenience wrapper | Identical to python -m close_engine | 0 / 1 (same semantics) | same as above |
python -m close_engine --demo-guardrails | Inject 12 faults; prove each is caught by its control | PASS table + 'Demo verdict: ALL GUARDRAILS HELD (12 faults, baseline clean)' | 0 only if baseline clean AND all 12 caught | none (stdout only) |
python -m close_engine.loop --demo --out output | Drift a close + a locked prior; run the autonomous loop | 'Verdict: AUTO-POSTED (PARTIAL)' + turn-by-turn log | 0 (AUTO-POSTED / PARTIAL); 1 (HALTED) | output/autonomous_close_loop.md + .html |
python -m close_engine.recurring_register --input samples/sample-recurring-register.json | Read-only preflight of a recurring-JE register | Preflight summary + 'Verdict: PASS' | 0 PASS; non-zero on NEEDS REVIEW | none; validation only (0 posting actions, 0 import payloads) |
python -m pytest -q | Run the test suite | pytest pass/fail summary | 0 if all pass | none |
Benchmarks
measured demo results| Measure | Result |
|---|---|
| Clean close, trial balance10 | 3,603,311.50 USD (Dr = Cr), 9 posted / 0 refused, exit 0 The March close balances to the cent with nothing refused |
| Guardrail fault coverage11 | 12 / 12 faults caught, baseline 0 findings, exit 0 Every injected close error caught by its mapped control |
| Autonomous loop remediation12 | 18 -> 1 criticals over 4 turns; verdict AUTO-POSTED (PARTIAL), 1 quarantined, exit 0 The loop clears in-period drift down to a single quarantined item and still posts |
| Postage exact-route allocation13 | 4 / 5 meter rows routed; acct 1460 clears to 0.00 in every entity Four of five postage rows route to an approved job; the clearing account nets to zero |
Control characteristics
engineeringPlain terms
The posting engine runs against seeded synthetic data and never touches a real ERP or source of record. Every read-only preflight is explicit that a mechanically clean result is only 'READY FOR HUMAN REVIEW' — source refresh and tie, mapping, and controller sign-off remain manual human gates. The autonomous loop replaces the person only for deterministic, logged in-period remediation, and still HALTS on an opening imbalance (C1) and quarantines a locked-period tamper (C10) rather than acting unilaterally.
Engineering
Deterministic envelope. seeded fictional inputs, offline default mode.
Demo gate. deterministic-policy
| Authority | Level | Note |
|---|---|---|
| 1 | HALT (C1 opening imbalance) | Action-authority precedence in the autonomous loop, not an evidence hierarchy. The loop has no authority to invent a broken opening carryforward, so it refuses to post and escalates to a human. HALT_CONTROLS = {C1} in loop.py; exit 1. |
| 2 | QUARANTINE (C10 locked-period tamper) | A signed-off, locked prior period was altered; the loop will not overwrite it — it holds and logs, and still posts the current period. QUARANTINE_CONTROLS = {C10} in loop.py; verdict AUTO-POSTED (PARTIAL). |
| 3 | AUTO-REMEDIATE (C2-C9 in-period drift) | The loop is authorized to resync a drifted recurring-entry category to the seeded sub-ledger of record. AUTO_REMEDIABLE = {C2,C3,C4,C5,C6,C7,C8,C9} in loop.py. |
| Severity | Verdict | Action |
|---|---|---|
| Sentinel: any CRITICAL (C1-C10) | NOT CLEAN | Close blocked; CLI exit 1 |
| Sentinel: WARN (e.g. C7 step-change) | clean, reviewer-escalated | Close proceeds; flagged for a reviewer |
| Sentinel: INFO | clean | Corroborated note kept in the audit trail |
| Loop: auto-remediable CRITICAL (C2-C9) | AUTO-POSTED | Resync the category from the source of record; exit 0 |
| Loop: C10 locked-period tamper | AUTO-POSTED (PARTIAL) | Quarantine + log; post the current period; exit 0 |
| Loop: C1 opening imbalance | HALTED | Escalate to a human; exit 1 |
| Preflight: gate fails | NEEDS REVIEW | Read-only; never posts (0 posting actions / 0 import payloads) |
- All money is integer cents; tie-outs are exact (money.py).
- Every JE balances debits == credits AND each entity leg self-balances, or it is refused (model.Ledger.post raises OutOfTieError).
- Allocation weights must sum to exactly 10000 bps (100.00%) or the entry is refused, never coerced (money.allocate_by_ratio; engine refuses invalid splits).
- Largest-remainder (Hamilton) allocation and split_evenly: parts sum exactly to the total, with no penny lost or created (money.py).
- C9 shadow recompute: a second implementation importing nothing from the engine must agree to the cent before the close stands (sentinel/shadow.py).
- Byte-identical settled register: the autonomous loop's output matches a clean engine run; it never invents a number (loop.py).
- Period lock (C10): each closed register is sealed with a hash, so a later mutation of a locked period is detected on recompute.
- Fully seeded and reproducible for a given seed; refused entries are reported, not silently posted.
- Non-zero exit code on any refused entry, failed schedule tie, unbalanced TB, or CRITICAL finding — so it can gate a pipeline (cli.py).
Authoritative close (posting). CloseEngine.run() computes and posts the nine recurring JEs, writes the register / TB / report, and Sentinel gates the exit code (cli.py).
Read-only preflight (validation). 13 validation-only modules validate an external JSON package and return PASS / NEEDS REVIEW; they never create a JE, import payload, or posting action (recurring_register.py and siblings).
Guardrail demo (proof). --demo-guardrails injects 12 seeded faults and asserts each is caught by its mapped control at a qualifying severity (faults.py).
Autonomous close loop (remediation). loop.py drives a drifted close back to a certifiable state (observe -> remediate -> re-verify -> gate) with a deterministic, logged gate policy. It is always offline and deterministic, with no model calls.
Operating limits
what it refuses to do- Single functional currency only — there is no FX translation layer, and no control asserts anything about rate selection or translation differences.14
- The 13 validation-only preflights never create a journal entry, import payload, posting action, or source-system mutation — a clean result is only 'READY FOR HUMAN REVIEW'.15
- The autonomous loop refuses to post on a broken opening carryforward (HALT / C1) and never auto-overwrites a signed-off locked prior period (QUARANTINE / C10); it invents no numbers and its settled register is byte-identical to a clean run.16
- C9 proves dataset-to-register translation, not dataset truth: if the raw sub-ledger is wrong, both independent implementations agree on the same wrong number, and offsetting shifts within one (entity, category, account) GL cell are invisible to GL-level controls.17
See it run
animated overview
Integration
how to run itDistribution: public repository, MIT license.
- 1monthly-close-automation/README.md 'What the engine computes' table; close_engine/engine.py run() posts 9 categories
- 2close_engine/sentinel/controls.py ALL_CONTROLS list (C1..C10)
- 3close_engine/faults.py FAULTS dict; verified via `python -m close_engine --demo-guardrails` -> 'ALL GUARDRAILS HELD' (exit 0)
- 4python -m pytest --collect-only -q (monthly-close-automation/); verified 2026-07-20 and pinned in site-datasheets/counts.json
- 5close_engine/money.py (to_cents, split_evenly, allocate_by_ratio)
- 6close_engine/generate.py generate_dataset(); output/close_report.md 'Entity group'
- 7monthly-close-automation/README.md 'Real example output' console summary + output/close_report.md; re-run: python -m close_engine --period 2026-03 --out ./output
- 8monthly-close-automation/README.md 'Fault-injection demo'; re-run: python -m close_engine --demo-guardrails
- 9monthly-close-automation/README.md 'Autonomous Close Loop' + output/autonomous_close_loop.md; re-run: python -m close_engine.loop --demo --out output
- 10Ran python -m close_engine --period 2026-03 (seed 2026); matches output/trial_balance.md and README console summary (Dr 3,603,311.50 / Cr 3,603,311.50 [OK])
- 11Ran python -m close_engine --demo-guardrails -> 'ALL GUARDRAILS HELD (12 faults, baseline clean)'
- 12output/autonomous_close_loop.md (19 initial findings); re-run: python -m close_engine.loop --demo --out output
- 13output/close_report.md tie-out; README console summary 'Postage routes : 4/5 meter rows'
- 14monthly-close-automation/README.md 'Scope and assumptions'
- 15monthly-close-automation/README.md validation-only sections; close_engine/recurring_register.py and siblings
- 16close_engine/loop.py (HALT_CONTROLS, QUARANTINE_CONTROLS); README 'Autonomous does not mean ungated'
- 17monthly-close-automation/README.md 'Scope and assumptions'
Show us where the hours go.
One conversation: you describe the work that consumes your team's month; we tell you plainly what this engine can take over, what it can't, and what a scoped first phase would cost. Your people keep approval authority.
Book a free consultation