eval-quality 0.1.0 → 0.2.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 (133) hide show
  1. package/README.md +142 -67
  2. package/corpus/dev/README.md +17 -11
  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/no-collection-quantifier.json +1 -1
  13. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  14. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  15. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  16. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  17. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  18. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  19. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  20. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  21. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  22. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  23. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  24. package/corpus/dev/index.json +1 -1
  25. package/dist/application/index.d.ts +2 -0
  26. package/dist/application/index.js +1 -0
  27. package/dist/application/score.d.ts +48 -0
  28. package/dist/application/score.js +186 -0
  29. package/dist/cli/arguments.d.ts +7 -3
  30. package/dist/cli/arguments.js +73 -5
  31. package/dist/cli/exit-codes.d.ts +6 -2
  32. package/dist/cli/exit-codes.js +8 -9
  33. package/dist/cli/main.js +9 -0
  34. package/dist/cli/render.d.ts +1 -1
  35. package/dist/cli/render.js +6 -4
  36. package/dist/cli/run.d.ts +13 -3
  37. package/dist/cli/run.js +136 -16
  38. package/dist/core/canonical/canonicalize.d.ts +12 -0
  39. package/dist/core/canonical/canonicalize.js +18 -4
  40. package/dist/core/compile/bindings.d.ts +70 -0
  41. package/dist/core/compile/bindings.js +317 -0
  42. package/dist/core/compile/compile.d.ts +5 -0
  43. package/dist/core/compile/compile.js +9 -0
  44. package/dist/core/compile/expression-legality.d.ts +49 -0
  45. package/dist/core/compile/expression-legality.js +105 -31
  46. package/dist/core/compile/forbidden-inputs.d.ts +8 -1
  47. package/dist/core/compile/forbidden-inputs.js +16 -3
  48. package/dist/core/compile/interface-inventory.d.ts +23 -1
  49. package/dist/core/compile/interface-inventory.js +32 -3
  50. package/dist/core/compile/reachability.d.ts +18 -1
  51. package/dist/core/compile/reachability.js +46 -1
  52. package/dist/core/compile/sensitivity-witness.d.ts +1 -1
  53. package/dist/core/compile/sensitivity-witness.js +1 -1
  54. package/dist/core/emit/emit.d.ts +9 -0
  55. package/dist/core/emit/emit.js +214 -0
  56. package/dist/core/emit/private-artifact-digest.d.ts +11 -0
  57. package/dist/core/emit/private-artifact-digest.js +31 -0
  58. package/dist/core/evaluate/evidence-resolution.d.ts +11 -1
  59. package/dist/core/evaluate/evidence-resolution.js +4 -1
  60. package/dist/core/failure-codes.d.ts +2 -2
  61. package/dist/core/failure-codes.js +4 -2
  62. package/dist/core/ingest/conditions.d.ts +201 -0
  63. package/dist/core/ingest/conditions.js +48 -0
  64. package/dist/core/ingest/index.d.ts +13 -0
  65. package/dist/core/ingest/index.js +11 -0
  66. package/dist/core/ingest/ingest.d.ts +68 -0
  67. package/dist/core/ingest/ingest.js +310 -0
  68. package/dist/core/lineage/stage-table.d.ts +21 -4
  69. package/dist/core/lineage/stage-table.js +35 -3
  70. package/dist/core/preflight/witness-evidence.js +10 -0
  71. package/dist/core/schemas/artifact.d.ts +385 -14
  72. package/dist/core/schemas/constraint-ledger.js +40 -0
  73. package/dist/core/schemas/defect-signature.d.ts +273 -0
  74. package/dist/core/schemas/defect-signature.js +127 -0
  75. package/dist/core/schemas/eval-contract.d.ts +33 -0
  76. package/dist/core/schemas/eval-contract.js +14 -0
  77. package/dist/core/schemas/evidence-artifact.d.ts +99 -19
  78. package/dist/core/schemas/evidence-artifact.js +64 -8
  79. package/dist/core/schemas/plan.d.ts +112 -0
  80. package/dist/core/schemas/plan.js +67 -2
  81. package/dist/core/schemas/probe-qualification.d.ts +168 -0
  82. package/dist/core/schemas/probe-qualification.js +106 -0
  83. package/dist/core/schemas/probe.d.ts +312 -0
  84. package/dist/core/schemas/probe.js +6 -2
  85. package/dist/core/schemas/scoring-policy.d.ts +1 -0
  86. package/dist/core/schemas/scoring-policy.js +6 -1
  87. package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
  88. package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
  89. package/dist/core/schemas/sealed-run-record.d.ts +30 -3
  90. package/dist/core/schemas/sealed-run-record.js +37 -5
  91. package/dist/core/score/binding-order.d.ts +31 -0
  92. package/dist/core/score/binding-order.js +88 -0
  93. package/dist/core/score/bindings.d.ts +93 -0
  94. package/dist/core/score/bindings.js +297 -0
  95. package/dist/core/score/ladder-table.d.ts +23 -0
  96. package/dist/core/score/ladder-table.js +109 -0
  97. package/dist/core/score/ladder.d.ts +229 -0
  98. package/dist/core/score/ladder.js +464 -0
  99. package/dist/core/score/mode-agreement.d.ts +30 -0
  100. package/dist/core/score/mode-agreement.js +16 -0
  101. package/dist/core/score/outcome-table.d.ts +17 -0
  102. package/dist/core/score/outcome-table.js +172 -0
  103. package/dist/core/score/outcome.d.ts +466 -0
  104. package/dist/core/score/outcome.js +562 -0
  105. package/dist/core/score/qualification.d.ts +83 -0
  106. package/dist/core/score/qualification.js +615 -0
  107. package/dist/core/score/quotation.d.ts +79 -0
  108. package/dist/core/score/quotation.js +201 -0
  109. package/dist/core/score/reduce-trials.d.ts +80 -0
  110. package/dist/core/score/reduce-trials.js +90 -0
  111. package/dist/core/score/score.d.ts +72 -0
  112. package/dist/core/score/score.js +581 -0
  113. package/dist/core/score/selection.d.ts +80 -0
  114. package/dist/core/score/selection.js +98 -0
  115. package/dist/core/score/strength.d.ts +49 -0
  116. package/dist/core/score/strength.js +193 -0
  117. package/dist/core/score/witness.d.ts +117 -0
  118. package/dist/core/score/witness.js +315 -0
  119. package/dist/core/seal/derived-reference.d.ts +1 -1
  120. package/dist/core/seal/derived-reference.js +236 -28
  121. package/dist/core/seal/seal.js +8 -3
  122. package/dist/core/stage-contracts.d.ts +62 -2
  123. package/dist/index.d.ts +1 -1
  124. package/dist/index.js +1 -1
  125. package/dist/testing/index.d.ts +2 -0
  126. package/dist/testing/index.js +1 -0
  127. package/package.json +12 -3
  128. package/schemas/eval-contract.schema.json +100 -2
  129. package/schemas/evidence-artifact.schema.json +96 -9
  130. package/schemas/probe.schema.json +508 -3
  131. package/schemas/scoring-policy.schema.json +8 -1
  132. package/schemas/sealed-evaluator-brief.schema.json +11 -1
  133. package/schemas/sealed-run-record.schema.json +19 -2
@@ -43,19 +43,22 @@ export type CheckResolutionValue = {
43
43
  */
44
44
  export declare const CheckResolution: z.ZodType<CheckResolutionValue>;
45
45
  /**
46
- * AD-11's five named inputs, transcribed so a reader can recompute the
47
- * digest and see what was compared (AD-8's "by digest and opaque reference,
48
- * never by content or path"). Named object rather than a concatenated tuple:
46
+ * AD-11's identity inputs, transcribed so a reader can recompute the digest
47
+ * and see what was compared (AD-8's "by digest and opaque reference, never
48
+ * by content or path"). Named object rather than a concatenated tuple:
49
49
  * revision 1 let two conforming scorers compute different versions from
50
50
  * identical inputs if they hashed a different ordering. The scorer computes
51
- * the scoring version over this shape.
51
+ * the scoring version over this shape. Six now, not five: `mode` is owed
52
+ * item 4's identity clause, superseding AD-11's original five-field
53
+ * sentence.
52
54
  */
53
- export declare const SCORING_VERSION_INPUT_NAMES: readonly ['contractSchemaVersion', 'corpusDigest', 'fixtureDigest', 'evaluatorConfigurationDigest', 'scoringPolicyDigest'];
55
+ export declare const SCORING_VERSION_INPUT_NAMES: readonly ['contractSchemaVersion', 'corpusDigest', 'fixtureDigest', 'evaluatorConfigurationDigest', 'scoringPolicyDigest', 'mode'];
54
56
  export declare const ScoringVersionInputName: z.ZodEnum<{
55
57
  contractSchemaVersion: "contractSchemaVersion";
56
58
  corpusDigest: "corpusDigest";
57
59
  evaluatorConfigurationDigest: "evaluatorConfigurationDigest";
58
60
  fixtureDigest: "fixtureDigest";
61
+ mode: "mode";
59
62
  scoringPolicyDigest: "scoringPolicyDigest";
60
63
  }>;
61
64
  export declare const ScoringVersionInputs: z.ZodObject<{
@@ -64,11 +67,17 @@ export declare const ScoringVersionInputs: z.ZodObject<{
64
67
  fixtureDigest: z.ZodString;
65
68
  evaluatorConfigurationDigest: z.ZodString;
66
69
  scoringPolicyDigest: z.ZodString;
70
+ mode: z.ZodEnum<{
71
+ "contract-scoring": "contract-scoring";
72
+ production: "production";
73
+ }>;
67
74
  }, z.core.$strict>;
68
75
  export declare const InvalidatedAttempt: z.ZodObject<{
69
76
  attempt: z.ZodInt;
70
77
  reason: z.ZodString;
71
78
  }, z.core.$strict>;
79
+ /** exported so the trial-set reducer names this shape without importing Zod. */
80
+ export type InvalidatedAttempt = z.infer<typeof InvalidatedAttempt>;
72
81
  /**
73
82
  * AD-6: "Every artifact records its trial count, its invalidated attempts, and
74
83
  * each attempt's reason, including on a PASS." None of the three is nullable,
@@ -83,6 +92,8 @@ export declare const Trials: z.ZodObject<{
83
92
  reason: z.ZodString;
84
93
  }, z.core.$strict>>;
85
94
  }, z.core.$strict>;
95
+ /** exported so the ladder names this shape without importing Zod. */
96
+ export type Trials = z.infer<typeof Trials>;
86
97
  export declare const Outcome: z.ZodObject<{
87
98
  oracleId: z.ZodString;
88
99
  probeId: z.ZodNullable<z.ZodString>;
@@ -119,6 +130,8 @@ export declare const Outcome: z.ZodObject<{
119
130
  selectedObservationIds: z.ZodArray<z.ZodString>;
120
131
  checkResolution: z.ZodNullable<z.ZodType<CheckResolutionValue, unknown, z.core.$ZodTypeInternals<CheckResolutionValue, unknown>>>;
121
132
  }, z.core.$strict>;
133
+ /** exported so the dominance comparator names this shape without importing Zod. */
134
+ export type Outcome = z.infer<typeof Outcome>;
122
135
  export declare const CoverageGap: z.ZodObject<{
123
136
  rule: z.ZodString;
124
137
  relevancePredicate: z.ZodString;
@@ -132,11 +145,49 @@ export declare const CoverageGap: z.ZodObject<{
132
145
  }, z.core.$strict>;
133
146
  /** the record as a type, so `core/coverage/` names it without importing Zod. */
134
147
  export type CoverageGap = z.infer<typeof CoverageGap>;
148
+ /**
149
+ * Owed item 5's record for an ingested `defect` finding citing no oracle:
150
+ * AD-23 already retains the finding rather than discarding it, but nothing
151
+ * downstream consumed it until this `schemaVersion`. Mirrors `CoverageGap`'s
152
+ * shape and the `defect`-branch fields on `Finding` it is read from, but
153
+ * carries no relevance or satisfaction predicate: AD-31's coverage gap is a
154
+ * declaration AD-31 could not confirm was satisfied, while an uncited defect
155
+ * finding is a runtime observation an evaluator made outside every declared
156
+ * oracle, with no relevance predicate to name and no satisfaction predicate
157
+ * that failed. This is the version-3 bump: required, not optional, per owed
158
+ * item 5, so a producer that never emits it stops parsing rather than
159
+ * silently under-reporting the gap.
160
+ */
161
+ export declare const UncitedFindingGap: z.ZodObject<{
162
+ findingId: z.ZodString;
163
+ observationIds: z.ZodArray<z.ZodString>;
164
+ quotedEvidence: z.ZodArray<z.ZodObject<{
165
+ quote: z.ZodString;
166
+ channel: z.ZodEnum<{
167
+ "call-inputs": "call-inputs";
168
+ "exit-code": "exit-code";
169
+ "response-body": "response-body";
170
+ "response-headers": "response-headers";
171
+ "response-status": "response-status";
172
+ stderr: "stderr";
173
+ stdout: "stdout";
174
+ }>;
175
+ }, z.core.$strict>>;
176
+ severity: z.ZodEnum<{
177
+ critical: "critical";
178
+ low: "low";
179
+ material: "material";
180
+ }>;
181
+ }, z.core.$strict>;
182
+ /** exported so `core/score/ladder.ts` names this shape without importing Zod. */
183
+ export type UncitedFindingGap = z.infer<typeof UncitedFindingGap>;
135
184
  export declare const ClassStrength: z.ZodObject<{
136
185
  caught: z.ZodInt;
137
186
  exercised: z.ZodInt;
138
187
  rate: z.ZodNullable<z.ZodNumber>;
139
188
  }, z.core.$strict>;
189
+ /** exported so the rate-vector builder names this shape without importing Zod. */
190
+ export type ClassStrength = z.infer<typeof ClassStrength>;
140
191
  /**
141
192
  * A fixed three-key object instead of a map keyed by probe class. AD-7 is
142
193
  * explicit that "canary probes and clean controls never enter the vector", so
@@ -159,6 +210,8 @@ export declare const StrengthVector: z.ZodObject<{
159
210
  rate: z.ZodNullable<z.ZodNumber>;
160
211
  }, z.core.$strict>>;
161
212
  }, z.core.$strict>;
213
+ /** exported so the rate-vector builder names this shape without importing Zod. */
214
+ export type StrengthVector = z.infer<typeof StrengthVector>;
162
215
  export declare const Strength: z.ZodObject<{
163
216
  denominator: z.ZodString;
164
217
  basis: z.ZodEnum<{
@@ -185,6 +238,8 @@ export declare const Strength: z.ZodObject<{
185
238
  comparable: z.ZodBoolean;
186
239
  note: z.ZodNullable<z.ZodString>;
187
240
  }, z.core.$strict>;
241
+ /** exported so the dominance comparator names this shape without importing Zod. */
242
+ export type Strength = z.infer<typeof Strength>;
188
243
  /**
189
244
  * AD-12's three named checks, transcribed from the same sentence the field
190
245
  * comes from: the package "validates a caller-presented lineage chain (length
@@ -221,13 +276,6 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
221
276
  revisionCount: z.ZodInt;
222
277
  runId: z.ZodString;
223
278
  scoringVersion: z.ZodString;
224
- scoringVersionInputs: z.ZodObject<{
225
- contractSchemaVersion: z.ZodInt;
226
- corpusDigest: z.ZodString;
227
- fixtureDigest: z.ZodString;
228
- evaluatorConfigurationDigest: z.ZodString;
229
- scoringPolicyDigest: z.ZodString;
230
- }, z.core.$strict>;
231
279
  comparabilityKey: z.ZodString;
232
280
  excludedProbeIds: z.ZodArray<z.ZodString>;
233
281
  exitCode: z.ZodInt;
@@ -237,6 +285,7 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
237
285
  corpusDigest: "corpusDigest";
238
286
  evaluatorConfigurationDigest: "evaluatorConfigurationDigest";
239
287
  fixtureDigest: "fixtureDigest";
288
+ mode: "mode";
240
289
  scoringPolicyDigest: "scoringPolicyDigest";
241
290
  }>>;
242
291
  trials: z.ZodObject<{
@@ -331,6 +380,14 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
331
380
  noGap: z.ZodBoolean;
332
381
  }, z.core.$strict>;
333
382
  }, z.core.$strict>;
383
+ scoringVersionInputs: z.ZodObject<{
384
+ contractSchemaVersion: z.ZodInt;
385
+ corpusDigest: z.ZodString;
386
+ fixtureDigest: z.ZodString;
387
+ evaluatorConfigurationDigest: z.ZodString;
388
+ scoringPolicyDigest: z.ZodString;
389
+ mode: z.ZodLiteral<"production">;
390
+ }, z.core.$strict>;
334
391
  mode: z.ZodLiteral<"production">;
335
392
  productionVerdict: z.ZodEnum<{
336
393
  CONCERNS: "CONCERNS";
@@ -344,13 +401,6 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
344
401
  revisionCount: z.ZodInt;
345
402
  runId: z.ZodString;
346
403
  scoringVersion: z.ZodString;
347
- scoringVersionInputs: z.ZodObject<{
348
- contractSchemaVersion: z.ZodInt;
349
- corpusDigest: z.ZodString;
350
- fixtureDigest: z.ZodString;
351
- evaluatorConfigurationDigest: z.ZodString;
352
- scoringPolicyDigest: z.ZodString;
353
- }, z.core.$strict>;
354
404
  comparabilityKey: z.ZodString;
355
405
  excludedProbeIds: z.ZodArray<z.ZodString>;
356
406
  exitCode: z.ZodInt;
@@ -360,6 +410,7 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
360
410
  corpusDigest: "corpusDigest";
361
411
  evaluatorConfigurationDigest: "evaluatorConfigurationDigest";
362
412
  fixtureDigest: "fixtureDigest";
413
+ mode: "mode";
363
414
  scoringPolicyDigest: "scoringPolicyDigest";
364
415
  }>>;
365
416
  trials: z.ZodObject<{
@@ -454,6 +505,14 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
454
505
  noGap: z.ZodBoolean;
455
506
  }, z.core.$strict>;
456
507
  }, z.core.$strict>;
508
+ scoringVersionInputs: z.ZodObject<{
509
+ contractSchemaVersion: z.ZodInt;
510
+ corpusDigest: z.ZodString;
511
+ fixtureDigest: z.ZodString;
512
+ evaluatorConfigurationDigest: z.ZodString;
513
+ scoringPolicyDigest: z.ZodString;
514
+ mode: z.ZodLiteral<"contract-scoring">;
515
+ }, z.core.$strict>;
457
516
  mode: z.ZodLiteral<"contract-scoring">;
458
517
  contractVerdict: z.ZodEnum<{
459
518
  CONCERNS: "CONCERNS";
@@ -461,6 +520,27 @@ export declare const EvidenceArtifact: z.ZodDiscriminatedUnion<[z.ZodObject<{
461
520
  PASS: "PASS";
462
521
  WAIVED: "WAIVED";
463
522
  }>;
523
+ uncitedFindingGaps: z.ZodArray<z.ZodObject<{
524
+ findingId: z.ZodString;
525
+ observationIds: z.ZodArray<z.ZodString>;
526
+ quotedEvidence: z.ZodArray<z.ZodObject<{
527
+ quote: z.ZodString;
528
+ channel: z.ZodEnum<{
529
+ "call-inputs": "call-inputs";
530
+ "exit-code": "exit-code";
531
+ "response-body": "response-body";
532
+ "response-headers": "response-headers";
533
+ "response-status": "response-status";
534
+ stderr: "stderr";
535
+ stdout: "stdout";
536
+ }>;
537
+ }, z.core.$strict>>;
538
+ severity: z.ZodEnum<{
539
+ critical: "critical";
540
+ low: "low";
541
+ material: "material";
542
+ }>;
543
+ }, z.core.$strict>>;
464
544
  systemRecommendationRecorded: z.ZodEnum<{
465
545
  CONCERNS: "CONCERNS";
466
546
  FAIL: "FAIL";
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  import { Severity } from './eval-contract.js';
4
4
  import { lineageFields } from './lineage.js';
5
5
  import { Digest, FindingId, Identifier, OracleId, ProbeId, } from './primitives.js';
6
- import { OracleDispositionValue } from './sealed-run-record.js';
6
+ import { OracleDispositionValue, QuotedEvidence, RunMode, } from './sealed-run-record.js';
7
7
  import { EvaluatorRecommendation, Verdict } from './verdict.js';
8
8
  /**
9
9
  * AD-6's closed twelve. Exported so a later reader can walk the list
@@ -52,12 +52,14 @@ export const CheckResolution = z
52
52
  description: "One node of an oracle's `check` tree as it resolved. AD-4's resolution is three-valued rather than two, and the introduction vocabulary has exactly one member because AD-4 closes the introduction set at one: an empty collection. `null` where the resolution is not `insufficient-evidence`. A leaf carries an empty `children` array.",
53
53
  });
54
54
  /**
55
- * AD-11's five named inputs, transcribed so a reader can recompute the
56
- * digest and see what was compared (AD-8's "by digest and opaque reference,
57
- * never by content or path"). Named object rather than a concatenated tuple:
55
+ * AD-11's identity inputs, transcribed so a reader can recompute the digest
56
+ * and see what was compared (AD-8's "by digest and opaque reference, never
57
+ * by content or path"). Named object rather than a concatenated tuple:
58
58
  * revision 1 let two conforming scorers compute different versions from
59
59
  * identical inputs if they hashed a different ordering. The scorer computes
60
- * the scoring version over this shape.
60
+ * the scoring version over this shape. Six now, not five: `mode` is owed
61
+ * item 4's identity clause, superseding AD-11's original five-field
62
+ * sentence.
61
63
  */
62
64
  export const SCORING_VERSION_INPUT_NAMES = [
63
65
  'contractSchemaVersion',
@@ -65,14 +67,44 @@ export const SCORING_VERSION_INPUT_NAMES = [
65
67
  'fixtureDigest',
66
68
  'evaluatorConfigurationDigest',
67
69
  'scoringPolicyDigest',
70
+ 'mode',
68
71
  ];
69
72
  export const ScoringVersionInputName = z.enum(SCORING_VERSION_INPUT_NAMES);
73
+ const SCORING_VERSION_MODE_DESCRIPTION = "Owed item 4's identity clause: mode enters AD-11's scoring version, superseding AD-11's five-field sentence. Read from the sealed run record, never re-derived, so two runs scored under one mode never collide with a version computed for the other. Required, not optional, which makes this the evidence artifact's `schemaVersion` 1 -> 2 BREAKING bump under AD-11, whose rule is that \"adding an optional field is a `schemaVersion` bump recorded in the field's own description; removing or retyping is breaking\". A version-1 artifact carries five scoring-version inputs where this one carries six, so it does not parse, and no version computed before the bump compares with one computed after it.";
70
74
  export const ScoringVersionInputs = z.strictObject({
71
75
  contractSchemaVersion: z.int().min(1),
72
76
  corpusDigest: Digest,
73
77
  fixtureDigest: Digest,
74
78
  evaluatorConfigurationDigest: Digest,
75
79
  scoringPolicyDigest: Digest,
80
+ mode: RunMode.describe(SCORING_VERSION_MODE_DESCRIPTION),
81
+ });
82
+ /**
83
+ * `scoringVersionInputs.mode` narrowed to the one value that agrees with
84
+ * `EvidenceArtifact`'s own branch discriminant (AD-11, AD-32), rather than a
85
+ * `.refine()` comparing the two fields. A refinement never exports, so the
86
+ * published document would keep admitting the disagreement, and the
87
+ * corpus-mutation generator synthesises witnesses for an untaken `oneOf`
88
+ * branch straight from that branch's own JSON Schema with no knowledge of a
89
+ * Zod-only cross-field rule -- verified: it produced two dozen genuine
90
+ * `zod=false published=true` disagreements this way. Narrowing the schema
91
+ * itself removes the gap at its source: a synthesised example for a
92
+ * `const`-typed field can only be that one value.
93
+ *
94
+ * Two named consts rather than one generic builder: a `<M extends
95
+ * RunModeValue>(mode: M)` helper builds each schema correctly, but TypeScript
96
+ * fails to preserve the per-call-site literal through `EvidenceArtifact`'s
97
+ * `z.discriminatedUnion` inference, so a fixture assigning the wrong branch's
98
+ * `mode` here compiles instead of erroring. Two concrete literals close that
99
+ * gap by construction; there is nothing generic left for the checker to lose.
100
+ */
101
+ const productionScoringVersionInputs = ScoringVersionInputs.extend({
102
+ mode: z.literal('production').describe(SCORING_VERSION_MODE_DESCRIPTION),
103
+ });
104
+ const contractScoringVersionInputs = ScoringVersionInputs.extend({
105
+ mode: z
106
+ .literal('contract-scoring')
107
+ .describe(SCORING_VERSION_MODE_DESCRIPTION),
76
108
  });
77
109
  export const InvalidatedAttempt = z.strictObject({
78
110
  attempt: z.int().min(1),
@@ -115,6 +147,25 @@ export const CoverageGap = z.strictObject({
115
147
  satisfied: z.boolean(),
116
148
  severity: Severity,
117
149
  });
150
+ /**
151
+ * Owed item 5's record for an ingested `defect` finding citing no oracle:
152
+ * AD-23 already retains the finding rather than discarding it, but nothing
153
+ * downstream consumed it until this `schemaVersion`. Mirrors `CoverageGap`'s
154
+ * shape and the `defect`-branch fields on `Finding` it is read from, but
155
+ * carries no relevance or satisfaction predicate: AD-31's coverage gap is a
156
+ * declaration AD-31 could not confirm was satisfied, while an uncited defect
157
+ * finding is a runtime observation an evaluator made outside every declared
158
+ * oracle, with no relevance predicate to name and no satisfaction predicate
159
+ * that failed. This is the version-3 bump: required, not optional, per owed
160
+ * item 5, so a producer that never emits it stops parsing rather than
161
+ * silently under-reporting the gap.
162
+ */
163
+ export const UncitedFindingGap = z.strictObject({
164
+ findingId: FindingId,
165
+ observationIds: z.array(Identifier).min(1),
166
+ quotedEvidence: z.array(QuotedEvidence).min(1),
167
+ severity: Severity,
168
+ });
118
169
  export const ClassStrength = z.strictObject({
119
170
  caught: z.int().min(0),
120
171
  exercised: z.int().min(0),
@@ -178,7 +229,7 @@ export const Remediation = z.strictObject({
178
229
  const evidenceCommonFields = {
179
230
  ...lineageFields,
180
231
  runId: z.string().min(1),
181
- scoringVersion: Digest.describe('AD-11: computed by the scorer over the five named inputs below and never caller-supplied.'),
232
+ scoringVersion: Digest.describe('AD-11: computed by the scorer over the six named inputs below and never caller-supplied.'),
182
233
  scoringVersionInputs: ScoringVersionInputs,
183
234
  comparabilityKey: Digest.describe("AD-7's declared key: the scoring policy digest plus the corpus digest restricted to the probes both results cover. Deliberately weaker than the scoring version, so adding a probe narrows a comparison rather than voiding every prior result."),
184
235
  excludedProbeIds: z
@@ -192,7 +243,7 @@ const evidenceCommonFields = {
192
243
  .describe('Every condition that fired, in AD-21 terms. Free text by design: the rungs are prose in AD-21 and assigning them identifiers would be minting a vocabulary that AD has not. Each member is non-empty, on the same reasoning that `InvalidatedAttempt.reason` is: a condition that names itself with nothing has not been recorded. An empty array is legal and is what a PASS with no firing condition carries.'),
193
244
  callerAttestedInputs: z
194
245
  .array(ScoringVersionInputName)
195
- .describe('Which of the five scoring-version inputs were caller-attested rather than computed by this package. An enum over the five key names rather than free strings, because AD-32 requires the artifact to state *which* inputs were attested and an unconstrained string cannot be checked against anything. AD-11 names three of the five as caller-attested; the enum admits any subset so a stricter or looser integration stays representable.'),
246
+ .describe('Which of the six scoring-version inputs were caller-attested rather than computed by this package. An enum over the six key names rather than free strings, because AD-32 requires the artifact to state *which* inputs were attested and an unconstrained string cannot be checked against anything. AD-11 names three of the six as caller-attested; the enum admits any subset so a stricter or looser integration stays representable.'),
196
247
  trials: Trials,
197
248
  outcomes: z.array(Outcome),
198
249
  uncitedFindings: z
@@ -212,6 +263,7 @@ export const EvidenceArtifact = z
212
263
  z
213
264
  .strictObject({
214
265
  ...evidenceCommonFields,
266
+ scoringVersionInputs: productionScoringVersionInputs,
215
267
  mode: z.literal('production'),
216
268
  productionVerdict: Verdict,
217
269
  })
@@ -219,8 +271,12 @@ export const EvidenceArtifact = z
219
271
  z
220
272
  .strictObject({
221
273
  ...evidenceCommonFields,
274
+ scoringVersionInputs: contractScoringVersionInputs,
222
275
  mode: z.literal('contract-scoring'),
223
276
  contractVerdict: Verdict,
277
+ uncitedFindingGaps: z
278
+ .array(UncitedFindingGap)
279
+ .describe("Owed item 5's rung record, contract-scoring only: one entry per uncited `defect` finding, per `core/score/outcome.ts`'s `uncitedDefectFindingGaps`. Required, not optional, which makes this the `schemaVersion` 2 -> 3 BREAKING bump under AD-11: an empty array is legal and is what a run with no uncited defect finding carries, but the key itself must be present. Never on the production branch, where the ladder signal alone is enough and this key would fail `strictObject`'s `unrecognized_keys` rule."),
224
280
  systemRecommendationRecorded: EvaluatorRecommendation.describe("The evaluator's recommendation about the SYSTEM, recorded and never promoted. On a knowingly defective probe a system-directed FAIL is an input rather than a signal about the contract."),
225
281
  systemRecommendationNote: z.string().nullable(),
226
282
  })
@@ -228,5 +284,5 @@ export const EvidenceArtifact = z
228
284
  ])
229
285
  .meta({
230
286
  id: 'EvidenceArtifact',
231
- description: "The scored output, owned by `emit` per AD-24, with no prior art; `score` produces the outcome and verdict values it serializes. The two modes are separate branches because AD-21 requires that the production verdict and the contract verdict never share a field. This expresses that settled sentence and no more: Owed item 4's remaining half stays open, namely mode being fixed before ingest and entering AD-11's identity inputs rather than appearing first here; and the Sealed Run Record still carries no mode.",
287
+ description: 'The scored output, owned by `emit` per AD-24, with no prior art; `score` produces the outcome and verdict values it serializes. The two modes are separate branches because AD-21 requires that the production verdict and the contract verdict never share a field. Mode is no longer first stated here: the Sealed Run Record now carries a required `mode`, where AD-21\'s "fixed before ingest" puts it, so this artifact restates a mode the sealed record already fixed and is never the source. Owed item 4 is closed as of this schemaVersion: `mode` is `ScoringVersionInputs`\'s sixth field, `core/score/ladder.ts` carries `ProductionAssessment`/`ContractAssessment` and their own total ladders, and `core/score/mode-agreement.ts` rejects a `production` record paired with a `contract-scoring` artifact in both directions as an AD-32 cross-artifact disagreement. That check is a hand-written function at the assembly boundary rather than a schema refinement, since no single schema sees both artifacts; `emit` is its one caller, immediately before returning the artifact it just built. Owed item 5 is closed as of this schemaVersion too: the contract-scoring branch carries a required `uncitedFindingGaps`, and both ladders in `core/score/ladder.ts` gain a shared `uncited-defect-finding` CONCERNS row over `AssessmentCommon.uncitedDefectFindings`, so an ingested defect finding citing no oracle now resolves at least CONCERNS instead of routing nowhere.',
232
288
  });
@@ -7,7 +7,52 @@ import { JsonValue } from './primitives.ts';
7
7
  * one implementation and the literal string to another, and flipped a witness
8
8
  * match between `caught` and `missed` on one record; that form is
9
9
  * unrepresentable here.
10
+ *
11
+ * Four members, one tag each. `{ literal }` writes the sent value down;
12
+ * `{ matcher: 'any' }` binds whatever was sent and `{ matcher: 'type-violating' }`
13
+ * binds a value whose JSON type differs from the operation's declared type for
14
+ * the key, which is how AD-31 rule 3's malformed-input behaviour is addressed.
15
+ *
16
+ * `{ captured }` addresses an earlier step's declared scalar output. Owed item
17
+ * 3: a `POST` returning a server-generated identifier followed by a `GET`
18
+ * proving persistence is unwritable with the first two, since a literal
19
+ * hard-codes a resource the evaluator never created and `any` matches
20
+ * unrelated reads. "Earlier" is earlier in the capture graph's own topological
21
+ * order, which `binding-cycle` makes exist. It is deliberately not AD-39's
22
+ * `after` clause: `nested-temporal-clause` already rejects every `after` cycle,
23
+ * so a capture forced to follow an `after` edge would leave `binding-cycle`
24
+ * unfireable. At score time the ordering is the record's `sequence`.
25
+ *
26
+ * `{ principal }` names a principal `testData.principals` declares. The two
27
+ * critical-severity cross-user behaviours (act as A, read as B, must be denied
28
+ * or absent) bind a step to an account that is neither a literal, since AD-19
29
+ * forbids credential values in declarations, nor an earlier step's output,
30
+ * since accounts are provisioned outside the observation stream. The name is
31
+ * an opaque label carrying no account identifier, credential, or subject data
32
+ * (AD-18). An undeclared name fires `undeclared-mandatory-input` in strict
33
+ * mode; a Zod refinement here would be a cross-subtree constraint the export
34
+ * cannot carry, and the published-schema differential sweep synthesises a
35
+ * union-branch witness that would expose the disagreement.
36
+ *
37
+ * A captured pointer resolves to a declared scalar with no transform applied,
38
+ * which keeps AD-4's ban on arithmetic, projection, and user-defined functions
39
+ * holding by construction: the grammar has no place to write one.
10
40
  */
41
+ /**
42
+ * The first two members, named individually so the probe-side selector under
43
+ * AD-40 admits exactly these two and nothing else. Bare `export const` with no
44
+ * `.meta({ id })`: an id would collapse the branches to `$ref`s and mint new
45
+ * `$defs`, so the export stays byte-identical.
46
+ */
47
+ export declare const LiteralBindingValue: z.ZodObject<{
48
+ literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
49
+ }, z.core.$strict>;
50
+ export declare const MatcherBindingValue: z.ZodObject<{
51
+ matcher: z.ZodEnum<{
52
+ any: "any";
53
+ "type-violating": "type-violating";
54
+ }>;
55
+ }, z.core.$strict>;
11
56
  export declare const BindingValue: z.ZodUnion<readonly [z.ZodObject<{
12
57
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
13
58
  }, z.core.$strict>, z.ZodObject<{
@@ -15,6 +60,10 @@ export declare const BindingValue: z.ZodUnion<readonly [z.ZodObject<{
15
60
  any: "any";
16
61
  "type-violating": "type-violating";
17
62
  }>;
63
+ }, z.core.$strict>, z.ZodObject<{
64
+ captured: z.ZodString;
65
+ }, z.core.$strict>, z.ZodObject<{
66
+ principal: z.ZodString;
18
67
  }, z.core.$strict>]>;
19
68
  /** the constraint identifier the ledger carries for the check below. */
20
69
  export declare const BINDING_CHANNEL_NON_EMPTY = "binding-channel-non-empty";
@@ -25,6 +74,10 @@ export declare const BindingChannel: z.ZodNullable<z.ZodRecord<z.ZodString, z.Zo
25
74
  any: "any";
26
75
  "type-violating": "type-violating";
27
76
  }>;
77
+ }, z.core.$strict>, z.ZodObject<{
78
+ captured: z.ZodString;
79
+ }, z.core.$strict>, z.ZodObject<{
80
+ principal: z.ZodString;
28
81
  }, z.core.$strict>]>>>;
29
82
  /**
30
83
  * A four-key strict object with each channel nullable, not a record over the
@@ -40,6 +93,10 @@ export declare const InputBinding: z.ZodObject<{
40
93
  any: "any";
41
94
  "type-violating": "type-violating";
42
95
  }>;
96
+ }, z.core.$strict>, z.ZodObject<{
97
+ captured: z.ZodString;
98
+ }, z.core.$strict>, z.ZodObject<{
99
+ principal: z.ZodString;
43
100
  }, z.core.$strict>]>>>;
44
101
  query: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
45
102
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -48,6 +105,10 @@ export declare const InputBinding: z.ZodObject<{
48
105
  any: "any";
49
106
  "type-violating": "type-violating";
50
107
  }>;
108
+ }, z.core.$strict>, z.ZodObject<{
109
+ captured: z.ZodString;
110
+ }, z.core.$strict>, z.ZodObject<{
111
+ principal: z.ZodString;
51
112
  }, z.core.$strict>]>>>;
52
113
  header: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
53
114
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -56,6 +117,10 @@ export declare const InputBinding: z.ZodObject<{
56
117
  any: "any";
57
118
  "type-violating": "type-violating";
58
119
  }>;
120
+ }, z.core.$strict>, z.ZodObject<{
121
+ captured: z.ZodString;
122
+ }, z.core.$strict>, z.ZodObject<{
123
+ principal: z.ZodString;
59
124
  }, z.core.$strict>]>>>;
60
125
  body: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
61
126
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -64,8 +129,34 @@ export declare const InputBinding: z.ZodObject<{
64
129
  any: "any";
65
130
  "type-violating": "type-violating";
66
131
  }>;
132
+ }, z.core.$strict>, z.ZodObject<{
133
+ captured: z.ZodString;
134
+ }, z.core.$strict>, z.ZodObject<{
135
+ principal: z.ZodString;
67
136
  }, z.core.$strict>]>>>;
68
137
  }, z.core.$strict>;
138
+ /**
139
+ * AD-39's declared selector cardinality (owed item 2): what a step means when
140
+ * its selector matches more than one observation was not decided at the
141
+ * architecture layer, so a first-match scorer and a last-match scorer could
142
+ * bind different evidence. The contract now declares the rule per step.
143
+ * `several` under `exactly-one`/`at-most-one` is a named ambiguity condition;
144
+ * a reference function returns it as data. Routing that ambiguity to a
145
+ * verdict rung is later work. `any` is unrelated to
146
+ * `BindingValue`'s `{ matcher: 'any' }` above: same string, different field,
147
+ * no type-level collision. `ExpectedCardinality` (`interface.ts`) is a
148
+ * second, unrelated reuse of the word for AD-20's response-collection
149
+ * cardinality, and its `at-most` mode is a near-miss for `at-most-one` here;
150
+ * different type, different field, no collision, but easy to misread as the
151
+ * same declaration.
152
+ */
153
+ export declare const SELECTOR_CARDINALITIES: readonly ['exactly-one', 'at-most-one', 'any'];
154
+ export type SelectorCardinalityValue = (typeof SELECTOR_CARDINALITIES)[number];
155
+ export declare const SelectorCardinality: z.ZodEnum<{
156
+ any: "any";
157
+ "at-most-one": "at-most-one";
158
+ "exactly-one": "exactly-one";
159
+ }>;
69
160
  /**
70
161
  * AD-39: a step is a selector over observations the evaluator produced, never
71
162
  * an instruction. Its selection predicate is spelled as its two members, the
@@ -84,6 +175,10 @@ export declare const InteractionStep: z.ZodObject<{
84
175
  any: "any";
85
176
  "type-violating": "type-violating";
86
177
  }>;
178
+ }, z.core.$strict>, z.ZodObject<{
179
+ captured: z.ZodString;
180
+ }, z.core.$strict>, z.ZodObject<{
181
+ principal: z.ZodString;
87
182
  }, z.core.$strict>]>>>;
88
183
  query: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
89
184
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -92,6 +187,10 @@ export declare const InteractionStep: z.ZodObject<{
92
187
  any: "any";
93
188
  "type-violating": "type-violating";
94
189
  }>;
190
+ }, z.core.$strict>, z.ZodObject<{
191
+ captured: z.ZodString;
192
+ }, z.core.$strict>, z.ZodObject<{
193
+ principal: z.ZodString;
95
194
  }, z.core.$strict>]>>>;
96
195
  header: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
97
196
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -100,6 +199,10 @@ export declare const InteractionStep: z.ZodObject<{
100
199
  any: "any";
101
200
  "type-violating": "type-violating";
102
201
  }>;
202
+ }, z.core.$strict>, z.ZodObject<{
203
+ captured: z.ZodString;
204
+ }, z.core.$strict>, z.ZodObject<{
205
+ principal: z.ZodString;
103
206
  }, z.core.$strict>]>>>;
104
207
  body: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
105
208
  literal: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -108,8 +211,17 @@ export declare const InteractionStep: z.ZodObject<{
108
211
  any: "any";
109
212
  "type-violating": "type-violating";
110
213
  }>;
214
+ }, z.core.$strict>, z.ZodObject<{
215
+ captured: z.ZodString;
216
+ }, z.core.$strict>, z.ZodObject<{
217
+ principal: z.ZodString;
111
218
  }, z.core.$strict>]>>>;
112
219
  }, z.core.$strict>;
113
220
  after: z.ZodNullable<z.ZodString>;
221
+ cardinality: z.ZodEnum<{
222
+ any: "any";
223
+ "at-most-one": "at-most-one";
224
+ "exactly-one": "exactly-one";
225
+ }>;
114
226
  }, z.core.$strict>;
115
227
  export type InteractionStep = z.infer<typeof InteractionStep>;
@@ -1,5 +1,6 @@
1
1
  /** the interaction plan: steps as selectors over observations. */
2
2
  import { z } from 'zod';
3
+ import { InteractionPointer } from './pointer.js';
3
4
  import { Identifier, JsonValue, KeyName } from './primitives.js';
4
5
  /**
5
6
  * AD-39: input-binding values are tagged and never share a value space. The
@@ -7,10 +8,52 @@ import { Identifier, JsonValue, KeyName } from './primitives.js';
7
8
  * one implementation and the literal string to another, and flipped a witness
8
9
  * match between `caught` and `missed` on one record; that form is
9
10
  * unrepresentable here.
11
+ *
12
+ * Four members, one tag each. `{ literal }` writes the sent value down;
13
+ * `{ matcher: 'any' }` binds whatever was sent and `{ matcher: 'type-violating' }`
14
+ * binds a value whose JSON type differs from the operation's declared type for
15
+ * the key, which is how AD-31 rule 3's malformed-input behaviour is addressed.
16
+ *
17
+ * `{ captured }` addresses an earlier step's declared scalar output. Owed item
18
+ * 3: a `POST` returning a server-generated identifier followed by a `GET`
19
+ * proving persistence is unwritable with the first two, since a literal
20
+ * hard-codes a resource the evaluator never created and `any` matches
21
+ * unrelated reads. "Earlier" is earlier in the capture graph's own topological
22
+ * order, which `binding-cycle` makes exist. It is deliberately not AD-39's
23
+ * `after` clause: `nested-temporal-clause` already rejects every `after` cycle,
24
+ * so a capture forced to follow an `after` edge would leave `binding-cycle`
25
+ * unfireable. At score time the ordering is the record's `sequence`.
26
+ *
27
+ * `{ principal }` names a principal `testData.principals` declares. The two
28
+ * critical-severity cross-user behaviours (act as A, read as B, must be denied
29
+ * or absent) bind a step to an account that is neither a literal, since AD-19
30
+ * forbids credential values in declarations, nor an earlier step's output,
31
+ * since accounts are provisioned outside the observation stream. The name is
32
+ * an opaque label carrying no account identifier, credential, or subject data
33
+ * (AD-18). An undeclared name fires `undeclared-mandatory-input` in strict
34
+ * mode; a Zod refinement here would be a cross-subtree constraint the export
35
+ * cannot carry, and the published-schema differential sweep synthesises a
36
+ * union-branch witness that would expose the disagreement.
37
+ *
38
+ * A captured pointer resolves to a declared scalar with no transform applied,
39
+ * which keeps AD-4's ban on arithmetic, projection, and user-defined functions
40
+ * holding by construction: the grammar has no place to write one.
10
41
  */
42
+ /**
43
+ * The first two members, named individually so the probe-side selector under
44
+ * AD-40 admits exactly these two and nothing else. Bare `export const` with no
45
+ * `.meta({ id })`: an id would collapse the branches to `$ref`s and mint new
46
+ * `$defs`, so the export stays byte-identical.
47
+ */
48
+ export const LiteralBindingValue = z.strictObject({ literal: JsonValue });
49
+ export const MatcherBindingValue = z.strictObject({
50
+ matcher: z.enum(['any', 'type-violating']),
51
+ });
11
52
  export const BindingValue = z.union([
12
- z.strictObject({ literal: JsonValue }),
13
- z.strictObject({ matcher: z.enum(['any', 'type-violating']) }),
53
+ LiteralBindingValue,
54
+ MatcherBindingValue,
55
+ z.strictObject({ captured: InteractionPointer }),
56
+ z.strictObject({ principal: Identifier }),
14
57
  ]);
15
58
  /** the constraint identifier the ledger carries for the check below. */
16
59
  export const BINDING_CHANNEL_NON_EMPTY = 'binding-channel-non-empty';
@@ -43,6 +86,27 @@ export const InputBinding = z.strictObject({
43
86
  header: BindingChannel,
44
87
  body: BindingChannel,
45
88
  });
89
+ /**
90
+ * AD-39's declared selector cardinality (owed item 2): what a step means when
91
+ * its selector matches more than one observation was not decided at the
92
+ * architecture layer, so a first-match scorer and a last-match scorer could
93
+ * bind different evidence. The contract now declares the rule per step.
94
+ * `several` under `exactly-one`/`at-most-one` is a named ambiguity condition;
95
+ * a reference function returns it as data. Routing that ambiguity to a
96
+ * verdict rung is later work. `any` is unrelated to
97
+ * `BindingValue`'s `{ matcher: 'any' }` above: same string, different field,
98
+ * no type-level collision. `ExpectedCardinality` (`interface.ts`) is a
99
+ * second, unrelated reuse of the word for AD-20's response-collection
100
+ * cardinality, and its `at-most` mode is a near-miss for `at-most-one` here;
101
+ * different type, different field, no collision, but easy to misread as the
102
+ * same declaration.
103
+ */
104
+ export const SELECTOR_CARDINALITIES = [
105
+ 'exactly-one',
106
+ 'at-most-one',
107
+ 'any',
108
+ ];
109
+ export const SelectorCardinality = z.enum(SELECTOR_CARDINALITIES);
46
110
  /**
47
111
  * AD-39: a step is a selector over observations the evaluator produced, never
48
112
  * an instruction. Its selection predicate is spelled as its two members, the
@@ -55,4 +119,5 @@ export const InteractionStep = z.strictObject({
55
119
  operationId: Identifier,
56
120
  inputBinding: InputBinding,
57
121
  after: Identifier.nullable().describe("The temporal clause: the identifier of an earlier step, or `null`. That the named step carries no clause of its own is AD-39's one-level bound, which fires `nested-temporal-clause` at compile time and is deliberately not enforced here, so the code keeps a shape to fire on."),
122
+ cardinality: SelectorCardinality.describe("AD-39's declared selector cardinality (owed item 2): `exactly-one` or `at-most-one` when the step expects a single matching observation, `any` when several are legitimate. Several matches under `exactly-one`/`at-most-one` is the named ambiguity condition; a reference selection function returns it as data. Required rather than optional, which makes this a BREAKING `schemaVersion` bump under AD-11: adding an optional field is additive, and this field is not optional."),
58
123
  });