Last audited 2026-05-17 against the primary sources below. Every claim in this document is a code reference (file:line) and a regulation citation.
| Annex II item | Regulation text (short) | Engine field | Code |
|---|---|---|---|
| 1 | Operator's name, address, and EORI (per Reg. 952/2013 Art. 9) | 1_operator | AnnexII::operator |
| 2 | HS code, free-text description (incl. trade name, scientific name where applicable), and quantity (kg + supplementary unit where applicable) | 2_product | ProductDescriptor |
| 3 | Country of production + geolocation of every plot | 3_geolocation (Vec\<Geolocation\>) | Geolocation |
| ~~4~~ | ~~Upstream-DDS reference~~ deleted by 2025/2650 | absent | AnnexII — comment block confirms intentional absence |
| 5 | Verbatim declaration text | 5_declaration | ANNEX_II_VERBATIM_DECLARATION; byte-exact, 287 bytes |
| 6 | Signature block (signed-for-and-on-behalf-of / date / function / signature) | 6_signature | Signature; ed25519 base64 + DID + pubkey |
The engine emits these as JSON keys exactly as written, so an auditor can hold the regulation open next to the JSON and read top-to-bottom.
"deforestation-free" means produced on land not subject to deforestation after 31 December 2020
| Where | What |
|---|---|
CUT_OFF_DATE | "2020-12-31" constant |
runner.rs | Passed verbatim to emem's /v1/eudr_dds request as cut_off_date |
| Report | "CUT-OFF 31 Dec 2020" tag in the tags strip; cut-off vertical line on field-history timeline |
"geolocation" means the geographical location of a plot of land described by means of latitude and longitude coordinates corresponding to at least one latitude and one longitude point and using at least six decimal digits [..] for plots of more than 4 hectares used for the production of the relevant commodities other than cattle, polygons [..]. For cattle, the geolocation refers to all establishments [..]
| Rule | Code | Status |
|---|---|---|
| ≥ 6 decimal digits required on every coordinate | validate_plot_geometry | Enforced. Server returns 400 with the exact precision reported. |
| Cattle requires polygon regardless of area | Commodity::polygon_always_required consulted by validate_plot_geometry | Enforced. Cattle + Point → 400. |
| Non-cattle plots > 4 ha must be polygons | ring_area_ha computed; Point geometries are accepted under that area | Polygon shape is the operator's responsibility above 4 ha; the validator records area_ha so the DDS document carries it. |
Test: art_2_28_rejects_under_6_decimal_precision. Test: art_2_28_cattle_rejects_point_geometry. Sample data: every samples/scenarios/.json polygon now meets the 6-digit minimum.
Relevant commodities and relevant products shall not be placed on the market [..] unless [..] (a) [..] deforestation-free; (b) [..] in accordance with the relevant legislation of the country of production; (c) [a DDS] has been made available [..].
| Where | What |
|---|---|
runner.rs | When verdict is NonNegligible, the DDS document carries a non-attestation declaration instead of the Annex II point-5 wording, and the schema id is suffixed .assessment so downstream verifiers can refuse to forward it as a DDS. |
| Report | When verdict is non-negligible the printable report renders "Don't ship yet" + an explicit Art. 4(1) sentence. |
This avoids the false-attestation trap: a signed document with the verbatim "no or only a negligible risk was found" wording over a non-negligible verdict would be a literal false statement.
Test: composite_risk tests cover every tier × fail-fraction combination including the high-risk floor.
The runner emits PipelineEvent::Blocked on non-negligible verdicts. The CLI and server present this state and refuse to mark a task paid on the assessment-only schema.
The scenario carries an optional production.customs_declaration_ref (MRN or local reference). The report's Customs & Shipping (3b) section surfaces this field; the engine does not pre-validate the MRN format (operators in different member states use different formats).
Operators shall keep [..] the due diligence statements they have submitted [..] for a period of five years from the date of submission.
| Where | What |
|---|---|
RECORD_RETENTION_YEARS = 5 | Constant surfaced in the report footer and Art. 12 panel |
EUDR_TASK_RETENTION_DAYS env (default 7) | In-process working-memory window. The 5-year archive is the operator's responsibility. |
DELETE /a2a/tasks/:id | Clears scenario + DDS payload but preserves the receipt CID + audit aggregate (Art. 17(3)(b) GDPR). |
| Step | Engine stage |
|---|---|
| Art. 8(2)(a) — information collection (Art. 9) | geo + forest stages |
| Art. 8(2)(b) — risk assessment (Art. 10) | legality + risk stages |
| Art. 8(2)(c) — risk mitigation (Art. 11) | The engine signals required mitigation in the report's Article 11 panel; the act of mitigating belongs to the operator. |
Coverage matrix is surfaced in the report as the "Article 9(1) · information collected" panel.
| Sub-article | Field on Scenario | Engine behaviour |
|---|---|---|
| (a) description, type, scientific name | product.description, product.trade_name, product.scientific_name | Description and HS required; trade name + scientific name optional but recommended |
| (b) quantity (kg + supplementary unit where applicable) | product.quantity_kg, product.supplementary_unit, product.supplementary_quantity | Quantity > 0 enforced |
| (c) country (and sub-region where relevant) of production | production.country, production.country_subregion | Country required |
| (d) geolocation of every plot + date or interval of production | production.plot_geojson + additional_plots; production.harvest_window | Required; ≥ 6 decimals enforced |
| (e) supplier name, postal address, email | supplier.name, supplier.address, supplier.email | Name + ID required; address + email optional but the report flags absence in the Art. 9 coverage matrix |
| (f) downstream recipient details (when applicable) | Out of scope (operator is the recipient on first placement) | Coverage matrix marks (f) with a contextual note |
| (g) deforestation-free evidence (conclusive + verifiable) | source_receipts.emem.per_cell[].fact_cids | Every per-cell verdict carries fact CIDs; signed receipt envelope verifies offline |
| (h) lawful production evidence | legality.findings | Baseline runs country-tier + supplier-id checks; land tenure and harvest permits remain in scope for a partner provider behind LegalityProvider |
Same five-year horizon as Art. 4(3). The receipt CID stays as the audit anchor even after operator-side erasure (Art. 17(3)(b) GDPR).
Reported in the report's "Article 10 · risk assessment" panel. Criteria (a)–(k) listed with what the engine assessed and what it did not. The composite-risk function is exposed and tested:
composite = clamp(fail_fraction × 0.85 × tier_mult, 0, 1)
composite >= 0.15 regardless of fail_fractionTest: 8 cases in composite_risk tests.
Operator-declared third-party certifications (RSPO, FSC, RA, Fairtrade, ISCC_EU, PEFC, …) are recorded in scenario.certifications and rendered in the report. They are surfaced in the Art. 10 row as mitigation evidence, not a substitute for due diligence.
The report's Article 11 panel is verdict-aware:
negligible → "No mitigation required."non_negligible → "Mitigation required" with the fourArt. 11 measures listed, plus a note that the report must NOT be submitted as a negligible-risk attestation.
The report's Article 12 panel records the engine identity (eudr-a2a, vortx-ai/eudr), notes annual reporting under Art. 12(3), and flags submission status:
eudr-a2a:engine:…)TRACES SOAP submission is on the roadmap; the engine output is otherwise submission-ready.
simplified_dd_applied is set true only when the production country is on the Implementing Reg. 2025/1093 low-risk list. The flag is recorded in conclusion.simplified_dd_applied and surfaced as a tag on the report.
The country tier table is data/country_benchmarks_2025_1093.json, loaded at startup. High-risk (4): BY, MM, KP, RU. Conservative low-risk list (~140). Anything unlisted resolves to STANDARD.
| Group | Application date | Constant |
|---|---|---|
| Default (most operators + medium-sized undertakings) | 30 December 2026 | APPLICATION_DATE_DEFAULT |
| Natural persons + micro / small operators established by 31 Dec 2024 (FLEGT-Annex products excluded) | 30 June 2027 | APPLICATION_DATE_DEFERRED_MICRO_SMALL_NATURAL |
Surfaced in the agent card's /spec page on the public console.
| Property | Value |
|---|---|
| Algorithm | ed25519 over canonical JSON bytes |
| CID | CIDv1, raw codec (0x55), BLAKE3-256 multihash |
| Canonicalisation | Hand-implemented JCS-style: sorted keys, no insignificant whitespace |
| Round-trip safety | canonical_b64 field carries the exact bytes that were signed and hashed, so JSON re-serialisation cannot break verification |
| Verifier | eudr verify <path> (offline); POST /api/verify_receipt (online via emem) |
Tamper detection test: tamper_detected.
Three formats, one CLI:
eudr compile samples/scenarios/civ-cocoa.json --out civ.json
# Engine-annotated XML (verbose; namespace urn:vortx-ai:eudr-a2a)
eudr export-traces civ.json --format engine --out civ.engine.xml
# EU Commission TRACES NT v2 shape. The wire format the Information System accepts
eudr export-traces civ.json --format official --activity IMPORT --out civ.official.xml
# Full SOAP envelope with WS-Security UsernameToken-Digest, ready to POST
EUDR_TRACES_USERNAME=service@deployer.eu EUDR_TRACES_PASSWORD=… \
eudr export-traces civ.json --format soap --activity IMPORT --out civ.soap.xml
civ.official.xml namespace: http://ec.europa.eu/tracesnt/certificate/eudr/submission/v2. Root element: <eudr:SubmitStatementRequest>. Field names match the EU Commission's v2 API spec (operatorType, statement.activityType, statement.countryOfActivity, statement.commodities[].hsHeading, statement.commodities[].producers[].geometryGeojson base64-encoded, operator.operatorAddress.fullAddress, …).
civ.soap.xml adds a SOAP 1.1 envelope with the WS-Security header: UsernameToken with PasswordDigest = base64(SHA1(nonce ‖ created ‖ password)), fresh 16-byte CSPRNG nonce, RFC-3339 UTC Created timestamp. TRACES NT's EUDRSubmissionServiceV2 endpoint accepts this verbatim.
civ.engine.xml is what we already emitted. Useful for archival and internal audit because every <EngineConclusion> and source-cited element is namespaced separately from the official submission shape.
# A2A SendMessage on the new skill (advertised in the agent card):
curl -X POST https://eudr.dev/a2a/rpc \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":"r","method":"SendMessage",
"params":{"task_type":"eudr.monitor.subscribe",
"input":{"dds_reference":"eudr-a2a:engine:CI:…","cadence":"monthly"}}}'
The agent re-runs the deforestation check on the cadence, when a new Hansen loss year appears post-cut-off for any sampled cell, the subscription emits a SignedReceipt-shaped event citing Art. 8(2). The first re-check after each annual Hansen update is free; later checks are priced per call.
civ.xml is a namespaced XML carrying every Annex II point that TRACES NT requires:
<DueDiligenceStatement
xmlns="urn:vortx-ai:eudr-a2a:dds:v2025_2650"
schema="eu.eudr.dds.v2025_2650"
submission_status="pre_submission">
<Header>..</Header>
<Operator>..<EORI>..</EORI>..</Operator> <!-- point 1 -->
<Product><HsCode>..</HsCode><Cn8>..</Cn8>..</Product> <!-- point 2 -->
<Geolocation><Plot index="0">..</Plot>..</Geolocation> <!-- point 3 -->
<!-- point 4 deleted by Reg. (EU) 2025/2650 -->
<Declaration verbatim="true">..</Declaration> <!-- point 5 -->
<Signature>
<Name>..</Name><Place>..</Place><Date>..</Date>
<Function>..</Function>
<CryptographicProof>
<Algorithm>ed25519</Algorithm>
<SignatureBase64>..</SignatureBase64>
<PublicKeyBase32>..</PublicKeyBase32>
<SignerDid>..</SignerDid>
</CryptographicProof>
</Signature> <!-- point 6 -->
<EngineConclusion>..</EngineConclusion>
</DueDiligenceStatement>
submission_status is pre_submission for filable, not_filable when the schema is .assessment, draft when the document is unsigned. A production TRACES adapter replaces the engine namespace with urn:eu:eudr:dds:v1, wraps the document in the official SOAP envelope, and POSTs to https://eudr.webcloud.ec.europa.eu/tracesnt/. The <EngineConclusion> block is dropped on submission (it is the engine's audit annex, not part of Annex II).
Documented openly so an auditor can see the boundary:
own response carries a legality_disclaimer saying production-law checks are structurally outside Earth-observation scope. The baseline legality provider here runs country-tier + supplier-id checks. Land-tenure deeds, harvest permits, and FPIC under Art. 10(2)(c)–(e) require a partner legality provider plugged behind the LegalityProvider trait. The report's Article 10 panel marks (c)–(e), (j) and (k) as "Not assessed by this engine."
document. The SOAP envelope and CIRCABC credential flow are on the roadmap.
deterministic and useful only for development. The server refuses to boot with the dev key over HTTPS. Production deployments must set EUDR_AGENT_PRIVKEY_B64 to a real 32-byte secret (ideally sourced from an HSM via KMS).
regulation places the obligation on the operator. The engine is a processor that runs steps required by Art. 8(2) and emits a signed record. The operator retains responsibility for verifying inputs and for the final submission decision.
| Date | Auditor | Finding | Fix |
|---|---|---|---|
| 2026-05-17 | self-audit | Art. 2(28) ≥ 6-decimal precision not enforced (sample data had 4 decimals; engine accepted) | Added validate_plot_geometry rejecting < 6 decimals; regenerated every sample to 6-decimal coords; geom test threshold raised. |
| 2026-05-17 | self-audit | Art. 2(28) cattle-polygon rule not enforced | validate_plot_geometry rejects Point + cattle. |
| 2026-05-17 | self-audit | Non-negligible verdict still produced a signed Annex II document carrying the verbatim attestation ("no or only a negligible risk was found") | Schema id now suffixed .assessment on non-negligible verdicts; declaration text replaced with a non-attestation explanation. The receipt schema matches so downstream verifiers can route or reject without parsing the payload. |
| 2026-05-17 | self-audit | Polygon-area Hansen post-cut-off loss could be missed by the 16-cell eudr_dds sampler and silently produce a "low risk" verdict | Report's live land-cover panel now compares the polygon-area Hansen scan to the per-cell sampler and emits an amber warning when they disagree, citing Art. 8(2). |
| 2026-05-17 | self-audit | Threshold-based verdict (composite < 0.20 → negligible) ignored confirmed per-cell post-cut-off loss. Art. 2(13) is a binary condition. | Runner now flips to NonNegligible when any per-cell entry has hansen_lossyear >= 21 (or >= 2021 full-year) OR a fail-class label. Threshold is now a secondary gate. |
| 2026-05-17 | self-audit | No TRACES NT submission output. The PDF / JSON cannot be filed; the EU Information System accepts SOAP/XML only. | Added eudr-core::traces::to_traces_xml + eudr export-traces CLI. Output is a namespaced XML carrying every Annex II field plus <EngineConclusion> + cryptographic proof. A TRACES adapter swaps the namespace for the official one and wraps in the SOAP envelope. |
| 2026-05-17 | self-audit | Annex II point 6 missed the Place of signing. Regulation lists "Place" in the signature block. | New Operator.signing_place (optional) + Signature.place. Defaults to country of establishment when absent. Rendered in the PDF signature block AND in the TRACES XML. |
| 2026-05-17 | self-audit | EORI format not validated. Any string was accepted. | New is_valid_eori: ISO-alpha-2 + 1.15 alphanumeric (Reg. 952/2013 Art. 9). Server returns 400 with the exact failure mode. |
| 2026-05-17 | self-audit | Polygon GIS sanity (self-intersection, degenerate) not checked. A bow-tie polygon would pass shape + precision checks and produce an ambiguous verdict. | check_polygon_simple runs OGC-Simple-Features pair-wise edge crossing + degeneracy check on every plot. |
| 2026-05-17 | self-audit | CN8 (8-digit Combined Nomenclature) not surfaced. EU customs declaration + TRACES require CN8; engine accepted HS6. | TRACES XML carries both <HsCode> (operator-supplied) and <Cn8> (derived via zero-pad from HS≥4). Operator-supplied CN8 round-trips unchanged. |
| 2026-05-17 | self-audit | Report didn't disclose it's the audit record, not the legal submission. | Cover page now has a .status-strip panel: submission status (filable vs assessment vs draft), document role (5-year audit record per Art. 4(3)/9(1)/12(5)), and the verification path (eudr verify, fact-CID dereference URL). |
| 2026-05-17 | self-audit | Report carried only the per-cell verdict matrix and a single satellite chip. No NDVI scalar with capture date, no SAR, no crop-calendar cross-check. | Runner now does a second emem pass after eudr_dds: pulls indices.ndvi + sentinel1_raw across the polygon bbox. Each fact carries its Sentinel-2 / Sentinel-1 scene id + captured_at. Surfaced in a new "Sentinel-1 SAR + Sentinel-2 NDVI · single-snapshot per cell" panel with per-cell table, statistics, and the regulation-cited interpretation bands. Disclosed honestly: emem returns single-snapshot, not time-series. |
| 2026-05-17 | self-audit | No reference cross-check for the operator-declared harvest window. | New data/crop_calendars.json table sourced from FAO GIEWS, USDA FAS, ICCO, RSPO. New eudr-core::crop_calendar module performs cross-check per commodity × country and returns in_main_season / in_secondary_season / off_season / no_reference. Surfaced as a 12-month grid in the report comparing the declared window to the reference calendar. Five new tests covering Côte d'Ivoire cocoa, Brazil soy (main + off-season), and the no-reference fallback. |
cargo test --workspace. 58 tests passing across the workspace.
| Crate / file | Tests |
|---|---|
eudr-core::commodity — from_hs | 18 |
eudr-core::country — BenchmarkTable::tier (loads shipped JSON) | 6 |
eudr-core::eu — EU_MEMBER_STATES sanity | 5 |
eudr-pipeline::runner — composite_risk | 8 |
eudr-pipeline::geom — inspect (incl. civ-cocoa sample) | 4 |
eudr-receipts — sign / verify / tamper | 6 |
eudr-a2a::skills — skill catalog shape and prices | 5 |
eudr-server::routes::validation_tests — Art. 2(28) cases | 6 |
The Annex II point 5 declaration text is mandated wording. The string in ANNEX_II_VERBATIM_DECLARATION is 287 bytes long and round-trips through the JSON serialiser unchanged:
By submitting this due diligence statement the operator confirms that due
diligence in accordance with Regulation (EU) 2023/1115 was carried out and
that no or only a negligible risk was found that the relevant products do
not comply with Article 3, point (a) or (b), of that Regulation.
This is the consolidated text from CELEX:02023R1115-20241226 Annex II, unchanged by Reg. 2025/2650.
cargo test --workspace
cargo run --bin eudr -- compile samples/scenarios/civ-cocoa.json --out /tmp/civ.json
cargo run --bin eudr -- verify /tmp/civ.json
python3 -c "
import json; d=json.load(open('/tmp/civ.json'))['payload']
print('Annex II keys:', sorted(k for k in d if k[:1].isdigit()))
print('Point 4 absent:', '4_' not in str(list(d.keys())))
for g in d['3_geolocation']:
ring = g['plot_geojson']['coordinates'][0]
md = min(min(len(repr(c).split('.')[-1]) for c in pt) for pt in ring)
print(f'plot {g[\"cell64\"]}: precision={md} digits. Art. 2(28):', 'PASS' if md >= 6 else 'FAIL')
print('Verdict:', d['conclusion']['verdict'], '· schema:', d['schema'])
"
Because this engine serves other AI agents (A2A v1.0 + MCP), not only humans, its own AI-regulation posture is part of the compliance surface:
| rule | status | where enforced |
|---|---|---|
| AI Act (EU) 2024/1689 Art. 50(1) — disclose AI interaction (applies 2 Aug 2026) | Implemented early. Human surfaces brand the service as an AI agent (obvious-from-context limb); the agent card carries a machine-readable ai_system_disclosure block for agent callers. | crates/eudr-a2a/src/card.rs (AiSystemDisclosure); test guard agent_card_carries_ai_act_disclosure |
| AI Act role classification | Downstream deployer; not a GPAI model provider; not Annex III high-risk. No in-force obligation binds the service today. | REGULATORY_CROSSWALK.md §Adjacent EU rules |
| GPSR (EU) 2023/988 | Out of scope — consumer-product regulation; this is a B2B operator/trader service with no consumer nexus. | legal/terms.md §2 (B2B scope) |
| MDR 2017/745 Annex I ("GSPR") | Out of scope — medical devices only. Listed to disambiguate the acronym. | — |
Human oversight invariant: a non-negligible or indeterminate verdict is never auto-filed; TRACES submission is the operator's regulatory act under their own EU Login identity.