Skip to content

EN 16931 Semantic JSON 0.9.2

Choose a tag to compare

@github-actions github-actions released this 26 Sep 19:36
· 129 commits to main since this release

Added

  • The business rule pack of EN 16931-1:2026, rules/en16931-2026/0.1: 252 rules, of which 199
    are those of clause 6.4 (194 implemented, 5 not decidable, each with its reason), the fraction
    digit rules of Table 28 and the code list rules. No official validation artefact is published
    for the edition, so every rule names in oracle what stands behind it — downgrade for the
    rules the edition leaves unchanged, measured against the Schematron of release 1.3.16 over the
    document written back with esj upgrade, cases for the rules it changed or adds — and a
    document of that edition validates INVALID or INDETERMINATE, never VALID, with the new
    cause no-artefacts-for-edition (conformance/rules-2026/coverage.md,
    ledger.md). The pack is separable with the edition.
  • derive() for the typed view of EN 16931-1:2026: …typed.v2026.Totals.of(minorUnits) writes
    the line net amounts, one VAT breakdown per category, rate, exemption reason and goods/services
    code, and the totals, with the third party charges (BG-34) added into the amount due.
    Each amount is rounded once, half up, to the minor unit of its currency; MinorUnits of
    esj-core holds those numbers and En16931V2026.minorUnits() reads them from the pack's
    currency snapshot. A currency the snapshot gives no minor unit is refused, never guessed; a
    rule of the pack that asks for one reports that it was not decided. The
    pack writes out BR-CO-10, -11, -12 and -15, which round to the same minor unit.
  • The rule language has three more operators — minorUnit, unit and atRoot — and the scale
    of round and of decimals may be an expression; decimals applies to every numeric term.
    A rule may name in undecided a case a document may state and the rule has no answer for;
    there it reports that it was not decided, at info, instead of holding silently — the 2026
    pack does so for a price base quantity of zero in BR-CO-32 and BR-67.
    A pack manifest names its edition, which the engine checks against the registry, may take
    rules of another pack over by identifier (shares) and read a code list snapshot of another
    pack ({"day", "from"}). The TypeScript and C# bindings read all of it and run the 2026 pack;
    the fixture manifest carries its cases in the part of that edition.
  • esj validate runs the pack of the edition a document names. The row, the JSON report
    (rules.pack.edition, rules.pack.corroborated) and the report file name the pack and, for
    the 2026 one, say it is not corroborated by an official artefact. esj --list-packs lists
    the rule packs with their edition and the count of rules per oracle. esj upgrade --to 2026
    evaluates a decimal bound over the minor unit of the currency a value is written in — BT-5,
    BT-6 for BT-111, BT-184 in a VAT breakdown — against the currency snapshot of that pack
    (UpgradeOptions.minorUnits) and reports a value beyond it, unrounded.
  • The container image is published with every release at ghcr.io/bsnsoft/esj, for linux/amd64
    and linux/arm64, tagged with the version and latest
    (docs/install.md).
  • esj convert --to cii|ubl --fail-on-loss: where the target syntax has no place for part of the
    document, nothing is written — neither to --out nor to the standard output — and the run
    leaves with exit code 8. The exit code table did not grow: 8 was reserved for exactly this
    case and is now used, and every printing of the table says so. A value written by convention
    and a term left behind by design are not losses. Without the option a loss is warned about and
    the exit code stays 0 (docs/cli.md).
  • The CI runs the TypeScript and C# bindings on every push, in the job bindings: npm ci and
    npm test, dotnet test, and the fixture runner over each binding.
  • The fixture manifest pins division in the rule language for every implementation: the section
    arithmetic runs the pack conformance/fixtures/arithmetic/pack.json, whose quotients are
    worked out by hand, and a rules request of the runner may name that pack. The C# binding
    reads a pack from a stream with RulePacks.Read.

Changed

  • Every rule of a pack, and every entry of javaRules, which is now an object {class, oracle},
    names its oracle; the rules of en16931/1.3.16 say artefact. verifiedAgainst is optional.
    A pack manifest written for 0.9.x — without edition, with a rule that names no oracle, or
    with a javaRules entry written as a string — is refused by RulePacks.read.
    no-pack-for-edition now means only that this build carries no pack for the edition.
  • The writer's report tells a term left behind by design from a loss. Handed the extension
    registries of a document (WriterOptions.builder().extensions(...), and esj convert and
    esj validate hand it the ones --extension loads), the writer notes a value of a term whose
    registry declares "transport": "none" — the B2C extension's — as TERM_BY_DESIGN with the
    registry in WriteNote.registry(), and counts it neither as written nor as dropped;
    WriteReport.byDesign() gathers those terms by registry. esj convert --extension b2c names
    them on one information line instead of warning that ten values were not written, and the
    JSON report carries the registry beside each such note. esj validate reaches the same rows
    and exit codes as before.
  • The letter layout is the default of esj render and of PdfRenderer
    (RenderOptions.DEFAULT_LAYOUT), for a template that names none as well; --layout generic,
    Layout.GENERIC or "layout": "generic" ask for the generic layout, in which the PDF
    validation report still draws the invoice it carries.
  • Letter layout: a page after the first begins as far under the rule of its compact head as the
    first section of page one begins under the rule of the title, whatever opens the page.
  • Letter layout: the seller's details in the foot of page one are written value first — an
    identifier followed by the code of its scheme, or by a short word where the document states
    none (HRB 12345 (Registernummer)), and the contact point without a label.
  • Letter layout: the preceding invoices of BG-3 stand under the title, up to three, with their
    dates; a credit note (the types the display names call one) labels its number, its date and
    its closing amount as a credit note's; neither a credit note nor a self-billed invoice carries
    an EPC QR code, since its reader is not the one who pays (docs/letter-layout.md).
  • The libraries underneath moved on: Saxon-HE 13.0 runs the validation artefacts and the
    stylesheets, where 12.10 did, the tests run on JUnit 6, and the PostgreSQL that
    docs/storage.md is executed against is release 18 on every platform the
    build runs on, pinned through the bill of materials of its binaries rather than left to the
    default of the artefact that starts it. Nothing observable changed: the ledgers, the fixture
    manifest and the checked-in renderings hold as they are.

Fixed

  • Division in the TypeScript binding is exact where the quotient terminates, as rules/README.md
    says: Decimal.divide computed every quotient to 34 fraction digits and stripped the zeros, so
    a terminating quotient that needs more digits was rounded — 1E-35 / 1 came out as 0. The
    Java and C# implementations were measured and already divided exactly.
  • A row of a table that fits on a page no longer sends the lines hanging under it to the next
    page under a carry-over line: the room for that line is asked for only where the lines go over.
  • The footer of the PDF validation report no longer runs into the page count for a long input
    path: the path is shortened in its middle, keeping its beginning and its file name.
  • A hybrid PDF with two embedded files under one name is refused instead of validated on the one
    a map kept: both are listed, the name is reported (PDF-EMBEDDED-DUPLICATE-NAME), and files a
    page refers to count as attachments too (docs/pdf-input.md).
  • The output intent of a rendering carries the vendored sRGB profile byte for byte on every
    runtime. PDFBox was handed the profile as a stream, read it into a java.awt.color.ICC_Profile
    and embedded what that object gave back, and on a runtime whose colour management
    re-serializes a profile it was handed — the Ubuntu build of OpenJDK 21, for one — the header
    of the embedded profile named that engine as the preferred CMM, so the file was no longer the
    ICC's and the rendering no longer the checked-in one. The stream is now filled from the file
    itself; the checked-in renderings are unchanged.
  • The checksum beside the platform-independent archive of a release, esj-<version>.zip.sha256,
    is the checksum of that archive. Each of the three packaging jobs of the release workflow
    uploaded the archive and its checksum under the same two names, and the release kept whichever
    copy of each came last — for 0.9.1 the archive of one job and the checksum of another, which
    does not match it; the six platform archives match theirs. The linux-x64 job alone delivers
    it now, its entries carry fixed times, modes and order, so that the clock, the time zone and the
    umask of a build leave no trace in its bytes, and nothing is attached before every checksum has
    been checked against the archive beside it, no name comes from two jobs with different
    contents, and a tag finds the archive of the version it names.
  • esj embed and esj render --embed cii hand the writer of the attachment the extension
    registries --extension loads, as esj convert does: a term whose registry declares
    "transport": "none" is named on one information line instead of being warned about as a value
    that was not written. EmbedOptions.withExtensions(...) hands them over in Java; the hybrid
    file is the same file.
  • conformance/fixtures/manifest.schema.json describes the rule case file as it is written: it
    required a part of every file, and the rule case file names its pack, the directory of that
    pack and its cases instead. FixtureManifestTest validates every file of the manifest against
    the schema.
  • dist/package.sh --out <directory> takes a relative directory: zip left with exit code 15,
    and linux-native and smoke looked for it under the repository rather than under the
    working directory.