@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.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Pure "can this passport be published?" check, shared between:
3
+ * - GET /api/passports (list response includes publishReady per row)
4
+ * - POST /api/passports/[id]/publish (single-publish eligibility)
5
+ * - POST /api/passports/bulk-publish (skipped-reason messages)
6
+ *
7
+ * One rule, one implementation. If the product policy for publishing
8
+ * changes (e.g. "warnings block", "min coverage %"), change it here.
9
+ */
10
+ import type { Passport, Template } from "@tracepass/dpp-types";
11
+ export interface PublishCheck {
12
+ ready: boolean;
13
+ /** Field keys that have no value set. */
14
+ missingFields: string[];
15
+ /** Field keys with a value but status != approved. */
16
+ unapprovedFields: string[];
17
+ /** Short human reason suitable for a tooltip or toast — undefined when ready. */
18
+ reason?: string;
19
+ /**
20
+ * Distinguishes WHY the passport isn't ready:
21
+ * - "hard" → structural blocker that can't be bypassed
22
+ * (already-published, wrong status, template missing).
23
+ * - "gap" → required fields are missing or unapproved. The UI
24
+ * can offer to publish anyway with an explicit "I
25
+ * understand the risk" acknowledgement.
26
+ * - `undefined` when ready === true.
27
+ */
28
+ blockerType?: "hard" | "gap";
29
+ }
30
+ /**
31
+ * Returns whether `passport` satisfies every publish precondition given
32
+ * its `template`. A passport is publish-ready iff:
33
+ * - status is in PUBLISHABLE_STATUSES (already-published + terminal
34
+ * states return ready:false with a specific reason),
35
+ * - every template field with validation.required has a non-empty
36
+ * value AND status === "approved".
37
+ *
38
+ * `undefined` template means we can't evaluate — treat as not-ready.
39
+ */
40
+ export declare function checkPublishReady(passport: Passport, template: Template | undefined): PublishCheck;
41
+ //# sourceMappingURL=publish-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-gate.d.ts","sourceRoot":"","sources":["../../src/vendor/publish-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,yCAAyC;IACzC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAQD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAC7B,YAAY,CAgEd"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Pure "can this passport be published?" check, shared between:
3
+ * - GET /api/passports (list response includes publishReady per row)
4
+ * - POST /api/passports/[id]/publish (single-publish eligibility)
5
+ * - POST /api/passports/bulk-publish (skipped-reason messages)
6
+ *
7
+ * One rule, one implementation. If the product policy for publishing
8
+ * changes (e.g. "warnings block", "min coverage %"), change it here.
9
+ */
10
+ const PUBLISHABLE_STATUSES = new Set([
11
+ "draft",
12
+ "in_review",
13
+ "approved",
14
+ ]);
15
+ /**
16
+ * Returns whether `passport` satisfies every publish precondition given
17
+ * its `template`. A passport is publish-ready iff:
18
+ * - status is in PUBLISHABLE_STATUSES (already-published + terminal
19
+ * states return ready:false with a specific reason),
20
+ * - every template field with validation.required has a non-empty
21
+ * value AND status === "approved".
22
+ *
23
+ * `undefined` template means we can't evaluate — treat as not-ready.
24
+ */
25
+ export function checkPublishReady(passport, template) {
26
+ if (passport.status === "published") {
27
+ return {
28
+ ready: false,
29
+ missingFields: [],
30
+ unapprovedFields: [],
31
+ reason: "Already published",
32
+ blockerType: "hard",
33
+ };
34
+ }
35
+ if (!PUBLISHABLE_STATUSES.has(passport.status)) {
36
+ return {
37
+ ready: false,
38
+ missingFields: [],
39
+ unapprovedFields: [],
40
+ reason: `Cannot publish from status ${passport.status}`,
41
+ blockerType: "hard",
42
+ };
43
+ }
44
+ if (!template) {
45
+ return {
46
+ ready: false,
47
+ missingFields: [],
48
+ unapprovedFields: [],
49
+ reason: "Template missing",
50
+ blockerType: "hard",
51
+ };
52
+ }
53
+ const missingFields = [];
54
+ const unapprovedFields = [];
55
+ for (const tf of template.fields) {
56
+ if (!tf.validation.required)
57
+ continue;
58
+ const f = passport.fields[tf.key];
59
+ if (!f ||
60
+ f.value === null ||
61
+ f.value === undefined ||
62
+ f.value === "") {
63
+ missingFields.push(tf.key);
64
+ }
65
+ else if (f.status !== "approved") {
66
+ unapprovedFields.push(tf.key);
67
+ }
68
+ }
69
+ if (missingFields.length === 0 && unapprovedFields.length === 0) {
70
+ return { ready: true, missingFields: [], unapprovedFields: [] };
71
+ }
72
+ const parts = [];
73
+ if (missingFields.length > 0)
74
+ parts.push(`${missingFields.length} missing`);
75
+ if (unapprovedFields.length > 0)
76
+ parts.push(`${unapprovedFields.length} not approved`);
77
+ return {
78
+ ready: false,
79
+ missingFields,
80
+ unapprovedFields,
81
+ reason: `Required fields: ${parts.join(", ")}`,
82
+ blockerType: "gap",
83
+ };
84
+ }
85
+ //# sourceMappingURL=publish-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-gate.js","sourceRoot":"","sources":["../../src/vendor/publish-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwBH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAqB;IACvD,OAAO;IACP,WAAW;IACX,UAAU;CACX,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAkB,EAClB,QAA8B;IAE9B,IAAI,QAAQ,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,aAAa,EAAE,EAAE;YACjB,gBAAgB,EAAE,EAAE;YACpB,MAAM,EAAE,mBAAmB;YAC3B,WAAW,EAAE,MAAM;SACpB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/C,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,aAAa,EAAE,EAAE;YACjB,gBAAgB,EAAE,EAAE;YACpB,MAAM,EAAE,8BAA8B,QAAQ,CAAC,MAAM,EAAE;YACvD,WAAW,EAAE,MAAM;SACpB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,aAAa,EAAE,EAAE;YACjB,gBAAgB,EAAE,EAAE;YACpB,MAAM,EAAE,kBAAkB;YAC1B,WAAW,EAAE,MAAM;SACpB,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,gBAAgB,GAAa,EAAE,CAAC;IAEtC,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ;YAAE,SAAS;QACtC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAClC,IACE,CAAC,CAAC;YACF,CAAC,CAAC,KAAK,KAAK,IAAI;YAChB,CAAC,CAAC,KAAK,KAAK,SAAS;YACrB,CAAC,CAAC,KAAK,KAAK,EAAE,EACd,CAAC;YACD,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;aAAM,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACnC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,MAAM,UAAU,CAAC,CAAC;IAC5E,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,MAAM,eAAe,CAAC,CAAC;IAEvF,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,aAAa;QACb,gBAAgB;QAChB,MAAM,EAAE,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC9C,WAAW,EAAE,KAAK;KACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Per-category required and optional party roles.
3
+ *
4
+ * Source of truth for "which parties does this passport need before it can
5
+ * be considered complete." Used by:
6
+ * - the editor UI (red asterisks on required roles, hides irrelevant ones)
7
+ * - the CSV bulk-import column generator (only emits columns for roles
8
+ * this category actually uses)
9
+ * - the publish-precondition check (warns on missing required roles, in
10
+ * parallel to the existing missing-required-fields warning)
11
+ * - the per-passport completionPercentage calculation
12
+ *
13
+ * The TypeScript module is the single source of truth — the role map is
14
+ * NOT mirrored into the templates collection. Adding or removing roles
15
+ * from a category is a code change + deploy, deliberate so we don't drift
16
+ * the regulation interpretation across environments.
17
+ *
18
+ * v1 design calls (per Malin's confirmation):
19
+ * - Chemicals "downstream user" (REACH-specific role) → folded into
20
+ * `distributor` for now; revisit when a chemicals customer pulls.
21
+ * - Tyre "retreader" → ignored for v1; retreaded tyres treated as a
22
+ * separate passport whose `manufacturer` is the retreader.
23
+ *
24
+ * When a category's regulation tightens (a new delegated act lands and
25
+ * adds a mandate), update the entry here and the change ships in the
26
+ * next deploy — no template re-seed needed.
27
+ */
28
+ import type { PartyRole } from "@tracepass/dpp-types";
29
+ /**
30
+ * The 12 categories the platform models. Mirrors the filenames in
31
+ * `templates/*.json`. We re-declare the union here rather than import
32
+ * a CategoryKey from elsewhere so this module stays the canonical
33
+ * place a future contributor looks when adding a 13th category.
34
+ */
35
+ export type CategoryKey = "battery" | "chemicals" | "construction" | "electronics" | "fmcg" | "furniture" | "jewelry" | "packaging" | "steel" | "textile" | "toys" | "tyres";
36
+ export interface CategoryPartyRoles {
37
+ /** Roles that MUST be set before the passport is considered complete.
38
+ * The publish flow warns (not blocks) when these are missing — a
39
+ * user can still publish via the existing "I acknowledge incomplete"
40
+ * ack, which sets `publishedIncomplete: true`. */
41
+ required: readonly PartyRole[];
42
+ /** Roles that the editor surfaces but doesn't require. Order matters —
43
+ * the UI renders them in this order under the required block. */
44
+ optional: readonly PartyRole[];
45
+ }
46
+ /**
47
+ * Per-category role requirements. Each entry's `required` is the
48
+ * regulator's mandate; `optional` is roles that a tenant might
49
+ * legitimately want to record but isn't compelled to.
50
+ */
51
+ export declare const CATEGORY_PARTY_ROLES: Record<CategoryKey, CategoryPartyRoles>;
52
+ /**
53
+ * Get the required + optional role lists for a category. Returns
54
+ * `null` when the category isn't one of the 12 modeled categories,
55
+ * so callers can early-return rather than crash on legacy / unknown
56
+ * category strings.
57
+ */
58
+ export declare function getPartyRoles(category: string): CategoryPartyRoles | null;
59
+ /**
60
+ * True when `role` is required for the given category. Used by the
61
+ * publish-precondition check + the editor's red-asterisk render.
62
+ * Unknown categories return false (don't block on what we can't
63
+ * interpret).
64
+ */
65
+ export declare function isRequiredRole(category: string, role: PartyRole): boolean;
66
+ /**
67
+ * Combined role list (required first, then optional, in declaration
68
+ * order). Returns `[]` for unknown categories. Convenience for UI
69
+ * iteration when both sets need to render.
70
+ */
71
+ export declare function allRolesForCategory(category: string): readonly PartyRole[];
72
+ //# sourceMappingURL=required-roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"required-roles.d.ts","sourceRoot":"","sources":["../../src/vendor/required-roles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,WAAW,GACX,cAAc,GACd,aAAa,GACb,MAAM,GACN,WAAW,GACX,SAAS,GACT,WAAW,GACX,OAAO,GACP,SAAS,GACT,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,WAAW,kBAAkB;IACjC;;;uDAGmD;IACnD,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC;IAC/B;sEACkE;IAClE,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC;CAChC;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,WAAW,EAAE,kBAAkB,CA2F/D,CAAC;AAEX;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,GACf,kBAAkB,GAAG,IAAI,CAE3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAI1E"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Per-category required and optional party roles.
3
+ *
4
+ * Source of truth for "which parties does this passport need before it can
5
+ * be considered complete." Used by:
6
+ * - the editor UI (red asterisks on required roles, hides irrelevant ones)
7
+ * - the CSV bulk-import column generator (only emits columns for roles
8
+ * this category actually uses)
9
+ * - the publish-precondition check (warns on missing required roles, in
10
+ * parallel to the existing missing-required-fields warning)
11
+ * - the per-passport completionPercentage calculation
12
+ *
13
+ * The TypeScript module is the single source of truth — the role map is
14
+ * NOT mirrored into the templates collection. Adding or removing roles
15
+ * from a category is a code change + deploy, deliberate so we don't drift
16
+ * the regulation interpretation across environments.
17
+ *
18
+ * v1 design calls (per Malin's confirmation):
19
+ * - Chemicals "downstream user" (REACH-specific role) → folded into
20
+ * `distributor` for now; revisit when a chemicals customer pulls.
21
+ * - Tyre "retreader" → ignored for v1; retreaded tyres treated as a
22
+ * separate passport whose `manufacturer` is the retreader.
23
+ *
24
+ * When a category's regulation tightens (a new delegated act lands and
25
+ * adds a mandate), update the entry here and the change ships in the
26
+ * next deploy — no template re-seed needed.
27
+ */
28
+ /**
29
+ * Per-category role requirements. Each entry's `required` is the
30
+ * regulator's mandate; `optional` is roles that a tenant might
31
+ * legitimately want to record but isn't compelled to.
32
+ */
33
+ export const CATEGORY_PARTY_ROLES = {
34
+ // Battery Regulation 2023/1542 Articles 47–50 require all three
35
+ // operators to be identifiable on the passport for serial-level
36
+ // batteries (LMT, EV, industrial >2 kWh).
37
+ battery: {
38
+ required: ["manufacturer", "recycler", "producerResponsibilityOrg"],
39
+ optional: ["importer", "authorisedRepresentative", "distributor"],
40
+ },
41
+ // RoHS / WEEE / RED / EMC — manufacturer is mandatory for any
42
+ // EU placement; AR + importer become mandatory for non-EU
43
+ // manufacturers under Article 4 of the relevant directive, but
44
+ // since we can't tell at schema time whether the manufacturer is
45
+ // EU or not, we keep them optional and let the editor surface
46
+ // them when the manufacturer's `country` is non-EU.
47
+ electronics: {
48
+ required: ["manufacturer"],
49
+ optional: ["importer", "authorisedRepresentative", "distributor", "recycler"],
50
+ },
51
+ // Toy Safety Directive 2009/48 Article 4 — both manufacturer and
52
+ // importer are mandatory for non-EU toys, the most common case.
53
+ toys: {
54
+ required: ["manufacturer", "importer"],
55
+ optional: ["authorisedRepresentative", "distributor"],
56
+ },
57
+ // PPWR 2025/40 Article 11 + national packaging EPR schemes —
58
+ // PRO required where the EPR scheme applies (most EU member states).
59
+ fmcg: {
60
+ required: ["manufacturer", "producerResponsibilityOrg"],
61
+ optional: ["importer", "distributor", "recycler"],
62
+ },
63
+ // PPWR Articles 7 + 45 — packaging materials themselves carry
64
+ // direct PRO obligations.
65
+ packaging: {
66
+ required: ["manufacturer", "producerResponsibilityOrg"],
67
+ optional: ["importer", "distributor", "recycler"],
68
+ },
69
+ // Textile EPR is national (DE, FR, NL have schemes; harmonised
70
+ // EU rules pending). Keep PRO optional until ESPR delegated act
71
+ // mandates it. Manufacturer canonical.
72
+ textile: {
73
+ required: ["manufacturer"],
74
+ optional: ["producerResponsibilityOrg", "importer", "distributor", "recycler"],
75
+ },
76
+ // No EU-level operator mandate beyond CE for upholstered furniture
77
+ // (national flammability rules in IT/UK/FR). Manufacturer canonical.
78
+ furniture: {
79
+ required: ["manufacturer"],
80
+ optional: ["importer", "distributor"],
81
+ },
82
+ // REACH places obligations on manufacturer + importer + downstream
83
+ // user. v1 maps "downstream user" to `distributor` per design call;
84
+ // revisit when a chemicals customer pulls.
85
+ chemicals: {
86
+ required: ["manufacturer"],
87
+ optional: ["importer", "distributor", "authorisedRepresentative"],
88
+ },
89
+ // CPR 2024/3110 — manufacturer mandatory; AR for non-EU
90
+ // construction-product placers.
91
+ construction: {
92
+ required: ["manufacturer"],
93
+ optional: ["importer", "distributor", "authorisedRepresentative"],
94
+ },
95
+ // Tyre Labelling Reg (EU) 2020/740 places obligations on the
96
+ // manufacturer. Retreader role deferred to v2 — see module doc.
97
+ tyres: {
98
+ required: ["manufacturer"],
99
+ optional: ["importer", "distributor"],
100
+ },
101
+ // CBAM places obligations on the importer of CBAM goods, not
102
+ // the steel-mill DPP itself. Manufacturer canonical here.
103
+ steel: {
104
+ required: ["manufacturer"],
105
+ optional: ["importer", "distributor"],
106
+ },
107
+ // REACH SVHC notification — manufacturer canonical; no specific
108
+ // party-mandate beyond that for fine jewellery.
109
+ jewelry: {
110
+ required: ["manufacturer"],
111
+ optional: ["importer", "distributor"],
112
+ },
113
+ };
114
+ /**
115
+ * Get the required + optional role lists for a category. Returns
116
+ * `null` when the category isn't one of the 12 modeled categories,
117
+ * so callers can early-return rather than crash on legacy / unknown
118
+ * category strings.
119
+ */
120
+ export function getPartyRoles(category) {
121
+ return CATEGORY_PARTY_ROLES[category] ?? null;
122
+ }
123
+ /**
124
+ * True when `role` is required for the given category. Used by the
125
+ * publish-precondition check + the editor's red-asterisk render.
126
+ * Unknown categories return false (don't block on what we can't
127
+ * interpret).
128
+ */
129
+ export function isRequiredRole(category, role) {
130
+ const roles = getPartyRoles(category);
131
+ return roles?.required.includes(role) ?? false;
132
+ }
133
+ /**
134
+ * Combined role list (required first, then optional, in declaration
135
+ * order). Returns `[]` for unknown categories. Convenience for UI
136
+ * iteration when both sets need to render.
137
+ */
138
+ export function allRolesForCategory(category) {
139
+ const roles = getPartyRoles(category);
140
+ if (!roles)
141
+ return [];
142
+ return [...roles.required, ...roles.optional];
143
+ }
144
+ //# sourceMappingURL=required-roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"required-roles.js","sourceRoot":"","sources":["../../src/vendor/required-roles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Compliance-verdict engine.
3
+ *
4
+ * Takes a passport, its template, and the category; returns a three-tier
5
+ * verdict (compliant / compliant_with_warnings / incomplete) with
6
+ * regulation-cited findings a caller can act on, then re-check after fixing
7
+ * — the read → fix → verify loop.
8
+ *
9
+ * Pure and IO-free: no database, no network, no clock.
10
+ *
11
+ * Composition:
12
+ * static tier — required fields (checkPublishReady) + required
13
+ * economic operators (required-roles) + format validation
14
+ * conditional tier — CONDITIONAL_RULES[category] + CROSS_CUTTING_RULES
15
+ * coverage — "evaluated" if the category has conditional rules,
16
+ * else "static-only"
17
+ *
18
+ * Each finding cites the article of the instrument that mandates it, taken
19
+ * from the template field's own `regulationRef`.
20
+ */
21
+ import type { Passport, Template } from "@tracepass/dpp-types";
22
+ import type { ComplianceResult } from "./types.js";
23
+ /**
24
+ * Evaluate a passport's compliance. `template` undefined → we can't
25
+ * assess; returns an `incomplete` verdict with a single explanatory
26
+ * critical finding (mirrors checkPublishReady's "template missing" hard
27
+ * blocker rather than throwing).
28
+ */
29
+ export declare function evaluateCompliance(passport: Passport, template: Template | undefined, category: string): ComplianceResult;
30
+ //# sourceMappingURL=verdict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAiB,MAAM,sBAAsB,CAAC;AAK9E,OAAO,KAAK,EAEV,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAiEpB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,QAAQ,EAAE,MAAM,GACf,gBAAgB,CA2GlB"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Compliance-verdict engine.
3
+ *
4
+ * Takes a passport, its template, and the category; returns a three-tier
5
+ * verdict (compliant / compliant_with_warnings / incomplete) with
6
+ * regulation-cited findings a caller can act on, then re-check after fixing
7
+ * — the read → fix → verify loop.
8
+ *
9
+ * Pure and IO-free: no database, no network, no clock.
10
+ *
11
+ * Composition:
12
+ * static tier — required fields (checkPublishReady) + required
13
+ * economic operators (required-roles) + format validation
14
+ * conditional tier — CONDITIONAL_RULES[category] + CROSS_CUTTING_RULES
15
+ * coverage — "evaluated" if the category has conditional rules,
16
+ * else "static-only"
17
+ *
18
+ * Each finding cites the article of the instrument that mandates it, taken
19
+ * from the template field's own `regulationRef`.
20
+ */
21
+ import { checkPublishReady } from "./vendor/publish-gate.js";
22
+ import { getPartyRoles } from "./vendor/required-roles.js";
23
+ import { derivePassportCounts } from "./vendor/counts.js";
24
+ import { CONDITIONAL_RULES, CROSS_CUTTING_RULES } from "./rules.js";
25
+ /** Validate a filled value against a template field's format rules.
26
+ * Returns a finding (warning) on mismatch, or null when clean. Empty
27
+ * values are NOT checked here — presence is the static field check's
28
+ * job; this only judges the SHAPE of values that ARE set. */
29
+ function checkFieldFormat(tf, value) {
30
+ const v = tf.validation;
31
+ const base = {
32
+ type: "invalid_format",
33
+ severity: "warning",
34
+ target: tf.key,
35
+ regulation: undefined,
36
+ // Template JSON stores an absent article as an explicit `null`; findings
37
+ // expose it as `undefined` so consumers only handle one absent case.
38
+ article: tf.regulationRef?.article ?? undefined,
39
+ };
40
+ // enum / multi_enum: value(s) must be among the allowed options.
41
+ if ((tf.dataType === "enum" || tf.dataType === "multi_enum") && tf.enumOptions) {
42
+ const allowed = new Set(tf.enumOptions.map((o) => o.value));
43
+ const vals = Array.isArray(value) ? value : [value];
44
+ const bad = vals.filter((x) => !allowed.has(String(x)));
45
+ if (bad.length > 0) {
46
+ return { ...base, why: `Value "${bad.join(", ")}" is not one of the allowed options for ${tf.key}.`, fix: `Use one of: ${[...allowed].join(", ")}.` };
47
+ }
48
+ }
49
+ // string-shaped checks (string / url). Bounds use `!= null` so an
50
+ // explicit `null` in the template JSON (the seeded shape for "no
51
+ // bound" — not `undefined`) is correctly treated as "no constraint"
52
+ // rather than coercing to 0.
53
+ if (typeof value === "string") {
54
+ if (v.minLength != null && value.length < v.minLength) {
55
+ return { ...base, why: `${tf.key} is shorter than the required ${v.minLength} characters.`, fix: `Provide at least ${v.minLength} characters.` };
56
+ }
57
+ if (v.maxLength != null && value.length > v.maxLength) {
58
+ return { ...base, why: `${tf.key} exceeds the maximum ${v.maxLength} characters.`, fix: `Shorten to at most ${v.maxLength} characters.` };
59
+ }
60
+ if (v.pattern) {
61
+ let re = null;
62
+ try {
63
+ re = new RegExp(v.pattern);
64
+ }
65
+ catch {
66
+ re = null;
67
+ }
68
+ if (re && !re.test(value)) {
69
+ return { ...base, why: `${tf.key} doesn't match the required format.`, fix: `Expected format: ${tf.aiHints?.expectedFormat ?? v.pattern}.` };
70
+ }
71
+ }
72
+ }
73
+ // numeric bounds — `!= null` for the same reason (template carries
74
+ // explicit `min: null` / `max: null`, which must not coerce to 0).
75
+ if (typeof value === "number") {
76
+ if (v.min != null && value < v.min) {
77
+ return { ...base, why: `${tf.key} (${value}) is below the minimum ${v.min}.`, fix: `Use a value ≥ ${v.min}.` };
78
+ }
79
+ if (v.max != null && value > v.max) {
80
+ return { ...base, why: `${tf.key} (${value}) is above the maximum ${v.max}.`, fix: `Use a value ≤ ${v.max}.` };
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ /**
86
+ * Evaluate a passport's compliance. `template` undefined → we can't
87
+ * assess; returns an `incomplete` verdict with a single explanatory
88
+ * critical finding (mirrors checkPublishReady's "template missing" hard
89
+ * blocker rather than throwing).
90
+ */
91
+ export function evaluateCompliance(passport, template, category) {
92
+ const { completionPercentage } = derivePassportCounts(passport.fields);
93
+ if (!template) {
94
+ return {
95
+ verdict: "incomplete",
96
+ category,
97
+ conditionalCoverage: "static-only",
98
+ critical: [
99
+ {
100
+ type: "missing_field",
101
+ severity: "critical",
102
+ why: "No template is available for this passport, so compliance can't be evaluated.",
103
+ fix: "Ensure the passport references a valid category template.",
104
+ },
105
+ ],
106
+ warnings: [],
107
+ checkedRules: [],
108
+ completionPercentage,
109
+ };
110
+ }
111
+ const critical = [];
112
+ const warnings = [];
113
+ const checkedRules = [];
114
+ // ── Static tier 1: required fields (reuse the publish gate) ────────
115
+ checkedRules.push("static:required-fields");
116
+ const publish = checkPublishReady(passport, template);
117
+ const fieldRef = (key) => template.fields.find((f) => f.key === key)?.regulationRef;
118
+ for (const key of publish.missingFields) {
119
+ const ref = fieldRef(key);
120
+ critical.push({
121
+ type: "missing_field",
122
+ severity: "critical",
123
+ target: key,
124
+ regulation: ref ? template.regulation?.number : undefined,
125
+ article: ref?.article ?? undefined,
126
+ why: `Required field "${key}" has no value.`,
127
+ fix: `Provide a value for ${key}.`,
128
+ });
129
+ }
130
+ for (const key of publish.unapprovedFields) {
131
+ critical.push({
132
+ type: "unapproved_field",
133
+ severity: "critical",
134
+ target: key,
135
+ why: `Required field "${key}" has a value but isn't approved yet.`,
136
+ fix: `Review and approve ${key}.`,
137
+ });
138
+ }
139
+ // ── Static tier 2: required economic-operator parties ──────────────
140
+ checkedRules.push("static:required-parties");
141
+ const roles = getPartyRoles(category);
142
+ if (roles) {
143
+ for (const role of roles.required) {
144
+ if (!passport.parties?.[role]) {
145
+ critical.push({
146
+ type: "missing_party",
147
+ severity: "critical",
148
+ target: role,
149
+ why: `Required economic operator "${role}" is not set for a ${category} passport.`,
150
+ fix: `Add the ${role} party (legal name + GS1 GLN where available).`,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ // ── Static tier 3: format of filled values (warnings) ──────────────
156
+ checkedRules.push("static:format");
157
+ for (const tf of template.fields) {
158
+ const f = passport.fields[tf.key];
159
+ if (!f || f.value === null || f.value === undefined || f.value === "")
160
+ continue;
161
+ const bad = checkFieldFormat(tf, f.value);
162
+ if (bad)
163
+ warnings.push(bad);
164
+ }
165
+ // ── Conditional tier: per-category + cross-cutting ─────────────────
166
+ const categoryRules = CONDITIONAL_RULES[category] ?? [];
167
+ const conditionalCoverage = categoryRules.length > 0 ? "evaluated" : "static-only";
168
+ for (const rule of [...categoryRules, ...CROSS_CUTTING_RULES]) {
169
+ checkedRules.push(rule.id);
170
+ for (const finding of rule.run(passport, template)) {
171
+ (finding.severity === "critical" ? critical : warnings).push(finding);
172
+ }
173
+ }
174
+ const verdict = critical.length > 0
175
+ ? "incomplete"
176
+ : warnings.length > 0
177
+ ? "compliant_with_warnings"
178
+ : "compliant";
179
+ return {
180
+ verdict,
181
+ category,
182
+ conditionalCoverage,
183
+ critical,
184
+ warnings,
185
+ checkedRules,
186
+ completionPercentage,
187
+ };
188
+ }
189
+ //# sourceMappingURL=verdict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.js","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@tracepass/dpp-validate",
3
+ "version": "0.1.0",
4
+ "description": "Evaluate an EU Digital Product Passport against its category field-spec — a three-tier compliance verdict with regulation-cited findings. Pure functions, no third-party runtime dependencies.",
5
+ "keywords": ["digital-product-passport", "dpp", "espr", "compliance", "eu-regulation", "battery-passport", "validation", "circular-economy"],
6
+ "license": "Apache-2.0",
7
+ "author": "TracePass LTD (https://tracepass.eu)",
8
+ "homepage": "https://github.com/malinoto/tracepass-open/tree/main/packages/dpp-validate#readme",
9
+ "repository": { "type": "git", "url": "git+https://github.com/malinoto/tracepass-open.git", "directory": "packages/dpp-validate" },
10
+ "bugs": "https://github.com/malinoto/tracepass-open/issues",
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
15
+ "files": ["dist", "README.md", "LICENSE"],
16
+ "sideEffects": false,
17
+ "publishConfig": { "access": "public" },
18
+ "scripts": { "build": "tsc -p tsconfig.build.json", "clean": "rm -rf dist" },
19
+ "dependencies": { "@tracepass/dpp-types": "^0.1.0" },
20
+ "devDependencies": { "typescript": "^5.6.3" },
21
+ "engines": { "node": ">=18" }
22
+ }