Repository navigation
Releases: Stoicera/einvoice_at
Release list
v0.2.0 — Wartung und Launch-Vorbereitung
Live demo: https://einvoice.sebastiankern.net — the public validator needs no login.
Peppol BIS Billing 3.0 rule set 2026.5, a CVE dependency update (Spring Boot 4.1.1, Tomcat 11.0.26, Netty 4.2.19), anonymous uploads kept in memory, a ReDoS fix in the PII scrubber, and problem type URIs that resolve.
API-visible change: problem type URIs moved from https://einvoice-at.stoicera.com/problems/ (never resolved) to https://einvoice.sebastiankern.net/problems/; the slugs are unchanged. Configurable via PROBLEM_TYPE_BASE_URI, catalogue in docs/problems.md.
Full list: CHANGELOG.md.
What's Changed
- docs: record the completion sprint — M6 closed on every item by @SebastianKrn in #24
- feat(validation): adopt Peppol BIS Billing 3.0 rule set 2026.5 by @SebastianKrn in #26
- fix(web): stop advertising the API docs on deployments that switch them off by @SebastianKrn in #27
- feat(ops): copy database dumps off-site, and prove the copy is readable by @SebastianKrn in #25
- fix(api): the OpenAPI description announced UBL support as future work it already shipped by @SebastianKrn in #28
- fix(privacy): keep anonymous uploads in memory — the page promised they never touch disk by @SebastianKrn in #29
- docs: publish API docs in production, record the session, and hand the next one a work queue by @SebastianKrn in #30
- docs(worklog): the API docs are live, and the conditional links proved themselves in production by @SebastianKrn in #31
- Abhängigkeiten: Spring Boot 4.1.x, Tomcat, Netty (CVE-Fix) by @SebastianKrn in #38
- AGENTS.md: Non-goals aus PRD §5 und Rollback-Befehl eingetragen by @SebastianKrn in #39
- AGENTS.md: Platzhalter durch echte Befehle ersetzt by @SebastianKrn in #37
- Repo-Baseline: AGENTS.md, CLAUDE.md, Copilot-Anweisung by @SebastianKrn in #36
- chore(backup): upstream the host backup scripts, record the armed off-site copy by @SebastianKrn in #35
- ci: OWASP-Job auf 90 Minuten begrenzt by @SebastianKrn in #41
- build(deps): bump phive-rules.version from 4.4.1 to 4.4.2 by @dependabot[bot] in #13
- build(deps): bump org.glassfish.jaxb:jaxb-runtime from 4.0.5 to 4.0.9 by @dependabot[bot] in #16
- build(deps): Bump eclipse-temurin from
201fbb8toc42fecfby @dependabot[bot] in #33 - build(deps-dev): bump com.diffplug.spotless:spotless-maven-plugin from 3.8.0 to 3.9.0 by @dependabot[bot] in #15
- build(deps-dev): Bump com.tngtech.archunit:archunit from 1.4.2 to 1.5.0 by @dependabot[bot] in #32
- build(deps): bump docker/login-action from 4.5.1 to 4.6.0 by @dependabot[bot] in #12
- build(deps): bump actions/setup-java from 5.6.0 to 5.7.0 by @dependabot[bot] in #23
- build(deps): Bump docker/setup-buildx-action from 4.2.0 to 4.3.0 by @dependabot[bot] in #34
- fix(ai-assist): E-Mail-Muster im PiiScrubber begrenzt (ReDoS) by @SebastianKrn in #42
- fix(api): Problem-Type-URIs auf auflösbaren Host, Basis konfigurierbar by @SebastianKrn in #43
- docs: README mit Live-Demo, Community-Dateien, llms.txt, CHANGELOG 0.2.0 by @SebastianKrn in #44
- Portfolio-Readiness: Checkliste vor dem öffentlichen Launch, Security-Kontakt repariert by @SebastianKrn in #40
New Contributors
- @dependabot[bot] made their first contribution in #13
Full Changelog: v0.1.0...v0.2.0
v0.1.0 — Austrian e-invoicing: generate, validate, convert
Live demo: the public validator runs at https://einvoice.sebastiankern.net — no login needed; the dashboard behind Keycloak at /app.
The first release. A self-hostable platform that generates, validates and converts Austrian
e-invoices, with a German-language public validator, an authenticated dashboard, an optional AI
explanation per finding, and the operational scaffolding to run it.
Added — invoice model and formats
- Canonical invoice model (
core): the EN 16931 core subset as pure Java with zero
dependencies, money asBigDecimalwith explicit scale and rounding, Austrian VAT logic
(20/13/10/0 %, reverse charge, exemption), and invariants checked at construction. Property-based
tests (jqwik) for the arithmetic. (ADR-0003) - ebInterface 6.1 read and write (
formats-ebinterface, wrapping ph-ebinterface), and the
canonical ↔ ebInterface mapping in both directions. - Peppol BIS Billing 3.0 / UBL 2.1 read and write (
formats-ubl, wrapping ph-ubl), including
ubl:Invoiceandubl:CreditNotebehind a sealed type, and both mapping directions.
(ADR-0007) - Conversion between the two formats, always through the canonical model so the invoice is
understood once, with a per-document report of exactly which fields the target could not carry. - PDF print view (
rendering, Apache PDFBox): a German A4 invoice with the VAT breakdown as its
own table, because § 11 UStG requires tax per rate. (ADR-0008)
Added — validation
- Two pipelines behind one entry point. ebInterface: XSD → this project's own AT-B2G Schematron
→ hand-written business rules. UBL: the official OpenPeppol rule set, executed unmodified at a
version pinned in code. (ADR-0004) - XXE-hardened parsing of every untrusted document, refusing a document merely for declaring a
DOCTYPE, with an independent 20 MB module-level size guard. - German findings. Every finding carries a German message; English second. ~80 Peppol rules an
Austrian filer realistically trips are translated from the rule set's own assertion texts. - A golden-file corpus of valid and systematically broken documents, one per rule.
Added — API, persistence and security
POST /invoices,POST /validate(anonymous, rate-limited, never stored),
POST /convert,POST /reports/{id}/explain,DELETE /tenant, per-invoiceebinterface/ubl/
pdfoutputs, and tenant-scoped listings. RFC 9457 problem+json throughout; OpenAPI via springdoc
with an off switch.- PostgreSQL + Flyway from
V1, five tables, canonical JSON stored as JSONB and no XML ever
persisted. (ADR-0005) - OAuth2 / Keycloak plus API keys, exactly one per request; keys stored as hashes only and
refused the ability to manage keys or delete the tenant. Audit log of who did what and when,
recording the payload's SHA-256 rather than the payload.
(ADR-0006) - Rate limiting on the endpoints that cost CPU or money, with a different policy per endpoint and
a stated reason for each difference.
Added — web UI and AI
- Public validator (German-first, SEO meta, an explicit DSGVO notice) sharing one code path with
the anonymous API so "an anonymous upload is never stored" has a single implementation. - Authenticated dashboard: invoice list and detail with all three downloads, report history, a
four-step create-invoice wizard, API-key management and a Konto page. Server-rendered Thymeleaf
with no CSS or JavaScript build step; every page works with JavaScript disabled.
(ADR-0009) - AI explanations behind
features.ai-explanations, off by default, degrading to a friendly
notice when the provider is unreachable. PII is scrubbed before any call and no document fragment
is ever sent. (ADR-0010, docs/privacy.md)
Added — GDPR
- Art. 17 erasure: every invoice, report, key and audit event of the calling tenant, in one
transaction, scoped by the credential so it cannot be aimed at anyone else. - Art. 5(1)(e) retention: a nightly purge of reports and audit events — and never invoices,
because § 132 BAO obliges an Austrian business to keep them for seven years.
(ADR-0011)
Added — operations (M6)
- OpenTelemetry across the pipeline stages. Traces and metrics over OTLP; the validation stages
reach the tracer through a plain-Java port sovalidationstays free of Spring. An
observabilitycompose profile brings Prometheus, Tempo and Grafana with datasources and a
dashboard provisioned. (ADR-0012) /actuator/infonaming the commit and build time — and nothing about the people who wrote the
code.docs/deployment.mdfor Hetzner + Dokploy, plusscripts/backup.sh/scripts/restore.shand
an automated backup/restore drill that runs on every build.SECURITY.mdwith a STRIDE-light threat model and a disclosure policy.SERVER_FORWARD_HEADERS_STRATEGYso the rate limiter sees real client addresses behind a
trusted proxy — and ignores forged ones without one. Behind a proxy the value isnative, which
reads theX-Forwarded-Forchain from the end a proxy writes rather than the end a caller writes.
All three directions tested.- Instance election for the retention job via a PostgreSQL advisory lock, so several replicas
against one database no longer all purge. - A real authorization-code login driven through Keycloak's own form in a browser test, closing
the gap M5 recorded by name. - Lighthouse ≥ 95 asserted in CI on both public pages; measured 100 in all four categories.
- CI publishes to GHCR and triggers a Dokploy deploy webhook, both skipping loudly without their
secrets.
Known limits
Stated here as well as in SECURITY.md, because a release note that lists only
successes is marketing:
- The rate limiter is in-memory and per instance; two replicas mean two allowances.
- An Art. 17 erasure deletes the audit trail with the rest of the tenant's data — the alternative is
not honouring the request. - Logout is local: it ends this application's session, not the Keycloak SSO session.
- Peppol finding messages are English where the rule set publishes only English; the German message
is a German frame around the official wording, except for the ~80 translated rules. - This platform is not a certified Peppol Access Point and sends invoices nowhere.
- No ZUGFeRD/Factur-X hybrid PDF.