Pipeline¶
core/pipeline/ is the document-agnostic spine of a run: plan → create → drive →
render → persist → manifest, then export. It calls the pack only through the
contract hooks.
| Module | Role |
|---|---|
run.py |
Orchestration — create_run, work_run, resume_run. |
planner.py |
plan_units — a document count → a list of work units (index ranges). |
worker.py |
GenerationWorker — the drain loop: claim → generate → render → persist per unit. |
source.py |
DocumentSource protocol, stable_seed, prepare_source. |
renderer.py |
DocumentRenderer protocol, HtmlRenderer; render() → bytes. |
pdf.py |
PdfRenderer — Chromium/Playwright print; running header; page-fit. |
degrade.py |
degrade_pdf — scan post-processing (rasterise, noise, JPEG, no text layer). |
golden.py |
encode_shard / decode_shard — the exactness-preserving golden codec. |
manifest.py |
UnitManifest / RunManifest — the self-describing per-run manifest. |
export.py |
export_run — golden shards → a sink (see Export). |
Run orchestration (run.py)¶
Three entry points, all called by a CLI command or a Cloud Run task:
create_runplans the run into units and records it. Safe to call from every worker at once — creation is a conditional write, so exactly one worker plans and the rest wait for the plan to land (Run.plannedcloses the follow-on race).work_rundrives one worker until the unit pool is empty. Multiple processes calling it against the same store is the multi-worker case; the atomic claim keeps them from colliding. On the transition toCOMPLETED(and only then) it writes the root manifest — so the root's presence is the completion signal.resume_runclears any pause, returns failed units to the pool, and reclaims units abandoned by crashed workers (expired leases).
A run's identity lives in the state store, not a Python object, so any process that can reach the store can start, resume, pause, or cancel it.
The worker loop (worker.py)¶
GenerationWorker.run() claims the next unit and processes it until none remain.
For each document index in a unit it: source.generate(run_id, index) → a
GoldenRecord; renderer.render(record) → bytes; blob.put(key, …);
record.to_rows() → golden shards; and — before marking the unit done —
writes the unit's manifest part. That order is load-bearing: a completed unit
always has a manifest part, so the root manifest can trust the parts. A failed
document marks the unit failed and the worker moves on (retried on the next
resume, not in a hot loop).
Determinism (source.py)¶
stable_seed(run_id, index) is a SHA-256 over (run_id, index) — every draw an
invoice makes is seeded from it, so the same index always yields the same
document. That is what makes a run resumable (re-generating an index
overwrites identical content) and the golden set reproducible.
prepare_source runs a source's one-time, run-scoped setup (e.g. resolving a
Selection against the roster) once, up front, so a bad configuration stops the
run rather than failing document after document.
Rendering (renderer.py, pdf.py, degrade.py)¶
HtmlRenderer produces the HTML via the kernel's Jinja environment
(render_record(pack, record)). PdfRenderer prints that through
Chromium/Playwright, using header_fields(record) for the running header and
handling page-fit (fit_scale) and (cont'd) markers on page-spanning sections
(spanning_sections). degrade.py then realises the capture condition: clean
passes through with its text layer intact; a scan rasterises at 150 dpi,
degrades with the record's own seed, and re-wraps with no text layer so OCR
must read pixels. (Handwritten pads are rendered by the invoice pack; see
Invoice generation.)
Golden shards & manifest (golden.py, manifest.py)¶
golden.py encodes a unit's rows to a gzipped-JSONL shard, preserving exact
values (money as strings, not floats). manifest.py writes a part per unit
(<prefix>/manifest/unit-XXXXXX.json, listing that unit's documents and shards
with a sha256 each) and, at completion, a root (<prefix>/manifest.json)
indexing the parts plus totals. The root refuses to write over a missing part,
and its presence is the run's done-signal — a partial run has parts but no root.
A consumer can reconstruct and verify a run from the manifest alone
(read_run_manifest, is_complete, verify_run, enumerate_document_keys).
See Concurrency & coordination for the claim / lease / completion contract, and Execution flows for the end-to-end sequences.