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