# contract - what an honest reading looks like Source: https://truecopy.dev/docs/contract/ Module: truecopy/contract Three methods you must write, two with safe defaults, and the one rule the pipeline enforces - a reading that contradicts its document never comes back as sound. The skeleton belongs to this library, the meaning to your project. This module knows of no transaction, no quarter and no invoice: it knows the **shape** of an honest reading, and it makes that shape compulsory. ```ts import { readDocument } from 'truecopy/contract'; const result = readDocument(document, reader); // { verdict, reading, selfCheck, discrepancy, repaired, refusal, rowsToReview } ``` ## Three methods are required **`read(document)` → `{ records, header }`** `header` is what the document says about **itself** without being a record: a declared total, a closing balance, a period. Without that second field the self-check has no raw material. **`selfCheck(document, reading)` → `SelfCheck`** ```ts { declared: number[], read: number, unit: string } // or { nothing: 'why this document declares nothing' } ``` `declared` is a list because a document may announce several candidate values when its layout scattered a label from its number; the gap kept is the smallest. Saying `{ nothing }` costs a sentence, and that is the point: the oversight becomes something written down instead of a silent `null`. **`rowsToReview(document, reading)` → `ReviewableRow[]`** Takes the **document**, not only the reading: a correction screen has to show the rows that were **dropped**, and those are not in the reading by definition. Each row carries `raw`, `fields`, an optional `droppedBecause`, and an optional [`where`](https://truecopy.dev/docs/layout/) so a person can jump to it on the page. ## Two are optional, and their defaults err toward refusing | | Left out | | -------- | ----------------------------------------------------- | | `repair` | nothing is attempted. An honest gap beats a patch-up. | | `refuse` | a reading that produced **no record** is refused. | Five methods before anything runs at all is a wall, and a wall in front of an interface has one predictable outcome: whoever meets it writes `return null` five times and ships. That is the very dodge [the kit](https://truecopy.dev/docs/kit/) exists to catch, and the likeliest author of it is now a language model writing from the types. So the two that have an honest default get one, and **both defaults err the safe way**. ## The one rule enforced > A reading that contradicts its document **never** comes back as `read`. It is deliberately weaker than "refuse as soon as the reading contradicts the document": a statement that does not balance is still usable row by row, a career record is not. Imposing refusal would have excluded the first. So you choose between `refused` and `needs-review`. You cannot choose to say nothing. ## The verdict | | | | -------------- | ----------------------------------------------------------------------- | | `read` | the reading holds, or the document declares nothing to check it against | | `needs-review` | the reading contradicts the document: show it to a person | | `refused` | your `refuse` returned a refusal, or no record came back | A `Refusal` carries `title`, `explanation` and **`next`**, always. A refusal with no way out is a dead end. ## Repair, and why giving up is the default ```ts repair(reading, discrepancy) { // Flipping ONE row changes the sum by twice its value, so the row sought // carries half the gap. None, or several: give up. const target = discrepancy.amount / 2; const candidates = reading.records.filter((row) => row.amount === target); if (candidates.length !== 1) return null; … } ``` An arbitrary choice among several candidates is worth less than an honest gap. When a repair lands, the self-check runs **again**: a repair that is not re-checked is a guess with better manners. Map of this site for a model: https://truecopy.dev/llms.txt Every page in one file: https://truecopy.dev/llms-full.txt