Build1 publisher3 min readPublished
A parser built to Vietnam's mandated tag names rejected five of six AI-sourced samples
The files were shaped like invoices and copied from an unrelated open-source repo. Because Vietnam's tax authority fixes the tag vocabulary by decree, strictness cost nothing here and the rejections were the result.
The Engineer · Build desk

What happened
- An AI assistant was asked to gather six sample Vietnamese e-invoice XML files, one from each of several well-known providers plus a generic tax-authority standard file, to check a new parser against real-world variation.
- The parser rejected five of the six outright and accepted one, which the author initially read as five separate failures against five separate providers.
- The README the assistant had written for the samples disclosed that the company names, tax codes and amounts were simulated test data rather than issued invoices, sourced from an open-source repo.
- The five rejected files used readable English tags such as SellerInfo, BuyerTaxCode and InvoiceNumber, which the author says no real Vietnamese e-invoice has ever carried.
- Real invoices later dug out of the client's own email and provider portals surfaced three bugs, none of which had appeared on any of the generated samples.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- capability Where a tax authority fixes the schema by decree, a strict parser doubles as a provenance test on its own fixtures, because a rejection means the file was never emitted by any real system rather than that a vendor dialect is unsupported.
- constraint Synthetic fixtures can only certify the mapping their author already understood, so a field nobody involved knew about stays untested on both sides of the harness and the suite goes green anyway.
- exposure The party exposed is the buyer, not the developer: a debug convenience shipped a real name, home address and email past a redaction rule that only the clean output honoured.
- decision Widening the parser to accept the invented tags would have bought a tidier demo and left every genuine invoice arriving afterwards competing with tolerance for a format that does not exist.
Strictness usually costs compatibility. In this format it costs nothing, and the reason is in the decree rather than in the code. The tax authority's own systems have to read every invoice whoever issued it, so the mandated vocabulary is the same set of Vietnamese abbreviations for every provider: TTChung, NBan, NMua, MST, DChi [5]. A file assembled out of `<SellerInfo>` and `<BuyerTaxCode>` is not an invoice from a provider the parser has not met yet, because no provider is permitted to invent tags [6]. Judged against that spec, the parser made six correct calls on six files: one accept, five rejects [1].
The retrieval optimised for shape. Files that parse as XML, carry invoice-shaped field names and stay internally consistent look like samples, and these came out of an open-source project's fixtures; the author's read is that they were written by a developer who had never seen an actual Vietnamese invoice, for an unrelated purpose [13]. The disclaimer was in the README before the first test run and got read after the rejections [4]. READMEs are where disclaimers go to be safe from readers.
A synthetic corpus can only exercise the mapping its author already understood. The mandated spec carries two separate tags for who bought the thing, one for a company name and one for an individual's, and the parser handled only the company case; none of the five inventions used the individual tag either, because whoever wrote them did not know it existed [8]. Both sides of the test were built from the same partial model of the format, which is why the test passed.
The privacy bug is the portable part. The redaction predicate came from the input: no buyer tax code implies a private individual, so drop the name and address [9]. It was enforced at the point where the clean output was assembled. The raw fallback existed because the field map was known to be incomplete, so it dumped every tag/value pair it saw, including the ones redaction was about [9]. A rule that filters one serialiser is a property of that serialiser rather than a policy, and the first real file through the parser, a utility bill, showed which one it was by emitting a buyer's name, home address and email [10].
The transfer condition is the mandate. Strict tag matching works as a provenance check on your own fixtures only where a single legal schema exists and the vocabulary is closed. In a format with genuine vendor dialects, a bank CSV export or an EDI variant, a rejection is ambiguous between a fabricated fixture and a real dialect you have not handled, and you are back to asking the boring question about where the file came from. The author asked it, declined to widen the parser to fit the invented tags, kept the one conforming sample and waited for real data [7]. The utility bill that found the leak cost nothing but the asking [11].
What to watch
- Whether the writeup documents the third bug: the post counts three and the available text details two of them.
- Whether coding assistants begin attaching provenance to fixture files they retrieve, which is the check that would have caught this before the first test run.
- How the parser handles pre-mandate files, since the 2021 logistics invoice predates the 2022 tag requirement and old invoices are where genuine format variation lives.