In your pipeline
Fail the build, not the customer
Check generated invoices in the pull request, before a customer or access point rejects them.
The whole integration
- uses: attestwire/validate-einvoice-action@v1
with:
files: "invoices/**/*.xml"
sarif: einvoice.sarifFatal findings fail the job. The action annotates the pull request, writes a linked findings table, and can export SARIF.
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: einvoice.sarifIt runs offline, in the runner
No API key, account or network call. The action bundles a pinned engine version, so the same version runs the same rules.
Upgrade the rules by changing the action version in a pull request. You can review any changed findings before enforcing them.
Add api-key to use the hosted ruleset instead. Each response
names the engine and ruleset used for that document.
Two pure functions, if you are not on GitHub
The action is a wrapper. Underneath it, the library exports the same two reports directly:
import { validateInput, toSarif, toJunitXml } from "@attestwire/en16931";
const { errors } = validateInput(invoice);
const provenance = { engineVersion: "0.7.3", profile: "xrechnung-ubl" };
writeFileSync("einvoice.sarif", JSON.stringify(toSarif(errors, provenance)));
writeFileSync("einvoice.xml", toJunitXml(errors, provenance));Jenkins, GitLab, CircleCI, Buildkite and Azure Pipelines all ingest the JUnit XML that Ant defined and Surefire extended; GitHub reads the SARIF.
Both are pure functions. No clock, no filesystem, no
environment: every value that varies between runs — the engine
version, the ruleset versions, the timestamp, the document URI — is passed
in. The same inputs give byte-identical output, so two reports diff to
nothing rather than to a changed timestamp on every line. A report that
cannot name the validator version that produced it cannot be checked
later, which is why engineVersion is the one required
field.
Neither function decides whether your build fails. SARIF has no "fail the
build" flag; the gate is your repository's, configured where gates belong.
toJunitXml maps a fatal finding to <failure>,
and the mapping is documented rather than left to be inferred.
Exit codes
| Exit | When |
|---|---|
0 |
Every document validated, and nothing crossed the fail-on threshold. |
1 |
A document produced a finding at or above fail-on. |
1 |
A file could not be read, parsed, or is not an invoice. |
1 |
The files pattern matched nothing. |
1 |
An input is invalid, or (api mode) the key was rejected or the API unreachable. |
A run over zero files fails. An unreadable file also fails instead of being skipped. Invalid settings are reported before files are opened.
Informational findings never fail a build under any setting. A warning
fails only under fail-on: warning, because a warning here is
something the official validator raises and then accepts the document
anyway, and turning a build red for one by default would misrepresent the
authority.
Use it as a preflight
A release pipeline that ships documents into a regulated flow may still want the official validator in it.
What this action runs is a semantic pre-flight over the parsed input
model. It is not a schematron over the document, and rules that constrain
the XML itself — BR-01, BR-DE-13,
BR-DE-21 on BT-24 — do not run at all. A document we accept
can still be rejected by the reference validator, and
the limits page lists every gap rather than a
representative sample.
A practical setup is to run this action on pull requests and keep the official validator in the release job. One gives developers a fast, actionable result; the other remains the external authority.
What we can show you is our own homework against those validators, failures included: the KoSIT record, the Peppol record, the DGFiP record.