eval-quality 1.4.2 → 3.0.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 (110) hide show
  1. package/README.md +6 -6
  2. package/corpus/dev/README.md +24 -13
  3. package/corpus/dev/compile-seal-example/brief.json +1 -1
  4. package/corpus/dev/compile-seal-example/contract.json +1 -1
  5. package/corpus/dev/contracts/absent-collection-locations.json +1 -1
  6. package/corpus/dev/contracts/absent-sibling-groups.json +1 -1
  7. package/corpus/dev/contracts/absent-success-indicator.json +1 -1
  8. package/corpus/dev/contracts/captured-read-back.json +1 -0
  9. package/corpus/dev/contracts/checklist-selection.json +1 -0
  10. package/corpus/dev/contracts/empty-channel-roles.json +1 -1
  11. package/corpus/dev/contracts/empty-collection-locations.json +1 -1
  12. package/corpus/dev/contracts/empty-request-shapes.json +1 -1
  13. package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
  14. package/corpus/dev/contracts/fragment-selection.json +1 -1
  15. package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
  16. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  17. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  18. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  19. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  20. package/corpus/dev/contracts/notes-tool-server.json +1 -0
  21. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  22. package/corpus/dev/contracts/review-corpus.json +1 -1
  23. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  24. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  25. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  26. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  27. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  28. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  29. package/corpus/dev/index.json +1 -1
  30. package/dist/adapters/command-line-adapter.js +44 -6
  31. package/dist/adapters/index.d.ts +2 -0
  32. package/dist/adapters/index.js +1 -0
  33. package/dist/adapters/mcp-adapter.d.ts +32 -0
  34. package/dist/adapters/mcp-adapter.js +357 -0
  35. package/dist/adapters/mcp-target-policy.d.ts +39 -0
  36. package/dist/adapters/mcp-target-policy.js +30 -0
  37. package/dist/cli/render.d.ts +8 -3
  38. package/dist/cli/render.js +13 -6
  39. package/dist/cli/run.js +2 -1
  40. package/dist/core/compile/bindings.d.ts +8 -18
  41. package/dist/core/compile/bindings.js +13 -10
  42. package/dist/core/compile/compile.js +6 -1
  43. package/dist/core/compile/interface-inventory.d.ts +81 -3
  44. package/dist/core/compile/interface-inventory.js +134 -23
  45. package/dist/core/compile/reachability.d.ts +73 -1
  46. package/dist/core/compile/reachability.js +134 -19
  47. package/dist/core/compile/schema-version.d.ts +15 -2
  48. package/dist/core/compile/schema-version.js +11 -3
  49. package/dist/core/compile/sensitivity-witness.d.ts +22 -10
  50. package/dist/core/compile/sensitivity-witness.js +101 -13
  51. package/dist/core/coverage/operations.d.ts +1 -1
  52. package/dist/core/coverage/operations.js +1 -1
  53. package/dist/core/coverage/relevance.d.ts +3 -3
  54. package/dist/core/coverage/relevance.js +3 -3
  55. package/dist/core/declared-inputs.d.ts +21 -9
  56. package/dist/core/declared-inputs.js +51 -16
  57. package/dist/core/evaluate/evidence-resolution.d.ts +8 -7
  58. package/dist/core/evaluate/evidence-resolution.js +25 -18
  59. package/dist/core/preflight/plan.js +43 -6
  60. package/dist/core/preflight/projection.d.ts +10 -1
  61. package/dist/core/preflight/projection.js +9 -5
  62. package/dist/core/preflight/reduce.js +6 -2
  63. package/dist/core/preflight/witness-evidence.js +31 -9
  64. package/dist/core/schemas/artifact.d.ts +145 -49
  65. package/dist/core/schemas/defect-signature.d.ts +280 -23
  66. package/dist/core/schemas/defect-signature.js +65 -37
  67. package/dist/core/schemas/eval-contract.d.ts +33 -48
  68. package/dist/core/schemas/eval-contract.js +3 -3
  69. package/dist/core/schemas/interface.d.ts +193 -55
  70. package/dist/core/schemas/interface.js +82 -15
  71. package/dist/core/schemas/plan.d.ts +45 -1
  72. package/dist/core/schemas/plan.js +13 -2
  73. package/dist/core/schemas/pointer.d.ts +23 -9
  74. package/dist/core/schemas/pointer.js +25 -11
  75. package/dist/core/schemas/port-messages.d.ts +81 -0
  76. package/dist/core/schemas/port-messages.js +50 -3
  77. package/dist/core/schemas/primitives.d.ts +18 -0
  78. package/dist/core/schemas/primitives.js +29 -0
  79. package/dist/core/schemas/probe-policy.d.ts +41 -0
  80. package/dist/core/schemas/probe-policy.js +61 -1
  81. package/dist/core/schemas/probe.d.ts +156 -2
  82. package/dist/core/schemas/probe.js +45 -2
  83. package/dist/core/schemas/sealed-run-record.d.ts +11 -5
  84. package/dist/core/schemas/sealed-run-record.js +21 -9
  85. package/dist/core/schemas/sensitivity-witness.d.ts +31 -7
  86. package/dist/core/schemas/sensitivity-witness.js +32 -9
  87. package/dist/core/score/bindings.d.ts +1 -1
  88. package/dist/core/score/bindings.js +4 -4
  89. package/dist/core/score/qualification.d.ts +7 -5
  90. package/dist/core/score/qualification.js +92 -23
  91. package/dist/core/score/score.d.ts +1 -1
  92. package/dist/core/score/score.js +24 -1
  93. package/dist/core/seal/derived-reference.js +20 -17
  94. package/dist/core/seal/plan-index.d.ts +18 -9
  95. package/dist/core/seal/plan-index.js +79 -37
  96. package/dist/index.d.ts +1 -1
  97. package/dist/index.js +1 -1
  98. package/dist/ports/environment-probe-port.d.ts +50 -17
  99. package/dist/ports/environment-probe-port.js +26 -17
  100. package/dist/testing/conformance.d.ts +3 -2
  101. package/dist/testing/conformance.js +2 -1
  102. package/dist/testing/index.d.ts +3 -3
  103. package/dist/testing/index.js +1 -1
  104. package/dist/testing/probe-conformance.d.ts +90 -13
  105. package/dist/testing/probe-conformance.js +375 -160
  106. package/package.json +4 -2
  107. package/schemas/eval-contract.schema.json +571 -18
  108. package/schemas/probe.schema.json +152 -12
  109. package/schemas/rubric.schema.json +1 -1
  110. package/schemas/sealed-run-record.schema.json +24 -17
@@ -1,5 +1,5 @@
1
1
  /** resolves a pointer to its step and operation; nothing about reachability. */
2
- import type { AnyOperation, CommandOperation, InterfaceKindName, Operation, PermittedInterface } from '../schemas/interface.ts';
2
+ import type { AnyOperation, CommandOperation, InterfaceKindName, McpOperation, Operation, PermittedInterface } from '../schemas/interface.ts';
3
3
  import type { InteractionStep } from '../schemas/plan.ts';
4
4
  import { type EvidenceChannelName, type InputChannelName } from '../schemas/pointer.ts';
5
5
  /**
@@ -17,13 +17,13 @@ export declare const decodeTail: (tailSource: string) => readonly string[];
17
17
  export type EvidenceTarget = {
18
18
  stepId: string;
19
19
  channel: EvidenceChannelName;
20
- transportChannel: InputChannelName | null;
20
+ inputChannel: InputChannelName | null;
21
21
  artifactId: string | null;
22
22
  tail: readonly string[];
23
23
  };
24
24
  /**
25
25
  * Parses one `InteractionPointer` string (`INTERACTION_POINTER_PATTERN` in
26
- * `pointer.ts`) into its step id, channel, transport channel, and tail, using
26
+ * `pointer.ts`) into its step id, channel, input channel, and tail, using
27
27
  * the schema's own channel partition so this accepts exactly what
28
28
  * `InteractionPointer.safeParse` accepts. A should-never-happen precondition
29
29
  * violation throws `TypeError`, per `digest.ts`'s precedent.
@@ -38,14 +38,15 @@ export declare function parseEvidenceTarget(pointer: string): EvidenceTarget;
38
38
  *
39
39
  * `operationOf` stays narrow on purpose. It hands a resolved `Operation` to
40
40
  * every downstream consumer, so widening its return type to the operation
41
- * union would retype seventeen files at once. A command operation resolves to
42
- * `undefined` from it and to a value from `commandOperationOf`, and each
43
- * caller that has to branch reads the declaring kind first.
41
+ * union would retype seventeen files at once. A command operation and a tool
42
+ * call each resolve to `undefined` from it and to a value from their own
43
+ * accessor, and each caller that has to branch reads the declaring kind first.
44
44
  */
45
45
  export type PlanIndex = {
46
46
  stepOf: (stepId: string) => InteractionStep | undefined;
47
47
  operationOf: (operationId: string) => Operation | undefined;
48
48
  commandOperationOf: (operationId: string) => CommandOperation | undefined;
49
+ mcpOperationOf: (operationId: string) => McpOperation | undefined;
49
50
  interfaceKindOf: (operationId: string) => InterfaceKindName | undefined;
50
51
  stepsUsing: (operationId: string) => readonly InteractionStep[];
51
52
  };
@@ -70,9 +71,17 @@ export declare function buildPlanIndex(interactionPlan: readonly InteractionStep
70
71
  export declare function resolveStep(index: PlanIndex, stepId: string): InteractionStep;
71
72
  /**
72
73
  * The declared operation of whichever kind, for the callers that read only
73
- * fields both shapes carry. Callers reading a kind-specific field ask
74
+ * fields all three shapes carry. Callers reading a kind-specific field ask
74
75
  * `interfaceKindOf` first and then take the matching accessor.
75
76
  */
76
77
  export declare const anyOperationOf: (index: PlanIndex, operationId: string) => AnyOperation | undefined;
77
- /** Resolves an operation id through the index or throws. See `resolveStep`. */
78
- export declare function resolveOperation(index: PlanIndex, operationId: string): Operation;
78
+ /**
79
+ * Resolves an operation id through the index or throws. See `resolveStep`.
80
+ *
81
+ * Reads all three maps. It read `operationOf` alone, so it threw for a command
82
+ * operation and then for a tool call with the message "the permitted interfaces
83
+ * do not declare it", which is false: they declare it, and a different accessor
84
+ * holds it. Callers narrow with the operation predicates in
85
+ * `core/declared-inputs.ts`.
86
+ */
87
+ export declare function resolveOperation(index: PlanIndex, operationId: string): AnyOperation;
@@ -1,5 +1,4 @@
1
- import { operationsOf } from '../schemas/interface.js';
2
- import { IDENTIFIER_ROOTED_CHANNEL, INPUT_CHANNELS, SCALAR_CHANNELS, TAIL_BEARING_CHANNELS, TRANSPORT_ROOTED_CHANNEL, } from '../schemas/pointer.js';
1
+ import { IDENTIFIER_ROOTED_CHANNEL, INPUT_CHANNELS, INPUT_ROOTED_CHANNEL, SCALAR_CHANNELS, TAIL_BEARING_CHANNELS, } from '../schemas/pointer.js';
3
2
  import { IDENTIFIER_CHARSET_SOURCE } from '../schemas/primitives.js';
4
3
  // An RFC 6901 reference token, respelled here because pointer.ts keeps its
5
4
  // own TOKEN/TAIL fragments private. IDENTIFIER_CHARSET_SOURCE and the channel
@@ -15,7 +14,7 @@ const alternation = (members) => members.join('|');
15
14
  // Named capture groups replace positional indices, avoiding a fragile
16
15
  // dependency on `IDENTIFIER_CHARSET_SOURCE` staying free of its own capturing
17
16
  // groups.
18
- const EVIDENCE_TARGET_PATTERN = new RegExp(`^/interactions/(?<stepId>${IDENTIFIER_CHARSET_SOURCE})/(?:(?<tailBearingChannel>${alternation(TAIL_BEARING_CHANNELS)})(?<tailBearingTail>${TAIL_SOURCE})|(?<scalarChannel>${alternation(SCALAR_CHANNELS)})|${TRANSPORT_ROOTED_CHANNEL}/(?<transportChannel>${alternation(INPUT_CHANNELS)})(?<callInputsTail>${TAIL_SOURCE})|${IDENTIFIER_ROOTED_CHANNEL}/(?<artifactId>${IDENTIFIER_CHARSET_SOURCE})(?<artifactTail>${TAIL_SOURCE}))$`);
17
+ const EVIDENCE_TARGET_PATTERN = new RegExp(`^/interactions/(?<stepId>${IDENTIFIER_CHARSET_SOURCE})/(?:(?<tailBearingChannel>${alternation(TAIL_BEARING_CHANNELS)})(?<tailBearingTail>${TAIL_SOURCE})|(?<scalarChannel>${alternation(SCALAR_CHANNELS)})|${INPUT_ROOTED_CHANNEL}/(?<inputChannel>${alternation(INPUT_CHANNELS)})(?<callInputsTail>${TAIL_SOURCE})|${IDENTIFIER_ROOTED_CHANNEL}/(?<artifactId>${IDENTIFIER_CHARSET_SOURCE})(?<artifactTail>${TAIL_SOURCE}))$`);
19
18
  const isEvidenceChannel = (value) => TAIL_BEARING_CHANNELS.includes(value) ||
20
19
  SCALAR_CHANNELS.includes(value);
21
20
  const isInputChannel = (value) => INPUT_CHANNELS.includes(value);
@@ -28,7 +27,7 @@ export const decodeToken = (token) => token.replace(/~1/g, '/').replace(/~0/g, '
28
27
  export const decodeTail = (tailSource) => tailSource === '' ? [] : tailSource.slice(1).split('/').map(decodeToken);
29
28
  /**
30
29
  * Parses one `InteractionPointer` string (`INTERACTION_POINTER_PATTERN` in
31
- * `pointer.ts`) into its step id, channel, transport channel, and tail, using
30
+ * `pointer.ts`) into its step id, channel, input channel, and tail, using
32
31
  * the schema's own channel partition so this accepts exactly what
33
32
  * `InteractionPointer.safeParse` accepts. A should-never-happen precondition
34
33
  * violation throws `TypeError`, per `digest.ts`'s precedent.
@@ -47,7 +46,7 @@ export function parseEvidenceTarget(pointer) {
47
46
  return {
48
47
  stepId,
49
48
  channel: groups.scalarChannel,
50
- transportChannel: null,
49
+ inputChannel: null,
51
50
  artifactId: null,
52
51
  tail: [],
53
52
  };
@@ -60,20 +59,20 @@ export function parseEvidenceTarget(pointer) {
60
59
  return {
61
60
  stepId,
62
61
  channel: groups.tailBearingChannel,
63
- transportChannel: null,
62
+ inputChannel: null,
64
63
  artifactId: null,
65
64
  tail: decodeTail(groups.tailBearingTail ?? ''),
66
65
  };
67
66
  }
68
- if (groups.transportChannel !== undefined) {
69
- if (!isInputChannel(groups.transportChannel)) {
67
+ if (groups.inputChannel !== undefined) {
68
+ if (!isInputChannel(groups.inputChannel)) {
70
69
  // Unreachable: INPUT_CHANNELS is exactly what this group can match.
71
- throw new TypeError(`call-inputs evidence target names no transport channel: ${pointer}`);
70
+ throw new TypeError(`call-inputs evidence target names no input channel: ${pointer}`);
72
71
  }
73
72
  return {
74
73
  stepId,
75
74
  channel: 'call-inputs',
76
- transportChannel: groups.transportChannel,
75
+ inputChannel: groups.inputChannel,
77
76
  artifactId: null,
78
77
  tail: decodeTail(groups.callInputsTail ?? ''),
79
78
  };
@@ -82,7 +81,7 @@ export function parseEvidenceTarget(pointer) {
82
81
  return {
83
82
  stepId,
84
83
  channel: IDENTIFIER_ROOTED_CHANNEL,
85
- transportChannel: null,
84
+ inputChannel: null,
86
85
  artifactId: groups.artifactId,
87
86
  tail: decodeTail(groups.artifactTail ?? ''),
88
87
  };
@@ -123,40 +122,73 @@ export function buildPlanIndex(interactionPlan, permittedInterfaces, options = {
123
122
  }
124
123
  const operations = new Map();
125
124
  const commandOperations = new Map();
125
+ const mcpOperations = new Map();
126
126
  const kinds = new Map();
127
127
  const duplicateOperationIds = new Set();
128
+ /**
129
+ * Records one operation id against the kind that declared it and answers
130
+ * whether the caller may store the operation. An id two permitted
131
+ * interfaces both declare is removed from every map instead of being
132
+ * resolved by array order, so `operationOf` and its two siblings answer
133
+ * `undefined` for it. One closure serves all three arms: the bookkeeping is
134
+ * the same for every kind and only the destination map differs.
135
+ */
136
+ const claim = (operationId, kind) => {
137
+ if (kinds.has(operationId) || duplicateOperationIds.has(operationId)) {
138
+ if (duplicateIds === 'throw') {
139
+ throw new TypeError(`duplicate operation id across permitted interfaces: ${operationId}`);
140
+ }
141
+ operations.delete(operationId);
142
+ commandOperations.delete(operationId);
143
+ mcpOperations.delete(operationId);
144
+ kinds.delete(operationId);
145
+ duplicateOperationIds.add(operationId);
146
+ return false;
147
+ }
148
+ kinds.set(operationId, kind);
149
+ return true;
150
+ };
128
151
  for (const iface of permittedInterfaces) {
129
- for (const operation of operationsOf(iface)) {
130
- if (kinds.has(operation.operationId) ||
131
- duplicateOperationIds.has(operation.operationId)) {
132
- if (duplicateIds === 'throw') {
133
- throw new TypeError(`duplicate operation id across permitted interfaces: ${operation.operationId}`);
152
+ // Narrowed on the interface's own kind before its operations are read.
153
+ // Each branch of `PermittedInterface` declares its own `operations`
154
+ // element type, so an arm that narrowed first iterates
155
+ // `CommandOperation`, `McpOperation` or `Operation` and stores it with
156
+ // no cast. Widening through `operationsOf` first discards that
157
+ // correlation, and no later kind test recovers it. `web` shares the api
158
+ // map: it carries the api operation shape and every consumer of a
159
+ // resolved operation reads the same declared fields off both.
160
+ switch (iface.kind) {
161
+ case 'cli':
162
+ for (const operation of iface.operations) {
163
+ if (claim(operation.operationId, iface.kind))
164
+ commandOperations.set(operation.operationId, operation);
134
165
  }
135
- operations.delete(operation.operationId);
136
- commandOperations.delete(operation.operationId);
137
- kinds.delete(operation.operationId);
138
- duplicateOperationIds.add(operation.operationId);
139
- }
140
- else {
141
- kinds.set(operation.operationId, iface.kind);
142
- // Sorted into the two maps by the interface's own kind rather
143
- // than by probing the operation for a field: `web` and `mcp`
144
- // carry the api operation shape and belong in the same map as
145
- // `api`, since every consumer of a resolved operation reads
146
- // the same declared fields off all three.
147
- if (iface.kind === 'cli') {
148
- commandOperations.set(operation.operationId, operation);
166
+ continue;
167
+ case 'mcp':
168
+ for (const operation of iface.operations) {
169
+ if (claim(operation.operationId, iface.kind))
170
+ mcpOperations.set(operation.operationId, operation);
149
171
  }
150
- else {
151
- operations.set(operation.operationId, operation);
172
+ continue;
173
+ case 'api':
174
+ case 'web':
175
+ for (const operation of iface.operations) {
176
+ if (claim(operation.operationId, iface.kind))
177
+ operations.set(operation.operationId, operation);
152
178
  }
153
- }
179
+ continue;
154
180
  }
181
+ // Reachable only once a fifth kind joins the union, and then `iface` is
182
+ // no longer `never` and this line fails the typecheck. That is the
183
+ // forcing function the three hand-narrowed casts did not have: a kind
184
+ // with no arm above was sorted into the api map by exhaustion.
185
+ iface;
155
186
  }
156
187
  return {
157
188
  stepOf: (stepId) => steps.get(stepId),
158
189
  operationOf: (operationId) => operations.get(operationId),
159
190
  commandOperationOf: (operationId) => commandOperations.get(operationId),
191
+ mcpOperationOf: (operationId) => mcpOperations.get(operationId),
160
192
  interfaceKindOf: (operationId) => kinds.get(operationId),
161
193
  stepsUsing: (operationId) => stepsByOperation.get(operationId) ?? [],
162
194
  };
@@ -177,13 +209,23 @@ export function resolveStep(index, stepId) {
177
209
  }
178
210
  /**
179
211
  * The declared operation of whichever kind, for the callers that read only
180
- * fields both shapes carry. Callers reading a kind-specific field ask
212
+ * fields all three shapes carry. Callers reading a kind-specific field ask
181
213
  * `interfaceKindOf` first and then take the matching accessor.
182
214
  */
183
- export const anyOperationOf = (index, operationId) => index.operationOf(operationId) ?? index.commandOperationOf(operationId);
184
- /** Resolves an operation id through the index or throws. See `resolveStep`. */
215
+ export const anyOperationOf = (index, operationId) => index.operationOf(operationId) ??
216
+ index.commandOperationOf(operationId) ??
217
+ index.mcpOperationOf(operationId);
218
+ /**
219
+ * Resolves an operation id through the index or throws. See `resolveStep`.
220
+ *
221
+ * Reads all three maps. It read `operationOf` alone, so it threw for a command
222
+ * operation and then for a tool call with the message "the permitted interfaces
223
+ * do not declare it", which is false: they declare it, and a different accessor
224
+ * holds it. Callers narrow with the operation predicates in
225
+ * `core/declared-inputs.ts`.
226
+ */
185
227
  export function resolveOperation(index, operationId) {
186
- const operation = index.operationOf(operationId);
228
+ const operation = anyOperationOf(index, operationId);
187
229
  if (operation === undefined) {
188
230
  throw new TypeError(`step names an operation the permitted interfaces do not declare: ${operationId}`);
189
231
  }
package/dist/index.d.ts CHANGED
@@ -12,4 +12,4 @@ export type { ScoringPolicy } from './core/schemas/scoring-policy.ts';
12
12
  export type { SealedEvaluatorBrief } from './core/schemas/sealed-evaluator-brief.ts';
13
13
  export type { SealedRunRecord } from './core/schemas/sealed-run-record.ts';
14
14
  export type { FixtureReset, ManifestationWitness, SensitivityWitness, SensitivityWitnessLeg, WitnessChannel, WitnessInputs, } from './core/schemas/sensitivity-witness.ts';
15
- export declare const VERSION = "1.4.2";
15
+ export declare const VERSION = "3.0.0";
package/dist/index.js CHANGED
@@ -19,4 +19,4 @@
19
19
  // subpath, where AD-37 puts the conformance definition an adapter author
20
20
  // reads; the reference adapters stay at `eval-quality/adapters`.
21
21
  export * from './application/index.js';
22
- export const VERSION = '1.4.2';
22
+ export const VERSION = '3.0.0';
@@ -4,25 +4,34 @@
4
4
  * identifier to an authorized target is the adapter's job, from configuration
5
5
  * outside the contract.
6
6
  *
7
- * Four rules an implementation MUST follow. Prose here because `ports/` holds
8
- * no logic; `src/testing/probe-conformance.ts` is the executable half.
7
+ * Four rules an implementation MUST follow, whichever mechanism it speaks.
8
+ * Prose here because `ports/` holds no logic; `src/testing/probe-conformance.ts`
9
+ * is the executable half.
9
10
  *
10
- * 1. Apply the policy before any network call, and again to every redirect
11
- * target.
12
- * 2. Issue the request against the address the policy validated
13
- * (`canonicalAddress`), keep the original host in the `Host` header, and
14
- * verify TLS against that host. Never re-resolve a hostname after
15
- * validation: re-resolving between the check and the connection is the
16
- * classic way past an allowlist like this one. Where a host resolves to
17
- * several addresses, validate each and connect only to a validated one.
11
+ * 1. Apply the policy before anything reaches the system under test, and again
12
+ * to every target the first one leads to. On HTTP that second clause is
13
+ * every redirect; on a mechanism that starts a process it is every process
14
+ * the authorization causes to run.
15
+ * 2. Act on exactly what the policy validated, and revalidate nothing away.
16
+ * Over HTTP: issue the request against the validated address
17
+ * (`canonicalAddress`), keep the original host in the `Host` header, verify
18
+ * TLS against that host, and never re-resolve a hostname after validation,
19
+ * since re-resolving between the check and the connection is the classic
20
+ * way past an allowlist like this one; where a host resolves to several
21
+ * addresses, validate each and connect only to a validated one. Over a
22
+ * mechanism that starts a process: spawn the authorized target with an argv
23
+ * array and never through a shell, so no declared value can become a
24
+ * metacharacter, and pass only the environment the mapping declares.
18
25
  * 3. A policy denial throws `forbidden-target`; a cap throws
19
- * `budget-exhausted`; an abort throws `aborted`; a transport failure throws
20
- * `port-failure`.
21
- * 4. Every response the server returns is an observation, at any status. A 4xx
22
- * or 5xx resolves to a schema-valid `ProbeObservation`, since AD-10's
23
- * "every declared seeded fault being observed to fire" reads the status as
24
- * payload. Throw on a non-2xx and a seeded fault goes invisible, which
25
- * makes the whole pre-flight vacuous.
26
+ * `budget-exhausted`; an abort throws `aborted`; a failure to reach the
27
+ * system at all throws `port-failure`.
28
+ * 4. Every answer the system returns is an observation, whatever it says. A
29
+ * 4xx or 5xx, a non-zero exit, and a tool result flagged as an error all
30
+ * resolve to a schema-valid `ProbeObservation`, since AD-10's "every
31
+ * declared seeded fault being observed to fire" reads the answer as
32
+ * payload. Throw on one of those and a seeded fault goes invisible, which
33
+ * makes the whole pre-flight vacuous. What throws under rule 3 is the case
34
+ * where nothing answered.
26
35
  */
27
36
  import { ProbeObservation, ProbeRequest } from '../core/schemas/port-messages.ts';
28
37
  import type { PortMethod } from './port.ts';
@@ -78,6 +87,15 @@ export declare const probeParsers: {
78
87
  kind: import("zod").ZodLiteral<"absent">;
79
88
  }, import("zod/v4/core").$strict>], "kind">;
80
89
  }, import("zod/v4/core").$strict>;
90
+ }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
91
+ probeId: import("zod").ZodString;
92
+ interfaceId: import("zod").ZodString;
93
+ operationId: import("zod").ZodString;
94
+ kind: import("zod").ZodLiteral<"mcp">;
95
+ toolName: import("zod").ZodString;
96
+ channels: import("zod").ZodObject<{
97
+ arguments: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodType<import("../core/schemas/primitives.ts").JsonValue, unknown, import("zod/v4/core").$ZodTypeInternals<import("../core/schemas/primitives.ts").JsonValue, unknown>>>;
98
+ }, import("zod/v4/core").$strict>;
81
99
  }, import("zod/v4/core").$strict>], "kind">;
82
100
  readonly response: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
83
101
  probeId: import("zod").ZodString;
@@ -128,5 +146,20 @@ export declare const probeParsers: {
128
146
  }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
129
147
  kind: import("zod").ZodLiteral<"absent">;
130
148
  }, import("zod/v4/core").$strict>], "kind">>;
149
+ }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
150
+ probeId: import("zod").ZodString;
151
+ interfaceId: import("zod").ZodString;
152
+ operationId: import("zod").ZodString;
153
+ kind: import("zod").ZodLiteral<"mcp">;
154
+ isError: import("zod").ZodBoolean;
155
+ result: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
156
+ kind: import("zod").ZodLiteral<"json">;
157
+ value: import("zod").ZodType<import("../core/schemas/primitives.ts").JsonValue, unknown, import("zod/v4/core").$ZodTypeInternals<import("../core/schemas/primitives.ts").JsonValue, unknown>>;
158
+ }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
159
+ kind: import("zod").ZodLiteral<"text">;
160
+ value: import("zod").ZodString;
161
+ }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
162
+ kind: import("zod").ZodLiteral<"absent">;
163
+ }, import("zod/v4/core").$strict>], "kind">;
131
164
  }, import("zod/v4/core").$strict>], "kind">;
132
165
  };
@@ -4,25 +4,34 @@
4
4
  * identifier to an authorized target is the adapter's job, from configuration
5
5
  * outside the contract.
6
6
  *
7
- * Four rules an implementation MUST follow. Prose here because `ports/` holds
8
- * no logic; `src/testing/probe-conformance.ts` is the executable half.
7
+ * Four rules an implementation MUST follow, whichever mechanism it speaks.
8
+ * Prose here because `ports/` holds no logic; `src/testing/probe-conformance.ts`
9
+ * is the executable half.
9
10
  *
10
- * 1. Apply the policy before any network call, and again to every redirect
11
- * target.
12
- * 2. Issue the request against the address the policy validated
13
- * (`canonicalAddress`), keep the original host in the `Host` header, and
14
- * verify TLS against that host. Never re-resolve a hostname after
15
- * validation: re-resolving between the check and the connection is the
16
- * classic way past an allowlist like this one. Where a host resolves to
17
- * several addresses, validate each and connect only to a validated one.
11
+ * 1. Apply the policy before anything reaches the system under test, and again
12
+ * to every target the first one leads to. On HTTP that second clause is
13
+ * every redirect; on a mechanism that starts a process it is every process
14
+ * the authorization causes to run.
15
+ * 2. Act on exactly what the policy validated, and revalidate nothing away.
16
+ * Over HTTP: issue the request against the validated address
17
+ * (`canonicalAddress`), keep the original host in the `Host` header, verify
18
+ * TLS against that host, and never re-resolve a hostname after validation,
19
+ * since re-resolving between the check and the connection is the classic
20
+ * way past an allowlist like this one; where a host resolves to several
21
+ * addresses, validate each and connect only to a validated one. Over a
22
+ * mechanism that starts a process: spawn the authorized target with an argv
23
+ * array and never through a shell, so no declared value can become a
24
+ * metacharacter, and pass only the environment the mapping declares.
18
25
  * 3. A policy denial throws `forbidden-target`; a cap throws
19
- * `budget-exhausted`; an abort throws `aborted`; a transport failure throws
20
- * `port-failure`.
21
- * 4. Every response the server returns is an observation, at any status. A 4xx
22
- * or 5xx resolves to a schema-valid `ProbeObservation`, since AD-10's
23
- * "every declared seeded fault being observed to fire" reads the status as
24
- * payload. Throw on a non-2xx and a seeded fault goes invisible, which
25
- * makes the whole pre-flight vacuous.
26
+ * `budget-exhausted`; an abort throws `aborted`; a failure to reach the
27
+ * system at all throws `port-failure`.
28
+ * 4. Every answer the system returns is an observation, whatever it says. A
29
+ * 4xx or 5xx, a non-zero exit, and a tool result flagged as an error all
30
+ * resolve to a schema-valid `ProbeObservation`, since AD-10's "every
31
+ * declared seeded fault being observed to fire" reads the answer as
32
+ * payload. Throw on one of those and a seeded fault goes invisible, which
33
+ * makes the whole pre-flight vacuous. What throws under rule 3 is the case
34
+ * where nothing answered.
26
35
  */
27
36
  import { ProbeObservation, ProbeRequest, } from '../core/schemas/port-messages.js';
28
37
  /** the boundary parsers `application/` and the conformance suite validate with. */
@@ -7,14 +7,15 @@ export type ConformanceOutcome = {
7
7
  readonly passed: boolean;
8
8
  readonly detail: string;
9
9
  };
10
- export type ConformancePort = 'corpus' | 'clock' | 'file-system' | 'environment-probe' | 'command-probe';
10
+ export type ConformancePort = 'corpus' | 'clock' | 'file-system' | 'environment-probe' | 'command-probe' | 'mcp-probe';
11
11
  /** how many outcomes a complete run of each port produces. Asserted as literals by fixture 58. */
12
12
  export declare const CONFORMANCE_OUTCOME_COUNTS: {
13
13
  readonly corpus: 6;
14
14
  readonly clock: 6;
15
15
  readonly 'file-system': 12;
16
16
  readonly 'environment-probe': 19;
17
- readonly 'command-probe': 15;
17
+ readonly 'command-probe': 16;
18
+ readonly 'mcp-probe': 14;
18
19
  };
19
20
  export type ConformanceReport = {
20
21
  readonly subject: string;
@@ -19,7 +19,8 @@ export const CONFORMANCE_OUTCOME_COUNTS = {
19
19
  clock: 6,
20
20
  'file-system': 12,
21
21
  'environment-probe': 19,
22
- 'command-probe': 15,
22
+ 'command-probe': 16,
23
+ 'mcp-probe': 14,
23
24
  };
24
25
  /** "Promptly" with no bound is unfalsifiable. One second clears any real adapter's abort latency and sits well inside a default test timeout. */
25
26
  export const DEFAULT_ABORT_BUDGET_MS = 1000;
@@ -13,7 +13,7 @@
13
13
  export type { RuntimeFaultCode } from '../core/schemas/faults.ts';
14
14
  export { RUNTIME_FAULT_CODES, RuntimeFault } from '../core/schemas/faults.ts';
15
15
  export type { ClockReadRequest, ClockReadResponse, CorpusResolveRequest, CorpusResolveResponse, FileReadRequest, FileReadResponse, FileWriteRequest, FileWriteResponse, ProbeObservation, ProbeObservedBody, ProbeRequest, ProbeRequestBody, } from '../core/schemas/port-messages.ts';
16
- export type { CommandTargetAuthorization, CommandTargetPolicy, ProbeTargetAuthorization, ProbeTargetPolicy, } from '../core/schemas/probe-policy.ts';
16
+ export type { CommandTargetAuthorization, CommandTargetPolicy, McpTargetAuthorization, McpTargetPolicy, ProbeTargetAuthorization, ProbeTargetPolicy, } from '../core/schemas/probe-policy.ts';
17
17
  export type { ClockPort } from '../ports/clock-port.ts';
18
18
  export { clockReadParsers } from '../ports/clock-port.ts';
19
19
  export type { CorpusPort } from '../ports/corpus-port.ts';
@@ -24,5 +24,5 @@ export type { FileSystemPort } from '../ports/file-system-port.ts';
24
24
  export { fileReadParsers, fileWriteParsers, } from '../ports/file-system-port.ts';
25
25
  export type { BuiltSubject, ConformanceOutcome, ConformancePort, ConformanceReport, PortSubject, ScenarioKind, } from './conformance.ts';
26
26
  export { CONFORMANCE_OUTCOME_COUNTS, formatConformanceReport, runClockPortConformance, runCorpusPortConformance, runFileSystemPortConformance, } from './conformance.ts';
27
- export type { CommandProbeSubject, ProbeSubject } from './probe-conformance.ts';
28
- export { runCommandLineProbeConformance, runEnvironmentProbePortConformance, } from './probe-conformance.ts';
27
+ export type { CommandProbeSubject, McpProbeSubject, ProbeSubject, } from './probe-conformance.ts';
28
+ export { runCommandLineProbeConformance, runEnvironmentProbePortConformance, runMcpProbeConformance, } from './probe-conformance.ts';
@@ -16,4 +16,4 @@ export { corpusResolveParsers } from '../ports/corpus-port.js';
16
16
  export { probeParsers } from '../ports/environment-probe-port.js';
17
17
  export { fileReadParsers, fileWriteParsers, } from '../ports/file-system-port.js';
18
18
  export { CONFORMANCE_OUTCOME_COUNTS, formatConformanceReport, runClockPortConformance, runCorpusPortConformance, runFileSystemPortConformance, } from './conformance.js';
19
- export { runCommandLineProbeConformance, runEnvironmentProbePortConformance, } from './probe-conformance.js';
19
+ export { runCommandLineProbeConformance, runEnvironmentProbePortConformance, runMcpProbeConformance, } from './probe-conformance.js';
@@ -4,24 +4,44 @@
4
4
  * since only it knows how its own interface-to-target mapping is wired.
5
5
  *
6
6
  * `runEnvironmentProbePortConformance` is the `api` arm: thirteen assertions,
7
- * every scenario HTTP (redirects, methods, schemes, an anomalous status).
8
- * `runCommandLineProbeConformance` is the `cli` arm, added once
9
- * `CommandTargetPolicy` gave the mechanism something to authorize: an
10
- * unmapped interface, an unmapped executable, an unauthorized subcommand path,
11
- * a non-zero exit read as an observation, a shell-metacharacter argument
12
- * proven to reach the process as one literal token rather than a shell
13
- * expansion, a declared artifact captured, and both caps enforced. The two
14
- * arms are separate functions rather than one, because their subjects need
7
+ * every scenario HTTP (an authorized target reached, an anomalous status read
8
+ * as an observation, an unmapped interface, four denied address classes, a
9
+ * method, a scheme, a redirect revalidated and refused, and three caps).
10
+ * `runCommandLineProbeConformance` is the `cli` arm: ten, over an authorized
11
+ * invocation, a non-zero exit read as an observation, an unmapped interface, an
12
+ * unmapped executable, an unauthorized subcommand path, an unpermitted
13
+ * environment key, a shell-metacharacter argument proven to reach the process
14
+ * as one literal token rather than a shell expansion, a declared artifact
15
+ * captured, and both caps.
16
+ * `runMcpProbeConformance` is the `mcp` arm: eight, over an authorized tool
17
+ * call, a tool-reported error read as an observation, an unmapped interface, an
18
+ * unauthorized tool, a declared argument proven to cross the JSON-RPC frame
19
+ * unchanged, the structured result the operation's descriptor describes, and
20
+ * both caps.
21
+ *
22
+ * Every field an authorization scopes owes a denial, which is the largest term
23
+ * in the three counts: an HTTP authorization scopes the interface, four
24
+ * address classes, the method and the scheme; a command authorization the
25
+ * interface, the executable, the subcommand path and the permitted environment
26
+ * keys; a tool-server authorization the interface and the tool. Each arm then adds the caps its
27
+ * mechanism can be made to exceed and the answers it has to read as
28
+ * observations.
29
+ *
30
+ * The arms are separate functions rather than one, because their subjects need
15
31
  * disjoint fixtures (an HTTP redirect chain has no command analogue, and a
16
- * subcommand allowlist has no HTTP one) and a subject presenting for one
17
- * mechanism is not asked to fake the other's scenarios.
32
+ * subcommand allowlist has no tool-call one) and a subject presenting for one
33
+ * mechanism is not asked to fake another's scenarios. What they do share is
34
+ * the machinery below `ArmAssertion`: one assertion shape generic over the
35
+ * subject type, one runner, one resolve-side checker and one call-count
36
+ * checker, so a fourth mechanism writes its subject and its list and nothing
37
+ * else.
18
38
  *
19
- * Each assertion in either arm builds its own `'resolves'` subject and reads
39
+ * Each assertion in every arm builds its own `'resolves'` subject and reads
20
40
  * `underlyingCalls()` from a counter starting at zero, so every count below is
21
41
  * absolute.
22
42
  */
23
43
  import type { ProbeRequest } from '../core/schemas/port-messages.ts';
24
- import type { CommandTargetPolicy, ProbeTargetPolicy } from '../core/schemas/probe-policy.ts';
44
+ import type { CommandTargetPolicy, McpTargetPolicy, ProbeTargetPolicy } from '../core/schemas/probe-policy.ts';
25
45
  import type { ConformanceReport, PortSubject } from './conformance.ts';
26
46
  export type ProbeSubject = PortSubject<ProbeRequest> & {
27
47
  readonly policy: ProbeTargetPolicy;
@@ -72,6 +92,8 @@ export type CommandProbeSubject = PortSubject<ProbeRequest> & {
72
92
  readonly unmappedExecutableRequest: ProbeRequest;
73
93
  /** an interface-executable pair the policy names, with a subcommandPath no authorization for it permits. */
74
94
  readonly unauthorizedSubcommandRequest: ProbeRequest;
95
+ /** authorized in every other respect, declaring one environment key its authorization's permittedEnvironmentKeys omits. */
96
+ readonly unauthorizedEnvironmentKeyRequest: ProbeRequest;
75
97
  /** authorized, and answered by the fixture exiting non-zero. */
76
98
  readonly nonZeroExitRequest: ProbeRequest;
77
99
  /** authorized, carrying a shell-metacharacter value on one argument channel key. */
@@ -88,9 +110,64 @@ export type CommandProbeSubject = PortSubject<ProbeRequest> & {
88
110
  readonly overOutputRequest: ProbeRequest;
89
111
  };
90
112
  /**
91
- * Fifteen outcomes: the six shared assertions plus the nine above. Unlike the
113
+ * Sixteen outcomes: the six shared assertions plus the ten above. Unlike the
92
114
  * `api` arm, every cap here has an assertion: a command subject's fixture
93
115
  * script can be told to overrun a byte cap directly, with no oversize-request
94
116
  * problem to work around.
95
117
  */
96
118
  export declare function runCommandLineProbeConformance(subject: CommandProbeSubject): Promise<ConformanceReport>;
119
+ /**
120
+ * The `mcp` arm's subject, on the division the other two draw: the subject
121
+ * knows how its own mapping is wired and what its fixture server answers, the
122
+ * suite only knows what each request should produce.
123
+ *
124
+ * Two of its requests carry a value the suite has to compare against
125
+ * something, so the subject publishes that value beside the request. Both
126
+ * comparisons read a scalar the tool returns rather than a collection: a check
127
+ * over an empty collection resolves to insufficient evidence, so an assertion
128
+ * spelled that way would report vacuous and could never witness a dropped
129
+ * argument or a dropped result.
130
+ */
131
+ export type McpProbeSubject = PortSubject<ProbeRequest> & {
132
+ readonly policy: McpTargetPolicy;
133
+ /** a tool call the policy ALLOWS, against the subject's own fixture server. */
134
+ readonly authorizedRequest: ProbeRequest;
135
+ /** an interfaceId no authorization names. */
136
+ readonly unmappedInterfaceRequest: ProbeRequest;
137
+ /** an interfaceId the policy names, asking for a toolName its authorization omits. */
138
+ readonly unauthorizedToolRequest: ProbeRequest;
139
+ /** authorized, and answered by a tool result carrying the envelope's error flag AND the content the tool reported the failure with, since the assertion checks both survived. */
140
+ readonly errorResultRequest: ProbeRequest;
141
+ /** authorized, carrying a metacharacter-bearing value on one argument key. */
142
+ readonly argumentEchoRequest: ProbeRequest;
143
+ /** the exact literal `argumentEchoRequest` carries, so the assertion can confirm the tool received it byte for byte. */
144
+ readonly argumentEchoValue: string;
145
+ /** the structured-result key the fixture tool publishes that received value under. */
146
+ readonly argumentEchoResultKey: string;
147
+ /** authorized, and answered with the structured result the operation's descriptor describes. */
148
+ readonly structuredResultRequest: ProbeRequest;
149
+ /** the keys that result carries, so a dropped or emptied result channel fails. */
150
+ readonly structuredResultKeys: readonly string[];
151
+ /** authorized against a maxElapsedMs the server is made to answer past. */
152
+ readonly overElapsedRequest: ProbeRequest;
153
+ /** authorized against a maxOutputBytes the server is made to write past. */
154
+ readonly overResultBytesRequest: ProbeRequest;
155
+ };
156
+ /**
157
+ * Fourteen outcomes: the six shared assertions plus the eight above. Fewer
158
+ * than either other arm because a tool-server authorization scopes two fields:
159
+ * one session is opened against one server and every tool it offers belongs to
160
+ * that server, so the interface identifier is the server identity and the tool
161
+ * allowlist is the only thing left to deny on.
162
+ *
163
+ * Two rules the reference adapter follows are outside what a green run
164
+ * certifies, and an author reading this should know which. A JSON-RPC error
165
+ * answering `tools/call` is an observation on the same terms a tool-reported
166
+ * error is, and the byte cap applies to the server's own stderr as well as its
167
+ * stdout. Neither has an assertion here, because the derivation above gives
168
+ * each arm one denial per authorization-scoped field and one assertion per cap
169
+ * the suite can make a subject exceed, and both of these are a second spelling
170
+ * of an assertion the arm already carries. The reference adapter's own tests
171
+ * cover both.
172
+ */
173
+ export declare function runMcpProbeConformance(subject: McpProbeSubject): Promise<ConformanceReport>;