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.
- package/README.md +17 -12
- package/corpus/dev/README.md +3 -2
- package/corpus/dev/compile-seal-example/brief.json +1 -1
- package/corpus/dev/compile-seal-example/contract.json +1 -1
- package/corpus/dev/contracts/absent-collection-locations.json +1 -1
- package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
- package/corpus/dev/contracts/absent-success-indicator.json +1 -1
- package/corpus/dev/contracts/empty-channel-roles.json +1 -1
- package/corpus/dev/contracts/empty-collection-locations.json +1 -1
- package/corpus/dev/contracts/empty-request-shapes.json +1 -1
- package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
- package/corpus/dev/contracts/fragment-selection.json +1 -0
- package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
- package/corpus/dev/contracts/no-operation-inventory.json +1 -1
- package/corpus/dev/contracts/no-read-back-relation.json +1 -1
- package/corpus/dev/contracts/no-state-change-marker.json +1 -1
- package/corpus/dev/contracts/no-type-violating-step.json +1 -1
- package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
- package/corpus/dev/contracts/review-corpus.json +1 -0
- package/corpus/dev/contracts/satisfied-declarations.json +1 -1
- package/corpus/dev/contracts/single-required-response-key.json +1 -1
- package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
- package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
- package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
- package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
- package/corpus/dev/index.json +1 -1
- package/dist/adapters/command-line-adapter.d.ts +44 -0
- package/dist/adapters/command-line-adapter.js +284 -0
- package/dist/adapters/command-target-policy.d.ts +36 -0
- package/dist/adapters/command-target-policy.js +33 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +1 -0
- package/dist/cli/render.d.ts +2 -5
- package/dist/cli/render.js +51 -1
- package/dist/core/compile/bindings.d.ts +26 -7
- package/dist/core/compile/bindings.js +49 -32
- package/dist/core/compile/compile.d.ts +12 -0
- package/dist/core/compile/compile.js +28 -1
- package/dist/core/compile/excluded-content.d.ts +11 -0
- package/dist/core/compile/excluded-content.js +42 -0
- package/dist/core/compile/expression-legality.d.ts +2 -2
- package/dist/core/compile/expression-legality.js +58 -12
- package/dist/core/compile/interface-inventory.d.ts +34 -1
- package/dist/core/compile/interface-inventory.js +88 -11
- package/dist/core/compile/reachability.d.ts +12 -2
- package/dist/core/compile/reachability.js +123 -36
- package/dist/core/compile/schema-version.d.ts +2 -0
- package/dist/core/compile/schema-version.js +25 -0
- package/dist/core/compile/sensitivity-witness.d.ts +31 -12
- package/dist/core/compile/sensitivity-witness.js +110 -23
- package/dist/core/compile/step-reference.d.ts +2 -0
- package/dist/core/compile/step-reference.js +49 -0
- package/dist/core/coverage/operations.d.ts +62 -0
- package/dist/core/coverage/operations.js +57 -0
- package/dist/core/coverage/relevance.d.ts +4 -2
- package/dist/core/coverage/relevance.js +22 -23
- package/dist/core/coverage/satisfaction.d.ts +2 -2
- package/dist/core/coverage/satisfaction.js +73 -48
- package/dist/core/declared-inputs.d.ts +83 -4
- package/dist/core/declared-inputs.js +105 -8
- package/dist/core/evaluate/evidence-resolution.d.ts +9 -12
- package/dist/core/evaluate/evidence-resolution.js +70 -10
- package/dist/core/evaluate/operators.d.ts +22 -0
- package/dist/core/evaluate/operators.js +17 -1
- package/dist/core/evaluate/resolution.d.ts +14 -3
- package/dist/core/evaluate/resolution.js +63 -3
- package/dist/core/excluded-content.d.ts +65 -0
- package/dist/core/excluded-content.js +113 -0
- package/dist/core/failure-codes.d.ts +2 -2
- package/dist/core/failure-codes.js +5 -2
- package/dist/core/ingest/conditions.d.ts +1 -1
- package/dist/core/ingest/ingest.js +8 -0
- package/dist/core/preflight/plan.d.ts +7 -5
- package/dist/core/preflight/plan.js +75 -32
- package/dist/core/preflight/projection.d.ts +6 -3
- package/dist/core/preflight/projection.js +22 -2
- package/dist/core/preflight/reduce.js +47 -7
- package/dist/core/preflight/witness-evidence.d.ts +5 -5
- package/dist/core/preflight/witness-evidence.js +63 -20
- package/dist/core/schemas/artifact.d.ts +683 -35
- package/dist/core/schemas/constraint-ledger.js +11 -0
- package/dist/core/schemas/defect-signature.d.ts +438 -16
- package/dist/core/schemas/defect-signature.js +58 -10
- package/dist/core/schemas/eval-contract.d.ts +517 -12
- package/dist/core/schemas/eval-contract.js +18 -2
- package/dist/core/schemas/evidence-artifact.d.ts +14 -4
- package/dist/core/schemas/evidence-artifact.js +1 -1
- package/dist/core/schemas/interface.d.ts +741 -13
- package/dist/core/schemas/interface.js +120 -7
- package/dist/core/schemas/isolation-manifest.js +16 -9
- package/dist/core/schemas/plan.d.ts +217 -3
- package/dist/core/schemas/plan.js +22 -1
- package/dist/core/schemas/pointer.d.ts +47 -1
- package/dist/core/schemas/pointer.js +89 -8
- package/dist/core/schemas/port-messages.d.ts +194 -10
- package/dist/core/schemas/port-messages.js +73 -6
- package/dist/core/schemas/probe-body.d.ts +18 -0
- package/dist/core/schemas/probe-body.js +13 -0
- package/dist/core/schemas/probe-policy.d.ts +36 -0
- package/dist/core/schemas/probe-policy.js +44 -0
- package/dist/core/schemas/probe.d.ts +122 -3
- package/dist/core/schemas/probe.js +1 -1
- package/dist/core/schemas/sealed-run-record.d.ts +131 -9
- package/dist/core/schemas/sealed-run-record.js +56 -11
- package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
- package/dist/core/schemas/sensitivity-witness.js +61 -5
- package/dist/core/score/bindings.d.ts +2 -2
- package/dist/core/score/bindings.js +25 -13
- package/dist/core/score/qualification.d.ts +5 -5
- package/dist/core/score/qualification.js +67 -37
- package/dist/core/score/quotation.d.ts +1 -24
- package/dist/core/score/quotation.js +29 -4
- package/dist/core/score/score.js +28 -2
- package/dist/core/score/strength.d.ts +9 -0
- package/dist/core/score/strength.js +35 -1
- package/dist/core/score/witness.d.ts +21 -0
- package/dist/core/score/witness.js +9 -4
- package/dist/core/seal/derived-reference.d.ts +2 -9
- package/dist/core/seal/derived-reference.js +62 -23
- package/dist/core/seal/plan-index.d.ts +18 -3
- package/dist/core/seal/plan-index.js +44 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ports/environment-probe-port.d.ts +60 -4
- package/dist/testing/conformance.d.ts +2 -1
- package/dist/testing/conformance.js +1 -0
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +1 -1
- package/dist/testing/probe-conformance.d.ts +56 -5
- package/dist/testing/probe-conformance.js +196 -5
- package/package.json +1 -1
- package/schemas/eval-contract.schema.json +1356 -464
- package/schemas/evidence-artifact.schema.json +59 -29
- package/schemas/isolation-manifest.schema.json +17 -10
- package/schemas/probe.schema.json +197 -71
- package/schemas/rubric.schema.json +1 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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 {
|
|
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:
|
|
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 {
|
|
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' }
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
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
|
|
199
|
-
for (const channel of
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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[]):
|
|
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:
|
|
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) =>
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 (
|
|
238
|
-
|
|
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
|
|
291
|
+
for (const channel of INPUT_CHANNELS) {
|
|
269
292
|
const binding = inputBinding[channel];
|
|
270
293
|
if (binding === null)
|
|
271
294
|
continue;
|
|
272
|
-
const
|
|
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 (!
|
|
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
|
|
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 (
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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. */
|