package pipeline // holes.go: what a HOLE in the export is — the one predicate the plaintext render (cmd/tmctl render.go), // the book writer (bookbuild.go) and any later consumer read the per-unit states through. // // BookExport's counters carry three book-level states (pending, ghost, drift). Two more are PER UNIT and // live only in the record: a unit that reached the final stage and shipped NO text (withheld — a // substantive flag or an upstream skip, D2: contaminated output never ships), and a unit that shipped // text with a member's worth of it MISSING (incomplete — the c-lite editor dropped a member). A file that // consults the counters alone comes out «silently whole» on a book with either, which is the worst thing a // reader's copy can do; hence one definition, here, rather than an arithmetic each surface repeats. // HoleKind names why a unit has no text, or not all of it. type HoleKind string const ( // HoleNone: the unit shipped its whole text. HoleNone HoleKind = "" // HolePending: a manifest unit with no final row — not translated yet (BookExport.PendingUnits). HolePending HoleKind = "pending" // HoleWithheld: a final row with no text — a substantive flag / upstream skip withheld it. HoleWithheld HoleKind = "withheld" // HoleIncomplete: text present, a member chunk of it missing (ChunkExport.DroppedMembers > 0). HoleIncomplete HoleKind = "incomplete" // HoleStale: the unit's final row was made for a source that is no longer the book's (its content // hash differs from what the run would render now). The WRITER's kind (bookbuild.go staleUnits) — // UnitHole never returns it, because the export record does not carry the fact. HoleStale HoleKind = "stale" ) // UnitHole classifies one export record. The order of the tests is the plaintext render's: pending // first, then «no text», then «text with a dropped member» — an incomplete unit DID ship text. func UnitHole(ce ChunkExport) HoleKind { switch { case ce.Disposition == exportPending: return HolePending case ce.FinalText == "": return HoleWithheld case ce.DroppedMembers > 0: return HoleIncomplete } return HoleNone } // HoleCounts returns the per-unit hole counts of an export: withheld and incomplete (pending is the // counter BookExport.PendingUnits). `incomplete` is a SUBSET of the exported units, not a fourth part // of the total: those units did ship text, with a piece of it missing. func HoleCounts(exp *BookExport) (withheld, incomplete int) { for _, ce := range exp.Chunks { switch UnitHole(ce) { case HoleWithheld: withheld++ case HoleIncomplete: incomplete++ } } return withheld, incomplete }