en16931

Contributing

Set up the en16931 workspace: fetch the CEN, KoSIT and Peppol artefacts, run the full CI gate locally, and regenerate the tables derived from the standard.

Three crates, one workspace, one version. just is the task runner; just alone lists every recipe.

git clone https://github.com/hupe1980/en16931
cd en16931

just references      # fetch the CEN / KoSIT / Peppol artefacts into the reference corpus — once
just ci              # everything CI runs, locally

The artefacts

The reference corpus is not committed. The CEN artefacts are EUPL-1.2, a reciprocal licence, and keeping them out of the tree is what keeps every crate here MIT OR Apache-2.0.

The suites that need it skip without it, so just references is the first thing to run. CI sets EN16931_REQUIRE_SPEC=1, which turns a skip into a failure; just test-artefacts does the same locally.

Every source is pinned in xtask/src/fetch.rs. cargo xtask watch reports, as JSON, every pin its source has moved past, tagged or not; the weekly Upstream watch workflow keeps one issue open per moved source.

Test-time tools

Two suites drive an external validator. Neither is a dependency of any crate; each skips with a printed line when its tool is missing, and fails under EN16931_REQUIRE_SPEC=1, as in CI.

ToolSuiteWhat it checks
xmllint (libxml2)schema_gateevery CII and UBL document the suites write against its schema
Docker, with a running daemonzugferd_embedevery PDF zugferd::embed writes, with veraPDF (PDF/A-3b) — the pinned verapdf/rest image, started once per run through testcontainers and pulled on first use (linux/amd64; emulated on Apple silicon)

The recipes worth knowing

just testunit and doc tests, default features — the fast loop
just test-allevery crate, every feature
just test-artefactsas above, but a missing reference corpus fails
just lintClippy over every target and feature, warnings as errors
just docrustdoc with -D warnings, which is what catches a broken intra-doc link
just wasmen16931 still builds for wasm32
just depsevery documented dependency-graph size, measured
just codegen-checkfail if a generated table drifted from the artefacts
just benchthe criterion benchmarks
just sitebuild this site with Zola
just featuresClippy over every feature combination a consumer can select
just trackedfail if a source file is gitignored, or missing from git
just auditsecurity advisories over Cargo.lock; nothing is ignored
just ciall of the above that CI runs
just fuzz <target> [seconds]one cargo-fuzz target (read, extract, roundtrip, embed); needs nightly and cargo-fuzz
just mutants-diffmutate the lines your change touches; fails on a survivor the baseline does not record
just mutantsthe full mutation sweep of the rules — optional and slow

A few notes:

  • just deps measures the dependency-graph sizes the documentation quotes. Raising one is a decision: the small graph is why en16931 builds for wasm32.
  • just features lints every feature combination, because --all-features cannot see code that is dead or does not compile without a feature.
  • just tracked runs locally only: a checkout has only the tracked files, so a needed file that .gitignore hides is invisible to CI. Anchor ignore patterns (/NOTES.md, not NOTES.md).
  • just wasm builds -p en16931 only; the formats crate’s PDF parser does not build for that target.
  • Mutation testing is local and optional: just mutants-diff mutates the lines your change touches; just mutants is the full, slow sweep of the rules. It is not run in CI.
  • Fuzzing runs in CI for 60 seconds per target; just fuzz <target> 3600 is a long local run.

The documentation is tested

Measured figures — rule counts, the coverage split, profile check counts, code-list totals, conformance counts — are read back out of every README, every source file and every page of this site, and compared against the code that produces them (crates/en16931/tests/documented_numbers.rs). Reword a sentence holding one and the test says its pattern stopped matching. Documentation describes the current state only; history belongs in the changelog.

No advisory is set aside

just audit runs cargo audit --deny warnings over Cargo.lock, and nothing is ignored: there is no .cargo/audit.toml. A security advisory or a yanked crate anywhere in the lockfile fails it, and CI runs the same command. The fix is a dependency update, not an ignore entry.

Generated code

The workspace has 7 generated files, derived from the artefacts and none written by hand: the code lists for en16931, and the element-order, prohibition and single-occurrence tables for each syntax in en16931-formats.

cargo xtask codegen     # regenerate every generated file
cargo xtask check       # fail if a committed one differs

Each generator exits non-zero rather than emitting a table it could not derive cleanly. If you change one, change the generator — a hand-edit will be reverted by the next codegen run and caught by CI in between.

Adding a rule

Every registered rule must have a fixture that makes it fire, or be declared in tests/corpus.rs as Why::Retired (the type system makes the failure unrepresentable) or Why::Undecidable (CEN’s own Schematron binds it to true() — exactly BR-CO-05 … BR-CO-08). The coverage gate fails if a rule is uncovered and undeclared, if a declared rule has since become covered, and if anything else is declared undecidable.

A rule nobody has seen fire may be inverted, unreachable, or checking the wrong field, and a suite of valid documents would be green either way.

If the rule is this crate’s own rather than an authority’s, namespace it EN-*. A BR- id that CEN did not publish is indistinguishable from one that it did, and someone will eventually go looking for it in CEN’s index.

Releasing

All three crates share one version, so one tag releases all of them, and CHANGELOG.md carries one entry for the three:

# 1. move the Unreleased section of CHANGELOG.md under the new version
# 2. bump `[workspace.package] version` and the two dependency requirements
git tag "v$(cargo pkgid -p en16931 | sed 's/.*#//')" && git push --tags

They publish in dependency order: en16931, then en16931-formats, then en16931-cli. The changelog is the one document the documented-number suite does not scan, because it records superseded figures on purpose.

This site

Zola, in site/.

just site-serve      # localhost with live reload
just site            # build into site/public

It deploys to GitHub Pages from main on every push that touches site/.