@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,652 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { CHAIN_SUMMARY_EVIDENCE_SCHEMA } from "../domain/evidence.js";
3
+ import { buildTrustedLinkFrame } from "../framing/frame-builders.js";
4
+ import { hashTrustedFrame } from "../hashing/hash-frame.js";
5
+ import { parseLinkEvidence } from "../evidence/evidence-parser.js";
6
+ import { computeRecord, validateProfile, } from "../records/record-service.js";
7
+ import { RuleSet } from "../rules/rule-set.js";
8
+ import { DiagnosticCollector } from "../validation/diagnostic-collector.js";
9
+ import { validateAlgorithmId } from "../validation/algorithm-validation.js";
10
+ import { equalDigest, validateDigest } from "../validation/digest-validation.js";
11
+ import { validateContextId, validateSequenceId } from "../validation/identifier-validation.js";
12
+ import { inspectPlainObject } from "../validation/object-inspection.js";
13
+ import { createDuplicateDetector, observedLink } from "./duplicate-detector.js";
14
+ export function verifyChain(input, options) {
15
+ const config = validateVerificationConfig(input, options.limits, "sync");
16
+ if (!config.ok)
17
+ return invalidResult(config.diagnostics, "internal");
18
+ const rules = RuleSet.create(config.value.rules, options.limits);
19
+ if (!rules.ok)
20
+ return invalidResult(rules.diagnostics, config.value.mode);
21
+ const verifier = new ChainVerificationAccumulator(config.value, options, rules.value);
22
+ if (!isIterable(config.value.records))
23
+ return invalidResult([], config.value.mode);
24
+ try {
25
+ for (const item of config.value.records)
26
+ verifier.process(item);
27
+ }
28
+ catch {
29
+ return verifier.streamFailure();
30
+ }
31
+ return verifier.finish();
32
+ }
33
+ export async function verifyChainStream(input, options, signal) {
34
+ const config = validateVerificationConfig(input, options.limits, "async");
35
+ if (!config.ok)
36
+ return invalidResult(config.diagnostics, "internal");
37
+ const rules = RuleSet.create(config.value.rules, options.limits);
38
+ if (!rules.ok)
39
+ return invalidResult(rules.diagnostics, config.value.mode);
40
+ const verifier = new ChainVerificationAccumulator(config.value, options, rules.value);
41
+ const asyncRecords = config.value.records;
42
+ if (!isAsyncIterable(asyncRecords))
43
+ return invalidResult([], config.value.mode);
44
+ let iterator;
45
+ try {
46
+ if (isAborted(signal))
47
+ return verifier.aborted();
48
+ iterator = asyncRecords[Symbol.asyncIterator]();
49
+ for (;;) {
50
+ if (isAborted(signal))
51
+ return verifier.aborted();
52
+ let next;
53
+ try {
54
+ next = await iterator.next();
55
+ }
56
+ catch {
57
+ return verifier.streamFailure();
58
+ }
59
+ if (next.done === true) {
60
+ if (isAborted(signal))
61
+ return verifier.aborted();
62
+ return verifier.finish();
63
+ }
64
+ verifier.process(next.value);
65
+ if (isAborted(signal))
66
+ return verifier.aborted();
67
+ }
68
+ }
69
+ finally {
70
+ if (iterator !== undefined && isAborted(signal)) {
71
+ try {
72
+ await iterator.return?.();
73
+ }
74
+ catch {
75
+ // The requested abort result remains authoritative; no payload is exposed.
76
+ }
77
+ }
78
+ }
79
+ }
80
+ class ChainVerificationAccumulator {
81
+ config;
82
+ options;
83
+ rules;
84
+ collector;
85
+ duplicateDetector;
86
+ previous;
87
+ first;
88
+ last;
89
+ expectedPosition;
90
+ bytesNormalized = 0;
91
+ recordsVerified = 0;
92
+ linksVerified = 0;
93
+ recordsSeen = 0;
94
+ fatal = false;
95
+ constructor(config, options, rules) {
96
+ this.config = config;
97
+ this.options = options;
98
+ this.rules = rules;
99
+ this.collector = new DiagnosticCollector(options.limits);
100
+ this.duplicateDetector = createDuplicateDetector(config.duplicatePolicy, options.limits);
101
+ }
102
+ process(item) {
103
+ this.recordsSeen += 1;
104
+ const parsedItem = parseVerificationItem(item, this.options.limits);
105
+ if (!parsedItem.ok) {
106
+ addAll(this.collector, parsedItem.diagnostics);
107
+ this.fatal = true;
108
+ return;
109
+ }
110
+ const evidence = parseLinkEvidence(parsedItem.value.evidence, this.options.limits);
111
+ if (!evidence.ok) {
112
+ addAll(this.collector, evidence.diagnostics);
113
+ this.fatal = true;
114
+ return;
115
+ }
116
+ if (this.rules.hasRules()) {
117
+ addAll(this.collector, this.rules.evaluate(Object.freeze({
118
+ phase: "link",
119
+ link: evidence.value,
120
+ recordId: evidence.value.recordId,
121
+ position: evidence.value.position,
122
+ }), this.options.limits));
123
+ }
124
+ if (this.first === undefined) {
125
+ this.first = evidence.value;
126
+ this.expectedPosition = initialPosition(this.config, evidence.value);
127
+ }
128
+ checkMetadata(evidence.value, this.config, this.collector);
129
+ if (this.expectedPosition !== undefined && evidence.value.position !== this.expectedPosition) {
130
+ this.collector.addCode("POSITION_MISMATCH", "chain", { position: evidence.value.position });
131
+ }
132
+ if (this.previous !== undefined &&
133
+ !samePrevious(evidence.value.previous, this.previous, this.config.algorithm)) {
134
+ this.collector.addCode("PREVIOUS_LINK_MISMATCH", "chain", {
135
+ position: evidence.value.position,
136
+ });
137
+ }
138
+ if (this.previous === undefined)
139
+ checkStartBoundary(evidence.value, this.config, this.collector);
140
+ const record = computeRecord({
141
+ contextId: this.config.contextId,
142
+ recordId: evidence.value.recordId,
143
+ payload: parsedItem.value.payload,
144
+ profile: this.config.profile,
145
+ algorithm: this.config.algorithm,
146
+ }, recordOptions(this.options, this.rules));
147
+ if (!record.ok) {
148
+ addAll(this.collector, record.diagnostics);
149
+ this.fatal = true;
150
+ }
151
+ else {
152
+ this.bytesNormalized += record.value.evidence.normalizedByteLength;
153
+ if (!sameDigest(record.value.evidence.contentDigest, evidence.value.contentDigest, this.config.algorithm)) {
154
+ this.collector.addCode("CONTENT_DIGEST_MISMATCH", "record", {
155
+ recordId: evidence.value.recordId,
156
+ position: evidence.value.position,
157
+ });
158
+ }
159
+ if (!sameDigest(record.value.evidence.recordDigest, evidence.value.recordDigest, this.config.algorithm)) {
160
+ this.collector.addCode("RECORD_DIGEST_MISMATCH", "record", {
161
+ recordId: evidence.value.recordId,
162
+ position: evidence.value.position,
163
+ });
164
+ }
165
+ else {
166
+ this.recordsVerified += 1;
167
+ }
168
+ }
169
+ const selfLink = recomposeLink(evidence.value, this.options.limits);
170
+ if (!selfLink.ok) {
171
+ addAll(this.collector, selfLink.diagnostics);
172
+ this.fatal = true;
173
+ }
174
+ else if (!sameDigest(selfLink.value.toHex(), evidence.value.linkDigest, this.config.algorithm)) {
175
+ this.collector.addCode("LINK_DIGEST_MISMATCH", "link", {
176
+ recordId: evidence.value.recordId,
177
+ position: evidence.value.position,
178
+ });
179
+ }
180
+ else {
181
+ this.linksVerified += 1;
182
+ }
183
+ const observed = observedLink(evidence.value);
184
+ const duplicates = this.duplicateDetector.inspect(observed);
185
+ if (!duplicates.ok) {
186
+ addAll(this.collector, duplicates.diagnostics);
187
+ this.fatal = true;
188
+ }
189
+ else {
190
+ addAll(this.collector, duplicates.value);
191
+ try {
192
+ this.duplicateDetector.commit(observed);
193
+ }
194
+ catch {
195
+ this.collector.addCode("INTERNAL_INVARIANT_BROKEN", "chain");
196
+ this.fatal = true;
197
+ }
198
+ }
199
+ this.previous = Object.freeze({ kind: "digest", value: evidence.value.linkDigest });
200
+ this.expectedPosition = evidence.value.position + 1;
201
+ this.last = evidence.value;
202
+ }
203
+ finish() {
204
+ if (this.recordsSeen === 0 && !this.config.allowEmpty) {
205
+ this.collector.addCode("EMPTY_CHAIN_FORBIDDEN", "chain");
206
+ }
207
+ const boundaries = determineBoundaries(this.config, this.first, this.last, this.recordsSeen, this.collector);
208
+ if (this.rules.hasRules()) {
209
+ addAll(this.collector, this.rules.evaluate(Object.freeze({
210
+ phase: "chain",
211
+ chain: Object.freeze({ count: this.recordsSeen, boundaries, mode: this.config.mode }),
212
+ }), this.options.limits));
213
+ }
214
+ const diagnostics = this.collector.finish();
215
+ const status = diagnostics.some(({ severity }) => severity === "error") || this.fatal
216
+ ? "invalid"
217
+ : this.config.mode === "complete" &&
218
+ (boundaries.start === "unverified" || boundaries.end === "unverified")
219
+ ? "indeterminate"
220
+ : "valid";
221
+ return Object.freeze({
222
+ status,
223
+ diagnostics,
224
+ evidence: buildSummary(this.config, this.first, this.last, this.recordsSeen, diagnostics, status, boundaries),
225
+ stats: buildStats(this.recordsSeen, this.recordsVerified, this.linksVerified, this.bytesNormalized, diagnostics),
226
+ boundaries,
227
+ verificationMode: this.config.mode,
228
+ });
229
+ }
230
+ streamFailure() {
231
+ this.collector.addCode("INPUT_STREAM_FAILED", "output");
232
+ return this.failureResult("invalid");
233
+ }
234
+ aborted() {
235
+ this.collector.addCode("OPERATION_ABORTED", "output");
236
+ return this.failureResult("aborted");
237
+ }
238
+ failureResult(status) {
239
+ const diagnostics = this.collector.finish();
240
+ return Object.freeze({
241
+ status,
242
+ diagnostics,
243
+ evidence: undefined,
244
+ stats: buildStats(this.recordsSeen, this.recordsVerified, this.linksVerified, this.bytesNormalized, diagnostics),
245
+ boundaries: Object.freeze({
246
+ start: "not-applicable",
247
+ end: "not-applicable",
248
+ }),
249
+ verificationMode: this.config.mode,
250
+ });
251
+ }
252
+ }
253
+ function validateVerificationConfig(value, limits, kind) {
254
+ const entries = inspectPlainObject(value);
255
+ if (entries === undefined)
256
+ return verificationFailure(limits);
257
+ const fields = new Map(entries);
258
+ const allowed = new Set([
259
+ "contextId",
260
+ "sequenceId",
261
+ "profile",
262
+ "algorithm",
263
+ "mode",
264
+ "records",
265
+ "expectedCount",
266
+ "expectedFinalLinkDigest",
267
+ "expectedPrevious",
268
+ "startPosition",
269
+ "allowEmpty",
270
+ "duplicatePolicy",
271
+ "rules",
272
+ ]);
273
+ for (const key of fields.keys())
274
+ if (!allowed.has(key))
275
+ return verificationFailure(limits);
276
+ for (const key of ["contextId", "sequenceId", "profile", "algorithm", "mode", "records"]) {
277
+ if (!fields.has(key))
278
+ return verificationFailure(limits);
279
+ }
280
+ const contextId = validateContextId(fields.get("contextId"));
281
+ const sequenceId = validateSequenceId(fields.get("sequenceId"));
282
+ const profile = validateProfile(fields.get("profile"), limits);
283
+ const algorithm = validateAlgorithmId(fields.get("algorithm"));
284
+ const mode = fields.get("mode");
285
+ const records = fields.get("records");
286
+ const allowEmpty = fields.get("allowEmpty") ?? false;
287
+ if (!contextId.ok ||
288
+ !sequenceId.ok ||
289
+ !profile.ok ||
290
+ !algorithm.ok ||
291
+ !isMode(mode) ||
292
+ typeof allowEmpty !== "boolean") {
293
+ return verificationFailure(limits);
294
+ }
295
+ const recordsResult = kind === "sync" ? asIterable(records, limits) : asAsyncIterable(records, limits);
296
+ if (!recordsResult.ok)
297
+ return recordsResult;
298
+ const expectedCount = optionalSafeInteger(fields.get("expectedCount"));
299
+ if (fields.has("expectedCount") && expectedCount === undefined)
300
+ return verificationFailure(limits);
301
+ const expectedFinal = fields.has("expectedFinalLinkDigest")
302
+ ? validateDigest(fields.get("expectedFinalLinkDigest"), algorithm.value)
303
+ : undefined;
304
+ if (expectedFinal !== undefined && !expectedFinal.ok)
305
+ return verificationFailure(limits);
306
+ const expectedPrevious = fields.has("expectedPrevious")
307
+ ? parsePrevious(fields.get("expectedPrevious"), algorithm.value, limits)
308
+ : undefined;
309
+ if (expectedPrevious !== undefined && !expectedPrevious.ok)
310
+ return verificationFailure(limits);
311
+ const startPosition = optionalSafeInteger(fields.get("startPosition"));
312
+ if (fields.has("startPosition") && startPosition === undefined)
313
+ return verificationFailure(limits);
314
+ const duplicatePolicy = parseDuplicatePolicy(fields.get("duplicatePolicy"), limits);
315
+ if (!duplicatePolicy.ok)
316
+ return duplicatePolicy;
317
+ return {
318
+ ok: true,
319
+ value: Object.freeze({
320
+ contextId: contextId.value.value,
321
+ sequenceId: sequenceId.value.value,
322
+ profile: profile.value,
323
+ algorithm: algorithm.value,
324
+ mode,
325
+ records: recordsResult.value,
326
+ ...(expectedCount === undefined ? {} : { expectedCount }),
327
+ ...(expectedFinal === undefined
328
+ ? {}
329
+ : { expectedFinalLinkDigest: expectedFinal.value.toHex() }),
330
+ ...(expectedPrevious === undefined ? {} : { expectedPrevious: expectedPrevious.value }),
331
+ ...(startPosition === undefined ? {} : { startPosition }),
332
+ allowEmpty,
333
+ duplicatePolicy: duplicatePolicy.value,
334
+ rules: fields.get("rules"),
335
+ }),
336
+ diagnostics: Object.freeze([]),
337
+ };
338
+ }
339
+ function parseVerificationItem(value, limits) {
340
+ const entries = inspectPlainObject(value);
341
+ if (entries === undefined)
342
+ return verificationFailure(limits);
343
+ const fields = new Map(entries);
344
+ if (fields.size !== 2 || !fields.has("payload") || !fields.has("evidence"))
345
+ return verificationFailure(limits);
346
+ return {
347
+ ok: true,
348
+ value: Object.freeze({ payload: fields.get("payload"), evidence: fields.get("evidence") }),
349
+ diagnostics: Object.freeze([]),
350
+ };
351
+ }
352
+ function checkMetadata(evidence, config, collector) {
353
+ if (evidence.contextId !== config.contextId ||
354
+ evidence.sequenceId !== config.sequenceId ||
355
+ evidence.profile.id !== config.profile.id ||
356
+ evidence.profile.version !== config.profile.version ||
357
+ evidence.algorithm !== config.algorithm)
358
+ collector.addCode("CHAIN_CONFIGURATION_MISMATCH", "chain", {
359
+ recordId: evidence.recordId,
360
+ position: evidence.position,
361
+ });
362
+ }
363
+ function checkStartBoundary(evidence, config, collector) {
364
+ if (config.mode === "complete") {
365
+ if (evidence.position !== 0 || evidence.previous.kind !== "none") {
366
+ collector.addCode("POSITION_MISMATCH", "chain", { position: evidence.position });
367
+ }
368
+ return;
369
+ }
370
+ if (config.mode === "fragment") {
371
+ if (config.startPosition !== undefined && evidence.position !== config.startPosition) {
372
+ collector.addCode("POSITION_MISMATCH", "chain", { position: evidence.position });
373
+ }
374
+ if (config.expectedPrevious !== undefined &&
375
+ !samePrevious(evidence.previous, config.expectedPrevious, config.algorithm)) {
376
+ collector.addCode("PREVIOUS_LINK_MISMATCH", "chain", { position: evidence.position });
377
+ }
378
+ }
379
+ }
380
+ function determineBoundaries(config, first, last, count, collector) {
381
+ if (config.mode === "internal")
382
+ return Object.freeze({ start: "not-applicable", end: "not-applicable" });
383
+ let start = "unverified";
384
+ let end = "unverified";
385
+ if (config.mode === "complete") {
386
+ start = first?.position === 0 && first.previous.kind === "none" ? "verified" : "unverified";
387
+ const countVerified = config.expectedCount !== undefined && config.expectedCount === count;
388
+ if (config.expectedCount === undefined) {
389
+ collector.addCode("BOUNDARY_UNVERIFIED", "chain");
390
+ }
391
+ else if (!countVerified) {
392
+ collector.addCode("EXPECTED_COUNT_MISMATCH", "chain");
393
+ }
394
+ if (config.expectedFinalLinkDigest === undefined) {
395
+ collector.addCode("BOUNDARY_UNVERIFIED", "chain");
396
+ }
397
+ else if (last === undefined ||
398
+ !sameDigest(last.linkDigest, config.expectedFinalLinkDigest, config.algorithm)) {
399
+ collector.addCode("FINAL_LINK_MISMATCH", "chain");
400
+ }
401
+ else if (countVerified) {
402
+ end = "verified";
403
+ }
404
+ if (start === "unverified")
405
+ collector.addCode("BOUNDARY_UNVERIFIED", "chain");
406
+ return Object.freeze({ start, end });
407
+ }
408
+ if (config.expectedPrevious !== undefined) {
409
+ start =
410
+ first !== undefined && samePrevious(first.previous, config.expectedPrevious, config.algorithm)
411
+ ? "verified"
412
+ : "unverified";
413
+ }
414
+ if (config.expectedFinalLinkDigest !== undefined) {
415
+ end =
416
+ last !== undefined &&
417
+ sameDigest(last.linkDigest, config.expectedFinalLinkDigest, config.algorithm)
418
+ ? "verified"
419
+ : "unverified";
420
+ if (end === "unverified")
421
+ collector.addCode("FINAL_LINK_MISMATCH", "chain");
422
+ }
423
+ if (start === "unverified")
424
+ collector.addCode("BOUNDARY_UNVERIFIED", "chain");
425
+ if (end === "unverified")
426
+ collector.addCode("BOUNDARY_UNVERIFIED", "chain");
427
+ return Object.freeze({ start, end });
428
+ }
429
+ function recomposeLink(evidence, limits) {
430
+ const recordDigest = validateDigest(evidence.recordDigest, evidence.algorithm);
431
+ if (!recordDigest.ok)
432
+ return recordDigest;
433
+ const previous = evidence.previous.kind === "digest"
434
+ ? validateDigest(evidence.previous.value, evidence.algorithm)
435
+ : undefined;
436
+ if (previous !== undefined && !previous.ok)
437
+ return previous;
438
+ return hashTrustedFrame(evidence.algorithm, buildTrustedLinkFrame({
439
+ algorithm: evidence.algorithm,
440
+ contextId: evidence.contextId,
441
+ sequenceId: evidence.sequenceId,
442
+ position: evidence.position,
443
+ recordId: evidence.recordId,
444
+ recordDigest: recordDigest.value,
445
+ ...(previous === undefined ? {} : { previousLinkDigest: previous.value }),
446
+ }, limits));
447
+ }
448
+ function buildSummary(config, first, last, count, diagnostics, status, boundaries) {
449
+ const base = {
450
+ $schema: CHAIN_SUMMARY_EVIDENCE_SCHEMA,
451
+ protocolVersion: 1,
452
+ contextId: config.contextId,
453
+ sequenceId: config.sequenceId,
454
+ profile: Object.freeze({ ...config.profile }),
455
+ algorithm: config.algorithm,
456
+ count,
457
+ boundaries,
458
+ status,
459
+ diagnostics: diagnosticSummary(diagnostics),
460
+ };
461
+ if (first === undefined || last === undefined)
462
+ return Object.freeze(base);
463
+ return Object.freeze({
464
+ ...base,
465
+ firstPosition: first.position,
466
+ lastPosition: last.position,
467
+ firstLinkDigest: first.linkDigest,
468
+ finalLinkDigest: last.linkDigest,
469
+ });
470
+ }
471
+ function buildStats(recordsSeen, recordsVerified, linksVerified, bytesNormalized, diagnostics) {
472
+ const summary = diagnosticSummary(diagnostics);
473
+ return Object.freeze({
474
+ recordsSeen,
475
+ recordsVerified,
476
+ linksVerified,
477
+ bytesNormalized,
478
+ ...summary,
479
+ });
480
+ }
481
+ function diagnosticSummary(diagnostics) {
482
+ let errors = 0;
483
+ let warnings = 0;
484
+ let info = 0;
485
+ for (const diagnostic of diagnostics) {
486
+ if (diagnostic.severity === "error")
487
+ errors += 1;
488
+ else if (diagnostic.severity === "warning")
489
+ warnings += 1;
490
+ else
491
+ info += 1;
492
+ }
493
+ return Object.freeze({
494
+ errors,
495
+ warnings,
496
+ info,
497
+ truncated: diagnostics.some(({ code }) => code === "DIAGNOSTIC_LIMIT_REACHED"),
498
+ });
499
+ }
500
+ function recordOptions(options, rules) {
501
+ return Object.freeze({ limits: options.limits, profiles: options.profiles, rules });
502
+ }
503
+ function initialPosition(config, evidence) {
504
+ if (config.mode === "fragment" && config.startPosition !== undefined)
505
+ return config.startPosition;
506
+ return evidence.position;
507
+ }
508
+ function samePrevious(left, right, algorithm) {
509
+ if (left.kind !== right.kind)
510
+ return false;
511
+ if (left.kind === "none" || right.kind === "none")
512
+ return true;
513
+ return sameDigest(left.value, right.value, algorithm);
514
+ }
515
+ function sameDigest(left, right, algorithm) {
516
+ const leftDigest = validateDigest(left, algorithm);
517
+ const rightDigest = validateDigest(right, algorithm);
518
+ return leftDigest.ok && rightDigest.ok && equalDigest(leftDigest.value, rightDigest.value);
519
+ }
520
+ function parsePrevious(value, algorithm, limits) {
521
+ const entries = inspectPlainObject(value);
522
+ if (entries === undefined)
523
+ return verificationFailure(limits);
524
+ const fields = new Map(entries);
525
+ if (fields.get("kind") === "none" && fields.size === 1)
526
+ return {
527
+ ok: true,
528
+ value: Object.freeze({ kind: "none" }),
529
+ diagnostics: Object.freeze([]),
530
+ };
531
+ if (fields.get("kind") !== "digest" || fields.size !== 2)
532
+ return verificationFailure(limits);
533
+ const digest = validateDigest(fields.get("value"), algorithm);
534
+ if (!digest.ok)
535
+ return verificationFailure(limits);
536
+ return {
537
+ ok: true,
538
+ value: Object.freeze({ kind: "digest", value: digest.value.toHex() }),
539
+ diagnostics: Object.freeze([]),
540
+ };
541
+ }
542
+ function parseDuplicatePolicy(value, limits) {
543
+ if (value === undefined)
544
+ return {
545
+ ok: true,
546
+ value: Object.freeze({ kind: "none" }),
547
+ diagnostics: Object.freeze([]),
548
+ };
549
+ const entries = inspectPlainObject(value);
550
+ if (entries === undefined)
551
+ return verificationFailure(limits);
552
+ const fields = new Map(entries);
553
+ const kind = fields.get("kind");
554
+ if (kind === "none" && fields.size === 1)
555
+ return { ok: true, value: Object.freeze({ kind }), diagnostics: Object.freeze([]) };
556
+ if (kind === "window" && fields.size === 2) {
557
+ const size = fields.get("size");
558
+ if (typeof size !== "number" ||
559
+ !Number.isSafeInteger(size) ||
560
+ size < 1 ||
561
+ size > limits.maxFullRecords)
562
+ return verificationFailure(limits);
563
+ return {
564
+ ok: true,
565
+ value: Object.freeze({ kind, size }),
566
+ diagnostics: Object.freeze([]),
567
+ };
568
+ }
569
+ if (kind === "full" && fields.size === 2) {
570
+ const maxRecords = fields.get("maxRecords");
571
+ if (typeof maxRecords !== "number" ||
572
+ !Number.isSafeInteger(maxRecords) ||
573
+ maxRecords < 1 ||
574
+ maxRecords > limits.maxFullRecords)
575
+ return verificationFailure(limits);
576
+ return {
577
+ ok: true,
578
+ value: Object.freeze({ kind, maxRecords }),
579
+ diagnostics: Object.freeze([]),
580
+ };
581
+ }
582
+ if (kind === "external" && fields.size === 2 && fields.has("index")) {
583
+ const indexEntries = inspectPlainObject(fields.get("index"));
584
+ if (indexEntries === undefined)
585
+ return verificationFailure(limits);
586
+ const indexFields = new Map(indexEntries);
587
+ const observe = indexFields.get("observe");
588
+ if (indexFields.size !== 1 || typeof observe !== "function")
589
+ return verificationFailure(limits);
590
+ return {
591
+ ok: true,
592
+ value: Object.freeze({
593
+ kind,
594
+ index: Object.freeze({
595
+ observe(batch) {
596
+ return Reflect.apply(observe, undefined, [batch]);
597
+ },
598
+ }),
599
+ }),
600
+ diagnostics: Object.freeze([]),
601
+ };
602
+ }
603
+ return verificationFailure(limits);
604
+ }
605
+ function optionalSafeInteger(value) {
606
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
607
+ }
608
+ function isMode(value) {
609
+ return value === "complete" || value === "fragment" || value === "internal";
610
+ }
611
+ function addAll(collector, diagnostics) {
612
+ for (const diagnostic of diagnostics)
613
+ collector.add(diagnostic);
614
+ }
615
+ function verificationFailure(limits) {
616
+ const collector = new DiagnosticCollector(limits);
617
+ collector.addCode("INPUT_TYPE_INVALID", "input");
618
+ return { ok: false, diagnostics: collector.finish() };
619
+ }
620
+ function invalidResult(diagnostics, mode) {
621
+ return Object.freeze({
622
+ status: "invalid",
623
+ diagnostics,
624
+ evidence: undefined,
625
+ stats: buildStats(0, 0, 0, 0, diagnostics),
626
+ boundaries: Object.freeze({ start: "not-applicable", end: "not-applicable" }),
627
+ verificationMode: mode,
628
+ });
629
+ }
630
+ function isIterable(value) {
631
+ if (typeof value !== "object" || value === null)
632
+ return false;
633
+ return typeof Reflect.get(value, Symbol.iterator) === "function";
634
+ }
635
+ function asIterable(value, limits) {
636
+ return isIterable(value)
637
+ ? { ok: true, value, diagnostics: Object.freeze([]) }
638
+ : verificationFailure(limits);
639
+ }
640
+ function isAsyncIterable(value) {
641
+ if (typeof value !== "object" || value === null)
642
+ return false;
643
+ return typeof Reflect.get(value, Symbol.asyncIterator) === "function";
644
+ }
645
+ function asAsyncIterable(value, limits) {
646
+ return isAsyncIterable(value)
647
+ ? { ok: true, value, diagnostics: Object.freeze([]) }
648
+ : verificationFailure(limits);
649
+ }
650
+ function isAborted(signal) {
651
+ return signal?.aborted === true;
652
+ }