Skip to content

feat(domain,rules): give a unit a product-life status - #226

Open
LKSNDRTMLKV wants to merge 1 commit into
mainfrom
feat/life-status
Open

feat(domain,rules): give a unit a product-life status#226
LKSNDRTMLKV wants to merge 1 commit into
mainfrom
feat/life-status

Conversation

@LKSNDRTMLKV

Copy link
Copy Markdown
Member

Closes #218. Phase 5 of docs/architecture/PRODUCT-LINEAGE.md, and the last of
#208's plan.

Three pieces, per the issue: the LifeStatus field, the TransferReason
variant for the waste handover, and the dpp-rules consistency rule that binds
the status to the derivation edges.

Non-breaking. life_status is Option with skip_serializing_if, and
TransferReason was already #[non_exhaustive].

Three things worth your eyes

Everything else follows the issue. These three are calls I had to make, and I'd
rather flag them than have them merge unnoticed.

1. This PR corrects a contradiction in the design note

§4.2 of PRODUCT-LINEAGE.md said life_status

needs a defined mutation path rather than a place in PROTECTED_PATCH_FIELDS

while the issue says it

goes in PROTECTED_PATCH_FIELDS with the other signed fields

I read these as reconciling rather than conflicting: the note demanded that
Phase 5 specify a mutation path, the issue specifies it (waste transition =
new passport version), and being in PROTECTED_PATCH_FIELDS is precisely what
forces a caller onto that path rather than an alternative to it. §4.2 is updated
to say so, and to record that it is correcting itself — leaving the note arguing
with the code would be the same failure as leaving a doc self-contradictory
after a merge.

If you meant the note's reading instead, this is the commit to push back on.

2. The wire forms are hyphenated, against house style

Every other wire vocabulary here is camelCase. These five are not:

original · repurposed · re-used · remanufactured · waste

Annex XIII point 4(c) does not name concepts for us to spell as we like — it
enumerates the literal values the status is "defined as". Serde's camelCase
would produce reused, which is not a value the instrument contains, so the
variants carry explicit #[serde(rename)] and a test pins all five against the
quoted text. The alternative — camelCase everywhere for internal consistency —
is defensible, but it invents a value, and a citation a reader cannot check
against the primary source is worth less than the consistency it buys.

3. waste is exempt from the consistency rule

The issue asks that life_status agree with derived_from's operations. Taken
literally that breaks a lawful record, so I carved out waste.

A waste battery's derivation edges describe how it was manufactured; they say
nothing about whether it is now waste. A repurposed unit that later became waste
carries a repurposing edge and a waste status, and both are correct
checking one against the other would report an entirely ordinary record as
inconsistent. This follows from the issue's own reasoning that 'waste' is "the
one transition that happens to a record that continues", but the issue does not
spell out the consequence for the rule, so I am naming it here.

The rule is some, not every

A claimed status must be supported by at least one edge, not by all of them.
Requiring agreement would make the plural mixed-predecessor case
unrepresentable — Art. 77(7) permits several predecessors and nothing forces
them to share an operation — and that case is exactly why the issue chose to
store the status rather than derive it. Requiring every edge to agree would
reintroduce the flaw by the back door.

repurposed is supported by either repurposing operation, since Art. 3(30) and
Art. 3(31) differ by the waste status of the input rather than the outcome.

Disclosure

lifeStatus is Disclosure::Individual in PASSPORT_FIELD_DISCLOSURE, and
there is a test asserting it. That entry is the only thing between an individual
unit's life status and an anonymous reader: passport_default()'s
default_disclosure is Public, so an unclassified envelope field is served to
everyone. Its absence is the silent failure, which is why it gets a test of
its own rather than relying on review.

No strip-list needed — the public view applies PASSPORT_FIELD_DISCLOSURE
generically through the scope-aware filter. RETENTION_MUTABLE_FIELDS
deliberately does not gain the field: a waste transition is a new version, not
a mutation of a retention-locked record.

Citations

Art. 77(7) and its second subparagraph at OJ L 191, 28.7.2023 p. 73;
Annex XIII point 4(c) at p. 109; Art. 3(29)–(32) at p. 27. These match the pins
re-verified during #208's Phase 1 rather than being freshly asserted.

There is no sixth value: "approaching end of life" appears nowhere in
Regulation (EU) 2023/1542, and a test would now fail if it came back.

Checks

just check green: fmt, clippy, nextest (1289 tests), doctests, plugins,
rustdoc, audit. New tests: 4 on LifeStatus, 10 on the consistency rule.

Two pre-existing tripwires fired during this work and both were right — the
PROTECTED_PATCH_FIELDS key-reality check (its fixture did not populate the new
field) and TransferReason::ALL's exhaustive match. Fixed rather than worked
around.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 28 complexity · 0 duplication

Metric Results
Complexity 28
Duplication 0

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.

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.

Product-life status has nowhere to live, and it cannot be create-time

1 participant