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,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-4's eleven scalar, structural, and relational operators over the
|
|
3
|
+
* resolved-value domain. The connectives, quantifiers, and the
|
|
4
|
+
* `insufficient-evidence` wrapper live in `resolution.ts`; nothing here
|
|
5
|
+
* decides any of them (AD-4). Every function takes `artifactPath: string`
|
|
6
|
+
* last, even when unused, so that resolver dispatches through one calling
|
|
7
|
+
* convention. The five that never throw name it `_artifactPath`; Biome
|
|
8
|
+
* treats that prefix as unused.
|
|
9
|
+
*/
|
|
10
|
+
import { digestArtifact } from '../canonical/digest.js';
|
|
11
|
+
import { RuntimeFault } from '../schemas/faults.js';
|
|
12
|
+
import { ABSENT } from './resolved-value.js';
|
|
13
|
+
function jsonKind(value) {
|
|
14
|
+
if (value === null)
|
|
15
|
+
return 'null';
|
|
16
|
+
if (Array.isArray(value))
|
|
17
|
+
return 'array';
|
|
18
|
+
if (typeof value === 'object')
|
|
19
|
+
return 'object';
|
|
20
|
+
return typeof value;
|
|
21
|
+
}
|
|
22
|
+
function isPlainObject(value) {
|
|
23
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
// Structural (canonical-JSON) equality, shared by `equality`'s compound
|
|
26
|
+
// branch, `deepEquality`, and element matching in `containment` and
|
|
27
|
+
// `setMembership`. `digestArtifact` throws on a domain-rejected value
|
|
28
|
+
// (AD-36); left to propagate here, since an unevaluable operand has no
|
|
29
|
+
// comparison result to report.
|
|
30
|
+
function structurallyEqual(a, b, artifactPath) {
|
|
31
|
+
return digestArtifact(a, artifactPath) === digestArtifact(b, artifactPath);
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// the identity family
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/** Reads only whether resolution happened; `null` counts as present (AD-26). */
|
|
37
|
+
export function existence(value, _artifactPath) {
|
|
38
|
+
return value !== ABSENT;
|
|
39
|
+
}
|
|
40
|
+
/** AD-26's exact complement of `existence`. */
|
|
41
|
+
export function absence(value, _artifactPath) {
|
|
42
|
+
return value === ABSENT;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Cost-ordered: the `ABSENT` guard runs before any digest call, since
|
|
46
|
+
* `ABSENT` is a JS `symbol` and `digestArtifact` faults on symbols. Only a
|
|
47
|
+
* matching compound type reaches structural comparison, so a scalar operand
|
|
48
|
+
* never inherits the digest path's fault surface.
|
|
49
|
+
*/
|
|
50
|
+
export function equality(a, b, artifactPath) {
|
|
51
|
+
if (a === ABSENT || b === ABSENT)
|
|
52
|
+
return false;
|
|
53
|
+
const kindA = jsonKind(a);
|
|
54
|
+
const kindB = jsonKind(b);
|
|
55
|
+
if (kindA !== kindB)
|
|
56
|
+
return false;
|
|
57
|
+
if (kindA === 'array' || kindA === 'object') {
|
|
58
|
+
return structurallyEqual(a, b, artifactPath);
|
|
59
|
+
}
|
|
60
|
+
return a === b;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Unconditionally structural (AD-27): the whole point of deep comparison.
|
|
64
|
+
* `ABSENT` is guarded first, same reason as `equality`'s. A canonicalization
|
|
65
|
+
* fault propagates undecorated: an unevaluable operand, not a false match.
|
|
66
|
+
*/
|
|
67
|
+
export function deepEquality(a, b, artifactPath) {
|
|
68
|
+
if (a === ABSENT || b === ABSENT)
|
|
69
|
+
return false;
|
|
70
|
+
return structurallyEqual(a, b, artifactPath);
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// the membership family
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* `false` on `ABSENT` is correct only when `value`'s pointer is not itself
|
|
77
|
+
* collection-typed; the resolver in `resolution.ts` disambiguates the two
|
|
78
|
+
* cases before calling in here.
|
|
79
|
+
*/
|
|
80
|
+
export function setMembership(value, set, artifactPath) {
|
|
81
|
+
if (value === ABSENT)
|
|
82
|
+
return false;
|
|
83
|
+
return set.some((member) => structurallyEqual(member, value, artifactPath));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* `candidate: ResolvedValue | JsonValue[]` because `Containment`'s operand
|
|
87
|
+
* can resolve to either a single value or a `referenceSet`'s member array.
|
|
88
|
+
*
|
|
89
|
+
* An array-shaped `candidate` is always read as a subset check, never as a
|
|
90
|
+
* single element to search for: this function receives resolved values only,
|
|
91
|
+
* so it cannot tell a `{ literal }` array from a resolved `{ referenceSet }`.
|
|
92
|
+
* Known, accepted limitation.
|
|
93
|
+
*/
|
|
94
|
+
export function containment(container, candidate, artifactPath) {
|
|
95
|
+
if (container === ABSENT)
|
|
96
|
+
return false;
|
|
97
|
+
if (Array.isArray(candidate)) {
|
|
98
|
+
// referenceSet path: requires container to also be an array.
|
|
99
|
+
if (!Array.isArray(container))
|
|
100
|
+
return false;
|
|
101
|
+
return candidate.every((expectedMember) => container.some((element) => structurallyEqual(element, expectedMember, artifactPath)));
|
|
102
|
+
}
|
|
103
|
+
if (candidate === ABSENT)
|
|
104
|
+
return false;
|
|
105
|
+
if (typeof container === 'string') {
|
|
106
|
+
if (typeof candidate !== 'string')
|
|
107
|
+
return false;
|
|
108
|
+
return container.includes(candidate);
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(container)) {
|
|
111
|
+
return container.some((element) => structurallyEqual(element, candidate, artifactPath));
|
|
112
|
+
}
|
|
113
|
+
// Object or scalar container: type mismatch. Key presence belongs to
|
|
114
|
+
// `existence` via a direct pointer; `containment` does not duplicate it.
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// regexMatch
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// A backslash-escaped character pair (`\+`, `\d`, `\[`, `\]`, …), neutralized
|
|
121
|
+
// to a single inert placeholder before `CHARACTER_CLASS_CONTENTS` runs. See
|
|
122
|
+
// the ordering note on `stripped` below.
|
|
123
|
+
const ESCAPED_CHARACTER_PAIR = /\\./g;
|
|
124
|
+
// Character-class contents, stripped after escape-neutralization so a
|
|
125
|
+
// literal `+`/`*`/`?`/`[`/`]` inside `[...]` is never read as a quantifier.
|
|
126
|
+
const CHARACTER_CLASS_CONTENTS = /\[[^\]]*\]/g;
|
|
127
|
+
// Strips a group's own `?:`/`?=`/`?!`/`?<name>` marker first, or the bare `?`
|
|
128
|
+
// would misread as a quantifier and false-flag `(?:GET|POST)+` as nested.
|
|
129
|
+
const GROUP_MARKER_PREFIX = /^\?(?:[:=!]|<[=!]?[^>]*>)/;
|
|
130
|
+
const CONTENT_QUANTIFIER_CHARACTER = /[*+?{]/;
|
|
131
|
+
const TRAILING_QUANTIFIER = /^(?:[*+?]|\{\d+(?:,\d*)?\})/;
|
|
132
|
+
/**
|
|
133
|
+
* Cheap structural paren-matching pass, modeled on `ANCHORED_PATTERN_FORM`'s
|
|
134
|
+
* precedent. True iff a `(...)`/`(?:...)` group's contents hold a quantifier
|
|
135
|
+
* character and the group itself is immediately followed by one: `(a+)+`,
|
|
136
|
+
* `(a*)*`, and similar catastrophic-backtracking shapes.
|
|
137
|
+
*/
|
|
138
|
+
function hasNestedQuantifier(strippedPattern) {
|
|
139
|
+
const groupStarts = [];
|
|
140
|
+
for (let index = 0; index < strippedPattern.length; index++) {
|
|
141
|
+
const character = strippedPattern[index];
|
|
142
|
+
if (character === '(') {
|
|
143
|
+
groupStarts.push(index);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (character === ')') {
|
|
147
|
+
const start = groupStarts.pop();
|
|
148
|
+
if (start === undefined)
|
|
149
|
+
continue;
|
|
150
|
+
const contents = strippedPattern
|
|
151
|
+
.slice(start + 1, index)
|
|
152
|
+
.replace(GROUP_MARKER_PREFIX, '');
|
|
153
|
+
const following = strippedPattern.slice(index + 1);
|
|
154
|
+
if (CONTENT_QUANTIFIER_CHARACTER.test(contents) &&
|
|
155
|
+
TRAILING_QUANTIFIER.test(following)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const QUANTIFIER_MARKER = /[*+?]|\{\d+(?:,\d*)?\}/g;
|
|
163
|
+
/**
|
|
164
|
+
* `false` if `value === ABSENT` or not a string. Pattern validity is checked
|
|
165
|
+
* here; the match-step budget is a two-tier static gate, structural
|
|
166
|
+
* nested-quantifier rejection then a linear estimate. The gate stays static
|
|
167
|
+
* because AD-1 keeps this function synchronous and pure, and native `RegExp`
|
|
168
|
+
* exposes no step counter to read.
|
|
169
|
+
*/
|
|
170
|
+
export function regexMatch(value, pattern, matchStepBudget, artifactPath) {
|
|
171
|
+
if (value === ABSENT || typeof value !== 'string')
|
|
172
|
+
return false;
|
|
173
|
+
let compiled;
|
|
174
|
+
try {
|
|
175
|
+
compiled = new RegExp(pattern);
|
|
176
|
+
}
|
|
177
|
+
catch (cause) {
|
|
178
|
+
throw new RuntimeFault('operator-cannot-accept-operand', artifactPath, `pattern is not a syntactically valid ECMA-262 source: ${pattern}`, { cause });
|
|
179
|
+
}
|
|
180
|
+
// Escape-neutralize before stripping character classes: neutralizing first
|
|
181
|
+
// keeps an escaped bracket from hiding a real nested-quantifier group from
|
|
182
|
+
// both tiers below and letting the regex hang.
|
|
183
|
+
const stripped = pattern
|
|
184
|
+
.replace(ESCAPED_CHARACTER_PAIR, '_')
|
|
185
|
+
.replace(CHARACTER_CLASS_CONTENTS, '[]');
|
|
186
|
+
if (hasNestedQuantifier(stripped)) {
|
|
187
|
+
throw new RuntimeFault('budget-exhausted', artifactPath, `pattern rejected outright for a nested-quantifier shape that risks catastrophic backtracking: ${pattern}`);
|
|
188
|
+
}
|
|
189
|
+
const estimatedSteps = (1 + (stripped.match(QUANTIFIER_MARKER)?.length ?? 0)) * value.length;
|
|
190
|
+
if (estimatedSteps > matchStepBudget) {
|
|
191
|
+
throw new RuntimeFault('budget-exhausted', artifactPath, `estimated ${estimatedSteps} regex match steps exceed the declared budget of ${matchStepBudget}`);
|
|
192
|
+
}
|
|
193
|
+
// The unstripped pattern; stripping only feeds the step estimate above.
|
|
194
|
+
return compiled.test(value);
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// the structural family
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/**
|
|
200
|
+
* Comparison is non-strict: ties are allowed. String comparison is plain
|
|
201
|
+
* `<=`/`>=` (UTF-16 code-unit order), matching AD-27's key-sorting basis.
|
|
202
|
+
*/
|
|
203
|
+
export function ordering(collection, key, order, _artifactPath) {
|
|
204
|
+
if (collection === ABSENT || !Array.isArray(collection))
|
|
205
|
+
return false;
|
|
206
|
+
for (let index = 0; index < collection.length - 1; index++) {
|
|
207
|
+
const left = collection[index];
|
|
208
|
+
const right = collection[index + 1];
|
|
209
|
+
if (left === undefined || right === undefined)
|
|
210
|
+
return false;
|
|
211
|
+
if (!isPlainObject(left) || !isPlainObject(right))
|
|
212
|
+
return false;
|
|
213
|
+
if (!Object.hasOwn(left, key) || !Object.hasOwn(right, key))
|
|
214
|
+
return false;
|
|
215
|
+
const leftValue = left[key];
|
|
216
|
+
const rightValue = right[key];
|
|
217
|
+
let inOrder;
|
|
218
|
+
if (typeof leftValue === 'number' && typeof rightValue === 'number') {
|
|
219
|
+
inOrder =
|
|
220
|
+
order === 'ascending'
|
|
221
|
+
? leftValue <= rightValue
|
|
222
|
+
: leftValue >= rightValue;
|
|
223
|
+
}
|
|
224
|
+
else if (typeof leftValue === 'string' &&
|
|
225
|
+
typeof rightValue === 'string') {
|
|
226
|
+
inOrder =
|
|
227
|
+
order === 'ascending'
|
|
228
|
+
? leftValue <= rightValue
|
|
229
|
+
: leftValue >= rightValue;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
if (!inOrder)
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* An empty array is a legitimate zero count, never special-cased; the
|
|
241
|
+
* resolver in `resolution.ts` intercepts before this runs on a genuinely
|
|
242
|
+
* empty collection. The allowed deviation is compared unrounded: `actual` is
|
|
243
|
+
* an integer, so `<=` against a fractional deviation is already exact, and
|
|
244
|
+
* rounding either direction would move the declared boundary.
|
|
245
|
+
*/
|
|
246
|
+
export function countTolerance(collection, expected, tolerance, relative, _artifactPath) {
|
|
247
|
+
if (collection === ABSENT || !Array.isArray(collection))
|
|
248
|
+
return false;
|
|
249
|
+
const actual = collection.length;
|
|
250
|
+
const allowedDeviation = relative ? (expected * tolerance) / 100 : tolerance;
|
|
251
|
+
return Math.abs(actual - expected) <= allowedDeviation;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The closed set is `permittedKeys` alone, never unioned with `requiredKeys`:
|
|
255
|
+
* a self-contradictory descriptor is unsatisfiable rather than repaired.
|
|
256
|
+
* `requiredKeys ⊆ permittedKeys` is unrefined in
|
|
257
|
+
* `core/schemas/primitives.ts` and closed by no compile-time check, so a key
|
|
258
|
+
* that is required and not permitted reaches here and fails every value.
|
|
259
|
+
*/
|
|
260
|
+
export function shape(value, descriptor, _artifactPath) {
|
|
261
|
+
if (value === ABSENT || !isPlainObject(value))
|
|
262
|
+
return false;
|
|
263
|
+
for (const requiredKey of descriptor.requiredKeys) {
|
|
264
|
+
if (!Object.hasOwn(value, requiredKey))
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
for (const ownKey of Object.keys(value)) {
|
|
268
|
+
if (!descriptor.permittedKeys.includes(ownKey))
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
for (const [key, declaredType] of Object.entries(descriptor.types)) {
|
|
272
|
+
// `null` in the type map means declared but type not stated: skip it.
|
|
273
|
+
if (declaredType === null)
|
|
274
|
+
continue;
|
|
275
|
+
if (!Object.hasOwn(value, key))
|
|
276
|
+
continue;
|
|
277
|
+
const actual = value[key];
|
|
278
|
+
if (jsonKind(actual) !== declaredType)
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// covers-by-key
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
/**
|
|
287
|
+
* Own-property lookup only, so a key like `__proto__` reads as missing
|
|
288
|
+
* rather than inherited. Returns `ABSENT`, never throws (AD-4: a missing key
|
|
289
|
+
* resolves `false`, not an error).
|
|
290
|
+
*/
|
|
291
|
+
function keyValueOf(element, key) {
|
|
292
|
+
if (!isPlainObject(element) || !Object.hasOwn(element, key))
|
|
293
|
+
return ABSENT;
|
|
294
|
+
// `Object.hasOwn` above already proves the key is present; `noUncheckedIndexedAccess`
|
|
295
|
+
// cannot see that, so the cast is narrowing, not widening (same pattern
|
|
296
|
+
// `shape`'s own per-key type check already uses in this file).
|
|
297
|
+
return element[key];
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* AD-4's bijection: equal cardinality and a distinct `actual` match per
|
|
301
|
+
* `expected` element on the named keys. `ABSENT` on either side resolves
|
|
302
|
+
* `false`, including a fully-missing `actual` collection: AD-4 calls that "a
|
|
303
|
+
* detected defect, not an empty examination," overriding the general
|
|
304
|
+
* empty-collection invariant for this operator alone. A non-array `actual` is
|
|
305
|
+
* an operand type this operator does not accept, which AD-4 assigns to
|
|
306
|
+
* `malformed-operator-expression`; `core/compile/expression-legality.ts`
|
|
307
|
+
* checks the operand *form* under that code and leaves this position's
|
|
308
|
+
* declared type unchecked, so a non-array reaching here resolves `false`.
|
|
309
|
+
*
|
|
310
|
+
* Cardinality is never checked separately: `actualByKey` starts with one
|
|
311
|
+
* entry per `actual` element (a synthetic slot for a keyless one, so nothing
|
|
312
|
+
* goes uncounted), and the `expected` loop deletes one entry per match. A
|
|
313
|
+
* final `actualByKey.size === 0` is the bijection condition itself, since the
|
|
314
|
+
* map's starting size already equals `actual`'s cardinality. A duplicate
|
|
315
|
+
* `actualKey` fails immediately, at construction, because the second element
|
|
316
|
+
* finds its slot already occupied; a duplicate `expectedKey` is assumed
|
|
317
|
+
* prevented at compile time under `malformed-operator-expression`, but if it
|
|
318
|
+
* occurs it fails later, at lookup, because the second occurrence finds its
|
|
319
|
+
* slot already deleted by the first.
|
|
320
|
+
*/
|
|
321
|
+
export function coversByKey(expected, actual, expectedKey, actualKey, artifactPath) {
|
|
322
|
+
if (expected === ABSENT || actual === ABSENT)
|
|
323
|
+
return false;
|
|
324
|
+
if (!Array.isArray(actual))
|
|
325
|
+
return false;
|
|
326
|
+
const actualByKey = new Map();
|
|
327
|
+
for (const [index, element] of actual.entries()) {
|
|
328
|
+
const keyValue = keyValueOf(element, actualKey);
|
|
329
|
+
// A row missing actualKey can never be claimed by any expected digest
|
|
330
|
+
// (digestArtifact always returns a `sha256:`-prefixed string), but it
|
|
331
|
+
// still has to occupy its own slot: skipping it here would let it
|
|
332
|
+
// vanish from the cardinality check instead of surfacing as an
|
|
333
|
+
// unmatched extra.
|
|
334
|
+
const digest = keyValue === ABSENT
|
|
335
|
+
? `missing-actualKey:${index}`
|
|
336
|
+
: digestArtifact(keyValue, artifactPath);
|
|
337
|
+
if (actualByKey.has(digest))
|
|
338
|
+
return false;
|
|
339
|
+
actualByKey.set(digest, element);
|
|
340
|
+
}
|
|
341
|
+
for (const element of expected) {
|
|
342
|
+
const keyValue = keyValueOf(element, expectedKey);
|
|
343
|
+
if (keyValue === ABSENT)
|
|
344
|
+
return false;
|
|
345
|
+
const digest = digestArtifact(keyValue, artifactPath);
|
|
346
|
+
if (!actualByKey.delete(digest))
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return actualByKey.size === 0;
|
|
350
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AD-4's connectives, quantifiers, and three-valued resolution: the tree-walker
|
|
3
|
+
* that turns an `Expression` into one `CheckResolutionValue`. Leaf operators,
|
|
4
|
+
* `covers-by-key` included, live in `operators.ts`. Operand resolution, every
|
|
5
|
+
* pointer form including the bound-element `@/` form, is injected;
|
|
6
|
+
* `evidence-resolution.ts` supplies it, and `ResolveOperand` and
|
|
7
|
+
* `PointerDenotesCollection` are the consumer-side contract it satisfies.
|
|
8
|
+
*/
|
|
9
|
+
import type { CheckResolutionValue } from '../schemas/evidence-artifact.ts';
|
|
10
|
+
import type { Expression, Operand } from '../schemas/expression.ts';
|
|
11
|
+
import { type ResolvedValue } from './resolved-value.ts';
|
|
12
|
+
/**
|
|
13
|
+
* Resolves one operand to its evidence value. `boundElement` is the element a
|
|
14
|
+
* quantifier currently has bound, `ABSENT` outside any predicate: not `null`,
|
|
15
|
+
* since `JsonValue` already includes `null` and only a third, distinct value
|
|
16
|
+
* can tell "no active binding" apart from "bound to a JSON `null` element."
|
|
17
|
+
* Interpreting the `@/…` form itself belongs to AD-26's addressing grammar in
|
|
18
|
+
* `evidence-resolution.ts`; this type only fixes its shape.
|
|
19
|
+
*/
|
|
20
|
+
export type ResolveOperand = (operand: Operand, boundElement: ResolvedValue, artifactPath: string) => ResolvedValue;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a `{ pointer }` operand's declared response descriptor types it as a
|
|
23
|
+
* collection: AD-4 counts such a pointer resolving `absent` as an empty
|
|
24
|
+
* collection for the `insufficient-evidence` invariant. Consulted only when
|
|
25
|
+
* `resolveOperand` returns `ABSENT` for a `{ pointer }` operand; every other
|
|
26
|
+
* operand form is exempt. Takes the bare pointer string, since only that
|
|
27
|
+
* branch of the union ever calls it. A
|
|
28
|
+
* conforming implementation must return `false` for any bound-element (`@/…`)
|
|
29
|
+
* pointer: this predicate is called unconditionally for every `{ pointer }`
|
|
30
|
+
* operand including `@/…` ones, and a `true` answer there would break the
|
|
31
|
+
* soft-delete agreement pair (AD-4's own worked example).
|
|
32
|
+
*/
|
|
33
|
+
export type PointerDenotesCollection = (pointer: string) => boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The public entry point. Walks `expression` and produces one
|
|
36
|
+
* `CheckResolutionValue`, exactly what `Outcome.checkResolution` needs,
|
|
37
|
+
* unmodified. `boundElement` starts `ABSENT` at the root, where no quantifier
|
|
38
|
+
* has bound anything yet.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveCheck(expression: Expression, resolveOperand: ResolveOperand, pointerDenotesCollection: PointerDenotesCollection, regexMatchStepBudget: number, artifactPath: string): CheckResolutionValue;
|