@intentset/verification 0.6.1 → 0.7.1
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 +50 -0
- package/dist/adapters/common.d.ts +16 -6
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +13 -8
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/playwright.d.ts +3 -0
- package/dist/adapters/playwright.d.ts.map +1 -0
- package/dist/adapters/playwright.js +78 -0
- package/dist/adapters/playwright.js.map +1 -0
- package/dist/binding.d.ts +31 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +46 -0
- package/dist/binding.js.map +1 -0
- package/dist/classify.d.ts +39 -7
- package/dist/classify.d.ts.map +1 -1
- package/dist/classify.js +208 -14
- package/dist/classify.js.map +1 -1
- package/dist/fixture.d.ts +18 -2
- package/dist/fixture.d.ts.map +1 -1
- package/dist/fixture.js +23 -3
- package/dist/fixture.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/records.d.ts +8 -1
- package/dist/records.d.ts.map +1 -1
- package/dist/records.js +24 -1
- package/dist/records.js.map +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +4 -0
- package/dist/report.js.map +1 -1
- package/dist/stale.d.ts +65 -0
- package/dist/stale.d.ts.map +1 -0
- package/dist/stale.js +95 -0
- package/dist/stale.js.map +1 -0
- package/dist/subject.d.ts +130 -0
- package/dist/subject.d.ts.map +1 -0
- package/dist/subject.js +329 -0
- package/dist/subject.js.map +1 -0
- package/package.json +9 -2
- package/src/adapters/common.ts +26 -11
- package/src/adapters/playwright.ts +94 -0
- package/src/binding.ts +68 -0
- package/src/classify.ts +288 -14
- package/src/fixture.ts +36 -3
- package/src/index.ts +4 -0
- package/src/records.ts +40 -2
- package/src/report.ts +4 -0
- package/src/stale.ts +160 -0
- package/src/subject.ts +383 -0
package/dist/subject.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subject of a verification (Core §8, ADR 0013): a hash of what the check
|
|
3
|
+
* is about, so a run record can stay current while the subject is unchanged.
|
|
4
|
+
* Pure: it reads nothing but what it is given, so the same inputs give the
|
|
5
|
+
* same hash on every machine.
|
|
6
|
+
*
|
|
7
|
+
* The subject is:
|
|
8
|
+
*
|
|
9
|
+
* - the verification record and what it verifies (each verified behavior, the
|
|
10
|
+
* rules that govern it and the scenarios that illustrate it), each by the
|
|
11
|
+
* source hash of its whole record: its prose is its promise;
|
|
12
|
+
* - the slices that implement the verified behaviors, the transitive closure
|
|
13
|
+
* of their `dependsOn`, and those the verification names in
|
|
14
|
+
* `verification.exercises`: each by the hash of its **binding fields**
|
|
15
|
+
* (`implements`, `dependsOn`, `claims`, `usesResources`, `entrypoints`;
|
|
16
|
+
* never its prose) and the content of every file its claims resolve to;
|
|
17
|
+
* A slice reached only through `dependsOn` is a **dependency**: under
|
|
18
|
+
* `dependencies: "entrypoint"` (`evidence.dependencies`) it contributes its
|
|
19
|
+
* binding fields, the files of its `entrypoints` and the records of the
|
|
20
|
+
* contracts it exposes or consumes and the decisions those name (a
|
|
21
|
+
* `contract` and a `decision` part), not its other claimed files; the
|
|
22
|
+
* files left out are `dependencyInternals`, so a report can say what the
|
|
23
|
+
* subject could not see (EVID007). An implementing slice and an exercised
|
|
24
|
+
* slice always contribute every file. `dependencies: "files"` is the rule
|
|
25
|
+
* of Core §8 up to 0.7.0 (ADR 0013, amended 2026-10-15, second);
|
|
26
|
+
* - the resources the verification exercises: each by its registry entry and
|
|
27
|
+
* the content of the files its path matches. A resource counts where
|
|
28
|
+
* `verification.exercises` names it, or where a slice that implements a
|
|
29
|
+
* verified behavior lists it in `usesResources` and also claims a file it
|
|
30
|
+
* matches (the slice's own resource). A resource a slice merely lists is not
|
|
31
|
+
* hashed, so a change to a backend a web slice talks to does not move every
|
|
32
|
+
* check of that slice; `resourceMode: "used"` (`evidence.resources: used`)
|
|
33
|
+
* hashes the files of every resource an included slice lists, as Core §8
|
|
34
|
+
* did from 0.7.0 to 0.7.1 (ADR 0013, amended 2026-10-15). What a subject
|
|
35
|
+
* leaves out is `unexercised`, so a report can say what it could not see
|
|
36
|
+
* (EVID006);
|
|
37
|
+
* - the test file (`locator`) and the local files it imports, transitively,
|
|
38
|
+
* which the caller resolves (`localImports`) with the architecture
|
|
39
|
+
* package's resolver and stops at a package boundary;
|
|
40
|
+
* - the repository's global inputs, which the caller has expanded to paths.
|
|
41
|
+
*
|
|
42
|
+
* The slices' and resources' files are resolved by the architecture package
|
|
43
|
+
* and passed in, so this package does not depend on it. The hash is the
|
|
44
|
+
* SHA-256 of the canonical JSON of the sorted entries (ADR 0005), like
|
|
45
|
+
* `graphHash`; a file is its content's SHA-256, a missing one the word
|
|
46
|
+
* `absent`, so a deleted file moves the subject. `parts` carries each entry,
|
|
47
|
+
* so a report can say which part moved (`movedParts`).
|
|
48
|
+
*/
|
|
49
|
+
import { canonicalJson, DEFAULT_EVIDENCE_DEPENDENCIES, sha256Hex, } from "@intentset/core";
|
|
50
|
+
export const SUBJECT_PART_KINDS = [
|
|
51
|
+
"verification",
|
|
52
|
+
"verified",
|
|
53
|
+
"slice",
|
|
54
|
+
"slice-file",
|
|
55
|
+
"contract",
|
|
56
|
+
"decision",
|
|
57
|
+
"resource",
|
|
58
|
+
"resource-file",
|
|
59
|
+
"test",
|
|
60
|
+
"test-import",
|
|
61
|
+
"global-input",
|
|
62
|
+
];
|
|
63
|
+
/** What a missing file hashes as, so a deleted file moves the subject and is told apart from an empty one. */
|
|
64
|
+
export const ABSENT = "absent";
|
|
65
|
+
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
66
|
+
/** The verification's `verification.exercises`, or none. */
|
|
67
|
+
export function exercisesOf(graph, id) {
|
|
68
|
+
const list = graph.artifacts.get(id)?.meta.verification?.exercises;
|
|
69
|
+
return Array.isArray(list) ? list.filter((x) => typeof x === "string") : [];
|
|
70
|
+
}
|
|
71
|
+
/** The behaviors a verification is about: those it verifies, those governed by a rule it verifies, those a scenario it verifies illustrates. */
|
|
72
|
+
export function verifiedBehaviors(graph, verificationId) {
|
|
73
|
+
const behaviors = new Set();
|
|
74
|
+
for (const edge of graph.out.get(verificationId) ?? []) {
|
|
75
|
+
if (edge.kind !== "verifies")
|
|
76
|
+
continue;
|
|
77
|
+
const target = graph.artifacts.get(edge.to);
|
|
78
|
+
if (target === undefined)
|
|
79
|
+
continue;
|
|
80
|
+
if (target.meta.type === "behavior")
|
|
81
|
+
behaviors.add(target.meta.id);
|
|
82
|
+
else if (target.meta.type === "rule") {
|
|
83
|
+
for (const back of graph.in.get(target.meta.id) ?? [])
|
|
84
|
+
if (back.kind === "governedBy")
|
|
85
|
+
behaviors.add(back.from);
|
|
86
|
+
}
|
|
87
|
+
else if (target.meta.type === "scenario") {
|
|
88
|
+
for (const out of graph.out.get(target.meta.id) ?? [])
|
|
89
|
+
if (out.kind === "illustrates")
|
|
90
|
+
behaviors.add(out.to);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return [...behaviors].filter((id) => graph.artifacts.get(id)?.meta.type === "behavior").sort(cmp);
|
|
94
|
+
}
|
|
95
|
+
/** The slices a subject includes, each with why: implementers, their `dependsOn` closure, and the exercised slices. */
|
|
96
|
+
export function includedSlices(graph, verificationId) {
|
|
97
|
+
const why = new Map();
|
|
98
|
+
const add = (id, reason) => {
|
|
99
|
+
const set = why.get(id) ?? new Set();
|
|
100
|
+
set.add(reason);
|
|
101
|
+
why.set(id, set);
|
|
102
|
+
};
|
|
103
|
+
const queue = [];
|
|
104
|
+
const seed = (id, reason) => {
|
|
105
|
+
if (graph.artifacts.get(id)?.meta.type !== "slice")
|
|
106
|
+
return;
|
|
107
|
+
if (!why.has(id))
|
|
108
|
+
queue.push(id);
|
|
109
|
+
add(id, reason);
|
|
110
|
+
};
|
|
111
|
+
for (const behavior of verifiedBehaviors(graph, verificationId)) {
|
|
112
|
+
for (const edge of graph.in.get(behavior) ?? [])
|
|
113
|
+
if (edge.kind === "implements")
|
|
114
|
+
seed(edge.from, "implements");
|
|
115
|
+
}
|
|
116
|
+
for (const id of exercisesOf(graph, verificationId))
|
|
117
|
+
seed(id, "exercises");
|
|
118
|
+
while (queue.length > 0) {
|
|
119
|
+
const id = queue.pop();
|
|
120
|
+
for (const edge of graph.out.get(id) ?? []) {
|
|
121
|
+
if (edge.kind !== "dependsOn")
|
|
122
|
+
continue;
|
|
123
|
+
if (!why.has(edge.to))
|
|
124
|
+
queue.push(edge.to);
|
|
125
|
+
if (graph.artifacts.get(edge.to)?.meta.type === "slice")
|
|
126
|
+
add(edge.to, "dependsOn");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return new Map([...why].sort(([a], [b]) => cmp(a, b)).map(([id, set]) => [id, [...set].sort(cmp).join(",")]));
|
|
130
|
+
}
|
|
131
|
+
/** The contracts a slice exposes or consumes: its public contract records, whose change a dependent sees. */
|
|
132
|
+
export function contractsOf(graph, id) {
|
|
133
|
+
const out = new Set();
|
|
134
|
+
for (const edge of graph.out.get(id) ?? []) {
|
|
135
|
+
if ((edge.kind === "exposes" || edge.kind === "consumes") &&
|
|
136
|
+
graph.artifacts.get(edge.to)?.meta.type === "contract") {
|
|
137
|
+
out.add(edge.to);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return [...out].sort(cmp);
|
|
141
|
+
}
|
|
142
|
+
/** The binding fields of a slice record, canonical: what the subject hashes of it instead of the whole record. */
|
|
143
|
+
export function sliceBinding(graph, id) {
|
|
144
|
+
const artifact = graph.artifacts.get(id);
|
|
145
|
+
if (artifact === undefined)
|
|
146
|
+
return null;
|
|
147
|
+
const links = artifact.meta.links;
|
|
148
|
+
const slice = artifact.meta.slice;
|
|
149
|
+
const sorted = (list) => [...(list ?? [])].sort(cmp);
|
|
150
|
+
return {
|
|
151
|
+
implements: sorted(links.implements),
|
|
152
|
+
dependsOn: sorted(links.dependsOn),
|
|
153
|
+
claims: (slice?.claims ?? [])
|
|
154
|
+
.map((c) => ({ kind: c.kind, path: c.path }))
|
|
155
|
+
.sort((a, b) => cmp(a.kind, b.kind) || cmp(a.path, b.path)),
|
|
156
|
+
usesResources: sorted(slice?.usesResources),
|
|
157
|
+
entrypoints: sorted(slice?.entrypoints),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function computeSubject(input) {
|
|
161
|
+
const { graph, verificationId, readFile, globalInputs } = input;
|
|
162
|
+
const verification = graph.artifacts.get(verificationId);
|
|
163
|
+
if (verification === undefined || verification.meta.type !== "verification") {
|
|
164
|
+
throw new Error(`${verificationId} is not a verification in the graph`);
|
|
165
|
+
}
|
|
166
|
+
const claimed = (id) => input.sliceFiles instanceof Map
|
|
167
|
+
? (input.sliceFiles.get(id) ?? [])
|
|
168
|
+
: (input.sliceFiles[id] ?? []);
|
|
169
|
+
const fileHash = input.fileHash ??
|
|
170
|
+
((path) => {
|
|
171
|
+
const bytes = readFile(path);
|
|
172
|
+
return bytes === undefined ? ABSENT : sha256Hex(bytes);
|
|
173
|
+
});
|
|
174
|
+
const recordPart = (kind, id, extra = {}) => {
|
|
175
|
+
const artifact = graph.artifacts.get(id);
|
|
176
|
+
return {
|
|
177
|
+
kind,
|
|
178
|
+
id,
|
|
179
|
+
hash: artifact === undefined ? ABSENT : artifact.sourceHash,
|
|
180
|
+
...extra,
|
|
181
|
+
...(artifact === undefined ? {} : { path: artifact.path }),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const parts = [recordPart("verification", verificationId)];
|
|
185
|
+
const targets = new Set();
|
|
186
|
+
for (const edge of graph.out.get(verificationId) ?? [])
|
|
187
|
+
if (edge.kind === "verifies")
|
|
188
|
+
targets.add(edge.to);
|
|
189
|
+
for (const id of verifiedBehaviors(graph, verificationId)) {
|
|
190
|
+
targets.add(id);
|
|
191
|
+
for (const edge of graph.out.get(id) ?? [])
|
|
192
|
+
if (edge.kind === "governedBy")
|
|
193
|
+
targets.add(edge.to);
|
|
194
|
+
for (const edge of graph.in.get(id) ?? [])
|
|
195
|
+
if (edge.kind === "illustrates")
|
|
196
|
+
targets.add(edge.from);
|
|
197
|
+
}
|
|
198
|
+
for (const id of targets)
|
|
199
|
+
parts.push(recordPart("verified", id));
|
|
200
|
+
const mode = input.resourceMode ?? "exercised";
|
|
201
|
+
const dependencyMode = input.dependencyMode ?? DEFAULT_EVIDENCE_DEPENDENCIES;
|
|
202
|
+
const internal = new Map();
|
|
203
|
+
const contractIds = new Set();
|
|
204
|
+
const resourceWhy = new Map();
|
|
205
|
+
const useResource = (id, reason) => {
|
|
206
|
+
if (input.resources?.has(id) !== true)
|
|
207
|
+
return;
|
|
208
|
+
const set = resourceWhy.get(id) ?? new Set();
|
|
209
|
+
set.add(reason);
|
|
210
|
+
resourceWhy.set(id, set);
|
|
211
|
+
};
|
|
212
|
+
const listed = new Set();
|
|
213
|
+
for (const id of exercisesOf(graph, verificationId))
|
|
214
|
+
useResource(id, "exercises");
|
|
215
|
+
for (const [id, why] of includedSlices(graph, verificationId)) {
|
|
216
|
+
const artifact = graph.artifacts.get(id);
|
|
217
|
+
parts.push({
|
|
218
|
+
kind: "slice",
|
|
219
|
+
id,
|
|
220
|
+
hash: artifact === undefined ? ABSENT : sha256Hex(canonicalJson(sliceBinding(graph, id))),
|
|
221
|
+
why,
|
|
222
|
+
...(artifact === undefined ? {} : { path: artifact.path }),
|
|
223
|
+
});
|
|
224
|
+
const dependency = why === "dependsOn";
|
|
225
|
+
if (dependency && dependencyMode === "entrypoint") {
|
|
226
|
+
const surface = new Set(artifact?.meta.slice?.entrypoints ?? []);
|
|
227
|
+
for (const path of [...surface].sort(cmp)) {
|
|
228
|
+
parts.push({ kind: "slice-file", id: path, hash: fileHash(path), owner: id, path });
|
|
229
|
+
}
|
|
230
|
+
internal.set(id, [...new Set(claimed(id))].filter((path) => !surface.has(path)).sort(cmp));
|
|
231
|
+
for (const contract of contractsOf(graph, id))
|
|
232
|
+
contractIds.add(contract);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
for (const path of [...new Set(claimed(id))].sort(cmp)) {
|
|
236
|
+
parts.push({ kind: "slice-file", id: path, hash: fileHash(path), owner: id, path });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const implementing = why.split(",").includes("implements");
|
|
240
|
+
for (const resource of artifact?.meta.slice?.usesResources ?? []) {
|
|
241
|
+
if (input.resources?.has(resource) !== true)
|
|
242
|
+
continue;
|
|
243
|
+
if (mode === "used" || (implementing && ownsResource(claimed(id), input.resources.get(resource)?.files ?? []))) {
|
|
244
|
+
useResource(resource, "usesResources");
|
|
245
|
+
}
|
|
246
|
+
else
|
|
247
|
+
listed.add(resource);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const decisionIds = new Set();
|
|
251
|
+
for (const id of [...contractIds].sort(cmp)) {
|
|
252
|
+
parts.push(recordPart("contract", id));
|
|
253
|
+
for (const edge of graph.out.get(id) ?? [])
|
|
254
|
+
if (edge.kind === "informedBy")
|
|
255
|
+
decisionIds.add(edge.to);
|
|
256
|
+
}
|
|
257
|
+
for (const id of [...decisionIds].sort(cmp))
|
|
258
|
+
parts.push(recordPart("decision", id));
|
|
259
|
+
const unexercised = [...listed]
|
|
260
|
+
.filter((id) => !resourceWhy.has(id))
|
|
261
|
+
.sort(cmp)
|
|
262
|
+
.map((id) => ({ id, files: [...new Set(input.resources?.get(id)?.files ?? [])].sort(cmp) }));
|
|
263
|
+
for (const [id, set] of resourceWhy) {
|
|
264
|
+
const resource = input.resources?.get(id);
|
|
265
|
+
parts.push({
|
|
266
|
+
kind: "resource",
|
|
267
|
+
id,
|
|
268
|
+
hash: sha256Hex(canonicalJson(resource.record)),
|
|
269
|
+
why: [...set].sort(cmp).join(","),
|
|
270
|
+
});
|
|
271
|
+
for (const path of [...new Set(resource.files)].sort(cmp)) {
|
|
272
|
+
parts.push({ kind: "resource-file", id: path, hash: fileHash(path), owner: id, path });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const locator = verification.meta.verification?.locator;
|
|
276
|
+
if (locator !== undefined && locator !== "") {
|
|
277
|
+
parts.push({ kind: "test", id: locator, hash: fileHash(locator), path: locator });
|
|
278
|
+
for (const path of [...new Set(input.localImports?.(locator) ?? [])].sort(cmp)) {
|
|
279
|
+
if (path !== locator)
|
|
280
|
+
parts.push({ kind: "test-import", id: path, hash: fileHash(path), path });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const path of [...new Set(globalInputs)]) {
|
|
284
|
+
parts.push({ kind: "global-input", id: path, hash: fileHash(path), path });
|
|
285
|
+
}
|
|
286
|
+
// A claimed file of a dependency that another part hashes (an entrypoint of another slice, the test, a global input) is seen.
|
|
287
|
+
const hashed = new Set(parts.filter((p) => p.path !== undefined).map((p) => p.path));
|
|
288
|
+
const dependencyInternals = [...internal]
|
|
289
|
+
.map(([id, files]) => ({ id, files: files.filter((path) => !hashed.has(path)) }))
|
|
290
|
+
.filter((entry) => entry.files.length > 0)
|
|
291
|
+
.sort((a, b) => cmp(a.id, b.id));
|
|
292
|
+
parts.sort((a, b) => cmp(a.kind, b.kind) || cmp(a.id, b.id) || cmp(a.owner ?? "", b.owner ?? ""));
|
|
293
|
+
const entries = parts.map(({ kind, id, hash, why, owner }) => ({ kind, id, hash, why, owner }));
|
|
294
|
+
return { hash: sha256Hex(canonicalJson(entries)), parts, unexercised, dependencyInternals };
|
|
295
|
+
}
|
|
296
|
+
/** True when a slice claims a file a resource's path matches: the resource is the slice's own, not only one it talks to. */
|
|
297
|
+
function ownsResource(claimedFiles, resourceFiles) {
|
|
298
|
+
if (claimedFiles.length === 0 || resourceFiles.length === 0)
|
|
299
|
+
return false;
|
|
300
|
+
const files = new Set(resourceFiles);
|
|
301
|
+
return claimedFiles.some((path) => files.has(path));
|
|
302
|
+
}
|
|
303
|
+
const partKey = (p) => `${p.kind} ${p.id}${p.owner === undefined ? "" : ` (${p.owner})`}`;
|
|
304
|
+
/** The parts that differ between two subjects, as `kind id` keys, sorted: added, removed or changed. */
|
|
305
|
+
export function movedParts(before, after) {
|
|
306
|
+
const a = new Map(before.parts.map((p) => [partKey(p), p.hash]));
|
|
307
|
+
const b = new Map(after.parts.map((p) => [partKey(p), p.hash]));
|
|
308
|
+
const moved = [];
|
|
309
|
+
for (const [k, h] of a)
|
|
310
|
+
if (b.get(k) !== h)
|
|
311
|
+
moved.push(k);
|
|
312
|
+
for (const k of b.keys())
|
|
313
|
+
if (!a.has(k))
|
|
314
|
+
moved.push(k);
|
|
315
|
+
return moved.sort(cmp);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* The parts of a subject whose file or record is among `changed`: what a
|
|
319
|
+
* `git diff --name-only` since a record's commit says may have moved. A record
|
|
320
|
+
* file that changed only in prose is named too, since the diff cannot tell it
|
|
321
|
+
* from a change to a binding field; the hash decided the subject moved.
|
|
322
|
+
*/
|
|
323
|
+
export function partsChangedAmong(subject, changed) {
|
|
324
|
+
return subject.parts
|
|
325
|
+
.filter((p) => p.path !== undefined && changed.has(p.path))
|
|
326
|
+
.map(partKey)
|
|
327
|
+
.sort(cmp);
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=subject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subject.js","sourceRoot":"","sources":["../src/subject.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentset/verification",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Run records, evidence freshness classification, and test-reporter adapters.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"intentset",
|
|
7
|
+
"verification",
|
|
8
|
+
"test-evidence",
|
|
9
|
+
"vitest",
|
|
10
|
+
"traceability"
|
|
11
|
+
],
|
|
5
12
|
"license": "MIT",
|
|
6
13
|
"type": "module",
|
|
7
14
|
"engines": {
|
|
@@ -28,6 +35,6 @@
|
|
|
28
35
|
}
|
|
29
36
|
},
|
|
30
37
|
"dependencies": {
|
|
31
|
-
"@intentset/core": "^0.
|
|
38
|
+
"@intentset/core": "^0.7.1"
|
|
32
39
|
}
|
|
33
40
|
}
|
package/src/adapters/common.ts
CHANGED
|
@@ -22,8 +22,12 @@ export interface ParsedRun {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface ToRunRecordsOptions {
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Selector -> the verification ID, or every verification ID, that names it, from the graph's automated
|
|
27
|
+
* verifications. A selector two verifications share (one test verifying two behaviors) gives each of them its own
|
|
28
|
+
* record from the same tests.
|
|
29
|
+
*/
|
|
30
|
+
verifications: ReadonlyMap<string, string | readonly string[]>;
|
|
27
31
|
commit: string;
|
|
28
32
|
graphHash: string;
|
|
29
33
|
environment: string;
|
|
@@ -34,12 +38,18 @@ export interface ToRunRecordsOptions {
|
|
|
34
38
|
finishedAt: string;
|
|
35
39
|
/** Where the run's report lives; each record's uri is this plus `#` and the verification ID. */
|
|
36
40
|
uriBase: string;
|
|
41
|
+
/**
|
|
42
|
+
* Verification ID -> the hash of its subject, when the effective binding is subject (Core §8): a record is then
|
|
43
|
+
* written with `binding: "subject"` and that hash, and the commit and graph hash stay as provenance. A
|
|
44
|
+
* verification with no entry, or no map at all, gets a commit-binding record.
|
|
45
|
+
*/
|
|
46
|
+
subjects?: ReadonlyMap<string, string>;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
export interface ConvertedRun {
|
|
40
|
-
/** One record per verification at least one test matched, by verification ID. */
|
|
50
|
+
/** One record per verification whose selector at least one test matched, by verification ID. */
|
|
41
51
|
records: RunRecord[];
|
|
42
|
-
/** Selectors no test matched, sorted:
|
|
52
|
+
/** Selectors no test matched, sorted and listed once however many verifications name them: those verifications get no record from this run, and read as missing. */
|
|
43
53
|
unmatched: string[];
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -82,8 +92,8 @@ export function worst(a: RunResult, b: RunResult): RunResult {
|
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
/**
|
|
85
|
-
* One run record per verification whose selector matched at least one test
|
|
86
|
-
* Several matching tests make one record with the worst result and a
|
|
95
|
+
* One run record per verification whose selector matched at least one test,
|
|
96
|
+
* so a selector two verifications share gives each its own record. Several matching tests make one record with the worst result and a
|
|
87
97
|
* rationale that counts them. Throws when the options would make a malformed
|
|
88
98
|
* record, since that is a caller's mistake rather than evidence.
|
|
89
99
|
*/
|
|
@@ -91,16 +101,19 @@ export function toRunRecords(parsed: ParsedRun, options: ToRunRecordsOptions): C
|
|
|
91
101
|
const matched = new Map<string, { selectors: Set<string>; tests: Set<number> }>();
|
|
92
102
|
const unmatched: string[] = [];
|
|
93
103
|
for (const selector of [...options.verifications.keys()].sort(compareStrings)) {
|
|
94
|
-
const
|
|
104
|
+
const named = options.verifications.get(selector) as string | readonly string[];
|
|
105
|
+
const verificationIds = [...new Set(typeof named === "string" ? [named] : named)].sort(compareStrings);
|
|
95
106
|
const hits = parsed.tests.flatMap((test, index) => (selectorMatches(selector, test.fullName) ? [index] : []));
|
|
96
107
|
if (hits.length === 0) {
|
|
97
108
|
unmatched.push(selector);
|
|
98
109
|
continue;
|
|
99
110
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
for (const verificationId of verificationIds) {
|
|
112
|
+
const entry = matched.get(verificationId) ?? { selectors: new Set<string>(), tests: new Set<number>() };
|
|
113
|
+
entry.selectors.add(selector);
|
|
114
|
+
for (const hit of hits) entry.tests.add(hit);
|
|
115
|
+
matched.set(verificationId, entry);
|
|
116
|
+
}
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
const records: RunRecord[] = [];
|
|
@@ -114,6 +127,7 @@ export function toRunRecords(parsed: ParsedRun, options: ToRunRecordsOptions): C
|
|
|
114
127
|
.map(([r, n]) => `${n} ${r}`)
|
|
115
128
|
.join(", ");
|
|
116
129
|
const names = [...selectors].sort(compareStrings).join(", ");
|
|
130
|
+
const subjectHash = options.subjects?.get(verificationId);
|
|
117
131
|
records.push({
|
|
118
132
|
evidenceId: deriveEvidenceId(verificationId, options.commit, options.graphHash, options.finishedAt),
|
|
119
133
|
verificationId,
|
|
@@ -127,6 +141,7 @@ export function toRunRecords(parsed: ParsedRun, options: ToRunRecordsOptions): C
|
|
|
127
141
|
finishedAt: options.finishedAt,
|
|
128
142
|
result,
|
|
129
143
|
uri: `${options.uriBase}#${encodeURIComponent(verificationId)}`,
|
|
144
|
+
...(subjectHash === undefined ? {} : { binding: "subject" as const, subjectHash }),
|
|
130
145
|
rationale: `${results.length} test${results.length === 1 ? "" : "s"} matched ${names}: ${counts}.`,
|
|
131
146
|
});
|
|
132
147
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `playwright test --reporter=json`: `suites[]`, each a file whose `suites[]` are `describe` groups and whose
|
|
3
|
+
* `specs[]` are the tests, each spec holding one `tests[]` entry per project it ran in. A test's full name is its
|
|
4
|
+
* describe titles and its own, joined with " > " (the file's own suite is left out, as the runner's list reporter
|
|
5
|
+
* leaves it out), so a selector is matched as it is for the other runners; a test that runs in several projects makes
|
|
6
|
+
* one `ParsedTest` for each, and the worst of them is the verification's result.
|
|
7
|
+
*
|
|
8
|
+
* Results follow the test's own `status`: expected is pass (a `test.fail()` that failed as declared is expected),
|
|
9
|
+
* skipped is skip (`test.skip` and `test.fixme` are skipped), flaky is pass (it failed and passed on a retry, and the
|
|
10
|
+
* retry policy is the project's), and unexpected is fail when its last attempt was an assertion and error when it
|
|
11
|
+
* timed out, was interrupted or threw anything else. Errors the report lists for the whole run (a global setup that
|
|
12
|
+
* failed) are listed in `problems`, since no test title can carry them to a selector.
|
|
13
|
+
*/
|
|
14
|
+
import type { RunResult } from "../records.ts";
|
|
15
|
+
import type { ParsedRun, ParsedTest } from "./common.ts";
|
|
16
|
+
|
|
17
|
+
export function parsePlaywrightJson(json: unknown): ParsedRun {
|
|
18
|
+
if (!isRecord(json) || !Array.isArray(json.suites)) {
|
|
19
|
+
throw new TypeError("This is not Playwright JSON reporter output: expected an object with a suites array.");
|
|
20
|
+
}
|
|
21
|
+
const tests: ParsedTest[] = [];
|
|
22
|
+
const problems: string[] = [];
|
|
23
|
+
if (Array.isArray(json.errors)) {
|
|
24
|
+
for (const error of json.errors) {
|
|
25
|
+
problems.push(`The run reported an error: ${firstLine(isRecord(error) ? error.message : error) || "no message"}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
json.suites.forEach((file, index) => {
|
|
29
|
+
if (!isRecord(file)) throw new TypeError(`suites[${index}] is not an object.`);
|
|
30
|
+
walk(file, [], tests, problems);
|
|
31
|
+
});
|
|
32
|
+
return { tests, problems };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function walk(suite: Record<string, unknown>, ancestors: string[], tests: ParsedTest[], problems: string[]): void {
|
|
36
|
+
for (const spec of Array.isArray(suite.specs) ? suite.specs : []) {
|
|
37
|
+
if (!isRecord(spec)) throw new TypeError("A spec in the report is not an object.");
|
|
38
|
+
const title = typeof spec.title === "string" ? spec.title : "";
|
|
39
|
+
const fullName = [...ancestors, title].join(" > ");
|
|
40
|
+
for (const test of Array.isArray(spec.tests) ? spec.tests : []) {
|
|
41
|
+
if (!isRecord(test)) throw new TypeError(`${fullName} has a test entry that is not an object.`);
|
|
42
|
+
const result = resultOf(test, fullName, problems);
|
|
43
|
+
tests.push({ fullName, result, durationMs: durationOf(test) });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const child of Array.isArray(suite.suites) ? suite.suites : []) {
|
|
47
|
+
if (!isRecord(child)) throw new TypeError("A suite in the report is not an object.");
|
|
48
|
+
const title = typeof child.title === "string" ? child.title : "";
|
|
49
|
+
walk(child, title === "" ? ancestors : [...ancestors, title], tests, problems);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resultOf(test: Record<string, unknown>, fullName: string, problems: string[]): RunResult {
|
|
54
|
+
switch (test.status) {
|
|
55
|
+
case "expected":
|
|
56
|
+
case "flaky":
|
|
57
|
+
return "pass";
|
|
58
|
+
case "skipped":
|
|
59
|
+
return "skip";
|
|
60
|
+
case "unexpected":
|
|
61
|
+
return failureKind(lastResult(test));
|
|
62
|
+
default:
|
|
63
|
+
problems.push(`${fullName} has the unknown status ${JSON.stringify(test.status)}, read as error`);
|
|
64
|
+
return "error";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A timeout or an interrupted run is an error; an assertion that did not hold is a fail; anything else thrown is an error. */
|
|
69
|
+
function failureKind(last: Record<string, unknown> | undefined): RunResult {
|
|
70
|
+
if (last === undefined) return "fail";
|
|
71
|
+
if (last.status === "timedOut" || last.status === "interrupted") return "error";
|
|
72
|
+
const error = isRecord(last.error) ? last.error : undefined;
|
|
73
|
+
const message = typeof error?.message === "string" ? error.message : "";
|
|
74
|
+
return /\bexpect\(|AssertionError/.test(message) || message === "" ? "fail" : "error";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function lastResult(test: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
78
|
+
const results = Array.isArray(test.results) ? test.results : [];
|
|
79
|
+
const last = results[results.length - 1];
|
|
80
|
+
return isRecord(last) ? last : undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function durationOf(test: Record<string, unknown>): number | null {
|
|
84
|
+
const duration = lastResult(test)?.duration;
|
|
85
|
+
return typeof duration === "number" && Number.isFinite(duration) ? duration : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function firstLine(value: unknown): string {
|
|
89
|
+
return typeof value === "string" ? (value.trim().split("\n")[0] ?? "") : "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
93
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
}
|
package/src/binding.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which binding judges run evidence (Core §8, ADR 0013) and the subjects it
|
|
3
|
+
* needs. The decision is pure: the repository's configuration and whether it
|
|
4
|
+
* has slice claims. Computing a subject needs what the architecture package
|
|
5
|
+
* resolves (claimed files, import closures), which this package does not
|
|
6
|
+
* depend on, so a caller hands it in as a `SubjectSource`.
|
|
7
|
+
*/
|
|
8
|
+
import type { EvidenceBinding, EvidenceBindingReason, Graph } from "@intentset/core";
|
|
9
|
+
import { compareStrings } from "./diagnostic.ts";
|
|
10
|
+
import { ABSENT, computeSubject, type Subject, type SubjectInput } from "./subject.ts";
|
|
11
|
+
import { sha256Hex } from "@intentset/core";
|
|
12
|
+
|
|
13
|
+
export interface BindingDecision {
|
|
14
|
+
binding: EvidenceBinding;
|
|
15
|
+
/** `configured`, `default` (subject, since slices exist) or `no-slices` (commit, since there is nothing to hash). */
|
|
16
|
+
reason: EvidenceBindingReason;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Core §8: `binding: subject` is the default wherever the repository has slice
|
|
21
|
+
* claims; `binding: commit` is the stricter mode a repository or gate asks
|
|
22
|
+
* for; a repository with no slice claims is judged by commit binding unless it
|
|
23
|
+
* asked for that, and says so (EVID004), whatever it asked for.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveBinding(configured: EvidenceBinding | null, hasSlices: boolean): BindingDecision {
|
|
26
|
+
if (configured === "commit") return { binding: "commit", reason: "configured" };
|
|
27
|
+
if (!hasSlices) return { binding: "commit", reason: "no-slices" };
|
|
28
|
+
return { binding: "subject", reason: configured === "subject" ? "configured" : "default" };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** True when some slice that is not retired claims files: the repository has a subject to hash. */
|
|
32
|
+
export function hasSliceClaims(graph: Graph): boolean {
|
|
33
|
+
for (const artifact of graph.artifacts.values()) {
|
|
34
|
+
if (
|
|
35
|
+
artifact.meta.type === "slice" &&
|
|
36
|
+
artifact.meta.status !== "retired" &&
|
|
37
|
+
(artifact.meta.slice?.claims.length ?? 0) > 0
|
|
38
|
+
)
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** What a caller resolves for the subject hash: the architecture package's claims and imports, over one tree. */
|
|
45
|
+
export type SubjectSource = Omit<SubjectInput, "graph" | "verificationId"> & {
|
|
46
|
+
/** True for a file that can move some subject: a slice's, a used resource's, a global input, a test, a record. */
|
|
47
|
+
covers: (path: string) => boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** The subject of every verification in the graph, by ID, with each file hashed once. */
|
|
51
|
+
export function subjectsFor(graph: Graph, source: SubjectSource): Map<string, Subject> {
|
|
52
|
+
const hashes = new Map<string, string>();
|
|
53
|
+
const fileHash = (path: string): string => {
|
|
54
|
+
let hash = hashes.get(path);
|
|
55
|
+
if (hash === undefined) {
|
|
56
|
+
const bytes = source.readFile(path);
|
|
57
|
+
hash = bytes === undefined ? ABSENT : sha256Hex(bytes);
|
|
58
|
+
hashes.set(path, hash);
|
|
59
|
+
}
|
|
60
|
+
return hash;
|
|
61
|
+
};
|
|
62
|
+
const subjects = new Map<string, Subject>();
|
|
63
|
+
const ids = [...graph.artifacts.keys()].filter((id) => graph.artifacts.get(id)?.meta.type === "verification");
|
|
64
|
+
for (const id of ids.sort(compareStrings)) {
|
|
65
|
+
subjects.set(id, computeSubject({ ...source, graph, verificationId: id, fileHash }));
|
|
66
|
+
}
|
|
67
|
+
return subjects;
|
|
68
|
+
}
|