feat(vault): model the Art. 77(1) passport scope - #239
Open
LKSNDRTMLKV wants to merge 2 commits into
Open
Conversation
Nothing read Art. 77(1)'s scope, so the node could not tell a battery it must passport from one it need not: a portable or SLI battery published with no mandatory content at all while claiming to be a battery passport, and an industrial battery at or below 2 kWh — which the article exempts — was still asked for the full category content. The scope is now a predicate over the values core already owns and is reported on lint beside the blockers, including the part the engine cannot fix: the content gate lives in core's transition_to and fires whatever the scope says, so a voluntary passport is still gated and the note admits it rather than leaving the operator to wonder.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 25 |
| Duplication | 5 |
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.
`dpp_domain::instrument::PassportObligation` already exists and answers a different question — whether an act creates a passport duty for a product group, and from when — while this one answers whether Art. 77(1) reaches a given record; two types with one name in adjacent crates is the mistake the sector/productGroup rename was paid for, so the engine type is PassportScope and the module header says why they compose rather than compete.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses #238 — the second half needs
dpp-coreand stays open. Stacked onfeat/battery-import-templates(#237).Engine-only, deliberately —
dpp-coreis under review elsewhere, so nothinghere touches it. That constraint decides how much of #238 this can close; see
What this cannot fix below.
The rule
Art. 77(1), verbatim from the OJ:
Five categories are defined (Art. 3(9), (11), (12), (13), (14)); this article
reaches three.
domain::passport_scopeis one pure function over values corealready owns, so lifting it into core later is a move rather than a rewrite.
Two qualifiers the article turns on, both easy to lose:
definition of "rated capacity" — ampere-hours — is scoped "for the purposes
of this Annex" and does not govern Art. 77(1). The comparison is against
ratedCapacityKwh, nevernominalCapacityAh.rechargeablequalifier. Arts. 7 and 8 both say "rechargeableindustrial batteries with a capacity greater than 2 kWh". Art. 77(1) does not.
Which way to be wrong
An undeclared capacity is treated as in scope. Exempting on an unknown is
the error that silently switches off a statutory gate; including on an unknown
is friction an operator can end by declaring the capacity, and the note tells
them so. Deriving energy from
nominalVoltageV × nominalCapacityAh(bothrequired) was considered and rejected for the exempting direction — an inference
about a battery is not the ground on which to stop asking for content the law
requires. An unrecognised
BatteryType(the enum is#[non_exhaustive]) ishandled the same way, and logs.
What this cannot fix
check_mandatory_contentruns insidedpp-core'sPassport::transition_toonfirst publish. The engine calls that; it cannot skip it. So the concrete
over-demand in #238 — an industrial battery at or below 2 kWh being asked for
content the article exempts — is still present, and needs a core change.
Measured, not assumed: an industrial battery at 1.5 kWh reports
passportScope: voluntaryand still returns 34 blockers. Rather thanleave that unexplained, the note says it outright ("this node still applies the
category content gate, which is stricter than the article requires here"), and
a_battery_outside_article_77_is_voluntary_and_still_gatedpins both halves —so if core ever narrows the gate, the test fails and the note goes with it.
The other half of #238 is closed: a portable or SLI passport is now named
voluntary instead of passing silently.
Verified live
just checkgreen (971 tests),just openapi-checkgreen.Not core's
PassportObligationdpp_domain::instrument::PassportObligationalready exists and answers adifferent question, so this type is deliberately named
PassportScope:passport duty for batteries, and from when (
Required/NotRequired/DisplacedBy).this battery, whose type and capacity decide it.
They compose. Naming both "obligation" would invite a reader to assume one was
the other — the same two-names-one-concept mistake the
sector/productGrouprename was paid for.
Build integrity
Verified against published
dpp-core0.19.0, with the local[patch.crates-io]override removed, becausedpp-core's working tree is onecommit ahead of the published tag and is being edited by someone else. 971 tests
pass either way; CI builds the published pin, so that is the run that counts.
Nothing in this branch touches
dpp-core.