monthly-close-control-plane 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. monthly_close_control_plane-0.1.2/CONTRIBUTING.md +28 -0
  2. monthly_close_control_plane-0.1.2/LICENSE +21 -0
  3. monthly_close_control_plane-0.1.2/MANIFEST.in +7 -0
  4. monthly_close_control_plane-0.1.2/PKG-INFO +328 -0
  5. monthly_close_control_plane-0.1.2/README.md +303 -0
  6. monthly_close_control_plane-0.1.2/SECURITY.md +22 -0
  7. monthly_close_control_plane-0.1.2/closecontrol/__init__.py +5 -0
  8. monthly_close_control_plane-0.1.2/closecontrol/cli.py +134 -0
  9. monthly_close_control_plane-0.1.2/closecontrol/engine.py +419 -0
  10. monthly_close_control_plane-0.1.2/closecontrol/errors.py +18 -0
  11. monthly_close_control_plane-0.1.2/closecontrol/loader.py +274 -0
  12. monthly_close_control_plane-0.1.2/closecontrol/models.py +62 -0
  13. monthly_close_control_plane-0.1.2/closecontrol/pipeline_cli.py +261 -0
  14. monthly_close_control_plane-0.1.2/closecontrol/report.py +377 -0
  15. monthly_close_control_plane-0.1.2/closecontrol/viewer.py +394 -0
  16. monthly_close_control_plane-0.1.2/docs/architecture.md +80 -0
  17. monthly_close_control_plane-0.1.2/docs/follow-on-safety-layers.md +48 -0
  18. monthly_close_control_plane-0.1.2/examples/account_mapping.csv +7 -0
  19. monthly_close_control_plane-0.1.2/examples/current_trial_balance.csv +8 -0
  20. monthly_close_control_plane-0.1.2/examples/prior_trial_balance.csv +8 -0
  21. monthly_close_control_plane-0.1.2/examples/review_note.json +5 -0
  22. monthly_close_control_plane-0.1.2/examples/subledger_balances.csv +3 -0
  23. monthly_close_control_plane-0.1.2/monthly_close_control_plane.egg-info/PKG-INFO +328 -0
  24. monthly_close_control_plane-0.1.2/monthly_close_control_plane.egg-info/SOURCES.txt +38 -0
  25. monthly_close_control_plane-0.1.2/monthly_close_control_plane.egg-info/dependency_links.txt +1 -0
  26. monthly_close_control_plane-0.1.2/monthly_close_control_plane.egg-info/entry_points.txt +3 -0
  27. monthly_close_control_plane-0.1.2/monthly_close_control_plane.egg-info/requires.txt +7 -0
  28. monthly_close_control_plane-0.1.2/monthly_close_control_plane.egg-info/top_level.txt +1 -0
  29. monthly_close_control_plane-0.1.2/pyproject.toml +55 -0
  30. monthly_close_control_plane-0.1.2/schemas/account_mapping.csv +1 -0
  31. monthly_close_control_plane-0.1.2/schemas/canonical_trial_balance.csv +1 -0
  32. monthly_close_control_plane-0.1.2/schemas/subledger_balances.csv +1 -0
  33. monthly_close_control_plane-0.1.2/setup.cfg +4 -0
  34. monthly_close_control_plane-0.1.2/tests/test_close_loop.py +73 -0
  35. monthly_close_control_plane-0.1.2/tests/test_engine.py +568 -0
  36. monthly_close_control_plane-0.1.2/tests/test_loader.py +244 -0
  37. monthly_close_control_plane-0.1.2/tests/test_pipeline_cli.py +33 -0
  38. monthly_close_control_plane-0.1.2/tests/test_report_and_cli.py +967 -0
  39. monthly_close_control_plane-0.1.2/tests/test_viewer.py +339 -0
  40. monthly_close_control_plane-0.1.2/tests/test_workflow_examples.py +772 -0
@@ -0,0 +1,28 @@
1
+ # Contributing
2
+
3
+ Keep this project in its narrow role: a local, deterministic review-pack generator. No contribution should give it authority to post journals, make payments, lodge returns, lock periods, send reports, or approve a close.
4
+
5
+ ## Data boundary
6
+
7
+ - Use fabricated fixtures. Keep client trial balances, subledgers, workpapers, review packs, credentials, `.env` files, tokens and screenshots from a live accounting system out of the repository.
8
+ - Put fabricated CSV fixtures under `examples/` and header-only schema references under `schemas/`. The `.gitignore` blocks ordinary CSV files outside those two directories.
9
+ - Treat source CSV content and review notes as untrusted input. Keep the fail-closed validation and the spreadsheet-formula safeguards in place.
10
+
11
+ ## Local verification
12
+
13
+ Python 3.10 or newer. The repository uses `uv` and commits its lock file.
14
+
15
+ ```bash
16
+ uv lock --check
17
+ uv sync --locked --all-extras
18
+ uv run pytest
19
+ uv build
20
+ ```
21
+
22
+ For a behaviour change, add or update a focused test under `tests/`. Keep the output deterministic: no wall-clock timestamps, client identifiers or hidden state in a review pack.
23
+
24
+ ## Pull requests
25
+
26
+ Explain which control or boundary your change affects, include the test result, and name any operational limitation that remains. Never present a review acknowledgement as an approved or completed close.
27
+
28
+ For a potential security vulnerability, follow [SECURITY.md](SECURITY.md), and keep credentials, client data and exploit details out of the issue tracker.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Duguid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include LICENSE
2
+ include README.md
3
+ include SECURITY.md
4
+ include CONTRIBUTING.md
5
+ recursive-include docs *.md
6
+ recursive-include examples *.csv *.json
7
+ recursive-include schemas *.csv
@@ -0,0 +1,328 @@
1
+ Metadata-Version: 2.4
2
+ Name: monthly-close-control-plane
3
+ Version: 0.1.2
4
+ Summary: A review-first monthly close control pack for validated trial-balance exports
5
+ Author: Ryan Duguid
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ryanduguid/monthly-close-control-plane
8
+ Project-URL: Repository, https://github.com/ryanduguid/monthly-close-control-plane.git
9
+ Project-URL: Issues, https://github.com/ryanduguid/monthly-close-control-plane/issues
10
+ Keywords: accounting,month-end-close,reconciliation,xero,controls
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Provides-Extra: dev
19
+ Requires-Dist: ruff>=0.11; extra == "dev"
20
+ Requires-Dist: mypy>=1.14; extra == "dev"
21
+ Requires-Dist: build>=1.2; extra == "dev"
22
+ Requires-Dist: pytest>=8; extra == "dev"
23
+ Requires-Dist: PyYAML<7,>=6.0.3; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # monthly-close-control-plane
27
+
28
+ [![tests](https://github.com/ryanduguid/monthly-close-control-plane/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanduguid/monthly-close-control-plane/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-4F485E.svg?labelColor=04001F)](LICENSE) [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-5C2D91.svg?logo=python&logoColor=white&labelColor=04001F)](https://www.python.org/downloads/)
29
+
30
+ The repository name is the public project identity; the `monthly-close-control-plane` distribution and `close-control` command remain compatibility identifiers.
31
+
32
+ A small, **review-first** monthly-close control pack for a validated trial-balance export. You point it at a current and a prior trial balance and it hands you an exception pack for close review:
33
+
34
+ - `close-summary.md` answers "what needs my attention this close?": a concise, deterministic review pack with an overall status, the thresholds used, source evidence, and an exception table a reviewer reads top to bottom.
35
+ - `exceptions.csv` answers "which accounts, by how much, and why?": filterable exception detail for Excel or Power BI, one row per exception with values, differences, thresholds, and a suggested reviewer action.
36
+ - `close-review-pack.json` answers "what exactly did this run look at?": structured evidence, thresholds, source hashes, and any supplied review acknowledgement, for archiving or downstream tooling.
37
+
38
+ The pack surfaces material YTD variances, new and missing accounts, account metadata changes, unmapped accounts, and supplied subledger differences as explicit exceptions. Output has only `PASS`, `REVIEW`, and `BLOCKED` states. A reviewer, not the tool, decides whether a close is acceptable.
39
+
40
+ It is intentionally narrow:
41
+
42
+ ```text
43
+ Validated trial-balance export
44
+ |
45
+ v
46
+ Exact control gates and variance checks
47
+ |
48
+ v
49
+ Explicit exception queue
50
+ |
51
+ v
52
+ Human review and workpaper acknowledgement
53
+ ```
54
+
55
+ The first MVP accepts the canonical CSV written by [xero-trial-balance-export](https://github.com/ryanduguid/xero-trial-balance-export) (`xero-trial-balance-export`). Each file must contain exactly one tenant and one report date; current and prior files must name the same tenant, and the prior date must be earlier. It does **not** connect to Xero, store OAuth tokens, write journals, make payments, lodge BAS, lock a period, distribute a client report, or claim that a close has been approved.
56
+
57
+ ## Quick demo
58
+
59
+ The repository contains fabricated data only. Do not commit client trial balances, workpapers, exports, or credentials.
60
+
61
+ `examples/` is the assault course: every move the tool has, run against
62
+ fabricated data, with nothing at stake. Learn the flags here before pointing
63
+ it at a real ledger.
64
+
65
+ ```bash
66
+ python -m pip install -e ".[dev]"
67
+
68
+ close-control review \
69
+ --current examples/current_trial_balance.csv \
70
+ --prior examples/prior_trial_balance.csv \
71
+ --mapping examples/account_mapping.csv \
72
+ --subledger examples/subledger_balances.csv \
73
+ --absolute-threshold 10000 \
74
+ --percentage-threshold 0.10 \
75
+ --reconciliation-tolerance 0.01 \
76
+ --review-note examples/review_note.json \
77
+ --output outputs/demo
78
+ ```
79
+
80
+ The demo exits `2` because its deliberately fabricated exceptions need human review. It writes the three pack files described above.
81
+
82
+ Use exit code `0` only for an all-`PASS` pack, `2` for `REVIEW` or `BLOCKED`, and `1` for a malformed file, an invalid command configuration, or an `--output` path that cannot be written.
83
+
84
+ To run the check on a schedule in CI, copy [examples/github-actions-close-check.yml](examples/github-actions-close-check.yml) into `.github/workflows/`.
85
+ It runs against a repo-stored synthetic trial balance and fails the job when the pack is `BLOCKED`.
86
+
87
+ To run this pack against the sibling gateway's same-financial-year sample CSVs (not the Varrock June/July demo pair, which crosses the 1 July reset), see [examples/close-loop.md](examples/close-loop.md). That loop is local files only; it does not connect to Xero.
88
+
89
+ ## Local close workbench
90
+
91
+ `close-control workbench` is a local façade over the same validation, control
92
+ engine, and three-file writer used by `close-control review`. It is useful when
93
+ the close process starts with two already-created canonical exports in an
94
+ access-controlled directory outside this repository:
95
+
96
+ ```bash
97
+ close-control workbench \
98
+ --current C:\close-data\current.csv \
99
+ --prior C:\close-data\prior.csv \
100
+ --mapping C:\close-data\account-mapping.csv \
101
+ --subledger C:\close-data\subledger.csv \
102
+ --output C:\close-data\review-pack
103
+ ```
104
+
105
+ It writes exactly `close-summary.md`, `exceptions.csv`, and
106
+ `close-review-pack.json`. Open or import `exceptions.csv` in Excel or Power
107
+ Query if useful, then investigate and document conclusions through your normal
108
+ workpaper process. The command never starts Excel, creates a workbook, calls
109
+ Xero, reads OAuth credentials or tokens, calls an AI service, posts anything,
110
+ or copies the supplied sources. It records review evidence only; it does not
111
+ approve or close a period.
112
+
113
+ Keep inputs and output outside the checkout: repository fixtures remain
114
+ fabricated, and the existing `.gitignore` rules deliberately prevent ordinary
115
+ exports and generated packs becoming repository content.
116
+
117
+ ## Viewing an existing pack
118
+
119
+ `close-control view` is the read-only half of the workbench: it loads a
120
+ generated pack, proves the three files still agree with each other, and prints
121
+ a review sheet. It never writes, renames or deletes anything, and it cannot
122
+ change what the engine computed.
123
+
124
+ ```bash
125
+ close-control view --pack-dir C:\\close-data\\review-pack
126
+ ```
127
+
128
+ Before displaying anything it fails closed on: a missing artefact; JSON that is
129
+ not valid UTF-8, not valid JSON, or carries unknown, missing or duplicated
130
+ top-level members; a threshold or source digest that no longer parses as the
131
+ writer rendered it; a `close-summary.md` whose overall status, source-evidence
132
+ digests or review-boundary statement disagree with the JSON (including a second,
133
+ conflicting status line); and an `exceptions.csv` whose header, row count or
134
+ any cell disagrees with the JSON exceptions, honouring the writer's
135
+ formula-injection guard exactly. On success the sheet ends with the SHA-256 of
136
+ each artefact's exact bytes, so the displayed evidence can itself be archived.
137
+ Exit code is 0 when a pack was verified and shown, 1 when verification failed.
138
+
139
+ ## Worked example
140
+
141
+ Running the quick-demo command above against the fabricated fixtures in `examples/` prints:
142
+
143
+ ```text
144
+ close-control: REVIEW; 8 exception(s)
145
+ json: outputs/demo/close-review-pack.json
146
+ summary: outputs/demo/close-summary.md
147
+ exceptions: outputs/demo/exceptions.csv
148
+ ```
149
+
150
+ `close-summary.md` opens with the status, scope, and source digests, then lists every exception (abridged here to four of the eight rows):
151
+
152
+ ```markdown
153
+ # Monthly Close Review Pack
154
+
155
+ **Overall status: REVIEW**
156
+
157
+ This pack is a review aid. It does not approve a close, post a journal, make a payment, lodge a return, or lock a period.
158
+
159
+ ## Scope
160
+
161
+ - Current report date(s): 2026-07-31
162
+ - Prior report date(s): 2026-06-30
163
+ - Material variance thresholds: $10000.00 and 10.00%
164
+ - Reconciliation tolerance: $0.01
165
+ - Exceptions: 8 total; 0 blocked; 8 requiring review.
166
+
167
+ ## Exceptions
168
+
169
+ | Status | Control | Tenant | Account | Difference | Reason |
170
+ | --- | --- | --- | --- | ---: | --- |
171
+ | REVIEW | account_mapping | Varrock Ventures Pty Ltd | 6000 / Operating Expenses | n/a | Current account has no supplied review-group mapping. |
172
+ | REVIEW | financial_year_reset | n/a | n/a | n/a | Current ReportDate 2026-07-31 and prior ReportDate 2026-06-30 fall in different Australian financial years (1 July to 30 June). YTD figures reset on 1 July, so this YTD-vs-YTD comparison crosses a year reset and the period_variance verdicts for profit-and-loss-style rows are not meaningful. |
173
+ | REVIEW | period_variance | Varrock Ventures Pty Ltd | 1000 / Operating Bank | 15000.00 | YTD net balance moved beyond both configured materiality thresholds. |
174
+ | REVIEW | subledger_reconciliation | Varrock Ventures Pty Ltd | 2000 / Trade Creditors | -250.00 | Current trial-balance balance differs from the supplied subledger beyond tolerance. |
175
+ ```
176
+
177
+ `exceptions.csv` carries the same exceptions with full numeric detail. The Operating Bank variance row (wrapped here for readability):
178
+
179
+ ```csv
180
+ control,status,tenant,account_id,account_code,account_name,review_group,current_value,prior_value,difference,threshold,percentage_change,reason,reviewer_action
181
+ period_variance,REVIEW,Varrock Ventures Pty Ltd,100,1000,Operating Bank,Cash and cash equivalents,120000.00,105000.00,15000.00,10000.00,14.29%,
182
+ YTD net balance moved beyond both configured materiality thresholds.,
183
+ "Investigate the driver, retain supporting evidence, and document the reviewer conclusion."
184
+ ```
185
+
186
+ The reviewer reads this as: the Operating Bank YTD balance moved from $105,000.00 to $120,000.00, a $15,000.00 (14.29%) change that clears both the $10,000.00 absolute threshold and the 10% threshold, so a human must investigate the driver and document a conclusion.
187
+
188
+ `close-review-pack.json` records the same exception as structured evidence next to the thresholds and the source digests (abridged):
189
+
190
+ ```json
191
+ {
192
+ "exceptions": [
193
+ {
194
+ "account_code": "1000",
195
+ "account_name": "Operating Bank",
196
+ "review_group": "Cash and cash equivalents",
197
+ "control": "period_variance",
198
+ "current_value": "120000.00",
199
+ "prior_value": "105000.00",
200
+ "difference": "15000.00",
201
+ "percentage_change": "14.29%",
202
+ "threshold": "10000.00",
203
+ "status": "REVIEW",
204
+ "tenant": "Varrock Ventures Pty Ltd"
205
+ }
206
+ ],
207
+ "overall_status": "REVIEW",
208
+ "thresholds": {
209
+ "absolute_variance": "10000.00",
210
+ "percentage_variance": "10.00%",
211
+ "reconciliation_tolerance": "0.01"
212
+ }
213
+ }
214
+ ```
215
+
216
+ ## Canonical trial-balance contract
217
+
218
+ The initial input is the ten-column, normalised trial-balance schema from `xero-trial-balance-export`:
219
+
220
+ ```text
221
+ ReportDate,Tenant,Section,AccountID,AccountName,AccountCode,Debit,Credit,YTDDebit,YTDCredit
222
+ ```
223
+
224
+ `Tenant` plus `AccountID` is the control key. `AccountCode` and `AccountName` are display attributes, not stable identifiers. The loader rejects unknown/missing columns, duplicate control keys, malformed ISO dates, empty identifiers, and malformed monetary values.
225
+
226
+ The current-period `Debit`/`Credit` pair represents movement. `YTDDebit`/`YTDCredit` represents the position used for variance comparison. All values are read as exact decimals.
227
+
228
+ ## Optional mapping and reconciliation inputs
229
+
230
+ An account mapping is a two-column CSV:
231
+
232
+ ```text
233
+ AccountID,ReviewGroup
234
+ ```
235
+
236
+ Any current TB account that is missing from a supplied mapping remains in the pack as a `REVIEW` exception. The mapping is a review label; it does not transform source numbers.
237
+
238
+ An optional subledger CSV must have:
239
+
240
+ ```text
241
+ Tenant,AccountID,SubledgerBalance
242
+ ```
243
+
244
+ `SubledgerBalance` must use the same signed convention as `YTDDebit - YTDCredit`: debit balances positive; credit balances negative. Each supplied subledger row is compared only with the matching current TB account. A missing GL account, or a difference beyond `--reconciliation-tolerance`, requires review.
245
+
246
+ ## Human acknowledgement
247
+
248
+ If a reviewer wants the pack to record that it was read, supply a separate JSON file:
249
+
250
+ ```json
251
+ {
252
+ "reviewer_initials": "RD",
253
+ "reviewed_on": "2026-08-08",
254
+ "comment": "Reviewed demo exceptions; no client close was approved by this example."
255
+ }
256
+ ```
257
+
258
+ `reviewed_on` must not be earlier than the current `ReportDate`. A note dated before the period it claims to review is rejected as a malformed input: the run stops with exit `1` and writes no pack.
259
+
260
+ An acknowledgement is evidence of a human action only. It **never** changes `REVIEW` or `BLOCKED` to `PASS`, and it never asserts that a period has been closed.
261
+
262
+ ## Design and integrity
263
+
264
+ A close can be technically balanced and still need review. This tool keeps the evidence visible:
265
+
266
+ - Exact `Decimal` arithmetic for money controls, never binary floating point.
267
+ - Schema, duplicate-key, date, and numeric gates fail closed.
268
+ - Current-period and YTD debits must exactly equal credits.
269
+ - Material YTD variances, new/missing accounts, account metadata changes, unmapped accounts, and supplied subledger differences become explicit exceptions.
270
+ - A YTD variance is raised only when it clears both the absolute and the percentage threshold, with one carve-out: an account whose prior YTD balance is nil has no percentage change to compute, so the absolute threshold decides alone. Those exceptions name the absolute threshold only and leave `percentage_change` blank, rather than reporting that a percentage test passed that never ran.
271
+ - Output has only `PASS`, `REVIEW`, and `BLOCKED` states. A reviewer, not the tool, decides whether a close is acceptable.
272
+ - Source SHA-256 digests travel with the generated review pack so its source files can be identified later. Each digest is calculated from the same immutable byte snapshot the loader parses, so a file replaced during a run cannot be misidentified as the source of the calculations.
273
+ - Spreadsheet-facing source text whose first non-whitespace character is `=`, `+`, `-` or `@` is neutralised with a leading apostrophe. This includes identifier- and number-shaped text such as `+unsafe`, `@123` and `-1000`; the guard does not try to decide which formula-looking values a particular spreadsheet may evaluate. Every exception table cell rendered into `close-summary.md` is flattened onto one line, and its backslashes are escaped before its pipes so that neither a pipe nor a backslash shielding one can add a cell and shift the columns a reviewer reads. A reviewer-note comment keeps its line breaks: a multi-line comment renders as an indented blockquote under the acknowledgement item, with each line escaped the same way and a leading `#` escaped so quoted text cannot forge a document heading.
274
+ - `exceptions.csv` is written with a UTF-8 byte-order mark, matching the canonical input files, so a spreadsheet reads non-ASCII entity and account names correctly.
275
+ - The three pack files are staged beside their destinations and moved into place only once all three have been written. If one cannot be replaced (a reviewer holding `exceptions.csv` open is the usual cause), the files already moved are rolled back to the content they replaced, so the previous pack survives whole instead of half describing one trial balance and half describing another. A failed run never deletes a pack file it did not write. Run one export at a time into a given `--output` directory; concurrent runs are not serialised.
276
+ - Amounts are rendered with at least two decimal places and never fewer than the value carries. A percentage is rendered with at least two places and always enough to show its leading significant digit, so neither a tolerance finer than one cent nor a threshold finer than a hundredth of a per cent is flattened to `0.00`.
277
+
278
+ ### What formula neutralisation covers, exactly
279
+
280
+ The escaping in `exceptions.csv` applies to the five source-controlled text fields: `tenant`, `account_id`, `account_code`, `account_name`, and `review_group`. For those fields:
281
+
282
+ Neutralised (prefixed with an apostrophe so a spreadsheet reads them as text):
283
+
284
+ - Any value whose first non-whitespace character is `=`, `+`, `-` or `@`. For example, `=SUM(A1)` becomes `'=SUM(A1)`, `@123` becomes `'@123`, and a leading tab cannot bypass the check.
285
+ - Identifier- and number-shaped source text receives the same treatment. Account codes such as `-1000` remain visually recognisable in a spreadsheet but are explicitly stored as text.
286
+
287
+ Passed through unchanged:
288
+
289
+ - Values that do not start with a formula trigger after leading whitespace.
290
+ - Values already prefixed with an apostrophe; the guard does not add a second one.
291
+ - Numeric fields rendered by the tool (`current_value`, `difference`, thresholds and percentages) do not pass through this text guard and retain their ordinary numeric representation.
292
+
293
+ ## Data and operational boundaries
294
+
295
+ - Use a separate, access-controlled working directory for client source files and outputs.
296
+ - Keep this checkout limited to fabricated fixtures. Its `.gitignore` blocks CSVs outside `examples/` and `schemas/`, and blocks all three generated pack files by name wherever `--output` points them, including inside those two fixture directories.
297
+ - Produce the source CSV through a read-only export workflow. Live Xero OAuth, token storage, and client authorisation are deliberately outside this MVP.
298
+ - Do not use this as tax, financial, audit, or legal advice. It is a configurable review aid that requires professional judgement.
299
+
300
+ ## Development
301
+
302
+ ```bash
303
+ python -m pip install -e ".[dev]"
304
+ pytest
305
+ python -m build
306
+ ```
307
+
308
+ The test suite covers schema gates, exact balancing, variance and metadata exceptions, mapping and subledger checks, deterministic pack generation, acknowledgement parsing, and the command-line exit contract.
309
+
310
+ Continuous integration verifies the committed `uv.lock`, runs the test suite on Python 3.10, 3.11, 3.12, and 3.13, then builds and smoke-tests the wheel with the fabricated demo. CodeQL scans the Python source, and Dependabot is configured to propose updates for `uv` dependencies and pinned GitHub Actions. See [CONTRIBUTING.md](CONTRIBUTING.md) for the local verification and data-handling requirements.
311
+
312
+ ## Related
313
+
314
+ The next layers exist as separate repositories. This project stays a local review-pack generator; it does not grow a Xero client or a tax-advice engine.
315
+
316
+ - [xero-ai-review-gateway](https://github.com/ryanduguid/xero-ai-review-gateway) - a fixed-policy, synthetic-data review boundary for AI-assisted trial-balance analysis. No OAuth, no mutation tools.
317
+ - [Tax Radar AU](https://github.com/ryanduguid/tax-radar-au) - a provenance-first monitor that turns source-version metadata into a technical-review queue.
318
+
319
+ [examples/close-loop.md](examples/close-loop.md) runs close-control and the sibling gateway on local files: `close-control` reviews the gateway's May/June sample CSVs, then the gateway evaluates its own bundled context.
320
+
321
+ ## Roadmap
322
+
323
+ The next layers are deliberately separated from the control engine; they already live in the sibling repositories above. The close-loop example runs the gateway on local files only.
324
+
325
+ See [docs/follow-on-safety-layers.md](docs/follow-on-safety-layers.md) for the intended boundary contracts.
326
+
327
+
328
+ MIT licensed. Boundary statement: [DISCLAIMER.md](DISCLAIMER.md).