Skip to content

feat(domain)!: rebuild the unsold-goods disclosure to Impl. Reg. (EU) 2026/2 Annex I - #186

Merged
LKSNDRTMLKV merged 2 commits into
mainfrom
feat/unsold-goods-annex-i
Aug 25, 2026
Merged

feat(domain)!: rebuild the unsold-goods disclosure to Impl. Reg. (EU) 2026/2 Annex I#186
LKSNDRTMLKV merged 2 commits into
mainfrom
feat/unsold-goods-annex-i

Conversation

@LKSNDRTMLKV

Copy link
Copy Markdown
Member

Rebuilds UnsoldGoodsReport to the format its implementing act prescribes. Stacked on #185 — that PR adds the layout tripwires this one's baseline edit touches. Merge #185 first.

Why this is a rebuild and not a migration

Two acts adopted 9 February 2026 govern this disclosure, and the previous model predated both. Neither was in the instrument catalog either, which mattered more than it sounds: unsold-goods is the only binding we mark in_force and determinable, so we were emitting determinations under an act pair we did not record.

  • Commission Implementing Regulation (EU) 2026/2 (CELEX 32026R0002), under ESPR Art. 24(3). Art. 2(1) binds the disclosure's visual presentation and content to Annex I; Art. 3 delimits categories by CN code — two digits, or four for the products of its Annex II; Art. 1 scopes it to the undertaking's financial year, disclosed within 12 months of that year's end.
  • Commission Delegated Regulation (EU) 2026/296 (CELEX 32026R0296), under Art. 25(5) — the closed list of ten derogations, points (a)–(j). Annex I note (h) makes that list the reason vocabulary, so the two acts interlock and neither is complete alone.

Almost nothing of the old shape survived, because the axes are different:

Annex I requires Old model
Legal entity, EUID-or-other identifier, standalone vs consolidated with undertakings listed one optional operator_name
Financial year, both endpoints reporting_period: String, e.g. "2026-Q2"
CN chapter or heading per line product_category: String, e.g. "apparel"
Unit count, weight, packaging-included flag, per-figure estimate marking volume_kg: f64
One line per reason per category one flat record
Six-way percentage split of weight one destination enum
Measures taken and planned to prevent destruction absent

Three things worth reviewing closely

Total destruction is derived, never stored. Annex I note (i): "Destruction is the sum of recycling, other recovery and disposal." That leaves preparing-for-reuse and unknown outside it, which is not the intuitive reading. It is computed, and the wire has no such field — storing it would create a second home for a number the act already defines in terms of three others.

unknown is an answer, not a gap. Note (i) provides it for the share whose treatment could not be obtained from the waste treatment operator. So a well-formed split totals exactly 100 and nothing is left over — which is what the lint and the plugin check.

CnCategory is a new type and is deliberately not CommodityCode. Two or four digits against six/eight/ten. They are different levels of one nomenclature: substituting a product's own code for a disclosure category files a whole chapter's goods under a single article. My own earlier note said to reuse CommodityCode here; that was wrong.

Two changes that alter behaviour

unsold-goods/v1.0.0 is deleted, not migrated. No lens can carry a document forward from it — a financial year is not derivable from "2026-Q2", a CN code is not derivable from "apparel", a percentage split is not derivable from one destination, and the reason lists share no member. Two old reasons (EndOfSeason, OverProduction) named commercial circumstances that are not derogations at all, so a disclosure using them asserted a lawful destruction the act does not permit. Every field of a lens would have been invented. The frozen-fixture guard forces the choice: readable, lensable, or gone. Safe only because nothing has ever been stored under it.

A passport in this group no longer requires an Annex VII commodity code. Art. 24's disclosure duty and Art. 25's destruction ban have different scopes — the ban reaches Annex VII's apparel and footwear, the disclosure reaches discarded unsold consumer products generally, as 2026/2's own Annex II shows across 45 CN headings from soap to refrigerators. The old check rejected every lawful disclosure outside those two. The envelope commodity_code describes a product, and a year's disclosure has none; the categories are on the lines.

Also in here

  • InstrumentKind::Implementing. The catalog could name a delegated act but not an implementing one. The Treaty distinction is real — a delegated act may supplement or amend non-essential elements of the basic act, an implementing act only lays down uniform conditions for implementing it — so recording 2026/2 as Delegated would assert a power it does not have, and Other would drop a distinction the law draws. Same call as when Delegated itself was added.
  • Both acts in the instrument catalog, bound to unsold-goods, both notRequired — neither creates a passport. 2026/296 carries a five-year figure that is explicitly not passport availability: Art. 3 requires per-derogation documentation kept five years after destruction and produced within 30 days of a request.
  • New lints and plugin checks for the split totalling 100, the CN depth against Annex II, and point (h)'s subordination — that last one cannot be checked on a single line, since (h) applies "only where none of the circumstances referred to in points (a) to (g) are applicable".
  • Two hardcoded counts replaced with derivations (EMBEDDED.len(), EMBEDDED_COUNT) after they went stale mid-change — the same defect the docs pass in docs(architecture): make the docs true and put tripwires behind the layout rules #185 is about.

just check green; 1160 tests.

@codacy-production

codacy-production Bot commented Aug 25, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 57 complexity · 23 duplication

Metric Results
Complexity 57
Duplication 23

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@LKSNDRTMLKV
LKSNDRTMLKV changed the base branch from docs/truth-pass-and-layout-standard to main August 25, 2026 15:28
@LKSNDRTMLKV
LKSNDRTMLKV force-pushed the feat/unsold-goods-annex-i branch from e08ff4d to 352d692 Compare August 25, 2026 15:33
@LKSNDRTMLKV
LKSNDRTMLKV merged commit 68f559e into main Aug 25, 2026
18 checks passed
@LKSNDRTMLKV
LKSNDRTMLKV deleted the feat/unsold-goods-annex-i branch August 25, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant