You're reading the 0.10.0 archive.

Tour the quote-template-unification corpus

A procurement team rarely receives quotes in a convenient form. Suppliers answer the same request for quote with spreadsheets, PDFs, scans, JSON exports, and ERP dumps, each with its own units, part codes, and quirks, and someone has to turn that pile into a defensible award decision. This tutorial builds a Hankweave pipeline that does exactly that for a fictional RFQ: it normalizes eight supplier responses into shared records, isolates the hazards it cannot resolve, and produces a cited award brief.

This opening tour shows the destination before the mechanism. We will inspect the finished artifacts, meet the suppliers and their dialects, catalog the planted hazards, and confirm the fixture corpus is deterministic, so that every later chapter has a concrete target to build toward. Everything on this page is synthetic training material, marked as follows:

Output
FICTIONAL TRAINING FIXTURE — NOT CUSTOMER DATA

Every document in this corpus is fictional. Buyer (Northstar Components),
RFQ NC-RFQ-0042, all suppliers, part numbers, prices, and case details are
invented for testing/tutorial purposes. No real customer, vendor, or
transaction data is represented. data_class: synthetic. License: CC0-1.0
(pending project-counsel confirmation -- see forge-report.md OQ).

"Datalab" and "Reducto" appear only as digitizer-output-shape tags on
fixture filenames/adapters (matching corpus-spec.md's own file inventory),
not as case-study attribution.

See the destination before the mechanism#

Before we build quote-template-unification, let's inspect the four files we want to finish with: unified records, an exception ledger, a notes channel, and a cited award brief. These are captured results, not a transcript of a run, and each one answers a different question a buyer would ask.

The unified records answer "what did every supplier actually bid, in comparable terms?" The file holds one row for each of eight quoting suppliers and five BOM parts: 40 data rows, or 41 CSV lines when the header is counted. Granite is quarantined during survey, so it contributes no row. Each row records the supplier and part identity, RFQ revision, quantity, minor-unit price and basis, line extension, currency, status, ranking eligibility, and four source references. In the excerpt below, notice how a supplier's native pricing has been converted into normalized unit_price_minor cents, and how every value traces back to a source_line_id and source_block_id in the original document.

CSV
supplier_code,buyer_part_id,rfq_rev,qty,unit_price_minor,price_basis,line_extension_minor,currency,status,ranking_eligible,source_document_id,source_quote_id,source_line_id,source_block_id
AST,NC-1001-A,B,500,410,PER_EACH,205000,USD,QUOTED,true,doc-aster-qb-1047-rev2,q-aster-qb-1047-rev2,doc-aster-qb-1047-rev2-L1,doc-aster-qb-1047-rev2-b1
AST,NC-1002-A,B,2000,79,PER_EACH,158000,USD,QUOTED,true,doc-aster-qb-1047-rev2,q-aster-qb-1047-rev2,doc-aster-qb-1047-rev2-L2,doc-aster-qb-1047-rev2-b2
AST,NC-1003-B,B,1000,115,PER_EACH,115000,USD,QUOTED,true,doc-aster-qb-1047-rev2,q-aster-qb-1047-rev2,doc-aster-qb-1047-rev2-L3,doc-aster-qb-1047-rev2-b3

The exception ledger answers "what could not be resolved mechanically?" Hazards stay visible here instead of being dropped or silently coerced: Cedar's expired validity, its NO BID row, and Aster's deduplication conflict each get a typed row with a resolution note. The golden ledger has eight rows covering eight exception types; the anchor's current exceptions.csv is serialized from its structured ledger, and the row IDs need not match the golden file's ordering. Compare the type and supplier rather than the row ID.

CSV
exception_id,type,supplier_code,buyer_part_id,detail,resolution,source_ref
EX-01,EXPIRED_VALIDITY,CDR,NC-1001-A|NC-1002-A|NC-1003-B|NC-1004-A|NC-1005-A,CDR valid_until 2026-01-20 < award decision date 2026-02-01,"JC-1: planted irresolvable judgment call — exclude-vs-show-flagged is a human decision; quote stays visible, cannot win ranking",doc-cedar-cw-77
EX-02,NO_BID,CDR,NC-1004-A,CDR rendered NO BID for NC-1004-A,"normalized to status:NO_BID, not $0.00; excluded from ranking, visible",doc-cedar-cw-77-b4
EX-03,DEDUP_CONFLICT,AST,NC-1004-A,same supplier+RFQ rev submitted twice: doc-aster-qb-1047 stated_total 604000 vs doc-aster-qb-1047-rev2 stated_total 604001 (1-cent drift on NC-1004-A printed extension),"dedup rule: latest job (doc-aster-qb-1047-rev2, issued 2026-01-14) wins; doc-aster-qb-1047 (issued 2026-01-10) superseded, conflict logged",doc-aster-qb-1047-rev2

The notes channel answers "what did suppliers say that is not a price?" Annotations such as Cedar's lead-time remark and Iris's conditional discount offer live here, outside the line items. Neither is applied to pricing; the applied_to_pricing column says so explicitly.

CSV
note_id,supplier_code,buyer_part_id,type,text,applied_to_pricing,source_ref
N-01,CDR,NC-1002-A,LEAD_TIME,lead time 45 days on pins,false,doc-cedar-cw-77-b2
N-02,IRS,NC-1005-A,DISCOUNT_OFFER,can do 5% if you take all 5000,false,doc-iris-quote-b5

Finally, the award brief answers "who should win each part, and on what evidence?" It selects the lowest eligible bidder per part and cites the source block for each amount, so every number in the recommendation can be traced back to a supplier's own document. A setup rig (a setup program) serializes the exception ledger's seven declared fields in exception-ID order and derives the financial projection from the unified rows; the agent supplies only the qualitative assessment.

MARKDOWN
## Financial recommendation

Projection of unified-records.csv only. Rank QUOTED, ranking_eligible=true rows by line_extension_minor; break equal-price ties by supplier_code ascending. Unit prices are already normalized per each upstream. No eligible bid means no recommendation; unsupported data stops export rather than inventing a price or citation.

| Buyer part | Selected supplier | Unit price (USD per each) | Line extension (USD) | Tie / no-bid decision | Ineligible QUOTED suppliers |
| --- | --- | --- | --- | --- | --- |
| NC-1001-A | BCN | $3.95 [doc-beacon-8821:doc-beacon-8821-b1] | $1,975.00 [doc-beacon-8821:doc-beacon-8821-b1] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b1] |
| NC-1002-A | AST | $0.79 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b2] | $1,580.00 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b2] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b2] |
| NC-1003-B | EMB | $1.05 [doc-embar-quote:doc-embar-quote-b3] | $1,050.00 [doc-embar-quote:doc-embar-quote-b3] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b3] |
| NC-1004-A | AST | $6.40 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b4] | $160.00 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b4] | Unique lowest eligible bid | None |
| NC-1005-A | BCN | $0.19 [doc-beacon-8821:doc-beacon-8821-b5] | $950.00 [doc-beacon-8821:doc-beacon-8821-b5] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b5] |

These captures come from a real run, and the numbers behind them are worth reading carefully. The fixture tree was generated on 2026-09-01; the anchor's expected capture was re-captured on 2026-09-06. The current anchor capture is a complete 7/7-codon run. It matched all 40 unified rows to the oracle and routed all eight planted exception types. Its tracked codon cost was $0.88303367; provider health checks and sentinel calls are outside that tracked-codon total. The configured per-codon caps add to $9.00, but those are ceilings in hank.json, not a measured total or a price promise across retries. For historical context only, the original golden run used sonnet at ~$2.41.

One component is deliberately narrower than the artifacts suggest. The quality observer watches the validate-and-repair completion event and reports the status, cost, and duration fields present there. It cannot read output files, so row coverage and correctness come from the structured outputs and the deterministic checker, not from the observer log.

Check-it: In the extracted release bundle, open quote-template-unification/truth/expected.csv and confirm that the header is followed by 40 data rows. Then inspect truth/exceptions.csv and truth/notes.csv; these are the target contracts before a hank runs.

Name the procurement we will unify#

Having seen where we are going, we can look at what we start from. We will work with Northstar Components' fictional request for quote (RFQ), NC-RFQ-0042. Revision A was issued on 2026-01-03, revision B on 2026-01-05, and the award decision date is 2026-02-01. The bill of materials below is the shared reference every supplier quoted against.

Scroll to explore the table →
PartDescriptionRev-B quantityRevision-B change
NC-1001-Amounting bracket500 EA
NC-1002-Alocking pin2000 EArev-A quantity was 1000
NC-1003-Bfastener kit1000 EApack and box variants appear
NC-1004-Agasket sheet25 EA
NC-1005-Arating label roll5000 EAnew in rev B; Cedar may render NC-1OO5-A

The rev-B delta is exactly two changes: the locking-pin quantity doubles from 1000 to 2000, and the rating-label roll is new. Revision therefore matters to the pipeline: a rev-A quote is one of the 19 standalone failure fixtures, and accepting stale quantities would corrupt the award math.

Two conventions apply to every document in the corpus. Each carries the watermark FICTIONAL TRAINING FIXTURE — NOT CUSTOMER DATA and the data class synthetic. And the seed prices are USD cents per EA; fields ending in _minor hold minor currency units, while each row carries USD separately.

The three hand-authored digitizer contract fixtures mark actual_service_output: false. They establish a tutorial convention, not the response shape of a real Datalab, Reducto, or generic-OCR service.

Check-it: Find NC-1002-A and NC-1005-A in the BOM table, then confirm the watermark in the notice at the top of this page.

Survey the suppliers before normalizing#

Eight suppliers answered the RFQ, and no two answers look alike. Real procurement intake spans clean JSON, raw TSV, formatted spreadsheets, and degraded scans, so before we normalize any price into the shared record shape, we inspect its dialect. Aster, Beacon, and Cedar cross a digitizer boundary through hand-authored contract fixtures. Dover, Embar, Fjord, Aster rev2, Harbor, and Iris are already-digital extracts. Granite is surveyed and quarantined rather than forced into a template. The inventory below lists each dialect and the exact quirks it renders; every quirk is a deliberate test of a normalization or quarantine boundary.

MARKDOWN
## 5. Dialect specs (per supplier; every quirk is a rendered artifact, not a comment)

| id | supplier / file | format | exact quirks to render | cite |
|---|---|---|---|---|
| D1 | Aster Metals `aster-quote-qb-1047.xlsx` | XLSX, 2 sheets | merged heading cells; quantity-break price table (tiers 92/85/79¢ on NC-1002-A); blank cell = "same as above" (continuation rows); sheet 2 = terms | S§story, G§4-1 |
| D2 | Beacon Industrial `beacon-quote-8821.pdf` | 2-page digital PDF | per-PACK pricing (NC-1003-B `$115.00/pack of 100`); freight-excluded note (EXW); one approved substitute (BI-2002-X for NC-1002-A); one-time tooling line `$250.00` on p2 — stated total correct only after adding it | S§story, G§4-2, S§contract |
| D3 | Cedar Works `cedar-quote-cw-77-scan.png` | skewed scan PNG | OCR-confusable `NC-1OO5-A`; one `NO BID` row (NC-1004-A); handwritten "lead time 45 days on pins"; expired validity 2026-01-20 (JC-1); NC-1003-B priced per BOX(10) | S§story, G§4-3 |
| D4 | Dover `dover-quote.json` | Datalab-contract-shaped JSON | `fields[]` each with `{page,bbox}` citation; one value string mixes part+name (`"NC-1004-A gasket sheet"`); clean 5/5 bid | G§4-4 |
| D5 | Embar `embar-quote.md` | markdown (parse-not-extract) | per-page tables; NC-1004-A row SPLIT across page break (qty p1, price+ext p2) | G§4-5 |
| D6 | Fjord `fjord-erp.tsv` | TSV | internal part codes FJ-3341..3345; some rows price `TBD` → UNRESOLVED | G§4-6 |
| D7 | Aster rev2 `aster-quote-qb-1047_rev2.json` | JSON re-run | same RFQ+supplier as D1; 1¢ total drift (16000→16001 on NC-1004-A extension) | G§4-7 |
| D8 | Granite `granite-bid.xlsx` | XLSX | first-time supplier, never-seen template (arbitrary layout, no recognizable line-item grid) → survey quarantines + onboarding note | G§4-8 |
| D9 | Harbor `harbor-quote.json` | JSON | `ocr_confidence:0.81`; fixed-width text lines; `warnings:["low_confidence: page 2"]`; p2 rows quarantined | G§4-9 |
| D10 | Iris `iris-quote.json` | JSON | `annotations:[{type:"handwriting", text:"can do 5% if you take all 5000"}]` → notes channel only | G§4-10 |

Reading the table column by column pays off later. D2's per-pack pricing and D3's per-box pricing both have to land on the same per-each basis as D1's tiered spreadsheet prices. D5's page-split row tests whether the pipeline can rejoin a line without losing its source pages. D8 is the control case: a template nobody has seen before should be quarantined, not guessed at.

The native-input rig parses the five structured extracts without retyping them: it preserves their explicit source line or block identifiers, TSV currency even on TBD rows, native annotations, OCR warnings, and Embar's source pages when it joins the split line. Validation byte-checks those generated envelopes before using them. The survey begins incomplete and the agent records the quarantine decisions before validation can require complete intake coverage.

This division of labor is worth stating plainly. An adapter transforms input shape; it does not decide commercial meaning. The contract fixtures share a lines[] convention, but their actual_service_output:false marker matters: these are hand-authored tutorial fixtures. The already-digital extracts enter through their native formats. Granite's zero-row quarantine is the correct result for an unknown template.

JSON
{
  "adapter": "datalab",
  "actual_service_output": false,
  "data_class": "synthetic",
  "watermark": "FICTIONAL TRAINING FIXTURE — NOT CUSTOMER DATA",
  "document_id": "doc-aster-qb-1047",
  "quote_id": "q-aster-qb-1047",
  "source_file": "source-quotes/aster-quote-qb-1047.xlsx",
  "media_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "issued_at": "2026-01-10T09:00:00",
  "dedup_key": "AST|NC-RFQ-0042|B",

Check-it: In the dialect inventory, find D3's NO BID and D10's notes-only annotation. In the JSON excerpt, find actual_service_output: false.

Find the hazards before they surprise us#

The dialect quirks are not decoration; each one plants a hazard the pipeline must handle correctly. The corpus plants nine hazard classes: expired validity, NO BID, a deduplication conflict, a page-split line, a low-confidence page, handwriting, an OCR-confusable part number, internal part codes with TBD prices, and an unknown template. Each has a correct treatment, and most of those treatments share a theme: preserve the source meaning and its citation rather than forcing a number.

  • Expired validity (JC-1): Cedar expires on 2026-01-20, before the 2026-02-01 award date. Keep its rows visible and ineligible; the decision to exclude or show-flagged is a human call.
  • NO BID: Cedar's NC-1004-A has null pricing, not $0.00.
  • Deduplication: Aster rev2 wins the same supplier/RFQ-revision key; the one-cent drift is logged.
  • Page split: Embar's NC-1004-A fragments are joined while their source pages remain available.
  • Low confidence: Harbor page-2 rows remain visible but quarantined and unranked.
  • Handwriting: Iris's conditional discount is routed to notes and never changes the line price.
  • OCR alias: Cedar's NC-1OO5-A can resolve to the BOM part when its context supports that alias.
  • Unresolved price: Fjord's TBD rows remain null and ineligible.
  • Unknown template: Granite is quarantined at survey with an onboarding note.

The 19 failure fixtures F01–F19 are separate mutations of golden inputs. They cover failures such as manifest mismatch, broken citations, wrong pack conversion, an expired quote, an unlabeled synthetic document, and an unsupported award number; later validation chapters exercise their expected diagnostics.

Check-it: Compare the expired-validity and NO-BID rows in the captured exception excerpt. The first stays visible but cannot rank; the second carries no price – NO BID, not $0.00.

Verify the corpus before we build#

A tutorial corpus is only useful if its ground truth is trustworthy, so the fixture tree is checked before any hank runs. The corpus itself is deterministic. A typed Bun generator builds the fixture tree from the BOM and supplier seed, and a separate checker re-parses the emitted files against an independently transcribed oracle. The checker completed 76/76 checks, including the 19 failure-fixture shape checks.

A second generator/checker pass produced no directory differences. That result belongs to the fixture generator; it does not promise byte-identical model envelopes, judgments, or prose. The model-written normalizers should be repeated with --start-new, a fresh execution, and a distinct output location when semantic repeatability is being tested. The comparison projection checks identity, raw parts, quantity, prices and basis, status, and citations; it reports full-JSON and byte variation separately rather than rejecting every difference.

Five under-specified choices are recorded in the corpus report rather than hidden: line extensions are computed as quantity × unit price; the contract fixtures use a shared lines[] convention; F19 uses a structural proxy because comparison.json is absent; the Cedar PNG is structurally valid while its dialect quirks live in the digitized fixture; and generator_commit is n/a because generation ran outside a git repository.

Check-it: Read the corpus report's invariant result and determinism sections. Confirm that the 76/76 result belongs to the corpus checker, not to a claim that a model will reproduce every byte.

Protect the data boundary#

Because the corpus ships with known answers, we need a rule about what the agent may see. The data boundary separates task inputs from the held-out answer. Prepare the task-only input from the extracted corpus before a chapter run: it contains digitized/, digitized-extracts/, lookups/, rfq/, source-quotes/, and a filtered manifest. The truth/, generator/, and planted failure fixtures stay outside the agent input. This is evaluation-data separation, not a security sandbox.

The task inputs are exposed read-only at the mount seen by the codons. Upstream payloads remain immutable. The digitizer boundary is inside this rule: native files cross once through the three hand-authored contract fixtures or the deterministic native-inputs.ts rig, and later codons consume the shared envelope shape rather than the raw file.

Within that boundary, the pipeline works through four stages, and the diagram below is the roadmap for everything the later chapters build. Normalization reshapes dialects and resolves Fjord's internal aliases without judging prices. Validation and repair catch the planted hazards. Reconciliation produces one row per quoting supplier and BOM part, with the latest Aster job winning the dedup conflict. The brief names the lowest eligible bidder with unit price, line extension, and a source citation. The diagram is a teaching view of the corpus, not a transcript of a runtime event graph.

FIG. 1 Protect the data boundary — the tour's four-stage working shape
Read the diagram as text
Output
normalize  ──>  validate/repair  ──>  reconcile  ──>  brief
shared        catches the planted     one row per     lowest eligible
lines[]       hazards and repairs     supplier +     bidder with a
convention    what is repairable      BOM part       cited amount
              without dropping it     latest job     and source_ref
Fjord aliases                         wins the
resolve here                          one-cent drift

The held-out oracle is useful to us as readers, but it is not supplied to the codons. The later chapters use the same prepared task-input layout and then verify outputs against the separately held-out truth.

Check-it: Look at the prepared-input layout in the fixture documentation and verify that its top-level directories are the five task-input directories above, not a data/ prefix and not the oracle.

Start chapter 1 with a clean fixture#

We now know the target files, the source dialects, the hazards, and the boundary between task and answer. Next, in chapter 1, we will write and run the hank: its syntax, codons, model selection, and CLI commands. We have not run a hank yet.

Start with the versioned fixtures: download the 0.10.0 bundle or browse an individual fixture artifact. The archive already contains the top-level directory hankweave-fixtures-0.10.0/; after extraction, the relevant entries are minimal-single-provider/hank.json with minimal-single-provider/data/, and chapters/ch1 through chapters/ch5. These are directories, not checkpoint tags. The fixture command recipes require Node >=22.19.0; we use Bun's bunx package launcher (Node/npm users can use the equivalent npx spelling), and the recipes also assume Git and a non-root account. The supplied minimal capture ran on Node v23.8.0. Follow the commands in each chapter.

The credentials ladder comes before a paid run: chapters 1–2 use ANTHROPIC_API_KEY; chapters 3–5 and the full anchor also need BASETEN_API_KEY for their pi/baseten/deepseek-ai/DeepSeek-V4-Pro judgment codons. The anchor's quality observer uses the full anthropic/claude-haiku-4-5 registry ID. A passing preflight does not establish that the Baseten key is valid. Each chapter's own run page names its required keys before its command.

The chapter progression is deliberately cumulative: ch1 has one codon for Aster, ch2 has three codons for Aster/Beacon/Cedar, ch3 adds validation and exceptions, ch4 emits 15 Aster/Beacon/Cedar rows, and ch5 expands to eight quoting suppliers and 40 rows before producing the cited award brief. Fresh 2026-09-06 captures measured tracked codon costs of $0.10441000, $0.16203200, $0.28754050, $0.40847623, and $0.70680328 for ch1 through ch5. Use a fresh execution and output location for an independent repeat; input checksums do not make model extraction byte-deterministic. The data-boundary and outcome-before-mechanism ideas begin here and are formalized on their concepts pages.

Before any provider call, Hankweave can check a hank's configuration statically. The capture below shows that check passing for the full anchor: 7 codons • 0 loops, with no codons executed and no provider health checks run.

Output
✓ Configuration is valid!

╭──────────────────────────────────────────────────────────────────────────────╮
│  quote-template-unification v1.0.0                                           │
│  7 codons • 0 loops                                                          │
╰──────────────────────────────────────────────────────────────────────────────╯

Read the capture as a pre-codon configuration check only. It writes only a temporary log. The registry setup disables provider health checks for validation; runtime startup is a separate concern. Validation may add $schema to the hank. The capture is not a live-run success or a credential proof.

Check-it: Before moving to chapter 1, confirm the bundle-relative entry chapters/ch1, the two-key ladder for ch3 onward, and the 7 codons • 0 loops validation result. Then follow the chapter's own run and verification instructions.