Skip to content

refactor: one home for the passport obligation test - #366

Merged
LKSNDRTMLKV merged 1 commit into
mainfrom
refactor/one-home-for-the-passport-obligation-test
Sep 17, 2026
Merged

LKSNDRTMLKV merged 1 commit into
mainfrom
refactor/one-home-for-the-passport-obligation-test

Conversation

@LKSNDRTMLKV

@LKSNDRTMLKV LKSNDRTMLKV commented Sep 17, 2026

Copy link
Copy Markdown
Member

Closes #208 and closes #213.

One PR rather than two because #213's own next-steps sequence them together — "delete all four copies in the same change so they cannot be half-migrated" — and the rule reads better landing with its first worked example than on its own.

#208 was not blocked, and had not been for a while

Both issues describe this as waiting on a core release. It is not: InstrumentCatalog::passport_obligation_live is already in dpp-domain 0.20.0, which is the version pinned at Cargo.toml:55. Its body is character-for-character the conjunction this repo restates:

!self.determinable_for(product_group).is_empty() && self.passport_required_for(product_group)

So this needed no core change, no release and no repin. It is a delete against a dependency already in the lock file, and it has been available since 0.20.0 landed.

Only four of six uses are gates

determinable_for has six call sites. Four are the conjunction and are folded here. The fifth must not be, and core's own documentation draws the line:

Anything that gates on the answer wants this fold; anything that renders it wants the halves.

dpp-integrator's product_groups.rs serves determinable as its own field beside required and from. Those are different questions — an obligation can exist while the implementing acts that would make it determinable do not — and collapsing them would make a reporting endpoint answer one boolean where it currently answers two honest ones. Left alone deliberately; a find-and-replace across all six would have broken it.

The sixth is a comment.

Under one name

passport_determinable in dpp-plugin-host and passport_obligation_live in dpp-vault meant the same thing under different names, which #213 calls out as "its own tax on a reader trying to establish whether the gates agree". Both are now passport_obligation_live, and both are one line delegating to core.

The two wrappers stay rather than being deleted, and that is not laziness: dpp-plugin-host cannot see dpp-types, and each wrapper holds the crate-local instruments() lookup. Wrapping the call is a seam; restating the && was the duplication. After this, the rule has exactly one home.

The ESPR reasoning those doc comments carried — Arts. 24-25 bind while imposing no passport, and Art. 9(4)(b) discharges the duty through EPREL for the ecodesign/energy-labelling pair — is in core's own doc on the method, so it is a pointer here rather than a third copy of the argument.

#213: the boundary test, recorded

#213 was decided on 2026-09-03 (option 1) and its third step was "record the boundary test above where the next person will look", which never happened. CLAUDE.md now carries it beside the Golden Rule and the Core Purity Rule:

a change to the law changes it → dpp-core; a change to deployment, storage, or operation changes it → engine-side, in dpp-types, which already carries dpp-domain.

With the counter-example in each direction, because "is it about passports" gives the wrong answer both ways: snapshot_json_key is about passports and changes when the object-storage layout changes, so it stays; the obligation test is about passports and changes when an implementing act comes into force, so it is core's.

And the reason it matters, which is the part #213 is really about: CI can see a duplicated shape and cannot see a duplicated rule. every_published_object_shape_has_a_name fails the build on a copied struct. N copies of a predicate are N individually correct files that compile, pass, and diverge the first time somebody adds a condition to one. The three instances on record — PROTECTED_PATCH_FIELDS restated three entries short and making protected fields writable, a query parameter spelled three ways, and this conjunction in four places — are named there so the next person meets them before writing the fourth.

Verified

  • Zero hand-rolled conjunctions remain; the one renderer survives. Both checked by grep after the edits, not assumed.
  • just check green.

Behaviour is unchanged by construction — every folded site now calls a function whose body is the expression it replaced.

Summary by CodeRabbit

  • Refactor
    • Passport-obligation checks now consistently reflect whether product groups are currently in force.
    • Compliance, plugin processing, input enrichment, passport handling, and vault workflows use the same live obligation status.
    • This improves consistency when determining which products require passport-related processing and compliance treatment.

@LKSNDRTMLKV LKSNDRTMLKV added the review-ready Opt this PR into a CodeRabbit review label Sep 17, 2026
@LKSNDRTMLKV

Copy link
Copy Markdown
Member Author

@coderabbitai review

Two things to push on:

  1. Whether the fifth call site should have been folded too. dpp-integrator/src/handlers/product_groups.rs:268 keeps !determinable_for(key).is_empty() deliberately, because it renders determinable beside required rather than gating on the conjunction. Core's own doc draws that line. If you disagree, say so — it is the one judgement call in this change.
  2. The two surviving wrappers. dpp-plugin-host and dpp-vault each keep a one-line passport_obligation_live around the crate-local instruments() lookup. dpp-plugin-host cannot see dpp-types, so I claim the wrapper is a seam rather than a second copy — the conjunction itself is gone from both.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: bcb664d6-6943-4904-9c24-154fba01dde7

📥 Commits

Reviewing files that changed from the base of the PR and between 94fa811 and ab3f901.

📒 Files selected for processing (4)
  • CLAUDE.md
  • crates/dpp-node/src/plugins.rs
  • crates/dpp-plugin-host/src/host.rs
  • crates/dpp-vault/src/domain/service/mod.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The change centralizes live passport-obligation checks through InstrumentCatalog::passport_obligation_live. Vault, plugin-host, node compliance, and test-helper paths now use the centralized predicate. CLAUDE.md documents the placement rule for shared domain logic.

Changes

Passport obligation consolidation

Layer / File(s) Summary
Centralize obligation evaluation
crates/dpp-vault/src/domain/service/mod.rs, crates/dpp-plugin-host/src/host.rs, CLAUDE.md
The vault and plugin host delegate passport-obligation checks to InstrumentCatalog::passport_obligation_live. CLAUDE.md documents where shared legal and operational logic belongs.
Update obligation consumers
crates/dpp-plugin-host/src/host.rs, crates/dpp-node/src/plugins.rs
Plugin claim sanitization, compliance-status gating, plugin input enrichment, compliance counting, and the related test helper use passport_obligation_live.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Merge Risk: ⚪ Minimal · up to ab3f9

The consolidation does not leave an identified behavior or integration risk requiring changes before merge.

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The four #208 call sites now use InstrumentCatalog::passport_obligation_live: two dpp-node filters, the plugin-host wrapper, and the vault wrapper. Both wrappers use the same name. The predicate i…
Out of Scope Changes check ✅ Passed The changes are limited to the four predicate call sites, related comments, and CLAUDE.md guidance. The comments explain the regulatory ownership and wrapper boundary. The documentation records the …
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. (1 skipped: 1 …
Publication Boundary ✅ Passed PASS. The authoritative diff adds only local crate identifiers, regulatory documentation, and calls to InstrumentCatalog::passport_obligation_live. It contains no ADR reference, non-public repositor…
New Dependency Is Justified ✅ Passed PASS: The authoritative pull-request diff changes only CLAUDE.md and three Rust source files. It changes no Cargo.toml file, so it adds no new direct dependency.
Title check ✅ Passed The title clearly summarizes the main change: consolidating the passport-obligation test under one shared implementation.
Description check ✅ Passed The description is detailed and relevant. It explains the objectives, linked issues, implementation changes, preserved renderer behavior, boundary guidance, and verification results. It does not repro…
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/one-home-for-the-passport-obligation-test

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR diff ==='
git diff --unified=80 HEAD^ HEAD -- \
  CLAUDE.md \
  crates/dpp-node/src/plugins.rs \
  crates/dpp-plugin-host/src/host.rs \
  crates/dpp-vault/src/domain/service/mod.rs \
  dpp-integrator/src/handlers/product_groups.rs || true

echo '=== Changed files ==='
git diff --name-status HEAD^ HEAD

echo '=== Renderer context ==='
renderer=$(fd -a -t f 'product_groups.rs' . | rg 'dpp-integrator/src/handlers/product_groups\.rs$' | head -n1 || true)
if [ -n "$renderer" ]; then
  nl -ba "$renderer" | sed -n '230,300p'
fi

echo '=== Plugin host outline and predicate context ==='
host=$(fd -a -t f 'host.rs' . | rg 'crates/dpp-plugin-host/src/host\.rs$' | head -n1 || true)
if [ -n "$host" ]; then
  ast-grep outline "$host" --items all --match 'passport_obligation_live|instruments' || true
  rg -n -C 12 '\bpassport_obligation_live\s*\(' "$host" || true
fi

echo '=== Vault service outline and predicate context ==='
vault=$(fd -a -t f 'mod.rs' . | rg 'crates/dpp-vault/src/domain/service/mod\.rs$' | head -n1 || true)
if [ -n "$vault" ]; then
  ast-grep outline "$vault" --items all --match 'passport_obligation_live|instruments' || true
  rg -n -C 12 '\bpassport_obligation_live\s*\(' "$vault" || true
fi

echo '=== All core predicate invocations ==='
rg -n -C 4 '\.passport_obligation_live\s*\(' -g '*.rs' . || true

echo '=== Remaining conjunction candidates ==='
rg -n -U -P '(?s)determinable_for\s*\([^)]*\)\s*\.\s*is_empty\s*\(\).*?passport_required_for\s*\(' -g '*.rs' . || true

Length of output: 63773


@LKSNDRTMLKV I agree with both decisions.

dpp-integrator/src/handlers/product_groups.rs Line 260 and Line 268 expose two independent values. The handler does not use them to gate an operation. Replacing them with passport_obligation_live would discard the distinction that the response must show. Do not fold this call site.

The wrappers in crates/dpp-plugin-host/src/host.rs Line 493 and crates/dpp-vault/src/domain/service/mod.rs Line 429 are valid seams. Each wrapper performs only the crate-local instruments() lookup and delegates to InstrumentCatalog::passport_obligation_live. Neither wrapper restates the conjunction.

I also found no remaining source occurrence of the prior hand-written determinable_for(...).is_empty() && passport_required_for(...) predicate.

No findings on these points. 🐇


✏️ Learnings added
Learnt from: LKSNDRTMLKV
URL: https://github.com/odal-node/dpp-engine/pull/366

Timestamp: 2026-09-17T11:21:28.809Z
Learning: In pull request `#366`, `dpp-integrator/src/handlers/product_groups.rs` must expose `InstrumentCatalog::passport_required_for` and `InstrumentCatalog::determinable_for` as separate reporting fields. It must not replace them with `InstrumentCatalog::passport_obligation_live`, because that combined predicate is for operational gating and would discard rendered regulatory status information.

Learnt from: LKSNDRTMLKV
URL: https://github.com/odal-node/dpp-engine/pull/366

Timestamp: 2026-09-17T11:21:28.809Z
Learning: In pull request `#366`, crate-local `passport_obligation_live` functions in `crates/dpp-plugin-host/src/host.rs` and `crates/dpp-vault/src/domain/service/mod.rs` are valid seams when they only obtain the local `instruments()` catalog and delegate directly to `InstrumentCatalog::passport_obligation_live`. A wrapper is not duplicated regulatory logic unless it restates the underlying conjunction.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@LKSNDRTMLKV

Copy link
Copy Markdown
Member Author

@coderabbitai review

(Second ask — the first, with the review-ready label applied, produced no review at all. Recording that here since .coderabbit.yaml tracks exactly this: "a mechanism that fires sometimes means you cannot tell from a pull request whether anything read it". The CodeRabbit status on this PR is green and has reviewed nothing.)

The two things worth pushing on, repeated from above:

  1. dpp-integrator/src/handlers/product_groups.rs:268 keeps !determinable_for(key).is_empty() deliberately — it renders determinable beside required rather than gating. Core's own doc says anything that gates wants the fold and anything that renders wants the halves. Disagree if you think it should have been folded with the other four.
  2. The two surviving one-line wrappers: dpp-plugin-host cannot see dpp-types, so I claim each is a seam holding the crate-local instruments() lookup, not a second copy of the rule.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Changed files ==='
git diff --name-status HEAD^ HEAD

echo '=== Shared predicate definition and documentation ==='
rg -n -C 12 '\bpassport_obligation_live\s*\(' -g '*.rs' . || true

echo '=== Renderer context ==='
renderer=$(fd -a -t f 'product_groups.rs' . | rg 'dpp-integrator/src/handlers/product_groups\.rs$' | head -n1 || true)
if [ -n "$renderer" ]; then
  nl -ba "$renderer" | sed -n '240,285p'
fi

echo '=== Plugin-host wrapper and call sites ==='
host=$(fd -a -t f 'host.rs' . | rg 'crates/dpp-plugin-host/src/host\.rs$' | head -n1 || true)
if [ -n "$host" ]; then
  rg -n -C 10 '\bpassport_obligation_live\s*\(' "$host" || true
fi

echo '=== Vault wrapper and call sites ==='
vault=$(fd -a -t f 'mod.rs' . | rg 'crates/dpp-vault/src/domain/service/mod\.rs$' | head -n1 || true)
if [ -n "$vault" ]; then
  rg -n -C 10 '\bpassport_obligation_live\s*\(' "$vault" || true
fi

echo '=== Remaining same-expression candidates ==='
rg -n -U -P '(?s)determinable_for\s*\([^)]*\)\s*\.\s*is_empty\s*\(\).*?(?:&&|and)\s*.*?passport_required_for\s*\(' -g '*.rs' . || true
rg -n -U -P '(?s)passport_required_for\s*\([^)]*\).*?(?:&&|and)\s*.*?determinable_for\s*\([^)]*\)\s*\.\s*is_empty\s*\(' -g '*.rs' . || true

echo '=== Focused diff ==='
git diff --unified=20 HEAD^ HEAD -- \
  CLAUDE.md \
  crates/dpp-node/src/plugins.rs \
  crates/dpp-plugin-host/src/host.rs \
  crates/dpp-vault/src/domain/service/mod.rs \
  dpp-integrator/src/handlers/product_groups.rs || true

Length of output: 48843


@LKSNDRTMLKV I completed the focused review.

I agree with the reporting decision. dpp-integrator/src/handlers/product_groups.rs Line 260 and Line 268 return separate required and determinable fields. InstrumentCatalog::passport_obligation_live would remove information that the response must expose. This call site must not use the combined predicate.

I agree with the wrapper decision. crates/dpp-plugin-host/src/host.rs Line 493 and crates/dpp-vault/src/domain/service/mod.rs Line 429 only perform the crate-local instruments() lookup and delegate to InstrumentCatalog::passport_obligation_live. Neither wrapper restates the regulatory conjunction.

I also searched the Rust sources for the former conjunction in both operand orders. I found no remaining candidate.

No findings from this review. I used the existing PR-specific guidance for these two decisions.

🐇


🧠 Learnings used
Learnt from: LKSNDRTMLKV
URL: https://github.com/odal-node/dpp-engine/pull/366

Timestamp: 2026-09-17T11:21:28.961Z
Learning: In pull request `#366`, `dpp-integrator/src/handlers/product_groups.rs` must expose `InstrumentCatalog::passport_required_for` and `InstrumentCatalog::determinable_for` as separate reporting fields. It must not replace them with `InstrumentCatalog::passport_obligation_live`, because that combined predicate is for operational gating and would discard rendered regulatory status information.

Learnt from: LKSNDRTMLKV
URL: https://github.com/odal-node/dpp-engine/pull/366

Timestamp: 2026-09-17T11:21:28.961Z
Learning: In pull request `#366`, crate-local `passport_obligation_live` functions in `crates/dpp-plugin-host/src/host.rs` and `crates/dpp-vault/src/domain/service/mod.rs` are valid seams when they only obtain the local `instruments()` catalog and delegate directly to `InstrumentCatalog::passport_obligation_live`. A wrapper is not duplicated regulatory logic unless it restates the underlying conjunction.
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@LKSNDRTMLKV
LKSNDRTMLKV force-pushed the refactor/one-home-for-the-passport-obligation-test branch from ab3f901 to 6f63700 Compare September 17, 2026 11:49
@LKSNDRTMLKV
LKSNDRTMLKV merged commit 5df6d54 into main Sep 17, 2026
14 checks passed
@LKSNDRTMLKV
LKSNDRTMLKV deleted the refactor/one-home-for-the-passport-obligation-test branch September 17, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-ready Opt this PR into a CodeRabbit review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shared domain logic has no home between the service crates and core The live-passport-obligation test is restated in four places

1 participant