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:
- ADR-0171's first appearance on
masteris commit3bac4354— which is the #998/#1009 commit that added the bake. On master, this ADR has never described the image the Dockerfile beside it builds. - 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.
- 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
humandoes not round to its bytes fails. - The shipping flag is bound to
backend/Dockerfilerather 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 .Sizeis 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 MBto 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.