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,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;