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.
- package/LICENSE +190 -0
- package/README.md +355 -0
- package/corpus/dev/README.md +36 -0
- package/corpus/dev/compile-seal-example/brief.json +1 -0
- package/corpus/dev/compile-seal-example/contract.json +1 -0
- package/corpus/dev/contracts/absent-collection-locations.json +1 -0
- package/corpus/dev/contracts/absent-sibling-groups.json +1 -0
- package/corpus/dev/contracts/absent-success-indicator.json +1 -0
- package/corpus/dev/contracts/empty-channel-roles.json +1 -0
- package/corpus/dev/contracts/empty-collection-locations.json +1 -0
- package/corpus/dev/contracts/empty-request-shapes.json +1 -0
- package/corpus/dev/contracts/empty-sibling-groups.json +1 -0
- package/corpus/dev/contracts/no-collection-quantifier.json +1 -0
- package/corpus/dev/contracts/no-operation-inventory.json +1 -0
- package/corpus/dev/contracts/no-read-back-relation.json +1 -0
- package/corpus/dev/contracts/no-state-change-marker.json +1 -0
- package/corpus/dev/contracts/no-type-violating-step.json +1 -0
- package/corpus/dev/contracts/per-key-split-oracles.json +1 -0
- package/corpus/dev/contracts/satisfied-declarations.json +1 -0
- package/corpus/dev/contracts/single-required-response-key.json +1 -0
- package/corpus/dev/contracts/split-indicator-oracle.json +1 -0
- package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -0
- package/corpus/dev/contracts/unnamed-reference-set.json +1 -0
- package/corpus/dev/contracts/wrong-cardinality-form.json +1 -0
- package/corpus/dev/index.json +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/local-corpus-adapter.d.ts +7 -0
- package/dist/adapters/local-corpus-adapter.js +78 -0
- package/dist/adapters/node-file-system-adapter.d.ts +7 -0
- package/dist/adapters/node-file-system-adapter.js +39 -0
- package/dist/adapters/port-boundary.d.ts +18 -0
- package/dist/adapters/port-boundary.js +83 -0
- package/dist/adapters/system-clock-adapter.d.ts +10 -0
- package/dist/adapters/system-clock-adapter.js +18 -0
- package/dist/application/compile.d.ts +4 -0
- package/dist/application/compile.js +24 -0
- package/dist/application/diagnostics.d.ts +22 -0
- package/dist/application/diagnostics.js +18 -0
- package/dist/application/index.d.ts +23 -0
- package/dist/application/index.js +17 -0
- package/dist/application/invoke-port.d.ts +2 -0
- package/dist/application/invoke-port.js +45 -0
- package/dist/application/preflight.d.ts +27 -0
- package/dist/application/preflight.js +131 -0
- package/dist/application/seal.d.ts +4 -0
- package/dist/application/seal.js +28 -0
- package/dist/application/serialize.d.ts +6 -0
- package/dist/application/serialize.js +9 -0
- package/dist/cli/arguments.d.ts +28 -0
- package/dist/cli/arguments.js +167 -0
- package/dist/cli/exit-codes.d.ts +36 -0
- package/dist/cli/exit-codes.js +39 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +143 -0
- package/dist/cli/render.d.ts +26 -0
- package/dist/cli/render.js +53 -0
- package/dist/cli/run.d.ts +41 -0
- package/dist/cli/run.js +203 -0
- package/dist/core/canonical/canonicalize.d.ts +1 -0
- package/dist/core/canonical/canonicalize.js +113 -0
- package/dist/core/canonical/digest.d.ts +7 -0
- package/dist/core/canonical/digest.js +81 -0
- package/dist/core/canonical/scan-json.d.ts +1 -0
- package/dist/core/canonical/scan-json.js +298 -0
- package/dist/core/canonical/value-domain.d.ts +4 -0
- package/dist/core/canonical/value-domain.js +113 -0
- package/dist/core/compile/compile.d.ts +24 -0
- package/dist/core/compile/compile.js +67 -0
- package/dist/core/compile/declarations.d.ts +5 -0
- package/dist/core/compile/declarations.js +22 -0
- package/dist/core/compile/expression-legality.d.ts +14 -0
- package/dist/core/compile/expression-legality.js +346 -0
- package/dist/core/compile/forbidden-inputs.d.ts +5 -0
- package/dist/core/compile/forbidden-inputs.js +19 -0
- package/dist/core/compile/interface-inventory.d.ts +10 -0
- package/dist/core/compile/interface-inventory.js +55 -0
- package/dist/core/compile/oracle-alignment.d.ts +14 -0
- package/dist/core/compile/oracle-alignment.js +93 -0
- package/dist/core/compile/reachability.d.ts +17 -0
- package/dist/core/compile/reachability.js +177 -0
- package/dist/core/compile/rubrics.d.ts +55 -0
- package/dist/core/compile/rubrics.js +198 -0
- package/dist/core/compile/scripting-bound.d.ts +5 -0
- package/dist/core/compile/scripting-bound.js +151 -0
- package/dist/core/compile/sensitivity-witness.d.ts +73 -0
- package/dist/core/compile/sensitivity-witness.js +232 -0
- package/dist/core/compile/waivers.d.ts +3 -0
- package/dist/core/compile/waivers.js +21 -0
- package/dist/core/coverage/coverage.d.ts +18 -0
- package/dist/core/coverage/coverage.js +47 -0
- package/dist/core/coverage/relevance.d.ts +75 -0
- package/dist/core/coverage/relevance.js +186 -0
- package/dist/core/coverage/rules.d.ts +7 -0
- package/dist/core/coverage/rules.js +18 -0
- package/dist/core/coverage/satisfaction.d.ts +107 -0
- package/dist/core/coverage/satisfaction.js +415 -0
- package/dist/core/coverage/table.d.ts +26 -0
- package/dist/core/coverage/table.js +222 -0
- package/dist/core/declared-inputs.d.ts +16 -0
- package/dist/core/declared-inputs.js +18 -0
- package/dist/core/evaluate/evidence-resolution.d.ts +54 -0
- package/dist/core/evaluate/evidence-resolution.js +153 -0
- package/dist/core/evaluate/operators.d.ts +87 -0
- package/dist/core/evaluate/operators.js +350 -0
- package/dist/core/evaluate/resolution.d.ts +40 -0
- package/dist/core/evaluate/resolution.js +336 -0
- package/dist/core/evaluate/resolved-value.d.ts +9 -0
- package/dist/core/evaluate/resolved-value.js +6 -0
- package/dist/core/failure-codes.d.ts +18 -0
- package/dist/core/failure-codes.js +50 -0
- package/dist/core/lineage/chain.d.ts +49 -0
- package/dist/core/lineage/chain.js +210 -0
- package/dist/core/lineage/freeze.d.ts +12 -0
- package/dist/core/lineage/freeze.js +35 -0
- package/dist/core/lineage/stage-table.d.ts +43 -0
- package/dist/core/lineage/stage-table.js +106 -0
- package/dist/core/preflight/plan.d.ts +67 -0
- package/dist/core/preflight/plan.js +313 -0
- package/dist/core/preflight/projection.d.ts +40 -0
- package/dist/core/preflight/projection.js +96 -0
- package/dist/core/preflight/reduce.d.ts +8 -0
- package/dist/core/preflight/reduce.js +180 -0
- package/dist/core/preflight/witness-evidence.d.ts +42 -0
- package/dist/core/preflight/witness-evidence.js +88 -0
- package/dist/core/probe/target-policy.d.ts +63 -0
- package/dist/core/probe/target-policy.js +328 -0
- package/dist/core/schemas/artifact-reference.d.ts +19 -0
- package/dist/core/schemas/artifact-reference.js +30 -0
- package/dist/core/schemas/artifact.d.ts +1153 -0
- package/dist/core/schemas/artifact.js +84 -0
- package/dist/core/schemas/constraint-ledger.d.ts +57 -0
- package/dist/core/schemas/constraint-ledger.js +143 -0
- package/dist/core/schemas/eval-contract.d.ts +402 -0
- package/dist/core/schemas/eval-contract.js +147 -0
- package/dist/core/schemas/evaluator-configuration.d.ts +37 -0
- package/dist/core/schemas/evaluator-configuration.js +43 -0
- package/dist/core/schemas/evidence-artifact.d.ts +471 -0
- package/dist/core/schemas/evidence-artifact.js +232 -0
- package/dist/core/schemas/expression.d.ts +197 -0
- package/dist/core/schemas/expression.js +284 -0
- package/dist/core/schemas/faults.d.ts +9 -0
- package/dist/core/schemas/faults.js +32 -0
- package/dist/core/schemas/interface.d.ts +417 -0
- package/dist/core/schemas/interface.js +115 -0
- package/dist/core/schemas/isolation-manifest.d.ts +136 -0
- package/dist/core/schemas/isolation-manifest.js +92 -0
- package/dist/core/schemas/lineage.d.ts +21 -0
- package/dist/core/schemas/lineage.js +28 -0
- package/dist/core/schemas/oracle.d.ts +72 -0
- package/dist/core/schemas/oracle.js +35 -0
- package/dist/core/schemas/plan.d.ts +115 -0
- package/dist/core/schemas/plan.js +58 -0
- package/dist/core/schemas/pointer.d.ts +63 -0
- package/dist/core/schemas/pointer.js +94 -0
- package/dist/core/schemas/port-messages.d.ts +103 -0
- package/dist/core/schemas/port-messages.js +90 -0
- package/dist/core/schemas/preflight-verdict.d.ts +60 -0
- package/dist/core/schemas/preflight-verdict.js +38 -0
- package/dist/core/schemas/primitives.d.ts +78 -0
- package/dist/core/schemas/primitives.js +121 -0
- package/dist/core/schemas/private-artifact-manifest.d.ts +56 -0
- package/dist/core/schemas/private-artifact-manifest.js +46 -0
- package/dist/core/schemas/probe-body.d.ts +25 -0
- package/dist/core/schemas/probe-body.js +23 -0
- package/dist/core/schemas/probe-policy.d.ts +76 -0
- package/dist/core/schemas/probe-policy.js +33 -0
- package/dist/core/schemas/probe.d.ts +189 -0
- package/dist/core/schemas/probe.js +82 -0
- package/dist/core/schemas/publish.d.ts +30 -0
- package/dist/core/schemas/publish.js +135 -0
- package/dist/core/schemas/reference-set.d.ts +18 -0
- package/dist/core/schemas/reference-set.js +27 -0
- package/dist/core/schemas/rubric.d.ts +62 -0
- package/dist/core/schemas/rubric.js +68 -0
- package/dist/core/schemas/scoring-policy.d.ts +28 -0
- package/dist/core/schemas/scoring-policy.js +51 -0
- package/dist/core/schemas/sealed-evaluator-brief.d.ts +80 -0
- package/dist/core/schemas/sealed-evaluator-brief.js +59 -0
- package/dist/core/schemas/sealed-run-record.d.ts +382 -0
- package/dist/core/schemas/sealed-run-record.js +215 -0
- package/dist/core/schemas/sensitivity-witness.d.ts +138 -0
- package/dist/core/schemas/sensitivity-witness.js +86 -0
- package/dist/core/schemas/verdict.d.ts +25 -0
- package/dist/core/schemas/verdict.js +20 -0
- package/dist/core/schemas/waiver.d.ts +17 -0
- package/dist/core/schemas/waiver.js +24 -0
- package/dist/core/seal/derived-reference.d.ts +36 -0
- package/dist/core/seal/derived-reference.js +383 -0
- package/dist/core/seal/direction-prose.d.ts +13 -0
- package/dist/core/seal/direction-prose.js +120 -0
- package/dist/core/seal/plan-index.d.ts +63 -0
- package/dist/core/seal/plan-index.js +155 -0
- package/dist/core/seal/scripting-audit.d.ts +15 -0
- package/dist/core/seal/scripting-audit.js +51 -0
- package/dist/core/seal/seal.d.ts +18 -0
- package/dist/core/seal/seal.js +155 -0
- package/dist/core/stage-contracts.d.ts +30 -0
- package/dist/core/stage-contracts.js +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +22 -0
- package/dist/ports/clock-port.d.ts +13 -0
- package/dist/ports/clock-port.js +7 -0
- package/dist/ports/corpus-port.d.ts +16 -0
- package/dist/ports/corpus-port.js +7 -0
- package/dist/ports/environment-probe-port.d.ts +76 -0
- package/dist/ports/environment-probe-port.js +32 -0
- package/dist/ports/file-system-port.d.ts +27 -0
- package/dist/ports/file-system-port.js +11 -0
- package/dist/ports/port.d.ts +33 -0
- package/dist/ports/port.js +14 -0
- package/dist/testing/conformance.d.ts +88 -0
- package/dist/testing/conformance.js +281 -0
- package/dist/testing/index.d.ts +26 -0
- package/dist/testing/index.js +18 -0
- package/dist/testing/probe-conformance.d.ts +45 -0
- package/dist/testing/probe-conformance.js +186 -0
- package/package.json +130 -0
- package/schemas/artifact-reference.schema.json +64 -0
- package/schemas/eval-contract.schema.json +2145 -0
- package/schemas/evaluator-configuration.schema.json +199 -0
- package/schemas/evidence-artifact.schema.json +1229 -0
- package/schemas/isolation-manifest.schema.json +394 -0
- package/schemas/preflight-verdict.schema.json +114 -0
- package/schemas/private-artifact-manifest.schema.json +103 -0
- package/schemas/probe.schema.json +1151 -0
- package/schemas/rubric.schema.json +148 -0
- package/schemas/scoring-policy.schema.json +90 -0
- package/schemas/sealed-evaluator-brief.schema.json +268 -0
- 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;
|