@uncefact/untp-utils 0.0.1 → 0.2.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.
Files changed (221) hide show
  1. package/LICENSE +202 -674
  2. package/README.md +23 -1
  3. package/build/artefacts/detect-version-from-context.d.ts +46 -0
  4. package/build/artefacts/detect-version-from-context.js +77 -0
  5. package/build/artefacts/detect-version-from-context.js.map +1 -0
  6. package/build/artefacts/detect-version-from-context.test.d.ts +1 -0
  7. package/build/artefacts/detect-version-from-context.test.js +69 -0
  8. package/build/artefacts/detect-version-from-context.test.js.map +1 -0
  9. package/build/artefacts/index.d.ts +3 -0
  10. package/build/artefacts/index.js +4 -0
  11. package/build/artefacts/index.js.map +1 -0
  12. package/build/artefacts/urls.d.ts +79 -0
  13. package/build/artefacts/urls.js +133 -0
  14. package/build/artefacts/urls.js.map +1 -0
  15. package/build/artefacts/urls.test.d.ts +1 -0
  16. package/build/artefacts/urls.test.js +91 -0
  17. package/build/artefacts/urls.test.js.map +1 -0
  18. package/build/artefacts/version.d.ts +9 -0
  19. package/build/artefacts/version.js +17 -0
  20. package/build/artefacts/version.js.map +1 -0
  21. package/build/artefacts/version.test.d.ts +1 -0
  22. package/build/artefacts/version.test.js +16 -0
  23. package/build/artefacts/version.test.js.map +1 -0
  24. package/build/cache/in-memory-ttl-cache.d.ts +2 -0
  25. package/build/cache/in-memory-ttl-cache.js +76 -0
  26. package/build/cache/in-memory-ttl-cache.js.map +1 -0
  27. package/build/cache/in-memory-ttl-cache.test.d.ts +1 -0
  28. package/build/cache/in-memory-ttl-cache.test.js +236 -0
  29. package/build/cache/in-memory-ttl-cache.test.js.map +1 -0
  30. package/build/cache/index.d.ts +2 -0
  31. package/build/cache/index.js +2 -0
  32. package/build/cache/index.js.map +1 -0
  33. package/build/cache/ttl-cache.d.ts +22 -0
  34. package/build/cache/ttl-cache.js +2 -0
  35. package/build/cache/ttl-cache.js.map +1 -0
  36. package/build/common/as-non-empty-string.d.ts +7 -0
  37. package/build/common/as-non-empty-string.js +10 -0
  38. package/build/common/as-non-empty-string.js.map +1 -0
  39. package/build/common/as-non-empty-string.test.d.ts +1 -0
  40. package/build/common/as-non-empty-string.test.js +25 -0
  41. package/build/common/as-non-empty-string.test.js.map +1 -0
  42. package/build/common/require-string.d.ts +12 -0
  43. package/build/common/require-string.js +27 -0
  44. package/build/common/require-string.js.map +1 -0
  45. package/build/common/require-string.test.d.ts +1 -0
  46. package/build/common/require-string.test.js +53 -0
  47. package/build/common/require-string.test.js.map +1 -0
  48. package/build/conformity-vocabulary/codes.d.ts +10 -0
  49. package/build/conformity-vocabulary/codes.js +10 -0
  50. package/build/conformity-vocabulary/codes.js.map +1 -0
  51. package/build/conformity-vocabulary/errors.d.ts +44 -0
  52. package/build/conformity-vocabulary/errors.js +68 -0
  53. package/build/conformity-vocabulary/errors.js.map +1 -0
  54. package/build/conformity-vocabulary/index.d.ts +6 -0
  55. package/build/conformity-vocabulary/index.js +7 -0
  56. package/build/conformity-vocabulary/index.js.map +1 -0
  57. package/build/conformity-vocabulary/parse-conformity-catalogue.d.ts +26 -0
  58. package/build/conformity-vocabulary/parse-conformity-catalogue.js +106 -0
  59. package/build/conformity-vocabulary/parse-conformity-catalogue.js.map +1 -0
  60. package/build/conformity-vocabulary/parse-conformity-catalogue.test.d.ts +1 -0
  61. package/build/conformity-vocabulary/parse-conformity-catalogue.test.js +327 -0
  62. package/build/conformity-vocabulary/parse-conformity-catalogue.test.js.map +1 -0
  63. package/build/conformity-vocabulary/parse-conformity-scheme.d.ts +28 -0
  64. package/build/conformity-vocabulary/parse-conformity-scheme.js +47 -0
  65. package/build/conformity-vocabulary/parse-conformity-scheme.js.map +1 -0
  66. package/build/conformity-vocabulary/parse-conformity-scheme.test.d.ts +1 -0
  67. package/build/conformity-vocabulary/parse-conformity-scheme.test.js +284 -0
  68. package/build/conformity-vocabulary/parse-conformity-scheme.test.js.map +1 -0
  69. package/build/conformity-vocabulary/parsers/v0-7-0.parser.d.ts +19 -0
  70. package/build/conformity-vocabulary/parsers/v0-7-0.parser.js +219 -0
  71. package/build/conformity-vocabulary/parsers/v0-7-0.parser.js.map +1 -0
  72. package/build/conformity-vocabulary/types.d.ts +180 -0
  73. package/build/conformity-vocabulary/types.js +2 -0
  74. package/build/conformity-vocabulary/types.js.map +1 -0
  75. package/build/conformity-vocabulary/validate-conformity-claim.d.ts +26 -0
  76. package/build/conformity-vocabulary/validate-conformity-claim.js +165 -0
  77. package/build/conformity-vocabulary/validate-conformity-claim.js.map +1 -0
  78. package/build/conformity-vocabulary/validate-conformity-claim.test.d.ts +1 -0
  79. package/build/conformity-vocabulary/validate-conformity-claim.test.js +478 -0
  80. package/build/conformity-vocabulary/validate-conformity-claim.test.js.map +1 -0
  81. package/build/http-headers/index.d.ts +5 -0
  82. package/build/http-headers/index.js +6 -0
  83. package/build/http-headers/index.js.map +1 -0
  84. package/build/http-headers/is-safe-header-value.d.ts +10 -0
  85. package/build/http-headers/is-safe-header-value.js +15 -0
  86. package/build/http-headers/is-safe-header-value.js.map +1 -0
  87. package/build/http-headers/is-safe-header-value.test.d.ts +1 -0
  88. package/build/http-headers/is-safe-header-value.test.js +37 -0
  89. package/build/http-headers/is-safe-header-value.test.js.map +1 -0
  90. package/build/http-headers/parse-entity-tag.d.ts +9 -0
  91. package/build/http-headers/parse-entity-tag.js +18 -0
  92. package/build/http-headers/parse-entity-tag.js.map +1 -0
  93. package/build/http-headers/parse-entity-tag.test.d.ts +1 -0
  94. package/build/http-headers/parse-entity-tag.test.js +41 -0
  95. package/build/http-headers/parse-entity-tag.test.js.map +1 -0
  96. package/build/http-headers/parse-imf-date.d.ts +13 -0
  97. package/build/http-headers/parse-imf-date.js +23 -0
  98. package/build/http-headers/parse-imf-date.js.map +1 -0
  99. package/build/http-headers/parse-imf-date.test.d.ts +1 -0
  100. package/build/http-headers/parse-imf-date.test.js +31 -0
  101. package/build/http-headers/parse-imf-date.test.js.map +1 -0
  102. package/build/http-headers/parse-media-type.d.ts +8 -0
  103. package/build/http-headers/parse-media-type.js +21 -0
  104. package/build/http-headers/parse-media-type.js.map +1 -0
  105. package/build/http-headers/parse-media-type.test.d.ts +1 -0
  106. package/build/http-headers/parse-media-type.test.js +39 -0
  107. package/build/http-headers/parse-media-type.test.js.map +1 -0
  108. package/build/http-headers/user-agent.d.ts +35 -0
  109. package/build/http-headers/user-agent.js +45 -0
  110. package/build/http-headers/user-agent.js.map +1 -0
  111. package/build/http-headers/user-agent.test.d.ts +1 -0
  112. package/build/http-headers/user-agent.test.js +27 -0
  113. package/build/http-headers/user-agent.test.js.map +1 -0
  114. package/build/index.d.ts +1 -1
  115. package/build/index.js +1 -1
  116. package/build/index.js.map +1 -1
  117. package/build/loaders/errors.d.ts +21 -0
  118. package/build/loaders/errors.js +45 -0
  119. package/build/loaders/errors.js.map +1 -0
  120. package/build/loaders/index.d.ts +3 -0
  121. package/build/loaders/index.js +4 -0
  122. package/build/loaders/index.js.map +1 -0
  123. package/build/loaders/jsonld-document-loader.d.ts +46 -0
  124. package/build/loaders/jsonld-document-loader.js +34 -0
  125. package/build/loaders/jsonld-document-loader.js.map +1 -0
  126. package/build/loaders/jsonld-document-loader.test.d.ts +1 -0
  127. package/build/loaders/jsonld-document-loader.test.js +71 -0
  128. package/build/loaders/jsonld-document-loader.test.js.map +1 -0
  129. package/build/loaders/schema-loader.d.ts +21 -0
  130. package/build/loaders/schema-loader.js +67 -0
  131. package/build/loaders/schema-loader.js.map +1 -0
  132. package/build/loaders/schema-loader.ssrf.test.d.ts +1 -0
  133. package/build/loaders/schema-loader.ssrf.test.js +55 -0
  134. package/build/loaders/schema-loader.ssrf.test.js.map +1 -0
  135. package/build/loaders/schema-loader.test.d.ts +1 -0
  136. package/build/loaders/schema-loader.test.js +162 -0
  137. package/build/loaders/schema-loader.test.js.map +1 -0
  138. package/build/multibase-digest/multibase-digest.d.ts +23 -0
  139. package/build/multibase-digest/multibase-digest.js +40 -0
  140. package/build/multibase-digest/multibase-digest.js.map +1 -1
  141. package/build/multibase-digest/multibase-digest.test.js +46 -0
  142. package/build/multibase-digest/multibase-digest.test.js.map +1 -1
  143. package/build/node/errors.d.ts +41 -0
  144. package/build/node/errors.js +80 -0
  145. package/build/node/errors.js.map +1 -0
  146. package/build/node/index.d.ts +3 -0
  147. package/build/node/index.js +4 -0
  148. package/build/node/index.js.map +1 -0
  149. package/build/node/is-private-ip.d.ts +41 -0
  150. package/build/node/is-private-ip.js +138 -0
  151. package/build/node/is-private-ip.js.map +1 -0
  152. package/build/node/is-private-ip.test.d.ts +1 -0
  153. package/build/node/is-private-ip.test.js +96 -0
  154. package/build/node/is-private-ip.test.js.map +1 -0
  155. package/build/node/validate-public-url.d.ts +49 -0
  156. package/build/node/validate-public-url.js +94 -0
  157. package/build/node/validate-public-url.js.map +1 -0
  158. package/build/node/validate-public-url.test.d.ts +1 -0
  159. package/build/node/validate-public-url.test.js +177 -0
  160. package/build/node/validate-public-url.test.js.map +1 -0
  161. package/build/resolvers/errors.d.ts +43 -0
  162. package/build/resolvers/errors.js +99 -0
  163. package/build/resolvers/errors.js.map +1 -0
  164. package/build/resolvers/index.d.ts +5 -0
  165. package/build/resolvers/index.js +6 -0
  166. package/build/resolvers/index.js.map +1 -0
  167. package/build/resolvers/resolve-document-if-changed.d.ts +48 -0
  168. package/build/resolvers/resolve-document-if-changed.js +42 -0
  169. package/build/resolvers/resolve-document-if-changed.js.map +1 -0
  170. package/build/resolvers/resolve-document-if-changed.test.d.ts +1 -0
  171. package/build/resolvers/resolve-document-if-changed.test.js +109 -0
  172. package/build/resolvers/resolve-document-if-changed.test.js.map +1 -0
  173. package/build/resolvers/resolve-document.d.ts +86 -0
  174. package/build/resolvers/resolve-document.js +239 -0
  175. package/build/resolvers/resolve-document.js.map +1 -0
  176. package/build/resolvers/resolve-document.test.d.ts +1 -0
  177. package/build/resolvers/resolve-document.test.js +299 -0
  178. package/build/resolvers/resolve-document.test.js.map +1 -0
  179. package/build/resolvers/resolve-json-document.d.ts +40 -0
  180. package/build/resolvers/resolve-json-document.js +42 -0
  181. package/build/resolvers/resolve-json-document.js.map +1 -0
  182. package/build/resolvers/resolve-json-document.test.d.ts +1 -0
  183. package/build/resolvers/resolve-json-document.test.js +61 -0
  184. package/build/resolvers/resolve-json-document.test.js.map +1 -0
  185. package/build/structured-error.d.ts +49 -0
  186. package/build/structured-error.js +17 -0
  187. package/build/structured-error.js.map +1 -0
  188. package/build/structured-error.test.d.ts +1 -0
  189. package/build/structured-error.test.js +112 -0
  190. package/build/structured-error.test.js.map +1 -0
  191. package/build/validation/describe-jsonld-failure.d.ts +50 -0
  192. package/build/validation/describe-jsonld-failure.js +104 -0
  193. package/build/validation/describe-jsonld-failure.js.map +1 -0
  194. package/build/validation/describe-jsonld-failure.test.d.ts +1 -0
  195. package/build/validation/describe-jsonld-failure.test.js +141 -0
  196. package/build/validation/describe-jsonld-failure.test.js.map +1 -0
  197. package/build/validation/errors.d.ts +40 -0
  198. package/build/validation/errors.js +75 -0
  199. package/build/validation/errors.js.map +1 -0
  200. package/build/validation/index.d.ts +4 -0
  201. package/build/validation/index.js +5 -0
  202. package/build/validation/index.js.map +1 -0
  203. package/build/validation/validate-against-schemas.d.ts +22 -0
  204. package/build/validation/validate-against-schemas.js +65 -0
  205. package/build/validation/validate-against-schemas.js.map +1 -0
  206. package/build/validation/validate-against-schemas.test.d.ts +1 -0
  207. package/build/validation/validate-against-schemas.test.js +169 -0
  208. package/build/validation/validate-against-schemas.test.js.map +1 -0
  209. package/build/validation/validate-jsonld.context-cache.test.d.ts +1 -0
  210. package/build/validation/validate-jsonld.context-cache.test.js +32 -0
  211. package/build/validation/validate-jsonld.context-cache.test.js.map +1 -0
  212. package/build/validation/validate-jsonld.d.ts +33 -0
  213. package/build/validation/validate-jsonld.js +99 -0
  214. package/build/validation/validate-jsonld.js.map +1 -0
  215. package/build/validation/validate-jsonld.ssrf.test.d.ts +1 -0
  216. package/build/validation/validate-jsonld.ssrf.test.js +108 -0
  217. package/build/validation/validate-jsonld.ssrf.test.js.map +1 -0
  218. package/build/validation/validate-jsonld.test.d.ts +1 -0
  219. package/build/validation/validate-jsonld.test.js +200 -0
  220. package/build/validation/validate-jsonld.test.js.map +1 -0
  221. package/package.json +77 -11
@@ -0,0 +1,180 @@
1
+ import type { StructuredWarning } from '../structured-error.js';
2
+ import type { ConformityWarningCode } from './codes.js';
3
+ /**
4
+ * Reference to the scheme owner. Optional; not all scheme documents carry it.
5
+ */
6
+ export interface ConformitySchemeOwner {
7
+ /** Owner URI (typically a website or registered identifier). */
8
+ canonicalId?: string;
9
+ /** Human-readable owner name. */
10
+ name?: string;
11
+ }
12
+ /**
13
+ * A topic the criterion addresses (e.g. a SKOS concept from the UNTP
14
+ * conformity-topic vocabulary).
15
+ */
16
+ export interface ConformityTopic {
17
+ /** Topic URI. */
18
+ canonicalId: string;
19
+ /** Human-readable name, when the topic is inlined as a structured object. */
20
+ name?: string;
21
+ /** Free-text definition, when present. */
22
+ definition?: string;
23
+ }
24
+ /**
25
+ * A single auditable criterion within a profile.
26
+ *
27
+ * The criterion URI is stable and versioned per spec (e.g.
28
+ * `myscheme.org/criterion/forced-labour/1.0.0`).
29
+ */
30
+ export interface ConformityCriterion {
31
+ /** Canonical (versioned) criterion URI. */
32
+ canonicalId: string;
33
+ /** Human-readable criterion name. */
34
+ name: string;
35
+ /** Criterion version string (also encoded in `canonicalId`). */
36
+ version: string;
37
+ /** Lifecycle status, e.g. `active`. */
38
+ status: string;
39
+ description?: string;
40
+ documentation?: string;
41
+ /** Conformity topics this criterion addresses. */
42
+ topics: ConformityTopic[];
43
+ /** Free-form tags attached to the criterion. */
44
+ tags: string[];
45
+ }
46
+ /**
47
+ * A profile within a scheme. Profile URI is stable and versioned per spec.
48
+ */
49
+ export interface ConformityProfile {
50
+ /** Canonical (versioned) profile URI. */
51
+ canonicalId: string;
52
+ name: string;
53
+ /** Profile version string (also encoded in `canonicalId`). */
54
+ version: string;
55
+ /** Lifecycle status, e.g. `active`. */
56
+ status: string;
57
+ description?: string;
58
+ documentation?: string;
59
+ /** Optional ISO-8601 date the profile becomes valid. */
60
+ validFrom?: string;
61
+ /** Criteria inlined within this profile. */
62
+ criteria: ConformityCriterion[];
63
+ }
64
+ /**
65
+ * A parsed conformity scheme as published by a scheme owner.
66
+ *
67
+ * The scheme URI is stable but not independently versioned. Profiles and
68
+ * criteria inside the scheme carry their own version segments.
69
+ *
70
+ * @see https://untp.unece.org/docs/specification/ConformityVocabularyCatalog
71
+ */
72
+ export interface ConformityScheme {
73
+ /** Canonical scheme URI (no version segment). */
74
+ canonicalId: string;
75
+ /** URL the scheme document was fetched from (callers pass this in). */
76
+ sourceUrl: string;
77
+ /** CVC specification version that was used to parse this document. */
78
+ specVersion: string;
79
+ /** Human-readable scheme name. */
80
+ name: string;
81
+ description?: string;
82
+ documentation?: string;
83
+ owner?: ConformitySchemeOwner;
84
+ profiles: ConformityProfile[];
85
+ }
86
+ /**
87
+ * One entry in the UNTP Conformity Vocabulary Catalogue Register. Ingestion
88
+ * consumers route on {@link status} (skipping deprecated entries) and fetch
89
+ * the scheme document from {@link vocabularyUrl}.
90
+ */
91
+ export interface ConformityCatalogueEntry {
92
+ /** CVC-canonical scheme URI; from the register entry's `id`. */
93
+ canonicalId: string;
94
+ /** Owner-published scheme document URL; from the register entry's `vocabularyURL`. Validated as a parseable URL at parse time. */
95
+ vocabularyUrl: string;
96
+ /** Human-readable scheme name. */
97
+ name: string;
98
+ /** Lifecycle status of the scheme in the register, when supplied (`'pilot'`, `'active'`, `'deprecated'`, etc.). */
99
+ status?: string;
100
+ }
101
+ /**
102
+ * Warnings emitted by {@link validateConformityClaim}. Narrows
103
+ * {@link StructuredWarning} so the `code` is one of the validator's known codes.
104
+ */
105
+ export type ConformityWarning = StructuredWarning & {
106
+ code: ConformityWarningCode;
107
+ };
108
+ /**
109
+ * A single criterion entry on a credential's conformity claim. The credential
110
+ * issuer asserts conformity to `criterion` and may classify it with conformity
111
+ * topics.
112
+ */
113
+ export interface ConformityClaimCriterion {
114
+ /** Criterion URI the claim references. */
115
+ criterion: string;
116
+ /**
117
+ * Topic URIs the claim declares for this criterion. Optional and
118
+ * version-specific: an extractor populates it only when its data model
119
+ * carries criterion topics. A criterion may be classified by more than one
120
+ * topic, so it is a list; when present, the validator checks every entry
121
+ * against the criterion's published topic set.
122
+ */
123
+ conformityTopics?: string[];
124
+ }
125
+ /**
126
+ * A single assessment entry on a credential's conformity claim: the criteria
127
+ * the assessment references and the topics it declares for itself. The
128
+ * declared topics are validated against the deduplicated union of the
129
+ * published topics of the assessment's criteria, in one direction only: a
130
+ * declared topic outside the union warns, while a union topic the assessment
131
+ * does not declare is acceptable, because the assessment's topics are a
132
+ * categorisation rather than an exhaustive enumeration.
133
+ */
134
+ export interface ConformityClaimAssessment {
135
+ /**
136
+ * Criterion URIs the assessment references. Extractors must also emit every
137
+ * entry here as a {@link ConformityClaimCriterion} in the claim's `criteria`
138
+ * list: the validator's assessment check skips unresolved criteria on the
139
+ * assumption that `conformity-criterion.not-in-profile` has already surfaced
140
+ * them from `criteria`, so an entry present only here would silently escape
141
+ * both checks.
142
+ */
143
+ criteria: string[];
144
+ /** Topic URIs the assessment declares for itself. */
145
+ conformityTopics: string[];
146
+ }
147
+ /**
148
+ * A conformity claim extracted from a Digital Conformity Credential, in the
149
+ * minimal shape the validator needs.
150
+ *
151
+ * This type is the version-neutral interlingua between the version-specific
152
+ * data model bridge extractors and {@link validateConformityClaim}. Its fields
153
+ * are optional capabilities keyed on presence, not on spec version: an
154
+ * extractor populates a field only when its data model carries the concept,
155
+ * and the validator runs a rule only when the data is present. Never repurpose
156
+ * a field with changed semantics for a new spec version; add a new field, or
157
+ * fork the validation rules into versioned modules (the delta pattern the
158
+ * `parsers/` directory already uses) the first time a rule genuinely differs
159
+ * between versions. A version conditional inside the shared validator is the
160
+ * signal to fork, not a fix. See ADR-033 (update of 2026-07-13).
161
+ */
162
+ export interface ConformityClaim {
163
+ /** Scheme URI the claim references. */
164
+ scheme: string;
165
+ /**
166
+ * Profile URI the claim references. Optional because not every data model
167
+ * requires a profile reference. When absent, the validator checks the scheme
168
+ * reference only and emits a `conformity-profile.not-specified` advisory,
169
+ * since criteria are published per versioned profile.
170
+ */
171
+ profile?: string;
172
+ /** Criteria the claim addresses. */
173
+ criteria: ConformityClaimCriterion[];
174
+ /**
175
+ * Assessment-level topic declarations. Optional and version-specific: an
176
+ * extractor populates it only when its data model classifies assessments by
177
+ * topic.
178
+ */
179
+ assessments?: ConformityClaimAssessment[];
180
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/conformity-vocabulary/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import type { ConformityClaim, ConformityScheme, ConformityWarning } from './types.js';
2
+ /**
3
+ * Validates a credential's conformity claim against a parsed scheme.
4
+ *
5
+ * Short-circuits at the first miss:
6
+ * - scheme null or `canonicalId` mismatch → only `conformity-scheme.not-found`.
7
+ * - profile absent on the claim → scheme check plus a
8
+ * `conformity-profile.not-specified` advisory; criteria are published per
9
+ * versioned profile, so without one the criterion and topic checks cannot
10
+ * run, and silence would be indistinguishable from a clean pass.
11
+ * - profile not in scheme → only `conformity-profile.not-found`.
12
+ * - otherwise → criteria-level, criterion-topic, and assessment-topic warnings
13
+ * accumulate.
14
+ *
15
+ * Pointers are relative to `claim`. A consumer that holds the document the
16
+ * claim was extracted from re-maps them onto it, and prepending a wrapper path
17
+ * is only sound where the claim is a sub-document of that source. Where an
18
+ * extractor synthesises the claim, a prefix is not enough and the consumer
19
+ * needs the exact source path of each projected value: `criteria` may be
20
+ * flattened from several places, field names need not match, and an extractor
21
+ * that filters empty or malformed entries shifts both `/assessments/{i}` and
22
+ * topic indices away from their source positions (#753).
23
+ *
24
+ * @see ADR-033 §3 for warning code definitions.
25
+ */
26
+ export declare function validateConformityClaim(claim: ConformityClaim, scheme: ConformityScheme | null): readonly ConformityWarning[];
@@ -0,0 +1,165 @@
1
+ import { ConformityWarningCode } from './codes.js';
2
+ /**
3
+ * Validates a credential's conformity claim against a parsed scheme.
4
+ *
5
+ * Short-circuits at the first miss:
6
+ * - scheme null or `canonicalId` mismatch → only `conformity-scheme.not-found`.
7
+ * - profile absent on the claim → scheme check plus a
8
+ * `conformity-profile.not-specified` advisory; criteria are published per
9
+ * versioned profile, so without one the criterion and topic checks cannot
10
+ * run, and silence would be indistinguishable from a clean pass.
11
+ * - profile not in scheme → only `conformity-profile.not-found`.
12
+ * - otherwise → criteria-level, criterion-topic, and assessment-topic warnings
13
+ * accumulate.
14
+ *
15
+ * Pointers are relative to `claim`. A consumer that holds the document the
16
+ * claim was extracted from re-maps them onto it, and prepending a wrapper path
17
+ * is only sound where the claim is a sub-document of that source. Where an
18
+ * extractor synthesises the claim, a prefix is not enough and the consumer
19
+ * needs the exact source path of each projected value: `criteria` may be
20
+ * flattened from several places, field names need not match, and an extractor
21
+ * that filters empty or malformed entries shifts both `/assessments/{i}` and
22
+ * topic indices away from their source positions (#753).
23
+ *
24
+ * @see ADR-033 §3 for warning code definitions.
25
+ */
26
+ export function validateConformityClaim(claim, scheme) {
27
+ const warnings = [];
28
+ if (!scheme || scheme.canonicalId !== claim.scheme) {
29
+ warnings.push({
30
+ code: ConformityWarningCode.SchemeNotFound,
31
+ message: 'Scheme URI is not in the known set.',
32
+ received: claim.scheme,
33
+ pointer: '/scheme',
34
+ });
35
+ return warnings;
36
+ }
37
+ // `== null` covers undefined and a runtime null from JSON or database rows.
38
+ if (claim.profile == null) {
39
+ warnings.push({
40
+ code: ConformityWarningCode.ProfileNotSpecified,
41
+ message: 'The claim references no profile; criterion and topic checks were not performed because criteria are published per profile.',
42
+ pointer: '/profile',
43
+ });
44
+ return warnings;
45
+ }
46
+ const profile = scheme.profiles.find((p) => p.canonicalId === claim.profile);
47
+ if (!profile) {
48
+ warnings.push({
49
+ code: ConformityWarningCode.ProfileNotFound,
50
+ message: "Profile URI is not among the scheme's published profiles.",
51
+ received: claim.profile,
52
+ expected: scheme.profiles.map((p) => p.canonicalId),
53
+ pointer: '/profile',
54
+ });
55
+ return warnings;
56
+ }
57
+ const profileCriteriaById = new Map(profile.criteria.map((c) => [c.canonicalId, c]));
58
+ const claimCriteriaIds = new Set(claim.criteria.map((c) => c.criterion));
59
+ const publishedCriterionIds = profile.criteria.map((c) => c.canonicalId);
60
+ // criterion-not-in-profile
61
+ claim.criteria.forEach((claimCriterion, i) => {
62
+ if (!profileCriteriaById.has(claimCriterion.criterion)) {
63
+ warnings.push({
64
+ code: ConformityWarningCode.CriterionNotInProfile,
65
+ message: `Criterion URI is not in the criterion list published by profile ${profile.canonicalId}.`,
66
+ received: claimCriterion.criterion,
67
+ expected: publishedCriterionIds,
68
+ pointer: `/criteria/${i}/criterion`,
69
+ });
70
+ }
71
+ });
72
+ // criterion-missing
73
+ for (const profileCriterion of profile.criteria) {
74
+ if (!claimCriteriaIds.has(profileCriterion.canonicalId)) {
75
+ warnings.push({
76
+ code: ConformityWarningCode.CriterionMissing,
77
+ message: `Profile ${profile.canonicalId} publishes a criterion that the claim does not address.`,
78
+ expected: profileCriterion.canonicalId,
79
+ pointer: '/criteria',
80
+ });
81
+ }
82
+ }
83
+ // criterion-topic-mismatch: each criterion defines its own conformity topics,
84
+ // and a profile references a collection of independently-versioned criteria.
85
+ // For each criterion the claim addresses, the topics it declares should match
86
+ // the topics that criterion defines. The check runs whenever the claim carries
87
+ // a topic list, including an empty one, so a criterion that omits a topic it
88
+ // defines is flagged. An absent list marks a data model that classifies
89
+ // criteria without conformity topics, and the check passes over it.
90
+ claim.criteria.forEach((claimCriterion, i) => {
91
+ // `== null` covers both undefined and a runtime null (for example a claim
92
+ // parsed from JSON or a database row where the optional field serialised
93
+ // as null); an empty array still runs the check.
94
+ if (claimCriterion.conformityTopics == null) {
95
+ return;
96
+ }
97
+ const profileCriterion = profileCriteriaById.get(claimCriterion.criterion);
98
+ if (!profileCriterion) {
99
+ // already surfaced by criterion-not-in-profile
100
+ return;
101
+ }
102
+ const criterionTopicIds = profileCriterion.topics.map((t) => t.canonicalId);
103
+ const declaredTopics = claimCriterion.conformityTopics;
104
+ // A topic the criterion defines that the claim leaves out.
105
+ criterionTopicIds.forEach((expected) => {
106
+ if (!declaredTopics.includes(expected)) {
107
+ warnings.push({
108
+ code: ConformityWarningCode.CriterionTopicMismatch,
109
+ message: `The criterion, as published in profile ${profile.canonicalId}, defines a topic the claim does not declare.`,
110
+ expected,
111
+ pointer: `/criteria/${i}/conformityTopics`,
112
+ });
113
+ }
114
+ });
115
+ // A topic the claim declares that the criterion does not define.
116
+ declaredTopics.forEach((declared, t) => {
117
+ if (!criterionTopicIds.includes(declared)) {
118
+ warnings.push({
119
+ code: ConformityWarningCode.CriterionTopicMismatch,
120
+ message: `The claim declares a topic the criterion, as published in profile ${profile.canonicalId}, does not define.`,
121
+ received: declared,
122
+ expected: criterionTopicIds,
123
+ pointer: `/criteria/${i}/conformityTopics/${t}`,
124
+ });
125
+ }
126
+ });
127
+ });
128
+ // assessment-topic-mismatch: an assessment's declared topics must sit within
129
+ // the deduplicated union of the published topics of the criteria it
130
+ // references. One direction only: an assessment may declare a subset of the
131
+ // union (its topics categorise the assessment, they do not enumerate it), but
132
+ // a declared topic outside the union references taxonomy none of its criteria
133
+ // carry. The check runs only when the assessment references at least one
134
+ // criterion and every referenced criterion resolves in the profile. With no
135
+ // referenced criteria the parent-level topic is the only classification the
136
+ // claim carries, so there is no criteria union to check it against. An
137
+ // unresolved criterion could itself carry the declared topic, so warning
138
+ // against a partial union would be unfounded, and unresolved criteria are
139
+ // already surfaced by criterion-not-in-profile.
140
+ claim.assessments?.forEach((assessment, i) => {
141
+ if (assessment.criteria.length === 0) {
142
+ return;
143
+ }
144
+ const resolvedCriteria = assessment.criteria
145
+ .map((criterionId) => profileCriteriaById.get(criterionId))
146
+ .filter((criterion) => criterion !== undefined);
147
+ if (resolvedCriteria.length !== assessment.criteria.length) {
148
+ return;
149
+ }
150
+ const unionTopicIds = new Set(resolvedCriteria.flatMap((criterion) => criterion.topics.map((t) => t.canonicalId)));
151
+ assessment.conformityTopics.forEach((declared, t) => {
152
+ if (!unionTopicIds.has(declared)) {
153
+ warnings.push({
154
+ code: ConformityWarningCode.AssessmentTopicMismatch,
155
+ message: `The assessment declares a topic that none of its assessed criteria, as published in profile ${profile.canonicalId}, define.`,
156
+ received: declared,
157
+ expected: [...unionTopicIds],
158
+ pointer: `/assessments/${i}/conformityTopics/${t}`,
159
+ });
160
+ }
161
+ });
162
+ });
163
+ return warnings;
164
+ }
165
+ //# sourceMappingURL=validate-conformity-claim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-conformity-claim.js","sourceRoot":"","sources":["../../src/conformity-vocabulary/validate-conformity-claim.ts"],"names":[],"mappings":"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"}