staysfixed 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +534 -402
- package/package.json +8 -3
- package/src/cli/index.js +14 -0
- package/src/v2/adapters/android-driver.js +1705 -0
- package/src/v2/adapters/android.js +1117 -0
- package/src/v2/adapters/contract.js +565 -0
- package/src/v2/adapters/electron.js +1594 -0
- package/src/v2/adapters/http.js +733 -0
- package/src/v2/adapters/ios-driver.js +1551 -0
- package/src/v2/adapters/ios.js +989 -0
- package/src/v2/adapters/isolate.js +739 -0
- package/src/v2/adapters/process.js +920 -0
- package/src/v2/adapters/source.js +1241 -0
- package/src/v2/adapters/web-driver.js +1532 -0
- package/src/v2/adapters/web.js +1009 -0
- package/src/v2/adapters/windows.js +1329 -0
- package/src/v2/browsers.js +1203 -0
- package/src/v2/cause.js +364 -0
- package/src/v2/check.js +1331 -0
- package/src/v2/ci.js +1209 -0
- package/src/v2/cli.js +657 -0
- package/src/v2/cluster.js +372 -0
- package/src/v2/coverage.js +1116 -0
- package/src/v2/detect.js +1199 -0
- package/src/v2/doctor.js +1690 -0
- package/src/v2/escalate.js +679 -0
- package/src/v2/init.js +1394 -0
- package/src/v2/intent.js +659 -0
- package/src/v2/journeys/from-routes.js +498 -0
- package/src/v2/journeys/from-suite.js +988 -0
- package/src/v2/journeys/index.js +651 -0
- package/src/v2/journeys/record.js +516 -0
- package/src/v2/mcp/server.js +374 -0
- package/src/v2/mcp/tools.js +1571 -0
- package/src/v2/normalise.js +783 -0
- package/src/v2/observation.js +877 -0
- package/src/v2/rank.js +672 -0
- package/src/v2/reference.js +1051 -0
- package/src/v2/remote.js +911 -0
- package/src/v2/run.js +964 -0
- package/src/v2/sealed.js +564 -0
- package/src/v2/selfcheck.js +564 -0
- package/src/v2/ship.js +684 -0
- package/src/v2/store.js +703 -0
- package/src/v2/types.js +503 -0
- package/src/v2/waiver.js +511 -0
- package/src/watch/panel.js +73 -44
package/src/v2/types.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stays Fixed v2 — the whole contract, in one file.
|
|
3
|
+
*
|
|
4
|
+
* v1 photographed screens and compared pixels. v2 is a difference machine: it walks the
|
|
5
|
+
* product, writes down what it observed as `path -> value` facts, and reports only the
|
|
6
|
+
* facts that changed. Pictures become the seventh channel and the last one.
|
|
7
|
+
*
|
|
8
|
+
* Everything here is a JSDoc typedef. Nothing runs. It exists so the pieces built in
|
|
9
|
+
* parallel fit together, and so `npm run typecheck` proves it before anything is wired up.
|
|
10
|
+
*
|
|
11
|
+
* THE ONE IDEA WORTH READING TWICE: every platform flattens to the same shape. A button on
|
|
12
|
+
* an iPhone screen, an HTTP status, an exit code and an IPC channel are all one addressable
|
|
13
|
+
* fact. That is why one comparison engine can serve seven platforms, and why the path
|
|
14
|
+
* grammar below is the most load-bearing thing in the repository.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Channels — the seven ways we can observe a product
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The seven observation channels, in the order we trust them.
|
|
23
|
+
*
|
|
24
|
+
* - `meaning` What the interface says a control is and does: role, name, state.
|
|
25
|
+
* Never the raw DOM — the DOM changes when nothing did.
|
|
26
|
+
* - `effects` What went out: network calls, files written, processes spawned,
|
|
27
|
+
* storage writes. Recorded at the CALL boundary, so an irreversible
|
|
28
|
+
* effect is observed without being performed.
|
|
29
|
+
* - `complaints` Console messages, stderr, crashes, exit codes.
|
|
30
|
+
* - `results` What came back: stdout, HTTP bodies, the exported API surface.
|
|
31
|
+
* - `contract` Read statically out of the source: routes, exports, IPC channels.
|
|
32
|
+
* Free, exact, and it sees doors no walkthrough ever opens.
|
|
33
|
+
* - `counters` Coarse counts and timing. Deliberately coarse: fine timing is wobble.
|
|
34
|
+
* - `pixels` What it looked like. Evidence for a finding another channel already
|
|
35
|
+
* made — never the accusation itself.
|
|
36
|
+
*
|
|
37
|
+
* @typedef {'meaning'|'effects'|'complaints'|'results'|'contract'|'counters'|'pixels'} Channel
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Where an observation came from, when it matters which platform produced it.
|
|
42
|
+
* @typedef {'cli'|'library'|'server'|'web'|'electron'|'android'|'ios'|'windows'} Surface
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Observations — one fact about the product at one moment
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A value we can write down, compare and store.
|
|
51
|
+
*
|
|
52
|
+
* `undefined` is deliberately not in this list. A fact we do not have is an ABSENT PATH,
|
|
53
|
+
* not a path holding nothing — and telling those two apart is how appeared/vanished works.
|
|
54
|
+
*
|
|
55
|
+
* @typedef {string|number|boolean|null|any[]|Record<string, any>} ObservedValue
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Anything extra that helps a human understand a difference, and nothing that is compared.
|
|
60
|
+
*
|
|
61
|
+
* Values in here are NEVER part of the comparison — they are context. Put the fact in
|
|
62
|
+
* `value`; put the story in `meta`.
|
|
63
|
+
*
|
|
64
|
+
* @typedef {object} ObservationMeta
|
|
65
|
+
* @property {string} [describe] One plain sentence a person can read.
|
|
66
|
+
* @property {string} [source] Concretely where it came from: a file, a URL, a channel name.
|
|
67
|
+
* @property {number} [line] Line in `source`, for the contract channel.
|
|
68
|
+
* @property {Surface} [surface]
|
|
69
|
+
* @property {string} [journey] Journey that produced it, when observations get mixed.
|
|
70
|
+
* @property {string} [step] Step within the journey.
|
|
71
|
+
* @property {string} [evidence] Path to a picture, a log, a HAR — proof, not comparison.
|
|
72
|
+
* @property {boolean} [refused] True when we stopped at the call boundary on purpose
|
|
73
|
+
* (money, a message, data loss). A refusal is missing
|
|
74
|
+
* coverage, never a pass.
|
|
75
|
+
* @property {string} [refusedWhy] Plain English: what we would have had to do.
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One fact about the product at one moment.
|
|
80
|
+
*
|
|
81
|
+
* @typedef {object} Observation
|
|
82
|
+
* @property {string} path Stable dotted address. See PATH_RULES in observation.js.
|
|
83
|
+
* @property {Channel} channel
|
|
84
|
+
* @property {ObservedValue} value
|
|
85
|
+
* @property {number} [at] Milliseconds since the capture started. Never compared —
|
|
86
|
+
* it is a clock, and clocks are pure wobble.
|
|
87
|
+
* @property {ObservationMeta} [meta]
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Journeys — the named sequences that produce observations
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Where a journey came from, and it matters.
|
|
96
|
+
*
|
|
97
|
+
* - `code` Read out of the source: routes, exports, IPC channels. Free and exact.
|
|
98
|
+
* - `suite` The project's own existing tests, run under instrumentation.
|
|
99
|
+
* - `recorded` A real session someone actually performed, frozen into a file.
|
|
100
|
+
* - `explored` The agent opened one named gap and froze what it found.
|
|
101
|
+
*
|
|
102
|
+
* Ranked by how much we trust them, best first. A finding from a `code` journey is a fact
|
|
103
|
+
* about the product; a finding from an `explored` journey is a fact about one path an agent
|
|
104
|
+
* happened to take, and it is reported that way.
|
|
105
|
+
*
|
|
106
|
+
* @typedef {'code'|'suite'|'recorded'|'explored'} JourneySource
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One step of a journey. The driving lanes each extend this with their own keys —
|
|
111
|
+
* a CLI step carries argv, a web step carries a selector — so the shape stays open.
|
|
112
|
+
* @typedef {{act: string, note?: string} & Record<string, unknown>} JourneyStep
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A named sequence of steps that produces observations.
|
|
117
|
+
*
|
|
118
|
+
* @typedef {object} Journey
|
|
119
|
+
* @property {string} name File-safe id. It becomes a folder name and the head of
|
|
120
|
+
* every path this journey produces.
|
|
121
|
+
* @property {string} describe One plain sentence: what this journey does.
|
|
122
|
+
* @property {JourneySource} source
|
|
123
|
+
* @property {Surface} surface
|
|
124
|
+
* @property {string} [from] Concretely where it came from — a test file, a recording,
|
|
125
|
+
* the source file a route was read out of.
|
|
126
|
+
* @property {JourneyStep[]} [steps]
|
|
127
|
+
* @property {Channel[]} [channels] Channels this journey actually collects. Anything not
|
|
128
|
+
* listed is not claimed, and shows up in Coverage as a gap.
|
|
129
|
+
* @property {boolean} [irreversible] A step here would spend money, send a message or destroy
|
|
130
|
+
* data. Observed at the call boundary, refused at the effect.
|
|
131
|
+
* @property {string} [skip] Why it is switched off. Skipped is missing coverage.
|
|
132
|
+
* @property {number} [timeoutMs]
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Captures — one run of one journey against one build
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Which build a capture ran against.
|
|
141
|
+
*
|
|
142
|
+
* `id` is content-addressed against the build artifact wherever that is possible — the
|
|
143
|
+
* point being that two captures with the same `id` really did run the same bytes. Computing
|
|
144
|
+
* it belongs to the build lane; this is only the shape it hands over.
|
|
145
|
+
*
|
|
146
|
+
* @typedef {object} BuildFingerprint
|
|
147
|
+
* @property {string} id File-safe. Content hash where we have one, else a run id.
|
|
148
|
+
* @property {string} product Which product this build is of. One repo can make five.
|
|
149
|
+
* @property {Surface} [surface]
|
|
150
|
+
* @property {string} [version]
|
|
151
|
+
* @property {string|null} [gitSha]
|
|
152
|
+
* @property {string|null} [branch]
|
|
153
|
+
* @property {boolean} [dirty] Working tree had uncommitted changes.
|
|
154
|
+
* @property {string} [artifact] Path to the built thing. NOT stored here — see store.js.
|
|
155
|
+
* @property {string} [artifactSha256]
|
|
156
|
+
* @property {string} [builtAt] ISO timestamp.
|
|
157
|
+
* @property {string} [platform] e.g. 'darwin-arm64'. Comparing across platforms warns.
|
|
158
|
+
* @property {string} [tool] Stays Fixed version that captured it.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Which of the two runs of the same build this is.
|
|
163
|
+
*
|
|
164
|
+
* We run the new build TWICE, so `a` and `b` are the same bytes minutes apart, and anything
|
|
165
|
+
* that disagrees between them is the product arguing with itself. `single` is one run with
|
|
166
|
+
* no wobble measurement — honest, but weaker, and it has to say so.
|
|
167
|
+
*
|
|
168
|
+
* @typedef {'a'|'b'|'single'} CaptureRun
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* One run of one journey against one build.
|
|
173
|
+
*
|
|
174
|
+
* @typedef {object} Capture
|
|
175
|
+
* @property {string} id Sortable: '20260829-013245-a'.
|
|
176
|
+
* @property {string} journey Journey name.
|
|
177
|
+
* @property {JourneySource} [source] Copied off the journey so a stored capture explains itself.
|
|
178
|
+
* @property {BuildFingerprint} build
|
|
179
|
+
* @property {CaptureRun} run
|
|
180
|
+
* @property {string} startedAt ISO timestamp.
|
|
181
|
+
* @property {number} durationMs
|
|
182
|
+
* @property {Observation[]} observations
|
|
183
|
+
* @property {Coverage} [coverage] What this capture did NOT manage to look at.
|
|
184
|
+
* @property {boolean} [complete] False when the file was read back torn — see store.js.
|
|
185
|
+
* @property {string} [note]
|
|
186
|
+
* @property {string} [rules] Id of the normalisation rule set applied, if any.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Wobble — what a build disagrees with itself about
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* One path that would not sit still between two runs of the SAME build.
|
|
195
|
+
*
|
|
196
|
+
* @typedef {object} WobbleEntry
|
|
197
|
+
* @property {string} path
|
|
198
|
+
* @property {Channel} channel
|
|
199
|
+
* @property {DifferenceKind} kind `appeared` / `vanished` is the worst kind: the product
|
|
200
|
+
* does not agree with itself about what exists.
|
|
201
|
+
* @property {ObservedValue} [a] Value in the first run, when it had one.
|
|
202
|
+
* @property {ObservedValue} [b] Value in the second run, when it had one.
|
|
203
|
+
* @property {number} distance Rough size of the disagreement, 0..1. For ranking and for
|
|
204
|
+
* reading. NEVER a threshold — v2 has no tolerances.
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* What differed between two runs of the same build: the product's own noise, measured
|
|
209
|
+
* rather than guessed.
|
|
210
|
+
*
|
|
211
|
+
* @typedef {object} Wobble
|
|
212
|
+
* @property {string} buildId
|
|
213
|
+
* @property {string} journey
|
|
214
|
+
* @property {[string, string]} runs The two capture ids that were compared.
|
|
215
|
+
* @property {WobbleEntry[]} entries Sorted by path.
|
|
216
|
+
* @property {string[]} unstable Just the paths, for fast set arithmetic.
|
|
217
|
+
* @property {number} steady How many paths agreed. The denominator that makes the
|
|
218
|
+
* unstable count mean something.
|
|
219
|
+
* @property {boolean} measured False when only one run exists, so nothing was measured
|
|
220
|
+
* and the run must say so out loud.
|
|
221
|
+
*/
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* What came out of subtracting the wobble from the differences.
|
|
225
|
+
*
|
|
226
|
+
* @typedef {object} WobbleSubtraction
|
|
227
|
+
* @property {Difference[]} real Differences at paths the build holds steady. These are
|
|
228
|
+
* the only ones worth an agent's tokens.
|
|
229
|
+
* @property {Difference[]} noise Differences at paths that wobble anyway.
|
|
230
|
+
* @property {WobbleEntry[]} newlyUnstable
|
|
231
|
+
* Steady in the reference, wobbling now. Nobody else's tool
|
|
232
|
+
* catches this: the change made something unpredictable,
|
|
233
|
+
* which is a bug even though no value is "wrong".
|
|
234
|
+
* @property {boolean} couldTellNewlyUnstable
|
|
235
|
+
* False when we had no stability record for the reference,
|
|
236
|
+
* so `newlyUnstable` is empty for lack of evidence rather
|
|
237
|
+
* than because nothing became unstable.
|
|
238
|
+
* @property {string} note One plain sentence stating exactly that.
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Differences — reference against candidate
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* `appeared` and `vanished` are the important ones. A path that stopped existing is a door
|
|
247
|
+
* that closed, and no pixel comparison has ever noticed one.
|
|
248
|
+
* @typedef {'changed'|'appeared'|'vanished'} DifferenceKind
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* One path that differs between the reference and the candidate.
|
|
253
|
+
*
|
|
254
|
+
* @typedef {object} Difference
|
|
255
|
+
* @property {string} path
|
|
256
|
+
* @property {Channel} channel
|
|
257
|
+
* @property {DifferenceKind} kind
|
|
258
|
+
* @property {ObservedValue} [reference] Absent when the path appeared.
|
|
259
|
+
* @property {ObservedValue} [candidate] Absent when the path vanished.
|
|
260
|
+
* @property {number} distance Rough size, 0..1. Ranking only.
|
|
261
|
+
* @property {string} [journey]
|
|
262
|
+
* @property {boolean} [real] Survived the wobble floor. Set by subtractWobble.
|
|
263
|
+
* @property {boolean} [wobbling] This path does not sit still in this build anyway.
|
|
264
|
+
* @property {boolean} [proven] Re-checked against the old build booted live, and it
|
|
265
|
+
* survived. Cheap suspicion, expensive proof.
|
|
266
|
+
* @property {string} [describe] One plain sentence, carried from the observation.
|
|
267
|
+
* @property {string} [evidence] A picture or log that shows it, for a human.
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Findings — differences clustered into something worth acting on
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The classes an agent may never wave through on its own. Anything in one of these goes to a
|
|
276
|
+
* person, whatever the agent believes it meant to change.
|
|
277
|
+
* @typedef {'money'|'sign-in'|'data-loss'|'crash'|'guard'|'ordinary'} FindingClass
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* A cluster of differences with one likely cause, written for someone to act on.
|
|
282
|
+
*
|
|
283
|
+
* The whole point of clustering: one missing stylesheet is one finding, not four hundred
|
|
284
|
+
* differences. An agent should be able to read the title and know what to go and look at.
|
|
285
|
+
*
|
|
286
|
+
* @typedef {object} Finding
|
|
287
|
+
* @property {string} id Stable across runs while the cause persists, so the same
|
|
288
|
+
* finding is not reported as new every time.
|
|
289
|
+
* @property {string} title Plain English, no jargon, no test ids: "Saving a session
|
|
290
|
+
* no longer writes the file."
|
|
291
|
+
* @property {string} why The likely cause, said plainly, and hedged when it is a guess.
|
|
292
|
+
* @property {FindingClass} class
|
|
293
|
+
* @property {Difference[]} differences
|
|
294
|
+
* @property {number} rank Higher is more urgent. Distance from the changed code is
|
|
295
|
+
* the biggest term: a break far from the edit is the very
|
|
296
|
+
* definition of a side effect.
|
|
297
|
+
* @property {string} [signature] What the cluster was grouped on.
|
|
298
|
+
* @property {string[]} [nearFiles] Source files the cluster points at, nearest first.
|
|
299
|
+
* @property {boolean} [sealed] In an unwaivable class. Goes to a person, full stop.
|
|
300
|
+
* @property {string} [evidence] A picture, a log, a diff — for the human, at the end.
|
|
301
|
+
* @property {number} [count] How many differences this one finding stands for. Five hundred
|
|
302
|
+
* differences are not five hundred findings, and the count is
|
|
303
|
+
* what stops a cluster hiding its own size.
|
|
304
|
+
* @property {string} [summary] One line, for a list. `title` is the headline; this is the
|
|
305
|
+
* sentence under it.
|
|
306
|
+
* @property {string[]} [paths] The addresses involved, for an agent that wants to look.
|
|
307
|
+
* @property {Difference} [sample] One representative difference, so a reader sees the shape
|
|
308
|
+
* without being handed all of them.
|
|
309
|
+
* @property {number} [distance] How far from the code that changed. Bigger is more suspicious:
|
|
310
|
+
* a break far from the edit is the definition of a side effect.
|
|
311
|
+
*/
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Coverage — and mostly, what was NOT checked
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* One thing we did not look at, and what it would take to look at it.
|
|
319
|
+
*
|
|
320
|
+
* This shape carries the self-description requirement: an agent installing the tool reads
|
|
321
|
+
* `unlockedBy` and knows exactly what to install, start or supply. Nobody should have to read
|
|
322
|
+
* documentation to wire this up.
|
|
323
|
+
*
|
|
324
|
+
* @typedef {object} CoverageGap
|
|
325
|
+
* @property {string} what What is not covered, in plain English.
|
|
326
|
+
* @property {string} why Why not: missing runtime, refused effect, no snapshot,
|
|
327
|
+
* old build will not compile.
|
|
328
|
+
* @property {string} [unlockedBy] The concrete thing that would fix it: "install a Java
|
|
329
|
+
* runtime", "add an SSH host", "supply a database snapshot".
|
|
330
|
+
* @property {Channel} [channel]
|
|
331
|
+
* @property {Surface} [surface]
|
|
332
|
+
* @property {number} [doors] How many addressable things this gap hides, when countable.
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* What was checked and, much more importantly, what was not.
|
|
337
|
+
*
|
|
338
|
+
* A tool that reports "nothing changed" is indistinguishable from a broken tool. Coverage is
|
|
339
|
+
* how the difference is made visible instead of pretended away.
|
|
340
|
+
*
|
|
341
|
+
* @typedef {object} Coverage
|
|
342
|
+
* @property {number} paths Addresses observed.
|
|
343
|
+
* @property {number} journeys Journeys walked.
|
|
344
|
+
* @property {Partial<Record<Channel, number>>} byChannel Paths observed per channel.
|
|
345
|
+
* @property {number} [doorsKnown] Doors the contract channel found in the source.
|
|
346
|
+
* @property {number} [doorsWalked] How many of those any journey actually opened.
|
|
347
|
+
* @property {CoverageGap[]} gaps Everything we could not see. Never empty on a real run.
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Verdict — what a whole run concluded
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* How the reference was obtained, because it changes how much the answer is worth.
|
|
356
|
+
*
|
|
357
|
+
* - `paired` The old build was booted live on this machine, in this minute.
|
|
358
|
+
* - `stored-record` Compared against observations stored the last time the old build ran.
|
|
359
|
+
* Genuinely weaker: it lets back in every difference that comes from the
|
|
360
|
+
* day being different. It must announce itself in those words, every run.
|
|
361
|
+
*
|
|
362
|
+
* @typedef {'paired'|'stored-record'} ReferenceMode
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* What a whole run concluded.
|
|
367
|
+
*
|
|
368
|
+
* @typedef {object} Verdict
|
|
369
|
+
* @property {string} runId
|
|
370
|
+
* @property {string} product
|
|
371
|
+
* @property {boolean} ok Nothing unintended survived the wobble floor.
|
|
372
|
+
* @property {ReferenceMode} mode
|
|
373
|
+
* @property {string} [modeWarning] Present whenever `mode` is 'stored-record'.
|
|
374
|
+
* @property {BuildFingerprint} reference
|
|
375
|
+
* @property {BuildFingerprint} candidate
|
|
376
|
+
* @property {Finding[]} findings Ranked, worst first. The only thing an agent should read.
|
|
377
|
+
* @property {number} differencesReal
|
|
378
|
+
* @property {number} differencesNoise
|
|
379
|
+
* @property {WobbleEntry[]} newlyUnstable
|
|
380
|
+
* @property {Coverage} coverage
|
|
381
|
+
* @property {string} summary One paragraph of plain English. What changed, what did not,
|
|
382
|
+
* what was not looked at.
|
|
383
|
+
* @property {number} durationMs
|
|
384
|
+
* @property {string} startedAt
|
|
385
|
+
* @property {string} [tool]
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
export {};
|
|
389
|
+
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// Normalisation — the rules that decide what is a difference and what is churn
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* What a rule does to a value.
|
|
396
|
+
*
|
|
397
|
+
* - `replace` Rewrite text that matches a pattern. The workhorse.
|
|
398
|
+
* - `round` Cut a float back to a sane number of digits.
|
|
399
|
+
* - `sort` Put an unordered collection in a fixed order.
|
|
400
|
+
* - `drop` Remove a piece of the value entirely. The dangerous one — it does not
|
|
401
|
+
* normalise a difference, it deletes the ability to see one. Ships unused.
|
|
402
|
+
*
|
|
403
|
+
* @typedef {'replace'|'round'|'sort'|'drop'} RuleKind
|
|
404
|
+
*/
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* One normalisation rule.
|
|
408
|
+
*
|
|
409
|
+
* Rules are DATA, not code: every field here survives `JSON.stringify`, so a project keeps
|
|
410
|
+
* its own rules in git beside its config, reviews them in a pull request, and can see exactly
|
|
411
|
+
* what its tool is choosing not to look at.
|
|
412
|
+
*
|
|
413
|
+
* `wouldHide` is required by convention rather than by the type system, and no rule should
|
|
414
|
+
* ship without one. A rule set nobody can audit is how a difference machine goes quiet.
|
|
415
|
+
*
|
|
416
|
+
* @typedef {object} NormaliseRule
|
|
417
|
+
* @property {string} id Stable, dotted: 'clock.iso', 'id.uuid'.
|
|
418
|
+
* @property {RuleKind} kind
|
|
419
|
+
* @property {string} what Plain English: what this rewrites.
|
|
420
|
+
* @property {string} why Plain English: why it churns without this.
|
|
421
|
+
* @property {string} wouldHide Plain English: the real change this would wrongly hide.
|
|
422
|
+
* @property {string} [pattern] replace: a regular expression, as a string.
|
|
423
|
+
* @property {string} [flags] replace: default 'g'.
|
|
424
|
+
* @property {string} [with] replace: what to put in its place. '$1' works.
|
|
425
|
+
* @property {boolean} [numbers] replace: also test numeric values, whole-value only.
|
|
426
|
+
* @property {boolean} [keys] replace: also rewrite object keys. Can merge two entries
|
|
427
|
+
* into one — off unless a rule says otherwise.
|
|
428
|
+
* @property {number} [digits] round: significant digits to keep.
|
|
429
|
+
* @property {string[]} [paths] Path globs this rule applies to. Default: every path.
|
|
430
|
+
* @property {Channel[]} [channels] Channels this rule applies to. Default: every channel.
|
|
431
|
+
* @property {string[]} [at] Globs over the position INSIDE the value, written
|
|
432
|
+
* '$.items.3.name'. Used by sort, round and drop.
|
|
433
|
+
* @property {boolean} [off] Shipped, documented, and not switched on.
|
|
434
|
+
* @property {string} [whyOff] Why it is not on by default.
|
|
435
|
+
*/
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* One thing a rule actually changed, so a difference hidden by normalisation can be audited.
|
|
439
|
+
* @typedef {object} Replacement
|
|
440
|
+
* @property {string} ruleId
|
|
441
|
+
* @property {string} what
|
|
442
|
+
* @property {string} why
|
|
443
|
+
* @property {string} wouldHide
|
|
444
|
+
* @property {string} at Where inside the value: '$', '$.items.3.id'.
|
|
445
|
+
* @property {string} before
|
|
446
|
+
* @property {string} after
|
|
447
|
+
*/
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @typedef {object} Explanation
|
|
451
|
+
* @property {ObservedValue} value The value after normalising.
|
|
452
|
+
* @property {Replacement[]} replacements Every change, in the order they were made.
|
|
453
|
+
* @property {string} summary One plain sentence for a report.
|
|
454
|
+
*/
|
|
455
|
+
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
// Store — where observations live on disk
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @typedef {object} Store
|
|
462
|
+
* @property {string} root Project root.
|
|
463
|
+
* @property {string} dir The v2 folder: <root>/.staysfixed/v2.
|
|
464
|
+
* @property {string} buildsDir <dir>/builds — one folder per build fingerprint.
|
|
465
|
+
* @property {string} referencesFile <dir>/references.json — which build is 'working', per product.
|
|
466
|
+
*/
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Enough to find one stored capture again.
|
|
470
|
+
* @typedef {object} CaptureRef
|
|
471
|
+
* @property {string} buildId
|
|
472
|
+
* @property {string} journey
|
|
473
|
+
* @property {string} captureId
|
|
474
|
+
* @property {string} file Absolute path to the JSONL file.
|
|
475
|
+
*/
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* What the store remembers about a build. The build ARTIFACT is not kept here — a paired
|
|
479
|
+
* system that stored every binary would run to tens of gigabytes a year. Artifacts are kept
|
|
480
|
+
* only at markers, by another part of the tool; this record just says where one was.
|
|
481
|
+
*
|
|
482
|
+
* @typedef {object} BuildRecord
|
|
483
|
+
* @property {BuildFingerprint} fingerprint
|
|
484
|
+
* @property {string} firstSeenAt
|
|
485
|
+
* @property {string} lastSeenAt
|
|
486
|
+
* @property {number} captures How many capture files are stored for it.
|
|
487
|
+
* @property {string[]} journeys Journey names captured against it.
|
|
488
|
+
* @property {boolean} [isReference] Filled in by referenceFor / listBuilds.
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Which build a product currently calls 'working'.
|
|
493
|
+
*
|
|
494
|
+
* Cut by an act Asad already performs — saying ship — never by an agent, and never by the
|
|
495
|
+
* tool deciding on its own that a run looked fine.
|
|
496
|
+
*
|
|
497
|
+
* @typedef {object} ReferencePointer
|
|
498
|
+
* @property {string} product
|
|
499
|
+
* @property {string} buildId
|
|
500
|
+
* @property {string} setAt
|
|
501
|
+
* @property {string} [setBy] 'ship-everywhere', a person, a command.
|
|
502
|
+
* @property {string} [note]
|
|
503
|
+
*/
|