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
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Owed item 2's fix: a step selects observations by `operationId`, ordered by
3
+ * the record's monotonic `sequence` (ADR-006 forbids array position as
4
+ * ordering). `selectObservations` reports a several-match ambiguity as data;
5
+ * no first-match or last-match convention picks a winner. Pure and total:
6
+ * every input produces a defined result, nothing throws, and no AD-6 outcome
7
+ * state is assigned. Assigning outcome state is AD-33's reference decision
8
+ * procedure's job.
9
+ *
10
+ * `resolveTemporalAnchor` is a separate, narrower resolution built on top:
11
+ * picking a concrete point in time for an `any`-cardinality temporal anchor
12
+ * with several matches, by lowest sequence. The policy above still holds
13
+ * here: a single-valued cardinality's several-match ambiguity still comes
14
+ * back unresolved from `resolveTemporalAnchor` too.
15
+ *
16
+ * No stage claims this module yet; `stage-table.ts`'s `score` row stays
17
+ * `module: null` throughout.
18
+ */
19
+ /**
20
+ * Matches `step.operationId` against every observation's `operationId`,
21
+ * ordered by `sequence` ascending. Reads no other field of `step`: whether
22
+ * `several` is the named ambiguity condition or a legitimate `any`-cardinality
23
+ * match is a fact about the step's declared cardinality, decided by whoever
24
+ * reads this result, not by this function.
25
+ *
26
+ * Sorts a copy; the input `observations` array is never mutated, and its own
27
+ * order is never read as meaning anything (NFR9: a permutation of the same
28
+ * observations yields byte-identical `matchedObservationIds`). The schema
29
+ * layer enforces per-record `sequence` uniqueness, so a real `SealedRunRecord`
30
+ * never presents a tie. `observationId` is the secondary sort key regardless:
31
+ * this function's own permutation invariance holds even against a hand-built
32
+ * or malformed `observations` array carrying a duplicate or non-finite
33
+ * `sequence`, decided by this comparator alone.
34
+ *
35
+ * Matching on `operationId` alone, ignoring `step.inputBinding`, is a
36
+ * deliberate scope boundary that stays: candidate-tuple resolution lives in
37
+ * `selectWithBindings` (`score/bindings.ts`), which wraps this function and
38
+ * filters its matches against the step's own resolved bindings. Splitting them
39
+ * keeps this function's permutation guarantee provable on its own.
40
+ *
41
+ * `tests/seal/fixtures.ts`'s `irreducibleCollisionPair` was once cited here as
42
+ * a case "distinguishable only by input binding". It is not: its two steps
43
+ * bind nothing in any channel, so a filter over zero bindings separates
44
+ * nothing and both stay `several` even with `selectWithBindings`. The pair
45
+ * that separates is `literalCollisionPair`, whose two steps bind one key to
46
+ * two different literals.
47
+ */
48
+ export function selectObservations(step, observations) {
49
+ const matched = observations
50
+ .filter((observation) => observation.operationId === step.operationId)
51
+ .sort((a, b) => a.sequence - b.sequence || (a.observationId < b.observationId ? -1 : 1));
52
+ const matchedObservationIds = matched.map((observation) => observation.observationId);
53
+ const result = matchedObservationIds.length === 0
54
+ ? 'none'
55
+ : matchedObservationIds.length === 1
56
+ ? 'one'
57
+ : 'several';
58
+ return { result, matchedObservationIds };
59
+ }
60
+ /**
61
+ * Resolves an `after` temporal clause to the single observation it denotes,
62
+ * built directly from `selectObservations`'s own result over the anchor.
63
+ *
64
+ * Takes the anchor step itself, already resolved from the dependent step's
65
+ * `after` identifier (a one-line lookup against whichever declared plan the
66
+ * caller holds). A step whose clause is `null`, or whose clause names a step
67
+ * the plan does not declare (AD-39's permissive dangling reference), names no
68
+ * anchor and never reaches this function.
69
+ */
70
+ export function resolveTemporalAnchor(anchorStep, observations) {
71
+ const selection = selectObservations(anchorStep, observations);
72
+ if (selection.result === 'one') {
73
+ return {
74
+ resolved: true,
75
+ // `result === 'one'` guarantees exactly one member.
76
+ observationId: selection
77
+ .matchedObservationIds[0],
78
+ matchedObservationIds: selection.matchedObservationIds,
79
+ };
80
+ }
81
+ if (selection.result === 'several' && anchorStep.cardinality === 'any') {
82
+ // Ascending-`sequence` order already holds the lowest-sequence match first.
83
+ return {
84
+ resolved: true,
85
+ observationId: selection
86
+ .matchedObservationIds[0],
87
+ matchedObservationIds: selection.matchedObservationIds,
88
+ };
89
+ }
90
+ // `none`, or `several` under a single-valued cardinality: no single
91
+ // observation resolves. Reported as data; routing this to a verdict rung
92
+ // is later work.
93
+ return {
94
+ resolved: false,
95
+ result: selection.result,
96
+ matchedObservationIds: selection.matchedObservationIds,
97
+ };
98
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * AD-7's rate vector and its four-valued dominance relation.
3
+ *
4
+ * `buildStrengthVector` is a pure aggregation over a qualified probe set and
5
+ * the trial-set reducer's per-probe results: unweighted, per probe class,
6
+ * unique qualified probe identifiers over unique qualified probe identifiers
7
+ * exercised, with canary probes and clean controls excluded regardless of
8
+ * class or trial outcome. `compareDominance` takes two already-computed
9
+ * results and never re-derives a vector, reads a port, a corpus, or a clock;
10
+ * comparability is checked first, and the severity-floor override can only
11
+ * push the relation toward `incomparable`.
12
+ */
13
+ import { type Severity } from '../schemas/eval-contract.ts';
14
+ import type { Outcome, Strength, StrengthVector } from '../schemas/evidence-artifact.ts';
15
+ import type { QualifiedProbe } from './qualification.ts';
16
+ import type { TrialSetResult } from './reduce-trials.ts';
17
+ export declare const DOMINANCE_RELATIONS: readonly ['a-dominates-b', 'b-dominates-a', 'equivalent', 'incomparable'];
18
+ export type DominanceRelationValue = (typeof DOMINANCE_RELATIONS)[number];
19
+ /**
20
+ * The slice the dominance comparator reads: the aggregate `Strength` plus the
21
+ * per-probe `outcomes` array the severity-floor override needs, since that
22
+ * identity is lost once probes are aggregated into `ClassStrength` counts,
23
+ * and the `comparabilityKey` the comparator checks before comparing anything
24
+ * else. Every field already lives on `EvidenceArtifact`; this is the read
25
+ * projection the comparator needs from it, not a new artifact shape.
26
+ */
27
+ export type ComparableResult = {
28
+ readonly outcomes: readonly Outcome[];
29
+ readonly strength: Strength;
30
+ readonly comparabilityKey: string;
31
+ };
32
+ /**
33
+ * AD-7's rate vector: per probe class, the catch rate over unique qualified
34
+ * probe identifiers, with raw counts alongside. `admitted` carries each
35
+ * probe's identifier once by construction, so grouping by class needs no
36
+ * deduplication of its own.
37
+ */
38
+ export declare function buildStrengthVector(admitted: readonly QualifiedProbe[], results: ReadonlyMap<string, TrialSetResult>): StrengthVector;
39
+ /**
40
+ * AD-7's four-valued dominance relation. `comparabilityKey` and each side's
41
+ * own `strength.comparable` are checked before any component-wise comparison
42
+ * runs: a key mismatch means the two runs are not measuring a shared probe
43
+ * set, and `comparable: false` means AD-21 already marked that one side's own
44
+ * vector as thinner than the policy's declared minimum, so a `caught`/`rate`
45
+ * on it is not fit to decide a comparison either way. The severity-floor
46
+ * override runs only against the side the raw comparison favoured, and only
47
+ * ever downgrades that result to `incomparable`.
48
+ */
49
+ export declare function compareDominance(a: ComparableResult, b: ComparableResult, severityFloor: Severity): DominanceRelationValue;
@@ -0,0 +1,193 @@
1
+ /**
2
+ * AD-7's rate vector and its four-valued dominance relation.
3
+ *
4
+ * `buildStrengthVector` is a pure aggregation over a qualified probe set and
5
+ * the trial-set reducer's per-probe results: unweighted, per probe class,
6
+ * unique qualified probe identifiers over unique qualified probe identifiers
7
+ * exercised, with canary probes and clean controls excluded regardless of
8
+ * class or trial outcome. `compareDominance` takes two already-computed
9
+ * results and never re-derives a vector, reads a port, a corpus, or a clock;
10
+ * comparability is checked first, and the severity-floor override can only
11
+ * push the relation toward `incomparable`.
12
+ */
13
+ import { SEVERITY_LEVELS } from '../schemas/eval-contract.js';
14
+ export const DOMINANCE_RELATIONS = [
15
+ 'a-dominates-b',
16
+ 'b-dominates-a',
17
+ 'equivalent',
18
+ 'incomparable',
19
+ ];
20
+ const STRENGTH_VECTOR_CLASSES = [
21
+ 'defect',
22
+ 'gameability',
23
+ 'zero-action',
24
+ ];
25
+ /**
26
+ * `admitted`, after excluding `canary` and every `expectedClean: true` probe:
27
+ * AD-7's "canary probes and clean controls never enter the vector" applies
28
+ * regardless of class or trial outcome, and a canary carries `expectedClean:
29
+ * false` on its own schema branch, so both conditions are checked.
30
+ */
31
+ const vectorEligible = (admitted) => admitted.filter(({ probe }) => probe.probeClass !== 'canary' && !probe.expectedClean);
32
+ /**
33
+ * One class's aggregate, or `null` when the eligible set admits no probe of
34
+ * that class. A probe with no `TrialSetResult`, or one that is `exercised:
35
+ * false`, contributes to neither `caught` nor `exercised`, matching the
36
+ * reducer's own "zero valid trials excludes a probe entirely" rule. A class
37
+ * with admitted probes but zero exercised ones is still a present
38
+ * `ClassStrength` of `{ caught: 0, exercised: 0, rate: null }`, never a
39
+ * `null` class: `rate`'s nullability exists specifically for that case, and
40
+ * collapsing the whole class to `null` would make it unobservable.
41
+ */
42
+ const classStrengthOf = (probesInClass, results) => {
43
+ if (probesInClass.length === 0)
44
+ return null;
45
+ let exercised = 0;
46
+ let caught = 0;
47
+ for (const { probe } of probesInClass) {
48
+ const result = results.get(probe.probeId);
49
+ if (result === undefined || !result.exercised)
50
+ continue;
51
+ exercised += 1;
52
+ if (result.caught)
53
+ caught += 1;
54
+ }
55
+ return {
56
+ exercised,
57
+ caught,
58
+ rate: exercised === 0 ? null : caught / exercised,
59
+ };
60
+ };
61
+ /**
62
+ * AD-7's rate vector: per probe class, the catch rate over unique qualified
63
+ * probe identifiers, with raw counts alongside. `admitted` carries each
64
+ * probe's identifier once by construction, so grouping by class needs no
65
+ * deduplication of its own.
66
+ */
67
+ export function buildStrengthVector(admitted, results) {
68
+ const eligible = vectorEligible(admitted);
69
+ const byClass = Object.fromEntries(STRENGTH_VECTOR_CLASSES.map((probeClass) => [
70
+ probeClass,
71
+ classStrengthOf(eligible.filter(({ probe }) => probe.probeClass === probeClass), results),
72
+ ]));
73
+ return {
74
+ defect: byClass.defect,
75
+ gameability: byClass.gameability,
76
+ 'zero-action': byClass['zero-action'],
77
+ };
78
+ }
79
+ /**
80
+ * A class contributes to the comparison only when it is a non-null
81
+ * `ClassStrength` on both sides and both sides' `rate` is also non-null; a
82
+ * class absent on either side, or present with a `null` rate on either side,
83
+ * carries no comparative evidence and is skipped exactly alike. `equivalent`
84
+ * compares `caught` and `exercised` rather than the derived `rate`, avoiding
85
+ * a floating-point equality check.
86
+ *
87
+ * A class whose two sides tie on `rate` while disagreeing on `caught` or
88
+ * `exercised` contributes to the comparison, blocks `equivalent` (the counts
89
+ * are not equal), and hands neither side a win (neither `rate` is strictly
90
+ * greater). That third possibility has no named outcome of its own in AD-7's
91
+ * three stated cases, and `incomparable` is where a tied vector with no
92
+ * winner on either side belongs: the same value the "no class contributes at
93
+ * all" case already returns.
94
+ */
95
+ function componentComparison(a, b) {
96
+ let aWinsAClass = false;
97
+ let bWinsAClass = false;
98
+ let everyContributingClassEqual = true;
99
+ let contributingClasses = 0;
100
+ for (const key of STRENGTH_VECTOR_CLASSES) {
101
+ const left = a[key];
102
+ const right = b[key];
103
+ if (left === null || right === null)
104
+ continue;
105
+ if (left.rate === null || right.rate === null)
106
+ continue;
107
+ contributingClasses += 1;
108
+ if (left.caught !== right.caught || left.exercised !== right.exercised) {
109
+ everyContributingClassEqual = false;
110
+ }
111
+ if (left.rate > right.rate)
112
+ aWinsAClass = true;
113
+ if (right.rate > left.rate)
114
+ bWinsAClass = true;
115
+ }
116
+ if (contributingClasses === 0)
117
+ return 'incomparable';
118
+ if (everyContributingClassEqual)
119
+ return 'equivalent';
120
+ if (aWinsAClass && !bWinsAClass)
121
+ return 'a-dominates-b';
122
+ if (bWinsAClass && !aWinsAClass)
123
+ return 'b-dominates-a';
124
+ return 'incomparable';
125
+ }
126
+ const atOrAboveFloor = (severity, floor) => SEVERITY_LEVELS.indexOf(severity) >= SEVERITY_LEVELS.indexOf(floor);
127
+ /**
128
+ * Keyed by the first outcome carrying each `probeId`, not the last: two
129
+ * `Outcome` entries sharing one `probeId` is itself a defect somewhere
130
+ * upstream (this map has no way to tell which entry is the real one), and a
131
+ * silent last-write-wins overwrite would drop the earlier entry from the
132
+ * severity-floor scan below with no trace it was ever there.
133
+ */
134
+ const outcomesByProbeId = (outcomes) => {
135
+ const byProbeId = new Map();
136
+ for (const outcome of outcomes) {
137
+ if (outcome.probeId === null)
138
+ continue;
139
+ if (byProbeId.has(outcome.probeId))
140
+ continue;
141
+ byProbeId.set(outcome.probeId, outcome);
142
+ }
143
+ return byProbeId;
144
+ };
145
+ /**
146
+ * Whether `favored` failed to catch a probe that `other` caught at or above
147
+ * `severityFloor`: the condition that disqualifies `favored` from dominating,
148
+ * per AD-7's "a contract that missed a behaviour at or above the scoring
149
+ * policy's severity floor never dominates one that caught it, regardless of
150
+ * the rest of the vector". An outcome with `probeId: null` is not tied to any
151
+ * probe and is outside the vector entirely, so both sides skip it.
152
+ */
153
+ function favoredMissesWhatOtherCaught(favored, other, severityFloor) {
154
+ const favoredByProbeId = outcomesByProbeId(favored.outcomes);
155
+ const otherByProbeId = outcomesByProbeId(other.outcomes);
156
+ for (const [probeId, otherOutcome] of otherByProbeId) {
157
+ if (otherOutcome.state !== 'caught')
158
+ continue;
159
+ if (!atOrAboveFloor(otherOutcome.severity, severityFloor))
160
+ continue;
161
+ const favoredOutcome = favoredByProbeId.get(probeId);
162
+ if (favoredOutcome === undefined || favoredOutcome.state !== 'caught') {
163
+ return true;
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+ /**
169
+ * AD-7's four-valued dominance relation. `comparabilityKey` and each side's
170
+ * own `strength.comparable` are checked before any component-wise comparison
171
+ * runs: a key mismatch means the two runs are not measuring a shared probe
172
+ * set, and `comparable: false` means AD-21 already marked that one side's own
173
+ * vector as thinner than the policy's declared minimum, so a `caught`/`rate`
174
+ * on it is not fit to decide a comparison either way. The severity-floor
175
+ * override runs only against the side the raw comparison favoured, and only
176
+ * ever downgrades that result to `incomparable`.
177
+ */
178
+ export function compareDominance(a, b, severityFloor) {
179
+ if (a.comparabilityKey !== b.comparabilityKey)
180
+ return 'incomparable';
181
+ if (!a.strength.comparable || !b.strength.comparable)
182
+ return 'incomparable';
183
+ const raw = componentComparison(a.strength.vector, b.strength.vector);
184
+ if (raw === 'a-dominates-b' &&
185
+ favoredMissesWhatOtherCaught(a, b, severityFloor)) {
186
+ return 'incomparable';
187
+ }
188
+ if (raw === 'b-dominates-a' &&
189
+ favoredMissesWhatOtherCaught(b, a, severityFloor)) {
190
+ return 'incomparable';
191
+ }
192
+ return raw;
193
+ }
@@ -0,0 +1,117 @@
1
+ import type { DefectSignature } from '../schemas/defect-signature.ts';
2
+ import type { PermittedInterface } from '../schemas/interface.ts';
3
+ import type { Probe } from '../schemas/probe.ts';
4
+ import type { SealedRunRecord } from '../schemas/sealed-run-record.ts';
5
+ /**
6
+ * A module constant, following pre-flight's precedent for the same problem:
7
+ * `resolveCheck`'s budget parameter is a bare required number whose declared
8
+ * home is the scoring policy, and a probe is not scored under a policy at match
9
+ * time. Two implementations choosing two budgets would disagree about when the
10
+ * evaluator reports an exhausted budget, which is the disagreement one shared
11
+ * constant removes.
12
+ */
13
+ export declare const PROBE_REGEX_MATCH_STEP_BUDGET = 1000000;
14
+ /**
15
+ * The six results, declared in the evaluation order the match applies, so a
16
+ * reader cannot mistake this constant for a different ordering.
17
+ *
18
+ * - `unexercised`: the evaluator never invoked the home operation.
19
+ * - `unwitnessed-claim`: a defect finding cited this probe and cited home-operation
20
+ * observations, none of which satisfies the condition. An AD-32
21
+ * declared-versus-observed inconsistency, and it outranks a competing
22
+ * `matched` so a broken reporter cannot mask it.
23
+ * - `matched`: a defect finding cited an observation the condition satisfies.
24
+ * - `manifested-unclaimed`: the condition was satisfied and no finding claimed
25
+ * it. This is what makes non-detection reachable.
26
+ * - `not-triggered`: the system was examined and did not manifest the seeded
27
+ * defect. AD-6 calls this the common case on any defect probe. Named for what
28
+ * it is rather than "not manifested", because AD-40 already uses that phrase
29
+ * for the vacuous case and the two route to opposite verdicts.
30
+ * - `vacuous`: every candidate resolved insufficient-evidence, so the corpus
31
+ * presented no defect to detect. A fact about the instrument.
32
+ */
33
+ export declare const PROBE_WITNESS_RESULTS: readonly ['unexercised', 'unwitnessed-claim', 'matched', 'manifested-unclaimed', 'not-triggered', 'vacuous'];
34
+ export type ProbeWitnessResultValue = (typeof PROBE_WITNESS_RESULTS)[number];
35
+ /** A probe on the seeding branch whose signature is present. */
36
+ export type SignedProbe = Extract<Probe, {
37
+ expectedClean: false;
38
+ }> & {
39
+ readonly defectSignature: DefectSignature;
40
+ };
41
+ export type WitnessPartition = {
42
+ /** the candidates the condition resolved `true` over. */
43
+ readonly satisfying: readonly string[];
44
+ /** the candidates it resolved `false` over: the system examined and behaving. */
45
+ readonly refuting: readonly string[];
46
+ /** the candidates it could not examine. Never detection, and never manifestation. */
47
+ readonly inconclusive: readonly string[];
48
+ };
49
+ export type ProbeWitnessMatch = {
50
+ readonly result: ProbeWitnessResultValue;
51
+ /**
52
+ * AD-40 forbids pooling a quotation-reconstructed detection with a measured
53
+ * catch rate, so every result says which it is. The witness match resolves
54
+ * over cited identifiers and is always `measured`.
55
+ */
56
+ readonly basis: 'measured';
57
+ readonly homeOperationResolved: boolean;
58
+ readonly exercised: boolean;
59
+ /** every candidate identifier the match read, ascending by `sequence`. */
60
+ readonly observationIds: readonly string[];
61
+ readonly partition: WitnessPartition;
62
+ readonly partitionSizes: {
63
+ readonly satisfying: number;
64
+ readonly refuting: number;
65
+ readonly inconclusive: number;
66
+ };
67
+ /** the satisfying observations a defect finding actually cited. */
68
+ readonly witnessObservationIds: readonly string[];
69
+ /** the defect findings whose home-operation citations witness nothing. */
70
+ readonly unwitnessedFindingIds: readonly string[];
71
+ };
72
+ /**
73
+ * AD-40's deterministic witness match over one probe and one sealed run record.
74
+ *
75
+ * The candidates are partitioned by AD-4 resolution into satisfying, refuting,
76
+ * and inconclusive, and the result is read off that partition rather than off a
77
+ * precedence list. A partition is exhaustive and totally ordered over the whole
78
+ * input space; a precedence list left mixed refuting/inconclusive with no home
79
+ * and let an empty candidate set satisfy two rows that route to opposite
80
+ * verdicts.
81
+ *
82
+ * The verdict reads cited identifiers alone and never quotation. Quoted evidence
83
+ * that appears in no cited observation is a separate audit, and its invalidation
84
+ * belongs to ingest.
85
+ *
86
+ * No output reads an array's position. Observation identifiers follow the
87
+ * record's `sequence`; finding identifiers are sorted by identifier, because
88
+ * `findings` carries no ordering field of its own.
89
+ */
90
+ export declare function matchProbeWitness(probe: SignedProbe, interfaces: readonly PermittedInterface[], record: Pick<SealedRunRecord, 'observations' | 'findings'>): ProbeWitnessMatch;
91
+ export type MappedFinding = {
92
+ readonly findingId: string;
93
+ readonly probeId: string;
94
+ };
95
+ export type FindingMap = {
96
+ /** cited to a signed probe, and touching that signature's home operation. */
97
+ readonly mapped: readonly MappedFinding[];
98
+ /**
99
+ * cited to a signed probe whose home operation the cited observations never
100
+ * touch. An unexpected real defect under AD-23, never a catch.
101
+ */
102
+ readonly unmapped: readonly MappedFinding[];
103
+ /** cited to a probe identifier the set does not declare: an AD-32 cross-artifact dangling reference. */
104
+ readonly dangling: readonly MappedFinding[];
105
+ /** cited to a canary or a clean control, which carry no signature to map against. */
106
+ readonly signatureless: readonly MappedFinding[];
107
+ };
108
+ /**
109
+ * Sorts every defect finding in the record into the four buckets AD-40 and
110
+ * AD-23 between them require, so no finding is silently dropped and none is
111
+ * counted as a catch it did not earn.
112
+ *
113
+ * Defect findings only: they are the only type that enters a detection measure.
114
+ * The uncited case proper, a finding naming no oracle, is a different rule and
115
+ * a different record.
116
+ */
117
+ export declare function mapFindings(probes: readonly Probe[], interfaces: readonly PermittedInterface[], record: Pick<SealedRunRecord, 'observations' | 'findings'>): FindingMap;