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
@@ -0,0 +1,375 @@
1
+ import * as bunTest from "bun:test";
2
+ import nodeAssert from "node:assert/strict";
3
+ import { AsyncLocalStorage } from "node:async_hooks";
4
+ import { createHmac, randomUUID } from "node:crypto";
5
+ import { closeSync, readSync, writeSync } from "node:fs";
6
+ import path from "node:path";
7
+ import * as nodeTest from "node:test";
8
+ import { fileURLToPath } from "node:url";
9
+ import { types } from "node:util";
10
+
11
+ const root = process.env.ASSERTLEDGER_BUN_ROOT;
12
+ if (!path.isAbsolute(root ?? "")) throw new Error("ASSERTLEDGER_BUN_PRELOAD_CONFIGURATION_INVALID");
13
+ const preloadPath = fileURLToPath(import.meta.url);
14
+ const safeApply = Reflect.apply.bind(Reflect);
15
+ const safeGet = Reflect.get.bind(Reflect);
16
+ const issuedDuringTest = new WeakMap();
17
+ const markIssuingTest = WeakMap.prototype.set.bind(issuedDuringTest);
18
+ const issuingTest = WeakMap.prototype.get.bind(issuedDuringTest);
19
+ const activeTestExecution = new AsyncLocalStorage();
20
+ const currentTestId = AsyncLocalStorage.prototype.getStore.bind(activeTestExecution);
21
+ const runInTest = AsyncLocalStorage.prototype.run.bind(activeTestExecution);
22
+ const supportedMatchers = new Set([
23
+ "toBe",
24
+ "toEqual",
25
+ "toStrictEqual",
26
+ "toHaveProperty",
27
+ "toThrow",
28
+ ]);
29
+ const supportedNodeAssertions = new Set(["ok", "equal", "strictEqual"]);
30
+ const isProxy = types.isProxy.bind(types);
31
+ const descriptorsOf = Object.getOwnPropertyDescriptors.bind(Object);
32
+ const prototypeOf = Object.getPrototypeOf.bind(Object);
33
+ function refuse() {
34
+ throw new Error("ASSERTLEDGER_BUN_NATIVE_ACTIVE_OR_UNQUALIFIED_INPUT");
35
+ }
36
+ function passive(value, seen = new WeakSet(), depth = 0) {
37
+ if (typeof value === "function") refuse();
38
+ if (value === null || typeof value !== "object") return;
39
+ if (depth > 64 || isProxy(value)) refuse();
40
+ if (seen.has(value)) return;
41
+ seen.add(value);
42
+ const prototype = prototypeOf(value);
43
+ if (prototype !== Object.prototype && prototype !== Array.prototype && prototype !== null)
44
+ refuse();
45
+ const descriptors = descriptorsOf(value);
46
+ for (const descriptor of Reflect.ownKeys(descriptors).map((key) => descriptors[key])) {
47
+ if (!("value" in descriptor)) refuse();
48
+ passive(descriptor.value, seen, depth + 1);
49
+ }
50
+ }
51
+ function passiveThrown(value) {
52
+ if (value === null || typeof value !== "object") return passive(value);
53
+ if (isProxy(value) || prototypeOf(value) !== Error.prototype) refuse();
54
+ const descriptors = descriptorsOf(value);
55
+ for (const descriptor of Reflect.ownKeys(descriptors).map((key) => descriptors[key])) {
56
+ if (!("value" in descriptor)) refuse();
57
+ passive(descriptor.value);
58
+ }
59
+ }
60
+ function validateMatcher(name, arguments_, operand, modifier) {
61
+ if (!supportedMatchers.has(name)) refuse();
62
+ if (name === "toThrow") {
63
+ if (arguments_.length > 1 || (arguments_.length === 1 && typeof arguments_[0] !== "string"))
64
+ refuse();
65
+ if (modifier !== "rejects" && (typeof operand.actual !== "function" || isProxy(operand.actual)))
66
+ refuse();
67
+ return;
68
+ }
69
+ if (name === "toHaveProperty") {
70
+ if (
71
+ arguments_.length < 1 ||
72
+ arguments_.length > 2 ||
73
+ typeof arguments_[0] !== "string" ||
74
+ !/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/u.test(arguments_[0]) ||
75
+ arguments_[0]
76
+ .split(".")
77
+ .some((key) => ["__proto__", "constructor", "prototype"].includes(key))
78
+ )
79
+ refuse();
80
+ if (arguments_.length === 2) passive(arguments_[1]);
81
+ } else {
82
+ if (arguments_.length !== 1) refuse();
83
+ passive(arguments_[0]);
84
+ }
85
+ if (operand.promise === undefined) passive(operand.actual);
86
+ else if (modifier !== "resolves" && modifier !== "rejects") refuse();
87
+ }
88
+ function wrapExpectation(expectation, operand, modifier) {
89
+ return new Proxy(expectation, {
90
+ get(target, property) {
91
+ if (
92
+ property !== "not" &&
93
+ property !== "resolves" &&
94
+ property !== "rejects" &&
95
+ !supportedMatchers.has(property)
96
+ )
97
+ refuse();
98
+ const value = safeGet(target, property, target);
99
+ if (property === "not" || property === "resolves" || property === "rejects")
100
+ return wrapExpectation(value, operand, property === "not" ? modifier : property);
101
+ return (...arguments_) => {
102
+ validateMatcher(property, arguments_, operand, modifier);
103
+ const id = currentTestId();
104
+ const failed = (error) => {
105
+ if (
106
+ id !== undefined &&
107
+ error !== null &&
108
+ typeof error === "object" &&
109
+ !(error instanceof TypeError) &&
110
+ !(error instanceof RangeError) &&
111
+ !operand.invalidOperand &&
112
+ !operand.userErrors.has(error)
113
+ )
114
+ markIssuingTest(error, id);
115
+ throw error;
116
+ };
117
+ const invoke = () => {
118
+ try {
119
+ const result = safeApply(value, target, arguments_);
120
+ if (operand.invalidOperand) refuse();
121
+ return result && typeof result.then === "function"
122
+ ? Promise.resolve(result).catch(failed)
123
+ : result;
124
+ } catch (error) {
125
+ return failed(error);
126
+ }
127
+ };
128
+ return operand.promise !== undefined && modifier === "resolves"
129
+ ? operand.promise.then((actual) => {
130
+ passive(actual);
131
+ return invoke();
132
+ })
133
+ : operand.promise !== undefined && modifier === "rejects"
134
+ ? operand.promise.then(
135
+ () => invoke(),
136
+ (error) => {
137
+ passiveThrown(error);
138
+ return invoke();
139
+ },
140
+ )
141
+ : invoke();
142
+ };
143
+ },
144
+ });
145
+ }
146
+ const nativeExpect = new Proxy(bunTest.expect, {
147
+ get() {
148
+ refuse();
149
+ },
150
+ apply(target, thisArg, arguments_) {
151
+ if (arguments_.length !== 1 || isProxy(arguments_[0])) refuse();
152
+ const operand = {
153
+ actual: arguments_[0],
154
+ promise: undefined,
155
+ userErrors: new WeakSet(),
156
+ invalidOperand: false,
157
+ };
158
+ if (types.isPromise(operand.actual)) {
159
+ if (prototypeOf(operand.actual) !== Promise.prototype) refuse();
160
+ operand.promise = operand.actual;
161
+ }
162
+ const controlledArguments = [...arguments_];
163
+ if (typeof operand.actual === "function") {
164
+ controlledArguments[0] = (...callbackArguments) => {
165
+ try {
166
+ const result = safeApply(operand.actual, undefined, callbackArguments);
167
+ try {
168
+ passive(result);
169
+ } catch (error) {
170
+ operand.invalidOperand = true;
171
+ throw error;
172
+ }
173
+ return result;
174
+ } catch (error) {
175
+ if (error !== null && typeof error === "object") operand.userErrors.add(error);
176
+ try {
177
+ passiveThrown(error);
178
+ } catch (failure) {
179
+ operand.invalidOperand = true;
180
+ throw failure;
181
+ }
182
+ throw error;
183
+ }
184
+ };
185
+ }
186
+ return wrapExpectation(safeApply(target, thisArg, controlledArguments), operand, undefined);
187
+ },
188
+ });
189
+ const AssertionError = nodeAssert.AssertionError;
190
+ const assertionCache = new WeakMap();
191
+ function wrapNodeAssertion(native, name = "ok") {
192
+ if (assertionCache.has(native)) return assertionCache.get(native);
193
+ const wrapper = new Proxy(native, {
194
+ apply(target, thisArg, arguments_) {
195
+ if (!supportedNodeAssertions.has(name)) refuse();
196
+ if (
197
+ arguments_.length < (name === "ok" ? 1 : 2) ||
198
+ arguments_.length > (name === "ok" ? 2 : 3)
199
+ )
200
+ refuse();
201
+ for (const argument of arguments_) passive(argument);
202
+ const id = currentTestId();
203
+ try {
204
+ return safeApply(target, thisArg, arguments_);
205
+ } catch (error) {
206
+ if (id !== undefined && error instanceof AssertionError) markIssuingTest(error, id);
207
+ throw error;
208
+ }
209
+ },
210
+ get(target, property, receiver) {
211
+ const value = safeGet(target, property, receiver);
212
+ return typeof value === "function" &&
213
+ property !== "AssertionError" &&
214
+ property !== "constructor"
215
+ ? wrapNodeAssertion(value, property === "strict" ? "ok" : property)
216
+ : value;
217
+ },
218
+ });
219
+ assertionCache.set(native, wrapper);
220
+ return wrapper;
221
+ }
222
+ const stringify = JSON.stringify.bind(JSON);
223
+ const evidenceKey = Buffer.alloc(32);
224
+ let received = 0;
225
+ while (received < evidenceKey.length) {
226
+ const length = readSync(4, evidenceKey, received, evidenceKey.length - received, null);
227
+ if (length === 0) throw new Error("ASSERTLEDGER_BUN_EVIDENCE_KEY_MISSING");
228
+ received += length;
229
+ }
230
+ closeSync(4);
231
+ function record(event) {
232
+ const body = stringify(event);
233
+ const mac = createHmac("sha256", evidenceKey).update(body).digest("hex");
234
+ writeSync(3, `${stringify({ event, mac })}\n`);
235
+ }
236
+
237
+ function registrationFile() {
238
+ const lines = new Error().stack?.split(/\r?\n/u).slice(1) ?? [];
239
+ for (const line of lines) {
240
+ let location = line.trim().replace(/^at\s+/u, "");
241
+ if (location.endsWith(")") && location.includes("(")) {
242
+ location = location.slice(location.lastIndexOf("(") + 1, -1);
243
+ }
244
+ const match = location.match(/^(.+?):\d+(?::\d+)?$/u);
245
+ if (match === null) continue;
246
+ let file = match[1];
247
+ if (file.startsWith("file:")) {
248
+ try {
249
+ file = fileURLToPath(file);
250
+ } catch {
251
+ continue;
252
+ }
253
+ }
254
+ if (!path.isAbsolute(file)) continue;
255
+ const absolute = path.resolve(file);
256
+ if (absolute === preloadPath) continue;
257
+ return absolute;
258
+ }
259
+ throw new Error("ASSERTLEDGER_BUN_REGISTRATION_FILE_UNAVAILABLE");
260
+ }
261
+
262
+ function wrapRegistration(native, cache) {
263
+ if (cache.has(native)) return cache.get(native);
264
+ const wrapper = new Proxy(native, {
265
+ apply(target, thisArg, arguments_) {
266
+ const callbackIndex = arguments_.findIndex(
267
+ (value, index) => typeof value === "function" && (index > 0 || arguments_.length === 1),
268
+ );
269
+ if (callbackIndex < 0) {
270
+ const result = safeApply(target, thisArg, arguments_);
271
+ return typeof result === "function" ? wrapRegistration(result, cache) : result;
272
+ }
273
+ const callback = arguments_[callbackIndex];
274
+ const file = registrationFile();
275
+ const wrappedArguments = [...arguments_];
276
+ wrappedArguments[callbackIndex] = function (...callbackArguments) {
277
+ const id = randomUUID();
278
+ record({ kind: "found", id, file });
279
+ return runInTest(id, () => {
280
+ const passed = (value) => {
281
+ record({ kind: "end", id, status: "pass" });
282
+ return value;
283
+ };
284
+ const failed = (error) => {
285
+ record({
286
+ kind: "end",
287
+ id,
288
+ status: "fail",
289
+ owned: issuingTest(error) === id,
290
+ });
291
+ throw error;
292
+ };
293
+ try {
294
+ const result = safeApply(callback, this, callbackArguments);
295
+ return result && typeof result.then === "function"
296
+ ? Promise.resolve(result).then(passed, failed)
297
+ : passed(result);
298
+ } catch (error) {
299
+ return failed(error);
300
+ }
301
+ });
302
+ };
303
+ return safeApply(target, thisArg, wrappedArguments);
304
+ },
305
+ get(target, property, receiver) {
306
+ const value = safeGet(target, property, receiver);
307
+ return typeof value === "function" && property !== "constructor"
308
+ ? wrapRegistration(value.bind(target), cache)
309
+ : value;
310
+ },
311
+ });
312
+ cache.set(native, wrapper);
313
+ return wrapper;
314
+ }
315
+
316
+ function wrapHook(nativeHook) {
317
+ return (callback, ...options) => {
318
+ if (typeof callback !== "function")
319
+ return safeApply(nativeHook, bunTest, [callback, ...options]);
320
+ const wrappedCallback = function (...arguments_) {
321
+ const failed = (error) => {
322
+ record({ kind: "hook-error" });
323
+ throw error;
324
+ };
325
+ try {
326
+ const result = safeApply(callback, this, arguments_);
327
+ return result && typeof result.then === "function"
328
+ ? Promise.resolve(result).catch(failed)
329
+ : result;
330
+ } catch (error) {
331
+ return failed(error);
332
+ }
333
+ };
334
+ return safeApply(nativeHook, bunTest, [wrappedCallback, ...options]);
335
+ };
336
+ }
337
+
338
+ const cache = new WeakMap();
339
+ const wrappedTest = wrapRegistration(bunTest.test, cache);
340
+ const wrappedIt = wrapRegistration(bunTest.it, cache);
341
+ bunTest.mock.module("bun:test", () => ({
342
+ ...bunTest,
343
+ expect: nativeExpect,
344
+ test: wrappedTest,
345
+ it: wrappedIt,
346
+ beforeAll: wrapHook(bunTest.beforeAll),
347
+ afterAll: wrapHook(bunTest.afterAll),
348
+ beforeEach: wrapHook(bunTest.beforeEach),
349
+ afterEach: wrapHook(bunTest.afterEach),
350
+ }));
351
+ const wrappedNodeTest = wrapRegistration(nodeTest.test, cache);
352
+ bunTest.mock.module("node:test", () => ({
353
+ ...nodeTest,
354
+ default: wrappedNodeTest,
355
+ test: wrappedNodeTest,
356
+ it: wrapRegistration(nodeTest.it, cache),
357
+ before: wrapHook(nodeTest.before),
358
+ after: wrapHook(nodeTest.after),
359
+ beforeEach: wrapHook(nodeTest.beforeEach),
360
+ afterEach: wrapHook(nodeTest.afterEach),
361
+ }));
362
+ const controlledNodeAssert = wrapNodeAssertion(nodeAssert);
363
+ bunTest.mock.module("node:assert/strict", () => ({
364
+ ...nodeAssert,
365
+ ...Object.fromEntries(
366
+ Object.entries(nodeAssert).map(([name, value]) => [
367
+ name,
368
+ typeof value === "function" && name !== "AssertionError"
369
+ ? wrapNodeAssertion(value, name)
370
+ : value,
371
+ ]),
372
+ ),
373
+ default: controlledNodeAssert,
374
+ }));
375
+ record({ kind: "ready" });
@@ -2,7 +2,7 @@
2
2
  name: assertledger
3
3
  description: Analyze a repository, submit candidate tests, and accept only deterministic AssertLedger evidence.
4
4
  metadata:
5
- version: "1.0.0"
5
+ version: "1.1.0"
6
6
  ---
7
7
 
8
8
  # AssertLedger skill
@@ -16,10 +16,14 @@ window.
16
16
  ## Preferred path: a committed regression
17
17
 
18
18
  1. Run `assertledger doctor <repo> --json` for static readiness. Runtime probes require the separate
19
- `doctor --runtime --allow-unsafe-execution` command and prior operator authorization. A
20
- `UNSUPPORTED_REPOSITORY_SYMLINK` conflict stays a refusal: only the operator may declare the
21
- local-only entry that holds the link with `--exclude NAME`. Never add names to
22
- `repository.exclude` yourself; the configured list never removes files from campaign evidence.
19
+ `doctor --runtime --allow-unsafe-execution` command and prior operator authorization. Doctor
20
+ judges each repository link against the module closure of the tests that would run: a link
21
+ outside every closure is reported as `REPOSITORY_LINK_OUTSIDE_TEST_CLOSURE`, while a link in a
22
+ closure (`REPOSITORY_LINK_IN_TEST_CLOSURE`) or leaving the repository
23
+ (`REPOSITORY_LINK_ESCAPES_ROOT`) stays a `CONFLICT` that names the path. A refusal is never
24
+ yours to work around: only the operator may replace the link or declare the local-only entry that
25
+ holds it with `--exclude NAME`. Never add names to `repository.exclude` yourself; the configured
26
+ list never removes files from campaign evidence.
23
27
  2. Ask the operator or harness for the buggy, corrected and neutral revisions, the neutral reason,
24
28
  candidate path and unchanged base tests. Do not invent a neutral control's meaning.
25
29
  3. For committed dependency-free JavaScript `node:test`, use `assertledger check` or the MCP
@@ -29,6 +33,22 @@ window.
29
33
  5. The configuration, skill and server do not grant trust. Never treat a crash, timeout, compilation
30
34
  error or missing test as detection. The returned limitations remain part of the result.
31
35
 
36
+ ## Replaying a recorded red/green witness
37
+
38
+ 1. When the operator supplies a witness (mutated target files with their base digests, one existing
39
+ designated test named by file and describe path, and its expected first failure line), write it
40
+ as a `witness-import-request.v1.json` document; do not change the targets, test or line.
41
+ 2. The operator chooses the backend; a witness never does. Run
42
+ `assertledger import-witness <witness.json> --out <new-directory> --json` with exactly one of
43
+ `--container-image NAME@sha256:DIGEST [--container-runtime JSON_ARGV]`,
44
+ `--allow-unsafe-execution` or `--allow-windows-native-execution`, as authorized. The resulting
45
+ isolation level, `CONTAINER`, `UNSANDBOXED` or `WINDOWS_NATIVE_UNSANDBOXED`, is part of the
46
+ result and must be reported with it.
47
+ 3. Only an assertion failure attributed to the designated test counts as red. A timeout, crash,
48
+ collection or compile error, or a test that is not found is never red, and diverging repeated runs
49
+ make the candidate `UNSTABLE`. Accept the witness only on `VERIFIED` plus `assertledger replay` with
50
+ `valid: true`.
51
+
32
52
  ## Lower-level verification requests
33
53
 
34
54
  1. Run `assertledger init <repo> --dry-run --json`, review conflicts and planned bytes, then run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assertledger",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Verify that regression tests detect a declared bug, with deterministic verdicts and replayable evidence.",
5
5
  "type": "module",
6
6
  "private": false,