eval-quality 0.1.0 → 0.3.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 (179) hide show
  1. package/README.md +142 -67
  2. package/corpus/dev/README.md +20 -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/empty-channel-roles.json +1 -1
  9. package/corpus/dev/contracts/empty-collection-locations.json +1 -1
  10. package/corpus/dev/contracts/empty-request-shapes.json +1 -1
  11. package/corpus/dev/contracts/empty-sibling-groups.json +1 -1
  12. package/corpus/dev/contracts/fragment-selection.json +1 -0
  13. package/corpus/dev/contracts/no-collection-quantifier.json +1 -1
  14. package/corpus/dev/contracts/no-operation-inventory.json +1 -1
  15. package/corpus/dev/contracts/no-read-back-relation.json +1 -1
  16. package/corpus/dev/contracts/no-state-change-marker.json +1 -1
  17. package/corpus/dev/contracts/no-type-violating-step.json +1 -1
  18. package/corpus/dev/contracts/per-key-split-oracles.json +1 -1
  19. package/corpus/dev/contracts/review-corpus.json +1 -0
  20. package/corpus/dev/contracts/satisfied-declarations.json +1 -1
  21. package/corpus/dev/contracts/single-required-response-key.json +1 -1
  22. package/corpus/dev/contracts/split-indicator-oracle.json +1 -1
  23. package/corpus/dev/contracts/unaddressed-parameter-sibling.json +1 -1
  24. package/corpus/dev/contracts/unnamed-reference-set.json +1 -1
  25. package/corpus/dev/contracts/wrong-cardinality-form.json +1 -1
  26. package/corpus/dev/index.json +1 -1
  27. package/dist/application/index.d.ts +2 -0
  28. package/dist/application/index.js +1 -0
  29. package/dist/application/score.d.ts +48 -0
  30. package/dist/application/score.js +186 -0
  31. package/dist/cli/arguments.d.ts +7 -3
  32. package/dist/cli/arguments.js +73 -5
  33. package/dist/cli/exit-codes.d.ts +6 -2
  34. package/dist/cli/exit-codes.js +8 -9
  35. package/dist/cli/main.js +9 -0
  36. package/dist/cli/render.d.ts +3 -6
  37. package/dist/cli/render.js +57 -5
  38. package/dist/cli/run.d.ts +13 -3
  39. package/dist/cli/run.js +136 -16
  40. package/dist/core/canonical/canonicalize.d.ts +12 -0
  41. package/dist/core/canonical/canonicalize.js +18 -4
  42. package/dist/core/compile/bindings.d.ts +89 -0
  43. package/dist/core/compile/bindings.js +334 -0
  44. package/dist/core/compile/compile.d.ts +17 -0
  45. package/dist/core/compile/compile.js +37 -1
  46. package/dist/core/compile/excluded-content.d.ts +11 -0
  47. package/dist/core/compile/excluded-content.js +42 -0
  48. package/dist/core/compile/expression-legality.d.ts +49 -0
  49. package/dist/core/compile/expression-legality.js +158 -38
  50. package/dist/core/compile/forbidden-inputs.d.ts +8 -1
  51. package/dist/core/compile/forbidden-inputs.js +16 -3
  52. package/dist/core/compile/interface-inventory.d.ts +57 -2
  53. package/dist/core/compile/interface-inventory.js +119 -13
  54. package/dist/core/compile/reachability.d.ts +28 -1
  55. package/dist/core/compile/reachability.js +167 -35
  56. package/dist/core/compile/schema-version.d.ts +2 -0
  57. package/dist/core/compile/schema-version.js +25 -0
  58. package/dist/core/compile/sensitivity-witness.d.ts +31 -12
  59. package/dist/core/compile/sensitivity-witness.js +110 -23
  60. package/dist/core/compile/step-reference.d.ts +2 -0
  61. package/dist/core/compile/step-reference.js +49 -0
  62. package/dist/core/coverage/operations.d.ts +62 -0
  63. package/dist/core/coverage/operations.js +57 -0
  64. package/dist/core/coverage/relevance.d.ts +4 -2
  65. package/dist/core/coverage/relevance.js +22 -23
  66. package/dist/core/coverage/satisfaction.d.ts +2 -2
  67. package/dist/core/coverage/satisfaction.js +73 -48
  68. package/dist/core/declared-inputs.d.ts +83 -4
  69. package/dist/core/declared-inputs.js +105 -8
  70. package/dist/core/emit/emit.d.ts +9 -0
  71. package/dist/core/emit/emit.js +214 -0
  72. package/dist/core/emit/private-artifact-digest.d.ts +11 -0
  73. package/dist/core/emit/private-artifact-digest.js +31 -0
  74. package/dist/core/evaluate/evidence-resolution.d.ts +10 -3
  75. package/dist/core/evaluate/evidence-resolution.js +74 -11
  76. package/dist/core/evaluate/operators.d.ts +22 -0
  77. package/dist/core/evaluate/operators.js +17 -1
  78. package/dist/core/evaluate/resolution.d.ts +14 -3
  79. package/dist/core/evaluate/resolution.js +63 -3
  80. package/dist/core/excluded-content.d.ts +65 -0
  81. package/dist/core/excluded-content.js +113 -0
  82. package/dist/core/failure-codes.d.ts +2 -2
  83. package/dist/core/failure-codes.js +7 -2
  84. package/dist/core/ingest/conditions.d.ts +201 -0
  85. package/dist/core/ingest/conditions.js +48 -0
  86. package/dist/core/ingest/index.d.ts +13 -0
  87. package/dist/core/ingest/index.js +11 -0
  88. package/dist/core/ingest/ingest.d.ts +68 -0
  89. package/dist/core/ingest/ingest.js +318 -0
  90. package/dist/core/lineage/stage-table.d.ts +21 -4
  91. package/dist/core/lineage/stage-table.js +35 -3
  92. package/dist/core/preflight/plan.d.ts +7 -5
  93. package/dist/core/preflight/plan.js +75 -32
  94. package/dist/core/preflight/projection.d.ts +6 -3
  95. package/dist/core/preflight/projection.js +22 -2
  96. package/dist/core/preflight/reduce.js +47 -7
  97. package/dist/core/preflight/witness-evidence.d.ts +5 -5
  98. package/dist/core/preflight/witness-evidence.js +73 -20
  99. package/dist/core/schemas/artifact.d.ts +1049 -30
  100. package/dist/core/schemas/constraint-ledger.js +51 -0
  101. package/dist/core/schemas/defect-signature.d.ts +695 -0
  102. package/dist/core/schemas/defect-signature.js +175 -0
  103. package/dist/core/schemas/eval-contract.d.ts +550 -12
  104. package/dist/core/schemas/eval-contract.js +32 -2
  105. package/dist/core/schemas/evidence-artifact.d.ts +109 -19
  106. package/dist/core/schemas/evidence-artifact.js +64 -8
  107. package/dist/core/schemas/interface.d.ts +741 -13
  108. package/dist/core/schemas/interface.js +120 -7
  109. package/dist/core/schemas/isolation-manifest.js +16 -9
  110. package/dist/core/schemas/plan.d.ts +329 -3
  111. package/dist/core/schemas/plan.js +89 -3
  112. package/dist/core/schemas/pointer.d.ts +47 -1
  113. package/dist/core/schemas/pointer.js +89 -8
  114. package/dist/core/schemas/port-messages.d.ts +194 -10
  115. package/dist/core/schemas/port-messages.js +73 -6
  116. package/dist/core/schemas/probe-body.d.ts +18 -0
  117. package/dist/core/schemas/probe-body.js +13 -0
  118. package/dist/core/schemas/probe-qualification.d.ts +168 -0
  119. package/dist/core/schemas/probe-qualification.js +106 -0
  120. package/dist/core/schemas/probe.d.ts +431 -0
  121. package/dist/core/schemas/probe.js +6 -2
  122. package/dist/core/schemas/scoring-policy.d.ts +1 -0
  123. package/dist/core/schemas/scoring-policy.js +6 -1
  124. package/dist/core/schemas/sealed-evaluator-brief.d.ts +1 -0
  125. package/dist/core/schemas/sealed-evaluator-brief.js +4 -1
  126. package/dist/core/schemas/sealed-run-record.d.ts +161 -12
  127. package/dist/core/schemas/sealed-run-record.js +92 -15
  128. package/dist/core/schemas/sensitivity-witness.d.ts +108 -6
  129. package/dist/core/schemas/sensitivity-witness.js +61 -5
  130. package/dist/core/score/binding-order.d.ts +31 -0
  131. package/dist/core/score/binding-order.js +88 -0
  132. package/dist/core/score/bindings.d.ts +93 -0
  133. package/dist/core/score/bindings.js +309 -0
  134. package/dist/core/score/ladder-table.d.ts +23 -0
  135. package/dist/core/score/ladder-table.js +109 -0
  136. package/dist/core/score/ladder.d.ts +229 -0
  137. package/dist/core/score/ladder.js +464 -0
  138. package/dist/core/score/mode-agreement.d.ts +30 -0
  139. package/dist/core/score/mode-agreement.js +16 -0
  140. package/dist/core/score/outcome-table.d.ts +17 -0
  141. package/dist/core/score/outcome-table.js +172 -0
  142. package/dist/core/score/outcome.d.ts +466 -0
  143. package/dist/core/score/outcome.js +562 -0
  144. package/dist/core/score/qualification.d.ts +83 -0
  145. package/dist/core/score/qualification.js +645 -0
  146. package/dist/core/score/quotation.d.ts +56 -0
  147. package/dist/core/score/quotation.js +226 -0
  148. package/dist/core/score/reduce-trials.d.ts +80 -0
  149. package/dist/core/score/reduce-trials.js +90 -0
  150. package/dist/core/score/score.d.ts +72 -0
  151. package/dist/core/score/score.js +607 -0
  152. package/dist/core/score/selection.d.ts +80 -0
  153. package/dist/core/score/selection.js +98 -0
  154. package/dist/core/score/strength.d.ts +58 -0
  155. package/dist/core/score/strength.js +227 -0
  156. package/dist/core/score/witness.d.ts +138 -0
  157. package/dist/core/score/witness.js +320 -0
  158. package/dist/core/seal/derived-reference.d.ts +2 -9
  159. package/dist/core/seal/derived-reference.js +293 -46
  160. package/dist/core/seal/plan-index.d.ts +18 -3
  161. package/dist/core/seal/plan-index.js +44 -8
  162. package/dist/core/seal/seal.js +8 -3
  163. package/dist/core/stage-contracts.d.ts +62 -2
  164. package/dist/index.d.ts +1 -1
  165. package/dist/index.js +1 -1
  166. package/dist/ports/environment-probe-port.d.ts +60 -4
  167. package/dist/testing/index.d.ts +2 -0
  168. package/dist/testing/index.js +1 -0
  169. package/dist/testing/probe-conformance.d.ts +29 -0
  170. package/dist/testing/probe-conformance.js +34 -5
  171. package/package.json +12 -3
  172. package/schemas/eval-contract.schema.json +1787 -797
  173. package/schemas/evidence-artifact.schema.json +126 -9
  174. package/schemas/isolation-manifest.schema.json +17 -10
  175. package/schemas/probe.schema.json +636 -5
  176. package/schemas/rubric.schema.json +1 -1
  177. package/schemas/scoring-policy.schema.json +8 -1
  178. package/schemas/sealed-evaluator-brief.schema.json +11 -1
  179. package/schemas/sealed-run-record.schema.json +314 -40
@@ -0,0 +1,320 @@
1
+ /**
2
+ * AD-40's witness match: whether a finding cited against a probe actually
3
+ * witnessed the defect that probe seeded.
4
+ *
5
+ * This is the input that makes non-detection reachable. Without it, an oracle
6
+ * that correctly confirmed an untouched behaviour and one that failed to detect
7
+ * the seeded defect present identical inputs, so any table obeying AD-3 must
8
+ * give both the same answer and the catch rate is 1.00 by construction.
9
+ *
10
+ * Pure and total on its ordinary path, and no AD-6 outcome state is assigned
11
+ * here. The result is a fact about evidence; turning it into an outcome state
12
+ * is the decision procedure's.
13
+ *
14
+ * One deliberate non-totality: a `RuntimeFault` from the shipped evaluator
15
+ * propagates undecorated. A fault never becomes a verdict, and AD-28 makes it
16
+ * an invalidating condition under AD-21 rather than a behavioural result, so
17
+ * catching one here would convert AD-21's fault exit into a scored run. The
18
+ * qualification gate rejects every operand class that makes the evaluator's
19
+ * four plain-`Error` sites reachable, which leaves exactly two data-dependent
20
+ * `RuntimeFault`s: an exhausted regex budget and a non-canonicalizable value.
21
+ */
22
+ import { requestShapeOf } from '../declared-inputs.js';
23
+ import { makeResolveOperand } from '../evaluate/evidence-resolution.js';
24
+ import { resolveCheck } from '../evaluate/resolution.js';
25
+ import { makeWitnessPointerDenotesCollection } from '../preflight/witness-evidence.js';
26
+ import { OBSERVED_STEP_ID } from '../schemas/defect-signature.js';
27
+ import { INPUT_CHANNELS } from '../schemas/pointer.js';
28
+ import { deepEquals, jsonTypeOf } from './bindings.js';
29
+ import { resolveHomeOperation } from './qualification.js';
30
+ /**
31
+ * A module constant, following pre-flight's precedent for the same problem:
32
+ * `resolveCheck`'s budget parameter is a bare required number whose declared
33
+ * home is the scoring policy, and a probe is not scored under a policy at match
34
+ * time. Two implementations choosing two budgets would disagree about when the
35
+ * evaluator reports an exhausted budget, which is the disagreement one shared
36
+ * constant removes.
37
+ */
38
+ export const PROBE_REGEX_MATCH_STEP_BUDGET = 1_000_000;
39
+ /**
40
+ * The six results, declared in the evaluation order the match applies, so a
41
+ * reader cannot mistake this constant for a different ordering.
42
+ *
43
+ * - `unexercised`: the evaluator never invoked the home operation.
44
+ * - `unwitnessed-claim`: a defect finding cited this probe and cited home-operation
45
+ * observations, none of which satisfies the condition. An AD-32
46
+ * declared-versus-observed inconsistency, and it outranks a competing
47
+ * `matched` so a broken reporter cannot mask it.
48
+ * - `matched`: a defect finding cited an observation the condition satisfies.
49
+ * - `manifested-unclaimed`: the condition was satisfied and no finding claimed
50
+ * it. This is what makes non-detection reachable.
51
+ * - `not-triggered`: the system was examined and did not manifest the seeded
52
+ * defect. AD-6 calls this the common case on any defect probe. Named for what
53
+ * it is rather than "not manifested", because AD-40 already uses that phrase
54
+ * for the vacuous case and the two route to opposite verdicts.
55
+ * - `vacuous`: every candidate resolved insufficient-evidence, so the corpus
56
+ * presented no defect to detect. A fact about the instrument.
57
+ */
58
+ export const PROBE_WITNESS_RESULTS = [
59
+ 'unexercised',
60
+ 'unwitnessed-claim',
61
+ 'matched',
62
+ 'manifested-unclaimed',
63
+ 'not-triggered',
64
+ 'vacuous',
65
+ ];
66
+ /**
67
+ * Whether one observation's recorded call inputs satisfy the signature's
68
+ * selector, following the shipped contract-side binding filter rather than
69
+ * re-deriving it: the key-presence check binds both admitted members, a `null`
70
+ * observed channel against a non-null binding channel fails, and an
71
+ * indeterminate declared type fails closed because it cannot prove a violation.
72
+ *
73
+ * The declared type comes from the signature's own home operation, since a
74
+ * probe has no interaction plan to index.
75
+ */
76
+ function selectorAdmits(binding, observation, operation) {
77
+ for (const channel of INPUT_CHANNELS) {
78
+ const channelBinding = binding[channel];
79
+ if (channelBinding === null)
80
+ continue;
81
+ const observed = observation.callInputs[channel];
82
+ if (observed === null)
83
+ return false;
84
+ for (const key of Object.keys(channelBinding)) {
85
+ const value = channelBinding[key];
86
+ if (value === undefined)
87
+ continue;
88
+ if (!Object.hasOwn(observed, key))
89
+ return false;
90
+ const actual = observed[key];
91
+ if ('literal' in value) {
92
+ if (!deepEquals(actual, value.literal))
93
+ return false;
94
+ continue;
95
+ }
96
+ if (value.matcher === 'any')
97
+ continue;
98
+ const declared = requestShapeOf(operation, channel)?.types[key];
99
+ if (declared === undefined || declared === null)
100
+ return false;
101
+ if (jsonTypeOf(actual) === declared)
102
+ return false;
103
+ }
104
+ }
105
+ return true;
106
+ }
107
+ // Ascending `sequence`, `observationId` as tiebreak: the same comparator
108
+ // `selectObservations` uses, so array position is never read as ordering and a
109
+ // permuted record returns byte-identical identifiers in the same order.
110
+ const bySequence = (a, b) => a.sequence - b.sequence || (a.observationId < b.observationId ? -1 : 1);
111
+ /**
112
+ * Resolves the discriminating condition over one observation through the
113
+ * shipped evaluator, with no adapter: a resolver map holding exactly the one
114
+ * reserved key, this operation's own collection predicate, and the module
115
+ * budget.
116
+ *
117
+ * The map is built fresh per observation and is never merged with a plan's
118
+ * observations. That is what keeps the reserved identifier safe even against a
119
+ * contract that declares a step by the same name, and it is an invariant rather
120
+ * than an accident.
121
+ */
122
+ function resolveCondition(signature, observation, operation, artifactPath) {
123
+ return resolveCheck(signature.condition.predicate, makeResolveOperand({ [OBSERVED_STEP_ID]: observation }, {}), makeWitnessPointerDenotesCollection(operation),
124
+ // No reference sets, matching the empty members map above: the
125
+ // qualification gate rejects a reference-set operand in a defect
126
+ // signature under `condition-reference-set-operand`.
127
+ {}, PROBE_REGEX_MATCH_STEP_BUDGET, artifactPath).resolution;
128
+ }
129
+ const defectFindingsFor = (findings, probeId) => findings.filter((finding) => finding.findingType === 'defect' && finding.probeId === probeId);
130
+ /**
131
+ * AD-40's deterministic witness match over one probe and one sealed run record.
132
+ *
133
+ * The candidates are partitioned by AD-4 resolution into satisfying, refuting,
134
+ * and inconclusive, and the result is read off that partition rather than off a
135
+ * precedence list. A partition is exhaustive and totally ordered over the whole
136
+ * input space; a precedence list left mixed refuting/inconclusive with no home
137
+ * and let an empty candidate set satisfy two rows that route to opposite
138
+ * verdicts.
139
+ *
140
+ * The verdict reads cited identifiers alone and never quotation. Quoted evidence
141
+ * that appears in no cited observation is a separate audit, and its invalidation
142
+ * belongs to ingest.
143
+ *
144
+ * No output reads an array's position. Observation identifiers follow the
145
+ * record's `sequence`; finding identifiers are sorted by identifier, because
146
+ * `findings` carries no ordering field of its own.
147
+ */
148
+ export function matchProbeWitness(probe, interfaces, record) {
149
+ const signature = probe.defectSignature;
150
+ const homeOperation = resolveHomeOperation(signature, interfaces);
151
+ const homeOperationResolved = homeOperation !== null;
152
+ const onHomeOperation = homeOperation === null
153
+ ? []
154
+ : record.observations.filter((observation) => observation.operationId === homeOperation.operationId);
155
+ // AD-40's whole exclusion mechanism, and the only one available: an aborted
156
+ // in-flight call never becomes an observation at all, so there is no
157
+ // completion predicate to write, and a "no response channel is set" test
158
+ // would exclude a legitimately sparse completed call while catching nothing.
159
+ // A harness baseline and a fixture set-up call are excluded only where the
160
+ // caller recorded them as `baseline`, which is caller-attested under AD-32
161
+ // and is stated as trust rather than assumed.
162
+ const exercisedObservations = onHomeOperation.filter((observation) => observation.provenance === 'evaluator-chosen');
163
+ const exercised = exercisedObservations.length > 0;
164
+ const empty = {
165
+ result: 'unexercised',
166
+ basis: 'measured',
167
+ homeOperationResolved,
168
+ exercised: false,
169
+ observationIds: [],
170
+ partition: { satisfying: [], refuting: [], inconclusive: [] },
171
+ partitionSizes: { satisfying: 0, refuting: 0, inconclusive: 0 },
172
+ witnessObservationIds: [],
173
+ unwitnessedFindingIds: [],
174
+ };
175
+ if (homeOperation === null || !exercised)
176
+ return empty;
177
+ const artifactPath = `Probe[probeId=${probe.probeId}].defectSignature.condition.predicate`;
178
+ const candidates = exercisedObservations
179
+ .filter((observation) => selectorAdmits(signature.condition.selector.inputBinding, observation, homeOperation))
180
+ .sort(bySequence);
181
+ const satisfying = [];
182
+ const refuting = [];
183
+ const inconclusive = [];
184
+ for (const candidate of candidates) {
185
+ const resolution = resolveCondition(signature, candidate, homeOperation, artifactPath);
186
+ if (resolution === 'true')
187
+ satisfying.push(candidate.observationId);
188
+ else if (resolution === 'false')
189
+ refuting.push(candidate.observationId);
190
+ else
191
+ inconclusive.push(candidate.observationId);
192
+ }
193
+ const satisfyingSet = new Set(satisfying);
194
+ const homeOperationIds = new Set(onHomeOperation.map((observation) => observation.observationId));
195
+ const witnessObservationIds = [];
196
+ const unwitnessedFindingIds = [];
197
+ for (const finding of defectFindingsFor(record.findings, probe.probeId)) {
198
+ const citedHome = finding.observationIds.filter((id) => homeOperationIds.has(id));
199
+ // A citation that never touches the home operation is not a claim about
200
+ // this signature at all: it is an unexpected real defect preserved under
201
+ // AD-23, and collapsing the two would invalidate a run over a genuine
202
+ // discovery.
203
+ if (citedHome.length === 0)
204
+ continue;
205
+ const witnesses = citedHome.filter((id) => satisfyingSet.has(id));
206
+ if (witnesses.length === 0)
207
+ unwitnessedFindingIds.push(finding.findingId);
208
+ else
209
+ witnessObservationIds.push(...witnesses);
210
+ }
211
+ // Returned in the partition's own ascending order rather than in citation
212
+ // order, and deduplicated: two findings citing one observation witness one
213
+ // observation.
214
+ const witnessIds = satisfying.filter((id) => witnessObservationIds.includes(id));
215
+ // `record.findings` is a bare array with no ordering field, so its position
216
+ // is the same non-meaning `sequence` was added to remove from
217
+ // `observations`. Sorted by identifier, which every finding carries, so a
218
+ // permuted findings array returns byte-identical output here too.
219
+ unwitnessedFindingIds.sort();
220
+ const observationIds = candidates.map((observation) => observation.observationId);
221
+ const partition = { satisfying, refuting, inconclusive };
222
+ const shared = {
223
+ basis: 'measured',
224
+ homeOperationResolved,
225
+ exercised: true,
226
+ observationIds,
227
+ partition,
228
+ partitionSizes: {
229
+ satisfying: satisfying.length,
230
+ refuting: refuting.length,
231
+ inconclusive: inconclusive.length,
232
+ },
233
+ witnessObservationIds: witnessIds,
234
+ unwitnessedFindingIds,
235
+ };
236
+ // First match wins, in `PROBE_WITNESS_RESULTS`'s own order. Rows two and
237
+ // three deliberately overlap where one finding cites a satisfying
238
+ // observation while another claims a witness that is not one; that case
239
+ // resolves in the inconsistency's favour, because an invalidating AD-32
240
+ // disagreement must not be masked by a catch on the same probe.
241
+ if (unwitnessedFindingIds.length > 0) {
242
+ return { ...shared, result: 'unwitnessed-claim' };
243
+ }
244
+ if (satisfying.length > 0) {
245
+ return {
246
+ ...shared,
247
+ result: witnessIds.length > 0 ? 'matched' : 'manifested-unclaimed',
248
+ };
249
+ }
250
+ // The empty candidate set lands here explicitly rather than being swept into
251
+ // `vacuous` by a universal quantifier that is vacuously true over it: a probe
252
+ // exercised whose selector matched nothing was never triggered. AD-40 backs
253
+ // the split: an insufficient-evidence resolution counts as neither detection
254
+ // nor manifestation, while a `false` resolution is the system examined and
255
+ // behaving.
256
+ if (candidates.length === 0 || refuting.length > 0) {
257
+ return { ...shared, result: 'not-triggered' };
258
+ }
259
+ return { ...shared, result: 'vacuous' };
260
+ }
261
+ /**
262
+ * Sorts every defect finding in the record into the four buckets AD-40 and
263
+ * AD-23 between them require, so no finding is silently dropped and none is
264
+ * counted as a catch it did not earn.
265
+ *
266
+ * Defect findings only: they are the only type that enters a detection measure.
267
+ * The uncited case proper, a finding naming no oracle, is a different rule and
268
+ * a different record.
269
+ */
270
+ export function mapFindings(probes, interfaces, record) {
271
+ const byProbeId = new Map(probes.map((probe) => [probe.probeId, probe]));
272
+ const operationIdOf = new Map();
273
+ const mapped = [];
274
+ const unmapped = [];
275
+ const dangling = [];
276
+ const signatureless = [];
277
+ const observationOperation = new Map(record.observations.map((observation) => [
278
+ observation.observationId,
279
+ observation.operationId,
280
+ ]));
281
+ for (const finding of record.findings) {
282
+ if (finding.findingType !== 'defect')
283
+ continue;
284
+ const entry = {
285
+ findingId: finding.findingId,
286
+ probeId: finding.probeId,
287
+ };
288
+ const probe = byProbeId.get(finding.probeId);
289
+ if (probe === undefined) {
290
+ dangling.push(entry);
291
+ continue;
292
+ }
293
+ const signature = probe.expectedClean ? null : probe.defectSignature;
294
+ if (signature === null) {
295
+ signatureless.push(entry);
296
+ continue;
297
+ }
298
+ if (!operationIdOf.has(probe.probeId)) {
299
+ const operation = resolveHomeOperation(signature, interfaces);
300
+ operationIdOf.set(probe.probeId, operation?.operationId ?? null);
301
+ }
302
+ const homeOperationId = operationIdOf.get(probe.probeId) ?? null;
303
+ const touchesHome = homeOperationId !== null &&
304
+ finding.observationIds.some((id) => observationOperation.get(id) === homeOperationId);
305
+ if (touchesHome)
306
+ mapped.push(entry);
307
+ else
308
+ unmapped.push(entry);
309
+ }
310
+ // Sorted for the same reason `unwitnessedFindingIds` is: a bucket's order
311
+ // would otherwise be `record.findings`'s array position, which carries no
312
+ // meaning and is not a total order the record declares.
313
+ const byFindingId = (a, b) => a.findingId < b.findingId ? -1 : a.findingId > b.findingId ? 1 : 0;
314
+ return {
315
+ mapped: mapped.sort(byFindingId),
316
+ unmapped: unmapped.sort(byFindingId),
317
+ dangling: dangling.sort(byFindingId),
318
+ signatureless: signatureless.sort(byFindingId),
319
+ };
320
+ }
@@ -1,11 +1,4 @@
1
- /**
2
- * The derived-reference vocabulary (AD-16). Renders an evidence-target
3
- * pointer, or a temporally-paired pair, as a description of the step's
4
- * operation and selection predicate; the step's own identifier never appears.
5
- * Resolves pointers through a `PlanIndex` into a phrase, kept apart from
6
- * `plan-index.ts`'s resolving and `direction-prose.ts`'s relation templates.
7
- */
8
- import type { Operation } from '../schemas/interface.ts';
1
+ import type { AnyOperation } from '../schemas/interface.ts';
9
2
  import type { InteractionStep } from '../schemas/plan.ts';
10
3
  import { type PlanIndex } from './plan-index.ts';
11
4
  /**
@@ -21,7 +14,7 @@ import { type PlanIndex } from './plan-index.ts';
21
14
  * duplicate `stepId` check if two siblings still collide after full
22
15
  * escalation.
23
16
  */
24
- export declare function renderStepReference(step: InteractionStep, operation: Operation, siblings: readonly InteractionStep[]): string;
17
+ export declare function renderStepReference(step: InteractionStep, operation: AnyOperation, siblings: readonly InteractionStep[], index: PlanIndex): string;
25
18
  /**
26
19
  * Renders every evidence target a direction declares as one canonically
27
20
  * ordered, order-independent clause: byte-identical under any permutation of