eval-quality 0.1.0 → 0.3.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 (179) hide show
  1. package/README.md +142 -67
  2. package/corpus/dev/README.md +20 -13
  3. package/corpus/dev/compile-seal-example/brief.json +1 -1
  4. package/corpus/dev/compile-seal-example/contract.json +1 -1
  5. package/corpus/dev/contracts/absent-collection-locations.json +1 -1
  6. package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
  7. package/corpus/dev/contracts/absent-success-indicator.json +1 -1
  8. package/corpus/dev/contracts/empty-channel-roles.json +1 -1
  9. package/corpus/dev/contracts/empty-collection-locations.json +1 -1
  10. package/corpus/dev/contracts/empty-request-shapes.json +1 -1
  11. package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
  12. package/corpus/dev/contracts/fragment-selection.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  19. package/corpus/dev/contracts/review-corpus.json +1 -0
  20. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  21. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  22. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  23. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  24. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  25. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  26. package/corpus/dev/index.json +1 -1
  27. package/dist/application/index.d.ts +2 -0
  28. package/dist/application/index.js +1 -0
  29. package/dist/application/score.d.ts +48 -0
  30. package/dist/application/score.js +186 -0
  31. package/dist/cli/arguments.d.ts +7 -3
  32. package/dist/cli/arguments.js +73 -5
  33. package/dist/cli/exit-codes.d.ts +6 -2
  34. package/dist/cli/exit-codes.js +8 -9
  35. package/dist/cli/main.js +9 -0
  36. package/dist/cli/render.d.ts +3 -6
  37. package/dist/cli/render.js +57 -5
  38. package/dist/cli/run.d.ts +13 -3
  39. package/dist/cli/run.js +136 -16
  40. package/dist/core/canonical/canonicalize.d.ts +12 -0
  41. package/dist/core/canonical/canonicalize.js +18 -4
  42. package/dist/core/compile/bindings.d.ts +89 -0
  43. package/dist/core/compile/bindings.js +334 -0
  44. package/dist/core/compile/compile.d.ts +17 -0
  45. package/dist/core/compile/compile.js +37 -1
  46. package/dist/core/compile/excluded-content.d.ts +11 -0
  47. package/dist/core/compile/excluded-content.js +42 -0
  48. package/dist/core/compile/expression-legality.d.ts +49 -0
  49. package/dist/core/compile/expression-legality.js +158 -38
  50. package/dist/core/compile/forbidden-inputs.d.ts +8 -1
  51. package/dist/core/compile/forbidden-inputs.js +16 -3
  52. package/dist/core/compile/interface-inventory.d.ts +57 -2
  53. package/dist/core/compile/interface-inventory.js +119 -13
  54. package/dist/core/compile/reachability.d.ts +28 -1
  55. package/dist/core/compile/reachability.js +167 -35
  56. package/dist/core/compile/schema-version.d.ts +2 -0
  57. package/dist/core/compile/schema-version.js +25 -0
  58. package/dist/core/compile/sensitivity-witness.d.ts +31 -12
  59. package/dist/core/compile/sensitivity-witness.js +110 -23
  60. package/dist/core/compile/step-reference.d.ts +2 -0
  61. package/dist/core/compile/step-reference.js +49 -0
  62. package/dist/core/coverage/operations.d.ts +62 -0
  63. package/dist/core/coverage/operations.js +57 -0
  64. package/dist/core/coverage/relevance.d.ts +4 -2
  65. package/dist/core/coverage/relevance.js +22 -23
  66. package/dist/core/coverage/satisfaction.d.ts +2 -2
  67. package/dist/core/coverage/satisfaction.js +73 -48
  68. package/dist/core/declared-inputs.d.ts +83 -4
  69. package/dist/core/declared-inputs.js +105 -8
  70. package/dist/core/emit/emit.d.ts +9 -0
  71. package/dist/core/emit/emit.js +214 -0
  72. package/dist/core/emit/private-artifact-digest.d.ts +11 -0
  73. package/dist/core/emit/private-artifact-digest.js +31 -0
  74. package/dist/core/evaluate/evidence-resolution.d.ts +10 -3
  75. package/dist/core/evaluate/evidence-resolution.js +74 -11
  76. package/dist/core/evaluate/operators.d.ts +22 -0
  77. package/dist/core/evaluate/operators.js +17 -1
  78. package/dist/core/evaluate/resolution.d.ts +14 -3
  79. package/dist/core/evaluate/resolution.js +63 -3
  80. package/dist/core/excluded-content.d.ts +65 -0
  81. package/dist/core/excluded-content.js +113 -0
  82. package/dist/core/failure-codes.d.ts +2 -2
  83. package/dist/core/failure-codes.js +7 -2
  84. package/dist/core/ingest/conditions.d.ts +201 -0
  85. package/dist/core/ingest/conditions.js +48 -0
  86. package/dist/core/ingest/index.d.ts +13 -0
  87. package/dist/core/ingest/index.js +11 -0
  88. package/dist/core/ingest/ingest.d.ts +68 -0
  89. package/dist/core/ingest/ingest.js +318 -0
  90. package/dist/core/lineage/stage-table.d.ts +21 -4
  91. package/dist/core/lineage/stage-table.js +35 -3
  92. package/dist/core/preflight/plan.d.ts +7 -5
  93. package/dist/core/preflight/plan.js +75 -32
  94. package/dist/core/preflight/projection.d.ts +6 -3
  95. package/dist/core/preflight/projection.js +22 -2
  96. package/dist/core/preflight/reduce.js +47 -7
  97. package/dist/core/preflight/witness-evidence.d.ts +5 -5
  98. package/dist/core/preflight/witness-evidence.js +73 -20
  99. package/dist/core/schemas/artifact.d.ts +1049 -30
  100. package/dist/core/schemas/constraint-ledger.js +51 -0
  101. package/dist/core/schemas/defect-signature.d.ts +695 -0
  102. package/dist/core/schemas/defect-signature.js +175 -0
  103. package/dist/core/schemas/eval-contract.d.ts +550 -12
  104. package/dist/core/schemas/eval-contract.js +32 -2
  105. package/dist/core/schemas/evidence-artifact.d.ts +109 -19
  106. package/dist/core/schemas/evidence-artifact.js +64 -8
  107. package/dist/core/schemas/interface.d.ts +741 -13
  108. package/dist/core/schemas/interface.js +120 -7
  109. package/dist/core/schemas/isolation-manifest.js +16 -9
  110. package/dist/core/schemas/plan.d.ts +329 -3
  111. package/dist/core/schemas/plan.js +89 -3
  112. package/dist/core/schemas/pointer.d.ts +47 -1
  113. package/dist/core/schemas/pointer.js +89 -8
  114. package/dist/core/schemas/port-messages.d.ts +194 -10
  115. package/dist/core/schemas/port-messages.js +73 -6
  116. package/dist/core/schemas/probe-body.d.ts +18 -0
  117. package/dist/core/schemas/probe-body.js +13 -0
  118. package/dist/core/schemas/probe-qualification.d.ts +168 -0
  119. package/dist/core/schemas/probe-qualification.js +106 -0
  120. package/dist/core/schemas/probe.d.ts +431 -0
  121. package/dist/core/schemas/probe.js +6 -2
  122. package/dist/core/schemas/scoring-policy.d.ts +1 -0
  123. package/dist/core/schemas/scoring-policy.js +6 -1
  124. package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
  125. package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
  126. package/dist/core/schemas/sealed-run-record.d.ts +161 -12
  127. package/dist/core/schemas/sealed-run-record.js +92 -15
  128. package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
  129. package/dist/core/schemas/sensitivity-witness.js +61 -5
  130. package/dist/core/score/binding-order.d.ts +31 -0
  131. package/dist/core/score/binding-order.js +88 -0
  132. package/dist/core/score/bindings.d.ts +93 -0
  133. package/dist/core/score/bindings.js +309 -0
  134. package/dist/core/score/ladder-table.d.ts +23 -0
  135. package/dist/core/score/ladder-table.js +109 -0
  136. package/dist/core/score/ladder.d.ts +229 -0
  137. package/dist/core/score/ladder.js +464 -0
  138. package/dist/core/score/mode-agreement.d.ts +30 -0
  139. package/dist/core/score/mode-agreement.js +16 -0
  140. package/dist/core/score/outcome-table.d.ts +17 -0
  141. package/dist/core/score/outcome-table.js +172 -0
  142. package/dist/core/score/outcome.d.ts +466 -0
  143. package/dist/core/score/outcome.js +562 -0
  144. package/dist/core/score/qualification.d.ts +83 -0
  145. package/dist/core/score/qualification.js +645 -0
  146. package/dist/core/score/quotation.d.ts +56 -0
  147. package/dist/core/score/quotation.js +226 -0
  148. package/dist/core/score/reduce-trials.d.ts +80 -0
  149. package/dist/core/score/reduce-trials.js +90 -0
  150. package/dist/core/score/score.d.ts +72 -0
  151. package/dist/core/score/score.js +607 -0
  152. package/dist/core/score/selection.d.ts +80 -0
  153. package/dist/core/score/selection.js +98 -0
  154. package/dist/core/score/strength.d.ts +58 -0
  155. package/dist/core/score/strength.js +227 -0
  156. package/dist/core/score/witness.d.ts +138 -0
  157. package/dist/core/score/witness.js +320 -0
  158. package/dist/core/seal/derived-reference.d.ts +2 -9
  159. package/dist/core/seal/derived-reference.js +293 -46
  160. package/dist/core/seal/plan-index.d.ts +18 -3
  161. package/dist/core/seal/plan-index.js +44 -8
  162. package/dist/core/seal/seal.js +8 -3
  163. package/dist/core/stage-contracts.d.ts +62 -2
  164. package/dist/index.d.ts +1 -1
  165. package/dist/index.js +1 -1
  166. package/dist/ports/environment-probe-port.d.ts +60 -4
  167. package/dist/testing/index.d.ts +2 -0
  168. package/dist/testing/index.js +1 -0
  169. package/dist/testing/probe-conformance.d.ts +29 -0
  170. package/dist/testing/probe-conformance.js +34 -5
  171. package/package.json +12 -3
  172. package/schemas/eval-contract.schema.json +1787 -797
  173. package/schemas/evidence-artifact.schema.json +126 -9
  174. package/schemas/isolation-manifest.schema.json +17 -10
  175. package/schemas/probe.schema.json +636 -5
  176. package/schemas/rubric.schema.json +1 -1
  177. package/schemas/scoring-policy.schema.json +8 -1
  178. package/schemas/sealed-evaluator-brief.schema.json +11 -1
  179. package/schemas/sealed-run-record.schema.json +314 -40
@@ -0,0 +1,89 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ import type { InteractionStep } from '../schemas/plan.ts';
3
+ import type { InputChannelName } from '../schemas/pointer.ts';
4
+ import { type EvidenceTarget } from '../seal/plan-index.ts';
5
+ /**
6
+ * The one channel a captured pointer may name: whichever channel the
7
+ * referenced operation's own response descriptor describes.
8
+ *
9
+ * `ResponseDescriptor` declares `requiredKeys`, `permittedKeys`, `types`,
10
+ * `successIndicator`, `channelRoles`, and `collectionLocations`, and every one
11
+ * of them is about the channel the operation nominates. Off an interface that
12
+ * speaks HTTP that is the response body; off a command it is the stream the
13
+ * operation names. `response-headers` and `response-status` are never it,
14
+ * because `Observation.responseHeaders` admits objects, arrays, numbers, and
15
+ * `null`, so a header capture compiled as a `string` could resolve to an
16
+ * object at score time.
17
+ */
18
+ /** One `{ captured }` binding, resolved to the pointer target it addresses. */
19
+ export type CapturedBinding = {
20
+ readonly transportChannel: InputChannelName;
21
+ readonly key: string;
22
+ readonly pointer: string;
23
+ readonly target: EvidenceTarget;
24
+ };
25
+ /**
26
+ * Every captured binding one step declares, in fixed transport-channel order
27
+ * then by key name, so which binding a check reports never depends on a
28
+ * caller-keyed map's insertion order. Exported because `score/binding-order.ts`
29
+ * and `score/bindings.ts` need the same reading of which bindings are captures.
30
+ */
31
+ export declare function capturedBindings(step: InteractionStep): readonly CapturedBinding[];
32
+ /**
33
+ * `binding-cycle`: a cycle over the union of the capture edges and AD-39's
34
+ * `after` edges that contains at least one capture edge.
35
+ *
36
+ * Both edge kinds assert that this step's observation comes after that one, so
37
+ * a capture B to A together with `A.after = B` is an unsatisfiable ordering
38
+ * while each graph alone stays acyclic. A cycle made only of `after` edges is
39
+ * left to `checkNestedTemporalClause`, which already catches every one of them,
40
+ * so requiring a capture edge in the cycle keeps the two codes disjoint.
41
+ *
42
+ * Decided by strongly connected components rather than by a depth-first walk
43
+ * that inspects each back edge. A cycle lies entirely inside one component, and
44
+ * a capture edge whose endpoints share a component is closed by a path back
45
+ * through that component, so "some cycle contains a capture edge" is exactly
46
+ * "some capture edge has both endpoints in one component". A self-capture is
47
+ * the singleton case and needs no rule of its own. The depth-first form was
48
+ * written first and rejected: settling a node after rejecting a pure-`after`
49
+ * back edge can hide a mixed cycle a later root would reach, and whether it
50
+ * does depends on the order edges happen to be enumerated in, which is an
51
+ * invariant nothing states.
52
+ *
53
+ * Nodes are the plan's declared step ids; an edge whose target no step declares
54
+ * is dropped, since a dangling reference is not a cycle and AD-39 makes a
55
+ * dangling `after` permissive besides. The binding reported is the first, in
56
+ * the plan's own declaration order, whose endpoints share a component.
57
+ */
58
+ export declare function checkBindingCycle(contract: EvalContract): void;
59
+ /**
60
+ * `captured-channel-undeclared`: a captured pointer naming any AD-26 channel
61
+ * but the one the referenced operation's response descriptor describes. That
62
+ * is `response-body` off an interface that speaks HTTP and whichever channel
63
+ * `descriptorChannel` nominates off one that runs behind a command, which is
64
+ * the same rule read against the declaration rather than assumed.
65
+ *
66
+ * Every other channel is refused for a reason that does not depend on the
67
+ * kind: `call-inputs` addresses a step's own request, and `response-headers`,
68
+ * `response-status`, and `exit-code` have no declared structure to give a
69
+ * captured value a type. On the `artifact` channel the identifier is compared
70
+ * too, so a capture from a file the operation writes but does not describe is
71
+ * refused alongside one from a file it never writes.
72
+ */
73
+ export declare function checkCapturedChannel(contract: EvalContract): void;
74
+ /**
75
+ * `unreachable-check-evidence` over the captured-pointer residue: an
76
+ * unresolvable step or operation, an undeclared body key, a tail that is not
77
+ * exactly one segment, an array index, an absent, indeterminate, or non-scalar
78
+ * declared type, and a declared type that does not equal the bound
79
+ * parameter's.
80
+ *
81
+ * A capture naming another channel is reported here only for the two conditions
82
+ * that are decidable without a response descriptor: an unresolvable step and an
83
+ * unresolvable operation. Everything else about such a pointer is
84
+ * `checkCapturedChannel`'s. That split is what lets this check run at the
85
+ * registry's third rung without shadowing the fifteenth, and it keeps the
86
+ * higher-ranked code first on a pointer that is unresolvable and off-body at
87
+ * once.
88
+ */
89
+ export declare function checkCapturedReachability(contract: EvalContract): void;
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Owed item 3's compile-time checks over captured input bindings:
3
+ * `binding-cycle` over the ordering graph, `captured-channel-undeclared` on a
4
+ * pointer naming any channel but `response-body`, and the typed residue that
5
+ * fires the shipped `unreachable-check-evidence`.
6
+ *
7
+ * The three run at two registry positions. `checkCapturedReachability` fires
8
+ * `unreachable-check-evidence`, whose rung is third, so it runs beside
9
+ * `checkEvidenceReachability`; the two new codes run at their own inserted
10
+ * position. Running captured reachability late would let a lower-ranked code
11
+ * win on a contract carrying both defects, which `compile.ts`'s own documented
12
+ * priority forbids.
13
+ *
14
+ * `evaluatePointerReachability` decides the step, operation, body-key, and
15
+ * scalar-descent half. The scalar determination, the one-segment tail rule, and
16
+ * type equality are this module's own.
17
+ */
18
+ import { boundChannelsOf, descriptorChannelOf, requestShapeOf, targetsDescribedChannel, } from '../declared-inputs.js';
19
+ import { ARRAY_INDEX_PATTERN } from '../evaluate/evidence-resolution.js';
20
+ import { StructuralFailure } from '../failure-codes.js';
21
+ import { JsonTypeName } from '../schemas/primitives.js';
22
+ import { anyOperationOf, buildPlanIndex, parseEvidenceTarget, resolveStep, } from '../seal/plan-index.js';
23
+ import { evaluatePointerReachability } from './reachability.js';
24
+ /**
25
+ * Every captured binding one step declares, in fixed transport-channel order
26
+ * then by key name, so which binding a check reports never depends on a
27
+ * caller-keyed map's insertion order. Exported because `score/binding-order.ts`
28
+ * and `score/bindings.ts` need the same reading of which bindings are captures.
29
+ */
30
+ export function capturedBindings(step) {
31
+ const captures = [];
32
+ for (const { channel: transportChannel, bound: map } of boundChannelsOf(step.inputBinding)) {
33
+ if (map === null)
34
+ continue;
35
+ for (const key of Object.keys(map).sort()) {
36
+ const value = map[key];
37
+ if (value === undefined || !('captured' in value))
38
+ continue;
39
+ captures.push({
40
+ transportChannel,
41
+ key,
42
+ pointer: value.captured,
43
+ target: parseEvidenceTarget(value.captured),
44
+ });
45
+ }
46
+ }
47
+ return captures;
48
+ }
49
+ // The one shipped precedent for addressing a caller-keyed binding entry
50
+ // (`interface-inventory.ts`'s undeclared-key throw), reused so the two new
51
+ // codes and the widened `undeclared-mandatory-input` agree on one spelling.
52
+ function bindingPath(step, capture) {
53
+ return `EvalContract.interactionPlan[stepId=${step.stepId}].inputBinding.${capture.transportChannel}[${JSON.stringify(capture.key)}]`;
54
+ }
55
+ // ---- binding-cycle -------------------------------------------------------
56
+ /**
57
+ * `binding-cycle`: a cycle over the union of the capture edges and AD-39's
58
+ * `after` edges that contains at least one capture edge.
59
+ *
60
+ * Both edge kinds assert that this step's observation comes after that one, so
61
+ * a capture B to A together with `A.after = B` is an unsatisfiable ordering
62
+ * while each graph alone stays acyclic. A cycle made only of `after` edges is
63
+ * left to `checkNestedTemporalClause`, which already catches every one of them,
64
+ * so requiring a capture edge in the cycle keeps the two codes disjoint.
65
+ *
66
+ * Decided by strongly connected components rather than by a depth-first walk
67
+ * that inspects each back edge. A cycle lies entirely inside one component, and
68
+ * a capture edge whose endpoints share a component is closed by a path back
69
+ * through that component, so "some cycle contains a capture edge" is exactly
70
+ * "some capture edge has both endpoints in one component". A self-capture is
71
+ * the singleton case and needs no rule of its own. The depth-first form was
72
+ * written first and rejected: settling a node after rejecting a pure-`after`
73
+ * back edge can hide a mixed cycle a later root would reach, and whether it
74
+ * does depends on the order edges happen to be enumerated in, which is an
75
+ * invariant nothing states.
76
+ *
77
+ * Nodes are the plan's declared step ids; an edge whose target no step declares
78
+ * is dropped, since a dangling reference is not a cycle and AD-39 makes a
79
+ * dangling `after` permissive besides. The binding reported is the first, in
80
+ * the plan's own declaration order, whose endpoints share a component.
81
+ */
82
+ export function checkBindingCycle(contract) {
83
+ const declaredStepIds = new Set(contract.interactionPlan.map((step) => step.stepId));
84
+ const edgesFrom = new Map();
85
+ const addEdge = (from, to) => {
86
+ const list = edgesFrom.get(from);
87
+ if (list === undefined)
88
+ edgesFrom.set(from, [to]);
89
+ else
90
+ list.push(to);
91
+ };
92
+ for (const step of contract.interactionPlan) {
93
+ for (const capture of capturedBindings(step)) {
94
+ if (declaredStepIds.has(capture.target.stepId)) {
95
+ addEdge(step.stepId, capture.target.stepId);
96
+ }
97
+ }
98
+ if (step.after !== null && declaredStepIds.has(step.after)) {
99
+ addEdge(step.stepId, step.after);
100
+ }
101
+ }
102
+ const component = stronglyConnectedComponents(declaredStepIds, edgesFrom);
103
+ for (const step of contract.interactionPlan) {
104
+ for (const capture of capturedBindings(step)) {
105
+ const target = capture.target.stepId;
106
+ if (!declaredStepIds.has(target))
107
+ continue;
108
+ if (component.get(step.stepId) !== component.get(target))
109
+ continue;
110
+ throw new StructuralFailure('binding-cycle', bindingPath(step, capture), `captured pointer "${capture.pointer}" closes a cycle over the capture and temporal-clause edges; a captured value has no earlier step to resolve from (AD-39)`);
111
+ }
112
+ }
113
+ }
114
+ /** Tarjan's algorithm, returning each node's component identifier. */
115
+ function stronglyConnectedComponents(nodes, edgesFrom) {
116
+ const order = new Map();
117
+ const lowLink = new Map();
118
+ const onStack = new Set();
119
+ const stack = [];
120
+ const component = new Map();
121
+ let nextOrder = 0;
122
+ let nextComponent = 0;
123
+ const connect = (node) => {
124
+ order.set(node, nextOrder);
125
+ lowLink.set(node, nextOrder);
126
+ nextOrder += 1;
127
+ stack.push(node);
128
+ onStack.add(node);
129
+ for (const next of edgesFrom.get(node) ?? []) {
130
+ if (!order.has(next)) {
131
+ connect(next);
132
+ lowLink.set(node, Math.min(lowLink.get(node) ?? 0, lowLink.get(next) ?? 0));
133
+ }
134
+ else if (onStack.has(next)) {
135
+ lowLink.set(node, Math.min(lowLink.get(node) ?? 0, order.get(next) ?? 0));
136
+ }
137
+ }
138
+ if (lowLink.get(node) !== order.get(node))
139
+ return;
140
+ const id = nextComponent;
141
+ nextComponent += 1;
142
+ for (;;) {
143
+ const member = stack.pop();
144
+ if (member === undefined)
145
+ break;
146
+ onStack.delete(member);
147
+ component.set(member, id);
148
+ if (member === node)
149
+ break;
150
+ }
151
+ };
152
+ for (const node of nodes) {
153
+ if (!order.has(node))
154
+ connect(node);
155
+ }
156
+ return component;
157
+ }
158
+ // ---- captured-channel-undeclared -----------------------------------------
159
+ /**
160
+ * `captured-channel-undeclared`: a captured pointer naming any AD-26 channel
161
+ * but the one the referenced operation's response descriptor describes. That
162
+ * is `response-body` off an interface that speaks HTTP and whichever channel
163
+ * `descriptorChannel` nominates off one that runs behind a command, which is
164
+ * the same rule read against the declaration rather than assumed.
165
+ *
166
+ * Every other channel is refused for a reason that does not depend on the
167
+ * kind: `call-inputs` addresses a step's own request, and `response-headers`,
168
+ * `response-status`, and `exit-code` have no declared structure to give a
169
+ * captured value a type. On the `artifact` channel the identifier is compared
170
+ * too, so a capture from a file the operation writes but does not describe is
171
+ * refused alongside one from a file it never writes.
172
+ */
173
+ export function checkCapturedChannel(contract) {
174
+ let index;
175
+ for (const step of contract.interactionPlan) {
176
+ for (const capture of capturedBindings(step)) {
177
+ index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
178
+ const referenced = index.stepOf(capture.target.stepId);
179
+ const operation = referenced === undefined
180
+ ? undefined
181
+ : anyOperationOf(index, referenced.operationId);
182
+ // An unresolvable reference is `checkCapturedReachability`'s, at a
183
+ // higher rung. With nothing to ask, this check falls back to the
184
+ // api answer so an unresolvable off-body capture still reports.
185
+ const capturable = operation === undefined
186
+ ? 'response-body'
187
+ : descriptorChannelOf(operation);
188
+ if (operation === undefined
189
+ ? capture.target.channel === capturable
190
+ : targetsDescribedChannel(operation, capture.target)) {
191
+ continue;
192
+ }
193
+ throw new StructuralFailure('captured-channel-undeclared', bindingPath(step, capture), `captured pointer "${capture.pointer}" names the ${capture.target.channel} channel, which the referenced operation's response descriptor does not declare (AD-26)`);
194
+ }
195
+ }
196
+ }
197
+ // ---- the typed residue: unreachable-check-evidence -----------------------
198
+ const SCALAR_TYPES = new Set(JsonTypeName.options.filter((name) => name !== 'object' && name !== 'array'));
199
+ /**
200
+ * The type a captured pointer resolves to, or why nothing scalar is declared
201
+ * there. The tail must be exactly one segment: `ResponseDescriptor.types` is
202
+ * keyed by plain key name, so a nested segment has no declared type to compare
203
+ * against, and an array element has none either even where
204
+ * `evaluatePointerReachability` admits the index against a root collection.
205
+ */
206
+ function capturedType(target, index) {
207
+ const channel = target.channel;
208
+ const segments = target.tail.length;
209
+ if (segments !== 1) {
210
+ return {
211
+ reason: `addresses ${segments === 0 ? `the whole ${channel}` : `a ${channel} path ${segments} segments deep`}, which declares no scalar to capture`,
212
+ };
213
+ }
214
+ const key = target.tail[0];
215
+ if (key === undefined) {
216
+ // Unreachable: the length check above guarantees one segment.
217
+ throw new TypeError('captured pointer tail is one segment but is empty');
218
+ }
219
+ if (ARRAY_INDEX_PATTERN.test(key)) {
220
+ return {
221
+ reason: `addresses ${channel} element ${key}, which no declaration gives a type`,
222
+ };
223
+ }
224
+ const step = resolveStep(index, target.stepId);
225
+ const operation = anyOperationOf(index, step.operationId);
226
+ if (operation === undefined) {
227
+ throw new TypeError(`step names an operation the permitted interfaces do not declare: ${step.operationId}`);
228
+ }
229
+ const declared = operation.responseDescriptor.types[key];
230
+ if (declared === undefined || declared === null) {
231
+ return {
232
+ reason: `addresses ${channel} field "${key}", whose type operation "${operation.operationId}" ${declared === undefined ? 'does not declare' : 'declares indeterminate'}`,
233
+ };
234
+ }
235
+ if (!SCALAR_TYPES.has(declared)) {
236
+ return {
237
+ reason: `addresses ${channel} field "${key}", which operation "${operation.operationId}" declares "${declared}" rather than a scalar`,
238
+ };
239
+ }
240
+ return { type: declared };
241
+ }
242
+ /**
243
+ * The type the bound parameter itself is declared as. An indeterminate
244
+ * declaration on either side is not equal to anything and fails closed, which
245
+ * is AD-31's disposition for an indeterminate descriptor. A step whose own
246
+ * operation does not resolve is skipped, matching
247
+ * `checkUndeclaredMandatoryInput`: that defect belongs to a separate
248
+ * cross-field rule.
249
+ */
250
+ function boundParameterType(step, capture, index) {
251
+ const operation = anyOperationOf(index, step.operationId);
252
+ if (operation === undefined)
253
+ return null;
254
+ const shape = requestShapeOf(operation, capture.transportChannel);
255
+ // A channel the operation does not accept input on declares no type for
256
+ // the key either, so there is nothing to compare and the check abstains
257
+ // for the same reason the undeclared-key branch below does.
258
+ if (shape === undefined)
259
+ return null;
260
+ // A key the operation declares in neither list is an input the contract did
261
+ // not declare, which is `undeclared-mandatory-input`'s and strict-only under
262
+ // AD-4. This check runs unconditionally, so claiming it here would reject a
263
+ // non-strict contract under a code that names a different defect, and would
264
+ // treat a captured binding more harshly than the same key bound to a literal.
265
+ if (!shape.requiredKeys.includes(capture.key) &&
266
+ !shape.permittedKeys.includes(capture.key)) {
267
+ return null;
268
+ }
269
+ const declared = shape.types[capture.key];
270
+ if (declared === undefined || declared === null) {
271
+ return {
272
+ reason: `binds ${capture.transportChannel} parameter "${capture.key}", whose type operation "${operation.operationId}" ${declared === undefined ? 'does not declare' : 'declares indeterminate'}, so no type equality is decidable`,
273
+ };
274
+ }
275
+ return { type: declared };
276
+ }
277
+ /**
278
+ * `unreachable-check-evidence` over the captured-pointer residue: an
279
+ * unresolvable step or operation, an undeclared body key, a tail that is not
280
+ * exactly one segment, an array index, an absent, indeterminate, or non-scalar
281
+ * declared type, and a declared type that does not equal the bound
282
+ * parameter's.
283
+ *
284
+ * A capture naming another channel is reported here only for the two conditions
285
+ * that are decidable without a response descriptor: an unresolvable step and an
286
+ * unresolvable operation. Everything else about such a pointer is
287
+ * `checkCapturedChannel`'s. That split is what lets this check run at the
288
+ * registry's third rung without shadowing the fifteenth, and it keeps the
289
+ * higher-ranked code first on a pointer that is unresolvable and off-body at
290
+ * once.
291
+ */
292
+ export function checkCapturedReachability(contract) {
293
+ let index;
294
+ for (const step of contract.interactionPlan) {
295
+ for (const capture of capturedBindings(step)) {
296
+ const path = bindingPath(step, capture);
297
+ index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
298
+ // Resolved before the channel test, so a pointer that is both
299
+ // unresolvable and on a non-body channel reports the higher-ranked
300
+ // code. Deferring this to `checkCapturedChannel` would fire index 15
301
+ // where AD-5's order gives index 2, and would assert something about
302
+ // a response descriptor that no declared operation supplies. The
303
+ // wording matches `evaluatePointerReachability`'s own two reasons, so
304
+ // one defect reads the same however it is reached.
305
+ const referenced = index.stepOf(capture.target.stepId);
306
+ if (referenced === undefined) {
307
+ throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" names a step the interaction plan does not declare`);
308
+ }
309
+ const operation = anyOperationOf(index, referenced.operationId);
310
+ if (operation === undefined) {
311
+ throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" names step "${capture.target.stepId}", which names operation "${referenced.operationId}", not declared by any permitted interface`);
312
+ }
313
+ if (!targetsDescribedChannel(operation, capture.target))
314
+ continue;
315
+ const reachability = evaluatePointerReachability(capture.pointer, index);
316
+ if (!reachability.reachable) {
317
+ throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" ${reachability.reason}`);
318
+ }
319
+ const captured = capturedType(capture.target, index);
320
+ if (captured.type === undefined) {
321
+ throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" ${captured.reason}`);
322
+ }
323
+ const bound = boundParameterType(step, capture, index);
324
+ if (bound === null)
325
+ continue;
326
+ if (bound.type === undefined) {
327
+ throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" resolves to a declared "${captured.type}", but the step ${bound.reason}`);
328
+ }
329
+ if (bound.type !== captured.type) {
330
+ throw new StructuralFailure('unreachable-check-evidence', path, `captured pointer "${capture.pointer}" resolves to a declared "${captured.type}", which is not the "${bound.type}" the bound ${capture.transportChannel} parameter "${capture.key}" is declared as`);
331
+ }
332
+ }
333
+ }
334
+ }
@@ -13,6 +13,23 @@
13
13
  * code; the other two witness checks fire different codes and run
14
14
  * unconditionally, after the registry order, since AD-5 names no rung for them.
15
15
  *
16
+ * `checkCapturedReachability` fires the shipped `unreachable-check-evidence`,
17
+ * so it runs at that code's own third rung, beside `checkEvidenceReachability`.
18
+ * Grouping it with the two codes owed item 3 added, at the inserted position
19
+ * below, would let a lower-ranked code win on a contract carrying both defects.
20
+ *
21
+ * `checkArtifactReferences` fires `unresolved-artifact-reference`, the
22
+ * registry's last code, but runs near the front for the reason its own call
23
+ * site records: it names a fault that reachability can only describe the
24
+ * consequence of, and reachability abstains on the same pointer so the two
25
+ * never race.
26
+ *
27
+ * `checkStepReferenceReducibility` runs last, after every other check has had
28
+ * its say. It is the only check that reports what a LATER stage cannot do
29
+ * rather than what this contract declares wrongly, so a contract with an
30
+ * ordinary declaration fault should hear about that fault rather than about
31
+ * the sealing consequence of it.
32
+ *
16
33
  * `checkRubricIdentifiers` fires `rubric-unanchored` but runs ahead of
17
34
  * `checkRubricReasoningProse`, which outranks it in the registry. A duplicated
18
35
  * rubric or criterion id makes every `rubrics[id=...]` path the other three
@@ -13,26 +13,58 @@
13
13
  * code; the other two witness checks fire different codes and run
14
14
  * unconditionally, after the registry order, since AD-5 names no rung for them.
15
15
  *
16
+ * `checkCapturedReachability` fires the shipped `unreachable-check-evidence`,
17
+ * so it runs at that code's own third rung, beside `checkEvidenceReachability`.
18
+ * Grouping it with the two codes owed item 3 added, at the inserted position
19
+ * below, would let a lower-ranked code win on a contract carrying both defects.
20
+ *
21
+ * `checkArtifactReferences` fires `unresolved-artifact-reference`, the
22
+ * registry's last code, but runs near the front for the reason its own call
23
+ * site records: it names a fault that reachability can only describe the
24
+ * consequence of, and reachability abstains on the same pointer so the two
25
+ * never race.
26
+ *
27
+ * `checkStepReferenceReducibility` runs last, after every other check has had
28
+ * its say. It is the only check that reports what a LATER stage cannot do
29
+ * rather than what this contract declares wrongly, so a contract with an
30
+ * ordinary declaration fault should hear about that fault rather than about
31
+ * the sealing consequence of it.
32
+ *
16
33
  * `checkRubricIdentifiers` fires `rubric-unanchored` but runs ahead of
17
34
  * `checkRubricReasoningProse`, which outranks it in the registry. A duplicated
18
35
  * rubric or criterion id makes every `rubrics[id=...]` path the other three
19
36
  * rubric checks emit address two things, so identifiers are settled before any
20
37
  * of them reports.
21
38
  */
39
+ import { EVAL_CONTRACT_SCHEMA_VERSION } from '../schemas/eval-contract.js';
40
+ import { checkBindingCycle, checkCapturedChannel, checkCapturedReachability, } from './bindings.js';
22
41
  import { checkObservableSuccessCriterion, checkRequirementLinkage, } from './declarations.js';
42
+ import { checkExcludedContent } from './excluded-content.js';
23
43
  import { checkOperandLegality, checkQuantifierNesting, checkQuantifierOverNonCollection, checkReferenceSetResolution, checkRegexConstructs, } from './expression-legality.js';
24
44
  import { checkForbiddenInputFloor, checkScopedResourceReferences, } from './forbidden-inputs.js';
25
- import { checkDuplicateOperationSignature, checkInterfaceKind, checkUndeclaredMandatoryInput, } from './interface-inventory.js';
45
+ import { checkArtifactReferences, checkDuplicateOperationSignature, checkInterfaceKind, checkUndeclaredMandatoryInput, } from './interface-inventory.js';
26
46
  import { checkOracleAlignment, checkOracleChannel } from './oracle-alignment.js';
27
47
  import { checkBoundElementScope, checkEvidenceReachability, } from './reachability.js';
28
48
  import { checkRubricAnchoring, checkRubricEvidenceReachability, checkRubricIdentifiers, checkRubricReasoningProse, } from './rubrics.js';
49
+ import { checkSchemaVersion } from './schema-version.js';
29
50
  import { checkNestedTemporalClause, checkScriptingBound, } from './scripting-bound.js';
30
51
  import { checkSensitivityWitnessDeclared, checkWitnessLegality, checkWitnessLegIdentifiers, } from './sensitivity-witness.js';
52
+ import { checkStepReferenceReducibility } from './step-reference.js';
31
53
  import { checkWaiverCompleteness } from './waivers.js';
32
54
  export function compile(contract, options) {
55
+ // First, and before any check reads a declaration. AD-11 makes an unequal
56
+ // stamp a rejection rather than a degraded read, and every check below is
57
+ // written against this version's field shapes.
58
+ checkSchemaVersion(contract.schemaVersion, EVAL_CONTRACT_SCHEMA_VERSION, 'EvalContract.schemaVersion');
33
59
  checkRequirementLinkage(contract);
34
60
  checkObservableSuccessCriterion(contract);
61
+ // Ahead of reachability, because a pointer naming an artifact nothing
62
+ // declares has no operation shape to be reachable against, and the code
63
+ // that names the fault directly should not lose the race to the code that
64
+ // describes its consequence.
65
+ checkArtifactReferences(contract);
35
66
  checkEvidenceReachability(contract);
67
+ checkCapturedReachability(contract);
36
68
  checkBoundElementScope(contract);
37
69
  checkOperandLegality(contract);
38
70
  checkRegexConstructs(contract);
@@ -49,13 +81,17 @@ export function compile(contract, options) {
49
81
  checkInterfaceKind(contract);
50
82
  checkNestedTemporalClause(contract);
51
83
  checkScriptingBound(contract);
84
+ checkBindingCycle(contract);
85
+ checkCapturedChannel(contract);
52
86
  checkRubricIdentifiers(contract);
53
87
  checkRubricReasoningProse(contract);
54
88
  checkRubricAnchoring(contract);
55
89
  checkRubricEvidenceReachability(contract);
56
90
  checkForbiddenInputFloor(contract);
91
+ checkExcludedContent(contract);
57
92
  checkScopedResourceReferences(contract);
58
93
  checkWaiverCompleteness(contract);
94
+ checkStepReferenceReducibility(contract);
59
95
  // Identifiers before legality. A duplicated or plan-colliding leg id makes
60
96
  // the legality check's question ("does the relation address both legs?")
61
97
  // unanswerable, so legality-first reports an unreachable-evidence failure on
@@ -0,0 +1,11 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ /**
3
+ * `excluded-content-in-declaration`: the contract carries a value-shaped secret.
4
+ *
5
+ * The first hit is reported and the rest are dropped. A contract carrying one
6
+ * has to be re-authored before it compiles at all, so enumerating the others
7
+ * would print more of the secret material into a failure message that gets
8
+ * pasted into a terminal, an issue, and a log. The message names the category
9
+ * and the path and quotes nothing.
10
+ */
11
+ export declare function checkExcludedContent(contract: EvalContract): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * AD-18's exclusion, read against the contract rather than against a directory.
3
+ *
4
+ * AD-18 excludes credentials, tokens, real names, email addresses, account
5
+ * identifiers, and transaction content from every artifact this package produces
6
+ * or publishes, and says so binds "published examples and test fixtures as
7
+ * strictly as real runs". The only mechanism enforcing it was a test that greps
8
+ * `corpus/dev`, which reaches the corpus this repository ships and no contract an
9
+ * adopter authors. A token written into a contract therefore compiled clean and
10
+ * travelled onward: `seal` copies permitted interfaces onto the sealed brief, so
11
+ * the value reaches the one artifact AD-16 hands to an evaluator, and `emit`
12
+ * copies quoted evidence onto the evidence artifact.
13
+ *
14
+ * The whole contract is scanned, not a chosen subtree. Choosing one would have to
15
+ * be right about where an author pastes a secret, and the answer is wherever the
16
+ * field they were filling in happened to be: a witness input value, a fixture
17
+ * reset payload, an oracle's expected operand, a behaviour description. The
18
+ * scan's cost is one walk of an already-parsed object.
19
+ *
20
+ * Unconditional rather than strict-gated, unlike `undeclared-mandatory-input`
21
+ * next to it. That code is strict-only because AD-4 leaves a contract two
22
+ * legitimate readings of how completely it declares its inputs. AD-18 has one
23
+ * reading and no lenient mode: a credential that reaches a published artifact is
24
+ * unrecoverable, which is the harm the decision names.
25
+ */
26
+ import { scanExcludedContent } from '../excluded-content.js';
27
+ import { StructuralFailure } from '../failure-codes.js';
28
+ /**
29
+ * `excluded-content-in-declaration`: the contract carries a value-shaped secret.
30
+ *
31
+ * The first hit is reported and the rest are dropped. A contract carrying one
32
+ * has to be re-authored before it compiles at all, so enumerating the others
33
+ * would print more of the secret material into a failure message that gets
34
+ * pasted into a terminal, an issue, and a log. The message names the category
35
+ * and the path and quotes nothing.
36
+ */
37
+ export function checkExcludedContent(contract) {
38
+ const hit = scanExcludedContent(contract, 'EvalContract')[0];
39
+ if (hit === undefined)
40
+ return;
41
+ throw new StructuralFailure('excluded-content-in-declaration', hit.path, `carries a value shaped like ${hit.category}, which AD-18 excludes from every artifact this package produces; store a digest or an AD-8 opaque reference instead. The matched text is not quoted here, since a failure message travels further than the contract does`);
42
+ }
@@ -1,10 +1,58 @@
1
1
  import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ import type { Expression, Operand, SetOperand } from '../schemas/expression.ts';
3
+ import type { AnyOperation } from '../schemas/interface.ts';
4
+ type OperandPosition = 0 | 1 | 'collection';
5
+ type OperandBearingOp = Exclude<Expression['op'], 'not' | 'all' | 'any'>;
6
+ type Visitor = {
7
+ onOperand?: (operand: Operand, op: OperandBearingOp, position: OperandPosition, path: string, quantifierDepth: number) => void;
8
+ onSetOperand?: (setOperand: SetOperand, path: string) => void;
9
+ onQuantifier?: (expr: {
10
+ op: 'for-all' | 'for-any';
11
+ collection: Operand;
12
+ predicate: Expression;
13
+ }, path: string, quantifierDepth: number) => void;
14
+ onCoversByKey?: (expr: Extract<Expression, {
15
+ op: 'covers-by-key';
16
+ }>, path: string, quantifierDepth: number) => void;
17
+ onRegex?: (pattern: string, path: string) => void;
18
+ };
19
+ /**
20
+ * The one expression walker in this package. Contract-free and a superset of
21
+ * the narrower walks the checks below need, so a probe-side legality pass
22
+ * reuses it rather than adding a fifth traversal that can drift from it.
23
+ */
24
+ export declare function walkExpression(expr: Expression, quantifierDepth: number, path: string, visitor: Visitor): void;
25
+ /**
26
+ * `checkOperandLegality` over one bare `Expression`, minus the `covers-by-key`
27
+ * `expectedKey` uniqueness rule, which reads the contract's declared reference
28
+ * sets and has no probe-side form: a probe-side condition may carry no
29
+ * `{ referenceSet }` operand at all.
30
+ */
31
+ export declare function checkExpressionOperandLegality(expression: Expression, artifactPath: string): void;
32
+ /** `checkRegexConstructs` over one bare `Expression`. */
33
+ export declare function checkExpressionRegexConstructs(expression: Expression, artifactPath: string): void;
34
+ /** `checkQuantifierNesting` over one bare `Expression`. */
35
+ export declare function checkExpressionQuantifierNesting(expression: Expression, artifactPath: string): void;
2
36
  /** Checks each operator's operands against its position-specific constraints. */
3
37
  export declare function checkOperandLegality(contract: EvalContract): void;
4
38
  /** Rejects invalid regexes, partial anchors, backreferences, and lookbehind. */
5
39
  export declare function checkRegexConstructs(contract: EvalContract): void;
6
40
  /** Rejects nested quantifiers and covers-by-key inside a quantifier. */
7
41
  export declare function checkQuantifierNesting(contract: EvalContract): void;
42
+ /**
43
+ * `checkQuantifierOverNonCollection` over one bare `Expression` rooted at a
44
+ * known set of step identifiers, all of which resolve to one operation. The
45
+ * `legIds` argument is load-bearing: omit it and the check silently no-ops on
46
+ * every expression, which is the trap the witness branch below already warns
47
+ * about. It reads the operation's declared response types, never its
48
+ * `collectionLocations`; a declared non-array type beats a contradictory
49
+ * collection location, and a re-derivation that read the other field would
50
+ * disagree with the compiler on a committed fixture.
51
+ */
52
+ export declare function checkExpressionQuantifierOverNonCollection(expression: Expression, artifactPath: string, scope: {
53
+ readonly operation: AnyOperation;
54
+ readonly legIds: readonly string[];
55
+ }): void;
8
56
  /** Checks response-body collection pointers, after bound-element substitution. */
9
57
  export declare function checkQuantifierOverNonCollection(contract: EvalContract): void;
10
58
  /**
@@ -12,3 +60,4 @@ export declare function checkQuantifierOverNonCollection(contract: EvalContract)
12
60
  * This preserves legal identifiers such as `constructor`.
13
61
  */
14
62
  export declare function checkReferenceSetResolution(contract: EvalContract): void;
63
+ export {};