What is missing
DigitalLink::parse reads a Digital Link URI. It cannot read the element string a scanner actually emits — the FNC1-separated AI data behind a ]d2 / ]C1 symbology identifier. Anything integrating a physical scanner has to convert that itself before it can talk to us.
Two things found while scoping it
1. The AI table is hand-written and covers 5 of 224 AIs
digital_link/ai.rs defines AI_TABLE with five entries: 01, 22, 10, 21, 235. GS1's published Syntax Dictionary defines 224. Unknown AIs are rejected with UnknownApplicationIdentifier.
2. We accept one of GS1's sixteen Digital Link primary keys
GS1 marks these AIs as valid DL primary keys (dlpkey in the dictionary):
00 01 253 255 401 402 414 415 417 8003 8004 8006 8010 8013 8017 8018
DigitalLink::parse looks for 01 and returns MissingGtin otherwise. So a conformant link keyed on an SSCC (00), a GRAI (8003), a GIAI (8004) or a GLN (414) is refused.
This is the failure direction our GS1 oracle warns about in its own header — "we reject, GS1 accepts — the quiet one, which loses interoperability with no error ever logged." The oracle cannot catch it. Its corpus is generated by our own builder, so it only ever contains links we can already build. It proves everything we emit is valid GS1; it says nothing about valid GS1 we refuse.
Approach: generate the table, do not write one
The AI length rules must not be hand-written. Getting a fixed-vs-variable length wrong silently truncates or over-reads scanned data, and a table typed from memory is exactly the kind of invented detail that has cost this project before.
GS1 publishes the authority: gs1-syntax-dictionary, Apache-2.0 (compatible with this crate), a single ~30 KB machine-readable text file. It encodes precisely what is needed:
01 *? N14,csum,gcppos2 ex=255,37 dlpkey=22,10,21|235 # GTIN
10 ? X..20 req=01,02,03,8006,8026 # BATCH/LOT
21 X..20 req=01,03,8006 ex=235 # SERIAL
235 X..28 req=01 # TPX
N14 fixed-length numeric, X..20 variable up to 20, plus check-digit (csum), primary-key (dlpkey) and mutual-exclusion (ex=) flags.
So: vendor the dictionary, generate the AI table from it, and derive both the element-string parser and the existing URI parser from the same generated source. That replaces the hand-written table rather than adding a second one beside it.
Shape
ElementString::parse(&str) — strips the symbology identifier, splits AIs on the dictionary's length rules, terminates variable-length AIs at FNC1 (\x1D) or end of input.
- Every AI is retained, including ones we do not model as typed fields. Silently dropping data read off a physical product is the dangerous failure.
ElementString::to_digital_link(resolver_base) for the conversion callers actually want.
Closing the oracle's blind spot
The corpus must stop being built solely from our own output. Add element strings and primary keys we do not currently support, assert our verdict, and let the engine judge — so "we reject, GS1 accepts" becomes a red build instead of silence.
Done when
A scanner element string decodes to its AIs and to a Digital Link; the AI table is generated from GS1's dictionary rather than hand-maintained; every dlpkey AI either parses or is refused with a reason that is not MissingGtin; and the oracle corpus contains cases our own builder cannot produce.
What is missing
DigitalLink::parsereads a Digital Link URI. It cannot read the element string a scanner actually emits — the FNC1-separated AI data behind a]d2/]C1symbology identifier. Anything integrating a physical scanner has to convert that itself before it can talk to us.Two things found while scoping it
1. The AI table is hand-written and covers 5 of 224 AIs
digital_link/ai.rsdefinesAI_TABLEwith five entries:01,22,10,21,235. GS1's published Syntax Dictionary defines 224. Unknown AIs are rejected withUnknownApplicationIdentifier.2. We accept one of GS1's sixteen Digital Link primary keys
GS1 marks these AIs as valid DL primary keys (
dlpkeyin the dictionary):DigitalLink::parselooks for01and returnsMissingGtinotherwise. So a conformant link keyed on an SSCC (00), a GRAI (8003), a GIAI (8004) or a GLN (414) is refused.This is the failure direction our GS1 oracle warns about in its own header — "we reject, GS1 accepts — the quiet one, which loses interoperability with no error ever logged." The oracle cannot catch it. Its corpus is generated by our own builder, so it only ever contains links we can already build. It proves everything we emit is valid GS1; it says nothing about valid GS1 we refuse.
Approach: generate the table, do not write one
The AI length rules must not be hand-written. Getting a fixed-vs-variable length wrong silently truncates or over-reads scanned data, and a table typed from memory is exactly the kind of invented detail that has cost this project before.
GS1 publishes the authority:
gs1-syntax-dictionary, Apache-2.0 (compatible with this crate), a single ~30 KB machine-readable text file. It encodes precisely what is needed:N14fixed-length numeric,X..20variable up to 20, plus check-digit (csum), primary-key (dlpkey) and mutual-exclusion (ex=) flags.So: vendor the dictionary, generate the AI table from it, and derive both the element-string parser and the existing URI parser from the same generated source. That replaces the hand-written table rather than adding a second one beside it.
Shape
ElementString::parse(&str)— strips the symbology identifier, splits AIs on the dictionary's length rules, terminates variable-length AIs at FNC1 (\x1D) or end of input.ElementString::to_digital_link(resolver_base)for the conversion callers actually want.Closing the oracle's blind spot
The corpus must stop being built solely from our own output. Add element strings and primary keys we do not currently support, assert our verdict, and let the engine judge — so "we reject, GS1 accepts" becomes a red build instead of silence.
Done when
A scanner element string decodes to its AIs and to a Digital Link; the AI table is generated from GS1's dictionary rather than hand-maintained; every
dlpkeyAI either parses or is refused with a reason that is notMissingGtin; and the oracle corpus contains cases our own builder cannot produce.