# Apostol Reconciliation — a demo you can run on your own files

No account. No cloud. No subscription. Nothing leaves your laptop. You keep
using every tool you already use — this runs *alongside* them and does one thing
your current software can't: it produces a **cryptographically re-derivable
proof** of exactly what was reconciled.

Boundary: this is an additive CPA/auditor workflow demo on top of Apostol OS,
not core OS, kernel, or AI Node functionality.

## What it does, in one sentence

You give it two CSV files you already export — a **bank statement** and a
**general ledger** — and it produces a penny-exact reconciliation (matched pairs,
unmatched on each side, exceptions) with a witness hash that lets anyone re-run
the exact same inputs and get the exact same answer.

## Why an auditor cares

Your engagement software reconciles. It does not hand you a hash that a reviewer,
a QA partner, or a PCAOB inspector can use to **replay your work byte-for-byte
and confirm it**. This does. The reconciliation is integer-exact to the cent (no
floating-point rounding ever touches a dollar), it refuses bad rows by name
instead of silently dropping them, and it runs entirely on your machine.

## The files

- `bank.csv` — a sample bank export (`Date, Amount, Description`).
- `ledger.csv` — a sample general-ledger export (`Date, Amount, Memo`).

These are **synthetic** — made-up numbers for the demo. To reconcile your own
month, replace these two files with your own exports in the same shape:
a date column (`YYYY-MM-DD`), an amount column (`1298.97`, `-89.99`, or
`(15.00)` for a negative — all three are accepted), and a description column.

## Run it

```bash
bash run_reconcile.sh bank.csv ledger.csv
```

Swap in your own two files the same way:

```bash
bash run_reconcile.sh /path/to/your_bank.csv /path/to/your_gl.csv
```

## What you'll see

A `RECONCILIATION REPORT` with:

- the SHA-256 of each input file (so the report is provably about *those* files);
- how many rows were read, accepted, and **refused** (a corrupted amount or an
  impossible date is refused by name, not silently skipped);
- every matched pair, with the amount, the posting-date gap, and both
  descriptions;
- everything unmatched on each side — your exceptions list;
- an **integer-cent identity check** that must read `HOLDS` (matched + unmatched
  equals accepted, exactly — if it doesn't, the tool tells you);
- a **match-stream witness** (a SHA-256). Re-run the same two files and you get
  the identical witness. Change one cent in one row and the witness changes.
- `Processed on this machine. No network was used.`

## Get a printable workpaper (for the engagement file)

Add `--html` to also write a self-contained, print-ready workpaper you can
attach to your file or print to PDF straight from a browser:

```bash
bash run_reconcile.sh bank.csv ledger.csv --html workpaper.html
```

Open `workpaper.html` in any browser and print to PDF (Ctrl/Cmd-P). It is a
**single file** — no internet, no fonts or scripts pulled from anywhere — with:

- a title block naming both input files and their SHA-256s;
- the summary, the **complete** exceptions list (every unmatched and every
  refused row — never shortened, because that is the part you review), and the
  integer-cent identity-check verdict;
- the **match-stream witness** boxed and labelled, with one line explaining that
  re-running the same two files reproduces the same hash.

On a large file the matched-pairs list shows the first 200 with a visible note
pointing to the full text report (your exceptions are always shown in full). Add
`--html-full` if you want every matched pair inlined in the one HTML file.

The workpaper is **deterministic**: running the same command on the same two
files reproduces a byte-identical HTML file. The evidence that matters — each
input file's SHA-256 and the match-stream witness — is computed from the file
*contents*, so it is identical no matter where the files live or what path you
type. (The report also prints the path you gave it, so two runs from different
folders differ only in that one line, never in a number.)

## Reconcile a whole engagement at once

If you have many clients or many periods, you don't have to run them one at a
time. Write a small **manifest** — a plain CSV that lists each job once — and
reconcile the whole engagement in a single command:

```
label,bank,ledger
acme_2025Q1,acme/bank.csv,acme/ledger.csv
beta_llc_2025Q1,beta/bank.csv,beta/ledger.csv
"gamma inc, 2025Q1",gamma/bank.csv,gamma/ledger.csv
```

- `label` names the client/period. It becomes the output filename, so it has to
  be a safe name (no slashes, not empty). A bad label is refused by name, not
  quietly changed.
- `bank` and `ledger` are the two CSVs for that job, written **relative to the
  manifest file's own folder**. Put the whole engagement in one folder, list the
  files with folder-relative paths, and you can move or copy that folder
  anywhere — it still works.

Run it:

```bash
zig-out/bin/apostol-reconcile-batch manifest.csv --html
```

You get, in a `batch_out` folder next to your manifest:

- one `<label>.report.txt` per job (the same penny-exact report as the
  single-file tool), plus `<label>.workpaper.html` when you pass `--html`;
- one **`batch_index.txt`** — a single summary table with one row per job:
  matched count, exceptions on each side, whether the integer-cent identity
  `HOLDS`, and short fingerprints of each input and of that job's witness;
- one **batch witness** at the bottom of the index: a single SHA-256 over the
  whole engagement. Re-run the same manifest on the same files and you get the
  identical batch witness; change one cent in any one client's file and the
  batch witness changes. It is a one-line, tamper-evident seal for the entire
  engagement, and it does **not** depend on the order you listed the jobs in
  (the index is sorted by label).

If one job can't be reconciled — a file is missing, an input is refused, a file
is over the row cap — that job is **not** silently dropped. It shows up in the
index as `REFUSED` with the exact reason, every other job still runs, and the
command exits with a non-zero status so a script notices something needs your
attention. (Exit code `1` means "finished, but one or more jobs were refused —
see the index"; `0` means every job reconciled.)

Nothing here needs the internet either. It is the same local, integer-exact
engine, run once per job.

## AI Node audit assists

The package also carries the AI Node system — vendored, stdlib-only Python
modules (no pip install, no internet) plus the native Premium Accounting
Angel binaries — as **assists you run alongside** the reconciler above, not a
replacement for it. Every number these print is computed by the vendored
module or the native binary; the `ai_node/*.sh` scripts only convert your CSV
into the shape each module reads and print its report.

| Capability | Command | What it's for |
|---|---|---|
| Duplicate-payment detection + tie-out | `ai_node/run_duplicates.sh samples/ledger.csv` | Fuzzy-propose (trigram similarity) then deterministic-confirm (amount + date exact gate) duplicate candidates; never asserts a duplicate without the exact gate. |
| Anomaly scan | `ai_node/run_anomaly.sh samples/ledger.csv` | Three deterministic exact-arithmetic tests: Benford first-digit MAD, round-number clustering, just-under-materiality-threshold clustering. Every threshold is a named, human-set parameter — the tool never picks its own risk cutoff. |
| Sampling + cross-ledger synthesis + audit envelope | `ai_node/run_sampling.sh samples/ledger.csv` | Deterministic monetary-unit sampling (auditor sets materiality + seed), attributed cross-category totals, and a 3-way VERIFIED / DERIVED / REFUSED envelope that never answers a question the data can't verify. |
| Accounting ratios / depreciation / covenants | `ai_node/ask_accounting.sh current_ratio current_assets=3000 current_liabilities=2000` | Direct pass-through to the native Premium Accounting Angel (`bin/apostol-22-premium-accounting-decide`): current/quick ratio, debt-to-equity, straight-line/DDB/units-of-production depreciation, covenant tests, method-consistency, current/non-current classification. Integer-exact, no allocator, typed refusal on bad input. |
| Portable-engine self-test | `cd ai_node && python3 -m node_core.selftest` | Proves the vendored learning core (`node_core/`) is byte-identical to the sealed canonical engine and runs correctly from this package's own layout. |

**Honest research-side note (read before you rely on any of these):**

- The anomaly and duplicate-detection modules were witnessed against 200 real
  UK-government transactions (Cabinet Office + Crown Commercial Service,
  January 2024) — see `ai_node/audit_pack/anomaly/README.md` and
  `ai_node/audit_pack/match/README.md` for the exact bars and numbers. Run
  against your own CSV, they print `NOTE: running on N records` when N != 200;
  the tests themselves still run and still refuse to flag below their stated
  minimums (e.g. the Benford test abstains below n=100).
- Every anomaly finding is phrased as **"flagged by <rule> at <attributed
  threshold>; finding for human review"** — never a fraud or guilt
  determination. The thresholds (Nigrini MAD cutoff, round-number units,
  materiality band) are the ones set in the module; they are not tuned to
  your data.
- The duplicate-detection module's O(n²) candidate scan was measured at
  n=200 (≈20,000 pair comparisons); it was not measured at production scale
  (10,000+ rows), and its README says so — treat anything past a few hundred
  rows as unmeasured, not unsafe.
- The sampling/synthesis/envelope module (`run_sampling.sh`) will REFUSE to
  answer a question the ledger data cannot verify (e.g. "is this vendor
  legitimate") — that refusal is by design, not a bug.
- `ask_accounting.sh` calls a native, integer-exact decision binary, not a
  language model — it has no natural-language understanding; it takes named
  `field=value` arguments for one of eleven scalar tools (see
  `ai_node/audit_pack/../README.md` inside the package, or run it with no
  arguments to see `unknown tool:` and the field list in `GUIDE.md` above).
- None of this replaces your professional judgment or a PCAOB-defensible
  workpaper process. It is a fast, re-runnable, integer-exact first pass —
  exactly like the reconciler above.

## Auditor skills

The package also ships the **auditor skill pack** (`ai_node/auditor_skills/`) —
a vendored, stdlib-only Python package built on the same node_core engine,
aimed at the fieldwork time-sinks: rollforward continuity checks, the search
for unrecorded liabilities, and lead-sheet tie-outs. Every amount is **integer
pence** (a float, a bool, or a string where money belongs is a **typed
refusal**, never a silent coercion), and every tool carries its own independent
oracle computed by a genuinely different algorithm.

Run each tool on a small JSON input file via the wrapper (wire-only; every
result comes from the pack's `SKILL.run`):

**Rollforward** — `beginning + additions - disposals = ending`; add
`tb_ending_balance` to the JSON and it ties the computed ending to the trial
balance instead (True/False + the exact difference).

```bash
ai_node/run_auditor.sh rollforward ai_node/auditor_skills/examples/rollforward.json
# input:  {"beginning_balance": 1000000, "additions": 250000, "disposals": 100000}
# output: {"provenance": "rollforward_ending: 1000000 + 250000 - 100000 = 1150000",
#          "unit": "pence", "value": 1150000}
```

**Search for unrecorded liabilities** — flags a post-period disbursement iff
its service period predates period-end AND its amount exceeds YOUR stated
threshold. Dates are real-calendar-validated ("2025-04-31" refuses the whole
population by name rather than mis-ordering it).

```bash
ai_node/run_auditor.sh liabilities ai_node/auditor_skills/examples/liabilities.json
# input:  {"period_end": "2025-04-30", "threshold_pence": 50000, "payments": [
#           {"id": "P1", "payment_date": "2025-05-05",
#            "service_period_end": "2025-04-20", "amount_pence": 75000}, ...]}
# output: {"provenance": "...1 of 4 post-period disbursements flagged: ('P1',)",
#          "unit": "ids", "value": ["P1"]}
```

**Lead-sheet tie-out** — groups trial-balance accounts to lead-sheet lines and
proves each group's sum ties to the reported FS line. An account whose group
is not a reported line is a **typed refusal** — never silently dropped (a
dropped account is exactly how a misstatement hides).

```bash
ai_node/run_auditor.sh leadsheet ai_node/auditor_skills/examples/leadsheet.json
# input:  {"accounts": [{"id": "A1", "group": "Cash", "amount_pence": 500000}, ...],
#          "reported": {"Cash": 700000, "AP": -300000}}
# output: value = [["AP", -300000, -300000, true], ["Cash", 700000, 700000, true]]
#         (per line: group, computed sum, reported, ties?)
```

**Conformance gate** — `ai_node/check_auditor.sh` runs the pack's own
falsifiable bar from the bundle layout: the full case battery against each
tool's independent oracle, a poison battery (float money, bool, string
amounts, sub-penny, hostile objects — all must refuse), the compounding
chain, the learn/refuse witness, the professional-judgment boundary, and an
H5 calibration that injects an off-by-one defect into a scratch copy and
proves the gate detects it. Expect `"WIN": true` and `"determinism": true`;
exit 0 only when both hold.

Honest notes:

- **What these tools refuse to judge:** materiality of a difference,
  completeness of a search population, whether a threshold should be lowered,
  and whether a given grouping is the right FS presentation. Those are
  professional-judgment questions the pack answers with a typed refusal
  (`judgment.py`), never a computed verdict.
- **Vendored-copy patches (path wiring only):** the bundled
  `auditor/__init__.py` resolves the BUNDLED `ai_node/node_core` instead of
  the source repo's `node_ai/` (the source shim walks up to a directory that
  does not exist in this package), and the bundled `gate.py` finds node_core
  one level up and puts its H5 calibration scratch copy in a run-time temp
  dir instead of a repo-side scratchpad path. No conformance, poison, or
  determinism check was weakened; the exact diffs are commented inline in
  both bundled files under "BUNDLE PATCH".
- The pack's research-side witness scripts (`learn_witness.py`,
  `birth_witness.py`, `smoke.py`) reference repo fixtures and are not
  vendored; their results are summarized in `ai_node/auditor_skills/README.md`.

## Teach it a new skill

The package ships the **self-teaching skill creator** (`ai_node/skill_birth/`)
— the vendored S053 topic-agnostic skill-birth engine. You describe a task
family in one JSON file (worked examples as input/expected pairs); the engine
runs its full birth loop and either **adopts** the one rule your examples
uniquely determine, or **refuses constructively**, naming the exact input it
needs you to label next. Refusal is success behavior: it means your examples
genuinely underdetermine the rule, and the engine tells you precisely what
would settle it instead of guessing.

```bash
ai_node/teach_skill.sh ai_node/skill_birth/examples/teach_adoptable.json
# examples: days 5/18/30 -> "flag", days 31/45/60 -> "no_flag"
# -> AUTHORED STANDARD (written by the engine BEFORE deciding): ...
# -> DECISION: ADOPT
#    learned rule: op=classify params={"rule": ["ge", 31, "no_flag", "flag"], "src": ["key", "days"]}

ai_node/teach_skill.sh ai_node/skill_birth/examples/teach_ambiguous.json
# examples: days 5/10 -> "flag", day 50 -> "no_flag" (gap between 10 and 50)
# -> DECISION: REFUSE (underdetermined)
#    "I can't tell yet — your examples fit these 2 rules: ... To decide, give
#     me the answer for {"days": 40}; those rules disagree there."
```

The engine **authors its own plain-English success standard before deciding**
(what counts as correct, how the hold-out is split, when it must refuse), then
holds every candidate rule to it. Every unit of work is counted and printed
(proposals, checks, survivors) — nothing is hidden.

**Provenance wiring (why you can trust the copy you're running):** the
engine's frozen contract (`skill_birth/interfaces.py`) pins the SHA-256 of
every node_core file it trusts. `teach_skill.sh` verifies those pins against
the BUNDLED `ai_node/node_core/` copies on every run and hard-refuses to
start on any mismatch — you'll see
`node_core sha pins verified: 5/5 bundled files match interfaces.NODE_CORE_SHAS (G11)`
at the top of every teach run.

**Honest scope (the engine's own stated cage, quoted from its README):**
"Families are simple/observable by construction — this is NOT a claim about
arbitrary real-world tasks; it is a claim about the LOOP over the stated shape
families." And: "Operator algebra is bounded (10 generic operators,
shape-derived params) — coverage is the loaded operators', not 'the engine's';
a family needing an operation outside the algebra correctly REFUSES (reach
limit), which is a feature, not coverage." In practice: enumerable candidate
rules (thresholds, classifications, mappings, compositions) over small
JSON-shaped examples — not free-form document understanding, and no language
model anywhere in the loop.

Notes on the teach surface:

- The JSON family shape is documented at the top of
  `ai_node/skill_birth/teach.py`: `name`, `description`, `examples`
  (input/expected pairs), optional `holdout` pairs, optional `probe_pool`
  (candidate inputs a refusal may name).
- `teach.py` is wire-only: the oracle it hands the engine is a pure lookup
  over the labeled pairs YOU supplied ("correct" = "matches your label");
  every decision comes from the vendored engine.
- The engine modules (`skill_birth.py`, `interfaces.py`) are vendored
  **byte-identical** to the source — no bundle patch was needed; they contain
  no path logic. The research harness (`families.py`, `run_skill_birth.py`,
  `surprise_families.py`) references repo fixtures and is not vendored; the
  full experiment design ships as `ai_node/skill_birth/DESIGN.md` and
  `README.md`.

## What it does NOT do (honest scope)

- It handles up to **65,536 rows per file** — a full year of a client's
  transactions fits comfortably. Past that it **refuses** with `TooManyRows`
  rather than truncate. (The demo dataset is small; a 50,000-row-per-side file
  reconciles in a fraction of a second.)
- It matches on amount (within 50 cents), posting date (within 3 days), and
  description similarity. It is a first-pass reconciler that surfaces exceptions
  for your judgment — it does not replace your professional review, and it
  doesn't decide materiality.
- The numbers in `bank.csv` / `ledger.csv` are synthetic. Nothing here is real
  client data.
