@tracepass/dpp-validate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Evaluate a Digital Product Passport against its category field-spec.
3
+ *
4
+ * `evaluateCompliance` takes a passport and its template as plain objects and
5
+ * returns a three-tier verdict with regulation-cited findings. It reads no
6
+ * database and makes no network calls.
7
+ */
8
+ export { evaluateCompliance } from "./verdict.js";
9
+ export { isEuEeaCountry } from "./eu-countries.js";
10
+ export { CONDITIONAL_RULES, CROSS_CUTTING_RULES, } from "./rules.js";
11
+ export { IN_SCOPE_BATTERY_CATEGORIES, BATTERY_FIELD_GATES, GATED_FIELD_KEYS, batteryFieldApplicability, gateForKey, } from "./battery-applicability.js";
12
+ // Re-exported because a passport's publish-readiness and required economic
13
+ // operators are part of judging whether it is compliant, not separate concerns.
14
+ export { checkPublishReady } from "./vendor/publish-gate.js";
15
+ export { getPartyRoles, isRequiredRole, allRolesForCategory, CATEGORY_PARTY_ROLES } from "./vendor/required-roles.js";
16
+ export { derivePassportCounts } from "./vendor/counts.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAWlD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,gBAAgB,EAChB,yBAAyB,EACzB,UAAU,GACX,MAAM,4BAA4B,CAAC;AAGpC,2EAA2E;AAC3E,gFAAgF;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEtH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Per-category CONDITIONAL compliance rules.
3
+ *
4
+ * These encode the conditional obligations that are binding and in force.
5
+ * Three categories carry them — battery, chemicals, construction — plus one
6
+ * cross-cutting rule (CC-1) that applies to every category. The remaining
7
+ * categories have no rule entry here, so the engine reports them
8
+ * `static-only`: their template's required fields are the whole obligation.
9
+ *
10
+ * Rules live in TypeScript rather than a JSON DSL in the templates.
11
+ * Regulatory logic stays in code, reviewed in pull requests, and is
12
+ * deliberately not drift-able by editing a template. Each rule cites its
13
+ * article so the verdict can show provenance.
14
+ *
15
+ * A rule returns findings. `unverifiable_conditional` (warning) is
16
+ * emitted when a rule's TRIGGER field is absent — we never silently
17
+ * skip a mandatory conditional, and never assert compliance on data we
18
+ * couldn't read.
19
+ *
20
+ * Tier discipline: `critical` is reserved for binding, in-force obligations
21
+ * traced to a primary source. A draft or not-yet-dated obligation is a
22
+ * `warning`, never a blocker.
23
+ */
24
+ import type { Passport, Template } from "@tracepass/dpp-types";
25
+ import type { ComplianceFinding } from "./types.js";
26
+ /** A conditional rule: given passport+template, produce zero or more findings. */
27
+ export interface ConditionalRule {
28
+ id: string;
29
+ run(passport: Passport, template: Template): ComplianceFinding[];
30
+ }
31
+ /**
32
+ * Conditional-rule registry. Categories ABSENT from this map have no
33
+ * binding conditionals in force → the engine reports them `static-only`.
34
+ * CC-1 runs for every category and is composed in by the engine, so it
35
+ * is NOT listed per-category here.
36
+ */
37
+ export declare const CONDITIONAL_RULES: Record<string, ConditionalRule[]>;
38
+ /** The cross-cutting rule the engine runs for every category. */
39
+ export declare const CROSS_CUTTING_RULES: ConditionalRule[];
40
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAapD,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,iBAAiB,EAAE,CAAC;CAClE;AAwVD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAI/D,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,EAAE,eAAe,EAAU,CAAC"}
package/dist/rules.js ADDED
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Per-category CONDITIONAL compliance rules.
3
+ *
4
+ * These encode the conditional obligations that are binding and in force.
5
+ * Three categories carry them — battery, chemicals, construction — plus one
6
+ * cross-cutting rule (CC-1) that applies to every category. The remaining
7
+ * categories have no rule entry here, so the engine reports them
8
+ * `static-only`: their template's required fields are the whole obligation.
9
+ *
10
+ * Rules live in TypeScript rather than a JSON DSL in the templates.
11
+ * Regulatory logic stays in code, reviewed in pull requests, and is
12
+ * deliberately not drift-able by editing a template. Each rule cites its
13
+ * article so the verdict can show provenance.
14
+ *
15
+ * A rule returns findings. `unverifiable_conditional` (warning) is
16
+ * emitted when a rule's TRIGGER field is absent — we never silently
17
+ * skip a mandatory conditional, and never assert compliance on data we
18
+ * couldn't read.
19
+ *
20
+ * Tier discipline: `critical` is reserved for binding, in-force obligations
21
+ * traced to a primary source. A draft or not-yet-dated obligation is a
22
+ * `warning`, never a blocker.
23
+ */
24
+ import { isEuEeaCountry } from "./eu-countries.js";
25
+ import { BATTERY_FIELD_GATES, batteryFieldApplicability } from "./battery-applicability.js";
26
+ /** A field counts as "present" when it exists with a non-empty value. */
27
+ function hasValue(passport, key) {
28
+ const f = passport.fields[key];
29
+ return !!f && f.value !== null && f.value !== undefined && f.value !== "";
30
+ }
31
+ /** Read a field's raw value (or undefined when absent/empty). */
32
+ function valueOf(passport, key) {
33
+ return hasValue(passport, key) ? passport.fields[key].value : undefined;
34
+ }
35
+ // ── CC-1 · cross-cutting EU economic-operator rule ──────────────────
36
+ // Reg (EU) 2019/1020 Art. 4(1)+(2): a product may be placed on the
37
+ // market only if there is an EU-established operator. When the
38
+ // manufacturer is non-EU, that operator must be one of importer / AR /
39
+ // fulfilment service provider. This is GROUPED (any one satisfies) —
40
+ // NOT "AR required". We have no fulfilmentServiceProvider role, so the
41
+ // satisfying set is {importer, authorisedRepresentative}; if neither is
42
+ // present we warn (not critical) and point at the FSP escape hatch, to
43
+ // avoid a false-positive on a legitimately FSP-only arrangement.
44
+ const CC1 = {
45
+ id: "CC-1",
46
+ run(passport) {
47
+ const mfr = passport.parties?.manufacturer;
48
+ // Trigger field absent → can't evaluate. Warn, don't assert.
49
+ if (!mfr || !mfr.country) {
50
+ return [
51
+ {
52
+ type: "unverifiable_conditional",
53
+ severity: "warning",
54
+ target: "manufacturer.country",
55
+ regulation: "(EU) 2019/1020",
56
+ article: "Art. 4",
57
+ ruleId: "CC-1",
58
+ why: "Manufacturer country is not set, so the EU economic-operator requirement couldn't be evaluated.",
59
+ fix: "Set the manufacturer party's country so compliance with Art. 4 can be confirmed.",
60
+ },
61
+ ];
62
+ }
63
+ if (isEuEeaCountry(mfr.country))
64
+ return []; // EU/EEA manufacturer satisfies Art. 4 directly.
65
+ const satisfied = !!passport.parties?.importer || !!passport.parties?.authorisedRepresentative;
66
+ if (satisfied)
67
+ return [];
68
+ return [
69
+ {
70
+ type: "conditional_missing",
71
+ severity: "warning",
72
+ target: "importer|authorisedRepresentative",
73
+ regulation: "(EU) 2019/1020",
74
+ article: "Art. 4(1)(2)",
75
+ ruleId: "CC-1",
76
+ why: `Manufacturer is established outside the EU/EEA (${mfr.country}); EU placing on market requires an EU-established operator.`,
77
+ fix: "Add an importer OR an authorised representative (or record a fulfilment service provider) established in the EU.",
78
+ },
79
+ ];
80
+ },
81
+ };
82
+ // ── Battery · Reg (EU) 2023/1542 ────────────────────────────────────
83
+ // BAT-1: battery passport required IF category ∈ {LMT, EV, industrial>2kWh}.
84
+ // The template's `batteryCategory` enum already encodes the scope split
85
+ // (LMT, EV, industrial_gt_2kwh vs industrial_lte_2kwh, SLI, portable),
86
+ // so the ">2 kWh" line is an enum check, not kWh math.
87
+ const IN_SCOPE_BATTERY = new Set(["LMT", "EV", "industrial_gt_2kwh"]);
88
+ // Fields the passport must carry once it's an in-scope battery. Kept tight —
89
+ // only the identifiers Art. 77 hangs the passport on; broader field
90
+ // completeness is the static tier's job.
91
+ const BATTERY_PASSPORT_FIELDS = ["batteryUniqueIdentifier"];
92
+ const BAT1 = {
93
+ id: "BAT-1",
94
+ run(passport) {
95
+ const cat = valueOf(passport, "batteryCategory");
96
+ if (cat === undefined) {
97
+ return [
98
+ {
99
+ type: "unverifiable_conditional",
100
+ severity: "warning",
101
+ target: "batteryCategory",
102
+ regulation: "(EU) 2023/1542",
103
+ article: "Art. 77(1)",
104
+ ruleId: "BAT-1",
105
+ why: "Battery category isn't set, so battery-passport scope (LMT / EV / industrial >2 kWh) couldn't be evaluated.",
106
+ fix: "Set batteryCategory. Portable and SLI batteries are out of scope; LMT, EV and industrial >2 kWh require a battery passport.",
107
+ },
108
+ ];
109
+ }
110
+ // Out-of-scope categories (portable, SLI, industrial_lte_2kwh) → no obligation.
111
+ if (!IN_SCOPE_BATTERY.has(String(cat)))
112
+ return [];
113
+ const findings = [];
114
+ for (const key of BATTERY_PASSPORT_FIELDS) {
115
+ if (!hasValue(passport, key)) {
116
+ findings.push({
117
+ type: "conditional_missing",
118
+ severity: "critical",
119
+ target: key,
120
+ regulation: "(EU) 2023/1542",
121
+ article: "Art. 77",
122
+ ruleId: "BAT-1",
123
+ why: `This is an in-scope battery (${String(cat)}); a battery passport with its unique identifier is mandatory.`,
124
+ fix: `Provide ${key} — the battery passport's unique identifier (GS1 Digital Link).`,
125
+ });
126
+ }
127
+ }
128
+ return findings;
129
+ },
130
+ };
131
+ // ── Battery · field-applicability (deck 27 May 2026, slide 36) ──────
132
+ // BAT-APP: not every Annex XIII field applies to every battery. The
133
+ // shared predicate (battery-applicability.ts) decides per field from
134
+ // batteryCategory + the batteryProfile classifiers. This rule turns that
135
+ // into findings WITHOUT ever blocking:
136
+ // - a field that's `not_applicable` but FILLED → warning (likely
137
+ // mis-entered or the battery is mis-classified). Never critical —
138
+ // extra data isn't a compliance failure, just a flag.
139
+ // - a gate whose trigger is absent (`unknown`) AND the dependent field
140
+ // is filled → unverifiable_conditional warning (can't confirm the
141
+ // field belongs; ask for the missing classifier).
142
+ // We deliberately do NOT emit "missing applicable field" here — required-
143
+ // field completeness is the static tier's job; this rule is purely about
144
+ // applicability, so it stays additive and can't double-count.
145
+ const BAT_APP = {
146
+ id: "BAT-APP",
147
+ run(passport) {
148
+ const findings = [];
149
+ // Reuse the shared predicate so the verdict + editor read applicability
150
+ // through ONE path (incl. the "pending AI flag = unknown" safety rule).
151
+ const applicability = batteryFieldApplicability(passport, "battery");
152
+ for (const gate of BATTERY_FIELD_GATES) {
153
+ // Every key in a gate shares the gate's verdict; sample the first.
154
+ const verdict = applicability[gate.keys[0]];
155
+ if (verdict === "applies")
156
+ continue;
157
+ for (const key of gate.keys) {
158
+ const filled = hasValue(passport, key);
159
+ if (verdict === "not_applicable" && filled) {
160
+ findings.push({
161
+ type: "invalid_format",
162
+ severity: "warning",
163
+ target: key,
164
+ regulation: "(EU) 2023/1542",
165
+ article: gate.article,
166
+ ruleId: "BAT-APP",
167
+ why: `${key} is filled but doesn't apply to this battery — ${gate.reason}`,
168
+ fix: `Remove ${key}, or correct the battery classification (batteryCategory / battery profile) if this field should apply.`,
169
+ });
170
+ }
171
+ else if (verdict === "unknown" && filled) {
172
+ findings.push({
173
+ type: "unverifiable_conditional",
174
+ severity: "warning",
175
+ target: key,
176
+ regulation: "(EU) 2023/1542",
177
+ article: gate.article,
178
+ ruleId: "BAT-APP",
179
+ why: `${key} is filled, but whether it applies couldn't be confirmed — ${gate.reason}`,
180
+ fix: "Set the battery classification (has-BMS / rechargeable / external-storage) so applicability can be confirmed.",
181
+ });
182
+ }
183
+ }
184
+ }
185
+ return findings;
186
+ },
187
+ };
188
+ // ── Battery · value validation (not just presence) ──────────────────
189
+ // BAT-VAL: the static tier + BAT-1 check fields are PRESENT; this rule
190
+ // checks the values that ARE present are PLAUSIBLE. All warnings, never
191
+ // blocking — a malformed value is a data-quality flag for the reviewer,
192
+ // not a hard compliance failure (the reviewer owns the value per the
193
+ // "AI suggests, human approves" product stance). Two kinds of check:
194
+ // (1) numeric-range: percentages must be 0–100; carbon footprints ≥ 0.
195
+ // (2) correlated-requirement: a recorded hazardous substance engages
196
+ // the REACH/SCIP notification obligation, so scipNotificationNumber
197
+ // should be present.
198
+ // Absent fields are skipped here (presence is BAT-1 / the static tier's
199
+ // job) so this rule stays additive and can't double-count.
200
+ /** Parse a field value as a finite number, or undefined if not numeric. */
201
+ function numberOf(passport, key) {
202
+ if (!hasValue(passport, key))
203
+ return undefined;
204
+ const raw = passport.fields[key].value;
205
+ const n = typeof raw === "number" ? raw : Number(raw);
206
+ return Number.isFinite(n) ? n : undefined;
207
+ }
208
+ // Percentage fields that must sit within 0–100 when present.
209
+ const BATTERY_PERCENT_FIELDS = [
210
+ "recycledContentCobalt",
211
+ "recycledContentLithium",
212
+ "recycledContentNickel",
213
+ "recycledContentLead",
214
+ "stateOfHealth",
215
+ "stateOfCertifiedEnergy",
216
+ ];
217
+ // Carbon-footprint fields that must be ≥ 0 when present (kg CO2e/kWh).
218
+ const BATTERY_NONNEGATIVE_FIELDS = [
219
+ "carbonFootprintTotal",
220
+ "cfRawMaterialAcquisition",
221
+ "cfMainProductProduction",
222
+ "cfDistribution",
223
+ "cfEndOfLifeRecycling",
224
+ ];
225
+ const BAT_VAL = {
226
+ id: "BAT-VAL",
227
+ run(passport) {
228
+ const findings = [];
229
+ // (1a) percentages in [0, 100].
230
+ for (const key of BATTERY_PERCENT_FIELDS) {
231
+ const n = numberOf(passport, key);
232
+ if (n !== undefined && (n < 0 || n > 100)) {
233
+ findings.push({
234
+ type: "invalid_format",
235
+ severity: "warning",
236
+ target: key,
237
+ regulation: "(EU) 2023/1542",
238
+ article: "Annex XIII",
239
+ ruleId: "BAT-VAL",
240
+ why: `${key} is ${n}, outside the valid 0–100% range.`,
241
+ fix: `Correct ${key} to a percentage between 0 and 100.`,
242
+ });
243
+ }
244
+ }
245
+ // (1b) carbon-footprint values ≥ 0.
246
+ for (const key of BATTERY_NONNEGATIVE_FIELDS) {
247
+ const n = numberOf(passport, key);
248
+ if (n !== undefined && n < 0) {
249
+ findings.push({
250
+ type: "invalid_format",
251
+ severity: "warning",
252
+ target: key,
253
+ regulation: "(EU) 2023/1542",
254
+ article: "Art. 7",
255
+ ruleId: "BAT-VAL",
256
+ why: `${key} is ${n}; a carbon-footprint value cannot be negative.`,
257
+ fix: `Correct ${key} to a non-negative value (kg CO2e/kWh).`,
258
+ });
259
+ }
260
+ }
261
+ // (2) correlated requirement: hazardous substance recorded ⇒ SCIP
262
+ // notification expected. Skipped entirely when no hazardous substance
263
+ // is recorded (obligation not engaged) — so it never fires spuriously.
264
+ const hazardous = valueOf(passport, "hazardousSubstances");
265
+ const hazardousList = Array.isArray(hazardous)
266
+ ? hazardous
267
+ : hazardous !== undefined
268
+ ? [hazardous]
269
+ : [];
270
+ if (hazardousList.length > 0 && !hasValue(passport, "scipNotificationNumber")) {
271
+ findings.push({
272
+ type: "conditional_missing",
273
+ severity: "warning",
274
+ target: "scipNotificationNumber",
275
+ regulation: "(EC) 1907/2006",
276
+ article: "WFD Art. 9(1)(i)",
277
+ ruleId: "BAT-VAL",
278
+ why: "A hazardous substance is recorded, which engages the SCIP notification obligation, but no SCIP notification number is set.",
279
+ fix: "Record scipNotificationNumber for the SCIP database notification covering the recorded hazardous substance(s).",
280
+ });
281
+ }
282
+ return findings;
283
+ },
284
+ };
285
+ // ── Chemicals · REACH Art. 33 + SCIP (WFD Art. 9(1)(i)) ─────────────
286
+ // CHEM-1: SVHC disclosure required IF any Candidate-List SVHC present
287
+ // >0.1% w/w. The trigger is composition data the passport may not carry
288
+ // machine-readably — when `svhcSubstances` is absent we cannot tell
289
+ // whether the threshold is crossed, so we WARN (verify manually) rather
290
+ // than assert compliance. When the array is present and non-empty, the
291
+ // disclosure obligation is engaged → require the safe-use disclosure.
292
+ const CHEM1 = {
293
+ id: "CHEM-1",
294
+ run(passport) {
295
+ const svhc = valueOf(passport, "svhcSubstances");
296
+ if (svhc === undefined) {
297
+ return [
298
+ {
299
+ type: "unverifiable_conditional",
300
+ severity: "warning",
301
+ target: "svhcSubstances",
302
+ regulation: "(EC) 1907/2006",
303
+ article: "Art. 33",
304
+ ruleId: "CHEM-1",
305
+ why: "SVHC content isn't recorded, so REACH Art. 33 / SCIP disclosure (triggered above 0.1% w/w) couldn't be evaluated.",
306
+ fix: "Record svhcSubstances. If any Candidate-List substance exceeds 0.1% w/w, safe-use info and a SCIP notification are required.",
307
+ },
308
+ ];
309
+ }
310
+ // Empty array → declared no SVHC above threshold → obligation not engaged.
311
+ const list = Array.isArray(svhc) ? svhc : [svhc];
312
+ if (list.length === 0)
313
+ return [];
314
+ // SVHC present → the safe-use disclosure (substance name min.) must be set.
315
+ if (!hasValue(passport, "svhcSubstanceName")) {
316
+ return [
317
+ {
318
+ type: "conditional_missing",
319
+ severity: "critical",
320
+ target: "svhcSubstanceName",
321
+ regulation: "(EC) 1907/2006",
322
+ article: "Art. 33(1)",
323
+ ruleId: "CHEM-1",
324
+ why: "A Candidate-List SVHC is recorded; Art. 33 requires safe-use information including, as a minimum, the substance name.",
325
+ fix: "Provide svhcSubstanceName and ensure a SCIP notification reference (WFD Art. 9(1)(i)) is recorded.",
326
+ },
327
+ ];
328
+ }
329
+ return [];
330
+ },
331
+ };
332
+ // ── Construction · Reg (EU) 2024/3110 (new CPR) ─────────────────────
333
+ // CON-1: a Declaration of Performance & Conformity is required IF the
334
+ // product is covered by a harmonised technical specification. The
335
+ // template's `harmonizedStandardReference` (required) signals coverage;
336
+ // when it's set, the CE-marking / declaration evidence must follow.
337
+ // NB: the CPR's *DPP* provisions are NOT in force (future delegated
338
+ // acts) — only this DoP/DoC conditional is binding today.
339
+ const CON1 = {
340
+ id: "CON-1",
341
+ run(passport) {
342
+ const hsRef = valueOf(passport, "harmonizedStandardReference");
343
+ // No harmonised spec referenced → DoP/DoC obligation not engaged here.
344
+ if (hsRef === undefined)
345
+ return [];
346
+ if (!hasValue(passport, "ceMarkingStatus")) {
347
+ return [
348
+ {
349
+ type: "conditional_missing",
350
+ severity: "critical",
351
+ target: "ceMarkingStatus",
352
+ regulation: "(EU) 2024/3110",
353
+ article: "DoP/DoC",
354
+ ruleId: "CON-1",
355
+ why: "Product is covered by a harmonised technical specification; a Declaration of Performance & Conformity (CE marking) is required.",
356
+ fix: "Record ceMarkingStatus and the declaration evidence for the referenced harmonised standard.",
357
+ },
358
+ ];
359
+ }
360
+ return [];
361
+ },
362
+ };
363
+ /**
364
+ * Conditional-rule registry. Categories ABSENT from this map have no
365
+ * binding conditionals in force → the engine reports them `static-only`.
366
+ * CC-1 runs for every category and is composed in by the engine, so it
367
+ * is NOT listed per-category here.
368
+ */
369
+ export const CONDITIONAL_RULES = {
370
+ battery: [BAT1, BAT_APP, BAT_VAL],
371
+ chemicals: [CHEM1],
372
+ construction: [CON1],
373
+ };
374
+ /** The cross-cutting rule the engine runs for every category. */
375
+ export const CROSS_CUTTING_RULES = [CC1];
376
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Types for the compliance-verdict engine.
3
+ *
4
+ * The engine answers, for one passport: "is this DPP compliant, and if not,
5
+ * what's missing — citing the regulation?" It composes three tiers of check:
6
+ *
7
+ * 1. STATIC — required template fields present + approved, required
8
+ * economic-operator parties present, filled values well-
9
+ * formed (pattern/enum/bounds). Applies to every category.
10
+ * 2. CONDITIONAL — per-category rules that only fire under a condition
11
+ * (e.g. battery passport required IF category is EV/LMT/
12
+ * industrial>2kWh; EU operator required IF manufacturer
13
+ * non-EU). Three categories carry binding conditionals —
14
+ * battery, chemicals, construction — plus one cross-cutting
15
+ * rule that applies to every category.
16
+ * 3. COVERAGE — the engine self-reports whether conditionals were
17
+ * evaluated or the category is static-only, so an agent
18
+ * NEVER reads silence as "compliant".
19
+ *
20
+ * Severity → verdict:
21
+ * - any `critical` → "incomplete"
22
+ * - else any `warning` → "compliant_with_warnings"
23
+ * - else → "compliant"
24
+ */
25
+ export type ComplianceVerdict = "compliant" | "compliant_with_warnings" | "incomplete";
26
+ /** Did per-category conditional rules actually run for this passport's
27
+ * category, or does no binding conditional exist yet (static-only)? */
28
+ export type ConditionalCoverage = "evaluated" | "static-only";
29
+ export type FindingSeverity = "critical" | "warning";
30
+ export type FindingType =
31
+ /** A required template field has no value (static tier). */
32
+ "missing_field"
33
+ /** A field has a value but isn't approved yet (static tier). */
34
+ | "unapproved_field"
35
+ /** A required economic-operator party isn't set (static tier). */
36
+ | "missing_party"
37
+ /** A filled value violates the template's pattern/enum/bounds (static tier). */
38
+ | "invalid_format"
39
+ /** A conditional rule fired and its required field/party is missing. */
40
+ | "conditional_missing"
41
+ /** A conditional rule should apply but its trigger field is absent, so the
42
+ * engine couldn't evaluate it — surfaced as a warning, never silent. */
43
+ | "unverifiable_conditional";
44
+ export interface ComplianceFinding {
45
+ type: FindingType;
46
+ severity: FindingSeverity;
47
+ /** Field key or party role this finding concerns, when applicable. */
48
+ target?: string;
49
+ /** Regulation number, e.g. "(EU) 2023/1542". Present for conditional
50
+ * findings and for static findings whose field carries a regulationRef. */
51
+ regulation?: string;
52
+ /** Article / annex citation, e.g. "Art. 77" / "Annex VI". */
53
+ article?: string;
54
+ /** Short rule id from the verified spec (e.g. "BAT-1", "CC-1"). */
55
+ ruleId?: string;
56
+ /** One-line human explanation of why this matters. */
57
+ why: string;
58
+ /** One-line, actionable next step for the agent / user. */
59
+ fix?: string;
60
+ }
61
+ export interface ComplianceResult {
62
+ verdict: ComplianceVerdict;
63
+ category: string;
64
+ conditionalCoverage: ConditionalCoverage;
65
+ /** Findings that force "incomplete". */
66
+ critical: ComplianceFinding[];
67
+ /** Findings that downgrade to "compliant_with_warnings" but don't block. */
68
+ warnings: ComplianceFinding[];
69
+ /** Rule ids that actually ran (static checks + every conditional rule
70
+ * evaluated). Lets an agent see WHAT was checked, not just the result. */
71
+ checkedRules: string[];
72
+ /** Completion metric, mirrors the passport's approved/total field ratio. */
73
+ completionPercentage: number;
74
+ }
75
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,yBAAyB,GACzB,YAAY,CAAC;AAEjB;wEACwE;AACxE,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,aAAa,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,SAAS,CAAC;AAErD,MAAM,MAAM,WAAW;AACrB,4DAA4D;AAC1D,eAAe;AACjB,gEAAgE;GAC9D,kBAAkB;AACpB,kEAAkE;GAChE,eAAe;AACjB,gFAAgF;GAC9E,gBAAgB;AAClB,wEAAwE;GACtE,qBAAqB;AACvB;yEACyE;GACvE,0BAA0B,CAAC;AAE/B,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;gFAC4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,wCAAwC;IACxC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B;+EAC2E;IAC3E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,4EAA4E;IAC5E,oBAAoB,EAAE,MAAM,CAAC;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Types for the compliance-verdict engine.
3
+ *
4
+ * The engine answers, for one passport: "is this DPP compliant, and if not,
5
+ * what's missing — citing the regulation?" It composes three tiers of check:
6
+ *
7
+ * 1. STATIC — required template fields present + approved, required
8
+ * economic-operator parties present, filled values well-
9
+ * formed (pattern/enum/bounds). Applies to every category.
10
+ * 2. CONDITIONAL — per-category rules that only fire under a condition
11
+ * (e.g. battery passport required IF category is EV/LMT/
12
+ * industrial>2kWh; EU operator required IF manufacturer
13
+ * non-EU). Three categories carry binding conditionals —
14
+ * battery, chemicals, construction — plus one cross-cutting
15
+ * rule that applies to every category.
16
+ * 3. COVERAGE — the engine self-reports whether conditionals were
17
+ * evaluated or the category is static-only, so an agent
18
+ * NEVER reads silence as "compliant".
19
+ *
20
+ * Severity → verdict:
21
+ * - any `critical` → "incomplete"
22
+ * - else any `warning` → "compliant_with_warnings"
23
+ * - else → "compliant"
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Pure helpers for rolling passport-field status counts up into the
3
+ * `fieldCounts` and `completionPercentage` denormalized on the passport row.
4
+ *
5
+ * The same derivation logic was duplicated across the field edit, batch edit,
6
+ * review, and agent-pipeline paths — getting it in one place means the number
7
+ * the UI shows is always consistent no matter which writer touched the fields
8
+ * last.
9
+ */
10
+ import type { FieldStatus } from "@tracepass/dpp-types";
11
+ export interface FieldCounts {
12
+ total: number;
13
+ empty: number;
14
+ pendingReview: number;
15
+ approved: number;
16
+ flagged: number;
17
+ }
18
+ export interface PassportFieldsRollup {
19
+ fieldCounts: FieldCounts;
20
+ completionPercentage: number;
21
+ }
22
+ /** Narrow shape — we only care about the status of each field to roll counts up. */
23
+ export interface CountableField {
24
+ status: FieldStatus;
25
+ }
26
+ /**
27
+ * completionPercentage is explicitly the share of approved fields, not the
28
+ * share of non-empty fields — a field that's pending_review or flagged is
29
+ * *known about* but not yet confirmed, so it doesn't count as done.
30
+ */
31
+ export declare function derivePassportCounts(fields: Record<string, CountableField>): PassportFieldsRollup;
32
+ //# sourceMappingURL=counts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counts.d.ts","sourceRoot":"","sources":["../../src/vendor/counts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,WAAW,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACrC,oBAAoB,CAatB"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pure helpers for rolling passport-field status counts up into the
3
+ * `fieldCounts` and `completionPercentage` denormalized on the passport row.
4
+ *
5
+ * The same derivation logic was duplicated across the field edit, batch edit,
6
+ * review, and agent-pipeline paths — getting it in one place means the number
7
+ * the UI shows is always consistent no matter which writer touched the fields
8
+ * last.
9
+ */
10
+ /**
11
+ * completionPercentage is explicitly the share of approved fields, not the
12
+ * share of non-empty fields — a field that's pending_review or flagged is
13
+ * *known about* but not yet confirmed, so it doesn't count as done.
14
+ */
15
+ export function derivePassportCounts(fields) {
16
+ const values = Object.values(fields);
17
+ const total = values.length;
18
+ const fieldCounts = {
19
+ total,
20
+ empty: values.filter((f) => f.status === "empty").length,
21
+ pendingReview: values.filter((f) => f.status === "pending_review").length,
22
+ approved: values.filter((f) => f.status === "approved").length,
23
+ flagged: values.filter((f) => f.status === "flagged").length,
24
+ };
25
+ const completionPercentage = total > 0 ? Math.round((fieldCounts.approved / total) * 100) : 0;
26
+ return { fieldCounts, completionPercentage };
27
+ }
28
+ //# sourceMappingURL=counts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counts.js","sourceRoot":"","sources":["../../src/vendor/counts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAsC;IAEtC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;IAC5B,MAAM,WAAW,GAAgB;QAC/B,KAAK;QACL,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,MAAM;QACxD,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,gBAAgB,CAAC,CAAC,MAAM;QACzE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,MAAM;QAC9D,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;KAC7D,CAAC;IACF,MAAM,oBAAoB,GACxB,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;AAC/C,CAAC"}