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,222 @@
1
+ import { evaluateCoverage } from './coverage.js';
2
+ import { evaluateRelevance } from './relevance.js';
3
+ import { DISCIPLINE_RULES, relevancePredicateId, satisfactionPredicateId, } from './rules.js';
4
+ import { evaluateSatisfaction } from './satisfaction.js';
5
+ /** AD-31's three declaration states, a populated one splitting in two. Column order. */
6
+ export const DECLARATION_STATES = [
7
+ 'absent',
8
+ 'explicitly-empty',
9
+ 'witnessed',
10
+ 'unwitnessed',
11
+ ];
12
+ /** The pair each state asserts. `absent` and `unwitnessed` share one; the reason separates them. */
13
+ export const STATE_VERDICTS = {
14
+ absent: { relevant: true, satisfied: false },
15
+ 'explicitly-empty': { relevant: false, satisfied: true },
16
+ witnessed: { relevant: true, satisfied: true },
17
+ unwitnessed: { relevant: true, satisfied: false },
18
+ };
19
+ /** `|` escaped: a reason is free text (fixture 229), and a broken table is still byte-stable, so the drift check would pass it. */
20
+ const cell = (text) => text.replace(/\|/g, '\\|');
21
+ const row = (cells) => `| ${cells.map(cell).join(' | ')} |`;
22
+ const table = (headers, rows) => [
23
+ row(headers),
24
+ `| ${headers.map(() => '---').join(' | ')} |`,
25
+ ...rows.map(row),
26
+ ];
27
+ const code = (text) => `\`${text}\``;
28
+ const yesNo = (value) => (value ? 'yes' : 'no');
29
+ /** `explicitly-empty` reads `Explicitly empty`, so a new state arrives with its header. */
30
+ const heading = (state) => {
31
+ const words = state.replace(/-/g, ' ');
32
+ return `${words.slice(0, 1).toUpperCase()}${words.slice(1)}`;
33
+ };
34
+ const verdictsOf = (contract) => {
35
+ const relevance = evaluateRelevance(contract);
36
+ const satisfaction = evaluateSatisfaction(contract);
37
+ const paired = new Map();
38
+ for (const [index, rule] of DISCIPLINE_RULES.entries()) {
39
+ const relevant = relevance[index];
40
+ const satisfied = satisfaction[index];
41
+ if (relevant === undefined || satisfied === undefined)
42
+ continue;
43
+ paired.set(rule, {
44
+ relevant: relevant.relevant,
45
+ satisfied: satisfied.satisfied,
46
+ relevanceReason: relevant.reason,
47
+ satisfactionReason: satisfied.reason,
48
+ });
49
+ }
50
+ return paired;
51
+ };
52
+ const HEADER = [
53
+ '# AD-31 coverage predicates',
54
+ '',
55
+ 'Generated by `npm run generate:ad31-table`. Do not edit: `npm run check:ad31-table` compares this',
56
+ 'file byte for byte against the builder and fails on any difference.',
57
+ '',
58
+ "AD-20's seven discipline rules, each with a relevance predicate and a satisfaction predicate, run",
59
+ 'over the hand-authored contract corpus AD-31 names as their publication target. The historical',
60
+ 'worked example is not a publication target and appears nowhere below.',
61
+ ];
62
+ const COVERAGE_NOTE = [
63
+ 'Each cell names the corpus contract that occupies it. Relevance `false` with satisfaction `false`',
64
+ 'occupies no cell and cannot: a rule that is relevant for no site is satisfied vacuously, so a',
65
+ 'relevance predicate answering `false` forces its satisfaction twin to answer `true`.',
66
+ ];
67
+ /** The whole document. Throws a worded diagnosis on an unoccupied cell, which would otherwise publish as coverage. */
68
+ export function coveragePredicateTable(contracts, cells) {
69
+ // First, because every check after it resolves a `contractId` to a
70
+ // contract, and that resolution is ambiguous while two share one.
71
+ const byId = new Map();
72
+ for (const contract of contracts) {
73
+ if (byId.has(contract.contractId)) {
74
+ throw new Error(`coveragePredicateTable: two corpus contracts share the identifier ${contract.contractId}`);
75
+ }
76
+ byId.set(contract.contractId, contract);
77
+ }
78
+ const occupants = new Map();
79
+ for (const entry of cells) {
80
+ if (!byId.has(entry.contractId)) {
81
+ throw new Error(`coveragePredicateTable: cell ${entry.rule}/${entry.state} names contract ${entry.contractId}, which the corpus does not carry`);
82
+ }
83
+ const key = `${entry.rule}/${entry.state}`;
84
+ const named = occupants.get(key);
85
+ if (named === undefined)
86
+ occupants.set(key, [entry.contractId]);
87
+ else
88
+ named.push(entry.contractId);
89
+ }
90
+ const verdicts = new Map();
91
+ for (const contract of contracts) {
92
+ verdicts.set(contract.contractId, verdictsOf(contract));
93
+ }
94
+ const occupantOf = (rule, state) => {
95
+ const named = occupants.get(`${rule}/${state}`) ?? [];
96
+ if (named.length === 0) {
97
+ throw new Error(`coveragePredicateTable: no corpus contract occupies cell ${rule}/${state}`);
98
+ }
99
+ if (named.length > 1) {
100
+ throw new Error(`coveragePredicateTable: the corpus occupies cell ${rule}/${state} more than once (${named.join(', ')})`);
101
+ }
102
+ return named[0];
103
+ };
104
+ const coverageRows = DISCIPLINE_RULES.map((rule) => [
105
+ code(rule),
106
+ ...DECLARATION_STATES.map((state) => code(occupantOf(rule, state))),
107
+ ]);
108
+ // Both lookups are total: the check above put every cell's contract in
109
+ // `byId`, and `verdictsOf` keys by `DISCIPLINE_RULES`.
110
+ for (const entry of cells) {
111
+ const asserted = STATE_VERDICTS[entry.state];
112
+ const measured = verdicts.get(entry.contractId).get(entry.rule);
113
+ if (measured.relevant !== asserted.relevant ||
114
+ measured.satisfied !== asserted.satisfied) {
115
+ throw new Error(`coveragePredicateTable: ${entry.contractId} places ${entry.rule} at relevant=${measured.relevant}/satisfied=${measured.satisfied}, but cell ${entry.state} asserts relevant=${asserted.relevant}/satisfied=${asserted.satisfied}`);
116
+ }
117
+ }
118
+ // `absent` and `unwitnessed` share a verdict pair, so the check above passes
119
+ // with the two occupants swapped and the coverage table publishes each
120
+ // under the other's heading. The reason is what separates them.
121
+ for (const rule of DISCIPLINE_RULES) {
122
+ const absent = occupantOf(rule, 'absent');
123
+ const unwitnessed = occupantOf(rule, 'unwitnessed');
124
+ const absentReason = verdicts.get(absent).get(rule).satisfactionReason;
125
+ const unwitnessedReason = verdicts
126
+ .get(unwitnessed)
127
+ .get(rule).satisfactionReason;
128
+ if (absentReason === unwitnessedReason) {
129
+ throw new Error(`coveragePredicateTable: ${absent} and ${unwitnessed} both decide ${rule} on "${absentReason}", so the absent and unwitnessed cells are indistinguishable`);
130
+ }
131
+ }
132
+ // A contract occupying nothing still renders seven matrix rows, which reads
133
+ // as corpus coverage the cell index never claimed.
134
+ const occupying = new Set(cells.map((entry) => entry.contractId));
135
+ for (const contract of contracts) {
136
+ if (!occupying.has(contract.contractId)) {
137
+ throw new Error(`coveragePredicateTable: ${contract.contractId} occupies no cell, so nothing states what it is in the corpus for`);
138
+ }
139
+ }
140
+ const predicateRows = DISCIPLINE_RULES.map((rule) => [
141
+ code(rule),
142
+ code(relevancePredicateId(rule)),
143
+ code(satisfactionPredicateId(rule)),
144
+ ]);
145
+ const gapRows = [];
146
+ const matrixRows = [];
147
+ for (const contract of contracts) {
148
+ const paired = verdicts.get(contract.contractId);
149
+ // Joined on the rule: `CoverageGap` carries no diagnosis string, so the
150
+ // reasons come from the verdicts and the identifiers from the record.
151
+ // Spelling the identifiers as literals renders a document 231 fails.
152
+ const recorded = new Map(evaluateCoverage(contract).map((record) => [record.rule, record]));
153
+ for (const rule of DISCIPLINE_RULES) {
154
+ const measured = paired.get(rule);
155
+ const record = recorded.get(rule);
156
+ if (record !== undefined) {
157
+ gapRows.push([
158
+ code(contract.contractId),
159
+ code(rule),
160
+ code(record.relevancePredicate),
161
+ code(record.satisfactionPredicate),
162
+ record.severity,
163
+ measured.relevanceReason,
164
+ measured.satisfactionReason,
165
+ ]);
166
+ }
167
+ matrixRows.push([
168
+ code(contract.contractId),
169
+ code(rule),
170
+ yesNo(measured.relevant),
171
+ yesNo(measured.satisfied),
172
+ // `measured.relevant &&` is unfalsifiable for the reason
173
+ // `coverage.ts` gives at `evaluateCoverage`: fixture 168.
174
+ yesNo(measured.relevant && !measured.satisfied),
175
+ measured.relevanceReason,
176
+ measured.satisfactionReason,
177
+ ]);
178
+ }
179
+ }
180
+ const lines = [
181
+ ...HEADER,
182
+ '',
183
+ '## The fourteen predicates',
184
+ '',
185
+ ...table(['Rule', 'Relevance predicate', 'Satisfaction predicate'], predicateRows),
186
+ '',
187
+ '## Declaration-state coverage',
188
+ '',
189
+ ...table(['Rule', ...DECLARATION_STATES.map(heading)], coverageRows),
190
+ '',
191
+ ...COVERAGE_NOTE,
192
+ '',
193
+ '## Coverage gaps',
194
+ '',
195
+ 'One `CoverageGap` record per corpus contract per rule where relevance fired and satisfaction failed.',
196
+ '',
197
+ ...table([
198
+ 'Contract',
199
+ 'Rule',
200
+ 'Relevance predicate',
201
+ 'Satisfaction predicate',
202
+ 'Severity',
203
+ 'Why relevance fired',
204
+ 'Why satisfaction failed',
205
+ ], gapRows),
206
+ '',
207
+ '## The full matrix',
208
+ '',
209
+ 'Every corpus contract against every rule.',
210
+ '',
211
+ ...table([
212
+ 'Contract',
213
+ 'Rule',
214
+ 'Relevant',
215
+ 'Satisfied',
216
+ 'Gap',
217
+ 'Relevance reason',
218
+ 'Satisfaction reason',
219
+ ], matrixRows),
220
+ ];
221
+ return `${lines.join('\n')}\n`;
222
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What an operation declares it accepts, as predicates over `RequestShape`.
3
+ *
4
+ * At the `core/` root because `core/schemas/` holds Zod definitions only, and
5
+ * because AD-10's exemption is read by two modules: the compile check that
6
+ * enforces it and the reducer that records it.
7
+ */
8
+ import type { Operation } from './schemas/interface.ts';
9
+ /**
10
+ * AD-10's exemption predicate. Any of a channel's three lists naming a key
11
+ * counts: a permitted-only or types-only channel is still a surface a witness
12
+ * can vary.
13
+ */
14
+ export declare function declaresNoRequestKeys(operation: Operation): boolean;
15
+ /** Whether no channel declares a required key. */
16
+ export declare function declaresNoRequiredKeys(operation: Operation): boolean;
@@ -0,0 +1,18 @@
1
+ import { TRANSPORT_CHANNELS } from './schemas/pointer.js';
2
+ /**
3
+ * AD-10's exemption predicate. Any of a channel's three lists naming a key
4
+ * counts: a permitted-only or types-only channel is still a surface a witness
5
+ * can vary.
6
+ */
7
+ export function declaresNoRequestKeys(operation) {
8
+ return TRANSPORT_CHANNELS.every((channel) => {
9
+ const shape = operation.requestShape[channel];
10
+ return (shape.requiredKeys.length === 0 &&
11
+ shape.permittedKeys.length === 0 &&
12
+ Object.keys(shape.types).length === 0);
13
+ });
14
+ }
15
+ /** Whether no channel declares a required key. */
16
+ export function declaresNoRequiredKeys(operation) {
17
+ return TRANSPORT_CHANNELS.every((channel) => operation.requestShape[channel].requiredKeys.length === 0);
18
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `makeResolveOperand` and `makePointerDenotesCollection` satisfy the
3
+ * `ResolveOperand`/`PointerDenotesCollection` contract that `resolution.ts`
4
+ * depends on. They parse every pointer form AD-26 declares, including the
5
+ * bound-element `@/` form, and walk it into an already-selected
6
+ * `Observation`.
7
+ */
8
+ import type { EvalContract } from '../schemas/eval-contract.ts';
9
+ import type { JsonValue } from '../schemas/primitives.ts';
10
+ import type { Observation } from '../schemas/sealed-run-record.ts';
11
+ import { type PlanIndex } from '../seal/plan-index.ts';
12
+ import type { PointerDenotesCollection, ResolveOperand } from './resolution.ts';
13
+ import { type ResolvedValue } from './resolved-value.ts';
14
+ /**
15
+ * A canonical RFC 6901 array-index token: no leading zero except "0" itself,
16
+ * no sign, digits only. Excludes "-" (RFC 6901's "nonexistent member after
17
+ * the last element"): this grammar only reads, so "-" resolves ABSENT like
18
+ * any other unmatched token. Exported so `reachability.ts` checks against
19
+ * the same grammar this resolver walks.
20
+ */
21
+ export declare const ARRAY_INDEX_PATTERN: RegExp;
22
+ /**
23
+ * Walks `tail` (already `~0`/`~1`-decoded tokens) into `root`. `Object.hasOwn`
24
+ * guards every object step, so an own property named `__proto__` or
25
+ * `constructor` still resolves correctly. Without that guard, the lookup
26
+ * falls through to `Object.prototype`. Any miss, type mismatch, or tail
27
+ * running past a scalar collapses to `ABSENT` uniformly (AD-26).
28
+ */
29
+ export declare function walkTail(root: JsonValue, tail: readonly string[]): ResolvedValue;
30
+ /**
31
+ * Decodes `BoundElementPointer`'s tail. "@/" is the shortest legal pointer
32
+ * form and denotes the element itself. `decodeTail` special-cases only a
33
+ * fully empty string, so this function handles the "/" case itself before
34
+ * calling it.
35
+ */
36
+ export declare function decodeBoundElementTail(pointer: string): readonly string[];
37
+ /**
38
+ * The `ResolveOperand`. `stepObservations` holds one already-selected
39
+ * `Observation` per interaction step; `referenceSets` mirrors the contract's
40
+ * declared reference sets by identifier. Both maps are looked up with
41
+ * `Object.hasOwn`: `Identifier`'s charset admits `constructor`, the same
42
+ * prototype-chain gotcha `walkTail` guards against.
43
+ */
44
+ export declare function makeResolveOperand(stepObservations: Readonly<Record<string, Observation>>, referenceSets: Readonly<Record<string, JsonValue[]>>): ResolveOperand;
45
+ /**
46
+ * Only `response-body` can ever answer `true` (AD-19: `collectionLocations`
47
+ * is the only declared-collection surface, scoped to the body alone). The
48
+ * `PlanIndex` builds lazily on first call unless the caller supplies one,
49
+ * since a schema-admitted duplicate step or operation id would make building
50
+ * it eagerly throw before this function ever runs. Uses `stepOf`/`operationOf`
51
+ * rather than `resolveStep`/`resolveOperation`: those throw on a miss, which
52
+ * would break this function's always-returns-a-boolean contract.
53
+ */
54
+ export declare function makePointerDenotesCollection(contract: EvalContract, providedIndex?: PlanIndex): PointerDenotesCollection;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * `makeResolveOperand` and `makePointerDenotesCollection` satisfy the
3
+ * `ResolveOperand`/`PointerDenotesCollection` contract that `resolution.ts`
4
+ * depends on. They parse every pointer form AD-26 declares, including the
5
+ * bound-element `@/` form, and walk it into an already-selected
6
+ * `Observation`.
7
+ */
8
+ import { buildPlanIndex, decodeTail, parseEvidenceTarget, } from '../seal/plan-index.js';
9
+ import { ABSENT } from './resolved-value.js';
10
+ /**
11
+ * A canonical RFC 6901 array-index token: no leading zero except "0" itself,
12
+ * no sign, digits only. Excludes "-" (RFC 6901's "nonexistent member after
13
+ * the last element"): this grammar only reads, so "-" resolves ABSENT like
14
+ * any other unmatched token. Exported so `reachability.ts` checks against
15
+ * the same grammar this resolver walks.
16
+ */
17
+ export const ARRAY_INDEX_PATTERN = /^(?:0|[1-9][0-9]*)$/;
18
+ /**
19
+ * Walks `tail` (already `~0`/`~1`-decoded tokens) into `root`. `Object.hasOwn`
20
+ * guards every object step, so an own property named `__proto__` or
21
+ * `constructor` still resolves correctly. Without that guard, the lookup
22
+ * falls through to `Object.prototype`. Any miss, type mismatch, or tail
23
+ * running past a scalar collapses to `ABSENT` uniformly (AD-26).
24
+ */
25
+ export function walkTail(root, tail) {
26
+ let current = root;
27
+ for (const token of tail) {
28
+ if (current === null || typeof current !== 'object')
29
+ return ABSENT;
30
+ if (Array.isArray(current)) {
31
+ if (!ARRAY_INDEX_PATTERN.test(token))
32
+ return ABSENT;
33
+ const index = Number(token);
34
+ if (!Object.hasOwn(current, index))
35
+ return ABSENT;
36
+ current = current[index];
37
+ continue;
38
+ }
39
+ if (!Object.hasOwn(current, token))
40
+ return ABSENT;
41
+ current = current[token];
42
+ }
43
+ return current;
44
+ }
45
+ /**
46
+ * Decodes `BoundElementPointer`'s tail. "@/" is the shortest legal pointer
47
+ * form and denotes the element itself. `decodeTail` special-cases only a
48
+ * fully empty string, so this function handles the "/" case itself before
49
+ * calling it.
50
+ */
51
+ export function decodeBoundElementTail(pointer) {
52
+ const tailSource = pointer.slice(1);
53
+ return tailSource === '/' ? [] : decodeTail(tailSource);
54
+ }
55
+ /**
56
+ * Selects the channel `target` names off one `Observation`. `stdout`/`stderr`
57
+ * can carry a tail even though they're bare strings; `walkTail` already
58
+ * resolves any non-empty tail against a string to `ABSENT`, so no special
59
+ * case is needed here.
60
+ */
61
+ function channelRoot(observation, target) {
62
+ switch (target.channel) {
63
+ case 'response-body':
64
+ return observation.responseBody;
65
+ case 'response-headers':
66
+ return observation.responseHeaders;
67
+ case 'response-status':
68
+ return observation.responseStatus;
69
+ case 'stdout':
70
+ return observation.stdout;
71
+ case 'stderr':
72
+ return observation.stderr;
73
+ case 'exit-code':
74
+ return observation.exitCode;
75
+ case 'call-inputs': {
76
+ const { transportChannel } = target;
77
+ if (transportChannel === null) {
78
+ // parseEvidenceTarget sets transportChannel exactly when the
79
+ // channel is 'call-inputs', so this throw should never fire.
80
+ throw new TypeError('call-inputs evidence target carries no transport channel');
81
+ }
82
+ return observation.callInputs[transportChannel];
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * The `ResolveOperand`. `stepObservations` holds one already-selected
88
+ * `Observation` per interaction step; `referenceSets` mirrors the contract's
89
+ * declared reference sets by identifier. Both maps are looked up with
90
+ * `Object.hasOwn`: `Identifier`'s charset admits `constructor`, the same
91
+ * prototype-chain gotcha `walkTail` guards against.
92
+ */
93
+ export function makeResolveOperand(stepObservations, referenceSets) {
94
+ return (operand, boundElement, _artifactPath) => {
95
+ if ('literal' in operand)
96
+ return operand.literal;
97
+ if ('referenceSet' in operand) {
98
+ if (!Object.hasOwn(referenceSets, operand.referenceSet))
99
+ return ABSENT;
100
+ return referenceSets[operand.referenceSet];
101
+ }
102
+ const { pointer } = operand;
103
+ if (pointer.startsWith('@')) {
104
+ // ABSENT means "no active binding." A correctly-compiled contract
105
+ // never hits this branch, but resolveOperand returns ABSENT here
106
+ // so it never has to throw.
107
+ if (boundElement === ABSENT)
108
+ return ABSENT;
109
+ return walkTail(boundElement, decodeBoundElementTail(pointer));
110
+ }
111
+ const target = parseEvidenceTarget(pointer);
112
+ if (!Object.hasOwn(stepObservations, target.stepId))
113
+ return ABSENT;
114
+ const observation = stepObservations[target.stepId];
115
+ return walkTail(channelRoot(observation, target), target.tail);
116
+ };
117
+ }
118
+ function tokensEqual(a, b) {
119
+ return a.length === b.length && a.every((token, index) => token === b[index]);
120
+ }
121
+ /**
122
+ * Only `response-body` can ever answer `true` (AD-19: `collectionLocations`
123
+ * is the only declared-collection surface, scoped to the body alone). The
124
+ * `PlanIndex` builds lazily on first call unless the caller supplies one,
125
+ * since a schema-admitted duplicate step or operation id would make building
126
+ * it eagerly throw before this function ever runs. Uses `stepOf`/`operationOf`
127
+ * rather than `resolveStep`/`resolveOperation`: those throw on a miss, which
128
+ * would break this function's always-returns-a-boolean contract.
129
+ */
130
+ export function makePointerDenotesCollection(contract, providedIndex) {
131
+ let index = providedIndex;
132
+ const getIndex = () => {
133
+ index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces);
134
+ return index;
135
+ };
136
+ return (pointer) => {
137
+ if (pointer.startsWith('@'))
138
+ return false;
139
+ const target = parseEvidenceTarget(pointer);
140
+ if (target.channel !== 'response-body')
141
+ return false;
142
+ const step = getIndex().stepOf(target.stepId);
143
+ if (step === undefined)
144
+ return false;
145
+ const operation = getIndex().operationOf(step.operationId);
146
+ if (operation === undefined)
147
+ return false;
148
+ const { collectionLocations } = operation.responseDescriptor;
149
+ if (collectionLocations === null)
150
+ return false;
151
+ return collectionLocations.some((location) => tokensEqual(decodeTail(location.pointer), target.tail));
152
+ };
153
+ }
@@ -0,0 +1,87 @@
1
+ import type { JsonValue, KeyedShapeDescriptor } from '../schemas/primitives.ts';
2
+ import { ABSENT, type ResolvedValue } from './resolved-value.ts';
3
+ /** Reads only whether resolution happened; `null` counts as present (AD-26). */
4
+ export declare function existence(value: ResolvedValue, _artifactPath: string): boolean;
5
+ /** AD-26's exact complement of `existence`. */
6
+ export declare function absence(value: ResolvedValue, _artifactPath: string): boolean;
7
+ /**
8
+ * Cost-ordered: the `ABSENT` guard runs before any digest call, since
9
+ * `ABSENT` is a JS `symbol` and `digestArtifact` faults on symbols. Only a
10
+ * matching compound type reaches structural comparison, so a scalar operand
11
+ * never inherits the digest path's fault surface.
12
+ */
13
+ export declare function equality(a: ResolvedValue, b: ResolvedValue, artifactPath: string): boolean;
14
+ /**
15
+ * Unconditionally structural (AD-27): the whole point of deep comparison.
16
+ * `ABSENT` is guarded first, same reason as `equality`'s. A canonicalization
17
+ * fault propagates undecorated: an unevaluable operand, not a false match.
18
+ */
19
+ export declare function deepEquality(a: ResolvedValue, b: ResolvedValue, artifactPath: string): boolean;
20
+ /**
21
+ * `false` on `ABSENT` is correct only when `value`'s pointer is not itself
22
+ * collection-typed; the resolver in `resolution.ts` disambiguates the two
23
+ * cases before calling in here.
24
+ */
25
+ export declare function setMembership(value: ResolvedValue, set: JsonValue[], artifactPath: string): boolean;
26
+ /**
27
+ * `candidate: ResolvedValue | JsonValue[]` because `Containment`'s operand
28
+ * can resolve to either a single value or a `referenceSet`'s member array.
29
+ *
30
+ * An array-shaped `candidate` is always read as a subset check, never as a
31
+ * single element to search for: this function receives resolved values only,
32
+ * so it cannot tell a `{ literal }` array from a resolved `{ referenceSet }`.
33
+ * Known, accepted limitation.
34
+ */
35
+ export declare function containment(container: ResolvedValue, candidate: ResolvedValue | JsonValue[], artifactPath: string): boolean;
36
+ /**
37
+ * `false` if `value === ABSENT` or not a string. Pattern validity is checked
38
+ * here; the match-step budget is a two-tier static gate, structural
39
+ * nested-quantifier rejection then a linear estimate. The gate stays static
40
+ * because AD-1 keeps this function synchronous and pure, and native `RegExp`
41
+ * exposes no step counter to read.
42
+ */
43
+ export declare function regexMatch(value: ResolvedValue, pattern: string, matchStepBudget: number, artifactPath: string): boolean;
44
+ /**
45
+ * Comparison is non-strict: ties are allowed. String comparison is plain
46
+ * `<=`/`>=` (UTF-16 code-unit order), matching AD-27's key-sorting basis.
47
+ */
48
+ export declare function ordering(collection: ResolvedValue, key: string, order: 'ascending' | 'descending', _artifactPath: string): boolean;
49
+ /**
50
+ * An empty array is a legitimate zero count, never special-cased; the
51
+ * resolver in `resolution.ts` intercepts before this runs on a genuinely
52
+ * empty collection. The allowed deviation is compared unrounded: `actual` is
53
+ * an integer, so `<=` against a fractional deviation is already exact, and
54
+ * rounding either direction would move the declared boundary.
55
+ */
56
+ export declare function countTolerance(collection: ResolvedValue, expected: number, tolerance: number, relative: boolean, _artifactPath: string): boolean;
57
+ /**
58
+ * The closed set is `permittedKeys` alone, never unioned with `requiredKeys`:
59
+ * a self-contradictory descriptor is unsatisfiable rather than repaired.
60
+ * `requiredKeys ⊆ permittedKeys` is unrefined in
61
+ * `core/schemas/primitives.ts` and closed by no compile-time check, so a key
62
+ * that is required and not permitted reaches here and fails every value.
63
+ */
64
+ export declare function shape(value: ResolvedValue, descriptor: KeyedShapeDescriptor, _artifactPath: string): boolean;
65
+ /**
66
+ * AD-4's bijection: equal cardinality and a distinct `actual` match per
67
+ * `expected` element on the named keys. `ABSENT` on either side resolves
68
+ * `false`, including a fully-missing `actual` collection: AD-4 calls that "a
69
+ * detected defect, not an empty examination," overriding the general
70
+ * empty-collection invariant for this operator alone. A non-array `actual` is
71
+ * an operand type this operator does not accept, which AD-4 assigns to
72
+ * `malformed-operator-expression`; `core/compile/expression-legality.ts`
73
+ * checks the operand *form* under that code and leaves this position's
74
+ * declared type unchecked, so a non-array reaching here resolves `false`.
75
+ *
76
+ * Cardinality is never checked separately: `actualByKey` starts with one
77
+ * entry per `actual` element (a synthetic slot for a keyless one, so nothing
78
+ * goes uncounted), and the `expected` loop deletes one entry per match. A
79
+ * final `actualByKey.size === 0` is the bijection condition itself, since the
80
+ * map's starting size already equals `actual`'s cardinality. A duplicate
81
+ * `actualKey` fails immediately, at construction, because the second element
82
+ * finds its slot already occupied; a duplicate `expectedKey` is assumed
83
+ * prevented at compile time under `malformed-operator-expression`, but if it
84
+ * occurs it fails later, at lookup, because the second occurrence finds its
85
+ * slot already deleted by the first.
86
+ */
87
+ export declare function coversByKey(expected: JsonValue[] | typeof ABSENT, actual: ResolvedValue, expectedKey: string, actualKey: string, artifactPath: string): boolean;