assertledger 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.fr.md +5 -2
  2. package/README.md +5 -2
  3. package/conformance/schema-extensions.json +15 -0
  4. package/dist/build-info.json +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +213 -8
  7. package/dist/cli.js.map +1 -1
  8. package/dist/contracts/index.d.ts +467 -4
  9. package/dist/contracts/index.d.ts.map +1 -1
  10. package/dist/contracts/index.js +256 -3
  11. package/dist/contracts/index.js.map +1 -1
  12. package/dist/contracts/qualification.d.ts +528 -0
  13. package/dist/contracts/qualification.d.ts.map +1 -0
  14. package/dist/contracts/qualification.js +357 -0
  15. package/dist/contracts/qualification.js.map +1 -0
  16. package/dist/core/index.d.ts +9 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +46 -2
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/qualification.d.ts +17 -0
  21. package/dist/core/qualification.d.ts.map +1 -0
  22. package/dist/core/qualification.js +571 -0
  23. package/dist/core/qualification.js.map +1 -0
  24. package/dist/diagnostics.d.ts.map +1 -1
  25. package/dist/diagnostics.js +81 -4
  26. package/dist/diagnostics.js.map +1 -1
  27. package/dist/engine/adapters/bun-native.d.ts +35 -0
  28. package/dist/engine/adapters/bun-native.d.ts.map +1 -0
  29. package/dist/engine/adapters/bun-native.js +179 -0
  30. package/dist/engine/adapters/bun-native.js.map +1 -0
  31. package/dist/engine/adapters/bun-test-designated-profile.d.ts +23 -0
  32. package/dist/engine/adapters/bun-test-designated-profile.d.ts.map +1 -0
  33. package/dist/engine/adapters/bun-test-designated-profile.js +24 -0
  34. package/dist/engine/adapters/bun-test-designated-profile.js.map +1 -0
  35. package/dist/engine/container.d.ts +7 -0
  36. package/dist/engine/container.d.ts.map +1 -1
  37. package/dist/engine/container.js +22 -7
  38. package/dist/engine/container.js.map +1 -1
  39. package/dist/engine/git-regression.d.ts.map +1 -1
  40. package/dist/engine/git-regression.js +9 -2
  41. package/dist/engine/git-regression.js.map +1 -1
  42. package/dist/engine/index.d.ts +14 -0
  43. package/dist/engine/index.d.ts.map +1 -1
  44. package/dist/engine/index.js +828 -97
  45. package/dist/engine/index.js.map +1 -1
  46. package/dist/engine/qualification.d.ts +154 -0
  47. package/dist/engine/qualification.d.ts.map +1 -0
  48. package/dist/engine/qualification.js +725 -0
  49. package/dist/engine/qualification.js.map +1 -0
  50. package/dist/engine/test-closure.d.ts +39 -0
  51. package/dist/engine/test-closure.d.ts.map +1 -0
  52. package/dist/engine/test-closure.js +513 -0
  53. package/dist/engine/test-closure.js.map +1 -0
  54. package/dist/engine/witness-import.d.ts +31 -0
  55. package/dist/engine/witness-import.d.ts.map +1 -0
  56. package/dist/engine/witness-import.js +224 -0
  57. package/dist/engine/witness-import.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +3 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +65 -0
  64. package/dist/mcp/index.js.map +1 -1
  65. package/dist/sdk/index.d.ts +292 -3
  66. package/dist/sdk/index.d.ts.map +1 -1
  67. package/dist/sdk/index.js +39 -1
  68. package/dist/sdk/index.js.map +1 -1
  69. package/docs/container-isolation.md +6 -0
  70. package/docs/migration-orchestration-qualification-v1.md +109 -0
  71. package/docs/migration-verification-v4.md +94 -0
  72. package/docs/orchestration-qualification.md +167 -0
  73. package/docs/reference.md +7 -0
  74. package/docs/witness-import.md +67 -0
  75. package/examples/orchestration/README.md +68 -0
  76. package/examples/orchestration/consumer-obligations.json +101 -0
  77. package/examples/orchestration/workspace/bitbucket-pipelines.yml +10 -0
  78. package/examples/orchestration/workspace/command-shape.test.mjs +25 -0
  79. package/examples/orchestration/workspace/commands.mjs +14 -0
  80. package/examples/orchestration/workspace/completion.mjs +39 -0
  81. package/examples/orchestration/workspace/faults.json +1 -0
  82. package/examples/orchestration/workspace/native.test.ts +4 -0
  83. package/examples/orchestration/workspace/package.json +6 -0
  84. package/examples/orchestration/workspace/packages/app/package.json +12 -0
  85. package/examples/orchestration/workspace/packages/app/verdict.txt +1 -0
  86. package/examples/orchestration/workspace/packages/leaf/package.json +9 -0
  87. package/examples/orchestration/workspace/packages/leaf/verdict.txt +1 -0
  88. package/examples/orchestration/workspace/pnpm-lock.yaml +170 -0
  89. package/examples/orchestration/workspace/pnpm-workspace.yaml +2 -0
  90. package/examples/orchestration/workspace/task.mjs +23 -0
  91. package/examples/orchestration/workspace/turbo.json +38 -0
  92. package/examples/orchestration/workspace/wrapper.mjs +45 -0
  93. package/integrations/bun/driver.d.mts +30 -1
  94. package/integrations/bun/driver.mjs +215 -6
  95. package/integrations/bun/preload.mjs +186 -5
  96. package/integrations/bun-native/README.md +86 -0
  97. package/integrations/bun-native/driver.mjs +396 -0
  98. package/integrations/bun-native/preload.mjs +375 -0
  99. package/integrations/skill/SKILL.md +25 -5
  100. package/package.json +1 -1
  101. package/schemas/evidence-manifest.v4.json +995 -0
  102. package/schemas/qualification-execution-request.v1.json +571 -0
  103. package/schemas/qualification-plan.v1.json +528 -0
  104. package/schemas/qualification-receipt.v1.json +821 -0
  105. package/schemas/qualification-replay-result.v1.json +37 -0
  106. package/schemas/verification-request.v4.json +590 -0
  107. package/schemas/witness-import-request.v1.json +231 -0
@@ -14,11 +14,62 @@ if (!path.isAbsolute(root ?? "")) {
14
14
  const preloadPath = fileURLToPath(import.meta.url);
15
15
  const safeApply = Reflect.apply.bind(Reflect);
16
16
  const safeGet = Reflect.get.bind(Reflect);
17
+ const ownDescriptor = Reflect.getOwnPropertyDescriptor.bind(Reflect);
18
+ const prototypeOf = Reflect.getPrototypeOf.bind(Reflect);
19
+ const stringReplace = String.prototype.replace;
20
+ const stringSplit = String.prototype.split;
21
+ const stringTrim = String.prototype.trim;
22
+ const stringStartsWith = String.prototype.startsWith;
23
+ const isErrorInstance = Object.prototype.isPrototypeOf.bind(Error.prototype);
24
+ const ANSI_SGR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "gu");
25
+ const LINE_BREAK = /\r?\n/u;
26
+ const fileKey = (file) =>
27
+ process.platform === "win32" ? path.resolve(file).toLowerCase() : path.resolve(file);
28
+
29
+ // A designated selection runs one existing test ("run") or only loads its file ("load"). Without it
30
+ // the preload keeps the candidate-file behavior: only assertSame failures can be owned.
31
+ const designatedSelection = (() => {
32
+ const value = process.env.ASSERTLEDGER_BUN_DESIGNATED;
33
+ if (value === undefined) return undefined;
34
+ const parsed = JSON.parse(value);
35
+ if (parsed?.mode !== "run" && parsed?.mode !== "load") {
36
+ throw new Error("ASSERTLEDGER_BUN_PRELOAD_CONFIGURATION_INVALID");
37
+ }
38
+ return parsed;
39
+ })();
40
+ const designatedKeys = (
41
+ designatedSelection === undefined
42
+ ? []
43
+ : designatedSelection.mode === "run"
44
+ ? [designatedSelection.test]
45
+ : designatedSelection.tests
46
+ ).map((test) => JSON.stringify([fileKey(path.resolve(root, test.file)), ...test.path]));
47
+ // The expected line may be copied from Bun's output, which prefixes "error:".
48
+ const expectedFailure =
49
+ designatedSelection?.mode === "run" && typeof designatedSelection.expectedFailure === "string"
50
+ ? designatedSelection.expectedFailure
51
+ .replace(ANSI_SGR, "")
52
+ .split(LINE_BREAK)[0]
53
+ .trim()
54
+ .replace(/^error:\s*/u, "")
55
+ : null;
56
+
57
+ /** The first line of a failure message without terminal colors, as Bun prints it after "error:". */
58
+ function failureSignature(error) {
59
+ const descriptor = ownDescriptor(error, "message");
60
+ const message = typeof descriptor?.value === "string" ? descriptor.value : "";
61
+ const plain = safeApply(stringReplace, message, [ANSI_SGR, ""]);
62
+ return safeApply(stringTrim, safeApply(stringSplit, plain, [LINE_BREAK])[0] ?? "", []);
63
+ }
64
+
17
65
  const rawAssertSame = assertionHelper.assertSame;
18
66
  const verifyIssuedError = assertionHelper.isAssertSameFailure;
19
67
  const issuedDuringTest = new WeakMap();
20
68
  const markIssuingTest = WeakMap.prototype.set.bind(issuedDuringTest);
21
69
  const issuingTest = WeakMap.prototype.get.bind(issuedDuringTest);
70
+ const expectIssued = new WeakMap();
71
+ const markExpectIssued = WeakMap.prototype.set.bind(expectIssued);
72
+ const expectIssuingTest = WeakMap.prototype.get.bind(expectIssued);
22
73
  const activeTestExecution = new AsyncLocalStorage();
23
74
  const currentTestId = AsyncLocalStorage.prototype.getStore.bind(activeTestExecution);
24
75
  const runInTest = AsyncLocalStorage.prototype.run.bind(activeTestExecution);
@@ -31,6 +82,71 @@ function scopedAssertSame(...arguments_) {
31
82
  throw error;
32
83
  }
33
84
  }
85
+
86
+ // Only a built-in matcher, present before any test file can extend expect, can issue an owned
87
+ // failure, and only when its message has Bun's matcher form. Usage errors and custom matchers stay
88
+ // ordinary errors.
89
+ const nativeExpect = bunTest.expect;
90
+ const builtInMatchers = new Set();
91
+ const matcherChains = new Set(["not", "resolves", "rejects"]);
92
+ const matcherPrototype =
93
+ designatedSelection === undefined ? null : prototypeOf(nativeExpect(undefined));
94
+ for (const name of matcherPrototype === null ? [] : Reflect.ownKeys(matcherPrototype)) {
95
+ const descriptor = ownDescriptor(matcherPrototype, name);
96
+ if (typeof name === "string" && name !== "constructor" && typeof descriptor?.value === "function")
97
+ builtInMatchers.add(name);
98
+ }
99
+ const hasBuiltInMatcher = Set.prototype.has.bind(builtInMatchers);
100
+ const isMatcherChain = Set.prototype.has.bind(matcherChains);
101
+ function markMatcherFailure(error, id) {
102
+ if (id === undefined || !isErrorInstance(error)) return;
103
+ const signature = failureSignature(error);
104
+ if (safeApply(stringStartsWith, signature, ["expect("]))
105
+ markExpectIssued(error, { id, signature });
106
+ }
107
+ function scopedMatchers(matchers) {
108
+ return new Proxy(matchers, {
109
+ get(target, property) {
110
+ const value = safeGet(target, property, target);
111
+ if (typeof property === "string" && isMatcherChain(property)) return scopedMatchers(value);
112
+ if (typeof value !== "function") return value;
113
+ if (typeof property !== "string" || !hasBuiltInMatcher(property)) return value.bind(target);
114
+ return (...arguments_) => {
115
+ const id = currentTestId();
116
+ try {
117
+ const result = safeApply(value, target, arguments_);
118
+ return result && typeof result.then === "function"
119
+ ? Promise.resolve(result).catch((error) => {
120
+ markMatcherFailure(error, id);
121
+ throw error;
122
+ })
123
+ : result;
124
+ } catch (error) {
125
+ markMatcherFailure(error, id);
126
+ throw error;
127
+ }
128
+ };
129
+ },
130
+ });
131
+ }
132
+ const scopedExpect = new Proxy(nativeExpect, {
133
+ apply(target, thisArg, arguments_) {
134
+ return scopedMatchers(safeApply(target, thisArg, arguments_));
135
+ },
136
+ get(target, property) {
137
+ const value = safeGet(target, property, target);
138
+ return typeof value === "function" ? value.bind(target) : value;
139
+ },
140
+ });
141
+
142
+ /** Whether a designated test's failure is an owned assertion with the expected first line. */
143
+ function designatedFailureOwned(error, id) {
144
+ const assertSameOwned = verifyIssuedError(error) && issuingTest(error) === id;
145
+ const expectOwned = expectIssuingTest(error)?.id === id;
146
+ if (!assertSameOwned && !expectOwned) return false;
147
+ return expectedFailure === null || failureSignature(error) === expectedFailure;
148
+ }
149
+
34
150
  const stringify = JSON.stringify.bind(JSON);
35
151
  const evidenceKey = Buffer.alloc(32);
36
152
  let received = 0;
@@ -71,7 +187,19 @@ function registrationFile() {
71
187
  throw new Error("ASSERTLEDGER_BUN_REGISTRATION_FILE_UNAVAILABLE");
72
188
  }
73
189
 
74
- function wrapRegistration(native, cache) {
190
+ // Describe names enclosing the registration in progress. A templated (`each`) or non-string name
191
+ // can never match a designated path.
192
+ const describeStack = [];
193
+ const UNNAMED = Symbol("unnamed");
194
+
195
+ function designatedIndex(file, name) {
196
+ if (designatedSelection === undefined || describeStack.includes(UNNAMED) || name === UNNAMED) {
197
+ return -1;
198
+ }
199
+ return designatedKeys.indexOf(JSON.stringify([fileKey(file), ...describeStack, name]));
200
+ }
201
+
202
+ function wrapRegistration(native, cache, templated = false) {
75
203
  if (cache.has(native)) return cache.get(native);
76
204
  const wrapper = new Proxy(native, {
77
205
  apply(target, thisArg, arguments_) {
@@ -80,14 +208,25 @@ function wrapRegistration(native, cache) {
80
208
  );
81
209
  if (callbackIndex < 0) {
82
210
  const result = safeApply(target, thisArg, arguments_);
83
- return typeof result === "function" ? wrapRegistration(result, cache) : result;
211
+ return typeof result === "function" ? wrapRegistration(result, cache, templated) : result;
84
212
  }
85
213
  const callback = arguments_[callbackIndex];
86
214
  const file = registrationFile();
215
+ const name = !templated && typeof arguments_[0] === "string" ? arguments_[0] : UNNAMED;
216
+ const designated = designatedIndex(file, name);
217
+ if (designatedSelection?.mode === "load") {
218
+ // Loading registers nothing: the file and every module it imports are evaluated only.
219
+ record({ kind: "registered", file, designated });
220
+ return undefined;
221
+ }
87
222
  const wrappedArguments = [...arguments_];
88
223
  wrappedArguments[callbackIndex] = function (...callbackArguments) {
89
224
  const id = randomUUID();
90
- record({ kind: "found", id, file });
225
+ record(
226
+ designatedSelection === undefined
227
+ ? { kind: "found", id, file }
228
+ : { kind: "found", id, file, designated: designated >= 0 },
229
+ );
91
230
  return runInTest(id, () => {
92
231
  const passed = (value) => {
93
232
  record({ kind: "end", id, status: "pass" });
@@ -98,7 +237,10 @@ function wrapRegistration(native, cache) {
98
237
  kind: "end",
99
238
  id,
100
239
  status: "fail",
101
- owned: verifyIssuedError(error) && issuingTest(error) === id,
240
+ owned:
241
+ designatedSelection === undefined
242
+ ? verifyIssuedError(error) && issuingTest(error) === id
243
+ : designated >= 0 && designatedFailureOwned(error, id),
102
244
  });
103
245
  throw error;
104
246
  };
@@ -117,7 +259,42 @@ function wrapRegistration(native, cache) {
117
259
  get(target, property, receiver) {
118
260
  const value = safeGet(target, property, receiver);
119
261
  return typeof value === "function" && property !== "constructor"
120
- ? wrapRegistration(value.bind(target), cache)
262
+ ? wrapRegistration(value.bind(target), cache, templated || property === "each")
263
+ : value;
264
+ },
265
+ });
266
+ cache.set(native, wrapper);
267
+ return wrapper;
268
+ }
269
+
270
+ function wrapDescribe(native, cache, templated = false) {
271
+ if (cache.has(native)) return cache.get(native);
272
+ const wrapper = new Proxy(native, {
273
+ apply(target, thisArg, arguments_) {
274
+ const callbackIndex = arguments_.findIndex(
275
+ (value, index) => typeof value === "function" && (index > 0 || arguments_.length === 1),
276
+ );
277
+ if (callbackIndex < 0) {
278
+ const result = safeApply(target, thisArg, arguments_);
279
+ return typeof result === "function" ? wrapDescribe(result, cache, templated) : result;
280
+ }
281
+ const callback = arguments_[callbackIndex];
282
+ const name = !templated && typeof arguments_[0] === "string" ? arguments_[0] : UNNAMED;
283
+ const wrappedArguments = [...arguments_];
284
+ wrappedArguments[callbackIndex] = function (...callbackArguments) {
285
+ describeStack.push(name);
286
+ try {
287
+ return safeApply(callback, this, callbackArguments);
288
+ } finally {
289
+ describeStack.pop();
290
+ }
291
+ };
292
+ return safeApply(target, thisArg, wrappedArguments);
293
+ },
294
+ get(target, property, receiver) {
295
+ const value = safeGet(target, property, receiver);
296
+ return typeof value === "function" && property !== "constructor"
297
+ ? wrapDescribe(value.bind(target), cache, templated || property === "each")
121
298
  : value;
122
299
  },
123
300
  });
@@ -150,6 +327,7 @@ function wrapHook(nativeHook) {
150
327
  const cache = new WeakMap();
151
328
  const wrappedTest = wrapRegistration(bunTest.test, cache);
152
329
  const wrappedIt = wrapRegistration(bunTest.it, cache);
330
+ const describeCache = new WeakMap();
153
331
  bunTest.mock.module("bun:test", () => ({
154
332
  ...bunTest,
155
333
  test: wrappedTest,
@@ -158,6 +336,9 @@ bunTest.mock.module("bun:test", () => ({
158
336
  afterAll: wrapHook(bunTest.afterAll),
159
337
  beforeEach: wrapHook(bunTest.beforeEach),
160
338
  afterEach: wrapHook(bunTest.afterEach),
339
+ ...(designatedSelection === undefined
340
+ ? {}
341
+ : { describe: wrapDescribe(bunTest.describe, describeCache), expect: scopedExpect }),
161
342
  }));
162
343
  bunTest.mock.module("assertledger/bun", () => ({
163
344
  ...assertionHelper,
@@ -0,0 +1,86 @@
1
+ # Native Bun collection profile 1.0.0
2
+
3
+ This profile is separate from the frozen helper-only Bun adapter. It requires
4
+ Bun 1.4.2, revision 744846f844374847c902b5e7fd59b4342a51ef99.
5
+ The outer Node process runs the driver with argument arrays and a bounded process
6
+ tree; the driver loads the signed-pipe preload and checks complete JUnit evidence.
7
+
8
+ The outer Node driver exits 0 whenever it emits a complete report, including
9
+ reports of assertion failures or operational inner Bun failures. The report's
10
+ `exitCode` retains the inner Bun exit code and its `outcome` retains the semantic
11
+ classification. Driver exceptions, nonzero or missing outer exit codes, signals,
12
+ process errors, timeouts and truncated output invalidate collection regardless
13
+ of a previously emitted report. This separates transport completion from test
14
+ outcome, including on Windows where forced termination may appear as exit 1
15
+ without a signal. This internal native profile contract does not change the
16
+ frozen helper adapter protocols.
17
+
18
+ The internal native report now always includes `assertionFailureFiles`: the
19
+ sorted, unique relative test-file paths owning issued assertions that failed.
20
+ These paths come from controlled test-registration IDs and failure events, never
21
+ from error stacks or helper locations. An attributed `ASSERTION_FAILURE` report
22
+ requires a nonempty subset of `testFiles`; passing and operational reports carry
23
+ an empty array. Counts, declared files, attribution and inner exit status must
24
+ agree with the outcome. Consumers must use these actual failure owners when
25
+ evaluating a required suite, so a failure in another collected file cannot credit
26
+ a passing suite. This is an additive migration of the unreleased native profile;
27
+ old native reports without this field are refused. Frozen v1-v4 helper reports
28
+ and their schema versions are unchanged.
29
+
30
+ The collector translates driver filesystem keys back to the declared test-file
31
+ names before returning `testFiles` and `assertionFailureFiles`. These outward
32
+ facts use sorted, unique POSIX relative paths and preserve declared case, such as
33
+ `Nested/Example.test.ts`, on every host. Aliased declarations that normalize to
34
+ the same filesystem key, or observations without an unambiguous declared name,
35
+ invalidate collection. Internal driver path normalization remains host-specific.
36
+
37
+ The qualified forms are TypeScript tests importing `bun:test`, `test`, `it`,
38
+ `describe`, async functions, helper-module imports, `test.each`, native `expect`
39
+ matchers and `not`/`resolves`/`rejects` chains. The qualified matcher names are
40
+ `toBe`, `toEqual`, `toStrictEqual`, `toHaveProperty`, and `toThrow`; each has
41
+ executable reference, target and neutral witnesses. Comparison operands and
42
+ arguments must be primitive values or recursively passive plain objects/arrays:
43
+ accessors, functions, proxies, custom prototypes and custom coercions are refused
44
+ before matcher issuance. `toHaveProperty` accepts a simple dotted identifier path
45
+ and an optional passive expected value. `toThrow` accepts a function and an optional
46
+ string expectation, or a rejected native Promise with a plain Error. Callback
47
+ errors are tracked separately from errors issued by the native matcher; active
48
+ returned/thrown values invalidate collection. All other matcher names, asymmetric
49
+ matchers and static expect helpers are explicitly unqualified.
50
+ A matcher failure is attributed through the issuing test's
51
+ async-local identity and an error WeakMap. Test errors are never classified from
52
+ their message or JUnit failure text. Cross-test rethrows, hooks, collection errors,
53
+ timeouts, skipped or missing tests, unmatched events and report inconsistency
54
+ cannot establish a detection. Rejected inputs under `resolves`, TypeError and
55
+ RangeError remain operational failures. Custom matchers (`expect.extend`) are
56
+ explicitly unqualified.
57
+
58
+ Both the outer process timeout and Bun's per-test timeout are operational failures.
59
+ JUnit `TimeoutError` observations invalidate collection even when a synchronous
60
+ callback finishes before the outer budget and throws an owned matcher error after
61
+ its per-test deadline. This observation matches the existing v4 Bun driver's
62
+ timeout handling; its private JUnit parser is not imported as a new dependency.
63
+
64
+ The Node compatibility forms `node:test` default/named registrations with callback
65
+ contexts (`t.after`), generated parameterized registrations, helper imports and
66
+ `node:assert/strict` `ok`, `equal` and `strictEqual` assertions are qualified under
67
+ the pinned Bun revision, with reference/target/neutral witnesses for each name.
68
+ Their operands and optional message must be passive. All other assertion names,
69
+ including callback-bearing `throws` and `rejects`, are explicitly unqualified.
70
+ Native Node AssertionError is attributed only when issued by the wrapped assertion
71
+ function in the current test. Manually constructed errors and failing after hooks
72
+ remain operational failures.
73
+ Snapshots, mocks, global-only registrations, callback-style completion, browser
74
+ tests, and other runtime versions need additional qualification witnesses.
75
+ Successful collection is an observation, not a campaign admission or readiness
76
+ claim.
77
+
78
+ Every declared test file must start at least one test; an empty or missing file
79
+ beside an otherwise passing suite invalidates collection. Successful facts include
80
+ the sorted relative paths of files that actually started tests (`testFiles`).
81
+ The collector exposes binary, driver and preload identities. The consuming
82
+ campaign must bind test/helper/configuration input digests and all world identities.
83
+ The pipe MAC detects accidental or unsophisticated report substitution; this is
84
+ trusted-local, unsandboxed execution. It is not an OS boundary or an authentication
85
+ of a hostile producer. Candidates can execute arbitrary code in that process.
86
+ Replay does not reexecute the tests or authenticate their producer.