Don't derive the four X12 delimiters from the structure. Earn them first — from the weakest facts that pin them down — and then the structure is ordinary parsing plus repair.
The engineering notes were written as related‑but‑separate lessons — locating the ISA line, reading its delimiters, rebuilding it. The through‑line is a single architectural idea, and it was implicit. This note makes it explicit. Read it once; each note then picks up one act of the same argument.
An ANSI X12 interchange is a stack of envelopes: ISA wraps
one or more GS functional groups, each wrapping ST
transaction sets, closed in reverse by SE, GE,
IEA. The first segment, ISA, is a fixed
105‑byte record whose byte positions declare the delimiters
for everything after it — element separator at byte 3, component
separator at byte 104, segment terminator at byte 105, and
(version 00403+) the repetition separator in
ISA11.
The standard is strict about this, so the obvious reader is offset
arithmetic: slice the delimiters out at 3, 104, 105; check GS
sits at 106; parse the rest. It works on conformant files and fails on a
large share of real ones, because the senders x12-tidy exists for
strip empty elements, prepend bytes, append newlines, and
re‑encode. Three bytes of BOM and one omitted element are
enough to put byte 106 in the middle of a field. The byte positions are
not an invariant.
GS. Prepend three bytes of BOM and drop one
blank element and it falls two fields short. An offset parser has
already failed here — before it has read a single value.Every conventional X12 reader derives the delimiters from the structure — it trusts the offsets, reads the bytes there, and moves on. When the structure is wrong, it has already failed.
x12-tidy goes the other way. The only things it must be certain of are the four delimiters. So it earns those first, from the weakest structural facts that still pin them down — never from an offset or a width — and only then looks at anything else. Once the delimiters are known and nothing fatal was found, they are ground truth: every later step (split the elements, check the widths, rebuild the line, walk the body) is ordinary parsing plus repair.
This reframes what each part of the parse is for:
ISA,
ends immediately before GS + the element
separator, holds exactly 16 element separators) is exactly the
set of facts sufficient to guarantee the delimiters can be read and the
line split into 16 elements without ambiguity. No more, no less. A run
that fails the bar is fatal and terminal — not because locating
failed, but because the delimiters could not be trusted out of it.
GS header), and
recovers the other three from a single split. A finding
here is fatal only if a delimiter the whole interchange needs is
unusable.
Each phase validates exactly one more thing and hands the next a stronger guarantee. Nothing checks a property the phase below it has not already established.
| phase | establishes | hands forward |
|---|---|---|
| locate | the run has the shape of an ISA line (identifier, GS boundary, 16 separators) |
a run the delimiters can be parsed from |
| delimiters | the four delimiter bytes, and which are usable | ground‑truth delimiters |
| structure | 16 elements at fixed width, one canonical 105‑byte line, the sender's delimiters kept | a conformant ISA line |
| values (later) | ISA05 is a real qualifier, ISA09 a real date, ISA13 matches IEA02 |
a validated interchange |
A note that says “that is the next step's problem” is pointing down this ladder.
Every terminal decision in the system is the same move. The tool could guess, but a wrong guess silently corrupts an identifier — a sender or receiver ID, a control number — so instead it refuses and names the fault:
ISA06 / ISA08
data, or a false GS match). Terminal.ISA11 holding something other than U on a
pre‑00403 version → a wrong value in an
informational field, not a repetition separator the sender
“meant.” Reported, not used.Permissive parsing tolerates a lot. It never invents.
Each step ships an adversarial sweep — tens to hundreds of thousands of mutated, truncated, mis‑encoded inputs — checked against a single invariant: no crash, and either a clean refusal (a fatal diagnostic, no output) or a result that satisfies the phase's contract. There is no third outcome — no silent wrong answer, no partial parse. The notes each end with the sweep that backs them.