Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,15 @@ jobs:
# `just check`.
- name: Sector plugin tests
run: just test-plugins
# The same exclusion keeps them out of `cargo fmt --all` and `cargo
# clippy --workspace`, so plugin code had no formatting check and no lint
# at all — seven of ten crates were unformatted when this was added.
- name: Sector plugin formatting
run: just fmt-check-plugins
- name: Sector plugin lint
run: just lint-plugins
# Each loop above asserts it did some work. This proves that assertion
# fails on an empty match, because the failure that keeps recurring here
# is a gate that goes green without running.
- name: Plugin gate guards fail on an empty match
run: just plugin-gates-self-test
115 changes: 98 additions & 17 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
# Usage: just <recipe>
# =============================================================================

# The directory prefix the product-group plugin crates share.
#
# One home for it, and a parameter on every recipe that globs it, so
# `plugin-gates-self-test` can drive those recipes against a prefix that
# matches nothing. These directories were once `plugins/sector-*`, and the
# rename is what silently emptied a glob before.
PLUGIN_PREFIX := "product-group"

# ---------------------------------------------------------------------------
# Quality gates
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -88,15 +96,90 @@ bench:
# them: a plugin can be broken while the workspace gate is green. Runs on the
# host (not wasm32) because these are ordinary #[cfg(test)] unit tests; the
# wasm build is covered separately by `build-plugins`.
test-plugins:
test-plugins prefix=PLUGIN_PREFIX:
#!/usr/bin/env bash
set -euo pipefail
for plugin in plugins/product-group-*; do
ran=0
for plugin in plugins/{{prefix}}-*; do
[ -f "$plugin/Cargo.toml" ] || continue
echo "Testing $plugin..."
(cd "$plugin" && cargo test --quiet)
ran=$((ran + 1))
done
# 🚨 A loop over a glob that matches nothing succeeds. This recipe printed
# "All plugin tests passed." and exited 0 having tested nothing — and that
# is not hypothetical: these directories were once `plugins/sector-*`, and
# the CI job that globbed them went silently green at the rename. That job
# gained this guard; this recipe, which is what `just check` runs, did not.
[ "$ran" -gt 0 ] || { echo "ERROR: no plugins matched plugins/{{prefix}}-*"; exit 1; }
echo "All $ran plugin test suites passed."

# Formatting and lint for the product-group plugins.
#
# 🚨 The plugins are `exclude`d from the workspace, so `cargo fmt --all` and
# `cargo clippy --workspace` do not reach them **at all**. Plugin code shipped
# for months with no formatting check and no lint: seven of the ten crates were
# unformatted when this recipe was first run. `test-plugins` covered their
# behaviour and nothing covered their shape.
fmt-check-plugins prefix=PLUGIN_PREFIX:
#!/usr/bin/env bash
set -euo pipefail
ran=0
for plugin in plugins/{{prefix}}-*; do
[ -f "$plugin/Cargo.toml" ] || continue
(cd "$plugin" && cargo fmt --check)
ran=$((ran + 1))
done
[ "$ran" -gt 0 ] || { echo "ERROR: no plugins matched plugins/{{prefix}}-*"; exit 1; }
echo "$ran plugin crates formatted correctly."

lint-plugins prefix=PLUGIN_PREFIX:
#!/usr/bin/env bash
set -euo pipefail
ran=0
for plugin in plugins/{{prefix}}-*; do
[ -f "$plugin/Cargo.toml" ] || continue
echo "Linting $plugin..."
(cd "$plugin" && cargo clippy --all-targets -- -D warnings)
ran=$((ran + 1))
done
[ "$ran" -gt 0 ] || { echo "ERROR: no plugins matched plugins/{{prefix}}-*"; exit 1; }
echo "$ran plugin crates linted clean."

# Format the plugins in place — the counterpart to `just fmt` for the crates
# `cargo fmt --all` cannot see.
fmt-plugins prefix=PLUGIN_PREFIX:
#!/usr/bin/env bash
set -euo pipefail
for plugin in plugins/{{prefix}}-*; do
[ -f "$plugin/Cargo.toml" ] || continue
(cd "$plugin" && cargo fmt)
done
echo "All plugin tests passed."

# Prove the vacuous-pass guards actually bite.
#
# Every looping gate above asserts it did some work, because the failure that
# keeps recurring here is a gate that succeeds without running. A guard nobody
# has watched fail is itself unverified, so this runs each loop against a glob
# that matches nothing and requires a non-zero exit.
plugin-gates-self-test:
#!/usr/bin/env bash
set -euo pipefail
# 🚨 This drives the REAL recipes against a prefix that matches nothing,
# which is why they take one. An earlier version re-implemented the guard
# inline and ran that copy three times — it proved a fact about bash, not a
# fact about these recipes, and would have stayed green if the guard were
# deleted from any of them. That is the same vacuous pass the guards exist
# to prevent, one level up.
#
# Each guard fires before the loop body, so no cargo invocation happens.
for gate in test-plugins fmt-check-plugins lint-plugins build-plugins; do
if just "$gate" no-such-prefix > /dev/null 2>&1; then
echo "SELF-TEST FAILED: $gate passed on an empty match"
exit 1
fi
done
echo "Plugin gate guards fail on an empty match, as they must."

# Ask the European Commission's AdES reference implementation (DSS) what our
# JAdES signature actually is.
Expand Down Expand Up @@ -134,7 +217,7 @@ jades-oracle:
#
# The private-material scan is deliberately absent: it was removed pending a
# redesign, so nothing here checks for a leak into this public repository.
check: fmt-check lint test test-doc test-plugins doc audit
check: fmt-check lint fmt-check-plugins lint-plugins test test-doc test-plugins plugin-gates-self-test doc audit

# `check` is a subset of CI: it never cross-compiles, so the two WASM jobs and
# the orphaned-tests guard can fail in CI on a change that passed locally. That
Expand Down Expand Up @@ -195,24 +278,22 @@ build:
cargo build --workspace --release

# Build all Wasm product-group plugins (requires wasm32-wasip1 target)
build-plugins:
build-plugins prefix=PLUGIN_PREFIX:
#!/usr/bin/env bash
set -euo pipefail
for plugin in \
plugins/product-group-battery \
plugins/product-group-textile \
plugins/product-group-steel \
plugins/product-group-electronics \
plugins/product-group-construction \
plugins/product-group-tyre \
plugins/product-group-toy \
plugins/product-group-aluminium \
plugins/product-group-furniture \
plugins/product-group-detergent; do
# Globbed, not listed. This recipe named its ten plugins one per line while
# `test-plugins` and the CI wasm job globbed the same directory — so an
# eleventh plugin would have been tested and linted here and never built,
# which is the drift a hand-maintained list always ends in.
built=0
for plugin in plugins/{{prefix}}-*; do
[ -f "$plugin/Cargo.toml" ] || continue
echo "Building $plugin..."
(cd "$plugin" && cargo build --target wasm32-wasip1 --release)
built=$((built + 1))
done
echo "All plugins built."
[ "$built" -gt 0 ] || { echo "ERROR: no plugins matched plugins/{{prefix}}-*"; exit 1; }
echo "All $built plugins built."

# Build a single product-group plugin and print the artifact path.
# Usage: just build-plugin product-group-battery or just build-plugin battery
Expand Down
4 changes: 2 additions & 2 deletions plugins/product-group-aluminium/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

use dpp_plugin_sdk::export_plugin;
use dpp_plugin_sdk::traits::{
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, PluginError, PluginIdentity,
PluginInput, PluginResult, SchemaVersionRange,
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, PluginError,
PluginIdentity, PluginInput, PluginResult, SchemaVersionRange,
};
use dpp_plugin_sdk::validate::{Validator, num, str_of, threshold_status};
use serde_json::{Value, json};
Expand Down
96 changes: 47 additions & 49 deletions plugins/product-group-battery/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,60 +195,60 @@ impl DppProductGroupPlugin for BatteryPlugin {
Some(date) => {
let (shortfalls, year, standing) =
match art8_phase_for(art8_category, date, art8_second_life(input)) {
Art8Phase::Phase1 => (
art8_shortfalls_2031(&scoped),
"2031",
"binding for this battery",
),
Art8Phase::Phase2 => (
art8_shortfalls_2036(&scoped),
"2036",
"binding for this battery",
),
// In scope, but placed on the market before the phase
// began. Reported as forward-looking guidance rather
// than dropped: useful to know, but not a duty this
// battery carries.
Art8Phase::NotYetBinding => match art8_category {
Art8Category::Lmt => (
Art8Phase::Phase1 => (
art8_shortfalls_2031(&scoped),
"2031",
"binding for this battery",
),
Art8Phase::Phase2 => (
art8_shortfalls_2036(&scoped),
"2036",
"not binding for this battery — Art. 8(3) applies to LMT \
batteries placed on the market from 18 Aug 2036",
"binding for this battery",
),
_ => (
art8_shortfalls_2031(&scoped),
"2031",
"not binding for this battery — Art. 8(2) applies to \
// In scope, but placed on the market before the phase
// began. Reported as forward-looking guidance rather
// than dropped: useful to know, but not a duty this
// battery carries.
Art8Phase::NotYetBinding => match art8_category {
Art8Category::Lmt => (
art8_shortfalls_2036(&scoped),
"2036",
"not binding for this battery — Art. 8(3) applies to LMT \
batteries placed on the market from 18 Aug 2036",
),
_ => (
art8_shortfalls_2031(&scoped),
"2031",
"not binding for this battery — Art. 8(2) applies to \
batteries placed on the market from 18 Aug 2031",
),
},
// Art. 8(4): the paragraphs do not apply at all. No
// shortfall is reportable, and reporting one would
// assert a duty the Regulation removed.
//
// Said out loud rather than left silent. Absence of a
// recycled-content finding is not a statement — it is
// what a battery outside Art. 8, a battery whose shares
// are all met, and a battery nobody assessed all look
// like. An exemption an operator has to infer from
// silence is one they cannot show an authority.
Art8Phase::ExemptSecondLife => {
warnings.push(PluginFinding::new(
"battery.recycled_content.exempt_second_life",
"/batteryStatus",
"EU 2023/1542 Art. 8(4) disapplies the Art. 8(1)-(3) \
),
},
// Art. 8(4): the paragraphs do not apply at all. No
// shortfall is reportable, and reporting one would
// assert a duty the Regulation removed.
//
// Said out loud rather than left silent. Absence of a
// recycled-content finding is not a statement — it is
// what a battery outside Art. 8, a battery whose shares
// are all met, and a battery nobody assessed all look
// like. An exemption an operator has to infer from
// silence is one they cannot show an authority.
Art8Phase::ExemptSecondLife => {
warnings.push(PluginFinding::new(
"battery.recycled_content.exempt_second_life",
"/batteryStatus",
"EU 2023/1542 Art. 8(4) disapplies the Art. 8(1)-(3) \
recycled-content minimums to this battery: batteryStatus \
records a second-life operation, and a battery reaching \
that state was necessarily on the market before it. No \
minimum share is assessed.",
));
(Vec::new(), "", "")
}
// Nothing to say: the category was never in scope, so
// there is no duty whose absence needs explaining.
Art8Phase::NotCovered => (Vec::new(), "", ""),
};
));
(Vec::new(), "", "")
}
// Nothing to say: the category was never in scope, so
// there is no duty whose absence needs explaining.
Art8Phase::NotCovered => (Vec::new(), "", ""),
};
for sf in shortfalls {
let field = match sf.material {
"cobalt" => "/recycledContentCobaltPct",
Expand Down Expand Up @@ -344,9 +344,7 @@ impl DppProductGroupPlugin for BatteryPlugin {
/// error the operator can see and correct now.
fn art8_second_life(input: &PluginInput) -> Art8SecondLife {
match input.get("batteryStatus").and_then(Value::as_str) {
Some("repurposed" | "re-used" | "remanufactured") => {
Art8SecondLife::PlacedBeforeOperations
}
Some("repurposed" | "re-used" | "remanufactured") => Art8SecondLife::PlacedBeforeOperations,
_ => Art8SecondLife::None,
}
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/product-group-detergent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

use dpp_plugin_sdk::export_plugin;
use dpp_plugin_sdk::traits::{
DppProductGroupPlugin, METRIC_CO2E_SCORE, PluginComplianceStatus, PluginError, PluginFieldError,
PluginIdentity, PluginInput, PluginResult, SchemaVersionRange,
DppProductGroupPlugin, METRIC_CO2E_SCORE, PluginComplianceStatus, PluginError,
PluginFieldError, PluginIdentity, PluginInput, PluginResult, SchemaVersionRange,
};
use dpp_plugin_sdk::validate::{Validator, num};
use serde_json::Value;
Expand Down
13 changes: 9 additions & 4 deletions plugins/product-group-electronics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

use dpp_plugin_sdk::export_plugin;
use dpp_plugin_sdk::traits::{
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, METRIC_REPAIRABILITY_INDEX,
PluginComplianceStatus, PluginError, PluginIdentity, PluginInput, PluginResult,
SchemaVersionRange,
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT,
METRIC_REPAIRABILITY_INDEX, PluginComplianceStatus, PluginError, PluginIdentity, PluginInput,
PluginResult, SchemaVersionRange,
};
use dpp_plugin_sdk::validate::{Validator, num, str_of};
use serde_json::Value;
Expand Down Expand Up @@ -44,7 +44,12 @@ impl DppProductGroupPlugin for ElectronicsPlugin {
.require_product_identifier("productIdentifier")
.require_enum(
"productCategory",
&["smartphone", "other-mobile-phone", "cordless-phone", "tablet"],
&[
"smartphone",
"other-mobile-phone",
"cordless-phone",
"tablet",
],
)
.require_enum(
"energyEfficiencyClass",
Expand Down
6 changes: 3 additions & 3 deletions plugins/product-group-furniture/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

use dpp_plugin_sdk::export_plugin;
use dpp_plugin_sdk::traits::{
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, METRIC_REPAIRABILITY_INDEX,
PluginComplianceStatus, PluginError, PluginIdentity, PluginInput, PluginResult,
SchemaVersionRange,
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT,
METRIC_REPAIRABILITY_INDEX, PluginComplianceStatus, PluginError, PluginIdentity, PluginInput,
PluginResult, SchemaVersionRange,
};
use dpp_plugin_sdk::validate::{Validator, num};
use serde_json::Value;
Expand Down
4 changes: 2 additions & 2 deletions plugins/product-group-steel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

use dpp_plugin_sdk::export_plugin;
use dpp_plugin_sdk::traits::{
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, PluginError, PluginIdentity,
PluginInput, PluginResult, SchemaVersionRange,
DppProductGroupPlugin, METRIC_CO2E_SCORE, METRIC_RECYCLED_CONTENT_PCT, PluginError,
PluginIdentity, PluginInput, PluginResult, SchemaVersionRange,
};
use dpp_plugin_sdk::validate::{Validator, num, str_of, threshold_status};
use serde_json::{Value, json};
Expand Down
3 changes: 2 additions & 1 deletion plugins/product-group-textile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ mod unsold_goods;

use dpp_plugin_sdk::export_plugin;
use dpp_plugin_sdk::traits::{
DppProductGroupPlugin, PluginError, PluginIdentity, PluginInput, PluginResult, SchemaVersionRange,
DppProductGroupPlugin, PluginError, PluginIdentity, PluginInput, PluginResult,
SchemaVersionRange,
};
use dpp_plugin_sdk::validate::{Validator, str_of};
use serde_json::Value;
Expand Down
Loading
Loading