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,177 @@
1
+ /**
2
+ * Two compile-time checks over an EvalContract's oracle trees:
3
+ * `malformed-operator-expression` (a `@/` bound-element pointer outside any
4
+ * quantifier's predicate) and `unreachable-check-evidence` (a pointer the
5
+ * declared interfaces cannot produce). Both throw `StructuralFailure` on the
6
+ * first violation, matching `auditBriefScripting`'s fail-fast convention.
7
+ * `evaluatePointerReachability` is exported separately as the non-throwing
8
+ * per-pointer core, for reuse and direct testing.
9
+ */
10
+ import { ARRAY_INDEX_PATTERN } from '../evaluate/evidence-resolution.js';
11
+ import { StructuralFailure } from '../failure-codes.js';
12
+ import { JsonTypeName } from '../schemas/primitives.js';
13
+ import { buildPlanIndex, parseEvidenceTarget, } from '../seal/plan-index.js';
14
+ function visitOperand(operand, path, insideQuantifier, visit) {
15
+ if ('pointer' in operand)
16
+ visit({ pointer: operand.pointer, path, insideQuantifier });
17
+ // `{ literal }` and `{ referenceSet }` address no interaction evidence.
18
+ }
19
+ // `set-membership`'s second operand is a `SetOperand` (`{ referenceSet }` or
20
+ // `{ literal: [...] }`). That type excludes `{ pointer }`, so only its first
21
+ // operand can carry one. Every other tuple-shaped op falls through to the
22
+ // default branch, since each declares `operands: Operand[]`.
23
+ function visitExpression(expression, path, insideQuantifier, visit) {
24
+ switch (expression.op) {
25
+ case 'not':
26
+ visitExpression(expression.operands[0], `${path}.operands[0]`, insideQuantifier, visit);
27
+ return;
28
+ case 'all':
29
+ case 'any':
30
+ expression.operands.forEach((child, index) => {
31
+ visitExpression(child, `${path}.operands[${index}]`, insideQuantifier, visit);
32
+ });
33
+ return;
34
+ case 'for-all':
35
+ case 'for-any':
36
+ visitOperand(expression.collection, `${path}.collection`, insideQuantifier, visit);
37
+ visitExpression(expression.predicate, `${path}.predicate`, true, visit);
38
+ return;
39
+ case 'set-membership':
40
+ visitOperand(expression.operands[0], `${path}.operands[0]`, insideQuantifier, visit);
41
+ return;
42
+ default:
43
+ expression.operands.forEach((operand, index) => {
44
+ visitOperand(operand, `${path}.operands[${index}]`, insideQuantifier, visit);
45
+ });
46
+ }
47
+ }
48
+ function forEachCheckPointer(contract, visit) {
49
+ contract.oracles.forEach((oracle) => {
50
+ if (oracle.check === null)
51
+ return;
52
+ visitExpression(oracle.check, 'check', false, (site) => visit(site, oracle.id));
53
+ });
54
+ }
55
+ // ---- malformed-operator-expression: @/ outside any quantifier -----------
56
+ export function checkBoundElementScope(contract) {
57
+ forEachCheckPointer(contract, (site, oracleId) => {
58
+ if (site.pointer.startsWith('@') && !site.insideQuantifier) {
59
+ throw new StructuralFailure('malformed-operator-expression', `EvalContract.oracles[id=${oracleId}].${site.path}`, `bound-element pointer "${site.pointer}" appears outside any quantifier's predicate; "@/" binds only inside a quantifier (AD-26)`);
60
+ }
61
+ });
62
+ }
63
+ const reachable = () => ({ reachable: true });
64
+ const unreachable = (reason) => ({
65
+ reachable: false,
66
+ reason,
67
+ });
68
+ // Every JsonTypeName value except `object` and `array` is scalar. Widened
69
+ // to `ReadonlySet<string>` so `.has(declaredType)` below still typechecks.
70
+ const SCALAR_TYPES = new Set(JsonTypeName.options.filter((name) => name !== 'object' && name !== 'array'));
71
+ // A field blocks further descent only when its declared type is definitely
72
+ // scalar. `undefined` (not declared) and `null` (declared, type not stated)
73
+ // both stay permissive: nothing rules out descent.
74
+ function descendsIntoDeclaredScalar(types, tail, firstToken) {
75
+ if (tail.length <= 1)
76
+ return false;
77
+ const declaredType = types[firstToken];
78
+ return (declaredType !== undefined &&
79
+ declaredType !== null &&
80
+ SCALAR_TYPES.has(declaredType));
81
+ }
82
+ /**
83
+ * Non-throwing core that `checkEvidenceReachability` wraps; also called
84
+ * directly by tests.
85
+ */
86
+ export function evaluatePointerReachability(pointer, index) {
87
+ // A `@/` pointer is relative to a bound element. Nothing declared to
88
+ // check reachability against.
89
+ if (pointer.startsWith('@'))
90
+ return reachable();
91
+ const target = parseEvidenceTarget(pointer);
92
+ const step = index.stepOf(target.stepId);
93
+ if (step === undefined) {
94
+ return unreachable('names a step the interaction plan does not declare');
95
+ }
96
+ const operation = index.operationOf(step.operationId);
97
+ if (operation === undefined) {
98
+ return unreachable(`names step "${target.stepId}", which names operation "${step.operationId}", not declared by any permitted interface`);
99
+ }
100
+ if (target.channel === 'stdout' || target.channel === 'stderr') {
101
+ // stdout/stderr are always bare strings. A non-empty tail proves the
102
+ // pointer unreachable.
103
+ if (target.tail.length > 0) {
104
+ return unreachable(`addresses a field inside ${target.channel}, which never carries structure to descend into`);
105
+ }
106
+ return reachable();
107
+ }
108
+ if (target.channel === 'response-body') {
109
+ if (target.tail.length === 0)
110
+ return reachable();
111
+ const firstToken = target.tail[0];
112
+ if (firstToken === undefined) {
113
+ // Unreachable: the length check above guarantees an element.
114
+ throw new TypeError('evidence-target tail is non-empty but has no first token');
115
+ }
116
+ const { requiredKeys, permittedKeys, types, collectionLocations } = operation.responseDescriptor;
117
+ // A root-declared collection (`pointer: ''`) indexes directly, bypassing
118
+ // the key check below. `expectedCardinality` bounds the array size
119
+ // (`exact` is the true count; `at-most`/`page-bounded` is an upper
120
+ // bound), so an index at or past it is unreachable.
121
+ const rootCollection = collectionLocations?.find((location) => location.pointer === '');
122
+ if (rootCollection !== undefined && ARRAY_INDEX_PATTERN.test(firstToken)) {
123
+ const { expectedCardinality } = rootCollection;
124
+ const bound = expectedCardinality.mode === 'exact'
125
+ ? expectedCardinality.count
126
+ : expectedCardinality.max;
127
+ if (Number(firstToken) >= bound) {
128
+ return unreachable(`addresses response-body index ${firstToken}, out of bounds for the declared root collection's expectedCardinality (${expectedCardinality.mode} ${bound})`);
129
+ }
130
+ return reachable();
131
+ }
132
+ if (!requiredKeys.includes(firstToken) &&
133
+ !permittedKeys.includes(firstToken)) {
134
+ return unreachable(`addresses response-body field "${firstToken}", which operation "${operation.operationId}" declares in neither requiredKeys nor permittedKeys`);
135
+ }
136
+ if (descendsIntoDeclaredScalar(types, target.tail, firstToken)) {
137
+ return unreachable(`descends into response-body field "${firstToken}", which operation "${operation.operationId}" declares a scalar with no further structure`);
138
+ }
139
+ return reachable();
140
+ }
141
+ if (target.channel === 'call-inputs') {
142
+ if (target.tail.length === 0)
143
+ return reachable();
144
+ const { transportChannel } = target;
145
+ if (transportChannel === null) {
146
+ // Unreachable: parseEvidenceTarget's own guarantee.
147
+ throw new TypeError('call-inputs evidence target carries no transport channel');
148
+ }
149
+ const firstToken = target.tail[0];
150
+ if (firstToken === undefined) {
151
+ throw new TypeError('evidence-target tail is non-empty but has no first token');
152
+ }
153
+ const { requiredKeys, permittedKeys, types } = operation.requestShape[transportChannel];
154
+ if (!requiredKeys.includes(firstToken) &&
155
+ !permittedKeys.includes(firstToken)) {
156
+ return unreachable(`addresses call-inputs ${transportChannel} field "${firstToken}", which operation "${operation.operationId}" declares in neither requiredKeys nor permittedKeys`);
157
+ }
158
+ if (descendsIntoDeclaredScalar(types, target.tail, firstToken)) {
159
+ return unreachable(`descends into call-inputs ${transportChannel} field "${firstToken}", which operation "${operation.operationId}" declares a scalar with no further structure`);
160
+ }
161
+ return reachable();
162
+ }
163
+ // response-headers, response-status, and exit-code declare no shape, so
164
+ // a resolving operation is the entire compile-time check available.
165
+ return reachable();
166
+ }
167
+ /** `unreachable-check-evidence`: an interaction-rooted pointer the declared interfaces cannot produce. */
168
+ export function checkEvidenceReachability(contract) {
169
+ let index;
170
+ forEachCheckPointer(contract, (site, oracleId) => {
171
+ index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
172
+ const result = evaluatePointerReachability(site.pointer, index);
173
+ if (!result.reachable) {
174
+ throw new StructuralFailure('unreachable-check-evidence', `EvalContract.oracles[id=${oracleId}].${site.path}`, `"${site.pointer}" ${result.reason}`);
175
+ }
176
+ });
177
+ }
@@ -0,0 +1,55 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ /**
3
+ * Runs ahead of the three checks below, reversing AD-5's registry order for
4
+ * this one pair. Every other rubric check reports a path shaped
5
+ * `rubrics[id=...]`, and a duplicated id makes that path address two rubrics,
6
+ * so identifiers are settled first and every later path is unambiguous by
7
+ * construction.
8
+ *
9
+ * Criterion ids are unique within a rubric and may repeat across rubrics: a
10
+ * judge-call score cites `rubricId` and `criterionId` together, so the pair is
11
+ * what has to be unique.
12
+ */
13
+ export declare function checkRubricIdentifiers(contract: EvalContract): void;
14
+ /** The first forbidden term in `text`, or `undefined`. */
15
+ export declare function findReasoningProseTerm(text: string): string | undefined;
16
+ /**
17
+ * Criterion text is scanned first: AD-17 and AD-5 both scope this code to a
18
+ * criterion, so where a rubric carries the defect in more than one place the
19
+ * in-scope site is the one reported. Anchors and penalties are a deliberate
20
+ * expansion. An anchor reading "level 3: the reasoning is sound" scores
21
+ * reasoning through every criterion at once, and a penalty deducting for an
22
+ * unstated rationale scores it through a deduction. Leave either unscanned and
23
+ * an author walks around the criterion scan in one edit.
24
+ */
25
+ export declare function checkRubricReasoningProse(contract: EvalContract): void;
26
+ /**
27
+ * AD-22's three shapes, spelled out: an unanchored scale (rules 1 to 3), an
28
+ * unbounded length (rule 4), and missing named failure-mode penalties (rules 5
29
+ * and 6). Rule 7 applies rule 3's argument to a criterion and is the one rule
30
+ * that stretches AD-22's wording: a criterion stating no question is
31
+ * unanswerable in a way `rubric-evidence-unreachable` cannot see, since its
32
+ * pointer may resolve perfectly.
33
+ *
34
+ * A single-level scale, a repeated penalty name, and a rubric with no criteria
35
+ * are all legal, each with an accept test guarding it. The first two were
36
+ * proposed and rejected in review; AD-19 settles the third by stating that
37
+ * AD-17's rules bind only when a rubric names criteria.
38
+ */
39
+ export declare function checkRubricAnchoring(contract: EvalContract): void;
40
+ /**
41
+ * `evaluatePointerReachability` is reused, so the two evidence codes give the
42
+ * same reason for the same broken pointer. Two limits come with it. A `@/`
43
+ * pointer cannot parse into `criterion.evidence`, an `InteractionPointer`, so
44
+ * that branch is dead here. And a channel that declares no shape resolves as
45
+ * soon as its step does, so a criterion rooted at `stdout` compiles on an
46
+ * `api`-kind contract that can never produce one. That gap belongs to
47
+ * `unreachable-check-evidence` as well, and a second implementation written to
48
+ * close it for rubrics alone would leave the two codes disagreeing about the
49
+ * same pointer.
50
+ *
51
+ * `duplicateIds: 'unresolved'` is load-bearing. `buildPlanIndex` otherwise
52
+ * throws a `TypeError` on a duplicate step or operation id, and a caller has
53
+ * no way to classify a `TypeError` that escapes a compile check.
54
+ */
55
+ export declare function checkRubricEvidenceReachability(contract: EvalContract): void;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * AD-22's compile-time rubric checks. `checkRubricIdentifiers` runs first and
3
+ * fires `rubric-unanchored`; the other three follow AD-5's registry order,
4
+ * `rubric-scores-reasoning-prose`, `rubric-unanchored`, then
5
+ * `rubric-evidence-unreachable`. Each throws `StructuralFailure` on the first
6
+ * violation, the convention every `core/compile/` module keeps. An empty
7
+ * `rubrics` array is a no-op in all four, which is what makes a zero-rubric
8
+ * contract compile clean.
9
+ */
10
+ import { StructuralFailure } from '../failure-codes.js';
11
+ import { buildPlanIndex } from '../seal/plan-index.js';
12
+ import { evaluatePointerReachability } from './reachability.js';
13
+ const rubricPath = (rubric) => `EvalContract.rubrics[id=${rubric.id}]`;
14
+ // `trim()` leaves U+200B through U+200D and U+FEFF standing, and a zero-width
15
+ // space states no observable condition either.
16
+ const INVISIBLE = /[\u200b-\u200d\ufeff]/g;
17
+ const blank = (value) => value.replace(INVISIBLE, '').trim().length === 0;
18
+ // Function declarations, so TypeScript narrows control flow through the
19
+ // `never` return type: a guard below reads a nullable field straight after the
20
+ // call that rejects its null case.
21
+ function unanchored(path, detail) {
22
+ throw new StructuralFailure('rubric-unanchored', path, detail);
23
+ }
24
+ function scoresReasoning(path, term) {
25
+ throw new StructuralFailure('rubric-scores-reasoning-prose', path, `scores stated-reasoning prose: matched "${term}" (AD-17, AD-22)`);
26
+ }
27
+ // ---- rubric-unanchored: addressable identifiers ---------------------------
28
+ /**
29
+ * Runs ahead of the three checks below, reversing AD-5's registry order for
30
+ * this one pair. Every other rubric check reports a path shaped
31
+ * `rubrics[id=...]`, and a duplicated id makes that path address two rubrics,
32
+ * so identifiers are settled first and every later path is unambiguous by
33
+ * construction.
34
+ *
35
+ * Criterion ids are unique within a rubric and may repeat across rubrics: a
36
+ * judge-call score cites `rubricId` and `criterionId` together, so the pair is
37
+ * what has to be unique.
38
+ */
39
+ export function checkRubricIdentifiers(contract) {
40
+ const seenRubricIds = new Set();
41
+ contract.rubrics.forEach((rubric, index) => {
42
+ if (seenRubricIds.has(rubric.id)) {
43
+ unanchored(`EvalContract.rubrics[${index}].id`, `repeats rubric id "${rubric.id}", which a finding could then cite ambiguously (AD-22)`);
44
+ }
45
+ seenRubricIds.add(rubric.id);
46
+ });
47
+ // Second pass, so no `rubrics[id=...]` path is emitted while a rubric id
48
+ // could still address two rubrics.
49
+ for (const rubric of contract.rubrics) {
50
+ const seenCriterionIds = new Set();
51
+ rubric.criteria.forEach((criterion, index) => {
52
+ if (seenCriterionIds.has(criterion.id)) {
53
+ unanchored(`${rubricPath(rubric)}.criteria[${index}].id`, `repeats criterion id "${criterion.id}" inside one rubric (AD-22)`);
54
+ }
55
+ seenCriterionIds.add(criterion.id);
56
+ });
57
+ }
58
+ }
59
+ // ---- rubric-scores-reasoning-prose ---------------------------------------
60
+ // AD-17 forbids scoring chain-of-thought or stated-reasoning prose. Nothing in
61
+ // the schema declares which channel carries reasoning, so this check reads
62
+ // wording, the same trade `auditBriefScripting` makes. The alternative was a
63
+ // rung with no throw site.
64
+ //
65
+ // A term earns a place only when it has no ordinary sense in an API contract.
66
+ // A compile-time code has no waiver path, so a term that fires on legal
67
+ // authoring gets reworded around. Review dropped `scratchpad` (a file),
68
+ // `deliberation` (an endpoint), the `justif-` family (an error body justifies
69
+ // a 409), `explanation` (an error message explains what went wrong), bare
70
+ // `thinking` (a measurement, as in thinking time), bare `CoT` (an ordinary
71
+ // English noun), and `explain why` (a criterion can ask why an observable
72
+ // status was returned).
73
+ //
74
+ // `reasoning` and `rationale` stay despite their field-name senses. AD-17's
75
+ // own words are "stated-reasoning prose", and a criterion carries an
76
+ // `evidence` pointer so its text never has to name a field. Plurals and
77
+ // `-ing` are covered because one letter would otherwise walk past the check.
78
+ // ASCII hyphen, U+2010 hyphen, U+2011 non-breaking hyphen, or whitespace, one
79
+ // or more. A paste out of a styled document carries the two non-ASCII forms,
80
+ // and a single-character class made a doubled separator an evasion.
81
+ const SEPARATOR = '[-\\u2010\\u2011\\s]+';
82
+ // Built from fragments the way `pointer.ts` builds its pointer grammar, so the
83
+ // separator is stated once. The pattern is not global, so no `lastIndex`
84
+ // carries between calls. The first match is the whole answer.
85
+ const REASONING_PROSE_PATTERN = new RegExp(`\\b(?:chains?${SEPARATOR}of${SEPARATOR}thoughts?|trains?${SEPARATOR}of${SEPARATOR}thoughts?|thought${SEPARATOR}process(?:es|ing)?|thinking${SEPARATOR}process(?:es)?|(?:internal|inner)${SEPARATOR}monologues?|self${SEPARATOR}explanations?|reasonings?|rationales?)\\b`, 'i');
86
+ /** The first forbidden term in `text`, or `undefined`. */
87
+ export function findReasoningProseTerm(text) {
88
+ return REASONING_PROSE_PATTERN.exec(text)?.[0];
89
+ }
90
+ /**
91
+ * Criterion text is scanned first: AD-17 and AD-5 both scope this code to a
92
+ * criterion, so where a rubric carries the defect in more than one place the
93
+ * in-scope site is the one reported. Anchors and penalties are a deliberate
94
+ * expansion. An anchor reading "level 3: the reasoning is sound" scores
95
+ * reasoning through every criterion at once, and a penalty deducting for an
96
+ * unstated rationale scores it through a deduction. Leave either unscanned and
97
+ * an author walks around the criterion scan in one edit.
98
+ */
99
+ export function checkRubricReasoningProse(contract) {
100
+ for (const rubric of contract.rubrics) {
101
+ const path = rubricPath(rubric);
102
+ for (const criterion of rubric.criteria) {
103
+ const term = findReasoningProseTerm(criterion.text);
104
+ if (term !== undefined)
105
+ scoresReasoning(`${path}.criteria[id=${criterion.id}].text`, term);
106
+ }
107
+ rubric.scaleLevels?.forEach((level, index) => {
108
+ const term = findReasoningProseTerm(level.anchor);
109
+ if (term !== undefined)
110
+ scoresReasoning(`${path}.scaleLevels[${index}].anchor`, term);
111
+ });
112
+ rubric.failureModePenalties?.forEach((penalty, index) => {
113
+ const penaltyPath = `${path}.failureModePenalties[${index}]`;
114
+ const nameTerm = findReasoningProseTerm(penalty.name);
115
+ if (nameTerm !== undefined)
116
+ scoresReasoning(`${penaltyPath}.name`, nameTerm);
117
+ const descriptionTerm = findReasoningProseTerm(penalty.description);
118
+ if (descriptionTerm !== undefined)
119
+ scoresReasoning(`${penaltyPath}.description`, descriptionTerm);
120
+ });
121
+ }
122
+ }
123
+ // ---- rubric-unanchored: the scoring instrument ---------------------------
124
+ /**
125
+ * AD-22's three shapes, spelled out: an unanchored scale (rules 1 to 3), an
126
+ * unbounded length (rule 4), and missing named failure-mode penalties (rules 5
127
+ * and 6). Rule 7 applies rule 3's argument to a criterion and is the one rule
128
+ * that stretches AD-22's wording: a criterion stating no question is
129
+ * unanswerable in a way `rubric-evidence-unreachable` cannot see, since its
130
+ * pointer may resolve perfectly.
131
+ *
132
+ * A single-level scale, a repeated penalty name, and a rubric with no criteria
133
+ * are all legal, each with an accept test guarding it. The first two were
134
+ * proposed and rejected in review; AD-19 settles the third by stating that
135
+ * AD-17's rules bind only when a rubric names criteria.
136
+ */
137
+ export function checkRubricAnchoring(contract) {
138
+ for (const rubric of contract.rubrics) {
139
+ const path = rubricPath(rubric);
140
+ const { scaleLevels, failureModePenalties } = rubric;
141
+ if (scaleLevels === null || scaleLevels.length === 0)
142
+ unanchored(`${path}.scaleLevels`, 'declares no anchored scale levels (AD-22)');
143
+ const seenOrdinals = new Set();
144
+ scaleLevels.forEach((level, index) => {
145
+ if (seenOrdinals.has(level.level)) {
146
+ unanchored(`${path}.scaleLevels[${index}].level`, `repeats scale-level ordinal ${level.level}, so the ordinal addresses two anchors (AD-22)`);
147
+ }
148
+ seenOrdinals.add(level.level);
149
+ });
150
+ scaleLevels.forEach((level, index) => {
151
+ if (blank(level.anchor)) {
152
+ unanchored(`${path}.scaleLevels[${index}].anchor`, 'states no observable condition, so the level is unanchored (AD-22)');
153
+ }
154
+ });
155
+ if (rubric.maxLength === null)
156
+ unanchored(`${path}.maxLength`, 'declares no bounded length (AD-22)');
157
+ if (failureModePenalties === null || failureModePenalties.length === 0)
158
+ unanchored(`${path}.failureModePenalties`, 'names no failure-mode penalties (AD-22)');
159
+ failureModePenalties.forEach((penalty, index) => {
160
+ if (blank(penalty.name)) {
161
+ unanchored(`${path}.failureModePenalties[${index}].name`, 'is blank, so the penalty is unnamed (AD-22)');
162
+ }
163
+ });
164
+ for (const criterion of rubric.criteria) {
165
+ if (blank(criterion.text)) {
166
+ unanchored(`${path}.criteria[id=${criterion.id}].text`, 'states no question, so the criterion is unanchored (AD-22)');
167
+ }
168
+ }
169
+ }
170
+ }
171
+ // ---- rubric-evidence-unreachable -----------------------------------------
172
+ /**
173
+ * `evaluatePointerReachability` is reused, so the two evidence codes give the
174
+ * same reason for the same broken pointer. Two limits come with it. A `@/`
175
+ * pointer cannot parse into `criterion.evidence`, an `InteractionPointer`, so
176
+ * that branch is dead here. And a channel that declares no shape resolves as
177
+ * soon as its step does, so a criterion rooted at `stdout` compiles on an
178
+ * `api`-kind contract that can never produce one. That gap belongs to
179
+ * `unreachable-check-evidence` as well, and a second implementation written to
180
+ * close it for rubrics alone would leave the two codes disagreeing about the
181
+ * same pointer.
182
+ *
183
+ * `duplicateIds: 'unresolved'` is load-bearing. `buildPlanIndex` otherwise
184
+ * throws a `TypeError` on a duplicate step or operation id, and a caller has
185
+ * no way to classify a `TypeError` that escapes a compile check.
186
+ */
187
+ export function checkRubricEvidenceReachability(contract) {
188
+ let index;
189
+ for (const rubric of contract.rubrics) {
190
+ for (const criterion of rubric.criteria) {
191
+ index ??= buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
192
+ const result = evaluatePointerReachability(criterion.evidence, index);
193
+ if (!result.reachable) {
194
+ throw new StructuralFailure('rubric-evidence-unreachable', `${rubricPath(rubric)}.criteria[id=${criterion.id}].evidence`, `"${criterion.evidence}" ${result.reason}`);
195
+ }
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,5 @@
1
+ import type { EvalContract } from '../schemas/eval-contract.ts';
2
+ /** `nested-temporal-clause`: a step's declared parent itself carries a temporal clause. */
3
+ export declare function checkNestedTemporalClause(contract: EvalContract): void;
4
+ /** `plan-exceeds-scripting-bound`: the graph predicate over the whole plan. */
5
+ export declare function checkScriptingBound(contract: EvalContract): void;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * AD-39's two interaction-plan graph checks: `nested-temporal-clause` and
3
+ * `plan-exceeds-scripting-bound`, each walking `contract.interactionPlan`
4
+ * once. Both throw `StructuralFailure` on the first violation, matching
5
+ * every other `core/compile/` module's fail-fast convention.
6
+ */
7
+ import { StructuralFailure } from '../failure-codes.js';
8
+ import { buildPlanIndex } from '../seal/plan-index.js';
9
+ /**
10
+ * A dangling `after` (naming no declared step, including one made
11
+ * unresolvable by a duplicate id) resolves to `undefined`, the same as a
12
+ * genuinely absent clause.
13
+ */
14
+ function parentOf(step, index) {
15
+ if (step.after === null)
16
+ return undefined;
17
+ return index.stepOf(step.after);
18
+ }
19
+ function planIndexOf(contract) {
20
+ return buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, { duplicateIds: 'unresolved' });
21
+ }
22
+ // ---- nested-temporal-clause ------------------------------------------------
23
+ /** `nested-temporal-clause`: a step's declared parent itself carries a temporal clause. */
24
+ export function checkNestedTemporalClause(contract) {
25
+ const index = planIndexOf(contract);
26
+ for (const step of contract.interactionPlan) {
27
+ const parent = parentOf(step, index);
28
+ if (parent !== undefined && parentOf(parent, index) !== undefined) {
29
+ throw new StructuralFailure('nested-temporal-clause', `EvalContract.interactionPlan[stepId=${step.stepId}].after`, `names step "${parent.stepId}", which itself carries a temporal clause; a chain may not nest more than one level (AD-39)`);
30
+ }
31
+ }
32
+ }
33
+ // ---- plan-exceeds-scripting-bound ------------------------------------------
34
+ /**
35
+ * Bounds are exclusive ceilings: a metric sitting exactly at its bound is
36
+ * legal. Each is set against this codebase's own two whole-contract
37
+ * fixtures as the accept floor and AD-39's two counterexample plans as the
38
+ * reject ceiling: the eight-step single-root chain and the sixty-four
39
+ * independent `write-N`/`read-N` pairs, which AD-39 records as passing a
40
+ * depth bound alone. AD-5 mandates one authored reject fixture per shape
41
+ * this predicate rejects, and `tests/compile/scripting-bound.test.ts`
42
+ * carries them.
43
+ */
44
+ const WIDTH_MAX = 2; // gateCContract's own submit -> {poll, first-page} sits exactly here.
45
+ const SHARED_ANCHOR_MAX = 2; // twice gateCContract's own shared-anchor count of 1 (no real fixture reaches 2).
46
+ const DISJOINT_PAIR_MAX = 4; // populatedContract's own create -> list pair is 1.
47
+ const STEP_COUNT_MAX = 16; // gateCContract is 6, populatedContract is 2; roughly 2.7x the larger.
48
+ /**
49
+ * One pass over the plan's `after` edges, building three views at once: the
50
+ * one-hop nesting test (shared with `checkNestedTemporalClause`), each
51
+ * anchor's child count (width, shared anchors), and an undirected adjacency
52
+ * map for the connected-component scan below (disjoint pairs). O(n) in the
53
+ * step count.
54
+ *
55
+ * Every internal map is keyed by each step's array position. `stepId` is
56
+ * schema-legal to duplicate, so keying on it would merge two distinct
57
+ * steps sharing an id into one adjacency entry and corrupt
58
+ * `maxWidth`/`sharedAnchorCount`/`disjointPairCount`.
59
+ */
60
+ function computeGraphMetrics(plan, index) {
61
+ const positionOf = new Map();
62
+ for (const [position, step] of plan.entries())
63
+ positionOf.set(step, position);
64
+ const children = new Map();
65
+ const adjacency = new Map();
66
+ for (const [position] of plan.entries())
67
+ adjacency.set(position, new Set());
68
+ let hasNestedChain = false;
69
+ for (const [position, step] of plan.entries()) {
70
+ const parent = parentOf(step, index);
71
+ if (parent === undefined)
72
+ continue;
73
+ if (parentOf(parent, index) !== undefined)
74
+ hasNestedChain = true;
75
+ const parentPosition = positionOf.get(parent);
76
+ if (parentPosition === undefined) {
77
+ // Should-never-happen precondition violation, matching `resolveStep`'s
78
+ // convention (`plan-index.ts`): `index` is always built from this same
79
+ // `plan` array, so a resolved parent is always one of its elements.
80
+ throw new TypeError(`scripting-bound: resolved parent for step "${step.stepId}" is not present in the plan passed to computeGraphMetrics`);
81
+ }
82
+ const siblings = children.get(parentPosition);
83
+ if (siblings === undefined)
84
+ children.set(parentPosition, [position]);
85
+ else
86
+ siblings.push(position);
87
+ adjacency.get(position)?.add(parentPosition);
88
+ adjacency.get(parentPosition)?.add(position);
89
+ }
90
+ let maxWidth = 0;
91
+ let sharedAnchorCount = 0;
92
+ for (const siblings of children.values()) {
93
+ if (siblings.length > maxWidth)
94
+ maxWidth = siblings.length;
95
+ if (siblings.length >= 2)
96
+ sharedAnchorCount += 1;
97
+ }
98
+ // Connected components over the undirected `after` graph; a component of
99
+ // exactly two nodes is one disjoint witness pair (the 64-pair fixture's
100
+ // own shape). A plain stack (`.pop()`) is enough for an unordered scan;
101
+ // `.shift()` would cost O(n) per call.
102
+ let disjointPairCount = 0;
103
+ const visited = new Set();
104
+ for (const [position] of plan.entries()) {
105
+ if (visited.has(position))
106
+ continue;
107
+ let size = 0;
108
+ const stack = [position];
109
+ visited.add(position);
110
+ while (stack.length > 0) {
111
+ const id = stack.pop();
112
+ if (id === undefined)
113
+ continue;
114
+ size += 1;
115
+ for (const neighbor of adjacency.get(id) ?? []) {
116
+ if (!visited.has(neighbor)) {
117
+ visited.add(neighbor);
118
+ stack.push(neighbor);
119
+ }
120
+ }
121
+ }
122
+ if (size === 2)
123
+ disjointPairCount += 1;
124
+ }
125
+ return {
126
+ hasNestedChain,
127
+ maxWidth,
128
+ sharedAnchorCount,
129
+ disjointPairCount,
130
+ stepCount: plan.length,
131
+ };
132
+ }
133
+ /** `plan-exceeds-scripting-bound`: the graph predicate over the whole plan. */
134
+ export function checkScriptingBound(contract) {
135
+ const index = planIndexOf(contract);
136
+ const metrics = computeGraphMetrics(contract.interactionPlan, index);
137
+ const violation = metrics.hasNestedChain
138
+ ? 'its deepest temporal chain exceeds the published one-level bound'
139
+ : metrics.maxWidth > WIDTH_MAX
140
+ ? `one step anchors ${metrics.maxWidth} other steps, past the published width bound of ${WIDTH_MAX}`
141
+ : metrics.sharedAnchorCount > SHARED_ANCHOR_MAX
142
+ ? `${metrics.sharedAnchorCount} steps each anchor more than one other step, past the published bound of ${SHARED_ANCHOR_MAX} shared anchors`
143
+ : metrics.disjointPairCount > DISJOINT_PAIR_MAX
144
+ ? `${metrics.disjointPairCount} mutually disjoint two-step witness pairs appear in one plan, past the published bound of ${DISJOINT_PAIR_MAX}`
145
+ : metrics.stepCount > STEP_COUNT_MAX
146
+ ? `the plan declares ${metrics.stepCount} steps, past the published bound of ${STEP_COUNT_MAX}: an exhaustive operation inventory rather than a bounded set of witness relations`
147
+ : undefined;
148
+ if (violation !== undefined) {
149
+ throw new StructuralFailure('plan-exceeds-scripting-bound', 'EvalContract.interactionPlan', `${violation} (AD-5, AD-39)`);
150
+ }
151
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * AD-10's three declaration-side witness checks: that every input-bearing
3
+ * operation declares one, that a declared one is shaped so the plan can
4
+ * execute it, and that its leg identifiers are distinct from each other and
5
+ * from every interaction-plan step.
6
+ *
7
+ * No new AD-5 code is minted. The registry is closed at twenty-one and
8
+ * `check:ad5-registry` pins it against the spine, so each defect below takes the
9
+ * code that already names it. Two rows stretch that reading: a leg-id equality
10
+ * and a leg-id/step-id collision are identifier collisions, and
11
+ * `malformed-operator-expression` is the closest available code because the
12
+ * relation is illegal in its position once its operands cannot be told apart.
13
+ */
14
+ import { declaresNoRequestKeys } from '../declared-inputs.ts';
15
+ import type { EvalContract } from '../schemas/eval-contract.ts';
16
+ import type { Operation } from '../schemas/interface.ts';
17
+ import { type TransportChannelName } from '../schemas/pointer.ts';
18
+ import type { WitnessInputs } from '../schemas/sensitivity-witness.ts';
19
+ export { declaresNoRequestKeys };
20
+ /**
21
+ * The keys one set of witness inputs supplies on one channel. A body that is
22
+ * absent, or JSON that is not an object, supplies no keys: the channel is
23
+ * declared as a keyed shape, so such a leg omits every required key rather
24
+ * than being exempt from the comparison.
25
+ */
26
+ export declare function suppliedKeys(inputs: WitnessInputs, channel: TransportChannelName): readonly string[];
27
+ /**
28
+ * Every channel of one set of witness inputs against the operation that will
29
+ * receive them. All four, not only the differential channel: `planPreflight`
30
+ * copies all four onto the `ProbeRequest` and the port sends them, so a value
31
+ * on an unselected channel is as much an outbound value as one on the selected
32
+ * channel.
33
+ *
34
+ * The two directions have different standing, and the difference matters
35
+ * enough to name, because the next reader will diff this against
36
+ * `checkUndeclaredMandatoryInput` and find the two disagreeing.
37
+ *
38
+ * The permitted direction is that function's rule, carried over: it loops the
39
+ * same four channels and rejects a key the operation declares in neither list.
40
+ * That is what makes `WitnessInputs`'s AD-18 promise about `header` more than a
41
+ * comment.
42
+ *
43
+ * The required direction is new here, because the two shapes differ. An
44
+ * `InputBindingChannel` is nullable, and `null` means "this step binds nothing in
45
+ * this channel", so `checkUndeclaredMandatoryInput` skips an unbound channel and
46
+ * asks nothing about required keys. `WitnessInputs` has all four channels as
47
+ * concrete values, so a leg omitting a required key is a request the port cannot
48
+ * issue. The cost is that a required header forces a literal value into the
49
+ * contract artifact, the surface AD-18 governs; a placeholder satisfies this
50
+ * check, and what pre-flight probes is a fixture.
51
+ */
52
+ export declare function checkInputsAgainstShape(inputs: WitnessInputs, operation: Operation, owner: string, artifactPath: string): void;
53
+ /**
54
+ * Strict-gated, alongside `checkUndeclaredMandatoryInput`, whose code this
55
+ * shares: `compile.ts` already gates that code behind `options.strict`, and one
56
+ * code with two gating regimes would be worse than one code with two
57
+ * conditions.
58
+ */
59
+ export declare function checkSensitivityWitnessDeclared(contract: EvalContract): void;
60
+ /**
61
+ * The shape rules: the differential channel agrees with the state-change
62
+ * marker, the relation reads both legs and nothing else, and a declared
63
+ * fixture reset names a mutating operation the contract declares. Unconditional,
64
+ * since none of these fires `undeclared-mandatory-input`.
65
+ */
66
+ export declare function checkWitnessLegality(contract: EvalContract): void;
67
+ /**
68
+ * Leg identifiers share one namespace with interaction-plan step identifiers,
69
+ * because a witness relation is an ordinary AD-4 expression and addresses a leg
70
+ * as `/interactions/{legId}/…`. A collision would make a relation pointer
71
+ * resolve against the wrong observation.
72
+ */
73
+ export declare function checkWitnessLegIdentifiers(contract: EvalContract): void;