# auditor_skills — a big-firm CPA AUDITOR consumer skill pack

## Hypothesis

The S041 skill ABI (`node_ai/node_core/skill_abi.py`, frozen) generalizes past bookkeeping
(`skills/accounting/`) to actual **auditor** fieldwork — the profession's named time-sinks —
without any change to the ABI or the engine, and can be wired from OUTSIDE `node_ai/` (a real
consumer position: read the ABI, vendor nothing, import the real engine read-only) with the same
right-or-refuse, independent-oracle, fail-loud-conformance discipline the reference pack proves.

## The ABI it honors

`node_core/skill_abi.py` (S041): a skill = `SkillManifest` + `tools: {name: {"fn", "oracle",
"cases"}}`. `fn` is right-or-refuse (P1, hardened at the ABI boundary), every tool carries a
MANDATORY independent oracle (P2/F003), `fn` is deterministic (P3), a non-conformant skill fails
loudly at `DomainSkill(...)` construction (P4), and routing reads manifest metadata only (P5).
This pack imports `node_core` from `node_ai/node_core/` via a `sys.path` shim in
`auditor/__init__.py` — the REAL vendored engine, never a reimplementation. `gate.py` and
`learn_witness.py` both assert `node_core/engine.py`'s sha16 is `7efaf4ac36129bbe`, the sealed
value, before trusting anything it says.

## What's here

```
auditor/
  manifest.py      MANIFEST (name "auditor", when_to_use terms)
  rollforward.py    rollforward_ending, rollforward_check
  liabilities.py    unrecorded_liability_flags
  leadsheet.py       leadsheet_tie
  judgment.py        JUDGMENT_REFUSALS + judge() — the professional-judgment boundary
  __init__.py         assembles SKILL via node_core.DomainSkill (conformance-checks at import)
gate.py               H1-H5 falsifiable bar + both-ways calibration; writes results.json
learn_witness.py       node_core (S036 + S045) adopt/refuse witness on an auditor-shaped ability
birth_witness.py        S053 topic-agnostic skill-birth loop on two auditor-shaped families
birth_attempt.md         the interface question (does S053 accept an external family?), answered
smoke.py                 unrecorded_liability_flags + leadsheet_tie on real cpa_reconcile_demo data
```

Every script is re-runnable standalone with one command from this directory; each writes its own
`*_results.json`.

## Domain: the three named time-sinks

- **Rollforward** — `beginning_balance + additions - disposals = ending`, tied to the current
  trial balance. The first-year associate's bread and butter on every fixed-asset/reserve
  schedule.
- **Search for unrecorded liabilities** — the "boring test": a post-period cash disbursement
  whose service period predates period-end, and whose amount exceeds a stated threshold, gets
  flagged as a candidate accrual the client may have missed.
- **Lead-sheet tie-out** — every trial-balance account is grouped to a lead-sheet line; each
  group's sum must tie to the reported FS line. An account whose group is not a reported line is
  a **typed refusal**, never a silent drop — silently dropping an unmapped account is exactly how
  a real misstatement hides.

## Verdict after this session's runs (re-derived, not recalled)

**`python3 gate.py`** — `WIN: true`. Verbatim from this session's run:
```
H1_pass..H5_pass: true / true / true / true / true
H1_tool_cases: 12   n_tools: 4   confident_errors: 0   crashes: 0
adopted_cutoff: 30   ambiguous_refused: true
calibration_fires: true
calibration_fire_detail: "defective fn=1150001 vs REAL independent oracle=1150000 on
  {'beginning_balance': 1000000, 'additions': 250000, 'disposals': 100000} -> MISMATCH DETECTED"
core_sha16: "7efaf4ac36129bbe"   determinism: true
```
The calibration (H5) injects an off-by-one defect (`ending = beg + add - dis` →
`... + 1`) into a **scratch copy** of `auditor/rollforward.py` under
`/tmp/.../scratchpad/mason2/`, dynamically imports the corrupted copy, and shows its answer
(1150001) disagrees with the real, untouched oracle's answer (1150000) — the gate detects a
defect it did not have before, and the real package bytes are then independently confirmed
clean, in the same run.

**`python3 learn_witness.py`** — `WITNESS_OK: true`. The S045 loop (`learn_unique`) and the S036
engine (`learn`) both independently adopt cutoff **30** from tight examples and both refuse a
sparse example set as `"underdetermined: examples consistent with 4 candidates"` — quoted
verbatim in `learn_witness_results.json`.

**`python3 birth_witness.py`** — `WITNESS_OK: true`. The S053 engine, called directly via its
public `SkillBirthEngine.birth()` (no edit to `node_ai/`, checked by a before/after directory
snapshot), independently derives the rule `days >= 31 → no_flag else flag` (= the real cutoff)
from 6 worked examples, and issues a constructive `underdetermined` refusal on a sparse example
set, naming `{"days": 40}` as the exact input needed to resolve it. Details and the honesty of
what this run does and does not prove are in `birth_attempt.md`.

**`python3 smoke.py`** — `SMOKE_OK: true`, on real dollar amounts and dates from
`experiments/cpa_reconcile_demo/{bank,ledger}.csv`: `unrecorded_liability_flags` flagged 2 of 99
real derived candidate payments; on a population that folds in bank.csv's own already-present
malformed date row (`"04/31/2025,50.00,Impossible date row"`), the tool refused the WHOLE
population rather than silently skip the bad row (`"payment_date is not a valid ISO calendar
date: '2025-04-31'"`); `leadsheet_tie` tied all 3 derived groups exactly on the real 192-row
ledger, broke on one group after a disclosed +$123.45 perturbation, and refused on a
deliberately-unmapped account. Full numbers in `smoke_results.json`.

## Scope cage — what these tools do NOT do

- **No materiality judgment.** `rollforward_check`, `leadsheet_tie` report an exact tie/no-tie
  and an exact difference; whether a given difference matters to these financial statements is
  refused by `judgment.py`'s `JUDGMENT_REFUSALS` (`SKILL.judge(...)`), the same shape as
  `skills/accounting/compliance.py`.
- **No completeness opinion.** `unrecorded_liability_flags` mechanically flags what the supplied
  population contains; whether that population IS the complete set of post-period disbursements
  is a judgment question this pack refuses (`JUDGMENT_REFUSALS[1]`), not a count it can certify.
- **No threshold-setting judgment.** The search threshold is a caller-supplied input, not a
  computed risk assessment; `JUDGMENT_REFUSALS[2]` refuses "should the threshold be lowered."
- **No presentation-correctness opinion.** `leadsheet_tie` proves arithmetic tie-out for a GIVEN
  grouping; whether that grouping is the RIGHT financial-statement presentation is refused
  (`JUDGMENT_REFUSALS[3]`).
- **The `smoke.py` lead-sheet "reported" figures are the computed sums themselves** (this fixture
  carries no independent externally-reported FS line) — that run demonstrates the grouping + tie
  machinery on real dollar amounts, not an independent tie against a genuine external report.
  Named explicitly in `smoke.py`'s own docstring scope cage.
- **The `smoke.py` liability-search `period_end`, `service_period_end` offset, and threshold are
  constructed, disclosed constants** — the fixture has no real audit cutoff or service-period
  field; only the dollar amounts, dates, and one real malformed-date row are genuine.

## Deviations from the mission brief

- `birth_attempt.md` documents that the S053 harness's DESIGNATED external-family hook
  (`surprise_families.py`) lives inside `node_ai/` and was therefore not used (writable-scope
  violation); the engine's own public `SkillBirthEngine.birth()` API was used directly instead —
  read from `interfaces.py`, which shows the harness itself calls the engine no differently. This
  is documented as the honest resolution, not a workaround.
- `gate.py`'s H3 and `learn_witness.py` both exercise a "learn the unrecorded-liability cutoff"
  ability rather than depreciation-method identification (accounting's H3 precedent) — the
  closest genuinely auditor-shaped ability this pack's tools expose to a discrete-candidate
  learner.
