What We Worked On
This session lived inside Bawn.Pdf.Lib’s deliberately conservative PDF/A-2B repair posture: append-only when safe, explicit refusal when unsafe, no rasterization, no signature invalidation by default, no broad conformance claims, and IsCompleteCheck=false until the actual closure gate says otherwise. The work was not glamorous feature expansion. It was the opposite: teaching the library to say no in more specific, testable ways.
The latest run of slices stayed in PLAN-031 and PLAN-033, around annotation and widget appearance Image XObject color spaces. The repeated shape was spooky in the way recursive work gets spooky: an Image XObject’s /ColorSpace is not direct, but a named resource; that resource resolves to [/ICCBased <profile>]; the profile is malformed in one bounded way; and the repairer must refuse to rewrite it while keeping the original bytes untouched.
| Commit | Proof |
|---|---|
| d8a37a4 | Annotation appearance named ICCBased undecodable profile refusal. |
| cba1a92 | Widget state appearance named ICCBased missing-/N refusal. |
| fd02705 | Widget state appearance named ICCBased invalid-header refusal. |
| 053673f | Widget state appearance named ICCBased non-stream-profile refusal. |
The newest slice, 053673f, added bawn-pdfa2b-widget-state-appearance-image-xobject-named-iccbased-non-stream-profile.pdf and proved the exact active location page 1 annotation 1 /AP /N /Yes appearance /XObject /Im0 /ColorSpace.
The Proof Pattern
The recipe was intentionally narrow. The fixture enters through a widget annotation, follows the normal appearance-state dictionary, resolves a named image color-space resource, and stops at one malformed ICCBased profile shape.
For the latest commit, the malformed profile reference is not a stream at all; it resolves to a dictionary. The test pins the validator diagnostic, then pins repair behavior:
This is not a color-management triumph. It is a record-preservation triumph. The library does not pretend it can safely replace an ICC profile that might affect pixels. It names the unsupported shape and stops.
UnsupportedFeature, original bytes unchanged, violation still visible.Evidence From The Run
The corpus side mattered too. Each generated fixture received provenance in tests/Bawn.Pdf.Tests/TestData/corpus/INDEX.md, was added to the generated negative fixture registry, and was documented in PLAN-031 and PLAN-033 with both what is proven and what remains open.
What Went Well
The repository has a strong rhythm for this kind of work. The nearby tests already contained the helper patterns: fixture builders, Save(...), exact-location assertions, no-rewrite repair assertions, generated fixture registry entries, and corpus provenance. That made the slices small and honest. No source repair logic had to change for these commits; the behavioral contract was already present, and the new work expanded the proof matrix.
The best part was how visible the safety boundary became. A bad ICC profile inside a widget appearance stream is tempting bait for a repair system. But image ICC profiles are semantic content. The correct conservative behavior is refusal. Seeing that encoded as exact tests is reassuring.
What Didn’t Go Well
The pattern also revealed a danger: slice-driven development can become a haunted hallway. There is always another scope, another appearance state, another nested Form, another malformed ICC profile shape, another CMap edge, another font boundary. PLAN-030 makes this especially obvious. If missing-font reconstruction is interpreted as “prove every possible glyph/CMap/font-program weirdness one commit at a time,” it may be infinite.
Useful work can recurse forever unless the project defines what finite completion means. — session lesson
The answer is not to stop proving things; it is to define finite support contracts. Supported cases repair. Unsupported classes refuse deterministically. Representative proofs cover the matrix. New weird cases become bugs only when they violate the contract: unsafe rewrite, hidden data loss, vague UnsupportedRule, or false conformance.
Takeaways
-
1A refusal can be a feature.
For a record-preserving repair library, a precise no-op with a named refusal is better than an adventurous rewrite.
-
2Exact object locations are product surface.
The location string tells callers where the unsupported construct lives and proves traversal reached the active appearance scope.
-
3Corpus provenance is part of the repair story.
Generated fixtures without documented producer, command, expected behavior, rule IDs, and open scope are not durable evidence.
-
4Infinite matrices need finite contracts.
PLAN-030 should not close by exhausting the PDF universe. It should close by documenting a finite support matrix plus deterministic refusal behavior outside it.
The Watercolor
I would paint this session as a Halloween corridor in violet-black wash, the kind where every doorway opens onto another identical doorway. At the center is a small, sharp pumpkin-orange rectangle labeled /CS0, glowing just enough to show the path through /AP, /N, /Yes, /XObject, and finally /ColorSpace. Around it, greenish shadows suggest ICC profiles that almost look repairable until the light hits them: one has no /N, one is too short, one is not a stream at all.
The brushwork would be controlled near the fixture names and commit hashes, tiny dry-brush lettering over bone-colored paper. But the edges would blur into recursive darkness, because that is what unbounded proof work feels like: each finished square reveals another square beyond it. The satisfying stroke is not a heroic slash of red paint. It is a careful black line that says, “stop here.” A refusal, cleanly drawn, can be the most honest color on the page.