# Design notes: quote-template-unification anchor hank > **Historical iteration record — not the current runnable contract.** The notes below preserve the earlier three-supplier, six-codon, Sonnet-era scope, cost assumptions, ownership claims, and open design proposals as written. They are not instructions for the shipped fixture. > > For current behavior, use [README: Program and output contracts](README.md#program-and-output-contracts), [Award report boundary](README.md#award-report-boundary), and [hank.json](hank.json), with its referenced prompts and rigs. The current program has seven codons, eight quoting suppliers and forty supplier-part rows; five codons use Haiku and two use DeepSeek. Deterministic rigs own the cited financial projection and lossless exception CSV; the final agent owns only the qualitative assessment. Historical claims below do not override those contracts or certify a current capture. ## Scope decision: 3 suppliers, not 8 The full corpus has 8 suppliers across 10 digitizer dialects (`D1`–`D10`), but this hank processes exactly `digitized/`'s three hand-authored dialect fixtures: Aster (Datalab-shaped, from XLSX), Beacon (Reducto-shaped, from PDF), Cedar (generic-OCR-shaped, from a skewed scan). This matches `review:fable-1/6-docs-skeleton.md§tutorial/0-tour`'s own framing verbatim: "three supplier dialects (XLSX merged headings / two-page PDF with pack pricing + substitution + tooling / skewed scan with OCR-confusable part, NO BID, handwritten note, expired date)." The remaining five suppliers in `digitized-extracts/` (Dover, Embar, Fjord, Harbor, Iris) and the unrecognized-template case (Granite) are out of scope for the anchor hank itself — they're the corpus's reserved material for later exercises (chapter 5's maturity exercise, or a follow-on "add a fourth dialect" extension), not something this hank's six codons touch. Within that 3-supplier scope, three of the golden corpus's eight oracle exceptions are still fully reachable and exercised end-to-end: `EX-01` (Cedar `EXPIRED_VALIDITY`, the planted JC-1 judgment call), `EX-02` (Cedar `NO_BID`), and `EX-03` (Aster `DEDUP_CONFLICT` — Aster's two submissions, `doc-aster-qb-1047` and `doc-aster-qb-1047-rev2`, share a `dedup_key` and differ by a one-cent `stated_total_minor` drift). That's not a coincidence: all three of the oracle's non-suppler-specific failure categories (expiry, no-bid, dedup) happen to live on suppliers inside the 3-dialect scope, which is exactly why `0-tour` chose these three. ## The boundary drill, applied `review:fable-1/6-docs-skeleton.md§author/designing-codons-and-handoffs` names a three-question checklist for "codon boundary or same codon?": **(1) different model tier? (2) different failure policy? (3) artifact a human might inspect?** Run against this hank's four stage boundaries: **normalize-\* vs validate-and-repair.** (1) Yes — haiku for mechanical extraction, sonnet for the checks that require reasoning against the RFQ contract and lookups. (2) Yes — a normalize codon has nothing to fail *on*: it copies fields verbatim and cannot make a wrong judgment call, so `onFailure: abort` with a tight budget is enough. `validate-and-repair` is where wrong judgment becomes possible (a bad alias resolution, a missed exception), so it gets the sentinel and a softer budget (`onExceeded: complete` — let it finish reasoning about the case it's mid-way through rather than cutting it off). (3) Yes — `envelope-*.json` is inspectable per-supplier raw data; `validated-records.json`/`exception-ledger.json` is the inspectable "did the pipeline make the right call" artifact. All three answers point the same way, which is why these are three separate codons rather than one "normalize everything" codon, and a fourth separate codon rather than folding validation into normalize. **Aster's dedup decision: kept out of `normalize-aster`, on purpose.** `normalize-aster` extracts *both* Aster documents without picking a winner, even though the rev2 envelope's `supersedes` field makes "which one wins" almost mechanical. This is a deliberate application of question (2): dedup-conflict detection is a judgment/policy decision (which one wins, and is the conflict worth surfacing to a human) that belongs with `validate-and-repair`'s other exception-routing logic, not with mechanical extraction. Folding it into `normalize-aster` would have made that codon's failure policy inconsistent with its siblings (`normalize-beacon`/`normalize-cedar` truly can't fail on judgment) and would have hidden the conflict from the one codon whose job is to decide what's exception-worthy. **validate-and-repair vs reconcile.** (1) No — both sonnet. (2) Yes — `validate-and-repair`'s failures are per-line/per-quote (a bad alias, an expired quote); `reconcile`'s job is a completeness guarantee across the whole 3×5 grid, which is why it alone gets `budget.onExceeded: "fail"` — a partial join is a worse failure mode than a partial per-line repair, so it must hard-stop rather than gracefully wrap up. (3) Yes — `unified-records.csv` is the inspectable "does every cell exist" artifact, distinct from the per-line ledger. Two separate codons. **reconcile vs award-brief.** (1) Yes — sonnet for the join/coverage logic, haiku for rendering. (2) Yes — `award-brief` is explicitly forbidden from making new judgment calls (its prompt says so); everything it states must already be settled upstream, so it can safely use the cheap, fast-failing budget profile of a normalize codon. (3) Yes — `award-brief.md`/`exceptions.csv` are the human-facing deliverables, distinct from the machine-shaped `unified-records.csv`. Two separate codons. ## Tutorial chapter map Per `review:fable-1/6-docs-skeleton.md§tutorial/*`, each chapter is a checkpointed stage of building this exact hank: - **0-tour** — walks the fictional RFQ, the three dialects, and the target shape (unified records, invariants, exception queue, cited brief) without running anything yet. Concepts earned: the digitizer/evidence boundary (`review:sol-1/04-anchor-case-study.md`'s "upstream payloads are immutable evidence; the adapter may reshape but must not infer commercial meaning") and "outcome before mechanism." - **1-baseline** — builds the first codon (`normalize-aster`) alone, minimal-valid `hank.json`, first look at the execution directory. Checkpoint: one dialect, one envelope file. - **2-determinism** — adds `rigSetup`/`rigs/preflight.ts` and asks "what is an agent doing here that we don't need an agent for?" — the checksum/schema check is exactly the kind of work a deterministic script should do instead of an LLM call. Checkpoint: preflight blocks a corrupted corpus before any codon spends a token. - **3-validate-and-repair** — adds `normalize-beacon`, `normalize-cedar`, and `validate-and-repair` itself; introduces the typed exception ledger and "continue must never mean silent omission" against planted bad fixtures. Checkpoint: `exception-ledger.json` has the dedup conflict, the NO_BID, and JC-1. - **4-observe-and-budget** — adds `sentinels/quality-observer.json` and the budget fields on every codon, including `reconcile`'s `onExceeded: "fail"`; walks the resolved-plan/cost table together. Checkpoint: a full run's cost breakdown by codon. - **5-break-resume-inspect** — kills the run after `validate-and-repair`'s checkpoint, resumes, traces one `award-brief.md` dollar figure back to its `[doc:block]` citation, then reruns the haiku codons with `--model haiku` already in place (no change needed — they're haiku by design) versus rerunning `validate-and-repair`/`reconcile` at a cheaper tier to see what breaks. - **fixtures (reference chapter)** — the 19 failure fixtures under `fixtures/failures/` aren't run by this hank's golden path, but every check in `validate-and-repair.md`'s prompt (alias resolution, substitution approval, pack/box consistency, currency presence, rev-target, expiry) is written generically enough to catch its corresponding fixture (e.g. `unknown-part-alias.json`, `unsupported-substitution.json`, `per-pack-as-per-each.json`, `missing-currency.json`, `quote-revision-a.json`, `expired-quote.json`) if a reader points this same hank at one instead of the golden corpus. ## Open design decisions worth flagging to a reviewer 1. **`reference/rfq-bom-rev-b.json` is hand-transcribed, not read from the corpus at runtime.** The real RFQ contract ships as XLSX/PDF (`read_only_data_source/rfq/northstar-bom-0042-rev-b.xlsx`), which no codon prompt here parses directly. The values are transcribed once, checked against the PDF's own "Changes from rev A" block, and shipped inside `anchor-hank/` so the hank stays runnable standalone without a binary-parsing codon. This mirrors a choice the corpus's own `forge-report.md` flags about itself (computing `line_extension_minor` from qty×price rather than a document-stated field) as "one defensible design choice among several" — a future revision could add a seventh codon that digitizes the RFQ itself, at which point this file becomes generated output instead of hand-authored input. 2. **The sentinel attaches to one codon, not several.** `sentinels/quality-observer.json` is a single definition, attached only to `validate-and-repair` (via `trigger.conditions` matching `data.codonId`), not also to `reconcile`/`award-brief`. This keeps the sentinel's own prompt simple and single-purpose (per the task's "keep it simple" instruction) at the cost of not independently double-checking `reconcile`'s coverage guarantee or `award-brief`'s citations — a fuller build would reuse the same `sentinelConfig` path on more codons, or write a second, differently-scoped sentinel. 3. **`unified-records.csv` is deliberately not named `expected.csv`.** Column-for-column it matches `truth/expected.csv`'s header, but the filename is kept distinct so a reader never confuses this hank's own output with the held-out oracle it's checked against.