@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,27 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { ContextId as ContextIdValue, ProfileId as ProfileIdValue, RecordId as RecordIdValue, SequenceId as SequenceIdValue, } from "../domain/identifiers.js";
3
+ import { success } from "../domain/operation-result.js";
4
+ import { failureWithCode } from "./result-helpers.js";
5
+ const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u;
6
+ const profilePattern = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u;
7
+ export function validateRecordId(value) {
8
+ return validateIdentifier(value, (input) => RecordIdValue.fromValidated(input));
9
+ }
10
+ export function validateSequenceId(value) {
11
+ return validateIdentifier(value, (input) => SequenceIdValue.fromValidated(input));
12
+ }
13
+ export function validateContextId(value) {
14
+ return validateIdentifier(value, (input) => ContextIdValue.fromValidated(input));
15
+ }
16
+ export function validateProfileId(value) {
17
+ if (typeof value !== "string" || !profilePattern.test(value) || value.length > 128) {
18
+ return failureWithCode("IDENTIFIER_INVALID", "input");
19
+ }
20
+ return success(ProfileIdValue.fromValidated(value));
21
+ }
22
+ function validateIdentifier(value, create) {
23
+ if (typeof value !== "string" || !identifierPattern.test(value) || value.length > 128) {
24
+ return failureWithCode("IDENTIFIER_INVALID", "input");
25
+ }
26
+ return success(create(value));
27
+ }
@@ -0,0 +1,294 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { types } from "node:util";
3
+ import { failure, success } from "../domain/operation-result.js";
4
+ import { DiagnosticCollector } from "./diagnostic-collector.js";
5
+ import { validateJsonValue } from "./json-value-validation.js";
6
+ import { decodeUtf8, encodeUtf8, hasWellFormedUnicode } from "./utf8-validation.js";
7
+ import { failureWithCode } from "./result-helpers.js";
8
+ export function parseJsonText(input, limits) {
9
+ const source = decodeInput(input, limits);
10
+ if (!source.ok)
11
+ return source;
12
+ const parser = new JsonTextParser(source.value, limits);
13
+ const parsed = parser.parse();
14
+ if (!parsed.ok)
15
+ return parsed;
16
+ return validateJsonValue(parsed.value, limits);
17
+ }
18
+ function decodeInput(input, limits) {
19
+ if (types.isProxy(input))
20
+ return failureWithSyntax();
21
+ if (input instanceof Uint8Array) {
22
+ if (input.length > limits.maxPayloadBytes)
23
+ return failureWithCode("INPUT_LIMIT_EXCEEDED", "input");
24
+ return decodeUtf8(input);
25
+ }
26
+ if (typeof input !== "string")
27
+ return failureWithSyntax();
28
+ if (!hasWellFormedUnicode(input))
29
+ return failureWithSyntax();
30
+ const encoded = encodeUtf8(input, limits.maxPayloadBytes);
31
+ if (!encoded.ok)
32
+ return encoded;
33
+ return success(input);
34
+ }
35
+ function failureWithSyntax() {
36
+ return failureWithCode("JSON_SYNTAX_INVALID", "input");
37
+ }
38
+ class JsonTextParser {
39
+ source;
40
+ limits;
41
+ offset = 0;
42
+ constructor(source, limits) {
43
+ this.source = source;
44
+ this.limits = limits;
45
+ }
46
+ parse() {
47
+ try {
48
+ this.skipWhitespace();
49
+ const value = this.parseValue(0, "");
50
+ this.skipWhitespace();
51
+ if (this.offset !== this.source.length)
52
+ return this.syntaxFailure();
53
+ return success(value);
54
+ }
55
+ catch (error) {
56
+ if (error instanceof JsonParseFailure) {
57
+ const collector = new DiagnosticCollector(this.limits);
58
+ collector.addCode(error.code, "input", { path: error.path, position: this.offset });
59
+ return failure(collector.finish());
60
+ }
61
+ const collector = new DiagnosticCollector(this.limits);
62
+ collector.addCode("JSON_SYNTAX_INVALID", "input", { position: this.offset });
63
+ return failure(collector.finish());
64
+ }
65
+ }
66
+ parseValue(depth, path) {
67
+ if (depth > this.limits.maxJsonDepth)
68
+ this.fail("INPUT_LIMIT_EXCEEDED", path);
69
+ const character = this.source[this.offset];
70
+ if (character === "{")
71
+ return this.parseObject(depth, path);
72
+ if (character === "[")
73
+ return this.parseArray(depth, path);
74
+ if (character === '"')
75
+ return this.parseString(path);
76
+ if (character === "t")
77
+ return this.parseLiteral("true", true);
78
+ if (character === "f")
79
+ return this.parseLiteral("false", false);
80
+ if (character === "n")
81
+ return this.parseLiteral("null", null);
82
+ if (character === "-" || isDigit(character))
83
+ return this.parseNumber(path);
84
+ this.fail("JSON_SYNTAX_INVALID", path);
85
+ }
86
+ parseObject(depth, path) {
87
+ this.offset += 1;
88
+ const output = {};
89
+ const names = new Set();
90
+ this.skipWhitespace();
91
+ if (this.consume("}"))
92
+ return Object.freeze(output);
93
+ let propertyCount = 0;
94
+ while (this.offset < this.source.length) {
95
+ if (propertyCount >= this.limits.maxObjectProperties)
96
+ this.fail("INPUT_LIMIT_EXCEEDED", path);
97
+ if (this.source[this.offset] !== '"')
98
+ this.fail("JSON_SYNTAX_INVALID", path);
99
+ const key = this.parseString(path);
100
+ if (names.has(key))
101
+ this.fail("JSON_DUPLICATE_KEY", `${path}/${escapeJsonPointer(key)}`);
102
+ names.add(key);
103
+ this.skipWhitespace();
104
+ if (!this.consume(":"))
105
+ this.fail("JSON_SYNTAX_INVALID", path);
106
+ this.skipWhitespace();
107
+ const childPath = `${path}/${escapeJsonPointer(key)}`;
108
+ const value = this.parseValue(depth + 1, childPath);
109
+ Object.defineProperty(output, key, {
110
+ configurable: false,
111
+ enumerable: true,
112
+ value,
113
+ writable: false,
114
+ });
115
+ propertyCount += 1;
116
+ this.skipWhitespace();
117
+ if (this.consume("}"))
118
+ return Object.freeze(output);
119
+ if (!this.consume(","))
120
+ this.fail("JSON_SYNTAX_INVALID", path);
121
+ this.skipWhitespace();
122
+ }
123
+ this.fail("JSON_SYNTAX_INVALID", path);
124
+ }
125
+ parseArray(depth, path) {
126
+ this.offset += 1;
127
+ const output = [];
128
+ this.skipWhitespace();
129
+ if (this.consume("]"))
130
+ return Object.freeze(output);
131
+ while (this.offset < this.source.length) {
132
+ if (output.length >= this.limits.maxArrayElements)
133
+ this.fail("INPUT_LIMIT_EXCEEDED", path);
134
+ output.push(this.parseValue(depth + 1, `${path}/${String(output.length)}`));
135
+ this.skipWhitespace();
136
+ if (this.consume("]"))
137
+ return Object.freeze(output);
138
+ if (!this.consume(","))
139
+ this.fail("JSON_SYNTAX_INVALID", path);
140
+ this.skipWhitespace();
141
+ }
142
+ this.fail("JSON_SYNTAX_INVALID", path);
143
+ }
144
+ parseString(path) {
145
+ if (!this.consume('"'))
146
+ this.fail("JSON_SYNTAX_INVALID", path);
147
+ let output = "";
148
+ while (this.offset < this.source.length) {
149
+ const character = this.source.charAt(this.offset);
150
+ this.offset += 1;
151
+ if (character === '"') {
152
+ if (!hasWellFormedUnicode(output))
153
+ this.fail("UTF8_INVALID", path);
154
+ const encoded = encodeUtf8(output, this.limits.maxStringBytes);
155
+ if (!encoded.ok)
156
+ this.fail("INPUT_LIMIT_EXCEEDED", path);
157
+ return output;
158
+ }
159
+ if (character === "\\") {
160
+ output += this.parseEscape(path);
161
+ continue;
162
+ }
163
+ if (character.charCodeAt(0) <= 0x1f) {
164
+ this.fail("JSON_SYNTAX_INVALID", path);
165
+ }
166
+ output += character;
167
+ }
168
+ this.fail("JSON_SYNTAX_INVALID", path);
169
+ }
170
+ parseEscape(path) {
171
+ const escape = this.source[this.offset];
172
+ this.offset += 1;
173
+ const simple = {
174
+ '"': '"',
175
+ "\\": "\\",
176
+ "/": "/",
177
+ b: "\b",
178
+ f: "\f",
179
+ n: "\n",
180
+ r: "\r",
181
+ t: "\t",
182
+ };
183
+ const simpleValue = escape === undefined ? undefined : simple[escape];
184
+ if (simpleValue !== undefined)
185
+ return simpleValue;
186
+ if (escape !== "u")
187
+ this.fail("JSON_SYNTAX_INVALID", path);
188
+ const codePoint = this.parseHexCodeUnit(path);
189
+ if (codePoint >= 0xd800 && codePoint <= 0xdbff) {
190
+ if (this.source[this.offset] !== "\\" || this.source[this.offset + 1] !== "u") {
191
+ this.fail("UTF8_INVALID", path);
192
+ }
193
+ this.offset += 2;
194
+ const low = this.parseHexCodeUnit(path);
195
+ if (low < 0xdc00 || low > 0xdfff)
196
+ this.fail("UTF8_INVALID", path);
197
+ return String.fromCodePoint(0x10000 + ((codePoint - 0xd800) << 10) + low - 0xdc00);
198
+ }
199
+ if (codePoint >= 0xdc00 && codePoint <= 0xdfff)
200
+ this.fail("UTF8_INVALID", path);
201
+ return String.fromCharCode(codePoint);
202
+ }
203
+ parseHexCodeUnit(path) {
204
+ const value = this.source.slice(this.offset, this.offset + 4);
205
+ if (!/^[0-9a-fA-F]{4}$/u.test(value))
206
+ this.fail("JSON_SYNTAX_INVALID", path);
207
+ this.offset += 4;
208
+ return Number.parseInt(value, 16);
209
+ }
210
+ parseNumber(path) {
211
+ const start = this.offset;
212
+ if (this.consume("-")) {
213
+ if (!isDigit(this.source[this.offset]))
214
+ this.fail("JSON_SYNTAX_INVALID", path);
215
+ }
216
+ if (this.consume("0")) {
217
+ if (isDigit(this.source[this.offset]))
218
+ this.fail("JSON_SYNTAX_INVALID", path);
219
+ }
220
+ else {
221
+ if (!isNonZeroDigit(this.source[this.offset]))
222
+ this.fail("JSON_SYNTAX_INVALID", path);
223
+ while (isDigit(this.source[this.offset]))
224
+ this.offset += 1;
225
+ }
226
+ if (this.consume(".")) {
227
+ if (!isDigit(this.source[this.offset]))
228
+ this.fail("JSON_SYNTAX_INVALID", path);
229
+ while (isDigit(this.source[this.offset]))
230
+ this.offset += 1;
231
+ }
232
+ if (this.source[this.offset] === "e" || this.source[this.offset] === "E") {
233
+ this.offset += 1;
234
+ if (this.source[this.offset] === "+" || this.source[this.offset] === "-")
235
+ this.offset += 1;
236
+ if (!isDigit(this.source[this.offset]))
237
+ this.fail("JSON_SYNTAX_INVALID", path);
238
+ while (isDigit(this.source[this.offset]))
239
+ this.offset += 1;
240
+ }
241
+ const value = Number(this.source.slice(start, this.offset));
242
+ if (!Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value))) {
243
+ this.fail("JSON_NUMBER_UNSAFE", path);
244
+ }
245
+ return value;
246
+ }
247
+ parseLiteral(literal, value) {
248
+ if (this.source.slice(this.offset, this.offset + literal.length) !== literal) {
249
+ this.fail("JSON_SYNTAX_INVALID", "");
250
+ }
251
+ this.offset += literal.length;
252
+ return value;
253
+ }
254
+ skipWhitespace() {
255
+ while (this.source[this.offset] === " " ||
256
+ this.source[this.offset] === "\n" ||
257
+ this.source[this.offset] === "\r" ||
258
+ this.source[this.offset] === "\t") {
259
+ this.offset += 1;
260
+ }
261
+ }
262
+ consume(expected) {
263
+ if (this.source[this.offset] !== expected)
264
+ return false;
265
+ this.offset += 1;
266
+ return true;
267
+ }
268
+ fail(code, path) {
269
+ throw new JsonParseFailure(code, path);
270
+ }
271
+ syntaxFailure() {
272
+ const collector = new DiagnosticCollector(this.limits);
273
+ collector.addCode("JSON_SYNTAX_INVALID", "input", { position: this.offset });
274
+ return failure(collector.finish());
275
+ }
276
+ }
277
+ class JsonParseFailure extends Error {
278
+ code;
279
+ path;
280
+ constructor(code, path) {
281
+ super(code);
282
+ this.code = code;
283
+ this.path = path;
284
+ }
285
+ }
286
+ function isDigit(value) {
287
+ return value !== undefined && value >= "0" && value <= "9";
288
+ }
289
+ function isNonZeroDigit(value) {
290
+ return value !== undefined && value >= "1" && value <= "9";
291
+ }
292
+ function escapeJsonPointer(value) {
293
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
294
+ }
@@ -0,0 +1,152 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { types } from "node:util";
3
+ import { failure, success } from "../domain/operation-result.js";
4
+ import { DiagnosticCollector } from "./diagnostic-collector.js";
5
+ import { inspectPlainObject, isDataPropertyDescriptor } from "./object-inspection.js";
6
+ import { utf8ByteLength } from "./utf8-validation.js";
7
+ export function validateJsonValue(value, limits) {
8
+ const collector = new DiagnosticCollector(limits);
9
+ const active = new WeakSet();
10
+ const validated = validateValue(value, "", 0, limits, collector, active);
11
+ const diagnostics = collector.finish();
12
+ if (validated === undefined || collector.hasErrors())
13
+ return failure(diagnostics);
14
+ return success(validated, diagnostics);
15
+ }
16
+ function validateValue(value, path, depth, limits, collector, active) {
17
+ if (depth > limits.maxJsonDepth) {
18
+ collector.addCode("INPUT_LIMIT_EXCEEDED", "input", { path });
19
+ return undefined;
20
+ }
21
+ if (value === null || typeof value === "boolean")
22
+ return value;
23
+ if (typeof value === "string")
24
+ return validateString(value, path, limits, collector);
25
+ if (typeof value === "number")
26
+ return validateNumber(value, path, collector);
27
+ if (typeof value !== "object") {
28
+ collector.addCode("JSON_VALUE_UNSUPPORTED", "input", { path });
29
+ return undefined;
30
+ }
31
+ if (types.isProxy(value) || active.has(value)) {
32
+ collector.addCode("JSON_VALUE_UNSUPPORTED", "input", { path });
33
+ return undefined;
34
+ }
35
+ active.add(value);
36
+ try {
37
+ if (Array.isArray(value))
38
+ return validateArray(value, path, depth, limits, collector, active);
39
+ return validateObject(value, path, depth, limits, collector, active);
40
+ }
41
+ finally {
42
+ active.delete(value);
43
+ }
44
+ }
45
+ function validateString(value, path, limits, collector) {
46
+ const byteLength = utf8ByteLength(value);
47
+ if (byteLength === undefined) {
48
+ collector.addCode("UTF8_INVALID", "input", { path });
49
+ return undefined;
50
+ }
51
+ if (byteLength > limits.maxStringBytes) {
52
+ collector.addCode("INPUT_LIMIT_EXCEEDED", "input", { path });
53
+ return undefined;
54
+ }
55
+ return value;
56
+ }
57
+ function validateNumber(value, path, collector) {
58
+ if (!Number.isFinite(value)) {
59
+ collector.addCode("JSON_NUMBER_UNSAFE", "input", { path });
60
+ return undefined;
61
+ }
62
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
63
+ collector.addCode("JSON_NUMBER_UNSAFE", "input", { path });
64
+ return undefined;
65
+ }
66
+ return value;
67
+ }
68
+ function validateArray(value, path, depth, limits, collector, active) {
69
+ const descriptors = Object.getOwnPropertyDescriptors(value);
70
+ const symbols = Object.getOwnPropertySymbols(value);
71
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
72
+ if (symbols.length !== 0 ||
73
+ lengthDescriptor === undefined ||
74
+ !isDataPropertyDescriptor(lengthDescriptor)) {
75
+ collector.addCode("JSON_VALUE_UNSUPPORTED", "input", { path });
76
+ return undefined;
77
+ }
78
+ const length = lengthDescriptor.value;
79
+ if (typeof length !== "number" ||
80
+ !Number.isSafeInteger(length) ||
81
+ length > limits.maxArrayElements) {
82
+ collector.addCode("INPUT_LIMIT_EXCEEDED", "input", { path });
83
+ return undefined;
84
+ }
85
+ for (const [name, descriptor] of Object.entries(descriptors)) {
86
+ if (name === "length")
87
+ continue;
88
+ if (!isArrayIndex(name) ||
89
+ descriptor.enumerable !== true ||
90
+ descriptor.get !== undefined ||
91
+ descriptor.set !== undefined) {
92
+ collector.addCode("JSON_VALUE_UNSUPPORTED", "input", { path });
93
+ return undefined;
94
+ }
95
+ }
96
+ const output = [];
97
+ for (let index = 0; index < length; index += 1) {
98
+ const descriptor = descriptors[String(index)];
99
+ if (descriptor === undefined || !isDataPropertyDescriptor(descriptor)) {
100
+ collector.addCode("JSON_VALUE_UNSUPPORTED", "input", { path });
101
+ return undefined;
102
+ }
103
+ const itemPath = `${path}/${String(index)}`;
104
+ const item = validateValue(descriptor.value, itemPath, depth + 1, limits, collector, active);
105
+ if (item === undefined)
106
+ return undefined;
107
+ output.push(item);
108
+ }
109
+ return Object.freeze(output);
110
+ }
111
+ function validateObject(value, path, depth, limits, collector, active) {
112
+ const properties = inspectPlainObject(value);
113
+ if (properties === undefined) {
114
+ collector.addCode("JSON_VALUE_UNSUPPORTED", "input", { path });
115
+ return undefined;
116
+ }
117
+ if (properties.length > limits.maxObjectProperties) {
118
+ collector.addCode("INPUT_LIMIT_EXCEEDED", "input", { path });
119
+ return undefined;
120
+ }
121
+ const output = {};
122
+ for (const [name, property] of properties) {
123
+ const key = validateString(name, path, limits, collector);
124
+ if (key === undefined)
125
+ return undefined;
126
+ const propertyPath = `${path}/${escapeJsonPointer(key)}`;
127
+ const item = validateValue(property, propertyPath, depth + 1, limits, collector, active);
128
+ if (item === undefined)
129
+ return undefined;
130
+ if (key === "__proto__") {
131
+ Object.defineProperty(output, key, {
132
+ configurable: false,
133
+ enumerable: true,
134
+ value: item,
135
+ writable: false,
136
+ });
137
+ }
138
+ else {
139
+ output[key] = item;
140
+ }
141
+ }
142
+ return Object.freeze(output);
143
+ }
144
+ function isArrayIndex(name) {
145
+ if (!/^(?:0|[1-9][0-9]*)$/u.test(name))
146
+ return false;
147
+ const value = Number(name);
148
+ return (Number.isSafeInteger(value) && value >= 0 && value < 4_294_967_295 && String(value) === name);
149
+ }
150
+ function escapeJsonPointer(value) {
151
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
152
+ }
@@ -0,0 +1,71 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { DEFAULT_LIMITS, HARD_LIMITS } from "../domain/limits.js";
3
+ import { success } from "../domain/operation-result.js";
4
+ import { createDiagnostic } from "./diagnostic-collector.js";
5
+ import { failureWithCode } from "./result-helpers.js";
6
+ import { inspectPlainObject } from "./object-inspection.js";
7
+ const limitKeys = Object.freeze([
8
+ "maxPayloadBytes",
9
+ "maxJsonDepth",
10
+ "maxObjectProperties",
11
+ "maxArrayElements",
12
+ "maxStringBytes",
13
+ "maxNdjsonLineBytes",
14
+ "maxDiagnostics",
15
+ "maxFullRecords",
16
+ ]);
17
+ export function validateLimits(value) {
18
+ if (value === undefined)
19
+ return success(DEFAULT_LIMITS);
20
+ const properties = inspectPlainObject(value);
21
+ if (properties === undefined)
22
+ return failureWithCode("INPUT_TYPE_INVALID", "input");
23
+ const provided = new Map(properties);
24
+ for (const name of provided.keys()) {
25
+ if (!isLimitKey(name))
26
+ return failureWithCode("INPUT_TYPE_INVALID", "input");
27
+ }
28
+ const entries = {
29
+ maxPayloadBytes: DEFAULT_LIMITS.maxPayloadBytes,
30
+ maxJsonDepth: DEFAULT_LIMITS.maxJsonDepth,
31
+ maxObjectProperties: DEFAULT_LIMITS.maxObjectProperties,
32
+ maxArrayElements: DEFAULT_LIMITS.maxArrayElements,
33
+ maxStringBytes: DEFAULT_LIMITS.maxStringBytes,
34
+ maxNdjsonLineBytes: DEFAULT_LIMITS.maxNdjsonLineBytes,
35
+ maxDiagnostics: DEFAULT_LIMITS.maxDiagnostics,
36
+ maxFullRecords: DEFAULT_LIMITS.maxFullRecords,
37
+ };
38
+ for (const name of limitKeys) {
39
+ const candidate = provided.has(name) ? provided.get(name) : DEFAULT_LIMITS[name];
40
+ if (typeof candidate !== "number" ||
41
+ !Number.isSafeInteger(candidate) ||
42
+ candidate <= 0 ||
43
+ candidate > HARD_LIMITS[name]) {
44
+ return failureWithCode("INPUT_LIMIT_EXCEEDED", "input");
45
+ }
46
+ entries[name] = candidate;
47
+ }
48
+ const limits = {
49
+ maxPayloadBytes: entries.maxPayloadBytes,
50
+ maxJsonDepth: entries.maxJsonDepth,
51
+ maxObjectProperties: entries.maxObjectProperties,
52
+ maxArrayElements: entries.maxArrayElements,
53
+ maxStringBytes: entries.maxStringBytes,
54
+ maxNdjsonLineBytes: entries.maxNdjsonLineBytes,
55
+ maxDiagnostics: entries.maxDiagnostics,
56
+ maxFullRecords: entries.maxFullRecords,
57
+ };
58
+ return success(Object.freeze(limits));
59
+ }
60
+ function isLimitKey(value) {
61
+ return limitKeys.includes(value);
62
+ }
63
+ export function validateLimitValue(value, maximum) {
64
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
65
+ return {
66
+ ok: false,
67
+ diagnostics: Object.freeze([createDiagnostic("INPUT_LIMIT_EXCEEDED", "input")]),
68
+ };
69
+ }
70
+ return success(value);
71
+ }
@@ -0,0 +1,54 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { types } from "node:util";
3
+ export function inspectPlainObject(value) {
4
+ if (!isPlainObject(value))
5
+ return undefined;
6
+ if (Object.getOwnPropertySymbols(value).length !== 0)
7
+ return undefined;
8
+ const descriptors = Object.getOwnPropertyDescriptors(value);
9
+ const entries = [];
10
+ for (const [name, descriptor] of Object.entries(descriptors)) {
11
+ if (descriptor.enumerable !== true ||
12
+ descriptor.get !== undefined ||
13
+ descriptor.set !== undefined ||
14
+ !isDataPropertyDescriptor(descriptor)) {
15
+ return undefined;
16
+ }
17
+ entries.push([name, descriptor.value]);
18
+ }
19
+ return Object.freeze(entries);
20
+ }
21
+ /** Internal fixed-shape inspection that preserves the no-getter/no-proxy boundary. */
22
+ export function inspectExactProperties(value, names) {
23
+ if (!isPlainObject(value))
24
+ return undefined;
25
+ if (Object.getOwnPropertySymbols(value).length !== 0)
26
+ return undefined;
27
+ const ownNames = Object.getOwnPropertyNames(value);
28
+ if (ownNames.length !== names.length)
29
+ return undefined;
30
+ const descriptors = Object.getOwnPropertyDescriptors(value);
31
+ const values = [];
32
+ for (const name of names) {
33
+ const descriptor = descriptors[name];
34
+ if (descriptor === undefined)
35
+ return undefined;
36
+ if (descriptor.enumerable !== true ||
37
+ descriptor.get !== undefined ||
38
+ descriptor.set !== undefined ||
39
+ !isDataPropertyDescriptor(descriptor)) {
40
+ return undefined;
41
+ }
42
+ values.push(descriptor.value);
43
+ }
44
+ return values;
45
+ }
46
+ export function isDataPropertyDescriptor(descriptor) {
47
+ return "value" in descriptor;
48
+ }
49
+ export function isPlainObject(value) {
50
+ if (typeof value !== "object" || value === null || types.isProxy(value))
51
+ return false;
52
+ const prototype = Reflect.getPrototypeOf(value);
53
+ return prototype === Object.prototype || prototype === null;
54
+ }
@@ -0,0 +1,10 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { Position } from "../domain/position.js";
3
+ import { success } from "../domain/operation-result.js";
4
+ import { failureWithCode } from "./result-helpers.js";
5
+ export function validatePosition(value) {
6
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
7
+ return failureWithCode("INPUT_TYPE_INVALID", "input");
8
+ }
9
+ return success(Position.fromValidated(value));
10
+ }
@@ -0,0 +1,6 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { failure } from "../domain/operation-result.js";
3
+ import { createDiagnostic } from "./diagnostic-collector.js";
4
+ export function failureWithCode(code, phase) {
5
+ return failure([createDiagnostic(code, phase)]);
6
+ }
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { success } from "../domain/operation-result.js";
3
+ import { failureWithCode } from "./result-helpers.js";
4
+ const encoder = new TextEncoder();
5
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
6
+ export function hasWellFormedUnicode(value) {
7
+ for (let index = 0; index < value.length; index += 1) {
8
+ const codeUnit = value.charCodeAt(index);
9
+ if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
10
+ const next = value.charCodeAt(index + 1);
11
+ if (Number.isNaN(next) || next < 0xdc00 || next > 0xdfff)
12
+ return false;
13
+ index += 1;
14
+ }
15
+ else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
16
+ return false;
17
+ }
18
+ }
19
+ return true;
20
+ }
21
+ export function utf8ByteLength(value) {
22
+ let length = 0;
23
+ for (let index = 0; index < value.length; index += 1) {
24
+ const codeUnit = value.charCodeAt(index);
25
+ if (codeUnit <= 0x7f)
26
+ length += 1;
27
+ else if (codeUnit <= 0x7ff)
28
+ length += 2;
29
+ else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
30
+ const next = value.charCodeAt(index + 1);
31
+ if (Number.isNaN(next) || next < 0xdc00 || next > 0xdfff)
32
+ return undefined;
33
+ length += 4;
34
+ index += 1;
35
+ }
36
+ else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff)
37
+ return undefined;
38
+ else
39
+ length += 3;
40
+ }
41
+ return length;
42
+ }
43
+ export function encodeUtf8(value, maxBytes) {
44
+ if (typeof value !== "string" || !hasWellFormedUnicode(value)) {
45
+ return failureWithCode("UTF8_INVALID", "input");
46
+ }
47
+ const bytes = encoder.encode(value);
48
+ if (bytes.length > maxBytes)
49
+ return failureWithCode("INPUT_LIMIT_EXCEEDED", "input");
50
+ return success(bytes);
51
+ }
52
+ export function decodeUtf8(value) {
53
+ if (value.length >= 3 && value[0] === 0xef && value[1] === 0xbb && value[2] === 0xbf) {
54
+ return failureWithCode("UTF8_INVALID", "input");
55
+ }
56
+ try {
57
+ const decoded = decoder.decode(value);
58
+ return success(decoded);
59
+ }
60
+ catch {
61
+ return failureWithCode("UTF8_INVALID", "input");
62
+ }
63
+ }