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,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