@variance-authority/scenario 0.1.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/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/archive.d.ts +66 -0
- package/dist/archive.js +222 -0
- package/dist/archive.js.map +1 -0
- package/dist/assessment.d.ts +4 -0
- package/dist/assessment.js +157 -0
- package/dist/assessment.js.map +1 -0
- package/dist/contract.d.ts +190 -0
- package/dist/contract.js +80 -0
- package/dist/contract.js.map +1 -0
- package/dist/execution.d.ts +17 -0
- package/dist/execution.js +181 -0
- package/dist/execution.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/machine.d.ts +33 -0
- package/dist/machine.js +84 -0
- package/dist/machine.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +48 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { BANDS, decidesBand, deriveVariation, explainParting, observableBands, partingOf, } from '@variance-authority/core';
|
|
2
|
+
import { assertScenarioRun } from './execution.js';
|
|
3
|
+
/** Compare two witnessed paths without writing a verdict or mutating either execution. */
|
|
4
|
+
export function assessScenarios(left, right) {
|
|
5
|
+
assertScenarioRun(left);
|
|
6
|
+
assertScenarioRun(right);
|
|
7
|
+
const arrange = compareFrames(left.execution.frames[0], right.execution.frames[0], left, right);
|
|
8
|
+
const leftActs = actFrames(left);
|
|
9
|
+
const rightActs = actFrames(right);
|
|
10
|
+
const aligned = [];
|
|
11
|
+
const unmatched = [];
|
|
12
|
+
let prefix = 0;
|
|
13
|
+
while (prefix < leftActs.length && prefix < rightActs.length) {
|
|
14
|
+
const leftFrame = leftActs[prefix];
|
|
15
|
+
const rightFrame = rightActs[prefix];
|
|
16
|
+
if (!sameAct(leftFrame.act, rightFrame.act))
|
|
17
|
+
break;
|
|
18
|
+
const leftEffect = compareFrames(left.execution.frames[prefix], leftFrame, left, left);
|
|
19
|
+
const rightEffect = compareFrames(right.execution.frames[prefix], rightFrame, right, right);
|
|
20
|
+
aligned.push({
|
|
21
|
+
act: leftFrame.act,
|
|
22
|
+
leftEffect,
|
|
23
|
+
rightEffect,
|
|
24
|
+
divergence: compareEffects(leftEffect, rightEffect),
|
|
25
|
+
});
|
|
26
|
+
prefix += 1;
|
|
27
|
+
}
|
|
28
|
+
for (const frame of leftActs.slice(prefix))
|
|
29
|
+
unmatched.push({ ...frame.act, side: 'left' });
|
|
30
|
+
for (const frame of rightActs.slice(prefix))
|
|
31
|
+
unmatched.push({ ...frame.act, side: 'right' });
|
|
32
|
+
return {
|
|
33
|
+
arrange,
|
|
34
|
+
transitions: aligned,
|
|
35
|
+
firstDivergence: firstDivergence(aligned, unmatched, left, right),
|
|
36
|
+
unmatched,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function compareFrames(leftFrame, rightFrame, left, right) {
|
|
40
|
+
if (leftFrame === undefined || rightFrame === undefined) {
|
|
41
|
+
return { kind: 'unpaired', because: 'one execution has no corresponding frame' };
|
|
42
|
+
}
|
|
43
|
+
if (leftFrame.outcome.kind === 'unobserved' || rightFrame.outcome.kind === 'unobserved') {
|
|
44
|
+
const sides = [
|
|
45
|
+
...(leftFrame.outcome.kind === 'unobserved' ? ['left'] : []),
|
|
46
|
+
...(rightFrame.outcome.kind === 'unobserved' ? ['right'] : []),
|
|
47
|
+
];
|
|
48
|
+
return { kind: 'unobserved', because: `${sides.join(' and ')} outcome was not observed` };
|
|
49
|
+
}
|
|
50
|
+
const before = left.snapshots.get(leftFrame.outcome.snapshot);
|
|
51
|
+
const after = right.snapshots.get(rightFrame.outcome.snapshot);
|
|
52
|
+
if (before === undefined || after === undefined) {
|
|
53
|
+
const missing = [
|
|
54
|
+
...(before === undefined ? [leftFrame.outcome.snapshot] : []),
|
|
55
|
+
...(after === undefined ? [rightFrame.outcome.snapshot] : []),
|
|
56
|
+
];
|
|
57
|
+
return {
|
|
58
|
+
kind: 'unobserved',
|
|
59
|
+
because: `semantic snapshot ${missing.join(' and ')} is unavailable`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { kind: 'measured', variance: varianceOf(before, after) };
|
|
63
|
+
}
|
|
64
|
+
function varianceOf(before, after) {
|
|
65
|
+
const variation = deriveVariation(before, after);
|
|
66
|
+
return {
|
|
67
|
+
digest: variation.digest,
|
|
68
|
+
identical: variation.identical,
|
|
69
|
+
bands: variation.bands,
|
|
70
|
+
components: variation.components.map((component) => component.name),
|
|
71
|
+
unobserved: variation.unobserved,
|
|
72
|
+
blindSides: blindSides(before, after),
|
|
73
|
+
parting: partingBetween(before, after),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The edge, read for which input made it.
|
|
78
|
+
*
|
|
79
|
+
* Called for both pairs this file measures, because both are the same question
|
|
80
|
+
* asked of different snapshots: an Arrange comparison asks why two executions
|
|
81
|
+
* began differently, a transition effect asks what an Act did. Neither is a
|
|
82
|
+
* regression — there is no baseline in a scenario — so this is `partingOf`'s
|
|
83
|
+
* home ground rather than a borrowed reading.
|
|
84
|
+
*
|
|
85
|
+
* No lifting, unlike the composition graph's use of it: both snapshots are the
|
|
86
|
+
* same subject at two moments, so they are already rooted at the same node and
|
|
87
|
+
* `boundarySnapshot` would have nothing to do. That is the one way the time axis
|
|
88
|
+
* is *easier* than the A/B one.
|
|
89
|
+
*
|
|
90
|
+
* A second comparison after `deriveVariation`'s, and deliberately not fused with
|
|
91
|
+
* it. `compareTrees` returns on matching render hashes, so the edge that moved
|
|
92
|
+
* nothing — the common one on a long path — pays a digest compare, and the edge
|
|
93
|
+
* that did move is the one somebody is about to read a sentence about.
|
|
94
|
+
*/
|
|
95
|
+
function partingBetween(before, after) {
|
|
96
|
+
const parting = partingOf(before, after);
|
|
97
|
+
return { slice: parting.slice, lines: explainParting(parting) };
|
|
98
|
+
}
|
|
99
|
+
function blindSides(left, right) {
|
|
100
|
+
const here = observableBands(left.profile);
|
|
101
|
+
const there = observableBands(right.profile);
|
|
102
|
+
const blind = [];
|
|
103
|
+
for (const band of BANDS) {
|
|
104
|
+
const sides = [];
|
|
105
|
+
if (!decidesBand(band, here[band]))
|
|
106
|
+
sides.push('left');
|
|
107
|
+
if (!decidesBand(band, there[band]))
|
|
108
|
+
sides.push('right');
|
|
109
|
+
if (sides.length > 0)
|
|
110
|
+
blind.push({ band, sides });
|
|
111
|
+
}
|
|
112
|
+
return blind;
|
|
113
|
+
}
|
|
114
|
+
function compareEffects(left, right) {
|
|
115
|
+
if (left.kind === 'unpaired' || right.kind === 'unpaired') {
|
|
116
|
+
return { kind: 'unpaired', because: 'one transition effect has no corresponding effect' };
|
|
117
|
+
}
|
|
118
|
+
if (left.kind === 'unobserved' || right.kind === 'unobserved') {
|
|
119
|
+
return { kind: 'unobserved', because: 'one transition effect was not measured' };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
kind: 'measured',
|
|
123
|
+
identical: left.variance.digest === right.variance.digest,
|
|
124
|
+
left: left.variance.digest,
|
|
125
|
+
right: right.variance.digest,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function firstDivergence(transitions, unmatched, left, right) {
|
|
129
|
+
for (const transition of transitions) {
|
|
130
|
+
if (transition.divergence.kind !== 'measured') {
|
|
131
|
+
return {
|
|
132
|
+
kind: 'unresolved',
|
|
133
|
+
because: `the effect of \`${transition.act.key}\` was not measured on both sides`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (!transition.divergence.identical)
|
|
137
|
+
return { kind: 'found', act: transition.act };
|
|
138
|
+
}
|
|
139
|
+
if (unmatched.length > 0) {
|
|
140
|
+
return { kind: 'unresolved', because: 'the executions contain unmatched authored acts' };
|
|
141
|
+
}
|
|
142
|
+
if (!complete(left) || !complete(right)) {
|
|
143
|
+
return { kind: 'unresolved', because: 'one execution did not observe its complete path' };
|
|
144
|
+
}
|
|
145
|
+
return { kind: 'none' };
|
|
146
|
+
}
|
|
147
|
+
function actFrames(run) {
|
|
148
|
+
return run.execution.frames.slice(1);
|
|
149
|
+
}
|
|
150
|
+
function complete(run) {
|
|
151
|
+
return (run.execution.termination === undefined &&
|
|
152
|
+
run.execution.frames.length === run.definition.acts.length + 1);
|
|
153
|
+
}
|
|
154
|
+
function sameAct(left, right) {
|
|
155
|
+
return left.key === right.key && left.occurrence === right.occurrence;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=assessment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assessment.js","sourceRoot":"","sources":["../src/assessment.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,SAAS,GAEV,MAAM,0BAA0B,CAAC;AAclC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,0FAA0F;AAC1F,MAAM,UAAU,eAAe,CAAC,IAAiB,EAAE,KAAkB;IACnE,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxB,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAChG,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,OAAO,GAAmC,EAAE,CAAC;IACnD,MAAM,SAAS,GAA2B,EAAE,CAAC;IAC7C,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAE,CAAC;QACpC,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAE,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAI,EAAE,UAAU,CAAC,GAAI,CAAC;YAAE,MAAM;QAErD,MAAM,UAAU,GAAG,aAAa,CAC9B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,SAAS,EACT,IAAI,EACJ,IAAI,CACL,CAAC;QACF,MAAM,WAAW,GAAG,aAAa,CAC/B,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAC9B,UAAU,EACV,KAAK,EACL,KAAK,CACN,CAAC;QACF,OAAO,CAAC,IAAI,CAAC;YACX,GAAG,EAAE,SAAS,CAAC,GAAI;YACnB,UAAU;YACV,WAAW;YACX,UAAU,EAAE,cAAc,CAAC,UAAU,EAAE,WAAW,CAAC;SACpD,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,CAAC;IACd,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,GAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,GAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAE9F,OAAO;QACL,OAAO;QACP,WAAW,EAAE,OAAO;QACpB,eAAe,EAAE,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC;QACjE,SAAS;KACV,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CACpB,SAAoC,EACpC,UAAqC,EACrC,IAAiB,EACjB,KAAkB;IAElB,IAAI,SAAS,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACxD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,0CAA0C,EAAE,CAAC;IACnF,CAAC;IACD,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACxF,MAAM,KAAK,GAAG;YACZ,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC;QACF,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,CAAC;IAC5F,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG;YACd,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;QACF,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,qBAAqB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB;SACrE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,UAAU,CAAC,MAAwB,EAAE,KAAuB;IACnE,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,OAAO;QACL,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC;QACnE,UAAU,EAAE,SAAS,CAAC,UAAU;QAChC,UAAU,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC;QACrC,OAAO,EAAE,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC;KACvC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,cAAc,CAAC,MAAwB,EAAE,KAAuB;IACvE,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACzC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,UAAU,CACjB,IAAsB,EACtB,KAAuB;IAEvB,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAwB,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAyB,EAAE,CAAC;QACvC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CACrB,IAAwB,EACxB,KAAyB;IAEzB,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC1D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAC;IAC5F,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC9D,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAC;IACnF,CAAC;IACD,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,QAAQ,CAAC,MAAM;QACzD,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;QAC1B,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;KAC7B,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,WAAoD,EACpD,SAA0C,EAC1C,IAAiB,EACjB,KAAkB;IAElB,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC9C,OAAO;gBACL,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,mBAAmB,UAAU,CAAC,GAAG,CAAC,GAAG,mCAAmC;aAClF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,CAAC;IACtF,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,gDAAgD,EAAE,CAAC;IAC3F,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAAC;IAC5F,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED,SAAS,SAAS,CAAC,GAAgB;IACjC,OAAO,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAgB;IAChC,OAAO,CACL,GAAG,CAAC,SAAS,CAAC,WAAW,KAAK,SAAS;QACvC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAC/D,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,IAAoB,EAAE,KAAqB;IAC1D,OAAO,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC;AACxE,CAAC","sourcesContent":["import {\n BANDS,\n decidesBand,\n deriveVariation,\n explainParting,\n observableBands,\n partingOf,\n type SemanticSnapshot,\n} from '@variance-authority/core';\nimport type {\n ScenarioActRef,\n ScenarioAssessment,\n ScenarioBlindSide,\n ScenarioComparison,\n ScenarioEffectDivergence,\n ScenarioFrame,\n ScenarioParting,\n ScenarioRun,\n ScenarioTransitionAssessment,\n ScenarioUnmatchedAct,\n ScenarioVariance,\n} from './contract.js';\nimport { assertScenarioRun } from './execution.js';\n\n/** Compare two witnessed paths without writing a verdict or mutating either execution. */\nexport function assessScenarios(left: ScenarioRun, right: ScenarioRun): ScenarioAssessment {\n assertScenarioRun(left);\n assertScenarioRun(right);\n const arrange = compareFrames(left.execution.frames[0], right.execution.frames[0], left, right);\n const leftActs = actFrames(left);\n const rightActs = actFrames(right);\n const aligned: ScenarioTransitionAssessment[] = [];\n const unmatched: ScenarioUnmatchedAct[] = [];\n let prefix = 0;\n\n while (prefix < leftActs.length && prefix < rightActs.length) {\n const leftFrame = leftActs[prefix]!;\n const rightFrame = rightActs[prefix]!;\n if (!sameAct(leftFrame.act!, rightFrame.act!)) break;\n\n const leftEffect = compareFrames(\n left.execution.frames[prefix],\n leftFrame,\n left,\n left,\n );\n const rightEffect = compareFrames(\n right.execution.frames[prefix],\n rightFrame,\n right,\n right,\n );\n aligned.push({\n act: leftFrame.act!,\n leftEffect,\n rightEffect,\n divergence: compareEffects(leftEffect, rightEffect),\n });\n prefix += 1;\n }\n\n for (const frame of leftActs.slice(prefix)) unmatched.push({ ...frame.act!, side: 'left' });\n for (const frame of rightActs.slice(prefix)) unmatched.push({ ...frame.act!, side: 'right' });\n\n return {\n arrange,\n transitions: aligned,\n firstDivergence: firstDivergence(aligned, unmatched, left, right),\n unmatched,\n };\n}\n\nfunction compareFrames(\n leftFrame: ScenarioFrame | undefined,\n rightFrame: ScenarioFrame | undefined,\n left: ScenarioRun,\n right: ScenarioRun,\n): ScenarioComparison {\n if (leftFrame === undefined || rightFrame === undefined) {\n return { kind: 'unpaired', because: 'one execution has no corresponding frame' };\n }\n if (leftFrame.outcome.kind === 'unobserved' || rightFrame.outcome.kind === 'unobserved') {\n const sides = [\n ...(leftFrame.outcome.kind === 'unobserved' ? ['left'] : []),\n ...(rightFrame.outcome.kind === 'unobserved' ? ['right'] : []),\n ];\n return { kind: 'unobserved', because: `${sides.join(' and ')} outcome was not observed` };\n }\n\n const before = left.snapshots.get(leftFrame.outcome.snapshot);\n const after = right.snapshots.get(rightFrame.outcome.snapshot);\n if (before === undefined || after === undefined) {\n const missing = [\n ...(before === undefined ? [leftFrame.outcome.snapshot] : []),\n ...(after === undefined ? [rightFrame.outcome.snapshot] : []),\n ];\n return {\n kind: 'unobserved',\n because: `semantic snapshot ${missing.join(' and ')} is unavailable`,\n };\n }\n\n return { kind: 'measured', variance: varianceOf(before, after) };\n}\n\nfunction varianceOf(before: SemanticSnapshot, after: SemanticSnapshot): ScenarioVariance {\n const variation = deriveVariation(before, after);\n return {\n digest: variation.digest,\n identical: variation.identical,\n bands: variation.bands,\n components: variation.components.map((component) => component.name),\n unobserved: variation.unobserved,\n blindSides: blindSides(before, after),\n parting: partingBetween(before, after),\n };\n}\n\n/**\n * The edge, read for which input made it.\n *\n * Called for both pairs this file measures, because both are the same question\n * asked of different snapshots: an Arrange comparison asks why two executions\n * began differently, a transition effect asks what an Act did. Neither is a\n * regression — there is no baseline in a scenario — so this is `partingOf`'s\n * home ground rather than a borrowed reading.\n *\n * No lifting, unlike the composition graph's use of it: both snapshots are the\n * same subject at two moments, so they are already rooted at the same node and\n * `boundarySnapshot` would have nothing to do. That is the one way the time axis\n * is *easier* than the A/B one.\n *\n * A second comparison after `deriveVariation`'s, and deliberately not fused with\n * it. `compareTrees` returns on matching render hashes, so the edge that moved\n * nothing — the common one on a long path — pays a digest compare, and the edge\n * that did move is the one somebody is about to read a sentence about.\n */\nfunction partingBetween(before: SemanticSnapshot, after: SemanticSnapshot): ScenarioParting {\n const parting = partingOf(before, after);\n return { slice: parting.slice, lines: explainParting(parting) };\n}\n\nfunction blindSides(\n left: SemanticSnapshot,\n right: SemanticSnapshot,\n): readonly ScenarioBlindSide[] {\n const here = observableBands(left.profile);\n const there = observableBands(right.profile);\n const blind: ScenarioBlindSide[] = [];\n\n for (const band of BANDS) {\n const sides: ('left' | 'right')[] = [];\n if (!decidesBand(band, here[band])) sides.push('left');\n if (!decidesBand(band, there[band])) sides.push('right');\n if (sides.length > 0) blind.push({ band, sides });\n }\n return blind;\n}\n\nfunction compareEffects(\n left: ScenarioComparison,\n right: ScenarioComparison,\n): ScenarioEffectDivergence {\n if (left.kind === 'unpaired' || right.kind === 'unpaired') {\n return { kind: 'unpaired', because: 'one transition effect has no corresponding effect' };\n }\n if (left.kind === 'unobserved' || right.kind === 'unobserved') {\n return { kind: 'unobserved', because: 'one transition effect was not measured' };\n }\n return {\n kind: 'measured',\n identical: left.variance.digest === right.variance.digest,\n left: left.variance.digest,\n right: right.variance.digest,\n };\n}\n\nfunction firstDivergence(\n transitions: readonly ScenarioTransitionAssessment[],\n unmatched: readonly ScenarioUnmatchedAct[],\n left: ScenarioRun,\n right: ScenarioRun,\n): ScenarioAssessment['firstDivergence'] {\n for (const transition of transitions) {\n if (transition.divergence.kind !== 'measured') {\n return {\n kind: 'unresolved',\n because: `the effect of \\`${transition.act.key}\\` was not measured on both sides`,\n };\n }\n if (!transition.divergence.identical) return { kind: 'found', act: transition.act };\n }\n\n if (unmatched.length > 0) {\n return { kind: 'unresolved', because: 'the executions contain unmatched authored acts' };\n }\n if (!complete(left) || !complete(right)) {\n return { kind: 'unresolved', because: 'one execution did not observe its complete path' };\n }\n return { kind: 'none' };\n}\n\nfunction actFrames(run: ScenarioRun): readonly ScenarioFrame[] {\n return run.execution.frames.slice(1);\n}\n\nfunction complete(run: ScenarioRun): boolean {\n return (\n run.execution.termination === undefined &&\n run.execution.frames.length === run.definition.acts.length + 1\n );\n}\n\nfunction sameAct(left: ScenarioActRef, right: ScenarioActRef): boolean {\n return left.key === right.key && left.occurrence === right.occurrence;\n}\n"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Band, Diagnostic, Digest, PartingSlice, ProfileId, SemanticSnapshot, SubjectRef } from '@variance-authority/core';
|
|
2
|
+
declare const scenarioToken: unique symbol;
|
|
3
|
+
export interface ScenarioDefinitionData {
|
|
4
|
+
readonly scenarioVersion: 1;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly acts: readonly ScenarioAct[];
|
|
7
|
+
}
|
|
8
|
+
/** A definition checked and created by `defineScenario`. */
|
|
9
|
+
export declare class ScenarioDefinition implements ScenarioDefinitionData {
|
|
10
|
+
private readonly checked;
|
|
11
|
+
readonly scenarioVersion: 1;
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly acts: readonly ScenarioAct[];
|
|
14
|
+
constructor(token: typeof scenarioToken, value: ScenarioDefinitionData);
|
|
15
|
+
}
|
|
16
|
+
export interface ScenarioAct {
|
|
17
|
+
/** Stable authored identity. A DOM event or resolved target is evidence, not identity. */
|
|
18
|
+
readonly key: string;
|
|
19
|
+
readonly kind?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PreconditionAxisStep {
|
|
22
|
+
readonly axis: string;
|
|
23
|
+
readonly from: string;
|
|
24
|
+
readonly to: string;
|
|
25
|
+
}
|
|
26
|
+
/** The result of the existing subject-parent resolver, carried rather than re-parsed. */
|
|
27
|
+
export type PreconditionLink = {
|
|
28
|
+
readonly kind: 'resolved';
|
|
29
|
+
readonly parent: string;
|
|
30
|
+
readonly how: 'declared' | 'named';
|
|
31
|
+
readonly step?: PreconditionAxisStep;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'unresolved';
|
|
34
|
+
readonly because: string;
|
|
35
|
+
};
|
|
36
|
+
export interface ScenarioExecutionData {
|
|
37
|
+
readonly executionVersion: 1;
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly definition: string;
|
|
40
|
+
readonly precondition: SubjectRef;
|
|
41
|
+
readonly preconditionLink?: PreconditionLink;
|
|
42
|
+
readonly profile: ProfileId;
|
|
43
|
+
readonly frames: readonly ScenarioFrame[];
|
|
44
|
+
readonly termination?: readonly Diagnostic[];
|
|
45
|
+
}
|
|
46
|
+
/** An execution whose frame ordering, Act identities, and terminal prefix are checked. */
|
|
47
|
+
export declare class ScenarioExecution implements ScenarioExecutionData {
|
|
48
|
+
private readonly checked;
|
|
49
|
+
readonly executionVersion: 1;
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly definition: string;
|
|
52
|
+
readonly precondition: SubjectRef;
|
|
53
|
+
readonly preconditionLink?: PreconditionLink;
|
|
54
|
+
readonly profile: ProfileId;
|
|
55
|
+
readonly frames: readonly ScenarioFrame[];
|
|
56
|
+
readonly termination?: readonly Diagnostic[];
|
|
57
|
+
constructor(token: typeof scenarioToken, value: ScenarioExecutionData);
|
|
58
|
+
}
|
|
59
|
+
export interface ScenarioFrame {
|
|
60
|
+
/** Zero is Arrange; later frames are Act outcomes. */
|
|
61
|
+
readonly at: number;
|
|
62
|
+
readonly act?: ScenarioActRef;
|
|
63
|
+
readonly outcome: ScenarioOutcome;
|
|
64
|
+
}
|
|
65
|
+
export interface ScenarioActRef {
|
|
66
|
+
readonly key: string;
|
|
67
|
+
readonly occurrence: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ScenarioUnmatchedAct extends ScenarioActRef {
|
|
70
|
+
readonly side: 'left' | 'right';
|
|
71
|
+
}
|
|
72
|
+
export type ScenarioOutcome = {
|
|
73
|
+
readonly kind: 'observed';
|
|
74
|
+
readonly snapshot: Digest;
|
|
75
|
+
readonly state: Digest;
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: 'unobserved';
|
|
78
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
79
|
+
};
|
|
80
|
+
/** Ephemeral evidence. Dropping this value drops the scenario unless it is archived explicitly. */
|
|
81
|
+
/** Ephemeral evidence checked for frame-to-snapshot and definition-to-execution consistency. */
|
|
82
|
+
export declare class ScenarioRun {
|
|
83
|
+
private readonly checked;
|
|
84
|
+
readonly definition: ScenarioDefinition;
|
|
85
|
+
readonly execution: ScenarioExecution;
|
|
86
|
+
readonly snapshots: ReadonlyMap<Digest, SemanticSnapshot>;
|
|
87
|
+
constructor(token: typeof scenarioToken, value: {
|
|
88
|
+
readonly definition: ScenarioDefinition;
|
|
89
|
+
readonly execution: ScenarioExecution;
|
|
90
|
+
readonly snapshots: ReadonlyMap<Digest, SemanticSnapshot>;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export interface UnobservedScenarioOutcome {
|
|
94
|
+
readonly kind: 'unobserved';
|
|
95
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
96
|
+
}
|
|
97
|
+
export type ScenarioObservation = SemanticSnapshot | UnobservedScenarioOutcome;
|
|
98
|
+
export interface ScenarioBlindSide {
|
|
99
|
+
readonly band: Band;
|
|
100
|
+
readonly sides: readonly ('left' | 'right')[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Which kind of parting an edge is, and which input made it.
|
|
104
|
+
*
|
|
105
|
+
* The same value the composition graph attaches to a divergence, on the axis
|
|
106
|
+
* where two readings are separated by a moment rather than by a page. That is
|
|
107
|
+
* the claim worth stating: *snapshot, act, snapshot* and *snapshot, wait,
|
|
108
|
+
* snapshot* are not two features but one comparison, and the slice is the only
|
|
109
|
+
* place their answers part. {@link PartingSlice} defines the seven.
|
|
110
|
+
*
|
|
111
|
+
* {@link lines} is `explainParting` output — the triage sentence first, then a
|
|
112
|
+
* line per boundary. A run whose collector read no fiber gets the honest pair
|
|
113
|
+
* rather than silence: the slice is `unread`, and the line under it says so.
|
|
114
|
+
*/
|
|
115
|
+
export interface ScenarioParting {
|
|
116
|
+
readonly slice: PartingSlice;
|
|
117
|
+
readonly lines: readonly string[];
|
|
118
|
+
}
|
|
119
|
+
export interface ScenarioVariance {
|
|
120
|
+
readonly digest: Digest;
|
|
121
|
+
readonly identical: boolean;
|
|
122
|
+
readonly bands: readonly Band[];
|
|
123
|
+
readonly components: readonly string[];
|
|
124
|
+
readonly unobserved: readonly Band[];
|
|
125
|
+
readonly blindSides: readonly ScenarioBlindSide[];
|
|
126
|
+
/**
|
|
127
|
+
* Why these two readings differ, not merely that they do.
|
|
128
|
+
*
|
|
129
|
+
* `components` names who moved and stops there, which is the same half-finding
|
|
130
|
+
* a divergence carried before it was given a parting: a reader holding it still
|
|
131
|
+
* has to open two frames and diff them by eye. This is the other half.
|
|
132
|
+
*
|
|
133
|
+
* Always present. A parting is decidable from any two snapshots — `unread` is
|
|
134
|
+
* a rung, not a gap — so an absent field here would mean the assessment did not
|
|
135
|
+
* run, and there is no such state.
|
|
136
|
+
*/
|
|
137
|
+
readonly parting: ScenarioParting;
|
|
138
|
+
}
|
|
139
|
+
export type ScenarioComparison = {
|
|
140
|
+
readonly kind: 'measured';
|
|
141
|
+
readonly variance: ScenarioVariance;
|
|
142
|
+
} | {
|
|
143
|
+
readonly kind: 'unobserved';
|
|
144
|
+
readonly because: string;
|
|
145
|
+
} | {
|
|
146
|
+
readonly kind: 'unpaired';
|
|
147
|
+
readonly because: string;
|
|
148
|
+
};
|
|
149
|
+
export type ScenarioEffectDivergence = {
|
|
150
|
+
readonly kind: 'measured';
|
|
151
|
+
readonly identical: boolean;
|
|
152
|
+
readonly left: Digest;
|
|
153
|
+
readonly right: Digest;
|
|
154
|
+
} | {
|
|
155
|
+
readonly kind: 'unobserved';
|
|
156
|
+
readonly because: string;
|
|
157
|
+
} | {
|
|
158
|
+
readonly kind: 'unpaired';
|
|
159
|
+
readonly because: string;
|
|
160
|
+
};
|
|
161
|
+
export interface ScenarioTransitionAssessment {
|
|
162
|
+
readonly act: ScenarioActRef;
|
|
163
|
+
readonly leftEffect: ScenarioComparison;
|
|
164
|
+
readonly rightEffect: ScenarioComparison;
|
|
165
|
+
readonly divergence: ScenarioEffectDivergence;
|
|
166
|
+
}
|
|
167
|
+
export type ScenarioDivergence = {
|
|
168
|
+
readonly kind: 'found';
|
|
169
|
+
readonly act: ScenarioActRef;
|
|
170
|
+
} | {
|
|
171
|
+
readonly kind: 'none';
|
|
172
|
+
} | {
|
|
173
|
+
readonly kind: 'unresolved';
|
|
174
|
+
readonly because: string;
|
|
175
|
+
};
|
|
176
|
+
export interface ScenarioAssessment {
|
|
177
|
+
readonly arrange: ScenarioComparison;
|
|
178
|
+
readonly transitions: readonly ScenarioTransitionAssessment[];
|
|
179
|
+
readonly firstDivergence: ScenarioDivergence;
|
|
180
|
+
readonly unmatched: readonly ScenarioUnmatchedAct[];
|
|
181
|
+
}
|
|
182
|
+
export declare function checkedDefinition(value: ScenarioDefinitionData): ScenarioDefinition;
|
|
183
|
+
export declare function checkedExecution(value: ScenarioExecutionData): ScenarioExecution;
|
|
184
|
+
export declare function checkedRun(value: {
|
|
185
|
+
readonly definition: ScenarioDefinition;
|
|
186
|
+
readonly execution: ScenarioExecution;
|
|
187
|
+
readonly snapshots: ReadonlyMap<Digest, SemanticSnapshot>;
|
|
188
|
+
}): ScenarioRun;
|
|
189
|
+
export {};
|
|
190
|
+
//# sourceMappingURL=contract.d.ts.map
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const scenarioToken = Symbol('checked scenario value');
|
|
2
|
+
/** A definition checked and created by `defineScenario`. */
|
|
3
|
+
export class ScenarioDefinition {
|
|
4
|
+
scenarioVersion = 1;
|
|
5
|
+
id;
|
|
6
|
+
acts;
|
|
7
|
+
constructor(token, value) {
|
|
8
|
+
if (token !== scenarioToken)
|
|
9
|
+
throw new Error('ScenarioDefinition is constructor-owned');
|
|
10
|
+
this.id = value.id;
|
|
11
|
+
this.acts = value.acts.map((act) => Object.freeze({ ...act }));
|
|
12
|
+
Object.freeze(this);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** An execution whose frame ordering, Act identities, and terminal prefix are checked. */
|
|
16
|
+
export class ScenarioExecution {
|
|
17
|
+
executionVersion = 1;
|
|
18
|
+
id;
|
|
19
|
+
definition;
|
|
20
|
+
precondition;
|
|
21
|
+
preconditionLink;
|
|
22
|
+
profile;
|
|
23
|
+
frames;
|
|
24
|
+
termination;
|
|
25
|
+
constructor(token, value) {
|
|
26
|
+
if (token !== scenarioToken)
|
|
27
|
+
throw new Error('ScenarioExecution is constructor-owned');
|
|
28
|
+
this.id = value.id;
|
|
29
|
+
this.definition = value.definition;
|
|
30
|
+
this.precondition = Object.freeze({ ...value.precondition });
|
|
31
|
+
if (value.preconditionLink !== undefined)
|
|
32
|
+
this.preconditionLink = value.preconditionLink;
|
|
33
|
+
this.profile = value.profile;
|
|
34
|
+
this.frames = value.frames.map((frame) => freezeFrame(frame));
|
|
35
|
+
if (value.termination !== undefined)
|
|
36
|
+
this.termination = freezeDiagnostics(value.termination);
|
|
37
|
+
Object.freeze(this);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Ephemeral evidence. Dropping this value drops the scenario unless it is archived explicitly. */
|
|
41
|
+
/** Ephemeral evidence checked for frame-to-snapshot and definition-to-execution consistency. */
|
|
42
|
+
export class ScenarioRun {
|
|
43
|
+
definition;
|
|
44
|
+
execution;
|
|
45
|
+
snapshots;
|
|
46
|
+
constructor(token, value) {
|
|
47
|
+
if (token !== scenarioToken)
|
|
48
|
+
throw new Error('ScenarioRun is constructor-owned');
|
|
49
|
+
this.definition = value.definition;
|
|
50
|
+
this.execution = value.execution;
|
|
51
|
+
this.snapshots = new Map(value.snapshots);
|
|
52
|
+
Object.freeze(this);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function checkedDefinition(value) {
|
|
56
|
+
return new ScenarioDefinition(scenarioToken, value);
|
|
57
|
+
}
|
|
58
|
+
export function checkedExecution(value) {
|
|
59
|
+
return new ScenarioExecution(scenarioToken, value);
|
|
60
|
+
}
|
|
61
|
+
export function checkedRun(value) {
|
|
62
|
+
return new ScenarioRun(scenarioToken, value);
|
|
63
|
+
}
|
|
64
|
+
function freezeFrame(frame) {
|
|
65
|
+
const outcome = frame.outcome.kind === 'observed'
|
|
66
|
+
? Object.freeze({ ...frame.outcome })
|
|
67
|
+
: Object.freeze({
|
|
68
|
+
kind: 'unobserved',
|
|
69
|
+
diagnostics: freezeDiagnostics(frame.outcome.diagnostics),
|
|
70
|
+
});
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
at: frame.at,
|
|
73
|
+
...(frame.act !== undefined ? { act: Object.freeze({ ...frame.act }) } : {}),
|
|
74
|
+
outcome,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function freezeDiagnostics(diagnostics) {
|
|
78
|
+
return diagnostics.map((diagnostic) => Object.freeze({ ...diagnostic }));
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Band,\n Diagnostic,\n Digest,\n PartingSlice,\n ProfileId,\n SemanticSnapshot,\n SubjectRef,\n} from '@variance-authority/core';\n\nconst scenarioToken: unique symbol = Symbol('checked scenario value');\n\nexport interface ScenarioDefinitionData {\n readonly scenarioVersion: 1;\n readonly id: string;\n readonly acts: readonly ScenarioAct[];\n}\n\n/** A definition checked and created by `defineScenario`. */\nexport class ScenarioDefinition implements ScenarioDefinitionData {\n declare private readonly checked: true;\n readonly scenarioVersion = 1 as const;\n readonly id: string;\n readonly acts: readonly ScenarioAct[];\n\n constructor(token: typeof scenarioToken, value: ScenarioDefinitionData) {\n if (token !== scenarioToken) throw new Error('ScenarioDefinition is constructor-owned');\n this.id = value.id;\n this.acts = value.acts.map((act) => Object.freeze({ ...act }));\n Object.freeze(this);\n }\n}\n\nexport interface ScenarioAct {\n /** Stable authored identity. A DOM event or resolved target is evidence, not identity. */\n readonly key: string;\n readonly kind?: string;\n}\n\nexport interface PreconditionAxisStep {\n readonly axis: string;\n readonly from: string;\n readonly to: string;\n}\n\n/** The result of the existing subject-parent resolver, carried rather than re-parsed. */\nexport type PreconditionLink =\n | {\n readonly kind: 'resolved';\n readonly parent: string;\n readonly how: 'declared' | 'named';\n readonly step?: PreconditionAxisStep;\n }\n | { readonly kind: 'unresolved'; readonly because: string };\n\nexport interface ScenarioExecutionData {\n readonly executionVersion: 1;\n readonly id: string;\n readonly definition: string;\n readonly precondition: SubjectRef;\n readonly preconditionLink?: PreconditionLink;\n readonly profile: ProfileId;\n readonly frames: readonly ScenarioFrame[];\n readonly termination?: readonly Diagnostic[];\n}\n\n/** An execution whose frame ordering, Act identities, and terminal prefix are checked. */\nexport class ScenarioExecution implements ScenarioExecutionData {\n declare private readonly checked: true;\n readonly executionVersion = 1 as const;\n readonly id: string;\n readonly definition: string;\n readonly precondition: SubjectRef;\n readonly preconditionLink?: PreconditionLink;\n readonly profile: ProfileId;\n readonly frames: readonly ScenarioFrame[];\n readonly termination?: readonly Diagnostic[];\n\n constructor(token: typeof scenarioToken, value: ScenarioExecutionData) {\n if (token !== scenarioToken) throw new Error('ScenarioExecution is constructor-owned');\n this.id = value.id;\n this.definition = value.definition;\n this.precondition = Object.freeze({ ...value.precondition });\n if (value.preconditionLink !== undefined) this.preconditionLink = value.preconditionLink;\n this.profile = value.profile;\n this.frames = value.frames.map((frame) => freezeFrame(frame));\n if (value.termination !== undefined) this.termination = freezeDiagnostics(value.termination);\n Object.freeze(this);\n }\n}\n\nexport interface ScenarioFrame {\n /** Zero is Arrange; later frames are Act outcomes. */\n readonly at: number;\n readonly act?: ScenarioActRef;\n readonly outcome: ScenarioOutcome;\n}\n\nexport interface ScenarioActRef {\n readonly key: string;\n readonly occurrence: number;\n}\n\nexport interface ScenarioUnmatchedAct extends ScenarioActRef {\n readonly side: 'left' | 'right';\n}\n\nexport type ScenarioOutcome =\n | { readonly kind: 'observed'; readonly snapshot: Digest; readonly state: Digest }\n | { readonly kind: 'unobserved'; readonly diagnostics: readonly Diagnostic[] };\n\n/** Ephemeral evidence. Dropping this value drops the scenario unless it is archived explicitly. */\n/** Ephemeral evidence checked for frame-to-snapshot and definition-to-execution consistency. */\nexport class ScenarioRun {\n declare private readonly checked: true;\n readonly definition: ScenarioDefinition;\n readonly execution: ScenarioExecution;\n readonly snapshots: ReadonlyMap<Digest, SemanticSnapshot>;\n\n constructor(\n token: typeof scenarioToken,\n value: {\n readonly definition: ScenarioDefinition;\n readonly execution: ScenarioExecution;\n readonly snapshots: ReadonlyMap<Digest, SemanticSnapshot>;\n },\n ) {\n if (token !== scenarioToken) throw new Error('ScenarioRun is constructor-owned');\n this.definition = value.definition;\n this.execution = value.execution;\n this.snapshots = new Map(value.snapshots);\n Object.freeze(this);\n }\n}\n\nexport interface UnobservedScenarioOutcome {\n readonly kind: 'unobserved';\n readonly diagnostics: readonly Diagnostic[];\n}\n\nexport type ScenarioObservation = SemanticSnapshot | UnobservedScenarioOutcome;\n\nexport interface ScenarioBlindSide {\n readonly band: Band;\n readonly sides: readonly ('left' | 'right')[];\n}\n\n/**\n * Which kind of parting an edge is, and which input made it.\n *\n * The same value the composition graph attaches to a divergence, on the axis\n * where two readings are separated by a moment rather than by a page. That is\n * the claim worth stating: *snapshot, act, snapshot* and *snapshot, wait,\n * snapshot* are not two features but one comparison, and the slice is the only\n * place their answers part. {@link PartingSlice} defines the seven.\n *\n * {@link lines} is `explainParting` output — the triage sentence first, then a\n * line per boundary. A run whose collector read no fiber gets the honest pair\n * rather than silence: the slice is `unread`, and the line under it says so.\n */\nexport interface ScenarioParting {\n readonly slice: PartingSlice;\n readonly lines: readonly string[];\n}\n\nexport interface ScenarioVariance {\n readonly digest: Digest;\n readonly identical: boolean;\n readonly bands: readonly Band[];\n readonly components: readonly string[];\n readonly unobserved: readonly Band[];\n readonly blindSides: readonly ScenarioBlindSide[];\n\n /**\n * Why these two readings differ, not merely that they do.\n *\n * `components` names who moved and stops there, which is the same half-finding\n * a divergence carried before it was given a parting: a reader holding it still\n * has to open two frames and diff them by eye. This is the other half.\n *\n * Always present. A parting is decidable from any two snapshots — `unread` is\n * a rung, not a gap — so an absent field here would mean the assessment did not\n * run, and there is no such state.\n */\n readonly parting: ScenarioParting;\n}\n\nexport type ScenarioComparison =\n | { readonly kind: 'measured'; readonly variance: ScenarioVariance }\n | { readonly kind: 'unobserved'; readonly because: string }\n | { readonly kind: 'unpaired'; readonly because: string };\n\nexport type ScenarioEffectDivergence =\n | {\n readonly kind: 'measured';\n readonly identical: boolean;\n readonly left: Digest;\n readonly right: Digest;\n }\n | { readonly kind: 'unobserved'; readonly because: string }\n | { readonly kind: 'unpaired'; readonly because: string };\n\nexport interface ScenarioTransitionAssessment {\n readonly act: ScenarioActRef;\n readonly leftEffect: ScenarioComparison;\n readonly rightEffect: ScenarioComparison;\n readonly divergence: ScenarioEffectDivergence;\n}\n\nexport type ScenarioDivergence =\n | { readonly kind: 'found'; readonly act: ScenarioActRef }\n | { readonly kind: 'none' }\n | { readonly kind: 'unresolved'; readonly because: string };\n\nexport interface ScenarioAssessment {\n readonly arrange: ScenarioComparison;\n readonly transitions: readonly ScenarioTransitionAssessment[];\n readonly firstDivergence: ScenarioDivergence;\n readonly unmatched: readonly ScenarioUnmatchedAct[];\n}\n\nexport function checkedDefinition(value: ScenarioDefinitionData): ScenarioDefinition {\n return new ScenarioDefinition(scenarioToken, value);\n}\n\nexport function checkedExecution(value: ScenarioExecutionData): ScenarioExecution {\n return new ScenarioExecution(scenarioToken, value);\n}\n\nexport function checkedRun(value: {\n readonly definition: ScenarioDefinition;\n readonly execution: ScenarioExecution;\n readonly snapshots: ReadonlyMap<Digest, SemanticSnapshot>;\n}): ScenarioRun {\n return new ScenarioRun(scenarioToken, value);\n}\n\nfunction freezeFrame(frame: ScenarioFrame): ScenarioFrame {\n const outcome =\n frame.outcome.kind === 'observed'\n ? Object.freeze({ ...frame.outcome })\n : Object.freeze({\n kind: 'unobserved' as const,\n diagnostics: freezeDiagnostics(frame.outcome.diagnostics),\n });\n return Object.freeze({\n at: frame.at,\n ...(frame.act !== undefined ? { act: Object.freeze({ ...frame.act }) } : {}),\n outcome,\n });\n}\n\nfunction freezeDiagnostics(diagnostics: readonly Diagnostic[]): readonly Diagnostic[] {\n return diagnostics.map((diagnostic) => Object.freeze({ ...diagnostic }));\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Diagnostic, type Digest, type SemanticSnapshot } from '@variance-authority/core';
|
|
2
|
+
import type { PreconditionLink, ScenarioAct, ScenarioDefinition, ScenarioObservation, ScenarioRun, UnobservedScenarioOutcome } from './contract.js';
|
|
3
|
+
export interface StartScenarioOptions {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly precondition: SemanticSnapshot['subject'];
|
|
6
|
+
readonly profile: SemanticSnapshot['profile']['id'];
|
|
7
|
+
readonly preconditionLink?: PreconditionLink;
|
|
8
|
+
}
|
|
9
|
+
export declare function defineScenario(id: string, acts: readonly ScenarioAct[]): ScenarioDefinition;
|
|
10
|
+
export declare function unobserved(...diagnostics: readonly Diagnostic[]): UnobservedScenarioOutcome;
|
|
11
|
+
/** Content address of the complete canonical semantic object, not its render identity. */
|
|
12
|
+
export declare function semanticSnapshotDigest(snapshot: SemanticSnapshot): Digest;
|
|
13
|
+
export declare function startScenario(definition: ScenarioDefinition, options: StartScenarioOptions, arrange: ScenarioObservation): ScenarioRun;
|
|
14
|
+
export declare function recordAct(run: ScenarioRun, key: string, observation: ScenarioObservation): ScenarioRun;
|
|
15
|
+
/** Runtime guard for values crossing an archive or untyped host boundary. */
|
|
16
|
+
export declare function assertScenarioRun(run: ScenarioRun): void;
|
|
17
|
+
//# sourceMappingURL=execution.d.ts.map
|