@noeos/verification-engine 0.0.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 (184) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +11 -0
  4. package/README.md +7 -0
  5. package/dist/cjs/api/engine.js +271 -0
  6. package/dist/cjs/api/types.js +10 -0
  7. package/dist/cjs/chains/chain-builder.js +541 -0
  8. package/dist/cjs/chains/duplicate-detector.js +208 -0
  9. package/dist/cjs/chains/verify-chain.js +656 -0
  10. package/dist/cjs/contracts/generated.js +512 -0
  11. package/dist/cjs/contracts/schema-assets.js +6 -0
  12. package/dist/cjs/contracts/vector-assets.js +6 -0
  13. package/dist/cjs/domain/algorithm-id.js +5 -0
  14. package/dist/cjs/domain/byte-view.js +7 -0
  15. package/dist/cjs/domain/chain.js +3 -0
  16. package/dist/cjs/domain/diagnostic.js +6 -0
  17. package/dist/cjs/domain/digest.js +37 -0
  18. package/dist/cjs/domain/duplicate-policy.js +3 -0
  19. package/dist/cjs/domain/evidence.js +10 -0
  20. package/dist/cjs/domain/identifiers.js +52 -0
  21. package/dist/cjs/domain/json-value.js +3 -0
  22. package/dist/cjs/domain/limits.js +24 -0
  23. package/dist/cjs/domain/operation-result.js +19 -0
  24. package/dist/cjs/domain/position.js +16 -0
  25. package/dist/cjs/domain/protocol-version.js +5 -0
  26. package/dist/cjs/domain/rule.js +3 -0
  27. package/dist/cjs/domain/stream.js +3 -0
  28. package/dist/cjs/domain/version.js +16 -0
  29. package/dist/cjs/evidence/digest-evidence.js +22 -0
  30. package/dist/cjs/evidence/evidence-parser.js +288 -0
  31. package/dist/cjs/framing/frame-builders.js +124 -0
  32. package/dist/cjs/framing/frame-encoder.js +238 -0
  33. package/dist/cjs/framing/frame-parser.js +173 -0
  34. package/dist/cjs/framing/frame-types.js +3 -0
  35. package/dist/cjs/hashing/hash-adapter.js +90 -0
  36. package/dist/cjs/hashing/hash-frame.js +16 -0
  37. package/dist/cjs/index.js +16 -0
  38. package/dist/cjs/normalization/byte-sink.js +70 -0
  39. package/dist/cjs/normalization/jcs-profile.js +154 -0
  40. package/dist/cjs/normalization/normalize.js +56 -0
  41. package/dist/cjs/normalization/profile-registry.js +66 -0
  42. package/dist/cjs/normalization/profile.js +3 -0
  43. package/dist/cjs/normalization/raw-bytes-profile.js +27 -0
  44. package/dist/cjs/package.json +3 -0
  45. package/dist/cjs/profiles.js +9 -0
  46. package/dist/cjs/records/record-service.js +278 -0
  47. package/dist/cjs/rules/rule-set.js +211 -0
  48. package/dist/cjs/schemas.js +6 -0
  49. package/dist/cjs/validation/algorithm-validation.js +12 -0
  50. package/dist/cjs/validation/diagnostic-collector.js +159 -0
  51. package/dist/cjs/validation/digest-validation.js +48 -0
  52. package/dist/cjs/validation/identifier-validation.js +33 -0
  53. package/dist/cjs/validation/json-text-parser.js +297 -0
  54. package/dist/cjs/validation/json-value-validation.js +155 -0
  55. package/dist/cjs/validation/limit-validation.js +75 -0
  56. package/dist/cjs/validation/object-inspection.js +60 -0
  57. package/dist/cjs/validation/position-validation.js +13 -0
  58. package/dist/cjs/validation/result-helpers.js +9 -0
  59. package/dist/cjs/validation/utf8-validation.js +69 -0
  60. package/dist/cjs/validation/version-validation.js +31 -0
  61. package/dist/cjs/vectors.js +6 -0
  62. package/dist/esm/api/engine.js +266 -0
  63. package/dist/esm/api/types.js +4 -0
  64. package/dist/esm/chains/chain-builder.js +536 -0
  65. package/dist/esm/chains/duplicate-detector.js +204 -0
  66. package/dist/esm/chains/verify-chain.js +652 -0
  67. package/dist/esm/contracts/generated.js +509 -0
  68. package/dist/esm/contracts/schema-assets.js +2 -0
  69. package/dist/esm/contracts/vector-assets.js +2 -0
  70. package/dist/esm/domain/algorithm-id.js +2 -0
  71. package/dist/esm/domain/byte-view.js +4 -0
  72. package/dist/esm/domain/chain.js +2 -0
  73. package/dist/esm/domain/diagnostic.js +3 -0
  74. package/dist/esm/domain/digest.js +32 -0
  75. package/dist/esm/domain/duplicate-policy.js +2 -0
  76. package/dist/esm/domain/evidence.js +7 -0
  77. package/dist/esm/domain/identifiers.js +45 -0
  78. package/dist/esm/domain/json-value.js +2 -0
  79. package/dist/esm/domain/limits.js +21 -0
  80. package/dist/esm/domain/operation-result.js +15 -0
  81. package/dist/esm/domain/position.js +12 -0
  82. package/dist/esm/domain/protocol-version.js +2 -0
  83. package/dist/esm/domain/rule.js +2 -0
  84. package/dist/esm/domain/stream.js +2 -0
  85. package/dist/esm/domain/version.js +12 -0
  86. package/dist/esm/evidence/digest-evidence.js +19 -0
  87. package/dist/esm/evidence/evidence-parser.js +281 -0
  88. package/dist/esm/framing/frame-builders.js +114 -0
  89. package/dist/esm/framing/frame-encoder.js +234 -0
  90. package/dist/esm/framing/frame-parser.js +170 -0
  91. package/dist/esm/framing/frame-types.js +2 -0
  92. package/dist/esm/hashing/hash-adapter.js +84 -0
  93. package/dist/esm/hashing/hash-frame.js +12 -0
  94. package/dist/esm/index.js +5 -0
  95. package/dist/esm/normalization/byte-sink.js +66 -0
  96. package/dist/esm/normalization/jcs-profile.js +151 -0
  97. package/dist/esm/normalization/normalize.js +52 -0
  98. package/dist/esm/normalization/profile-registry.js +62 -0
  99. package/dist/esm/normalization/profile.js +2 -0
  100. package/dist/esm/normalization/raw-bytes-profile.js +24 -0
  101. package/dist/esm/profiles.js +6 -0
  102. package/dist/esm/records/record-service.js +270 -0
  103. package/dist/esm/rules/rule-set.js +206 -0
  104. package/dist/esm/schemas.js +2 -0
  105. package/dist/esm/validation/algorithm-validation.js +9 -0
  106. package/dist/esm/validation/diagnostic-collector.js +154 -0
  107. package/dist/esm/validation/digest-validation.js +43 -0
  108. package/dist/esm/validation/identifier-validation.js +27 -0
  109. package/dist/esm/validation/json-text-parser.js +294 -0
  110. package/dist/esm/validation/json-value-validation.js +152 -0
  111. package/dist/esm/validation/limit-validation.js +71 -0
  112. package/dist/esm/validation/object-inspection.js +54 -0
  113. package/dist/esm/validation/position-validation.js +10 -0
  114. package/dist/esm/validation/result-helpers.js +6 -0
  115. package/dist/esm/validation/utf8-validation.js +63 -0
  116. package/dist/esm/validation/version-validation.js +26 -0
  117. package/dist/esm/vectors.js +2 -0
  118. package/dist/types/api/engine.d.ts +8 -0
  119. package/dist/types/api/types.d.ts +124 -0
  120. package/dist/types/chains/chain-builder.d.ts +42 -0
  121. package/dist/types/chains/duplicate-detector.d.ts +18 -0
  122. package/dist/types/chains/verify-chain.d.ts +9 -0
  123. package/dist/types/contracts/generated.d.ts +442 -0
  124. package/dist/types/contracts/schema-assets.d.ts +2 -0
  125. package/dist/types/contracts/vector-assets.d.ts +2 -0
  126. package/dist/types/domain/algorithm-id.d.ts +4 -0
  127. package/dist/types/domain/byte-view.d.ts +1 -0
  128. package/dist/types/domain/chain.d.ts +43 -0
  129. package/dist/types/domain/diagnostic.d.ts +23 -0
  130. package/dist/types/domain/digest.d.ts +12 -0
  131. package/dist/types/domain/duplicate-policy.d.ts +26 -0
  132. package/dist/types/domain/evidence.d.ts +100 -0
  133. package/dist/types/domain/identifiers.d.ts +29 -0
  134. package/dist/types/domain/json-value.d.ts +6 -0
  135. package/dist/types/domain/limits.d.ts +13 -0
  136. package/dist/types/domain/operation-result.d.ts +12 -0
  137. package/dist/types/domain/position.d.ts +8 -0
  138. package/dist/types/domain/protocol-version.d.ts +2 -0
  139. package/dist/types/domain/rule.d.ts +34 -0
  140. package/dist/types/domain/stream.d.ts +5 -0
  141. package/dist/types/domain/version.d.ts +8 -0
  142. package/dist/types/evidence/digest-evidence.d.ts +4 -0
  143. package/dist/types/evidence/evidence-parser.d.ts +8 -0
  144. package/dist/types/framing/frame-builders.d.ts +40 -0
  145. package/dist/types/framing/frame-encoder.d.ts +6 -0
  146. package/dist/types/framing/frame-parser.d.ts +4 -0
  147. package/dist/types/framing/frame-types.d.ts +27 -0
  148. package/dist/types/hashing/hash-adapter.d.ts +12 -0
  149. package/dist/types/hashing/hash-frame.d.ts +6 -0
  150. package/dist/types/index.d.ts +8 -0
  151. package/dist/types/normalization/byte-sink.d.ts +19 -0
  152. package/dist/types/normalization/jcs-profile.d.ts +3 -0
  153. package/dist/types/normalization/normalize.d.ts +11 -0
  154. package/dist/types/normalization/profile-registry.d.ts +26 -0
  155. package/dist/types/normalization/profile.d.ts +22 -0
  156. package/dist/types/normalization/raw-bytes-profile.d.ts +2 -0
  157. package/dist/types/profiles.d.ts +8 -0
  158. package/dist/types/records/record-service.d.ts +38 -0
  159. package/dist/types/rules/rule-set.d.ts +13 -0
  160. package/dist/types/schemas.d.ts +2 -0
  161. package/dist/types/validation/algorithm-validation.d.ts +3 -0
  162. package/dist/types/validation/diagnostic-collector.d.ts +24 -0
  163. package/dist/types/validation/digest-validation.d.ts +6 -0
  164. package/dist/types/validation/identifier-validation.d.ts +6 -0
  165. package/dist/types/validation/json-text-parser.d.ts +4 -0
  166. package/dist/types/validation/json-value-validation.d.ts +4 -0
  167. package/dist/types/validation/limit-validation.d.ts +4 -0
  168. package/dist/types/validation/object-inspection.d.ts +11 -0
  169. package/dist/types/validation/position-validation.d.ts +3 -0
  170. package/dist/types/validation/result-helpers.d.ts +3 -0
  171. package/dist/types/validation/utf8-validation.d.ts +5 -0
  172. package/dist/types/validation/version-validation.d.ts +9 -0
  173. package/dist/types/vectors.d.ts +2 -0
  174. package/package.json +61 -0
  175. package/schemas/chain-summary.v1.schema.json +156 -0
  176. package/schemas/diagnostic.v1.schema.json +107 -0
  177. package/schemas/link-evidence.v1.schema.json +165 -0
  178. package/schemas/record-evidence.v1.schema.json +91 -0
  179. package/schemas/vector-set.v1.schema.json +52 -0
  180. package/vectors/evidence.json +26 -0
  181. package/vectors/framing.json +136 -0
  182. package/vectors/hashing.json +58 -0
  183. package/vectors/invalid.json +56 -0
  184. package/vectors/manifest.json +46 -0
@@ -0,0 +1,270 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { types } from "node:util";
3
+ import { RECORD_EVIDENCE_SCHEMA } from "../domain/evidence.js";
4
+ import { failure, success } from "../domain/operation-result.js";
5
+ import { buildTrustedContentFrame, buildTrustedRecordFrame } from "../framing/frame-builders.js";
6
+ import { hashTrustedFrame } from "../hashing/hash-frame.js";
7
+ import { normalizeRegisteredToBytes } from "../normalization/normalize.js";
8
+ import { createReadonlyByteView } from "../rules/rule-set.js";
9
+ import { DiagnosticCollector } from "../validation/diagnostic-collector.js";
10
+ import { validateAlgorithmId } from "../validation/algorithm-validation.js";
11
+ import { equalDigest, validateDigest } from "../validation/digest-validation.js";
12
+ import { validateContextId, validateProfileId, validateRecordId, } from "../validation/identifier-validation.js";
13
+ import { inspectExactProperties } from "../validation/object-inspection.js";
14
+ import { failureWithCode } from "../validation/result-helpers.js";
15
+ import { validateProfileVersion } from "../validation/version-validation.js";
16
+ export function hashRecord(input, options) {
17
+ const computed = computeRecord(input, options);
18
+ if (!computed.ok)
19
+ return computed;
20
+ return success(computed.value.evidence, computed.diagnostics);
21
+ }
22
+ export function computeRecord(input, options) {
23
+ const validated = validateRecordInput(input, options.limits);
24
+ if (!validated.ok)
25
+ return validated;
26
+ const collector = new DiagnosticCollector(options.limits);
27
+ const hasRules = options.rules?.hasRules() === true;
28
+ if (hasRules) {
29
+ addAll(collector, options.rules.evaluate(Object.freeze({
30
+ phase: "input",
31
+ input: freezeInputView(validated.value),
32
+ recordId: validated.value.recordId,
33
+ }), options.limits));
34
+ }
35
+ if (collector.hasErrors())
36
+ return failure(collector.finish());
37
+ const profileReference = validateProfileReference(validated.value, options);
38
+ if (!profileReference.ok)
39
+ return profileReference;
40
+ const profile = profileReference.value;
41
+ if (profile.inputKind === "json" && inputPayloadIsBytes(validated.value.payload)) {
42
+ collector.addCode("PROFILE_INPUT_KIND_MISMATCH", "input", {
43
+ recordId: validated.value.recordId,
44
+ });
45
+ return failure(collector.finish());
46
+ }
47
+ if (profile.inputKind === "bytes" && !inputPayloadIsBytes(validated.value.payload)) {
48
+ collector.addCode("PROFILE_INPUT_KIND_MISMATCH", "input", {
49
+ recordId: validated.value.recordId,
50
+ });
51
+ return failure(collector.finish());
52
+ }
53
+ if (isBuiltinRawProfile(profile) && !hasRules) {
54
+ return computeBuiltinRawRecord(validated.value, options);
55
+ }
56
+ const profileInput = profile.validate(validated.value.payload, options.limits);
57
+ if (!profileInput.ok)
58
+ return profileInput;
59
+ const normalized = normalizeRegisteredToBytes(profile, profileInput.value, options.limits);
60
+ if (!normalized.ok)
61
+ return normalized;
62
+ addAll(collector, normalized.diagnostics);
63
+ if (hasRules) {
64
+ addAll(collector, options.rules.evaluate(Object.freeze({
65
+ phase: "normalization",
66
+ normalized: createReadonlyByteView(normalized.value.bytes),
67
+ recordId: validated.value.recordId,
68
+ }), options.limits));
69
+ }
70
+ if (collector.hasErrors())
71
+ return failure(collector.finish());
72
+ const contentDigest = hashTrustedFrame(validated.value.algorithm, buildTrustedContentFrame({
73
+ algorithm: validated.value.algorithm,
74
+ profileId: validated.value.profile.id,
75
+ profileVersion: validated.value.profile.version,
76
+ normalizedBytes: normalized.value.bytes,
77
+ }, options.limits));
78
+ if (!contentDigest.ok)
79
+ return contentDigest;
80
+ const recordDigest = hashTrustedFrame(validated.value.algorithm, buildTrustedRecordFrame({
81
+ algorithm: validated.value.algorithm,
82
+ contextId: validated.value.contextId,
83
+ recordId: validated.value.recordId,
84
+ profileId: validated.value.profile.id,
85
+ profileVersion: validated.value.profile.version,
86
+ normalizedByteLength: normalized.value.stats.byteLength,
87
+ contentDigest: contentDigest.value,
88
+ }, options.limits));
89
+ if (!recordDigest.ok)
90
+ return recordDigest;
91
+ const evidence = freezeRecordEvidence({
92
+ $schema: RECORD_EVIDENCE_SCHEMA,
93
+ protocolVersion: 1,
94
+ contextId: validated.value.contextId,
95
+ recordId: validated.value.recordId,
96
+ profile: validated.value.profile,
97
+ algorithm: validated.value.algorithm,
98
+ normalizedByteLength: normalized.value.stats.byteLength,
99
+ contentDigest: contentDigest.value.toHex(),
100
+ recordDigest: recordDigest.value.toHex(),
101
+ });
102
+ if (hasRules) {
103
+ addAll(collector, options.rules.evaluate(Object.freeze({
104
+ phase: "record",
105
+ record: evidence,
106
+ recordId: evidence.recordId,
107
+ }), options.limits));
108
+ }
109
+ if (collector.hasErrors())
110
+ return failure(collector.finish());
111
+ return success(Object.freeze({
112
+ evidence,
113
+ contentDigest: contentDigest.value,
114
+ recordDigest: recordDigest.value,
115
+ }), collector.finish());
116
+ }
117
+ function computeBuiltinRawRecord(input, options) {
118
+ if (types.isProxy(input.payload) || !(input.payload instanceof Uint8Array)) {
119
+ return failureWithCode("INPUT_TYPE_INVALID", "input");
120
+ }
121
+ if (input.payload.length > options.limits.maxPayloadBytes) {
122
+ return failureWithCode("INPUT_LIMIT_EXCEEDED", "input");
123
+ }
124
+ const contentDigest = hashTrustedFrame(input.algorithm, buildTrustedContentFrame({
125
+ algorithm: input.algorithm,
126
+ profileId: input.profile.id,
127
+ profileVersion: input.profile.version,
128
+ normalizedBytes: input.payload,
129
+ }, options.limits));
130
+ if (!contentDigest.ok)
131
+ return contentDigest;
132
+ const recordDigest = hashTrustedFrame(input.algorithm, buildTrustedRecordFrame({
133
+ algorithm: input.algorithm,
134
+ contextId: input.contextId,
135
+ recordId: input.recordId,
136
+ profileId: input.profile.id,
137
+ profileVersion: input.profile.version,
138
+ normalizedByteLength: input.payload.length,
139
+ contentDigest: contentDigest.value,
140
+ }, options.limits));
141
+ if (!recordDigest.ok)
142
+ return recordDigest;
143
+ const evidence = freezeRecordEvidence({
144
+ $schema: RECORD_EVIDENCE_SCHEMA,
145
+ protocolVersion: 1,
146
+ contextId: input.contextId,
147
+ recordId: input.recordId,
148
+ profile: input.profile,
149
+ algorithm: input.algorithm,
150
+ normalizedByteLength: input.payload.length,
151
+ contentDigest: contentDigest.value.toHex(),
152
+ recordDigest: recordDigest.value.toHex(),
153
+ });
154
+ return success(Object.freeze({
155
+ evidence,
156
+ contentDigest: contentDigest.value,
157
+ recordDigest: recordDigest.value,
158
+ }));
159
+ }
160
+ export function verifyComputedRecord(expected, payload, options) {
161
+ const computed = computeRecord({
162
+ contextId: expected.contextId,
163
+ recordId: expected.recordId,
164
+ payload,
165
+ profile: expected.profile,
166
+ algorithm: expected.algorithm,
167
+ }, options);
168
+ if (!computed.ok)
169
+ return computed;
170
+ const expectedContent = expected.contentDigest;
171
+ const expectedRecord = expected.recordDigest;
172
+ const actualContent = computed.value.evidence.contentDigest;
173
+ const actualRecord = computed.value.evidence.recordDigest;
174
+ const collector = new DiagnosticCollector(options.limits);
175
+ addAll(collector, computed.diagnostics);
176
+ if (!sameDigest(expectedContent, actualContent, expected.algorithm)) {
177
+ collector.addCode("CONTENT_DIGEST_MISMATCH", "record", { recordId: expected.recordId });
178
+ }
179
+ if (!sameDigest(expectedRecord, actualRecord, expected.algorithm)) {
180
+ collector.addCode("RECORD_DIGEST_MISMATCH", "record", { recordId: expected.recordId });
181
+ }
182
+ if (collector.hasErrors())
183
+ return failure(collector.finish());
184
+ return success(computed.value.evidence, collector.finish());
185
+ }
186
+ export function validateRecordInput(value, limits) {
187
+ const exact = inspectExactProperties(value, [
188
+ "contextId",
189
+ "recordId",
190
+ "payload",
191
+ "profile",
192
+ "algorithm",
193
+ ]);
194
+ if (exact === undefined)
195
+ return inputFailure(limits);
196
+ const [contextIdInput, recordIdInput, payload, profileInput, algorithmInput] = exact;
197
+ const contextId = validateContextId(contextIdInput);
198
+ const recordId = validateRecordId(recordIdInput);
199
+ const profile = validateProfile(profileInput, limits);
200
+ const algorithm = validateAlgorithmId(algorithmInput);
201
+ if (!contextId.ok)
202
+ return contextId;
203
+ if (!recordId.ok)
204
+ return recordId;
205
+ if (!profile.ok)
206
+ return profile;
207
+ if (!algorithm.ok)
208
+ return algorithm;
209
+ return success(Object.freeze({
210
+ contextId: contextId.value.value,
211
+ recordId: recordId.value.value,
212
+ payload,
213
+ profile: profile.value,
214
+ algorithm: algorithm.value,
215
+ }));
216
+ }
217
+ export function validateProfile(value, limits) {
218
+ void limits;
219
+ const exact = inspectExactProperties(value, ["id", "version"]);
220
+ if (exact === undefined)
221
+ return inputFailure(limits);
222
+ const [idInput, versionInput] = exact;
223
+ const id = validateProfileId(idInput);
224
+ const version = validateProfileVersion(versionInput);
225
+ if (!id.ok)
226
+ return id;
227
+ if (!version.ok)
228
+ return version;
229
+ return success(Object.freeze({ id: id.value.value, version: version.value.value }));
230
+ }
231
+ export function freezeRecordEvidence(value) {
232
+ return Object.freeze(value);
233
+ }
234
+ function validateProfileReference(input, options) {
235
+ return options.profiles.resolveValidated(input.profile);
236
+ }
237
+ function inputPayloadIsBytes(value) {
238
+ return value instanceof Uint8Array;
239
+ }
240
+ function isBuiltinRawProfile(profile) {
241
+ return profile.id.value === "dev.noeos.raw-bytes";
242
+ }
243
+ function freezeInputView(value) {
244
+ return Object.freeze({
245
+ contextId: value.contextId,
246
+ recordId: value.recordId,
247
+ profile: Object.freeze({ ...value.profile }),
248
+ algorithm: value.algorithm,
249
+ });
250
+ }
251
+ function sameDigest(expected, actual, algorithm) {
252
+ const left = hexToDigest(expected, algorithm);
253
+ const right = hexToDigest(actual, algorithm);
254
+ return left !== undefined && right !== undefined && equalDigest(left, right);
255
+ }
256
+ function hexToDigest(value, algorithm) {
257
+ const result = validateDigest(value, algorithm);
258
+ return result.ok ? result.value : undefined;
259
+ }
260
+ function addAll(collector, diagnostics) {
261
+ if (diagnostics === undefined)
262
+ return;
263
+ for (const diagnostic of diagnostics)
264
+ collector.add(diagnostic);
265
+ }
266
+ function inputFailure(limits) {
267
+ const collector = new DiagnosticCollector(limits);
268
+ collector.addCode("INPUT_TYPE_INVALID", "input");
269
+ return failure(collector.finish());
270
+ }
@@ -0,0 +1,206 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { failure, success } from "../domain/operation-result.js";
3
+ import { createDiagnostic, DiagnosticCollector, } from "../validation/diagnostic-collector.js";
4
+ import { inspectPlainObject } from "../validation/object-inspection.js";
5
+ import { validateProfileVersion } from "../validation/version-validation.js";
6
+ const ruleIdPattern = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u;
7
+ const EMPTY_DIAGNOSTICS = Object.freeze([]);
8
+ export class RuleSet {
9
+ rules;
10
+ constructor(rules) {
11
+ this.rules = rules;
12
+ Object.freeze(this);
13
+ }
14
+ static create(value, limits) {
15
+ if (value === undefined)
16
+ return success(new RuleSet(Object.freeze([])));
17
+ if (!Array.isArray(value))
18
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
19
+ const rules = [];
20
+ const keys = new Set();
21
+ for (const candidate of value) {
22
+ const validated = validateRule(candidate, limits);
23
+ if (!validated.ok)
24
+ return validated;
25
+ const key = `${validated.value.id}\u0000${validated.value.version}`;
26
+ if (keys.has(key))
27
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
28
+ keys.add(key);
29
+ rules.push(validated.value);
30
+ }
31
+ rules.sort((left, right) => compareText(left.id, right.id) || compareText(left.version, right.version));
32
+ return success(new RuleSet(Object.freeze(rules)));
33
+ }
34
+ evaluate(context, limits) {
35
+ if (this.rules.length === 0)
36
+ return EMPTY_DIAGNOSTICS;
37
+ const collector = new DiagnosticCollector(limits);
38
+ for (const rule of this.rules) {
39
+ if (!rule.phases.includes(context.phase))
40
+ continue;
41
+ try {
42
+ const findings = rule.evaluate(context);
43
+ if (!Array.isArray(findings)) {
44
+ collector.addCode("RULE_EXECUTION_FAILED", context.phase, failureOptions(context, "RULE_RESULT_INVALID"));
45
+ continue;
46
+ }
47
+ for (const finding of findings) {
48
+ const diagnostic = toDiagnostic(rule, finding, context);
49
+ if (diagnostic === undefined) {
50
+ collector.addCode("RULE_EXECUTION_FAILED", context.phase, failureOptions(context, "RULE_RESULT_INVALID"));
51
+ }
52
+ else {
53
+ collector.add(diagnostic);
54
+ }
55
+ }
56
+ }
57
+ catch {
58
+ collector.addCode("RULE_EXECUTION_FAILED", context.phase, failureOptions(context, "RULE_EVALUATION_FAILED"));
59
+ }
60
+ }
61
+ return collector.finish();
62
+ }
63
+ hasRules() {
64
+ return this.rules.length !== 0;
65
+ }
66
+ toArray() {
67
+ return this.rules;
68
+ }
69
+ }
70
+ export function createReadonlyByteView(bytes) {
71
+ const copy = Uint8Array.from(bytes);
72
+ return Object.freeze({
73
+ byteLength: copy.length,
74
+ copySlice(start, end) {
75
+ return Uint8Array.from(copy.slice(start, end));
76
+ },
77
+ });
78
+ }
79
+ function validateRule(value, limits) {
80
+ const entries = inspectPlainObject(value);
81
+ if (entries === undefined)
82
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
83
+ const fields = new Map(entries);
84
+ if (fields.size !== 5 ||
85
+ !fields.has("id") ||
86
+ !fields.has("version") ||
87
+ !fields.has("phases") ||
88
+ !fields.has("defaultSeverity") ||
89
+ !fields.has("evaluate")) {
90
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
91
+ }
92
+ const id = fields.get("id");
93
+ const version = fields.get("version");
94
+ const phases = fields.get("phases");
95
+ const defaultSeverity = fields.get("defaultSeverity");
96
+ const evaluate = fields.get("evaluate");
97
+ if (typeof id !== "string" || !ruleIdPattern.test(id) || id.length > 128) {
98
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
99
+ }
100
+ const validatedVersion = validateProfileVersion(version);
101
+ if (!validatedVersion.ok || !Array.isArray(phases) || typeof evaluate !== "function") {
102
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
103
+ }
104
+ if (defaultSeverity !== "error" && defaultSeverity !== "warning" && defaultSeverity !== "info") {
105
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
106
+ }
107
+ const normalizedPhases = [];
108
+ for (const phase of phases) {
109
+ if (!isRulePhase(phase) || normalizedPhases.includes(phase)) {
110
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
111
+ }
112
+ normalizedPhases.push(phase);
113
+ }
114
+ if (normalizedPhases.length === 0)
115
+ return ruleFailure("RULE_VERSION_CONFLICT", limits);
116
+ return success(Object.freeze({
117
+ id,
118
+ version: validatedVersion.value.value,
119
+ phases: Object.freeze([...normalizedPhases]),
120
+ defaultSeverity,
121
+ evaluate(context) {
122
+ return Reflect.apply(evaluate, undefined, [context]);
123
+ },
124
+ }));
125
+ }
126
+ function toDiagnostic(rule, value, context) {
127
+ const entries = inspectPlainObject(value);
128
+ if (entries === undefined)
129
+ return undefined;
130
+ const fields = new Map(entries);
131
+ for (const key of fields.keys()) {
132
+ if (key !== "messageKey" && key !== "path" && key !== "details" && key !== "severity")
133
+ return undefined;
134
+ }
135
+ const messageKey = fields.get("messageKey");
136
+ const path = fields.get("path");
137
+ const details = fields.get("details");
138
+ const severity = fields.get("severity");
139
+ if (typeof messageKey !== "string")
140
+ return undefined;
141
+ if (path !== undefined && typeof path !== "string")
142
+ return undefined;
143
+ if (severity !== undefined &&
144
+ severity !== "error" &&
145
+ severity !== "warning" &&
146
+ severity !== "info") {
147
+ return undefined;
148
+ }
149
+ const ruleDetails = {
150
+ "rule-id": rule.id,
151
+ "rule-version": rule.version,
152
+ };
153
+ if (details !== undefined) {
154
+ const detailEntries = inspectPlainObject(details);
155
+ if (detailEntries === undefined)
156
+ return undefined;
157
+ for (const [key, item] of detailEntries) {
158
+ if (key === "rule-id" || key === "rule-version")
159
+ return undefined;
160
+ if (item !== null &&
161
+ typeof item !== "string" &&
162
+ typeof item !== "number" &&
163
+ typeof item !== "boolean") {
164
+ return undefined;
165
+ }
166
+ ruleDetails[key] = item;
167
+ }
168
+ }
169
+ return createDiagnostic("RULE_FAILED", context.phase, {
170
+ messageKey,
171
+ details: ruleDetails,
172
+ severity: severity ?? rule.defaultSeverity,
173
+ ...optionalDiagnosticFields(context, path),
174
+ });
175
+ }
176
+ function isRulePhase(value) {
177
+ return (value === "input" ||
178
+ value === "normalization" ||
179
+ value === "record" ||
180
+ value === "link" ||
181
+ value === "chain");
182
+ }
183
+ function ruleFailure(code, limits) {
184
+ void limits;
185
+ return failure([createDiagnostic(code, "input")]);
186
+ }
187
+ function failureOptions(context, causeCode) {
188
+ return {
189
+ causeCode,
190
+ ...optionalDiagnosticFields(context),
191
+ };
192
+ }
193
+ function optionalDiagnosticFields(context, path) {
194
+ return {
195
+ ...(path === undefined ? {} : { path }),
196
+ ...(context.recordId === undefined ? {} : { recordId: context.recordId }),
197
+ ...(context.position === undefined ? {} : { position: context.position }),
198
+ };
199
+ }
200
+ function compareText(left, right) {
201
+ if (left < right)
202
+ return -1;
203
+ if (left > right)
204
+ return 1;
205
+ return 0;
206
+ }
@@ -0,0 +1,2 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ export { SCHEMA_ASSETS } from "./contracts/schema-assets.js";
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { success } from "../domain/operation-result.js";
3
+ import { failureWithCode } from "./result-helpers.js";
4
+ export function validateAlgorithmId(value) {
5
+ if (value === "sha-256" || value === "sha-384" || value === "sha-512") {
6
+ return success(value);
7
+ }
8
+ return failureWithCode("ALGORITHM_UNKNOWN", "input");
9
+ }
@@ -0,0 +1,154 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { DIAGNOSTIC_CODES } from "../contracts/generated.js";
3
+ import { DIAGNOSTIC_SCHEMA } from "../domain/diagnostic.js";
4
+ import { isDataPropertyDescriptor } from "./object-inspection.js";
5
+ export function createDiagnostic(code, phase, options = {}) {
6
+ const metadata = DIAGNOSTIC_CODES.find((entry) => entry.code === code);
7
+ if (metadata === undefined) {
8
+ throw new Error(`Unknown generated diagnostic code: ${code}`);
9
+ }
10
+ const details = options.details === undefined ? undefined : tryCopyDetails(options.details);
11
+ const path = safePath(options.path);
12
+ const recordId = safeString(options.recordId, /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u, 128);
13
+ const causeCode = safeString(options.causeCode, /^[A-Z][A-Z0-9_]{1,127}$/u, 128);
14
+ const position = safePosition(options.position);
15
+ const diagnostic = {
16
+ $schema: DIAGNOSTIC_SCHEMA,
17
+ code,
18
+ severity: code === "RULE_FAILED" && options.severity !== undefined
19
+ ? options.severity
20
+ : metadata.severity,
21
+ phase,
22
+ messageKey: safeString(options.messageKey, /^[a-z][a-z0-9.-]{0,127}$/u, 128) ??
23
+ code.toLowerCase().replaceAll("_", "."),
24
+ ...(path === undefined ? {} : { path }),
25
+ ...(recordId === undefined ? {} : { recordId }),
26
+ ...(position === undefined ? {} : { position }),
27
+ ...(details === undefined ? {} : { details }),
28
+ ...(causeCode === undefined ? {} : { causeCode }),
29
+ };
30
+ return Object.freeze(diagnostic);
31
+ }
32
+ function tryCopyDetails(value) {
33
+ try {
34
+ return copyDetails(value);
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ function safeString(value, pattern, maxLength) {
41
+ return typeof value === "string" && value.length <= maxLength && pattern.test(value)
42
+ ? value
43
+ : undefined;
44
+ }
45
+ function safePath(value) {
46
+ if (typeof value !== "string" || value.length > 512) {
47
+ return undefined;
48
+ }
49
+ if (value === "" || /^[a-z][a-z0-9.-]{0,127}$/u.test(value)) {
50
+ return value;
51
+ }
52
+ if (!value.startsWith("/")) {
53
+ return undefined;
54
+ }
55
+ const segments = value.split("/");
56
+ return segments.slice(1).every(isJsonPointerSegment) ? value : undefined;
57
+ }
58
+ function isJsonPointerSegment(segment) {
59
+ if (segment.length === 0) {
60
+ return false;
61
+ }
62
+ for (let index = 0; index < segment.length; index += 1) {
63
+ const character = segment[index];
64
+ if (character === "~") {
65
+ const escape = segment[index + 1];
66
+ if (escape !== "0" && escape !== "1") {
67
+ return false;
68
+ }
69
+ index += 1;
70
+ continue;
71
+ }
72
+ if (character === undefined || !/[A-Za-z0-9._~-]/u.test(character)) {
73
+ return false;
74
+ }
75
+ }
76
+ return true;
77
+ }
78
+ function safePosition(value) {
79
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
80
+ }
81
+ function copyDetails(value) {
82
+ const keys = Object.keys(value);
83
+ if (Object.getOwnPropertySymbols(value).length !== 0 || keys.length > 32) {
84
+ throw new Error("invalid diagnostic details");
85
+ }
86
+ const output = {};
87
+ for (const key of keys) {
88
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
89
+ if (!/^[a-z][a-z0-9.-]{0,63}$/u.test(key) ||
90
+ descriptor === undefined ||
91
+ !isDataPropertyDescriptor(descriptor)) {
92
+ throw new Error("invalid diagnostic details");
93
+ }
94
+ const detail = descriptor.value;
95
+ if (detail !== null &&
96
+ typeof detail !== "boolean" &&
97
+ (typeof detail !== "number" || !Number.isFinite(detail)) &&
98
+ (typeof detail !== "string" || detail.length > 256)) {
99
+ throw new Error("invalid diagnostic details");
100
+ }
101
+ Object.defineProperty(output, key, {
102
+ configurable: false,
103
+ enumerable: true,
104
+ value: detail,
105
+ writable: false,
106
+ });
107
+ }
108
+ return Object.freeze(output);
109
+ }
110
+ export class DiagnosticCollector {
111
+ limits;
112
+ entries = [];
113
+ truncated = false;
114
+ constructor(limits) {
115
+ this.limits = limits;
116
+ }
117
+ add(diagnostic) {
118
+ if (this.entries.length < this.limits.maxDiagnostics) {
119
+ this.entries.push(diagnostic);
120
+ return;
121
+ }
122
+ if (!this.truncated) {
123
+ this.truncated = true;
124
+ this.entries[this.entries.length - 1] = createDiagnostic("DIAGNOSTIC_LIMIT_REACHED", "output");
125
+ }
126
+ }
127
+ addCode(code, phase, options) {
128
+ this.add(createDiagnostic(code, phase, options));
129
+ }
130
+ hasErrors() {
131
+ return this.entries.some(({ severity }) => severity === "error");
132
+ }
133
+ finish() {
134
+ if (this.entries.length === 0)
135
+ return Object.freeze([]);
136
+ const sorted = [...this.entries].sort(compareDiagnostics);
137
+ return Object.freeze(sorted);
138
+ }
139
+ }
140
+ function compareDiagnostics(left, right) {
141
+ const leftPosition = left.position ?? Number.MAX_SAFE_INTEGER;
142
+ const rightPosition = right.position ?? Number.MAX_SAFE_INTEGER;
143
+ return (leftPosition - rightPosition ||
144
+ compareText(left.phase, right.phase) ||
145
+ compareText(left.code, right.code) ||
146
+ compareText(left.path ?? "", right.path ?? ""));
147
+ }
148
+ function compareText(left, right) {
149
+ if (left < right)
150
+ return -1;
151
+ if (left > right)
152
+ return 1;
153
+ return 0;
154
+ }
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { Digest } from "../domain/digest.js";
3
+ import { success } from "../domain/operation-result.js";
4
+ import { failureWithCode } from "./result-helpers.js";
5
+ import { validateAlgorithmId } from "./algorithm-validation.js";
6
+ const digestLengths = Object.freeze({
7
+ "sha-256": 32,
8
+ "sha-384": 48,
9
+ "sha-512": 64,
10
+ });
11
+ export function digestLength(algorithm) {
12
+ return digestLengths[algorithm];
13
+ }
14
+ export function validateDigest(value, algorithm) {
15
+ const algorithmResult = validateAlgorithmId(algorithm);
16
+ if (!algorithmResult.ok)
17
+ return algorithmResult;
18
+ if (typeof value !== "string" || !/^(?:[0-9a-f]{2})+$/u.test(value)) {
19
+ return failureWithCode("DIGEST_ENCODING_INVALID", "input");
20
+ }
21
+ const expectedLength = digestLength(algorithmResult.value) * 2;
22
+ if (value.length !== expectedLength) {
23
+ return failureWithCode("DIGEST_LENGTH_INVALID", "input");
24
+ }
25
+ const bytes = new Uint8Array(expectedLength / 2);
26
+ for (let index = 0; index < bytes.length; index += 1) {
27
+ bytes[index] = Number.parseInt(value.slice(index * 2, index * 2 + 2), 16);
28
+ }
29
+ return success(Digest.fromValidated(algorithmResult.value, bytes));
30
+ }
31
+ export function equalDigest(left, right) {
32
+ if (left.algorithm !== right.algorithm)
33
+ return false;
34
+ const leftBytes = left.toBytes();
35
+ const rightBytes = right.toBytes();
36
+ const leftView = new DataView(leftBytes.buffer, leftBytes.byteOffset, leftBytes.byteLength);
37
+ const rightView = new DataView(rightBytes.buffer, rightBytes.byteOffset, rightBytes.byteLength);
38
+ let difference = 0;
39
+ for (let index = 0; index < leftBytes.length; index += 1) {
40
+ difference |= leftView.getUint8(index) ^ rightView.getUint8(index);
41
+ }
42
+ return difference === 0;
43
+ }