Skip to main content

ADR-0178: Image-size figures live in one declared record, and ADR-0171's pair describes a build that never shipped

Date: 2026-08-08 Status: Accepted Deciders: Adrian (Soft4U BV), Claude Opus 5, Codex (review, PR #1120) Corrects (measurement only, not the decision): ADR-0171

Context

ADR-0171 decided to ship docling on the CPU-only torch build. That decision is untouched and remains correct. Its Consequences section also records a measurement:

The backend image goes 3.44 GB -> 893 MB on linux/amd64 (74% smaller)

Issue #1116 was filed against that figure on the belief that the artifact did not support it. That belief was wrong, and re-measurement withdrew it. Both numbers are genuine docker image inspect .Size readings on real linux/amd64 images, and — checked from layer history rather than assumed — neither side carries the RapidOCR weight bake: neither image contains an extraction models warmed layer. The pair is like-for-like and correctly isolates the effect of removing the CUDA stack, which is the question ADR-0171 existed to answer.

What the figure does not describe is the artifact. The image master builds is 1.48 GB. The 893 MB build has 12 layers; the shipping build has 14 — the two extra are the RapidOCR warm-up that #998/#1009 added, and its weights-presence assertion. docker history attributes 572 MB to the warm-up alone; the ~26.2 MB remainder is recorded as UNATTRIBUTED, because the assertion layer runs only test/find/echo and writes nothing, and docker history numbers do not sum to .Size. Assigning it to a plausible-looking candidate would be a fabricated measurement in the record written to remove them.

Three facts make this a decision rather than a typo:

  1. ADR-0171's first appearance on master is commit 3bac4354 — which is the #998/#1009 commit that added the bake. On master, this ADR has never described the image the Dockerfile beside it builds.
  2. The 893 MB image was built at 18:07 on 2026-08-02; the 1.49 GB one at 18:04. Both numbers were in hand and the smaller was published, unqualified and in the present tense.
  3. The figure is restated in four places, one of them docs/oem/cpu-only-torch.md, written for a partner audience. A number a partner's engineer cannot reproduce from the artifact is the claim-vs-check class (ADR-0163/0157/0167) in a measurement rather than a control.

A second, worse claim was introduced while fixing the first: the correction described the OCR bake as making extraction work "with no network reachable". backend/Dockerfile records the opposite, measured: baking the RapidOCR weights removes the modelscope.cn fetch, but docling's layout model is still pulled from HuggingFace Hub on first use, HF_HOME/HF_HUB_CACHE redirection was tried and did not relocate it, and under --network none both PDF and OCR raise LocalEntryNotFoundError. That file states the permitted claim in terms: extraction works in the container, for the non-root user, with network; air-gapped operation is unfinished and tracked rather than implied. The correction had introduced precisely the over-claim that comment exists to prevent.

Decision

1. Every backend-image size figure this repository states lives in docs/oem/image-size-measurements.json, with per-measurement provenance: content-addressed image ID, bytes, the human-readable rendering, platform, torch build, whether the OCR weights are baked, and one interpreted field — representative_of_shipping_artifact — with a stated reason either way.

2. A figure marked representative_of_shipping_artifact: false may not be cited without one marked true. Enforced by backend/tests/test_image_size_claims_1116.py over the ADR, the OEM document, the CLAUDE.md register and .github/workflows/security.yml. Declaring a new non-shipping measurement extends the rule with no code change.

3. ADR-0171's body is not edited. s4u-adr §10 makes an Accepted ADR's Context, Decision, Consequences and Alternatives immutable; only the Status line changes. ADR-0171's Status gains a pointer to this record — the mechanism §10 provides, and the shape several ADRs here already use (Accepted (implemented …)). The detector treats that pointer as the required qualification, so an immutable document is never required to be edited to satisfy a gate.

4. No document may claim air-gapped or offline document extraction. The permitted claim is the one backend/Dockerfile records: extraction works in the container, for the non-root runtime user, with network.

Consequences

Positive

  • A partner reading the OEM document gets the number they will actually pull, and the number that measures the torch change, and the reason they differ.
  • The 74% figure is preserved rather than deleted — it answers a real question, and suppressing it to avoid the confusion would lose the evidence for the decision ADR-0171 made.
  • The record is machine-checked in both directions: a figure that traces to no measurement fails, and a measurement whose human does not round to its bytes fails.
  • The shipping flag is bound to backend/Dockerfile rather than to the record's own assertion, so a build without the OCR bake cannot be relabelled "shipping" to satisfy the rule using the figure the rule exists to qualify.

Negative

  • The record is maintained by hand. Nothing re-measures the image in CI, so a Dockerfile change that moves the size leaves the record stale until someone re-measures. The detector catches a figure that traces to nothing; it cannot catch a figure that traces to a stale measurement.
  • docker image inspect .Size is host-local. The IDs recorded here are reproducible only where those images exist; a reviewer on another machine must rebuild to verify, which the record documents but cannot perform.
  • Two ADRs now describe one image size. A reader of ADR-0171 alone gets the uncorrected figure unless they follow the Status pointer.

Neutral

  • No code, schema, migration or flag changes. This governs documents and one test.

Alternatives Considered

Alternative 1: Edit ADR-0171's Consequences section

  • Change 3.44 GB -> 893 MB to the shipping figure in place.
  • Why rejected: s4u-adr §10 forbids it, and the reason is the point — rewriting a recorded measurement destroys the evidence that the measurement was once believed. It is also the exact defect this repository keeps finding. An earlier revision of this work did append a correction to the accepted ADR and was refused in review for that reason.

Alternative 2: Leave ADR-0171 alone entirely and record the correction only in the JSON

  • No Status change, no new ADR.
  • Why rejected: the detector scans the ADR and would flag it, so ADR-0171 would have to be declared an exception. A permanent declared exception on the document that states the misleading figure puts the correction furthest from the reader most likely to be misled.

Alternative 3: Delete the 893 MB figure as misleading

  • Publish only 3.44 GB -> 1.48 GB (57%).
  • Why rejected: 893 MB is the honest measure of what removing the CUDA stack bought, and 1.48 GB conflates that with a capability added for a different reason. Deleting it would make the ADR's own decision less well evidenced.

Decision context:

  • Latency: none — documents and one file-parsing test, well under a second. A test COUNT is deliberately not recorded here: the first draft said 10, the CLAUDE.md register said 7, and the file held 11 — three numbers for one fact, in the commit that introduces a record whose whole purpose is that a stated figure matches the artifact (Codex, PR #1120). The suite is the authority for its own size.
  • Dependency surface: none. Standard library only.
  • Debuggability: a failure names the document, the figure, and whether it is undeclared or unqualified.
  • Reversibility: delete one JSON file and one test; minutes.
  • Blast radius: four documents and one test. No application code.
  • Alternative considered: Alternative 1 above (edit in place) — rejected on s4u-adr §10 and on the evidence-destruction argument.