eval-quality 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +355 -0
  3. package/corpus/dev/README.md +36 -0
  4. package/corpus/dev/compile-seal-example/brief.json +1 -0
  5. package/corpus/dev/compile-seal-example/contract.json +1 -0
  6. package/corpus/dev/contracts/absent-collection-locations.json +1 -0
  7. package/corpus/dev/contracts/absent-sibling-groups.json +1 -0
  8. package/corpus/dev/contracts/absent-success-indicator.json +1 -0
  9. package/corpus/dev/contracts/empty-channel-roles.json +1 -0
  10. package/corpus/dev/contracts/empty-collection-locations.json +1 -0
  11. package/corpus/dev/contracts/empty-request-shapes.json +1 -0
  12. package/corpus/dev/contracts/empty-sibling-groups.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -0
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -0
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -0
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -0
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -0
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -0
  19. package/corpus/dev/contracts/satisfied-declarations.json +1 -0
  20. package/corpus/dev/contracts/single-required-response-key.json +1 -0
  21. package/corpus/dev/contracts/split-indicator-oracle.json +1 -0
  22. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -0
  23. package/corpus/dev/contracts/unnamed-reference-set.json +1 -0
  24. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -0
  25. package/corpus/dev/index.json +1 -0
  26. package/dist/adapters/index.d.ts +12 -0
  27. package/dist/adapters/index.js +3 -0
  28. package/dist/adapters/local-corpus-adapter.d.ts +7 -0
  29. package/dist/adapters/local-corpus-adapter.js +78 -0
  30. package/dist/adapters/node-file-system-adapter.d.ts +7 -0
  31. package/dist/adapters/node-file-system-adapter.js +39 -0
  32. package/dist/adapters/port-boundary.d.ts +18 -0
  33. package/dist/adapters/port-boundary.js +83 -0
  34. package/dist/adapters/system-clock-adapter.d.ts +10 -0
  35. package/dist/adapters/system-clock-adapter.js +18 -0
  36. package/dist/application/compile.d.ts +4 -0
  37. package/dist/application/compile.js +24 -0
  38. package/dist/application/diagnostics.d.ts +22 -0
  39. package/dist/application/diagnostics.js +18 -0
  40. package/dist/application/index.d.ts +23 -0
  41. package/dist/application/index.js +17 -0
  42. package/dist/application/invoke-port.d.ts +2 -0
  43. package/dist/application/invoke-port.js +45 -0
  44. package/dist/application/preflight.d.ts +27 -0
  45. package/dist/application/preflight.js +131 -0
  46. package/dist/application/seal.d.ts +4 -0
  47. package/dist/application/seal.js +28 -0
  48. package/dist/application/serialize.d.ts +6 -0
  49. package/dist/application/serialize.js +9 -0
  50. package/dist/cli/arguments.d.ts +28 -0
  51. package/dist/cli/arguments.js +167 -0
  52. package/dist/cli/exit-codes.d.ts +36 -0
  53. package/dist/cli/exit-codes.js +39 -0
  54. package/dist/cli/main.d.ts +2 -0
  55. package/dist/cli/main.js +143 -0
  56. package/dist/cli/render.d.ts +26 -0
  57. package/dist/cli/render.js +53 -0
  58. package/dist/cli/run.d.ts +41 -0
  59. package/dist/cli/run.js +203 -0
  60. package/dist/core/canonical/canonicalize.d.ts +1 -0
  61. package/dist/core/canonical/canonicalize.js +113 -0
  62. package/dist/core/canonical/digest.d.ts +7 -0
  63. package/dist/core/canonical/digest.js +81 -0
  64. package/dist/core/canonical/scan-json.d.ts +1 -0
  65. package/dist/core/canonical/scan-json.js +298 -0
  66. package/dist/core/canonical/value-domain.d.ts +4 -0
  67. package/dist/core/canonical/value-domain.js +113 -0
  68. package/dist/core/compile/compile.d.ts +24 -0
  69. package/dist/core/compile/compile.js +67 -0
  70. package/dist/core/compile/declarations.d.ts +5 -0
  71. package/dist/core/compile/declarations.js +22 -0
  72. package/dist/core/compile/expression-legality.d.ts +14 -0
  73. package/dist/core/compile/expression-legality.js +346 -0
  74. package/dist/core/compile/forbidden-inputs.d.ts +5 -0
  75. package/dist/core/compile/forbidden-inputs.js +19 -0
  76. package/dist/core/compile/interface-inventory.d.ts +10 -0
  77. package/dist/core/compile/interface-inventory.js +55 -0
  78. package/dist/core/compile/oracle-alignment.d.ts +14 -0
  79. package/dist/core/compile/oracle-alignment.js +93 -0
  80. package/dist/core/compile/reachability.d.ts +17 -0
  81. package/dist/core/compile/reachability.js +177 -0
  82. package/dist/core/compile/rubrics.d.ts +55 -0
  83. package/dist/core/compile/rubrics.js +198 -0
  84. package/dist/core/compile/scripting-bound.d.ts +5 -0
  85. package/dist/core/compile/scripting-bound.js +151 -0
  86. package/dist/core/compile/sensitivity-witness.d.ts +73 -0
  87. package/dist/core/compile/sensitivity-witness.js +232 -0
  88. package/dist/core/compile/waivers.d.ts +3 -0
  89. package/dist/core/compile/waivers.js +21 -0
  90. package/dist/core/coverage/coverage.d.ts +18 -0
  91. package/dist/core/coverage/coverage.js +47 -0
  92. package/dist/core/coverage/relevance.d.ts +75 -0
  93. package/dist/core/coverage/relevance.js +186 -0
  94. package/dist/core/coverage/rules.d.ts +7 -0
  95. package/dist/core/coverage/rules.js +18 -0
  96. package/dist/core/coverage/satisfaction.d.ts +107 -0
  97. package/dist/core/coverage/satisfaction.js +415 -0
  98. package/dist/core/coverage/table.d.ts +26 -0
  99. package/dist/core/coverage/table.js +222 -0
  100. package/dist/core/declared-inputs.d.ts +16 -0
  101. package/dist/core/declared-inputs.js +18 -0
  102. package/dist/core/evaluate/evidence-resolution.d.ts +54 -0
  103. package/dist/core/evaluate/evidence-resolution.js +153 -0
  104. package/dist/core/evaluate/operators.d.ts +87 -0
  105. package/dist/core/evaluate/operators.js +350 -0
  106. package/dist/core/evaluate/resolution.d.ts +40 -0
  107. package/dist/core/evaluate/resolution.js +336 -0
  108. package/dist/core/evaluate/resolved-value.d.ts +9 -0
  109. package/dist/core/evaluate/resolved-value.js +6 -0
  110. package/dist/core/failure-codes.d.ts +18 -0
  111. package/dist/core/failure-codes.js +50 -0
  112. package/dist/core/lineage/chain.d.ts +49 -0
  113. package/dist/core/lineage/chain.js +210 -0
  114. package/dist/core/lineage/freeze.d.ts +12 -0
  115. package/dist/core/lineage/freeze.js +35 -0
  116. package/dist/core/lineage/stage-table.d.ts +43 -0
  117. package/dist/core/lineage/stage-table.js +106 -0
  118. package/dist/core/preflight/plan.d.ts +67 -0
  119. package/dist/core/preflight/plan.js +313 -0
  120. package/dist/core/preflight/projection.d.ts +40 -0
  121. package/dist/core/preflight/projection.js +96 -0
  122. package/dist/core/preflight/reduce.d.ts +8 -0
  123. package/dist/core/preflight/reduce.js +180 -0
  124. package/dist/core/preflight/witness-evidence.d.ts +42 -0
  125. package/dist/core/preflight/witness-evidence.js +88 -0
  126. package/dist/core/probe/target-policy.d.ts +63 -0
  127. package/dist/core/probe/target-policy.js +328 -0
  128. package/dist/core/schemas/artifact-reference.d.ts +19 -0
  129. package/dist/core/schemas/artifact-reference.js +30 -0
  130. package/dist/core/schemas/artifact.d.ts +1153 -0
  131. package/dist/core/schemas/artifact.js +84 -0
  132. package/dist/core/schemas/constraint-ledger.d.ts +57 -0
  133. package/dist/core/schemas/constraint-ledger.js +143 -0
  134. package/dist/core/schemas/eval-contract.d.ts +402 -0
  135. package/dist/core/schemas/eval-contract.js +147 -0
  136. package/dist/core/schemas/evaluator-configuration.d.ts +37 -0
  137. package/dist/core/schemas/evaluator-configuration.js +43 -0
  138. package/dist/core/schemas/evidence-artifact.d.ts +471 -0
  139. package/dist/core/schemas/evidence-artifact.js +232 -0
  140. package/dist/core/schemas/expression.d.ts +197 -0
  141. package/dist/core/schemas/expression.js +284 -0
  142. package/dist/core/schemas/faults.d.ts +9 -0
  143. package/dist/core/schemas/faults.js +32 -0
  144. package/dist/core/schemas/interface.d.ts +417 -0
  145. package/dist/core/schemas/interface.js +115 -0
  146. package/dist/core/schemas/isolation-manifest.d.ts +136 -0
  147. package/dist/core/schemas/isolation-manifest.js +92 -0
  148. package/dist/core/schemas/lineage.d.ts +21 -0
  149. package/dist/core/schemas/lineage.js +28 -0
  150. package/dist/core/schemas/oracle.d.ts +72 -0
  151. package/dist/core/schemas/oracle.js +35 -0
  152. package/dist/core/schemas/plan.d.ts +115 -0
  153. package/dist/core/schemas/plan.js +58 -0
  154. package/dist/core/schemas/pointer.d.ts +63 -0
  155. package/dist/core/schemas/pointer.js +94 -0
  156. package/dist/core/schemas/port-messages.d.ts +103 -0
  157. package/dist/core/schemas/port-messages.js +90 -0
  158. package/dist/core/schemas/preflight-verdict.d.ts +60 -0
  159. package/dist/core/schemas/preflight-verdict.js +38 -0
  160. package/dist/core/schemas/primitives.d.ts +78 -0
  161. package/dist/core/schemas/primitives.js +121 -0
  162. package/dist/core/schemas/private-artifact-manifest.d.ts +56 -0
  163. package/dist/core/schemas/private-artifact-manifest.js +46 -0
  164. package/dist/core/schemas/probe-body.d.ts +25 -0
  165. package/dist/core/schemas/probe-body.js +23 -0
  166. package/dist/core/schemas/probe-policy.d.ts +76 -0
  167. package/dist/core/schemas/probe-policy.js +33 -0
  168. package/dist/core/schemas/probe.d.ts +189 -0
  169. package/dist/core/schemas/probe.js +82 -0
  170. package/dist/core/schemas/publish.d.ts +30 -0
  171. package/dist/core/schemas/publish.js +135 -0
  172. package/dist/core/schemas/reference-set.d.ts +18 -0
  173. package/dist/core/schemas/reference-set.js +27 -0
  174. package/dist/core/schemas/rubric.d.ts +62 -0
  175. package/dist/core/schemas/rubric.js +68 -0
  176. package/dist/core/schemas/scoring-policy.d.ts +28 -0
  177. package/dist/core/schemas/scoring-policy.js +51 -0
  178. package/dist/core/schemas/sealed-evaluator-brief.d.ts +80 -0
  179. package/dist/core/schemas/sealed-evaluator-brief.js +59 -0
  180. package/dist/core/schemas/sealed-run-record.d.ts +382 -0
  181. package/dist/core/schemas/sealed-run-record.js +215 -0
  182. package/dist/core/schemas/sensitivity-witness.d.ts +138 -0
  183. package/dist/core/schemas/sensitivity-witness.js +86 -0
  184. package/dist/core/schemas/verdict.d.ts +25 -0
  185. package/dist/core/schemas/verdict.js +20 -0
  186. package/dist/core/schemas/waiver.d.ts +17 -0
  187. package/dist/core/schemas/waiver.js +24 -0
  188. package/dist/core/seal/derived-reference.d.ts +36 -0
  189. package/dist/core/seal/derived-reference.js +383 -0
  190. package/dist/core/seal/direction-prose.d.ts +13 -0
  191. package/dist/core/seal/direction-prose.js +120 -0
  192. package/dist/core/seal/plan-index.d.ts +63 -0
  193. package/dist/core/seal/plan-index.js +155 -0
  194. package/dist/core/seal/scripting-audit.d.ts +15 -0
  195. package/dist/core/seal/scripting-audit.js +51 -0
  196. package/dist/core/seal/seal.d.ts +18 -0
  197. package/dist/core/seal/seal.js +155 -0
  198. package/dist/core/stage-contracts.d.ts +30 -0
  199. package/dist/core/stage-contracts.js +1 -0
  200. package/dist/index.d.ts +15 -0
  201. package/dist/index.js +22 -0
  202. package/dist/ports/clock-port.d.ts +13 -0
  203. package/dist/ports/clock-port.js +7 -0
  204. package/dist/ports/corpus-port.d.ts +16 -0
  205. package/dist/ports/corpus-port.js +7 -0
  206. package/dist/ports/environment-probe-port.d.ts +76 -0
  207. package/dist/ports/environment-probe-port.js +32 -0
  208. package/dist/ports/file-system-port.d.ts +27 -0
  209. package/dist/ports/file-system-port.js +11 -0
  210. package/dist/ports/port.d.ts +33 -0
  211. package/dist/ports/port.js +14 -0
  212. package/dist/testing/conformance.d.ts +88 -0
  213. package/dist/testing/conformance.js +281 -0
  214. package/dist/testing/index.d.ts +26 -0
  215. package/dist/testing/index.js +18 -0
  216. package/dist/testing/probe-conformance.d.ts +45 -0
  217. package/dist/testing/probe-conformance.js +186 -0
  218. package/package.json +130 -0
  219. package/schemas/artifact-reference.schema.json +64 -0
  220. package/schemas/eval-contract.schema.json +2145 -0
  221. package/schemas/evaluator-configuration.schema.json +199 -0
  222. package/schemas/evidence-artifact.schema.json +1229 -0
  223. package/schemas/isolation-manifest.schema.json +394 -0
  224. package/schemas/preflight-verdict.schema.json +114 -0
  225. package/schemas/private-artifact-manifest.schema.json +103 -0
  226. package/schemas/probe.schema.json +1151 -0
  227. package/schemas/rubric.schema.json +148 -0
  228. package/schemas/scoring-policy.schema.json +90 -0
  229. package/schemas/sealed-evaluator-brief.schema.json +268 -0
  230. package/schemas/sealed-run-record.schema.json +875 -0
@@ -0,0 +1,298 @@
1
+ /** reads raw text, catches what JSON.parse hides */
2
+ import { RuntimeFault } from '../schemas/faults.js';
3
+ import { MAX_NESTING_DEPTH } from './value-domain.js';
4
+ // Lexical pre-parse validation (AD-36): hashed-artifact input is scanned from
5
+ // raw text, never handed to bare `JSON.parse`. Domain violations throw
6
+ // `non-canonicalizable-value`; input that does not parse at all (malformed
7
+ // syntax, bytes that fail fatal UTF-8 decoding) throws `schema-parse-failure`.
8
+ export function scanJson(input, artifactPath) {
9
+ const text = typeof input === 'string' ? input : decodeUtf8(input, artifactPath);
10
+ return new Scanner(text, artifactPath).scanDocument();
11
+ }
12
+ function decodeUtf8(bytes, artifactPath) {
13
+ try {
14
+ // fatal: a non-fatal decode substitutes U+FFFD for invalid sequences (a
15
+ // producer's WTF-8-encoded lone surrogate would digest cleanly). ignoreBOM
16
+ // keeps a leading U+FEFF in the text, where it fails JSON syntax below;
17
+ // silently stripping it would let two implementations disagree on the same bytes.
18
+ return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
19
+ }
20
+ catch (error) {
21
+ throw new RuntimeFault('schema-parse-failure', artifactPath, 'input is not valid UTF-8', { cause: error });
22
+ }
23
+ }
24
+ const SAFE_INTEGER_MAX = 2n ** 53n - 1n;
25
+ const isDigit = (ch) => ch !== undefined && ch >= '0' && ch <= '9';
26
+ const isAlphanumeric = (ch) => ch !== undefined && /[A-Za-z0-9]/.test(ch);
27
+ class Scanner {
28
+ position = 0;
29
+ depth = 0;
30
+ // Assigned in the body: Node's type stripping refuses a constructor
31
+ // parameter property, and every script importing this module through
32
+ // `digest.ts` would die at load with `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`.
33
+ text;
34
+ artifactPath;
35
+ constructor(text, artifactPath) {
36
+ this.text = text;
37
+ this.artifactPath = artifactPath;
38
+ }
39
+ scanDocument() {
40
+ this.skipWhitespace();
41
+ const value = this.scanValue();
42
+ this.skipWhitespace();
43
+ if (this.position < this.text.length)
44
+ this.syntax('trailing content after the document');
45
+ return value;
46
+ }
47
+ syntax(detail) {
48
+ throw new RuntimeFault('schema-parse-failure', this.artifactPath, `${detail} at offset ${this.position}`);
49
+ }
50
+ domain(detail) {
51
+ throw new RuntimeFault('non-canonicalizable-value', this.artifactPath, `${detail} at offset ${this.position}`);
52
+ }
53
+ skipWhitespace() {
54
+ for (;;) {
55
+ const ch = this.text[this.position];
56
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r')
57
+ this.position++;
58
+ else
59
+ return;
60
+ }
61
+ }
62
+ scanValue() {
63
+ const ch = this.text[this.position];
64
+ switch (ch) {
65
+ case undefined:
66
+ this.syntax('unexpected end of input');
67
+ break;
68
+ case '{':
69
+ return this.scanObject();
70
+ case '[':
71
+ return this.scanArray();
72
+ case '"':
73
+ return this.scanString();
74
+ case 't':
75
+ return this.scanLiteral('true', true);
76
+ case 'f':
77
+ return this.scanLiteral('false', false);
78
+ case 'n':
79
+ return this.scanLiteral('null', null);
80
+ default:
81
+ if (ch === '-' || isDigit(ch))
82
+ return this.scanNumber();
83
+ this.syntax(`unexpected character ${JSON.stringify(ch)}`);
84
+ }
85
+ }
86
+ scanLiteral(literal, value) {
87
+ if (!this.text.startsWith(literal, this.position)) {
88
+ this.syntax(`invalid literal, expected ${literal}`);
89
+ }
90
+ this.position += literal.length;
91
+ // Rejects "truex" here as an invalid literal, not as a structural error
92
+ // surfacing later at the next comma or bracket.
93
+ if (isAlphanumeric(this.text[this.position])) {
94
+ this.syntax(`invalid literal starting with ${literal}`);
95
+ }
96
+ return value;
97
+ }
98
+ // See MAX_NESTING_DEPTH in value-domain.ts for why this is bounded.
99
+ enterNesting() {
100
+ if (++this.depth > MAX_NESTING_DEPTH) {
101
+ this.domain(`nesting depth exceeds ${MAX_NESTING_DEPTH}`);
102
+ }
103
+ }
104
+ scanObject() {
105
+ this.enterNesting();
106
+ this.position++; // consume {
107
+ const result = {};
108
+ const seenKeys = new Set();
109
+ this.skipWhitespace();
110
+ if (this.text[this.position] === '}') {
111
+ this.position++;
112
+ this.depth--;
113
+ return result;
114
+ }
115
+ for (;;) {
116
+ this.skipWhitespace();
117
+ if (this.text[this.position] !== '"')
118
+ this.syntax('expected a string object key');
119
+ const key = this.scanString();
120
+ // Duplicates are compared on unescaped keys: {"A":1,"A":2} is a duplicate.
121
+ if (seenKeys.has(key))
122
+ this.domain(`duplicate object key ${JSON.stringify(key)}`);
123
+ seenKeys.add(key);
124
+ this.skipWhitespace();
125
+ if (this.text[this.position] !== ':')
126
+ this.syntax("expected ':' after object key");
127
+ this.position++;
128
+ this.skipWhitespace();
129
+ const value = this.scanValue();
130
+ // A "__proto__" key must land as an own property, never a prototype mutation.
131
+ Object.defineProperty(result, key, {
132
+ value,
133
+ enumerable: true,
134
+ writable: true,
135
+ configurable: true,
136
+ });
137
+ this.skipWhitespace();
138
+ const next = this.text[this.position];
139
+ if (next === ',') {
140
+ this.position++;
141
+ continue;
142
+ }
143
+ if (next === '}') {
144
+ this.position++;
145
+ this.depth--;
146
+ return result;
147
+ }
148
+ this.syntax("expected ',' or '}' in object");
149
+ }
150
+ }
151
+ scanArray() {
152
+ this.enterNesting();
153
+ this.position++; // consume [
154
+ const result = [];
155
+ this.skipWhitespace();
156
+ if (this.text[this.position] === ']') {
157
+ this.position++;
158
+ this.depth--;
159
+ return result;
160
+ }
161
+ for (;;) {
162
+ this.skipWhitespace();
163
+ result.push(this.scanValue());
164
+ this.skipWhitespace();
165
+ const next = this.text[this.position];
166
+ if (next === ',') {
167
+ this.position++;
168
+ continue;
169
+ }
170
+ if (next === ']') {
171
+ this.position++;
172
+ this.depth--;
173
+ return result;
174
+ }
175
+ this.syntax("expected ',' or ']' in array");
176
+ }
177
+ }
178
+ scanString() {
179
+ this.position++; // consume opening quote
180
+ let result = '';
181
+ for (;;) {
182
+ const ch = this.text[this.position];
183
+ if (ch === undefined)
184
+ this.syntax('unterminated string');
185
+ if (ch === '"') {
186
+ this.position++;
187
+ break;
188
+ }
189
+ if (ch === '\\') {
190
+ result += this.scanEscape();
191
+ continue;
192
+ }
193
+ if (ch.charCodeAt(0) < 0x20)
194
+ this.syntax('unescaped control character in string');
195
+ result += ch;
196
+ this.position++;
197
+ }
198
+ // Covers literal surrogates and escaped ones alike, in values and keys.
199
+ if (!result.isWellFormed())
200
+ this.domain('lone surrogate in string');
201
+ return result;
202
+ }
203
+ scanEscape() {
204
+ this.position++; // consume backslash
205
+ const ch = this.text[this.position];
206
+ if (ch === undefined)
207
+ this.syntax('unterminated escape sequence');
208
+ this.position++;
209
+ switch (ch) {
210
+ case '"':
211
+ return '"';
212
+ case '\\':
213
+ return '\\';
214
+ case '/':
215
+ return '/';
216
+ case 'b':
217
+ return '\b';
218
+ case 'f':
219
+ return '\f';
220
+ case 'n':
221
+ return '\n';
222
+ case 'r':
223
+ return '\r';
224
+ case 't':
225
+ return '\t';
226
+ case 'u': {
227
+ const hex = this.text.slice(this.position, this.position + 4);
228
+ if (!/^[0-9a-fA-F]{4}$/.test(hex))
229
+ this.syntax('invalid \\u escape');
230
+ this.position += 4;
231
+ return String.fromCharCode(Number.parseInt(hex, 16));
232
+ }
233
+ default:
234
+ this.syntax(`invalid escape character ${JSON.stringify(ch)}`);
235
+ }
236
+ }
237
+ scanNumber() {
238
+ const start = this.position;
239
+ if (this.text[this.position] === '-')
240
+ this.position++;
241
+ if (this.text[this.position] === '0') {
242
+ this.position++;
243
+ if (isDigit(this.text[this.position]))
244
+ this.syntax('leading zero in number');
245
+ }
246
+ else if (isDigit(this.text[this.position])) {
247
+ while (isDigit(this.text[this.position]))
248
+ this.position++;
249
+ }
250
+ else {
251
+ this.syntax('invalid number');
252
+ }
253
+ let integerSyntax = true;
254
+ if (this.text[this.position] === '.') {
255
+ integerSyntax = false;
256
+ this.position++;
257
+ if (!isDigit(this.text[this.position]))
258
+ this.syntax('expected digits after decimal point');
259
+ while (isDigit(this.text[this.position]))
260
+ this.position++;
261
+ }
262
+ if (this.text[this.position] === 'e' || this.text[this.position] === 'E') {
263
+ integerSyntax = false;
264
+ this.position++;
265
+ if (this.text[this.position] === '+' || this.text[this.position] === '-')
266
+ this.position++;
267
+ if (!isDigit(this.text[this.position]))
268
+ this.syntax('expected digits in exponent');
269
+ while (isDigit(this.text[this.position]))
270
+ this.position++;
271
+ }
272
+ if (isAlphanumeric(this.text[this.position])) {
273
+ this.syntax('invalid number');
274
+ }
275
+ const literal = this.text.slice(start, this.position);
276
+ if (integerSyntax) {
277
+ // Compared on the digits: JSON.parse would round 9007199254740993
278
+ // invisibly. 2^53 - 1 has 16 digits, so anything longer is unsafe by
279
+ // inspection. Checked first so a multi-megabyte literal never pays (or
280
+ // weaponizes) an arbitrary-precision BigInt conversion.
281
+ const digits = literal[0] === '-' ? literal.slice(1) : literal;
282
+ if (digits.length > 16 || BigInt(digits) > SAFE_INTEGER_MAX) {
283
+ this.domain(`integer literal outside the safe range: ${literal}`);
284
+ }
285
+ }
286
+ const value = Number(literal);
287
+ if (!Number.isFinite(value)) {
288
+ this.domain(`numeric literal is not finite in binary64: ${literal}`);
289
+ }
290
+ // Any literal whose binary64 value is an unsafe integer is rejected
291
+ // regardless of spelling (9007199254740993.0, 9.007199254740993e15, 1e21):
292
+ // two producers must not disagree.
293
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
294
+ this.domain(`numeric literal is an unsafe integer in binary64: ${literal}`);
295
+ }
296
+ return value;
297
+ }
298
+ }
@@ -0,0 +1,4 @@
1
+ export declare const MAX_NESTING_DEPTH = 1024;
2
+ export declare function assertHashedArtifactValue(value: unknown, artifactPath: string): void;
3
+ export declare function assertDomainNumber(value: number, artifactPath: string, location: string): void;
4
+ export declare function assertDomainString(value: string, artifactPath: string, location: string): void;
@@ -0,0 +1,113 @@
1
+ /** same checks for values already in memory */
2
+ import { RuntimeFault } from '../schemas/faults.js';
3
+ // AD-36: a hashed artifact admits only finite binary64 numbers, integers within
4
+ // the safe range, well-formed strings, and plain acyclic JSON structure. Larger
5
+ // integers and exact decimals travel as strings with the declared formats in
6
+ // `core/schemas/primitives.ts`; this layer enforces the numeric domain.
7
+ // Structure bound, part of the value domain: nesting deeper than this rejects
8
+ // with the typed fault, so pathological input can never escape as a bare
9
+ // RangeError from the recursive walkers.
10
+ export const MAX_NESTING_DEPTH = 1024;
11
+ export function assertHashedArtifactValue(value, artifactPath) {
12
+ visit(value, artifactPath, '$', new Set(), 0);
13
+ }
14
+ const reject = (artifactPath, detail) => {
15
+ throw new RuntimeFault('non-canonicalizable-value', artifactPath, detail);
16
+ };
17
+ // Exported so canonicalize.ts can re-run these scalar checks at emit time, in
18
+ // the single traversal that closes the TOCTOU gap described there.
19
+ export function assertDomainNumber(value, artifactPath, location) {
20
+ if (!Number.isFinite(value)) {
21
+ reject(artifactPath, `non-finite number at ${location}`);
22
+ }
23
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
24
+ reject(artifactPath, `integer outside the safe range at ${location}: ${value}`);
25
+ }
26
+ }
27
+ export function assertDomainString(value, artifactPath, location) {
28
+ if (!value.isWellFormed()) {
29
+ reject(artifactPath, `lone surrogate in string at ${location}`);
30
+ }
31
+ }
32
+ function visit(value, artifactPath, location, ancestors, depth) {
33
+ switch (typeof value) {
34
+ case 'boolean':
35
+ return;
36
+ case 'number':
37
+ assertDomainNumber(value, artifactPath, location);
38
+ return;
39
+ case 'string':
40
+ assertDomainString(value, artifactPath, location);
41
+ return;
42
+ case 'object':
43
+ break;
44
+ default:
45
+ // undefined, function, bigint, symbol: unrepresentable, never coerced silently.
46
+ throw new RuntimeFault('non-canonicalizable-value', artifactPath, `${typeof value} is not a hashed-artifact value at ${location}`);
47
+ }
48
+ if (value === null)
49
+ return;
50
+ if (depth >= MAX_NESTING_DEPTH) {
51
+ reject(artifactPath, `nesting depth exceeds ${MAX_NESTING_DEPTH} at ${location}`);
52
+ }
53
+ if (ancestors.has(value)) {
54
+ reject(artifactPath, `cyclic reference at ${location}`);
55
+ }
56
+ ancestors.add(value);
57
+ if (Array.isArray(value)) {
58
+ // Arrays get the same plainness discipline as objects: a subclass or a
59
+ // toJSON carrier serializes differently under a stringify-honouring
60
+ // implementation, and non-index properties would be silently dropped.
61
+ if (Object.getPrototypeOf(value) !== Array.prototype) {
62
+ reject(artifactPath, `non-plain array at ${location}`);
63
+ }
64
+ if (typeof value.toJSON === 'function') {
65
+ reject(artifactPath, `array carrying toJSON at ${location}`);
66
+ }
67
+ assertOwnPropertiesArePlainData(value, artifactPath, location);
68
+ if (Object.keys(value).length !== value.length) {
69
+ reject(artifactPath, `array with holes or non-index properties at ${location}`);
70
+ }
71
+ for (const [index, element] of value.entries()) {
72
+ visit(element, artifactPath, `${location}[${index}]`, ancestors, depth + 1);
73
+ }
74
+ }
75
+ else {
76
+ const prototype = Object.getPrototypeOf(value);
77
+ if (prototype !== Object.prototype && prototype !== null) {
78
+ reject(artifactPath, `non-plain object at ${location}`);
79
+ }
80
+ if (typeof value.toJSON === 'function') {
81
+ reject(artifactPath, `object carrying toJSON at ${location}`);
82
+ }
83
+ assertOwnPropertiesArePlainData(value, artifactPath, location);
84
+ for (const [key, entry] of Object.entries(value)) {
85
+ if (!key.isWellFormed()) {
86
+ reject(artifactPath, `lone surrogate in object key at ${location}`);
87
+ }
88
+ visit(entry, artifactPath, `${location}.${key}`, ancestors, depth + 1);
89
+ }
90
+ }
91
+ ancestors.delete(value);
92
+ }
93
+ // Accessors, non-enumerable properties, and symbol-keyed properties are all
94
+ // rejected outright; canonicalize.ts explains why (the TOCTOU channel a
95
+ // second read reopens).
96
+ function assertOwnPropertiesArePlainData(value, artifactPath, location) {
97
+ if (Object.getOwnPropertySymbols(value).length > 0) {
98
+ reject(artifactPath, `symbol-keyed property at ${location}`);
99
+ }
100
+ for (const key of Object.getOwnPropertyNames(value)) {
101
+ if (key === 'length' && Array.isArray(value))
102
+ continue;
103
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
104
+ if (descriptor === undefined)
105
+ continue;
106
+ if (!('value' in descriptor)) {
107
+ reject(artifactPath, `accessor property ${JSON.stringify(key)} at ${location}`);
108
+ }
109
+ if (!descriptor.enumerable) {
110
+ reject(artifactPath, `non-enumerable property ${JSON.stringify(key)} at ${location}`);
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The pure, synchronous, fixed-order compile stage over an already-parsed
3
+ * `EvalContract`. No I/O, no state; every check throws `StructuralFailure`
4
+ * straight through to the caller.
5
+ *
6
+ * Call order is AD-5's registry order, the only published stable priority: a
7
+ * contract violating several checks reports whichever runs first. The
8
+ * `malformed-operator-expression` trio (`checkBoundElementScope`,
9
+ * `checkOperandLegality`, `checkRegexConstructs`) keeps that suborder so
10
+ * their shared result stays deterministic. `checkUndeclaredMandatoryInput`
11
+ * runs only when `options.strict` is true (AD-4), and AD-10's
12
+ * `checkSensitivityWitnessDeclared` joins it there because it fires the same
13
+ * code; the other two witness checks fire different codes and run
14
+ * unconditionally, after the registry order, since AD-5 names no rung for them.
15
+ *
16
+ * `checkRubricIdentifiers` fires `rubric-unanchored` but runs ahead of
17
+ * `checkRubricReasoningProse`, which outranks it in the registry. A duplicated
18
+ * rubric or criterion id makes every `rubrics[id=...]` path the other three
19
+ * rubric checks emit address two things, so identifiers are settled before any
20
+ * of them reports.
21
+ */
22
+ import type { EvalContract } from '../schemas/eval-contract.ts';
23
+ import type { CompileOptions } from '../stage-contracts.ts';
24
+ export declare function compile(contract: EvalContract, options: CompileOptions): EvalContract;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The pure, synchronous, fixed-order compile stage over an already-parsed
3
+ * `EvalContract`. No I/O, no state; every check throws `StructuralFailure`
4
+ * straight through to the caller.
5
+ *
6
+ * Call order is AD-5's registry order, the only published stable priority: a
7
+ * contract violating several checks reports whichever runs first. The
8
+ * `malformed-operator-expression` trio (`checkBoundElementScope`,
9
+ * `checkOperandLegality`, `checkRegexConstructs`) keeps that suborder so
10
+ * their shared result stays deterministic. `checkUndeclaredMandatoryInput`
11
+ * runs only when `options.strict` is true (AD-4), and AD-10's
12
+ * `checkSensitivityWitnessDeclared` joins it there because it fires the same
13
+ * code; the other two witness checks fire different codes and run
14
+ * unconditionally, after the registry order, since AD-5 names no rung for them.
15
+ *
16
+ * `checkRubricIdentifiers` fires `rubric-unanchored` but runs ahead of
17
+ * `checkRubricReasoningProse`, which outranks it in the registry. A duplicated
18
+ * rubric or criterion id makes every `rubrics[id=...]` path the other three
19
+ * rubric checks emit address two things, so identifiers are settled before any
20
+ * of them reports.
21
+ */
22
+ import { checkObservableSuccessCriterion, checkRequirementLinkage, } from './declarations.js';
23
+ import { checkOperandLegality, checkQuantifierNesting, checkQuantifierOverNonCollection, checkReferenceSetResolution, checkRegexConstructs, } from './expression-legality.js';
24
+ import { checkForbiddenInputFloor, checkScopedResourceReferences, } from './forbidden-inputs.js';
25
+ import { checkDuplicateOperationSignature, checkInterfaceKind, checkUndeclaredMandatoryInput, } from './interface-inventory.js';
26
+ import { checkOracleAlignment, checkOracleChannel } from './oracle-alignment.js';
27
+ import { checkBoundElementScope, checkEvidenceReachability, } from './reachability.js';
28
+ import { checkRubricAnchoring, checkRubricEvidenceReachability, checkRubricIdentifiers, checkRubricReasoningProse, } from './rubrics.js';
29
+ import { checkNestedTemporalClause, checkScriptingBound, } from './scripting-bound.js';
30
+ import { checkSensitivityWitnessDeclared, checkWitnessLegality, checkWitnessLegIdentifiers, } from './sensitivity-witness.js';
31
+ import { checkWaiverCompleteness } from './waivers.js';
32
+ export function compile(contract, options) {
33
+ checkRequirementLinkage(contract);
34
+ checkObservableSuccessCriterion(contract);
35
+ checkEvidenceReachability(contract);
36
+ checkBoundElementScope(contract);
37
+ checkOperandLegality(contract);
38
+ checkRegexConstructs(contract);
39
+ checkQuantifierOverNonCollection(contract);
40
+ checkQuantifierNesting(contract);
41
+ checkReferenceSetResolution(contract);
42
+ checkDuplicateOperationSignature(contract);
43
+ if (options.strict) {
44
+ checkUndeclaredMandatoryInput(contract);
45
+ checkSensitivityWitnessDeclared(contract);
46
+ }
47
+ checkOracleChannel(contract);
48
+ checkOracleAlignment(contract);
49
+ checkInterfaceKind(contract);
50
+ checkNestedTemporalClause(contract);
51
+ checkScriptingBound(contract);
52
+ checkRubricIdentifiers(contract);
53
+ checkRubricReasoningProse(contract);
54
+ checkRubricAnchoring(contract);
55
+ checkRubricEvidenceReachability(contract);
56
+ checkForbiddenInputFloor(contract);
57
+ checkScopedResourceReferences(contract);
58
+ checkWaiverCompleteness(contract);
59
+ // Identifiers before legality. A duplicated or plan-colliding leg id makes
60
+ // the legality check's question ("does the relation address both legs?")
61
+ // unanswerable, so legality-first reports an unreachable-evidence failure on
62
+ // a contract whose actual defect is the collision, and every identifier
63
+ // fixture would need a second mutation to reach its own code.
64
+ checkWitnessLegIdentifiers(contract);
65
+ checkWitnessLegality(contract);
66
+ return contract;
67
+ }
@@ -0,0 +1,5 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ /** Rejects a behavior whose requirement and risk link lists are both empty. */
3
+ export declare function checkRequirementLinkage(contract: EvalContract): void;
4
+ /** A null criterion fails. An empty oracle list remains valid. */
5
+ export declare function checkObservableSuccessCriterion(contract: EvalContract): void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Checks AD-19 behavior declarations for requirement or risk linkage and an
3
+ * observable success criterion. Each check reports the first violation.
4
+ */
5
+ import { StructuralFailure } from '../failure-codes.js';
6
+ /** Rejects a behavior whose requirement and risk link lists are both empty. */
7
+ export function checkRequirementLinkage(contract) {
8
+ for (const behavior of contract.behaviors) {
9
+ if (behavior.requirementLinks.length === 0 &&
10
+ behavior.riskLinks.length === 0) {
11
+ throw new StructuralFailure('missing-requirement-linkage', `EvalContract.behaviors[id=${behavior.id}]`, 'declares neither a requirementLinks nor a riskLinks entry (AD-19)');
12
+ }
13
+ }
14
+ }
15
+ /** A null criterion fails. An empty oracle list remains valid. */
16
+ export function checkObservableSuccessCriterion(contract) {
17
+ for (const behavior of contract.behaviors) {
18
+ if (behavior.observableSuccessCriterion === null) {
19
+ throw new StructuralFailure('no-observable-success-criterion', `EvalContract.behaviors[id=${behavior.id}].observableSuccessCriterion`, 'declares no observable success criterion (AD-19)');
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,14 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ /** Checks each operator's operands against its position-specific constraints. */
3
+ export declare function checkOperandLegality(contract: EvalContract): void;
4
+ /** Rejects invalid regexes, partial anchors, backreferences, and lookbehind. */
5
+ export declare function checkRegexConstructs(contract: EvalContract): void;
6
+ /** Rejects nested quantifiers and covers-by-key inside a quantifier. */
7
+ export declare function checkQuantifierNesting(contract: EvalContract): void;
8
+ /** Checks response-body collection pointers, after bound-element substitution. */
9
+ export declare function checkQuantifierOverNonCollection(contract: EvalContract): void;
10
+ /**
11
+ * Checks every reference-set operand with an own-property lookup.
12
+ * This preserves legal identifiers such as `constructor`.
13
+ */
14
+ export declare function checkReferenceSetResolution(contract: EvalContract): void;