Changelog
The press records its own releases here from v1.7.0 onward; earlier history lives in the tags and their messages (v1.0.0 through v1.6.0, 2026-07-18: the packaged instruments, the art department, generated front matter, the print pack, registrations, the operator, the aesthetic system, and the public-readiness hardening of the P0/P1 audit).
[Unreleased]
Added
Set-piece typographic constructs:
::: cascade,::: verse,::: tail. Some passages are neither prose nor a plain figure – a staggered address, a stanza, a poem shaped like a mouse’s tail – and Markdown cannot express their geometry. The author writes plain lines in a fenced div (an ordinary line block) and the house lays the form, projecting it into every edition:cascadesteps each line one indent further (an address, an inscription);versesets the house stanza measure (\PressVerse), opening quotes hanging by protrusion;tailcomputes a serpentine calligram from the lines themselves – a sine-driven offset with the size tapering head to tip. The words stay live, searchable, and accessible, and the plain-text edition shows them as verse; a book that uses none renders byte-for-byte as before. Handled byset-pieces.luaand the.cascade/.verse/.tailstylesheet rules. The Alice gallery example now sets its crocodile poem, the “Alice’s Right Foot” address, and the Mouse’s Tale with them.An
ornatechapter-opening style and an aesthetic-driven scene break.chapter-opening.style: ornatesets the chapter initial in a decorated foliate font (yinit, from the toolchain’stexlive-fonts-extra) for print; because that font cannot cross to the browser, the web reader and EPUB degrade it honestly to a styled display capital. Separately, a book whose aesthetic declaresscene-break: asterismrenders every Markdown thematic break (* * *) as a centered asterism in every edition, the light literary mark a bare rule reads too heavily for;scene-break: fairy-dustrenders it instead as the staggered three-row array of asterisks — four, three nested into the gaps, four — that Tenniel’s 1866 Alice set at each shrink and grow, rebuilt from one thematic break by a print macro and matching CSS. Both are opt-in: a book that chooses neither (stylenone/drop-cap/raised-cap, scene breakrule) typesets byte-for-byte as before. Choosingornatechanges a book’s rendered openings, so it is a design decision, not a fix. Documented indocs/CONFIGURATION.md.Numbered figures, cross-references, and a split illustration list. A figure that declares an explicit informative kind (
.figure,.chart,.map,.photo,.diagram) now earns a by-chapter number (“Figure 3.2”), a place in a new List of Figures kept apart from the Victorian List of Plates, and a cross-reference target: give it an#idand refer to it anywhere with@id, which resolves to a linked “Figure 3.2” — the same number in every format, computed once by a Lua filter with no pandoc-crossref dependency. A plate stays unnumbered (the woodcut idiom), and a bareimage is a plate too, so a book that declares no numbered figure typesets byte-for-byte as before. The PDF verifier checks both lists’ links land on real image pages.A hardened, relative, parity-aware figure-placement vocabulary on the image attributes:
widthas a measure (full-/half-/third-measure, never inches),place(inline,wrap-inner,wrap-outer,plate,frontispiece,full-bleed,margin— parity-aware inner/outer, never left/right),outset(the runaround gap, in em),fig-alt(accessible alt text, a fourth field distinct from the caption, theart:prompt, and any credit line), anddecorative(an empty-alt ornament).press checkrefuses a malformed placement — an absolute width, a left/right side, an out-of-vocabulary place, a non-em outset, a measure on a plate, or a decorative image that still carries alt text — before any render. Parsed and validated inpress.figures; documented – with a source-and-shot showcase of every placement on the illustrations page – indocs/ILLUSTRATIONS.mdand theplates-and-woodcutsskill. In the PDF each placement is now laid, not merely accepted: a wrap fuses to the head of the following paragraph so the running text closes around it on the binding-aware side – a standalone wrapfigure wraps nothing – with a\Needspaceguard that keeps a wrap off the last lines of a page; andfull-bleed/frontispiecetake their own cleared leaf, never a deferred float that lands a page late and leaves a blank, the caption set on the same leaf (\captionof, no float) and the frontispiece on the verso facing the next chapter. The caption package is the single caption authority – small italic, no automatic label, centred, its list anchor carried byhypcap=true– so a placed figure’s unnumbered caption is clean where the float package had\caption*print a stray asterisk above it; ordinary plate captions are unchanged. A numbered figure’s “Figure C.N.” label is boxed so a narrow column (a third-measure wrap) can never hyphenate it into “Fig-ure”. markdown, plain text, and docx keep a clean in-flow figure. The illustrations page shows the vocabulary at work on real dense pages, every placement, every width measure against each placement, and how a wrap behaves against running text – closing under ample prose, letting the next block ride up under short prose, and the page-foot guard carrying a wrap whole to the next page – as source and full-page shots. (A new major: the design contract’s figure treatment changes.)A repeatable print-profile lifecycle: scaffold, prove, seal (#221). Adding a trim or an ink is now one documented path rather than an ad hoc edit.
python3 -m press.profile_lifecycle scaffold <id> --trim WxH [--ink color]derives a new profile from proven geometry; the golden-copy geometry test renders every shipped profile at its declared trim, so a new profile is covered the moment it exists; and... seal <id>records the profile’s design-affecting digest in a shipped ledger (data/profile-seals.yaml). A new selftest gate (check_profile_seals) then turns red if a profile’s geometry ever drifts from its sealed digest, making the design contract mechanical. Documented indocs/PROFILE-LIFECYCLE.mdand theprofile-lifecycleskill.press checkrefuses an unsupported trim + ink + provider combination (#222). Which trims and inks each provider (KDP, IngramSpark, Lulu, and the house baseline) prints is captured as data in its provider spec, exposed as a queryable trim/ink support matrix (ProviderSpec.support_matrix,ProviderSpec.supported_inks). A book whose selected design profile asks for a trim its printer does not cut, a binding it does not offer at that trim, or a colour interior it does not print now failspress checkwith a clear message, before any Pandoc or TeX render rather than deep in the cover generator. The page-count bounds stay a build-time check, where the real page count is known. A book that names no provider keeps the house spec, which declares no catalog and refuses nothing, so every existing book is unchanged.A public-domain illustrated classic joins the gallery: the first three chapters of Alice’s Adventures in Wonderland, set in the house single-ink idiom with John Tenniel’s original 1865 wood engravings. It is the gallery’s first example to exercise the interior-plate (engraving) path on real public-domain art, imported from a plain-text source the way a live book would be. Nothing about it is generated; the plates are attributed as public domain in the colophon (#227).
Fixed
- A plate on a gradient or textured ground is refused at
intake, not ghosted. The luminance key reads every dark pixel
as ink, so a ground that is not a flat light colour survives as a smudge
(its darker end keys only part-way to transparent).
press art acceptnow judges the delivery’s border, where the ground shows: most of it must be light and free of a top-to-bottom or side-to-side drift. A ground that fails is refused with the fix named – a plain near-white ground, or a colour interior that keeps the plate’s exact pixels – rather than segmented into a ghost. Sparse ink strokes that reach the edge do not trip it. - A chapter that opens on dialogue hangs its opening quote in
the margin. The leading quotation mark now hangs into the left
margin at body size, so the dropped initial stays flush to the text
block – the same optical margin every other chapter opening keeps
(Bringhurst’s rule: keep the quotation mark, and hang the punctuation).
In print
\PressDropCaphangs it through lettrine’santewith\smash{\llap{...}}; on the web and in EPUB the.drop-cap-leadspan is pulled into the margin by a negative margin. A chapter that opens on an ordinary word compiles to the exact same\lettrinecall as before, so a drop-cap book is otherwise unchanged. The Alice gallery example, whose chapter 2 opens on “Curiouser and curiouser!”, demonstrates it. - A generated title page with no subtitle shows one rule, not two. The title page brackets a subtitle between two ornamental rules; a book with no subtitle rendered both rules with nothing between them. The second rule (and the subtitle’s spacing) is now conditional on a subtitle existing, so a subtitle-less book gets a single rule under the title. A book that does carry a subtitle is unchanged.
Changed
Plates are kept as alpha PNG masters, composited per surface (#226).
press art accept --as plate:<name>no longer bakes a white background into a JPEG: it keeps the plate as an alpha PNG master – ink on transparency, the light ground keyed out with a luminance key (ink-on-white is trivially separable), greyed to the single interior ink when the aesthetic states it. One master then composites onto any surface: the print interior flattens it onto white, a cloth cover onto the field colour (as the imprint logo already does throughprint_safe), and the reader edition serves it transparent so a plate reads on a white or a dark page. A baked-white delivery is segmented at intake, never shipped opaque; a delivery already on transparency keeps its mask.press art enhancepreserves a master’s alpha through finishing.A fine-press craft pass on the house typography (a new major: the design contract seals typography, and this moves it). The interior is now set with the microtypography a careful setter always applies: optical margins and even colour (microtype protrusion and expansion); oldstyle text figures for the aesthetic’s PDF font, so digits sit in the line instead of standing up; ligatures suppressed across a morpheme seam (selnolig –
shelf|ful, not anffwelded across the join); and a widow/orphan/hyphenation discipline (no widows or orphans, no paragraph ending on a hyphen, no two hyphenated lines stacked, no hyphen carried across a page turn, and no lone word left on a paragraph’s last line – a runt, which the page-edge widow and orphan penalties cannot catch because it is a break inside the paragraph; a finite\parfillskipmakes a near-empty last line underfull, so the breaker draws the word up instead). A newtypographic-spacing.luafilter binds a curated set of abbreviations to what follows with a non-breaking space in every edition – a title before a name (Mr. Smith), a reference before a number (p. 42,Fig. 3), a person’s initials (C. L. Dodgson) – so a line never breaks in the wrong place. The manuscript stays plain prose; the house does the fine work, and ordinary sentences (every full stop included) are left exactly as typed. Because it moves glyph positions and line breaks by design, the visual and structural regression baselines are re-recorded with a reason.
[2.3.1] - 2026-07-24
A hardening release. Two output bugs are corrected – a retail barcode that crowded its quiet zone, and a cover commission that invented generic motifs when the book named its subject only in prose – and a latent hole in the adapters boundary gate is closed, so the “one approved home for a subprocess” invariant is now enforced by construction rather than held by luck. Three verifier guards gain the known-bad fixtures that prove them, and the landing page features Make Ready as the flagship book built end to end through the press. Compatible within v2; no build changes a valid book’s typography or layout.
Changed
- The landing page features Make Ready as the flagship
showcase – a book taken end to end through the built-in
facilities: a commissioned clothbound cover and fourteen engraved plates
finished by
press art enhance, the reader dressed in the book’s own cloth, a subject index, registered ISBNs, and a table of authorities. It stands beside Mostly Done. as proof the pipeline makes real books, not just demos.
Fixed
- The retail cover barcode no longer crowds its quiet zone
(#210). The EAN-13 symbol was drawn in a TikZ node whose
default padding shifted it left, so the first guard bar read as ink
inside the left quiet zone and
verify-printrefused an otherwise-correct wrap. The barcode node now setsinner sep=0(matching the front-cover-art node), so the symbol sits exactly where the verifier’s pixel model expects it. The integration cover-wrap fixture now carries a real print ISBN, so the quiet-zone check runs in CI. - A cover commission draws the book’s own subject instead of
inventing one. When a book stated its subject only in
description(or as acover.emblem) and not as an explicitcover.subject,press coverfell back to the literal phrase “the book’s subject” and the image model invented generic motifs. It now draws the subject from--subject, thencover.subject, then the book’sdescription, thencover.emblem, and only then the literal – so a book that says what it is about gets that on its cover. - The adapters boundary gate no longer has a from-import blind
spot. The gate that proves nothing outside
press.adaptersholds a direct subprocess or environment call matched only thesubprocess.Popenattribute form;from subprocess import Popenandimport subprocess as spslipped past it, and the process controller’s launcher did exactly that – a real subprocess the gate reported as clean. The gate now flags the from-import binding of an executor and resolves aliased module imports, and the one production launcher has moved intopress.adapters(its streaming sibling ofSubprocessRunner) where a boundary call belongs, so the single-home invariant is enforced, not merely intended. Three verifier guards – plate-link resolution, cover-wrap transparency, and barcode readability – also gained the known-bad fixtures that give them teeth.
[2.3.0] - 2026-07-24
The art department grows a finisher, and a book becomes its showcase. The press learns to finish a commissioned plate – upscale it through a style-matched model, quantize it to a small palette, and write it as a lossless PNG smaller than the JPEG it replaces – and the whole plate pipeline becomes format- agnostic so that PNG is a first-class plate. The operator desk shows more of what it already knows, three confirmed defects are fixed, and the reference book Make Ready. is re-commissioned end to end through the built-in facilities. Compatible within v2.
Added
press art enhancefinishes plate art for print and web in three style-matched stages: an AI upscale through a Real-ESRGAN model chosen for the medium (a line model for an engraving, not a photo model), a resample to a print-grade long edge, and a quantize to a small palette written as a lossless PNG. The medium inconfig/aesthetic.yamldrives the model and the palette. The upscaler is a detected external tool (Upscayl’supscayl-binor a standalonerealesrgan-ncnn-vulkan), reported bypress doctor; absent, the command still quantizes and compresses, so the win lands without it. This retires the standing scar that “PNG barely compresses engraving grain”: once the grain is quantized to a handful of grays, a plate ships as a lossless PNG smaller than the lossy JPEG it replaces.- The operator desk shows more, and shows progress.
The dashboard renders the full
press doctorreport – every tool’s state and its purpose – not just a one-line summary, and a run screen tracks the build’s stages as they stream by instead of only echoing the log. The command list was already complete (catalog-driven, parity-enforced); this surfaces the data the desk already held.
Changed
- The plate pipeline accepts PNG as well as JPEG, so
a plate finished by
press art enhance(which produces a lossless quantized PNG) is a first-class plate: it is counted for the List of Plates, verified, and print-prepared exactly as a JPEG plate is. Every place that resolved plates globbed*.jpgonly, so an enhanced book silently lost its List of Plates and its plate verification; they now resolve through onebooklib.plate_fileshelper that returns both formats. This completes the enhance feature and fully retires the “PNG barely compresses engraving grain” scar.
Fixed
- The page secret scan no longer false-rejects a book whose
own words contain “secret” or “password”. The credential marker
matched bare English substrings, so a valid book titled “Secrets of the
Trade”, or any page whose prose read “the secretary kept the password to
herself”, failed
press pages/verify-pages/checkwith a spurious “page metadata leaks private build data or a secret” – the og:title, og:description, and JSON-LD carry the book’s own title and prose legitimately. The marker now matches on credential shape (a provider key prefix, a bearer token, a credential-bearing query parameter, or a key=value assignment whose value looks like a token), never a bare word, closing the false positive across all three scans that share it (the metadata head, the rendered-page commerce scan, and commerce-config validation) without weakening detection of a real leak such as?apikey=sk_live_.... - An ISSN lookup no longer reads one issued ISSN as a
collision. The live ISSN Portal returns a resource’s JSON-LD as
the main node plus
#ISSN,#KeyTitle,#Record, … fragment sub-nodes on the same base IRI; every node reduces to the same eight digits, sopress lookup issncounted them as several resources and returned AMBIGUOUS for a perfectly good ISSN. The match now collapses the nodes of one resource by their fragment-stripped IRI, so AMBIGUOUS means two genuinely distinct resources carry the ISSN, and the ordinary success path resolves to FOUND. - The print-profile PDF checks gain the known-bad fixtures
they lacked.
verify_black_inkandverify_mirrored_margins– the black-ink-only and mirrored-gutter guards for a print interior – had no negative test, so breaking either left the suite green. Each now has a synthetic page it must reject (a colored region; a wrong-side gutter), confirmed by mutation.
[2.2.1] - 2026-07-24
Mobile and boundary repairs, no design change to any book. The documentation site’s masthead no longer collapses and its wide tables read on a phone; a book’s own web tables stack on a narrow screen while rendering byte-identically at reading width; and a registration lookup bounds its response as it reads it. Compatible within v2.
Fixed
A registration lookup bounds the response body as it reads it (#209). The size cap was applied to the body the transport had already read, so a server that omitted or lied about
Content-Lengthdecided how much memory a lookup spent before the cap could refuse it. The bound now travels with the request:adapters.http.urlopen_transporttakes an optionalmax_bytesand reads one byte past it, so an overrun is visible and fails closed rather than being truncated in silence. It is opt-in and unbounded by default – a cover image and a provider payload legitimately read whatever the server sends, and a blanket cap would silently truncate them.Wide tables are readable on a phone, on every HTML surface the press publishes. A three- or four-column table used to run its far columns off the edge: on the docs site it scrolled sideways inside its own box, and in a book it fell back to browser defaults, which styled it not at all. Narrow screens now stack each row into a card, and every cell wears its column’s header – carried onto the cell as
data-labelbywebmeta.label_table_cellswhen the page is built, because CSS cannot reach a cell’s header on its own. One implementation serves the docs site, the book’s reader pages, the landing page, and the single-file HTML edition. On the book surfaces the change is scoped to the narrow breakpoint and nothing else: at reading width a book’s tables render exactly as they always have (proven pixel-identical), because within a major the press may correct broken output but must not restyle a valid book. Giving tables a house treatment at every width is a design change and waits for the next major. Tables with no header row – the reference records, a book’s downloads list – already read in two columns and are deliberately left untouched.The masthead lockup no longer collapses on the documentation site. The desktop sidebar is a column flex container capped at the viewport height, and the lockup’s
overflow:hiddenzeroed its automatic minimum size, so under shrink pressure the box collapsed to nothing and sheared the logo to a sliver at every desktop width. The browser layout check now measures the clipping box against the image – the image reported its full height throughout – and refuses a stacked table that still scrolls sideways or clips a cell.
[2.2.0] - 2026-07-24
Composability and proof. The typed adapter boundary closes over the
whole package, so a direct process, environment, or tool call is now
refused anywhere outside press.adapters; every public web
surface a book publishes carries one honest metadata contract; the
packaged provider ledger becomes a generated projection of its canonical
source; and the two prose checkers a book runs are held in step by a
differential corpus. The repository’s own security baseline is proven
end to end, limitations recorded rather than claimed away. Compatible
within v2: no typography or layout changes, and a book with no
site-url publishes byte-identically to before.
Added
Opt-in read-only registration lookups (#203).
press lookup lccn <number>andpress lookup issn <number>resolve a record from the Library of Congress and the ISSN Portal through read-only adapters, off by default: network access is explicit, response parsing is defensive (ElementTree expands no external entities), and every failure resolves to an honest UNAVAILABLE rather than a crash. Withregistrations.lccnorregistrations.issnconfigured, the number argument is optional.A jargon-parity contract between the packaged checker and the portable skill copy (#201, INV-editorial-jargon-parity). A differential corpus and fuzz prove the press’s jargon checker and the standalone skill agree case for case, and a source-identity check holds their shared logic byte-identical, so the copy an author runs cannot drift from the one CI enforces.
Automatic roadmap reconciliation (#197). The
Roadmap integrityworkflow reconcilesroadmap/milestones.jsonand ROADMAP.md against the live GitHub milestones after a merge tomain(and weekly), so the registry cannot silently drift from the issues it tracks.One metadata contract across every press-built web surface (#158). Canonical URLs, Open Graph, Twitter cards, and JSON-LD now cover the docs site, the book landing page, the reader index, and every chapter page, generated from the book model rather than duplicated in templates.
src/press/webmeta.pyis the single emitter, so the rule that an offline or preview build omits everything URL-shaped – canonical,og:url,og:image, and the URL fields inside the JSON-LD node – lives in exactly one place: a book with nosite-urlships no falsely-canonical output. The book site also gains asitemap.xmlandrobots.txt, emitted only when asite-urlis configured.verify_pagesgainscheck_metadataandcheck_book_sitemap, with eleven damage fixtures they must reject.
Changed
- The typed adapter boundary is now complete (#199).
The last nine legacy modules –
__main__,booklib,scaffold,selftest, and theverify_*/gen_coverwrapfamily – route every process, environment, and tool-probe call through the typed adapters. The boundary gate’s legacy allowlist is deleted, so a directsubprocess/os.environ/shutil.whichcall is now refused anywhere outsidepress.adapters, and the import-side-effect sandbox moved toadapters/import_guard.py. Every migrated site is proven to drive its injected adapter under a recording fake; deterministic composition, no behavior change. - The packaged provider ledger is now a generated artifact,
not a maintained duplicate (#200).
quality/providers.yamlis the one canonical source;src/press/data/providers.yamlis a deterministic projection of it (a fixed banner, then the canonical bytes verbatim), regenerated bypress selftest --write-docs. The selftest and the distribution tests now compare the packaged copy byte-for-byte against that projection, so a comment-only or whitespace edit — drift a semantic compare missed — fails CI, and the wheel can no longer ship a hand-copied, divergent record. The duplicated header and footer comment in the old mirror are gone. The update/review workflow is documented indocs/PROVIDER-DATA.md, linked from the print-format docs.
Fixed
- Collection-policy meta-tests are warning-clean across the supported pytest and pytest-asyncio range (#202). The nested pytest runs disable the asyncio plugin and the root suite pins the fixture loop scope, so a version bump within the supported range no longer raises warnings.
[2.1.0] - 2026-07-23
The art department and the test bar. The press gains a cover-design system and an in-book illustration system, its toolchain image goes native on Apple Silicon, and — after a title-page generator shipped a dropped and a clipped cover behind green integration tests — every example book is now verified as a built artifact and the machinery that lets a producer ship unproven is closed. Compatible within v2: a book without a baked cover renders byte-for-byte unchanged. The one design change touches only books that carry a cover — the cover plate now fits the text block of whatever trim it is, correcting a fixed plate that clipped on 5×8 and overflowed the block even at 6×9.
Added
- A cover-design system (
press cover). Ten house cover styles in a catalogue (src/press/data/cover-styles.yaml), selected byconfig(cover.style,cover.subject) and rendered through the shared image-model layer; a book may also write its own style directly into its repo. The art is generated once and committed as a static asset, never in CI, so a build is deterministic. Listed bypress coverand documented on the site with visual examples. - An in-book illustration system
(
press illustrate). Seven single-ink illustration media (src/press/data/illustration-styles.yaml) for plates, maps, and diagrams, selected byplates.style;--fromturns real source material (a photograph the author took, a rough map) into a house-format plate. Installed throughpress art accept --as plate:<name>, so it lands in the same geometry-checked pipeline as every other commission. - A gallery of eight example books, each with a distinct palette, a real Penguin-style illustrated cover, and its actual built PDF with page previews on the site — the press proving, on itself, that nothing is hardcoded.
- The example PDFs are verified in CI. The gallery
build now runs
press verifyon every example it builds; a book whose PDF loses a sentinel, a cover, or a page turns the build red. This caught a fragile sentinel on its first run. - A producer must name the rejection its verifier turns
on. Every module classified
producerin the surface inventory is now held, by the selftest, to an entry inPRODUCER_REJECTION_PROOFS(or a visible, shrinking pending list) — so a new artifact-maker cannot inherit a blind verifier the way the title-page generator did. - A coverage-floor gate. A module baselined below 50% branch coverage must now name its reason in the ratchet’s allowlist, and the list may only shrink; a new, silent low floor turns the gate red. CONTRIBUTING gains a “what a proof has to prove” section stating the principle: assert the artifact, not that the line ran.
Changed
- The toolchain image is multi-arch (#206).
press-toolchainis now built and smoked on native runners for bothlinux/amd64(CI) andlinux/arm64(Apple Silicon), assembled into a manifest list; local Docker PDF builds on a Mac run natively instead of dying under qemu.build.ymlpins the multi-arch image, so a book’s CI resolves the exact multi-arch bytes. - A baked cover fits the text block of any trim. The
generated title page sized its cover plate to a fixed 5.6×7.1in box; it
now fits
\textwidthby\textheight. A book carrying a cover renders its cover page at the trim’s own proportions (on 6×9, a hair smaller and no longer overflowing the block); a book without a cover is unaffected.
Fixed
- Every book’s reading PDF leads with its cover, on any
trim. The cover plate rendered only when a book supplied
config/front-matter.yaml, so a book with acover.jpgand no front-matter config shipped with no cover at all; and where it did render, the fixed plate size clipped on 5×8. Covers now trigger on the asset itself and fit the trim, andverify_pdfrefuses a reading PDF whose first page is not the cover — the miss that started this release, now a checked property. - The producer gate runs from an installed wheel. It
read the repo-only surface inventory unconditionally and crashed
press selfteston a fresh install; it now skips outside a checkout, like the other repo-reading checks. - A gallery example’s sentinel moved off its drop cap. A sentinel anchored on a chapter’s opening word could never match, because the house drop cap raises the initial and small-caps the remainder; re-anchored mid-chapter.
- Site reference tables render correctly on desktop, drop caps stay off back matter, the gallery preview strip no longer scrolls, and the signal-and-noise example’s index-terms schema builds.
[2.0.0] - 2026-07-21
The composable press. A new design major: trim, binding, cover
material, and print vendor are configurable; typography and web design
are profile-driven; chapters may open with a drop cap; and books extend
the press by declaration. A @v1 book is unaffected and
keeps rendering byte-for-byte; a book opts into v2 by repinning
(press migrate), and until it selects a non-house profile,
even a @v2 build is unchanged.
Fixed
- The cover wrap is now print-safe (#189). The wrap
embedded the raw
assets/cover.jpgand imprint logo, so a transparent logo (a soft mask) or an over-resolution source reached the cover and tripped a print-on-demand preflight (Lulu, KDP), even though the interior had been cleared. The generator now embeds flattened, resolution-capped copies viaprint_safe.prepare_cover: the logo is composited onto the exact field colour it lies on (so it stays invisible against the field, with no soft mask), and both cover art and logo are capped from the wrap geometry, since the logo prints small on the cover (a 1000px logo at 1.1in is 909 PPI, over the limit) where the same file clears the interior’s roomier placement.verify_coverwrapnow refuses a wrap that carries transparency or an image over 600 PPI, so a regression cannot ship a cover a printer would reject. Verified end to end on a real 46-page 6×9 wrap: the logo drops from 1024² RGBA at 909 PPI to an opaque 649² at 590 PPI, with zero soft masks.
Added (v2)
- Semantic chapter-opening drop caps (#192). A design
may open each chapter with a dropped or raised initial, and the
manuscript stays ordinary prose: no
\lettrine, no CSS floats, no font commands. The decision is made at the document-tree layer by a pandoc Lua filter that finds the first eligible paragraph after each chapter heading (skipping an epigraph or other non-prose opener) and splits its opening word into a Unicode grapheme initial (a base letter with its combining marks, never a stranded accent), the leading punctuation kept with it, and the remainder of the first word. For the PDF it emits a single centralized\PressDropCapmacro (built onlettrine, with aneedspaceguard so a chapter opening is never stranded at the foot of a page); for HTML and EPUB it emits semanticchapter-opening/drop-capspans a floated stylesheet renders, with a clearfix so a short opening paragraph never overlaps the initial. Enabled through the profile (design default) or a book’s ownchapter-openingoverride (style,lines,small-caps-remainder); off by default, so a book that does not opt in renders byte-for-byte unchanged (proven by the house visual baseline showing zero drift with the filter installed). The literary-novella gallery example opts in to show it. Guaranteed byINV-dropcap-opening, proven at the grapheme level intest_dropcapsand at the emission level by running the filter through pandoc. - A gallery of very different example books (#190),
proving nothing is hardcoded. Five complete books under
examples/— a naturalist field guide, a poetry chapbook, an academic monograph, a literary novella, and a seasonal cookbook — each produced by the same pipeline with only its own config differing: two trims (house 6×9 and novella 5×8), five aesthetics, footnotes, a subject index, a custom web stylesheet, four kinds of front matter, three kinds of appended matter, and a per-book jargon allowance. Every example is proven a valid press book on each test run (the config passes the typed model, the design surfaces genuinely vary, and each passespress check), and the docs site gains a gallery page cross-linked from the parts-of-a-book and configuration guides. - Registrations automation: ONIX 3.0, PCN prep, and ISBN
conversions (#191).
press onixgenerates the ONIX 3.0 metadata record distributors ingest (dist/<slug>.onix.xml) from config: a product per sellable edition (the print ISBN as a physical product whose form follows the binding, the EPUB ISBN as anEAproduct), the ISBN asProductIDType15, title, contributors, publisher, and a year-only publishing date — degrading honestly where the press holds nothing (no<Price>, because a book repository holds no price by design).press pcnassembles the Library of Congress PrePub Book Link field values (dist/<slug>-pcn.txt) for the LCCN application, flagging any gap the form needs. Newregistrationshelpers round out the arithmetic: ISBN-10↔︎13 conversion and the ISBN-10 / ISSN mod-11 check digits. There is no API that issues an identifier or accepts a feed without human onboarding; the automation is the generate/validate/ format side, and the manual gates are documented honestly. - Typography and web design are now profile-driven
(#172): a design profile carries not just trim and margins but the
structural type treatment (paragraph indent, leading) and the web
reading measure (max width, base size, line height). The house profile
projects exactly the v1 values, so a house book renders byte-for-byte
unchanged — proven against the committed visual baseline with zero drift
— while
novella-5x8becomes a genuinely different qualified design (its own type treatment and measure, not just a resized house). The split is deliberate: the profile seals the structural typography that defines a design, and the aesthetic keeps owning identity (font family, palette), overriding the profile. Each profile has a stabledigestover its design-affecting data, so a sealed value cannot change without the key that scopes its visual baseline moving; a newINV-profile-geometryproves every profile renders at its declared trim. - The v1→v2 migration and rollback contract (#174):
press migratemoves a book to the next press major by repinning it — the press major inrequirements.txtand the CI workflow — and nothing else. The manuscript, config, and accepted art come out byte-for-byte identical; a dry run (press migrate plan) reports every change and design consequence before any mutation;applywrites an exact backup first;rollbackrestores the pre-migration bytes exactly. Because the house profile reproduces the sealed v1 geometry, a v1 book that repins to v2 and keeps the house profile renders unchanged — the design moves only when the author selects a non-house profile. Custom overrides the profile does not govern (tex/title-page.tex,assets/web/*.css,config/aesthetic.yaml) are named by the plan, never silently carried. Guaranteed byINV-migration-safeandINV-migration-preview, proven bycheck_migration’s scaffolded-book round-trip, and documented indocs/MIGRATION.md. - The extension contract (#171): the decision record
and executable gate for what a book or third party may add to the press.
Everything extensible — a design profile, provider spec, artifact,
skill, or workflow — is a named data file selected by id, never an
imported plugin, so behavior cannot come from import or entry-point
order. An extension carries a manifest declaring the names it claims,
the contract major it targets, the invariants it takes on and their
proofs, and its capabilities;
press’sconformancegate refuses a manifest that collides with a core name, targets an unsupported contract major, is malformed, names an unknown dependency, claims a sealed capability, or leaves an invariant unproven — before anything is built. The mandatory verification, path containment, artifact graph, config validation, and release gate stay sealed. Modelled byINV-extension-conformanceandINV-extension-seal, proven bycheck_extension_conformanceover a reference and five hostile fixtures, and documented indocs/EXTENSION-CONTRACT.md.
Changed (v2 — breaking, configurable print formats)
- Trim, binding, cover material, and print vendor are now
configurable, a v2 (design-major) change: a book pins
@v2to opt in, and a@v1book stays a 6×9 paperback, byte-for-byte. Design is modelled as versioned print profiles (print.profile) carrying trim and interior geometry; the house 6×9 profile reproduces the v1 output exactly.print.binding(perfect-bound, saddle-stitch, coil, casewrap, dust-jacket),print.material(paperback, casewrap, linen), andprint.providerselect the rest. Trim is derived from the profile, not hand-entered; theINV-config-triminvariant is rewritten around it. - Provider manufacturing specs for Lulu, KDP, and IngramSpark (from sourced, attributed research): the spine caliper, cover bleed, safety, and hardcover wrap geometry are vendor-specific and no longer hardcoded. The spine model branches on binding (a paperback formula vs a hardcover lookup or caliper), and the cover generator composes per-binding geometry that matches IngramSpark’s published casewrap and jacket formulas. Unsupported combinations (a dust jacket at KDP, an uncut trim, a page count out of range) are refused before rendering.
- A user guide, trim
& binding, and the internal design record
docs/PRINT-PROFILES-PLAN.md.
[1.20.0] - 2026-07-21
Brand identity: the press pilcrow mark and lockup across the CLI (a banner, status glyphs, and the house palette), the docs site (favicon, nav lockup, social card), and the README.
Added
- Brand identity: the pilcrow mark and the
press.lockup. The docs site gains a favicon, a theme-swapped nav mark (ink badge on light, vermilion on dark), and a large social card (og:image/twitter:image); the README shows the theme-aware lockup. Assets live insite/brand/. - CLI branding (
press.brand): a runningpresswith no command shows the block banner and tagline, andpress allends with a “your book is ready” line, in the house vermilion. Color is emitted only to a real terminal (honoringNO_COLOR/FORCE_COLOR), so piped output stays plain and scriptable.
[1.19.1] - 2026-07-21
Print pack, registrations, and public-readiness: offline ISBN assignment from an owned prefix, print-safe interior images that clear the Lulu file warnings, a top-to-bottom Lulu printing guide and a parts-of-a-book guide, versioned dependency/code/secret security controls, Python 3.14 support, and a repository-metadata drift check.
Added
- Offline ISBN assignment (
press isbn): record the registrant prefix your agency sold you asregistrations.isbn-block: {prefix, size}, andpress isbn assign print|epubmints the next unused ISBN-13 from it — computing the check digit, hyphenating with the agency’s own prefix hyphenation, and writing it toregistrations.isbn.press isbn statusshows what is used and free; a malformed block failspress check, and an exhausted block or an already-assigned edition is refused. There is no API that issues an ISBN — you buy a prefix once, then assign from it offline. Also addsregistrations.lccn_normalize(the Library of Congress canonical LCCN form). - Python 3.14 is a supported and tested version. The suite and the
desk pass on 3.14; CI’s package matrix runs it on Ubuntu alongside
3.10-3.13, the wheel declares the per-minor classifiers,
press doctortreats 3.14 as in-range, and the installation and compatibility docs name the 3.10-3.14 range (#168). - A scheduled
Repository metadataworkflow asserts the repository’s discovery configuration cannot silently drift: the homepage points at the docs site, the default branch ismain, wiki and projects stay disabled, and the topics remain set. It runs weekly and on demand, read-only (#165). - Print-safe interior images (
press.print_safe): theprinttarget now flattens transparency onto white and caps image resolution, so a print-on-demand vendor sees no transparency and no image over 600 PPI. A pandoc Lua filter redirects manuscript figures to the sanitized copies underbuild/print-assets/, and the generated front matter’s logo follows; the reading PDF is unchanged. On the reference book this cleared both Lulu file warnings and roughly halved the interior PDF. A book with a hand-authored title page points its own logo atbuild/print-assets/to match. - A top-to-bottom Lulu printing guide: which two files to upload, why the cover wrap fits Paperback Perfect Bound (and not the hardcover/dust-jacket templates), the Lulu settings that must match a book’s config, the two advisory file warnings press produces (the transparent logomark and its ~602 PPI placement) with their fixes, and a troubleshooting table.
- A task-oriented guide, the
parts of a book, covering how to add each common part: the byline,
the generated front matter (dedication, epigraph, acknowledgements,
colophon), the author portrait, an about-the-author page,
further-reading and glossary pages, footnotes, the index, and the
sources-and-authorities bibliography. It leads with the
press configcommands and is honest about which parts are authored Markdown rather than configuration. - Versioned security controls (#154): a Dependabot config covering the Python package, the pinned GitHub Actions, and the toolchain base image (weekly, grouped); a CodeQL workflow analyzing the Python package on every pull request, push to main, and weekly; and a dependency-review workflow that fails a pull request adding a high-severity vulnerable dependency. Each new workflow runs at least privilege and uploads nothing to a third party.
[1.18.0] - 2026-07-20
Book web surfaces and developer-experience: structured metadata on the book landing page, a real start-reading reader index, git-index isolation so the commit hook runs without a workaround, and documentation corrected against the current code.
Added
- The book’s landing page declares structured metadata generated from
the book’s own config (#158, book half): Open Graph and Twitter-card
tags, and a schema.org
BookJSON-LD node (name, authors, publisher, description, date, and the available editions asworkExample). Nothing is invented – a canonical URL,og:url, and cover image appear only when asite-url(and cover) is configured, so an offline build never claims a false canonical.press verifyfails if the landing metadata drifts from the config: a stale JSON-LD title, a canonical without a site-url, or a site-url without a canonical.
Fixed
- The chunked reader’s index is a start-reading page, not an empty
shell. Its
<main id="content">– the target of the “Skip to chapter” link – was empty, so the skip link went nowhere and there was no explicit way to begin reading. It now opens with an orienting line and one primary “Start reading” action pointing at the first part (by the same ordered manifest the previous/next pager uses), above the chapter contents; a chapter page keeps its own body inmainand gets no start-reading block (#160). - Git subprocesses observe only their own repository. A
git commitruns its hooks withGIT_INDEX_FILE(andGIT_DIR) pointing at the outer commit’s transient index, so the test suite – and press itself – would build or inspect a nested repository against the wrong index (a source archive came out empty, damage tests failed for the wrong reason). The production process runner now strips the ambient repo-bindingGIT_*from every git command it runs, closing the leak at the one boundary all git calls pass through; an explicitly injected git env is still respected for tests where git is the subject. The full suite now passes inside a realgit commithook without a workaround (#176).
[1.17.0] - 2026-07-20
A public-experience and maintenance pass: accessibility landmarks and declared metadata on every docs page, community scaffolding and governance, one contributor verify command, every Action off the deprecated Node 20 runtime, and the code deprecation warnings cleared.
Added
- Public-project scaffolding: structured GitHub issue forms (defect,
proposal, documentation) that ask for the diagnostics a report needs and
route security privately, a pull-request template tied to the project’s
actual contracts (#163); a Contributor Covenant Code of Conduct and an
honest single-maintainer
GOVERNANCE.mdcovering decision/release/security authority and bus-factor (#164); andscripts/verify.sh, one fast-to- complete command that runs the local half of CI’s quality gate (lint/type/selftest/pytest, then the coverage and mutation ratchets and the site build), documented in CONTRIBUTING (#162). The repository’s discovery metadata (homepage, topics) and auxiliary-surface flags are set (#165). - The documentation site declares its metadata instead of leaving
search engines and link previews to infer it: every page carries a
canonical URL, a meta description derived from its own first paragraph,
and Open Graph/Twitter-card tags, and the build emits a deterministic
sitemap.xmlandrobots.txt. The build fails if a page lacks a canonical URL or description (part of #158; book-page structured data is separate). - Every documentation-site page declares its language
(
<html lang="en">), exposes onemainlandmark, and leads with a skip-to-content link as the first focusable element; the build fails if a page lacks any of them (#157). The CSS-only mobile menu shows a focus ring on its toggle so it is operable and visible by keyboard (#156). (Internal doc links already stay on the site rather than bouncing to GitHub, #159.)
Changed
- Every pinned GitHub Action moved off the deprecated Node 20 runtime to its current Node 24 release (checkout, setup-python, upload/download-artifact, deploy-pages, upload-pages-artifact), each pinned by full commit SHA with the reviewed version in a comment; the artifact inputs we use are unchanged across the bump. A posture test fails if any action is ever left unpinned by SHA (#179).
Fixed
- Deprecation cleanup.
verify_coverwrapreads pixels withImage.tobytes()instead of the deprecatedImage.getdata()(removed in Pillow 14), with a press-scoped warning-to-error filter so it cannot creep back (#167); the async fixture loop scope is pinned tofunctionexplicitly, matching the suite’s isolation law and ending the pytest-asyncio default-drift warning, proven by a loop-identity and task-leak test (#169). The site’s Pandoc--no-highlightdeprecation is already gone: the redesign turned highlighting on and colors the tokens in CSS, so no deprecated flag is passed (#170).
[1.16.4] - 2026-07-20
Seller-of-record print ordering plus a public-experience pass: a validated configuration CLI and desk wizard, conventional CLI discovery, one YAML library and version, an independently consumable public toolchain image, an executable beginner quickstart, and a redesigned documentation site. (Earlier 1.16.0-1.16.3 tags did not release: the release contract, which only floats the major and publishes after every trust gate proves, surfaced CI-only issues fixed here before any Release existed.)
Fixed
- The invariant ledger resolves from the working directory when the
packaged
__file__-relative path is absent, so the desk end-to-end proof (which runs the suite against an installed wheel) can load it instead of breaking collection. - The per-module coverage baselines are restored to the values CI
measures. A local
--updatehad re-measured them on a machine where the ratchet’s integration-deselection and toolchain-hiding did not take effect the same way, inflating many baselines above the deterministic floor CI enforces. - The quality job’s
build.logis ignored, so it no longer dirties the working tree before that tier emits its trust receipt; a release requires clean-tree receipts, and an untracked log was failing the chain.
Added
A guided setup wizard in the operator desk (
w), a keyboard-driven flow for a book’s identity that projects the typed configuration boundary (#155) onto the desk rather than being a second YAML editor. It reads and writes only throughconfig_cli/config_store, shows an edit as an exact deterministic diff with the real validator’s verdict before writing, and applies only a clean preview; cancel, back, or a validation failure leaves the file byte-for-byte unchanged. A secret-looking value is refused before it can reach a book’s config, and a completed wizard hands back a runnable next step rather than a readiness claim. A bare install stays Textual-free; thetuiextra proves the flow with headless active-signal tests (first run, edit-and-apply, invalid value, cancel, secret refusal) (#166).Conventional CLI discovery (#175).
press --help/-hprints every command grouped with its summary;press <command> --helpexplains one command;press --versionreports the installed version. All exit 0 and never execute a handler, build, mutate, or need a book, a TTY, or the toolchain, sopress doctor --helpno longer runs the diagnostic andpress desk --helpno longer tries to launch the TUI. An unknown command exits 2 with the nearest valid suggestion and a pointer topress --help. Help is rendered from the one command catalog, so it cannot describe a command the CLI does not dispatch or omit one it does.press config get|set|unset|list|validate: a validated command surface over every book-configuration field, so ordinary configuration no longer depends on hand-editing YAML. A write is checked by the same typed model that validates a build (bookmodel,commerce,registrations, the house-rules regex compiler) against the proposed document before a byte is touched, so a rejected edit changes nothing; the edit is applied to a comment-preserving round-trip and written atomically. A value the build’s YAML 1.1 loader would misread (a bareno/yes/on/2026) is written quoted, so the writer and the build agree on the document. Types are never guessed from a shell string (a list or mapping must arrive as--json); a value that looks like a secret is refused and never echoed; and every field is either writable or carries an explicit classification (the v1 trim is immutable, the authorities and index lists are structured). A drift test walks the configuration reference and fails if a documented field has neither classification, and the quickstart’spress configcommands are executed against the installed package. Direct YAML editing remains documented for experts (#155).A task-first beginner quickstart (
docs/QUICKSTART.md): one copyable path from a blank machine to a built, verified book, naming which steps are the publisher’s decisions and which are mechanical defaults, with a table of the common first-run refusals and their fixes. It is the canonical first-run path README, install, configuration, and support now point to, and it is executable documentation: a doc-test extracts the guide’s own commands, proves the anchor commands are present and ordered, and runs the toolchain-free spine (press new->press check) against the installed package so the guide cannot drift out from under a beginner (#152).When print ordering is enabled, a policy link the publisher does not host is now generated as an honest page on the book site rather than refused. Each generated support, privacy, or returns page discloses the seller of record and what they handle, and appends the publisher’s own words from an optional
policiesblock;press verifyfails closed if a generated page is missing or omits the seller disclosure (#151).
Changed
The documentation site is redesigned: a cool-graphite, one-cinnabar-ink system (Literata, Hanken Grotesk, JetBrains Mono, self-hosted), a left-sidebar nav, theme-aware syntax highlighting, a copy button on code blocks, and a first-time-author landing. Internal links stay on the site, and a “made with press” section links real books.
The toolchain image is public, so press is independently consumable (#161). A book repository under any account or org now builds the advertised CI path with no owner-granted package permission and no configured secret: the pull is authenticated with the workflow’s own
GITHUB_TOKEN, which works for a public image on fork and Dependabot pull requests too. The build still pins the exact toolchain image and the release contract still proves the pin is immutable, so every build runs against the toolchain bytes the release was proven on. Installation, compatibility, README, and the quickstart drop the per-repo grant step; a posture test guards that they cannot silently ask for one again. The owner’s private-image workflow is retired, not merely hidden.One YAML library, one version. Press read config with PyYAML (YAML 1.1, where a bare
no/yes/on/offis a boolean) while thepress configwriter used ruamel (YAML 1.2, where they are strings); the two disagreed about the document itself. The package now reads and writes all YAML throughpress.yamlio(ruamel at YAML 1.2, pure parser), so a barenois the string “no” everywhere and the writer and reader can never drift apart. A boundary test forbids a rawimport yamloutside the one door, and PyYAML is dropped as a dependency. No book’s valid config changes meaning under 1.2 except a value a book explicitly wrote as a bareyes/no/on/offand relied on being a boolean; write it astrue/falseinstead.
[1.15.2] - 2026-07-19
Completes the accumulated-delivery-trust work deferred from v1.14: the release chain now proves every layer, and two new ratchets guard the tests that do the proving.
Added
- The release trust chain is complete, not a placeholder. A release used to be witnessed by a two-layer chain in which one “collection” receipt stood in for every CI proof, and the verifier checked ordering and linkage but not completeness — so the stand-in passed and the release proved nothing about the layers between. Verification now requires the chain to be complete: every trust layer present, contiguous, and each extending its immediate predecessor. The release contract builds the full per-layer chain only after asserting that every trust-layer check (the whole test suite, the container gauntlet, the operator surface) is green on the tagged commit, so each layer’s receipt is backed by a proof that actually ran. Closes the per-layer prerequisite chain (#97) and the layered-CI trust ordering (#94).
- A per-module branch-coverage floor gate. Repository-wide coverage can stay green while one module’s branches rot; the ratchet holds each module to the minimum coverage it shows when the rendering toolchain is absent. It hides that toolchain internally before measuring, so the gate is deterministic whether or not the machine has pandoc or LuaLaTeX, and because ambient coverage is always at or above the floor, no toolchain posture can push a module below baseline — the gate goes red only on a real regression (#96).
- A deterministic mutation-score ratchet over the pure-computation modules. It mutates the EAN-13 checksum and bar encoding and the artifact-state derivation one edit at a time and runs each module’s example-based tests against the mutant; a surviving mutant is a missing proof. Each mutant runs once with no retry, against a shadow source tree with bytecode caching forbidden so no mutant is ever measured against another’s compiled code (#95).
- The change-impact mapper: on a pull request, changed policy code that maps to no classified surface or no invariant fails the build, so a new verifier or parser cannot land ungated (#96).
Changed
- The operator desk is a usable tool: a run can be cancelled and reports the child’s exact verdict; the target picker prompts for a command’s arguments before launching and grays out a command a missing toolchain would only let fail (#147, #148, #149).
[1.15.1] - 2026-07-19
Added
- The release boundary verifies accumulated provenance, not just a green workflow name. A release receipt names the source commit, the built wheel digest, the pinned toolchain image, and the quality manifest digests; the release contract builds the wheel on the clean tagged checkout, assembles the receipt, and refuses a chain that is not clean-tree or whose package or toolchain does not match the objects actually built and pinned, so a deliberate commit, wheel, or image substitution turns the release gate red. Because the contract must be green before the floating major moves, the identity gate is enforced before the tag floats. Part of the accumulated-delivery-trust work (#97); the full per-layer receipt chain follows with the layered-CI change.
[1.15.0] - 2026-07-19
Added
- The operator desk:
press desk, an optional Textual interface over the command line. It is genuinely optional, behind atuiextra: a bare install builds books untouched and the entry refuses cleanly with the install hint when the extra or a terminal is absent. - Its foundations are pure Python, provable without the UI. One typed command catalog is the single source the CLI usage text and the desk surface both read, so they cannot drift (#100). doctor.examine returns the machine’s capabilities as typed findings and main() renders them byte-identically (#103). Artifact status is projected from content digests and recorded verification, never mtimes: a touched-but-unchanged file is not stale and a rebuild to identical bytes is not new (#101). A versioned structured event protocol lets a child emit stage and diagnostic events without a consumer scraping text, and a malformed event is a surfaced failure that never hides raw output (#102). A single-child process controller streams a child’s output and reports its exact exit code as the verdict, with cancellation and the single-child invariant proven against a fake process (#105). The DESK read model assembles the desk’s facts from those registries and reads only (#106).
- The desk itself: the DESK dashboard renders the read model with the digest-based evidence vocabulary and grays out actions a missing toolchain blocks (#112); a RUN view streams a press child and shows its exact verdict (#109); a picker generated from the catalog offers exactly the CLI’s targets (#111); the app shell carries a house theme (#104, #107). A headless Pilot harness drives the real app against a scaffolded book (#108), and a bare-and-tui wheel matrix plus an installed end-to-end proof gate the milestone (#110, #114). docs/DESK.md documents it (#113).
[1.14.0] - 2026-07-19
This release ships the completed, self-contained portion of the accumulated-delivery-trust milestone. The receipt library is the foundation; the remaining CI-workflow integration (layered CI, the release receipt gate, coverage impact selection, mutation-score ratchets) and the live second-party proofs are tracked for a follow-up point release.
Fixed
- The distributions are clean and reproducible: the broad data glob used to ship pycache and bytecode from running the packaged scripts, so the wheel’s contents depended on prior execution and the interpreter version. exclude-package-data and a pruning MANIFEST.in drop bytecode from both wheel and sdist; a build emits no warnings, passes twine –strict, and CI installs the wheel into a fresh environment to run its selftest. That fresh-install run surfaced two real defects, now fixed: the pytest collection plugin (pure test infrastructure) was shipping in the runtime package, and several selftest checks read repo files a wheel does not carry; the plugin moved to tests/ and the repo-only checks skip cleanly from an install (#73).
Added
- Chained trust receipts: a receipt records the source commit and
tree-clean state, the digests of the source, toolchain, and quality
manifests a layer consumed, the proofs it executed, and the digests of
the prerequisite receipts it extends. Receipts are deterministic and
independently verifiable
(
python3 -m press.receipts verify), and verify_chain refuses a missing or tampered prerequisite, layers out of accumulated-trust order, a changed input, and a dirty-tree receipt in a release chain, each with a negative test (#93). - A consolidated sabotage suite proves each trust gate reddens when its protection is removed (unclassified callable, dangling proof reference, orphan fixture, removed graph edge, dirty release receipt, wrong fake command), indexed so a gate cannot lose its sabotage case unnoticed (part of #95).
- The CI privilege posture the second-party proofs would exercise is proven mechanically: no pull_request_target and every workflow least-privilege, so a regression is caught without a second account (part of #87, whose live runs are documented for a second party).
[1.13.0] - 2026-07-19
Added
- Adversarial artifact proof: the verifiers are now proven to fail, not just to pass. A library of named single-purpose damage operators (archive member add, remove, escaping-path, uncompressed-store, byte-flip; reader-site chapter duplication, dead stylesheet url, dead fragment) each records source and result digests and asserts the invariant-specific diagnostic, with a coverage gate so no deliberate-damage invariant loses its negative proof (#88). The artifact graph is modeled as deterministic build-mutate-verify state transitions over the git-only source archive, proving a mutated or missing output cannot be blessed, rebuild restores validity, and clean removes every declared output, in both cwd and BOOK_ROOT modes (#89).
- Mechanical configuration coverage: quality/scenarios.yaml declares ten optional-configuration dimensions, press.scenarios generates a deterministic eleven-combination pairwise covering set plus five named high-risk interactions, and gates fail on an untested surface or an unrealizable high-risk scenario (#90).
- Real-tool integration runners for every artifact family (PDF/print, EPUB, HTML and site and Pages, Markdown and text and DOCX, archives and the sources companion, cover wrap) build source-only factory books through the actual toolchain and inspect them with their real verifiers, capability-gated so a missing tool skips cleanly, each recording tool versions and input and output digests (#91).
- Design-major visual regression: a fixture book’s built PDF is measured for toolchain-stable geometry (page count, embedded fonts, trim, per-page ink bounds) against a committed v1 baseline, so a margin shift, font swap, or displaced plate is a drift while an encoder patch is not; baselines update only with a recorded reason (#92).
- docs/COMPATIBILITY.md states the supported Python range (3.10 to 3.13, tested), the pinned Ubuntu 24.04 toolchain image as the contract, and the OS families, and press doctor warns outside the tested Python range (#35).
[1.12.0] - 2026-07-19
Added
- Trust foundations: a test-quality architecture the press proves itself against. A pytest harness runs the selftest’s invariant checks as individual cases from one ordered CHECKS list both runners consume (#78). An executable invariant ledger, quality/invariants.yaml, is the single source of what the press promises: its validator proves every enforcer and proof it names resolves to a real function or fixture, it generates docs/INVARIANTS.md, and the narrative matrix in ARCHITECTURE.md now points at it rather than duplicating it (#79). The public surface is classified in quality/surfaces.yaml with a mechanical AST inventory that fails on an unclassified new callable (#81). Every regression fixture has a provenance entry reconciled against its inline expect comment (#84). Twenty-two hypothesis properties cover the pure policy, parsing, graph, and normalization code (#85). A deterministic fuzz corpus proves the hostile parsers refuse locatably instead of crashing (#86). A typed BookFactory with named presets builds isolated source-only books whose facts are inspectable and whose scenarios cannot contaminate one another (#83).
- Typed boundary adapters: subprocess, environment, credential, and image-HTTP calls move behind protocols with production and recording fake pairs; build, doctor, operator, art_commission, and package_source drive them through injected singletons with behavior preserved, domain results and exceptions give the layer a vocabulary, retry is deterministic, and a boundary lint keeps raw calls out of every module but the adapters, its legacy allowlist able only to shrink (#82).
- A pytest collection plugin enforces the invariant, layer, and proof markers where present, rejects an assertionless marked test, requires an xfail to cite a declared limitation and a skip to name a toolchain capability, and writes a test-to-invariant index before execution (#80).
Fixed
- docx_visible_text returned the empty string on unparseable bytes instead of raising an XML traceback, so a corrupt DOCX now surfaces as its caller’s locatable witness failure (found by the new fuzz corpus).
[1.11.0] - 2026-07-19
Changed
- Workflow coherence (the v1.11 milestone). The packaged workflows stop assuming the first book: authorities research derives its subject and preferred source kinds from the book’s own metadata and manuscript, extraction is exhaustive by default with any sampling cap disclosed in the accounting (#76); editorial synthesizer rejections become durable reviewed findings in build/editorial-rejections.md, fed into later rounds so a refuted suggestion is not re-filed unchanged, with staleness resolved against the current text (#77); and the book-aesthetics schema documents the full page-look surface (web palettes, typography, book colors) with a selftest tying the documentation to the keys the aesthetic engine actually consumes (#59).
- The complexity inventory is empty: the route-table refactor of the CLI dispatch and decompositions of the remaining oversized functions bring every function under the C901 ceiling and remove every noqa; the docs drift checker now derives routed targets from the route table itself instead of regexing source (#66).
- Contributor contracts are single-source: CLAUDE.md is canonical (release procedure now states scripts/release.sh; the authorities ledger renders a standalone companion, not an appendix) with AGENTS.md as a checked mirror, and the roadmap is registry-authoritative with generated milestone projections (#75).
Added
- Public reference documents for #33: an invariant matrix and provenance-versus-verification and design-versioning sections in docs/ARCHITECTURE.md, a complete configuration reference in docs/CONFIGURATION.md, and builder/verifier/destination columns in the generated docs/REFERENCE.md, all carried on the documentation site.
- Package metadata is publishable: README as long description, authors and maintainers stated, and a build-plus-twine-check –strict gate in CI; pip-from-git at a tag remains the one supported channel and the docs say so (#74).
[1.10.0] - 2026-07-19
Changed
- Boundary integrity, first half (the v1.10 milestone’s verifier and
CI groups). Public artifacts: source publication is now an allowlist
(git’s tracked files, inside a repo) stated once in
publication_members()and consumed by both the packager and the archive verifier, so an appended member, an untracked private file, or a flipped byte each fail digest-exact verification (#12, #23); the pages crawler follows stylesheeturl()assets and fragment anchors (#10); the reader site proves per-chapter identity witnesses (a duplicated or missing chapter page fails by name) and an underivable manuscript witness is a refusal, not a free pass (#20). CI: pull-request toolchain smokes run with read permissions and no registry credentials, and main publishes the exact smoked image object with docker push, never a second build (#67, #27); the composite action passes its command input as an environment value matched against a target grammar, so shell metacharacters are data (#68); the release script validates strict SemVer, preflights remote state, resumes idempotently after any failed step, and does not float the major until the immutable tag’s contract is green (#69, #70). Every audit-3 damage exploit is now a selftest fixture. - Retail artifacts tell the truth (the milestone’s second half):
press publishbuilds and verifies the interior, wrap, and EPUB through the registry before checking anything off, exits non-zero when a required artifact fails, and separates interior and wrap failures under their own labels;--report-onlysays “NOT verified” on every line instead of pretending (#71). The cover wrap gains its own verifier: one page at the exact trim-plus-bleed-plus-spine size recomputed from the generator’s own functions, embedded fonts, rendered ink, cover art on the front panel, surviving title text, and a barcode on a white card whose quiet zones are judged against the expected EAN symbol span, never against observed ink (#72).press verify-printverifies the interior first and degrades gracefully for coverless books. The agent workflows harden too: whole-book editorial suggestions normalize file paths against the scout list so a basename or absolute path reaches the right synthesizer and unresolvable paths are set aside loudly (#57), and research or audit agents that fail route their claims to an explicit unresolved list with reconciled counts, never silently out of the ledger (#58). - Contact and imprint identity: the security contact is clint@lgtm.systems, the license holder is LGTM Systems, LLC, and books appear under the LGTM Publishing imprint.
Added
Every checked-in format has a linter, locally and in CI: shellcheck, yamllint, pymarkdown (frontmatter-aware; known-bad fixtures excluded on purpose), TOML/JSON validity, and merge-conflict/large-file guards join ruff, mypy, and the selftest in pre-commit; the CI quality job runs the identical battery via
pre-commit run --all-files.docs/TUI-PLAN.md records the press desk design (Textual, screens, integration laws, first milestone) from the three research passes.
Full CSS freedom for a book’s web surfaces:
assets/web/reader.cssreplaces the house reader stylesheet outright andassets/web/extra.cssappends cascade-winning declarations to both the reader and the pages landing page. The aesthetic palette applies to either; a book supplying neither renders byte-identically.The press publishes its own documentation site (https://clintecker.github.io/press/):
scripts/build_site.pyrenders every repo document through pandoc with the site’s own design (self-hosted Source Serif, Space Grotesk, and Plex Mono; a two-ink letterpress palette), and the docs-site workflow deploys it on every push to main. Three phase guards keep site and repo in step: no page is hand-written; every repo Markdown file must be published or consciously excluded with a reason (the check caught SUPPORT, SECURITY, ROADMAP, and AGENTS on its first two runs); and every page footer stamps the commit it was built from, while CI builds the site pre-merge so a breakage goes red before deploy. The link check covers stylesheet url() references as well as page hrefs.
Changed
- The press repository is public (2026-07-19), and the repository
boundary matrix is proven with real repositories (#37):
press-smokeis the standing boundary fixture, exercised as a private consumer (minimum permissions plus the toolchain grant), as a public consumer (anonymous action fetch, zero owner-specific setup), through a cross-boundary Pages deployment, and through a witness-gate refusal on a vacuous release tag. Fork-PR posture is proven mechanically (no pull_request_target, default read token, per-job least privilege); the live second-party proofs are #87.
Fixed
- Bad input now gets a named refusal, never a traceback or an
injection, closing the audit’s failure-honesty tail: a malformed or
empty
config/metadata.yamlis refused with the file and line instead of a parser traceback (#53); the installedpresscommand passes a failing tool’s exit code through cleanly instead of leaking a CalledProcessError traceback (#54); a malformed banned-patterns regex namesconfig/house-rules.yamland the offending pattern (#55); a metadata title with quotes or angle brackets is escaped into the single-file HTML edition’s cover fragment (#43); and index terms pass through the shared print-safe sanitizer so a backslash inconfig/index-terms.yamlcan never reach the TeX engine (#44). The selftest proves every refusal. - The release script’s version bump is anchored to the
[project]version line; v1.9.0’s cut rewrote[tool.mypy]’spython_versionto the release number, and mypy silently fell back to checking against the running interpreter instead of 3.10.
[1.9.0] - 2026-07-19
Added
- An engineering-quality layer with teeth: ruff lints with a
cyclomatic-complexity ceiling of 15 (six pre-existing functions carry
# noqa: C901as tracked inventory, #66), mypy runs clean over the whole package, and aqualityCI job plus a.pre-commit-config.yamlrun the same gates locally and on every PR.pip install -e '.[dev]'brings the tools. - Build timing metrics: every pipeline command that takes a second or
more prints its elapsed time, and dependency-graph builds
(
press pages,press print) print a per-stage summary, so a regression in the slow stages is a number, not a feeling.
Fixed
- The doc-drift selftest now sees tuple routes
(
target in ("pages", "verify-pages")) as well as equality routes, so a routed target can no longer hide from the usage/README check (#41, #49). gen_authorities.generate(cyclomatic 35, the worst in the package) decomposed into_structural_problems,_locate, and_render_companionwith behavior unchanged; PDF verification and art intake carry precise types where Pillow and pypdf return unions, and an unresolvable plate-link destination is now a named refusal instead of a TypeError.
[1.8.2] - 2026-07-19
Fixed
- The release contract’s gate check requests checks:read, the scope the check-runs API actually demands; v1.8.1’s run had pins proven and manifest resolving but 403’d on its final question.
[1.8.1] - 2026-07-19
Fixed
- The release contract awaits the tagged commit’s own integration verdict instead of demanding prescience; v1.8.0’s maiden contract run refused itself by racing its own gate.
[1.8.0] - 2026-07-19
Fixed
- The second audit’s first two waves: smart-quote-proof sentinels, model-driven builders and bylines, headless research granted the web, counsel mode proving the manuscript untouched, the integration gate running on the pinned toolchain image, least-privilege jobs, sha-pinned actions, serialized toolchain publishes, tag-gating as machinery, and the release script the docs had only promised.
[1.7.0] - 2026-07-19
Added
- press doctor, the dependency examiner.
- The documentation suite: INSTALL, ARCHITECTURE, CONTRIBUTING, SECURITY, SUPPORT, and this changelog.
Changed
- The 190-word paragraph rule is enforced law in book prose.