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,107 @@
1
+ import { type EvalContract } from '../schemas/eval-contract.ts';
2
+ import type { Expression } from '../schemas/expression.ts';
3
+ import type { Operation } from '../schemas/interface.ts';
4
+ import { type PlanIndex } from '../seal/plan-index.ts';
5
+ import { type DisciplineRule } from './rules.ts';
6
+ export type SatisfactionVerdict = {
7
+ readonly rule: DisciplineRule;
8
+ readonly predicate: string;
9
+ readonly satisfied: boolean;
10
+ /** which site decided it, so a gap is diagnosable. */
11
+ readonly reason: string;
12
+ };
13
+ /** The rule fires on no site, so the universal over its sites holds. */
14
+ export declare const NO_RELEVANT_SITE = "the rule is relevant for no site, so satisfaction holds vacuously";
15
+ /** A contract declaring no operation leaves six of the rules an absent site. */
16
+ export declare const NO_OPERATION_WITNESS = "the contract declares no operation, so the site this rule fires on has no declaration to witness";
17
+ /**
18
+ * One `check` node with its operand pointers resolved through any enclosing
19
+ * quantifier. A connective node (`not`, `all`, `any`) carries neither an
20
+ * operand pointer nor a collection, since its operands are nested expressions
21
+ * that get nodes of their own; it witnesses nothing on its own.
22
+ */
23
+ type CheckNode = {
24
+ readonly expression: Expression;
25
+ /** one entry per operand position; `null` where the operand carries no pointer. */
26
+ readonly operandPointers: readonly (string | null)[];
27
+ /** a quantifier's collection pointer; `null` on every other node. */
28
+ readonly collection: string | null;
29
+ };
30
+ /** One oracle's two channels, flattened to what the predicates compare. */
31
+ type OracleView = {
32
+ readonly directionTargets: readonly string[];
33
+ readonly nodes: readonly CheckNode[];
34
+ readonly checkPointers: ReadonlySet<string>;
35
+ };
36
+ /**
37
+ * The three derived views the predicates read. `evaluateSatisfaction` builds
38
+ * one and passes it down, so seven predicates do not each rebuild the plan
39
+ * index and re-walk every check tree. A predicate called on its own builds its
40
+ * own, which is what every per-rule fixture does.
41
+ */
42
+ export type SatisfactionContext = {
43
+ readonly operations: readonly Operation[];
44
+ readonly index: PlanIndex;
45
+ readonly oracles: readonly OracleView[];
46
+ };
47
+ /**
48
+ * Rule 1: for every operation its relevance predicate fires on, some oracle's
49
+ * direction and check both address that operation's success indicator and a
50
+ * pointer whose declared role is something other than `success-indicator`,
51
+ * read at one step. An operation whose only other roled pointers are themselves
52
+ * indicators is a site with no witness.
53
+ */
54
+ export declare function successIndicatorSeparationSatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
55
+ /**
56
+ * Rule 2: for every operation declaring more than one distinct required
57
+ * response key, some oracle's direction and check both address every one of
58
+ * those keys at one step invoking that operation. AD-20's denominator is the
59
+ * required-key set of the operation the addressed step invokes.
60
+ */
61
+ export declare function wholeBodySatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
62
+ /**
63
+ * Rule 3: for every operation declaring a request key, some step invoking it
64
+ * binds AD-39's `type-violating` matcher and some check addresses that step.
65
+ * The matcher is a declaration, so the pair decides the rule without reading
66
+ * prose or a run.
67
+ */
68
+ export declare function malformedInputSatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
69
+ /**
70
+ * Rule 4: every declared collection location is the collection of some
71
+ * quantifier, read at a step invoking its operation. The comparison is
72
+ * equality: a quantifier one level inside a declared collection ranges over
73
+ * something else. Both quantifiers are admitted, since `not(for-any P)` is the
74
+ * idiomatic spelling of "no element satisfies P".
75
+ */
76
+ export declare function perRecordSatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
77
+ /**
78
+ * Rule 5: every declared sibling group has an oracle whose direction and check
79
+ * both address two of its members. An operation member is addressed through a
80
+ * step that invokes it; a parameter member through a `call-inputs` pointer on
81
+ * any transport channel. Members are deduplicated first, since `SiblingGroups`
82
+ * carries no uniqueness constraint.
83
+ */
84
+ export declare function siblingCrossCheckSatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
85
+ /**
86
+ * Rule 6: every collection location naming a reference set is reconciled
87
+ * against it, in the form its declared cardinality mode requires.
88
+ */
89
+ export declare function omissionAndCompletenessSatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
90
+ /**
91
+ * Rule 7: for every operation declaring `stateChangeMarker: true`, some check
92
+ * node relates a pointer under a step invoking it to a pointer under the
93
+ * response body of a later step whose operation changes no state and whose
94
+ * temporal clause names the write. One node carries the relation: two unrelated
95
+ * assertions under one `all` read nothing back.
96
+ */
97
+ export declare function stateChangeReadBackSatisfaction(contract: EvalContract, context?: SatisfactionContext): SatisfactionVerdict;
98
+ /**
99
+ * One predicate per rule. The mapped type fails the typecheck when a member of
100
+ * `DISCIPLINE_RULES` has no predicate, matching `RELEVANCE_PREDICATES`.
101
+ */
102
+ export declare const SATISFACTION_PREDICATES: {
103
+ readonly [Rule in DisciplineRule]: (contract: EvalContract, context?: SatisfactionContext) => SatisfactionVerdict;
104
+ };
105
+ /** All seven verdicts, in `DISCIPLINE_RULES` order, over declarations alone. */
106
+ export declare function evaluateSatisfaction(contract: EvalContract): readonly SatisfactionVerdict[];
107
+ export {};
@@ -0,0 +1,415 @@
1
+ /**
2
+ * AD-31's seven satisfaction predicates: decision procedures over AD-19's
3
+ * declarations and the structured direction and `check` pair. No run record,
4
+ * no probe, no outcome state.
5
+ *
6
+ * Each rule quantifies over the sites its relevance predicate fires on:
7
+ * satisfaction holds when every site is witnessed. A site whose declaration is
8
+ * absent has no witness, so under-declaration costs coverage. A rule firing on
9
+ * no site holds vacuously.
10
+ *
11
+ * Nothing here throws, matching `relevance.ts`: a coverage gap is recorded and
12
+ * the artifact still ships (AD-5), so there is no failure code.
13
+ */
14
+ import { substitutePointer } from '../compile/oracle-alignment.js';
15
+ import { SIBLING_GROUP_MINIMUM, } from '../schemas/eval-contract.js';
16
+ import { TRANSPORT_CHANNELS } from '../schemas/pointer.js';
17
+ import { buildPlanIndex } from '../seal/plan-index.js';
18
+ import { DISCIPLINE_RULES, satisfactionPredicateId, } from './rules.js';
19
+ const verdict = (rule, satisfied, reason) => ({
20
+ rule,
21
+ predicate: satisfactionPredicateId(rule),
22
+ satisfied,
23
+ reason,
24
+ });
25
+ /** The rule fires on no site, so the universal over its sites holds. */
26
+ export const NO_RELEVANT_SITE = 'the rule is relevant for no site, so satisfaction holds vacuously';
27
+ /** A contract declaring no operation leaves six of the rules an absent site. */
28
+ export const NO_OPERATION_WITNESS = 'the contract declares no operation, so the site this rule fires on has no declaration to witness';
29
+ /** Every declared operation, flattened. `relevance.ts` keeps its own copy. */
30
+ const operationsOf = (contract) => contract.permittedInterfaces.flatMap((declared) => declared.operations);
31
+ /** `unresolved` never throws; a duplicate identifier resolves to nothing. */
32
+ const planIndexOf = (contract) => buildPlanIndex(contract.interactionPlan, contract.permittedInterfaces, {
33
+ duplicateIds: 'unresolved',
34
+ });
35
+ // ---- the join between the two pointer spellings -------------------------
36
+ /** RFC 6901 escaping, `~` before `/`. */
37
+ const encodeToken = (token) => token.replace(/~/g, '~0').replace(/\//g, '~1');
38
+ /** Everything one step produced or was given. */
39
+ const stepRoot = (stepId) => `/interactions/${stepId}`;
40
+ /** A descriptor pointer read at one step, spelled interaction-rooted. */
41
+ const bodyPointer = (stepId, descriptorPointer) => `/interactions/${stepId}/response-body${descriptorPointer}`;
42
+ /** One declared response key at one step. */
43
+ const keyPointer = (stepId, key) => `/interactions/${stepId}/response-body/${encodeToken(key)}`;
44
+ /** One declared parameter on one transport channel at one step. */
45
+ const parameterPointer = (stepId, channel, key) => `/interactions/${stepId}/call-inputs/${channel}/${encodeToken(key)}`;
46
+ /** A pointer addresses a root when it names it or descends into it. */
47
+ const addresses = (pointer, root) => pointer === root || pointer.startsWith(`${root}/`);
48
+ const someAddresses = (pointers, root) => {
49
+ for (const pointer of pointers) {
50
+ if (addresses(pointer, root))
51
+ return true;
52
+ }
53
+ return false;
54
+ };
55
+ const operandPointer = (operand, boundElementRoot) => 'pointer' in operand
56
+ ? substitutePointer(operand.pointer, boundElementRoot)
57
+ : null;
58
+ function walkCheck(expression, boundElementRoot, nodes) {
59
+ switch (expression.op) {
60
+ case 'not':
61
+ nodes.push({ expression, operandPointers: [], collection: null });
62
+ walkCheck(expression.operands[0], boundElementRoot, nodes);
63
+ return;
64
+ case 'all':
65
+ case 'any':
66
+ nodes.push({ expression, operandPointers: [], collection: null });
67
+ for (const child of expression.operands) {
68
+ walkCheck(child, boundElementRoot, nodes);
69
+ }
70
+ return;
71
+ case 'for-all':
72
+ case 'for-any': {
73
+ const collection = operandPointer(expression.collection, boundElementRoot);
74
+ nodes.push({ expression, operandPointers: [], collection });
75
+ walkCheck(expression.predicate, collection, nodes);
76
+ return;
77
+ }
78
+ default: {
79
+ // Widened before mapping: `.map` over a union of tuple types resolves
80
+ // to a union of call signatures. `set-membership` falls here too: its
81
+ // second operand is a `SetOperand`, which carries no pointer, so
82
+ // `operandPointer` already answers `null` in that position.
83
+ const operands = expression.operands;
84
+ nodes.push({
85
+ expression,
86
+ operandPointers: operands.map((operand) => operandPointer(operand, boundElementRoot)),
87
+ collection: null,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ const oracleViewsOf = (contract) => contract.oracles.map((oracle) => {
93
+ const nodes = [];
94
+ if (oracle.check !== null)
95
+ walkCheck(oracle.check, null, nodes);
96
+ const checkPointers = new Set();
97
+ for (const node of nodes) {
98
+ if (node.collection !== null)
99
+ checkPointers.add(node.collection);
100
+ for (const pointer of node.operandPointers) {
101
+ if (pointer !== null)
102
+ checkPointers.add(pointer);
103
+ }
104
+ }
105
+ return {
106
+ directionTargets: oracle.direction === null ? [] : oracle.direction.evidenceTargets,
107
+ nodes,
108
+ checkPointers,
109
+ };
110
+ });
111
+ /** AD-20's "in both channels": the direction names the pointer and the check reads it. */
112
+ const bothChannelsAddress = (oracle, root) => someAddresses(oracle.directionTargets, root) &&
113
+ someAddresses(oracle.checkPointers, root);
114
+ const definedPointers = (node) => node.operandPointers.filter((pointer) => pointer !== null);
115
+ const contextOf = (contract) => ({
116
+ operations: operationsOf(contract),
117
+ index: planIndexOf(contract),
118
+ oracles: oracleViewsOf(contract),
119
+ });
120
+ /**
121
+ * Rule 1: for every operation its relevance predicate fires on, some oracle's
122
+ * direction and check both address that operation's success indicator and a
123
+ * pointer whose declared role is something other than `success-indicator`,
124
+ * read at one step. An operation whose only other roled pointers are themselves
125
+ * indicators is a site with no witness.
126
+ */
127
+ export function successIndicatorSeparationSatisfaction(contract, context = contextOf(contract)) {
128
+ const rule = 'success-indicator-separation';
129
+ const { operations, index, oracles } = context;
130
+ if (operations.length === 0)
131
+ return verdict(rule, false, NO_OPERATION_WITNESS);
132
+ let sites = 0;
133
+ for (const operation of operations) {
134
+ const { successIndicator, channelRoles } = operation.responseDescriptor;
135
+ if (successIndicator === null) {
136
+ return verdict(rule, false, `operation ${operation.operationId} nominates no success indicator, so no oracle can separate one from the body`);
137
+ }
138
+ if (channelRoles === null) {
139
+ return verdict(rule, false, `operation ${operation.operationId} declares no channel roles, so no pointer is declared to separate the indicator from`);
140
+ }
141
+ const roles = Object.entries(channelRoles);
142
+ if (!roles.some(([pointer]) => pointer !== successIndicator))
143
+ continue;
144
+ sites += 1;
145
+ const others = roles
146
+ .filter(([pointer, role]) => pointer !== successIndicator && role !== 'success-indicator')
147
+ .map(([pointer]) => pointer);
148
+ const witnessed = index
149
+ .stepsUsing(operation.operationId)
150
+ .some((step) => oracles.some((oracle) => bothChannelsAddress(oracle, bodyPointer(step.stepId, successIndicator)) &&
151
+ others.some((pointer) => bothChannelsAddress(oracle, bodyPointer(step.stepId, pointer)))));
152
+ if (!witnessed) {
153
+ return verdict(rule, false, `no oracle addresses operation ${operation.operationId}'s success indicator beside another roled pointer at one step, in both channels`);
154
+ }
155
+ }
156
+ return verdict(rule, true, sites === 0
157
+ ? NO_RELEVANT_SITE
158
+ : 'every operation the rule fires on has an oracle reading its success indicator beside another roled pointer');
159
+ }
160
+ /**
161
+ * Rule 2: for every operation declaring more than one distinct required
162
+ * response key, some oracle's direction and check both address every one of
163
+ * those keys at one step invoking that operation. AD-20's denominator is the
164
+ * required-key set of the operation the addressed step invokes.
165
+ */
166
+ export function wholeBodySatisfaction(contract, context = contextOf(contract)) {
167
+ const rule = 'whole-body';
168
+ const { operations, index, oracles } = context;
169
+ if (operations.length === 0)
170
+ return verdict(rule, false, NO_OPERATION_WITNESS);
171
+ let sites = 0;
172
+ for (const operation of operations) {
173
+ const required = [...new Set(operation.responseDescriptor.requiredKeys)];
174
+ if (required.length <= 1)
175
+ continue;
176
+ sites += 1;
177
+ const witnessed = index
178
+ .stepsUsing(operation.operationId)
179
+ .some((step) => oracles.some((oracle) => required.every((key) => bothChannelsAddress(oracle, keyPointer(step.stepId, key)))));
180
+ if (!witnessed) {
181
+ return verdict(rule, false, `no oracle covers every required response key of operation ${operation.operationId} at one addressed step, in both channels`);
182
+ }
183
+ }
184
+ return verdict(rule, true, sites === 0
185
+ ? NO_RELEVANT_SITE
186
+ : 'every operation declaring more than one required response key has an oracle covering all of them at one step');
187
+ }
188
+ /** The site condition rule 3 relevance reads: a key on any of the four channels. */
189
+ const declaresRequestKey = (operation) => TRANSPORT_CHANNELS.some((channel) => {
190
+ const shape = operation.requestShape[channel];
191
+ return (shape.requiredKeys.length > 0 ||
192
+ shape.permittedKeys.length > 0 ||
193
+ Object.keys(shape.types).length > 0);
194
+ });
195
+ /** AD-39's matcher, on any transport channel of one step. */
196
+ const bindsTypeViolating = (step) => TRANSPORT_CHANNELS.some((channel) => {
197
+ const binding = step.inputBinding[channel];
198
+ if (binding === null)
199
+ return false;
200
+ return Object.values(binding).some((value) => 'matcher' in value && value.matcher === 'type-violating');
201
+ });
202
+ /**
203
+ * Rule 3: for every operation declaring a request key, some step invoking it
204
+ * binds AD-39's `type-violating` matcher and some check addresses that step.
205
+ * The matcher is a declaration, so the pair decides the rule without reading
206
+ * prose or a run.
207
+ */
208
+ export function malformedInputSatisfaction(contract, context = contextOf(contract)) {
209
+ const rule = 'malformed-input';
210
+ const { operations, index, oracles } = context;
211
+ if (operations.length === 0)
212
+ return verdict(rule, false, NO_OPERATION_WITNESS);
213
+ let sites = 0;
214
+ for (const operation of operations) {
215
+ if (!declaresRequestKey(operation))
216
+ continue;
217
+ sites += 1;
218
+ const witnessed = index
219
+ .stepsUsing(operation.operationId)
220
+ .some((step) => bindsTypeViolating(step) &&
221
+ oracles.some((oracle) => someAddresses(oracle.checkPointers, stepRoot(step.stepId))));
222
+ if (!witnessed) {
223
+ return verdict(rule, false, `no step invoking operation ${operation.operationId} binds a type-violating matcher under a check that addresses it`);
224
+ }
225
+ }
226
+ return verdict(rule, true, sites === 0
227
+ ? NO_RELEVANT_SITE
228
+ : 'every operation declaring a request key has a type-violating step some check addresses');
229
+ }
230
+ /**
231
+ * Rule 4: every declared collection location is the collection of some
232
+ * quantifier, read at a step invoking its operation. The comparison is
233
+ * equality: a quantifier one level inside a declared collection ranges over
234
+ * something else. Both quantifiers are admitted, since `not(for-any P)` is the
235
+ * idiomatic spelling of "no element satisfies P".
236
+ */
237
+ export function perRecordSatisfaction(contract, context = contextOf(contract)) {
238
+ const rule = 'per-record';
239
+ const { operations, index, oracles } = context;
240
+ if (operations.length === 0)
241
+ return verdict(rule, false, NO_OPERATION_WITNESS);
242
+ let sites = 0;
243
+ for (const operation of operations) {
244
+ const { collectionLocations } = operation.responseDescriptor;
245
+ if (collectionLocations === null) {
246
+ return verdict(rule, false, `operation ${operation.operationId} declares no collection-location list, so no quantifier can range over a declared collection`);
247
+ }
248
+ const steps = index.stepsUsing(operation.operationId);
249
+ for (const location of collectionLocations) {
250
+ sites += 1;
251
+ const witnessed = steps.some((step) => {
252
+ const collection = bodyPointer(step.stepId, location.pointer);
253
+ return oracles.some((oracle) => oracle.nodes.some((node) => node.collection === collection));
254
+ });
255
+ if (!witnessed) {
256
+ return verdict(rule, false, `no check quantifies over collection ${location.pointer} of operation ${operation.operationId}`);
257
+ }
258
+ }
259
+ }
260
+ return verdict(rule, true, sites === 0
261
+ ? NO_RELEVANT_SITE
262
+ : 'every declared collection location is the collection of some quantifier');
263
+ }
264
+ /**
265
+ * Rule 5: every declared sibling group has an oracle whose direction and check
266
+ * both address two of its members. An operation member is addressed through a
267
+ * step that invokes it; a parameter member through a `call-inputs` pointer on
268
+ * any transport channel. Members are deduplicated first, since `SiblingGroups`
269
+ * carries no uniqueness constraint.
270
+ */
271
+ export function siblingCrossCheckSatisfaction(contract, context = contextOf(contract)) {
272
+ const rule = 'sibling-cross-check';
273
+ const groups = contract.siblingGroups;
274
+ if (groups === null) {
275
+ return verdict(rule, false, 'the contract declares no sibling groups, so no group is declared to cross-check');
276
+ }
277
+ const { index, oracles } = context;
278
+ let sites = 0;
279
+ for (const group of groups.operations) {
280
+ sites += 1;
281
+ const members = [...new Set(group)];
282
+ const witnessed = oracles.some((oracle) => members.filter((operationId) => index
283
+ .stepsUsing(operationId)
284
+ .some((step) => bothChannelsAddress(oracle, stepRoot(step.stepId)))).length >= SIBLING_GROUP_MINIMUM);
285
+ if (!witnessed) {
286
+ return verdict(rule, false, `no oracle addresses two members of the operation sibling group ${members.join(' and ')} in both channels`);
287
+ }
288
+ }
289
+ for (const group of groups.parameters) {
290
+ sites += 1;
291
+ const members = [...new Set(group)];
292
+ const witnessed = oracles.some((oracle) => members.filter((parameter) => contract.interactionPlan.some((step) => TRANSPORT_CHANNELS.some((channel) => bothChannelsAddress(oracle, parameterPointer(step.stepId, channel, parameter))))).length >= SIBLING_GROUP_MINIMUM);
293
+ if (!witnessed) {
294
+ return verdict(rule, false, `no oracle addresses two members of the parameter sibling group ${members.join(' and ')} in both channels`);
295
+ }
296
+ }
297
+ return verdict(rule, true, sites === 0
298
+ ? NO_RELEVANT_SITE
299
+ : 'every declared sibling group has an oracle reading two of its members');
300
+ }
301
+ const isReferenceSetOperand = (operand, identifier) => 'referenceSet' in operand && operand.referenceSet === identifier;
302
+ /**
303
+ * AD-20 rule 6's two forms, selected by `expectedCardinality.mode`. `exact`
304
+ * takes the bijection, `covers-by-key` against the reference set;
305
+ * `page-bounded` and `at-most` take the injection, `for-all` over the page
306
+ * whose predicate is `set-membership` against it. A page of twenty drawn from
307
+ * ninety-seven never equals its reference set, so the bijection resolves false
308
+ * against a correct server (AD-26).
309
+ */
310
+ function reconciles(node, collection, location, referenceSet) {
311
+ const { expression } = node;
312
+ if (location.expectedCardinality.mode === 'exact') {
313
+ return (expression.op === 'covers-by-key' &&
314
+ isReferenceSetOperand(expression.operands[0], referenceSet) &&
315
+ node.operandPointers[1] === collection);
316
+ }
317
+ if (expression.op !== 'for-all' || node.collection !== collection)
318
+ return false;
319
+ const { predicate } = expression;
320
+ return (predicate.op === 'set-membership' &&
321
+ isReferenceSetOperand(predicate.operands[1], referenceSet));
322
+ }
323
+ /**
324
+ * Rule 6: every collection location naming a reference set is reconciled
325
+ * against it, in the form its declared cardinality mode requires.
326
+ */
327
+ export function omissionAndCompletenessSatisfaction(contract, context = contextOf(contract)) {
328
+ const rule = 'omission-and-completeness';
329
+ const { operations, index, oracles } = context;
330
+ if (operations.length === 0)
331
+ return verdict(rule, false, NO_OPERATION_WITNESS);
332
+ let sites = 0;
333
+ for (const operation of operations) {
334
+ const { collectionLocations } = operation.responseDescriptor;
335
+ if (collectionLocations === null) {
336
+ return verdict(rule, false, `operation ${operation.operationId} declares no collection-location list, so no location can be reconciled against a reference set`);
337
+ }
338
+ const steps = index.stepsUsing(operation.operationId);
339
+ for (const location of collectionLocations) {
340
+ const referenceSet = location.referenceSet;
341
+ if (referenceSet === null)
342
+ continue;
343
+ sites += 1;
344
+ const witnessed = steps.some((step) => {
345
+ const collection = bodyPointer(step.stepId, location.pointer);
346
+ return oracles.some((oracle) => oracle.nodes.some((node) => reconciles(node, collection, location, referenceSet)));
347
+ });
348
+ if (!witnessed) {
349
+ return verdict(rule, false, `no check reconciles collection ${location.pointer} of operation ${operation.operationId} against reference set ${referenceSet} in the form its ${location.expectedCardinality.mode} cardinality requires`);
350
+ }
351
+ }
352
+ }
353
+ return verdict(rule, true, sites === 0
354
+ ? NO_RELEVANT_SITE
355
+ : 'every collection location naming a reference set is reconciled against it in the declared form');
356
+ }
357
+ /** AD-39: a read-back step names the write in its temporal clause and changes no state itself. */
358
+ const readBackStepsFor = (contract, index, writeStepId) => contract.interactionPlan.filter((step) => {
359
+ if (step.stepId === writeStepId || step.after !== writeStepId)
360
+ return false;
361
+ const operation = index.operationOf(step.operationId);
362
+ return operation !== undefined && !operation.stateChangeMarker;
363
+ });
364
+ /** One node holding a pointer into each side of the read-back relation. */
365
+ const relates = (node, writeStepId, readStepId) => {
366
+ const pointers = definedPointers(node);
367
+ return (someAddresses(pointers, `${stepRoot(writeStepId)}/call-inputs`) &&
368
+ someAddresses(pointers, `${stepRoot(readStepId)}/response-body`));
369
+ };
370
+ /**
371
+ * Rule 7: for every operation declaring `stateChangeMarker: true`, some check
372
+ * node relates a pointer under a step invoking it to a pointer under the
373
+ * response body of a later step whose operation changes no state and whose
374
+ * temporal clause names the write. One node carries the relation: two unrelated
375
+ * assertions under one `all` read nothing back.
376
+ */
377
+ export function stateChangeReadBackSatisfaction(contract, context = contextOf(contract)) {
378
+ const rule = 'state-change-read-back';
379
+ const { operations, index, oracles } = context;
380
+ if (operations.length === 0)
381
+ return verdict(rule, false, NO_OPERATION_WITNESS);
382
+ let sites = 0;
383
+ for (const operation of operations) {
384
+ if (!operation.stateChangeMarker)
385
+ continue;
386
+ sites += 1;
387
+ const witnessed = index
388
+ .stepsUsing(operation.operationId)
389
+ .some((writeStep) => readBackStepsFor(contract, index, writeStep.stepId).some((readStep) => oracles.some((oracle) => oracle.nodes.some((node) => relates(node, writeStep.stepId, readStep.stepId)))));
390
+ if (!witnessed) {
391
+ return verdict(rule, false, `no check relates operation ${operation.operationId}'s call inputs to the response body of a later step that changes no state`);
392
+ }
393
+ }
394
+ return verdict(rule, true, sites === 0
395
+ ? NO_RELEVANT_SITE
396
+ : 'every state-changing operation is read back through a later non-state-changing step');
397
+ }
398
+ /**
399
+ * One predicate per rule. The mapped type fails the typecheck when a member of
400
+ * `DISCIPLINE_RULES` has no predicate, matching `RELEVANCE_PREDICATES`.
401
+ */
402
+ export const SATISFACTION_PREDICATES = {
403
+ 'success-indicator-separation': successIndicatorSeparationSatisfaction,
404
+ 'whole-body': wholeBodySatisfaction,
405
+ 'malformed-input': malformedInputSatisfaction,
406
+ 'per-record': perRecordSatisfaction,
407
+ 'sibling-cross-check': siblingCrossCheckSatisfaction,
408
+ 'omission-and-completeness': omissionAndCompletenessSatisfaction,
409
+ 'state-change-read-back': stateChangeReadBackSatisfaction,
410
+ };
411
+ /** All seven verdicts, in `DISCIPLINE_RULES` order, over declarations alone. */
412
+ export function evaluateSatisfaction(contract) {
413
+ const context = contextOf(contract);
414
+ return DISCIPLINE_RULES.map((rule) => SATISFACTION_PREDICATES[rule](contract, context));
415
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AD-31's published predicate table, emitted by the implemented predicates.
3
+ * Pure, so the writer and the drift check cannot disagree about bytes, which
4
+ * is `publish.ts`'s split. Its only input is the corpus argument, so the
5
+ * worked example is unreachable from here, which is what AD-31 requires:
6
+ * publication is exercised against a compile-side contract fixture corpus.
7
+ */
8
+ import type { EvalContract } from '../schemas/eval-contract.ts';
9
+ import { type DisciplineRule } from './rules.ts';
10
+ /** AD-31's three declaration states, a populated one splitting in two. Column order. */
11
+ export declare const DECLARATION_STATES: readonly ['absent', 'explicitly-empty', 'witnessed', 'unwitnessed'];
12
+ export type DeclarationState = (typeof DECLARATION_STATES)[number];
13
+ /** The pair each state asserts. `absent` and `unwitnessed` share one; the reason separates them. */
14
+ export declare const STATE_VERDICTS: {
15
+ readonly [State in DeclarationState]: {
16
+ readonly relevant: boolean;
17
+ readonly satisfied: boolean;
18
+ };
19
+ };
20
+ export type CoverageCell = {
21
+ readonly rule: DisciplineRule;
22
+ readonly state: DeclarationState;
23
+ readonly contractId: string;
24
+ };
25
+ /** The whole document. Throws a worded diagnosis on an unoccupied cell, which would otherwise publish as coverage. */
26
+ export declare function coveragePredicateTable(contracts: readonly EvalContract[], cells: readonly CoverageCell[]): string;