The Auditor
🟡 Deployed source; agent flow unexercised end to end for this documentation cut. Strictly read-only, and the reason it was safe to build before any further write agent.The shape
Read-only, enforced by the database
Postgres refuses the write. That is the claim, and it took four mechanisms to make it true through the deployed transport, because the obvious two were both measured to fail. So the channel does four things, in this order, and refuses rather than degrades at every one of them.Refuse the transaction pooler, from the DSN
6543 is Supabase’s transaction-mode pooler.
This channel proves itself read-only with a session-scoped SET, which on a transaction pooler would persist onto a shared backend the pooler later hands to a writer, and whose read-back could be answered by a different backend than the one it was set on.
Reading the book through that pooler would be fine; proving the channel read-only through it is impossible, so the DSN is refused before anything connects.Force the startup parameter anyway
-c default_transaction_read_only=on, appended to any options the DSN already carries rather than replacing it.
It is the only form with no writable window at all, and it is the real enforcement on a direct DSN and in CI.
It is kept precisely because it is stronger where it survives.Ask libpq which endpoint it actually reached
PGPORT or a service= entry fill it in, which passes the string check and then lands the SET on a shared backend.
conn.info.port is libpq’s own answer after all that resolution.
A port that reads as 6543, or that cannot be read at all, is refused and the connection is discarded unused.SET it a second way, then make the database confirm it
SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY is measured to survive the pooler that discards the startup parameter.
Then show transaction_read_only is read back, and a session that does not answer on is refused.
That last clause is the whole design: an assertion defends the property against any transport, including the next pooler that swallows something else.Browser questions and job mode are separate
The browser may submit only conversationalmessages to the Auditor.
It cannot select mode="job", a privileged check family, job-only scope or an arbitrary job thread; those fields are absent from the browser allowlist and an attempted override is rejected.
Scheduled and event-triggered jobs are created server-side.
The job runner sets job mode, selects one recognized family and its bounded scope, owns retries, and persists the durable run and findings.
This keeps a browser question read-only without letting it impersonate a scheduled sweep.
If the job’s required scope cannot be established, the result is inconclusive rather than clean.
The monthly_close_gated job is the important period-scoped example.
It runs only the close_gated family with a bounded last_closed scope, revisits closed periods that have never been verified or still carry unresolved findings, and advances coverage only after a verified run.
Finding identity includes the period, so a later clean month cannot resolve an earlier month’s exception.
The seven check families
The Auditor started with four families and now declares seven. Two lists matter here and conflating them would make every “audit everything” answer permanently incomplete:close_gated is declared, runnable and schedulable.
A job that names it resolves an explicit finished period; an invalid, open or unevaluable period is answered inconclusive by name rather than dropped or crashed.
It is kept out of the routine full sweep on purpose: a permanent “could not verify” line under every sweep is not honesty, it is noise that teaches a reviewer to skim past the lines that matter.
hash_chain · recompute, do not trust a flag
hash_chain · recompute, do not trust a flag
_entry_checksum and _canonical_lines, so if the writer’s hashing changes, the Auditor recomputes with the new rule because it is literally the same code.
A verifier with its own copy drifts, and then reports either false tampering or false safety.One historical limit.
New checksum-version-2 entries, including foreign-currency ones, preserve complete canonical facts for recomputation.
A legacy version-1 non-base entry may lack the old raw rendering, so it falls back to legacy linkage-only verification - still chain-guarded, and explicitly counted rather than reported as fully recomputed.invariant · the GL invariant sweep
invariant · the GL invariant sweep
- the trial balance nets to zero, organisation-wide and per period;
- every posted entry balances in base currency;
- no posted entry sits in a closed or undefined period;
- every
postedintake row reconciles one to one to a real GL entry.
invariant; in prose it is the GL invariant sweep.It is not the Close checksums - those are the Accountant’s, and they report tie-outs across the seven sub-ledgers.orphan · rows stuck mid-pipeline
orphan · rows stuck mid-pipeline
received, classified and extracted are statuses a row passes through within one Clerk run.
Resting there means the run died mid-pipeline.flagged and pending (awaiting review), active (awaiting posting), and rejected and posted (terminal) are not orphans.
The eval case for this seeds a stale orphan alongside a legitimately in-flight row, so it grades false positives too.recon · one transaction, across every system it lives in
recon · one transaction, across every system it lives in
close_continuous · the Close checksums that hold at every instant
close_continuous · the Close checksums that hold at every instant
AP-CONTROL and AR-CONTROL belong to recon.
Two families reporting one fact would be two register rows for one exception.Its fail-safe is the period question. The Close oracle skips the period-scoped payroll, depreciation-coverage and Wave E kết chuyển controls when it is given no period, which is 26 of the 104 checksums, so a nightly run with no period would cover most of the list and report a clean pass.
Instead the open fiscal periods are resolved at run time, the payroll subset runs once per open period, and with no period open that subset is inconclusive with the reason stated rather than silently absent.The family is labelled “Close checksums (continuous)” and never “invariants”.close_gated · the Close checksums that need the period to have ended
close_gated · the Close checksums that need the period to have ended
close_continuous for a structural reason: the register resolves findings by family, never by job or parameter, so one family running under two phases would clear every close-gated finding the monthly job filed, every night.completeness · the missing document, named and aged
completeness · the missing document, named and aged
GRNI asks “is the goods-received-not-invoiced pool zero?” once, organisation-wide, at close.
This family asks “is this goods receipt, 47 days old, still uninvoiced?” tonight, and names the document.
Same money, two grains, with the Close code as the join, so the Accountant’s close and the Auditor’s nightly sweep agree by construction.Six rules, four of them aged on a configurable severity ladder.
The exception system is the page for all of it.Like recon it inherits the graph freshness gate: a stale or unprojected graph makes the whole family inconclusive and no rule runs.
A completeness sweep over a graph that stopped projecting last week would report “no missing documents” about documents it never saw.recon and completeness families’ routing triggers are narrower than the others, deliberately.
A full sweep and a generic assurance question still run them, but a narrow question reaches recon only on a genuinely cross-system term (duplicates, versions, the document of record, the graph itself) and completeness only on a genuinely completeness term (missing, unallocated, unsettled, overdue, aging, unsigned, three-way).
Bare “evidence” and bare “document” are excluded from the second list on purpose: both are ordinary single-family audit vocabulary.
The reason is the inconclusive verdict: a family routed in unnecessarily puts a “did not run” line in front of a reviewer who asked whether the trial balance matched.
Answering the whole question
A reviewer’s question routes to the families it names. “Any chain breaks?” runshash_chain only.
A generic assurance question, or an empty message, runs the full sweep.
- Routing. A family trigger matched a singular stem, which a plural cannot match, so the enumerated family silently vanished. Family stems now match plurals, the families are a union that never short-circuits, and an explicit “full / complete / whole check” wins outright. That last choice is deliberate: for a read-only agent, running one family too many costs a database read, while running one too few answers half the question. It keys on a generic assurance noun, never a family noun, so a narrow question still routes narrowly.
- Reporting. A per-family readout now narrates one line per family the reviewer requested -
PASS, an exception count, INCONCLUSIVE, or NOT RUN - on both the clean and the exception branch.
Driving it from what was requested rather than from what came back is what makes a family that never
ran read as
NOT RUNinstead of vanishing from a confident answer. - The thread. The message channel had no append reducer, so the terminal node’s write replaced the channel and wiped the reviewer’s own question, leaving an untitled thread with only an answer and no history for follow-ups.
Inconclusive is never a pass
A read failure produces inconclusive findings, never a false all-clear. That posture runs through the whole agent. An assurance process that reports success when it could not check is worse than one that does not run, because it manufactures confidence. Findings carry two severities at once:Worked cases
Six seeded faults, each injected into a real migrated GL, each asserted to be flagged.aud-known-good-passes- a clean book passes, which also proves the fresh recompute reproduces every stored checksum;aud-declines-write-request- a request to fix the books short-circuits at the gate;aud-unreadable-ledger-is-inconclusive- the fail-safe. An unreachable database must never render as a clean book.
Where it is enforced
The tests that would fail if it broke
The offline scope and report suites, plus live acceptance against a real migrated GL where each of the six faults is injected and asserted flagged.Related
- The exception system - the sweeps, the register, and what a person does with a finding
- Two layers on the same documents - the lane a finding is routed to, and why
- Immutability and the hash chain - what the recompute verifies
- The 18 GL invariants - what the sweep re-checks
- The Close checksums - the 104, by phase and by layer
- The reconciliation ontology - the graph the
reconfamily reads - Tenancy, RLS and derived identity - the read-only session
- Asking the Auditor - the same agent, from a user’s chair