eval-quality 0.2.0 → 0.5.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 (137) hide show
  1. package/README.md +17 -12
  2. package/corpus/dev/README.md +3 -2
  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/adapters/command-line-adapter.d.ts +44 -0
  28. package/dist/adapters/command-line-adapter.js +284 -0
  29. package/dist/adapters/command-target-policy.d.ts +36 -0
  30. package/dist/adapters/command-target-policy.js +33 -0
  31. package/dist/adapters/index.d.ts +2 -0
  32. package/dist/adapters/index.js +1 -0
  33. package/dist/cli/render.d.ts +2 -5
  34. package/dist/cli/render.js +51 -1
  35. package/dist/core/compile/bindings.d.ts +26 -7
  36. package/dist/core/compile/bindings.js +49 -32
  37. package/dist/core/compile/compile.d.ts +12 -0
  38. package/dist/core/compile/compile.js +28 -1
  39. package/dist/core/compile/excluded-content.d.ts +11 -0
  40. package/dist/core/compile/excluded-content.js +42 -0
  41. package/dist/core/compile/expression-legality.d.ts +2 -2
  42. package/dist/core/compile/expression-legality.js +58 -12
  43. package/dist/core/compile/interface-inventory.d.ts +34 -1
  44. package/dist/core/compile/interface-inventory.js +88 -11
  45. package/dist/core/compile/reachability.d.ts +12 -2
  46. package/dist/core/compile/reachability.js +123 -36
  47. package/dist/core/compile/schema-version.d.ts +2 -0
  48. package/dist/core/compile/schema-version.js +25 -0
  49. package/dist/core/compile/sensitivity-witness.d.ts +31 -12
  50. package/dist/core/compile/sensitivity-witness.js +110 -23
  51. package/dist/core/compile/step-reference.d.ts +2 -0
  52. package/dist/core/compile/step-reference.js +49 -0
  53. package/dist/core/coverage/operations.d.ts +62 -0
  54. package/dist/core/coverage/operations.js +57 -0
  55. package/dist/core/coverage/relevance.d.ts +4 -2
  56. package/dist/core/coverage/relevance.js +22 -23
  57. package/dist/core/coverage/satisfaction.d.ts +2 -2
  58. package/dist/core/coverage/satisfaction.js +73 -48
  59. package/dist/core/declared-inputs.d.ts +83 -4
  60. package/dist/core/declared-inputs.js +105 -8
  61. package/dist/core/evaluate/evidence-resolution.d.ts +9 -12
  62. package/dist/core/evaluate/evidence-resolution.js +70 -10
  63. package/dist/core/evaluate/operators.d.ts +22 -0
  64. package/dist/core/evaluate/operators.js +17 -1
  65. package/dist/core/evaluate/resolution.d.ts +14 -3
  66. package/dist/core/evaluate/resolution.js +63 -3
  67. package/dist/core/excluded-content.d.ts +65 -0
  68. package/dist/core/excluded-content.js +113 -0
  69. package/dist/core/failure-codes.d.ts +2 -2
  70. package/dist/core/failure-codes.js +5 -2
  71. package/dist/core/ingest/conditions.d.ts +1 -1
  72. package/dist/core/ingest/ingest.js +8 -0
  73. package/dist/core/preflight/plan.d.ts +7 -5
  74. package/dist/core/preflight/plan.js +75 -32
  75. package/dist/core/preflight/projection.d.ts +6 -3
  76. package/dist/core/preflight/projection.js +22 -2
  77. package/dist/core/preflight/reduce.js +47 -7
  78. package/dist/core/preflight/witness-evidence.d.ts +5 -5
  79. package/dist/core/preflight/witness-evidence.js +63 -20
  80. package/dist/core/schemas/artifact.d.ts +683 -35
  81. package/dist/core/schemas/constraint-ledger.js +11 -0
  82. package/dist/core/schemas/defect-signature.d.ts +438 -16
  83. package/dist/core/schemas/defect-signature.js +58 -10
  84. package/dist/core/schemas/eval-contract.d.ts +517 -12
  85. package/dist/core/schemas/eval-contract.js +18 -2
  86. package/dist/core/schemas/evidence-artifact.d.ts +14 -4
  87. package/dist/core/schemas/evidence-artifact.js +1 -1
  88. package/dist/core/schemas/interface.d.ts +741 -13
  89. package/dist/core/schemas/interface.js +120 -7
  90. package/dist/core/schemas/isolation-manifest.js +16 -9
  91. package/dist/core/schemas/plan.d.ts +217 -3
  92. package/dist/core/schemas/plan.js +22 -1
  93. package/dist/core/schemas/pointer.d.ts +47 -1
  94. package/dist/core/schemas/pointer.js +89 -8
  95. package/dist/core/schemas/port-messages.d.ts +194 -10
  96. package/dist/core/schemas/port-messages.js +73 -6
  97. package/dist/core/schemas/probe-body.d.ts +18 -0
  98. package/dist/core/schemas/probe-body.js +13 -0
  99. package/dist/core/schemas/probe-policy.d.ts +36 -0
  100. package/dist/core/schemas/probe-policy.js +44 -0
  101. package/dist/core/schemas/probe.d.ts +122 -3
  102. package/dist/core/schemas/probe.js +1 -1
  103. package/dist/core/schemas/sealed-run-record.d.ts +131 -9
  104. package/dist/core/schemas/sealed-run-record.js +56 -11
  105. package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
  106. package/dist/core/schemas/sensitivity-witness.js +61 -5
  107. package/dist/core/score/bindings.d.ts +2 -2
  108. package/dist/core/score/bindings.js +25 -13
  109. package/dist/core/score/qualification.d.ts +5 -5
  110. package/dist/core/score/qualification.js +67 -37
  111. package/dist/core/score/quotation.d.ts +1 -24
  112. package/dist/core/score/quotation.js +29 -4
  113. package/dist/core/score/score.js +28 -2
  114. package/dist/core/score/strength.d.ts +9 -0
  115. package/dist/core/score/strength.js +35 -1
  116. package/dist/core/score/witness.d.ts +21 -0
  117. package/dist/core/score/witness.js +9 -4
  118. package/dist/core/seal/derived-reference.d.ts +2 -9
  119. package/dist/core/seal/derived-reference.js +62 -23
  120. package/dist/core/seal/plan-index.d.ts +18 -3
  121. package/dist/core/seal/plan-index.js +44 -8
  122. package/dist/index.d.ts +1 -1
  123. package/dist/index.js +1 -1
  124. package/dist/ports/environment-probe-port.d.ts +60 -4
  125. package/dist/testing/conformance.d.ts +2 -1
  126. package/dist/testing/conformance.js +1 -0
  127. package/dist/testing/index.d.ts +3 -3
  128. package/dist/testing/index.js +1 -1
  129. package/dist/testing/probe-conformance.d.ts +56 -5
  130. package/dist/testing/probe-conformance.js +196 -5
  131. package/package.json +1 -1
  132. package/schemas/eval-contract.schema.json +1356 -464
  133. package/schemas/evidence-artifact.schema.json +59 -29
  134. package/schemas/isolation-manifest.schema.json +17 -10
  135. package/schemas/probe.schema.json +197 -71
  136. package/schemas/rubric.schema.json +1 -1
  137. package/schemas/sealed-run-record.schema.json +296 -39
@@ -11,7 +11,7 @@ import { Expression } from './expression.ts';
11
11
  * leg the plan could not map onto a `ProbeRequest` would declare work nothing
12
12
  * runs.
13
13
  */
14
- export declare const WitnessInputs: z.ZodObject<{
14
+ export declare const ApiWitnessInputs: z.ZodObject<{
15
15
  path: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
16
16
  query: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
17
17
  header: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -22,6 +22,72 @@ export declare const WitnessInputs: z.ZodObject<{
22
22
  kind: z.ZodLiteral<"absent">;
23
23
  }, z.core.$strict>], "kind">;
24
24
  }, z.core.$strict>;
25
+ export type ApiWitnessInputs = z.infer<typeof ApiWitnessInputs>;
26
+ /**
27
+ * The same, for a leg supplied to a command-kind operation. `stdin` is a
28
+ * tagged value rather than a key map for exactly the reason `body` is: one leg
29
+ * supplies a value and has to tell an absent standard input from one carrying
30
+ * JSON null, which a key map cannot express. The request shape's `stdin` stays
31
+ * a key map, because a declaration says which keys an operation accepts.
32
+ * `suppliedKeys` is the code that bridges the two spellings and it
33
+ * special-cases `stdin` alongside `body`.
34
+ *
35
+ * AD-18 applies to `environment` the way it applies to `header`, so an
36
+ * environment value carries no credential.
37
+ */
38
+ export declare const CommandWitnessInputs: z.ZodObject<{
39
+ argument: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
40
+ option: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
41
+ environment: z.ZodRecord<z.ZodString, z.ZodString>;
42
+ stdin: z.ZodDiscriminatedUnion<[z.ZodObject<{
43
+ kind: z.ZodLiteral<"json">;
44
+ value: z.ZodType<import("./primitives.ts").JsonValue, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonValue, unknown>>;
45
+ }, z.core.$strict>, z.ZodObject<{
46
+ kind: z.ZodLiteral<"text">;
47
+ value: z.ZodString;
48
+ }, z.core.$strict>, z.ZodObject<{
49
+ kind: z.ZodLiteral<"absent">;
50
+ }, z.core.$strict>], "kind">;
51
+ }, z.core.$strict>;
52
+ export type CommandWitnessInputs = z.infer<typeof CommandWitnessInputs>;
53
+ /**
54
+ * Either spelling. A plain union rather than a discriminated one for the same
55
+ * reason `InputBinding` is: the leg names an operation and the kind of the
56
+ * interface declaring it lives in another subtree, so no discriminator is
57
+ * available to the schema and the agreement is a compile-time check.
58
+ *
59
+ * Only the sensitivity leg takes the union. `ManifestationWitness` and
60
+ * `FixtureReset` keep the transport spelling, which keeps the probe artifact
61
+ * byte-identical and keeps this shape's widening inside the eval contract's own
62
+ * version bump. That is truthful rather than merely convenient: both of those
63
+ * legs are issued through the environment-probe port, whose `ProbeRequest`
64
+ * carries a method, a path template, and the four transport channels, and
65
+ * pre-flight rejects a non-api interface for exactly that reason.
66
+ */
67
+ export declare const WitnessInputs: z.ZodUnion<readonly [z.ZodObject<{
68
+ path: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
69
+ query: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
70
+ header: z.ZodRecord<z.ZodString, z.ZodString>;
71
+ body: z.ZodDiscriminatedUnion<[z.ZodObject<{
72
+ kind: z.ZodLiteral<"json">;
73
+ value: z.ZodType<import("./primitives.ts").JsonValue, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonValue, unknown>>;
74
+ }, z.core.$strict>, z.ZodObject<{
75
+ kind: z.ZodLiteral<"absent">;
76
+ }, z.core.$strict>], "kind">;
77
+ }, z.core.$strict>, z.ZodObject<{
78
+ argument: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
79
+ option: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
80
+ environment: z.ZodRecord<z.ZodString, z.ZodString>;
81
+ stdin: z.ZodDiscriminatedUnion<[z.ZodObject<{
82
+ kind: z.ZodLiteral<"json">;
83
+ value: z.ZodType<import("./primitives.ts").JsonValue, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonValue, unknown>>;
84
+ }, z.core.$strict>, z.ZodObject<{
85
+ kind: z.ZodLiteral<"text">;
86
+ value: z.ZodString;
87
+ }, z.core.$strict>, z.ZodObject<{
88
+ kind: z.ZodLiteral<"absent">;
89
+ }, z.core.$strict>], "kind">;
90
+ }, z.core.$strict>]>;
25
91
  export type WitnessInputs = z.infer<typeof WitnessInputs>;
26
92
  /**
27
93
  * Half a witness pair. `legId` roots the relation's pointers, which address this
@@ -30,7 +96,7 @@ export type WitnessInputs = z.infer<typeof WitnessInputs>;
30
96
  */
31
97
  export declare const SensitivityWitnessLeg: z.ZodObject<{
32
98
  legId: z.ZodString;
33
- inputs: z.ZodObject<{
99
+ inputs: z.ZodUnion<readonly [z.ZodObject<{
34
100
  path: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
35
101
  query: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
36
102
  header: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -40,14 +106,33 @@ export declare const SensitivityWitnessLeg: z.ZodObject<{
40
106
  }, z.core.$strict>, z.ZodObject<{
41
107
  kind: z.ZodLiteral<"absent">;
42
108
  }, z.core.$strict>], "kind">;
43
- }, z.core.$strict>;
109
+ }, z.core.$strict>, z.ZodObject<{
110
+ argument: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
111
+ option: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
112
+ environment: z.ZodRecord<z.ZodString, z.ZodString>;
113
+ stdin: z.ZodDiscriminatedUnion<[z.ZodObject<{
114
+ kind: z.ZodLiteral<"json">;
115
+ value: z.ZodType<import("./primitives.ts").JsonValue, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonValue, unknown>>;
116
+ }, z.core.$strict>, z.ZodObject<{
117
+ kind: z.ZodLiteral<"text">;
118
+ value: z.ZodString;
119
+ }, z.core.$strict>, z.ZodObject<{
120
+ kind: z.ZodLiteral<"absent">;
121
+ }, z.core.$strict>], "kind">;
122
+ }, z.core.$strict>]>;
44
123
  }, z.core.$strict>;
45
124
  export type SensitivityWitnessLeg = z.infer<typeof SensitivityWitnessLeg>;
46
- export declare const WITNESS_CHANNELS: readonly ['path', 'query', 'body'];
125
+ export declare const API_WITNESS_CHANNELS: readonly ['path', 'query', 'body'];
126
+ export declare const COMMAND_WITNESS_CHANNELS: readonly ['argument', 'option', 'environment', 'stdin'];
127
+ export declare const WITNESS_CHANNELS: readonly ["path", "query", "body", "argument", "option", "environment", "stdin"];
47
128
  export declare const WitnessChannel: z.ZodEnum<{
129
+ argument: "argument";
48
130
  body: "body";
131
+ environment: "environment";
132
+ option: "option";
49
133
  path: "path";
50
134
  query: "query";
135
+ stdin: "stdin";
51
136
  }>;
52
137
  export type WitnessChannel = z.infer<typeof WitnessChannel>;
53
138
  /**
@@ -67,13 +152,17 @@ export type WitnessChannel = z.infer<typeof WitnessChannel>;
67
152
  export declare const SensitivityWitness: z.ZodObject<{
68
153
  witnessId: z.ZodString;
69
154
  channel: z.ZodEnum<{
155
+ argument: "argument";
70
156
  body: "body";
157
+ environment: "environment";
158
+ option: "option";
71
159
  path: "path";
72
160
  query: "query";
161
+ stdin: "stdin";
73
162
  }>;
74
163
  legs: z.ZodArray<z.ZodObject<{
75
164
  legId: z.ZodString;
76
- inputs: z.ZodObject<{
165
+ inputs: z.ZodUnion<readonly [z.ZodObject<{
77
166
  path: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
78
167
  query: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
79
168
  header: z.ZodRecord<z.ZodString, z.ZodString>;
@@ -83,7 +172,20 @@ export declare const SensitivityWitness: z.ZodObject<{
83
172
  }, z.core.$strict>, z.ZodObject<{
84
173
  kind: z.ZodLiteral<"absent">;
85
174
  }, z.core.$strict>], "kind">;
86
- }, z.core.$strict>;
175
+ }, z.core.$strict>, z.ZodObject<{
176
+ argument: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
177
+ option: z.ZodType<import("./primitives.ts").JsonObject, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonObject, unknown>>;
178
+ environment: z.ZodRecord<z.ZodString, z.ZodString>;
179
+ stdin: z.ZodDiscriminatedUnion<[z.ZodObject<{
180
+ kind: z.ZodLiteral<"json">;
181
+ value: z.ZodType<import("./primitives.ts").JsonValue, unknown, z.core.$ZodTypeInternals<import("./primitives.ts").JsonValue, unknown>>;
182
+ }, z.core.$strict>, z.ZodObject<{
183
+ kind: z.ZodLiteral<"text">;
184
+ value: z.ZodString;
185
+ }, z.core.$strict>, z.ZodObject<{
186
+ kind: z.ZodLiteral<"absent">;
187
+ }, z.core.$strict>], "kind">;
188
+ }, z.core.$strict>]>;
87
189
  }, z.core.$strict>>;
88
190
  relation: z.ZodType<Expression, unknown, z.core.$ZodTypeInternals<Expression, unknown>>;
89
191
  }, z.core.$strict>;
@@ -2,7 +2,7 @@
2
2
  import { z } from 'zod';
3
3
  import { Expression } from './expression.js';
4
4
  import { Identifier, JsonObjectValue, KeyName } from './primitives.js';
5
- import { ProbeRequestBody } from './probe-body.js';
5
+ import { ProbeRequestBody, ProbeRequestStdin } from './probe-body.js';
6
6
  /**
7
7
  * The four transport channels one probe leg supplies, as values. `RequestShape`
8
8
  * declares what an operation accepts; this declares what one leg sends. AD-18
@@ -13,7 +13,7 @@ import { ProbeRequestBody } from './probe-body.js';
13
13
  * leg the plan could not map onto a `ProbeRequest` would declare work nothing
14
14
  * runs.
15
15
  */
16
- export const WitnessInputs = z
16
+ export const ApiWitnessInputs = z
17
17
  .strictObject({
18
18
  path: JsonObjectValue,
19
19
  query: JsonObjectValue,
@@ -24,6 +24,46 @@ export const WitnessInputs = z
24
24
  id: 'WitnessInputs',
25
25
  description: "One probe leg's supplied inputs, keyed by AD-19 transport channel, in the spelling the environment-probe port accepts. Shared by both witness kinds and by the fixture reset, so the export carries it once.",
26
26
  });
27
+ /**
28
+ * The same, for a leg supplied to a command-kind operation. `stdin` is a
29
+ * tagged value rather than a key map for exactly the reason `body` is: one leg
30
+ * supplies a value and has to tell an absent standard input from one carrying
31
+ * JSON null, which a key map cannot express. The request shape's `stdin` stays
32
+ * a key map, because a declaration says which keys an operation accepts.
33
+ * `suppliedKeys` is the code that bridges the two spellings and it
34
+ * special-cases `stdin` alongside `body`.
35
+ *
36
+ * AD-18 applies to `environment` the way it applies to `header`, so an
37
+ * environment value carries no credential.
38
+ */
39
+ // Bare, with no `.meta({ id })`, and the reason is a published-schema one
40
+ // rather than a size one. A `$ref`'d definition's own errors reach ajv with a
41
+ // schema path relative to that definition, so two definitions under one
42
+ // `anyOf` report `#/required` and `#/additionalProperties` at the same
43
+ // instance path and nothing tells them apart. AD-13's mutation sweep needs one
44
+ // keyword deletion to be attributable to one occurrence, and the branch is
45
+ // attributable only while it is spelled in place.
46
+ export const CommandWitnessInputs = z.strictObject({
47
+ argument: JsonObjectValue,
48
+ option: JsonObjectValue,
49
+ environment: z.record(KeyName, z.string()),
50
+ stdin: ProbeRequestStdin,
51
+ });
52
+ /**
53
+ * Either spelling. A plain union rather than a discriminated one for the same
54
+ * reason `InputBinding` is: the leg names an operation and the kind of the
55
+ * interface declaring it lives in another subtree, so no discriminator is
56
+ * available to the schema and the agreement is a compile-time check.
57
+ *
58
+ * Only the sensitivity leg takes the union. `ManifestationWitness` and
59
+ * `FixtureReset` keep the transport spelling, which keeps the probe artifact
60
+ * byte-identical and keeps this shape's widening inside the eval contract's own
61
+ * version bump. That is truthful rather than merely convenient: both of those
62
+ * legs are issued through the environment-probe port, whose `ProbeRequest`
63
+ * carries a method, a path template, and the four transport channels, and
64
+ * pre-flight rejects a non-api interface for exactly that reason.
65
+ */
66
+ export const WitnessInputs = z.union([ApiWitnessInputs, CommandWitnessInputs]);
27
67
  /**
28
68
  * Half a witness pair. `legId` roots the relation's pointers, which address this
29
69
  * leg's response as `/interactions/{legId}/response-body/...`, so it shares one
@@ -36,7 +76,23 @@ export const SensitivityWitnessLeg = z.strictObject({
36
76
  // AD-10 selects the differential channel by the operation's state-change
37
77
  // marker: `path` or `query` where the marker is false, `body` where it is true.
38
78
  // `header` is absent on purpose; no AD names a header differential.
39
- export const WITNESS_CHANNELS = ['path', 'query', 'body'];
79
+ export const API_WITNESS_CHANNELS = ['path', 'query', 'body'];
80
+ // A command operation's differential channel. All four are admitted rather
81
+ // than two: AD-10's marker rule selects `path` or `query` against `body`
82
+ // because an HTTP read carries its identifier in the URL and a write carries
83
+ // it in the body, and a command carries its inputs the same way whether or not
84
+ // it changes state, so the marker decides nothing here. Which channel a given
85
+ // command witness may use is the contract author's choice.
86
+ export const COMMAND_WITNESS_CHANNELS = [
87
+ 'argument',
88
+ 'option',
89
+ 'environment',
90
+ 'stdin',
91
+ ];
92
+ export const WITNESS_CHANNELS = [
93
+ ...API_WITNESS_CHANNELS,
94
+ ...COMMAND_WITNESS_CHANNELS,
95
+ ];
40
96
  export const WitnessChannel = z.enum(WITNESS_CHANNELS);
41
97
  /**
42
98
  * AD-10's typed sensitivity witness: a pair of inputs and the AD-4 relation
@@ -70,7 +126,7 @@ export const ManifestationWitness = z.strictObject({
70
126
  legId: Identifier,
71
127
  interfaceId: Identifier,
72
128
  operationId: Identifier,
73
- inputs: WitnessInputs,
129
+ inputs: ApiWitnessInputs,
74
130
  relation: Expression,
75
131
  });
76
132
  /**
@@ -82,5 +138,5 @@ export const FixtureReset = z.strictObject({
82
138
  legId: Identifier,
83
139
  interfaceId: Identifier,
84
140
  operationId: Identifier,
85
- inputs: WitnessInputs,
141
+ inputs: ApiWitnessInputs,
86
142
  });
@@ -1,6 +1,6 @@
1
1
  import { type ResolvedValue } from '../evaluate/resolved-value.ts';
2
2
  import type { InteractionStep } from '../schemas/plan.ts';
3
- import { type TransportChannelName } from '../schemas/pointer.ts';
3
+ import type { InputChannelName } from '../schemas/pointer.ts';
4
4
  import type { JsonValue } from '../schemas/primitives.ts';
5
5
  import type { Observation } from '../schemas/sealed-run-record.ts';
6
6
  import { type PlanIndex } from '../seal/plan-index.ts';
@@ -30,7 +30,7 @@ export type CapturedResolution = {
30
30
  * JSON-encoded rather than delimiter-joined, since a parameter key is arbitrary
31
31
  * caller-supplied text and could contain any separator.
32
32
  */
33
- export declare function bindingSiteKey(stepId: string, transportChannel: TransportChannelName, key: string): string;
33
+ export declare function bindingSiteKey(stepId: string, transportChannel: InputChannelName, key: string): string;
34
34
  /**
35
35
  * Resolves one captured pointer against the record.
36
36
  *
@@ -13,10 +13,10 @@
13
13
  * evidence, and no AD-6 outcome state is assigned.
14
14
  */
15
15
  import { capturedBindings } from '../compile/bindings.js';
16
+ import { boundChannelsOf, requestShapeOf } from '../declared-inputs.js';
16
17
  import { channelRoot, walkTail } from '../evaluate/evidence-resolution.js';
17
18
  import { ABSENT } from '../evaluate/resolved-value.js';
18
- import { TRANSPORT_CHANNELS, } from '../schemas/pointer.js';
19
- import { parseEvidenceTarget } from '../seal/plan-index.js';
19
+ import { anyOperationOf, parseEvidenceTarget, } from '../seal/plan-index.js';
20
20
  import { bindingOrder } from './binding-order.js';
21
21
  import { selectObservations } from './selection.js';
22
22
  const ABSENT_RESOLUTION = { status: 'absent' };
@@ -183,21 +183,22 @@ function capturedFloor(step, resolved) {
183
183
  * irrelevant.
184
184
  * - `{ captured: p }`: the observed value deep-equals the resolved captured
185
185
  * value. The ordering half is `capturedFloor`'s, applied before this runs.
186
- * - `{ matcher: 'any' }` and `{ principal }`: the key is present, and nothing
187
- * more. Neither declares a value the contract knows: a principal's value is
188
- * provisioned by the harness at runtime, which is the whole reason the
189
- * binding exists. The consequence is that two steps differing only by which
190
- * principal they bind cannot be separated here, because no field of a sealed
191
- * run record says which principal the harness used.
186
+ * - `{ matcher: 'any' }`: the key is present, and nothing more.
187
+ * - `{ principal }`: the key is present AND the observation names the same
188
+ * principal. The value behind the name is provisioned by the harness at
189
+ * runtime, which is the whole reason the binding exists, so the name is all
190
+ * either side can compare. `Observation.principal` is what makes that
191
+ * comparison possible: without it two steps differing only by which
192
+ * principal they bind both matched every observation and both resolved
193
+ * `several`.
192
194
  * - `{ matcher: 'type-violating' }`: the observed value's JSON type differs
193
195
  * from the operation's declared type for that key. A key whose declared type
194
196
  * is absent or `null` fails closed: an indeterminate type cannot prove a
195
197
  * violation.
196
198
  */
197
199
  function satisfiesBindings(step, observation, index, resolved) {
198
- const operation = index.operationOf(step.operationId);
199
- for (const channel of TRANSPORT_CHANNELS) {
200
- const binding = step.inputBinding[channel];
200
+ const operation = anyOperationOf(index, step.operationId);
201
+ for (const { channel, bound: binding } of boundChannelsOf(step.inputBinding)) {
201
202
  if (binding === null)
202
203
  continue;
203
204
  const observed = observation.callInputs[channel];
@@ -224,11 +225,22 @@ function satisfiesBindings(step, observation, index, resolved) {
224
225
  return false;
225
226
  continue;
226
227
  }
227
- if ('principal' in value)
228
+ // Presence plus identity. The contract declares a name and the
229
+ // harness provisions the value behind it, so the name is all either
230
+ // side can compare; comparing it is what separates two steps of one
231
+ // operation that differ only in the account they act as. A record
232
+ // naming no principal matches no principal-bound step, rather than
233
+ // matching every one of them.
234
+ if ('principal' in value) {
235
+ if (observation.principal !== value.principal)
236
+ return false;
228
237
  continue;
238
+ }
229
239
  if (value.matcher === 'any')
230
240
  continue;
231
- const declared = operation?.requestShape[channel].types[key];
241
+ const declared = operation === undefined
242
+ ? undefined
243
+ : requestShapeOf(operation, channel)?.types[key];
232
244
  if (declared === undefined || declared === null)
233
245
  return false;
234
246
  if (jsonTypeOf(actual) === declared)
@@ -1,11 +1,11 @@
1
1
  import { type DefectSignature } from '../schemas/defect-signature.ts';
2
- import type { Operation, PermittedInterface } from '../schemas/interface.ts';
2
+ import type { AnyOperation, PermittedInterface } from '../schemas/interface.ts';
3
3
  import type { Probe } from '../schemas/probe.ts';
4
4
  /**
5
5
  * The closed reason set. Nothing outside it can come back from `qualifyProbe`,
6
6
  * so a caller routing a rejection to a rung has a finite table to write.
7
7
  */
8
- export declare const QUALIFICATION_FAILURES: readonly ['qualification-route-incompatible', 'qualification-defect-sources-mixed', 'qualification-evidence-unverified', 'signature-absent', 'signature-present-on-canary', 'signature-interface-kind-unsupported', 'signature-observable-channel-not-response-side', 'condition-channels-underspecified', 'condition-disjunct-without-response-channel', 'condition-selector-key-undeclared', 'condition-pointer-not-observation-rooted', 'condition-pointer-unwritable', 'condition-text-channel-on-api', 'condition-reference-set-operand', 'condition-operand-illegal', 'condition-regex-illegal', 'condition-quantifier-nesting', 'condition-quantifier-over-non-collection', 'condition-bound-element-outside-quantifier'];
8
+ export declare const QUALIFICATION_FAILURES: readonly ['qualification-route-incompatible', 'qualification-defect-sources-mixed', 'qualification-evidence-unverified', 'signature-absent', 'signature-present-on-canary', 'signature-interface-kind-unsupported', 'signature-observable-channel-not-response-side', 'condition-channels-underspecified', 'condition-disjunct-without-response-channel', 'condition-selector-key-undeclared', 'condition-pointer-not-observation-rooted', 'condition-pointer-unwritable', 'condition-artifact-channel-contract-local', 'condition-text-channel-on-api', 'condition-reference-set-operand', 'condition-operand-illegal', 'condition-regex-illegal', 'condition-quantifier-nesting', 'condition-quantifier-over-non-collection', 'condition-bound-element-outside-quantifier'];
9
9
  export type QualificationFailureCode = (typeof QUALIFICATION_FAILURES)[number];
10
10
  export type QualificationFailure = {
11
11
  readonly code: QualificationFailureCode;
@@ -36,14 +36,14 @@ export type QualificationResult = {
36
36
  * failed compilation under `duplicate-operation-signature`, so the first match
37
37
  * is the only match for any contract that compiled.
38
38
  */
39
- export declare function resolveHomeOperation(signature: DefectSignature, interfaces: readonly PermittedInterface[]): Operation | null;
39
+ export declare function resolveHomeOperation(signature: DefectSignature, interfaces: readonly PermittedInterface[]): AnyOperation | null;
40
40
  /**
41
41
  * AD-9's gate over one probe. `homeOperation` is the operation the signature
42
42
  * resolves to in whatever inventory the caller is qualifying against, or `null`
43
43
  * when there is none to qualify against; the three declaration-dependent checks
44
44
  * are skipped in that case and `declarationChecksRan` says so.
45
45
  */
46
- export declare function qualifyProbe(probe: Probe, homeOperation: Operation | null): QualificationResult;
46
+ export declare function qualifyProbe(probe: Probe, homeOperation: AnyOperation | null): QualificationResult;
47
47
  export type QualifiedProbe = {
48
48
  readonly probe: Probe;
49
49
  readonly result: QualificationResult;
@@ -80,4 +80,4 @@ export type SealedProbeSet = {
80
80
  * no inventory writes `() => null` at the call site, where the choice shows up
81
81
  * in a diff.
82
82
  */
83
- export declare function sealProbeSet(probes: readonly Probe[], homeOperationOf: (probe: Probe) => Operation | null): SealedProbeSet;
83
+ export declare function sealProbeSet(probes: readonly Probe[], homeOperationOf: (probe: Probe) => AnyOperation | null): SealedProbeSet;
@@ -15,11 +15,13 @@
15
15
  * `Error`.
16
16
  */
17
17
  import { checkExpressionOperandLegality, checkExpressionQuantifierNesting, checkExpressionQuantifierOverNonCollection, checkExpressionRegexConstructs, walkExpression, } from '../compile/expression-legality.js';
18
- import { operationSignature } from '../compile/interface-inventory.js';
18
+ import { anyOperationSignature, commandSignature, operationSignature, } from '../compile/interface-inventory.js';
19
19
  import { checkExpressionBoundElementScope, checkExpressionEvidenceReachability, forEachExpressionPointer, } from '../compile/reachability.js';
20
+ import { requestShapeOf } from '../declared-inputs.js';
20
21
  import { StructuralFailure } from '../failure-codes.js';
21
22
  import { OBSERVED_STEP_ID, } from '../schemas/defect-signature.js';
22
- import { TRANSPORT_CHANNELS, } from '../schemas/pointer.js';
23
+ import { operationsOf } from '../schemas/interface.js';
24
+ import { API_RESPONSE_CHANNELS, COMMAND_RESPONSE_CHANNELS, IDENTIFIER_ROOTED_CHANNEL, INPUT_CHANNELS, RESPONSE_SIDE_CHANNELS, } from '../schemas/pointer.js';
23
25
  import { parseEvidenceTarget } from '../seal/plan-index.js';
24
26
  /**
25
27
  * The closed reason set. Nothing outside it can come back from `qualifyProbe`,
@@ -38,6 +40,7 @@ export const QUALIFICATION_FAILURES = [
38
40
  'condition-selector-key-undeclared',
39
41
  'condition-pointer-not-observation-rooted',
40
42
  'condition-pointer-unwritable',
43
+ 'condition-artifact-channel-contract-local',
41
44
  'condition-text-channel-on-api',
42
45
  'condition-reference-set-operand',
43
46
  'condition-operand-illegal',
@@ -55,10 +58,20 @@ export const QUALIFICATION_FAILURES = [
55
58
  * is the only match for any contract that compiled.
56
59
  */
57
60
  export function resolveHomeOperation(signature, interfaces) {
58
- const wanted = operationSignature(signature);
61
+ // The identity is compared within its own kind. A signature declaring a
62
+ // method and a path template can only name an operation declaring the same
63
+ // pair, and one declaring an invocation can only name an operation
64
+ // declaring one; comparing the rendered strings across kinds would let
65
+ // `GET /notes` collide with an executable literally named that.
66
+ const command = signature.interfaceKind === 'cli';
67
+ const wanted = command
68
+ ? commandSignature(signature)
69
+ : operationSignature(signature);
59
70
  for (const iface of interfaces) {
60
- for (const operation of iface.operations) {
61
- if (operationSignature(operation) === wanted)
71
+ if ((iface.kind === 'cli') !== command)
72
+ continue;
73
+ for (const operation of operationsOf(iface)) {
74
+ if (anyOperationSignature(operation) === wanted)
62
75
  return operation;
63
76
  }
64
77
  }
@@ -67,24 +80,14 @@ export function resolveHomeOperation(signature, interfaces) {
67
80
  // AD-26's channels that carry what came back, as opposed to what was sent. The
68
81
  // channel rule below needs at least one of these, or a condition naming two
69
82
  // channels could name `call-inputs` twice and pass.
70
- const RESPONSE_SIDE_CHANNELS = new Set([
71
- 'response-body',
72
- 'response-headers',
73
- 'response-status',
74
- 'stdout',
75
- 'stderr',
76
- 'exit-code',
77
- ]);
78
- // The three channels an `api` signature can never manifest in. The contract
79
- // side cannot decide this: reachability rejects a tailed `stdout` pointer and
80
- // returns reachable for a bare one unconditionally, because an operation
81
- // carries no interface kind. The signature's own declared kind is what makes
82
- // the rule decidable, so it is spent here rather than left as prose.
83
- const TEXT_CHANNELS = new Set([
84
- 'stdout',
85
- 'stderr',
86
- 'exit-code',
87
- ]);
83
+ //
84
+ // Derived from the vocabulary rather than transcribed: these were two
85
+ // hand-maintained string sets, and a string set does not fail a typecheck when
86
+ // a channel joins the enum, so a new channel would have gone unassigned and
87
+ // silently answered "no" to both questions below.
88
+ const RESPONSE_SIDE = new Set(RESPONSE_SIDE_CHANNELS);
89
+ /** The channels a signature of this kind can never manifest in. */
90
+ const foreignChannels = (kind) => new Set(kind === 'cli' ? API_RESPONSE_CHANNELS : COMMAND_RESPONSE_CHANNELS);
88
91
  const probePath = (probe, tail) => `Probe[probeId=${probe.probeId}]${tail}`;
89
92
  /**
90
93
  * Which routes this probe's class and control status admit. An empty list is
@@ -234,12 +237,32 @@ function checkOperandsAndCollectChannels(probe, signature, failures) {
234
237
  return;
235
238
  }
236
239
  channels.add(target.channel);
237
- if (signature.interfaceKind === 'api' &&
238
- TEXT_CHANNELS.has(target.channel)) {
240
+ if (target.channel === IDENTIFIER_ROOTED_CHANNEL) {
241
+ // AD-40 dropped `operationId` from the resolution key because it is
242
+ // contract-local: two contracts name the same operation differently, so
243
+ // a signature carrying one resolves against exactly the contract it was
244
+ // authored on. An artifact identifier is contract-local in the same way.
245
+ // It is minted by whoever declared the operation's artifact descriptor,
246
+ // so `/artifact/report` names one file in this contract and nothing at
247
+ // all in the next one, and a signature quantified over it stops being
248
+ // portable while still parsing and compiling clean.
249
+ //
250
+ // The restriction lifts the day the vocabulary reserves an identifier
251
+ // meaning "the artifact this operation describes", the way
252
+ // `OBSERVED_STEP_ID` reserves one for the step. Until then a signature
253
+ // reaches a written file through the descriptor channel of whatever
254
+ // operation it binds, which is kind-neutral and needs no identifier.
255
+ failures.push({
256
+ code: 'condition-artifact-channel-contract-local',
257
+ artifactPath: `${conditionPath}${path}`,
258
+ detail: `"${pointer}" names artifact "${target.artifactId}"; an artifact identifier is minted per contract, so a signature carrying one resolves only against the contract it was authored on, which is what AD-40 dropped operationId to avoid`,
259
+ });
260
+ }
261
+ if (foreignChannels(signature.interfaceKind).has(target.channel)) {
239
262
  failures.push({
240
263
  code: 'condition-text-channel-on-api',
241
264
  artifactPath: `${conditionPath}${path}`,
242
- detail: `"${pointer}" addresses ${target.channel}, which an api interface never produces (AD-19, AD-26)`,
265
+ detail: `"${pointer}" addresses ${target.channel}, which ${signature.interfaceKind === 'cli' ? 'an interface behind a command' : 'an api interface'} never produces (AD-19, AD-26)`,
243
266
  });
244
267
  }
245
268
  });
@@ -265,11 +288,18 @@ function checkOperandsAndCollectChannels(probe, signature, failures) {
265
288
  */
266
289
  function checkSelectorKeys(probe, signature, operation, failures) {
267
290
  const { inputBinding } = signature.condition.selector;
268
- for (const channel of TRANSPORT_CHANNELS) {
291
+ for (const channel of INPUT_CHANNELS) {
269
292
  const binding = inputBinding[channel];
270
293
  if (binding === null)
271
294
  continue;
272
- const { requiredKeys, permittedKeys, types } = operation.requestShape[channel];
295
+ const shape = requestShapeOf(operation, channel);
296
+ // A channel the operation does not accept input on declares no key,
297
+ // which is exactly the condition the first check below reports.
298
+ const { requiredKeys, permittedKeys, types } = shape ?? {
299
+ requiredKeys: [],
300
+ permittedKeys: [],
301
+ types: {},
302
+ };
273
303
  for (const key of Object.keys(binding)) {
274
304
  const at = probePath(probe, `.defectSignature.condition.selector.inputBinding.${channel}[${JSON.stringify(key)}]`);
275
305
  if (!requiredKeys.includes(key) && !permittedKeys.includes(key)) {
@@ -317,7 +347,7 @@ function checkSelectorKeys(probe, signature, operation, failures) {
317
347
  */
318
348
  function checkObservableChannel(probe, signature, failures) {
319
349
  const path = probePath(probe, '.defectSignature.observableChannel');
320
- if (!RESPONSE_SIDE_CHANNELS.has(signature.observableChannel)) {
350
+ if (!RESPONSE_SIDE.has(signature.observableChannel)) {
321
351
  failures.push({
322
352
  code: 'signature-observable-channel-not-response-side',
323
353
  artifactPath: path,
@@ -325,12 +355,11 @@ function checkObservableChannel(probe, signature, failures) {
325
355
  });
326
356
  return;
327
357
  }
328
- if (signature.interfaceKind === 'api' &&
329
- TEXT_CHANNELS.has(signature.observableChannel)) {
358
+ if (foreignChannels(signature.interfaceKind).has(signature.observableChannel)) {
330
359
  failures.push({
331
360
  code: 'condition-text-channel-on-api',
332
361
  artifactPath: path,
333
- detail: `declares observableChannel "${signature.observableChannel}", which an api interface never produces (AD-19, AD-26)`,
362
+ detail: `declares observableChannel "${signature.observableChannel}", which ${signature.interfaceKind === 'cli' ? 'an interface behind a command' : 'an api interface'} never produces (AD-19, AD-26)`,
334
363
  });
335
364
  }
336
365
  }
@@ -352,11 +381,11 @@ function checkChannels(probe, signature, channels, failures) {
352
381
  // reject, arriving through the field that names the rule. The declared
353
382
  // channel is rejected outright one check up, and this guard keeps the rule
354
383
  // true of this function on its own.
355
- if (RESPONSE_SIDE_CHANNELS.has(signature.observableChannel) &&
384
+ if (RESPONSE_SIDE.has(signature.observableChannel) &&
356
385
  channels.has(signature.observableChannel))
357
386
  return;
358
387
  const namesTwo = channels.size >= 2;
359
- const namesResponse = [...channels].some((channel) => RESPONSE_SIDE_CHANNELS.has(channel));
388
+ const namesResponse = [...channels].some((channel) => RESPONSE_SIDE.has(channel));
360
389
  if (namesTwo && namesResponse)
361
390
  return;
362
391
  failures.push({
@@ -421,7 +450,7 @@ function checkDisjuncts(probe, signature, failures) {
421
450
  const conditionPath = probePath(probe, '.defectSignature.condition.predicate');
422
451
  walkAnyNodes(signature.condition.predicate, '', null, (operand, path, bound) => {
423
452
  const named = channelsNamedBy(operand, bound);
424
- if ([...named].some((channel) => RESPONSE_SIDE_CHANNELS.has(channel))) {
453
+ if ([...named].some((channel) => RESPONSE_SIDE.has(channel))) {
425
454
  return;
426
455
  }
427
456
  failures.push({
@@ -560,11 +589,12 @@ export function qualifyProbe(probe, homeOperation) {
560
589
  }
561
590
  }
562
591
  if (signature !== null) {
563
- if (signature.interfaceKind !== 'api') {
592
+ if (signature.interfaceKind !== 'api' &&
593
+ signature.interfaceKind !== 'cli') {
564
594
  failures.push({
565
595
  code: 'signature-interface-kind-unsupported',
566
596
  artifactPath: probePath(probe, '.defectSignature.interfaceKind'),
567
- detail: `"${signature.interfaceKind}" declares a method and a path template that mean nothing off an api interface; v0 keeps all four kinds in the enum so unsupported-interface-kind stays fireable contract-side (AD-19)`,
597
+ detail: `"${signature.interfaceKind}" declares a method and a path template with no per-kind semantics behind them; the kinds stay in the enum so unsupported-interface-kind stays fireable contract-side (AD-19)`,
568
598
  });
569
599
  }
570
600
  checkObservableChannel(probe, signature, failures);
@@ -4,30 +4,7 @@ type DefectFinding = Extract<SealedRunRecord['findings'][number], {
4
4
  findingType: 'defect';
5
5
  }>;
6
6
  type QuotedEvidence = DefectFinding['quotedEvidence'][number];
7
- /**
8
- * One evidence channel of one observation, rendered as the text a quotation is
9
- * asked to be a substring of.
10
- *
11
- * `stdout` and `stderr` project as themselves; `response-status` and
12
- * `exit-code` through the ordinary integer rendering; `response-body`,
13
- * `response-headers`, and `call-inputs` through the same RFC 8785 serialization
14
- * the digest uses.
15
- *
16
- * No case folding and no whitespace normalization: AD-23 requires the evidence
17
- * verbatim, and a normalizing match would accept a quote the record does not
18
- * contain. Canonical serialization re-spells a body with sorted keys and no
19
- * whitespace, so a quote taken from a pretty-printed rendering is not a
20
- * substring of it. The record stores JSON values and has no pretty-printed form
21
- * to quote.
22
- *
23
- * A `null` channel projects to nothing and witnesses nothing. On
24
- * `response-body` that also swallows a body that genuinely was JSON `null`,
25
- * which is the schema's own stated cost of one uniform spelling for "absent".
26
- *
27
- * Should canonicalization fault on a caller's record, the fault propagates,
28
- * which is what keeps this procedure from being circular with it.
29
- */
30
- export declare function projectChannel(observation: Observation, channel: EvidenceChannelName, artifactPath: string): string | null;
7
+ export declare function projectChannel(observation: Observation, channel: EvidenceChannelName, artifactPath: string, artifactId?: string | null): string | null;
31
8
  export type UnwitnessedQuotation = {
32
9
  readonly findingId: string;
33
10
  /** the position of the quotation within the finding's own list. */