@norskvideo/ctl-test-harness 0.1.36 → 0.1.37

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/demo/dev-loop.js CHANGED
@@ -199,7 +199,18 @@ export async function devLoopDown(product, deps = defaultDemoDeps(process.cwd())
199
199
  }
200
200
  if (state.devPid !== undefined)
201
201
  deps.killPid?.(state.devPid);
202
- deps.nukeStoreAsRoot(state.storeDir);
202
+ // Never let the nuke strand the state record: it sits one line before
203
+ // state.remove, and the containment guard throws for a store outside THIS
204
+ // process's temp base -- which is a cross-process fact (the `up` shell's
205
+ // NORSK_CTL_TEST_TMP need not be the `down` shell's), not a bug in the run
206
+ // being torn down. Refusing to delete it is right; failing to forget it
207
+ // means `down` breaks identically on every retry.
208
+ try {
209
+ deps.nukeStoreAsRoot(state.storeDir);
210
+ }
211
+ catch (e) {
212
+ deps.log(`nuke skipped: ${e instanceof Error ? e.message : String(e)}`);
213
+ }
203
214
  deps.state.remove(product, "dev-loop");
204
215
  deps.log(`${product} dev-loop torn down (the workdir ${state.workdir ?? ""} is untouched)`);
205
216
  }
package/demo/run.js CHANGED
@@ -733,8 +733,19 @@ export async function demoDown(product, deps = defaultDemoDeps(process.cwd())) {
733
733
  deps.docker(["rm", "-f", name]);
734
734
  if (state.devPid !== undefined)
735
735
  deps.killPid?.(state.devPid);
736
- if (state.daemon !== "reuse")
737
- deps.nukeStoreAsRoot(state.storeDir);
736
+ // Never let the nuke strand the state record: it sits one line before
737
+ // state.remove, and the containment guard throws for a store outside THIS
738
+ // process's temp base -- which is a cross-process fact (the `up` shell's
739
+ // NORSK_CTL_TEST_TMP need not be the `down` shell's), not a bug in the run
740
+ // being torn down. Refusing to delete it is right; failing to forget it
741
+ // means `down` breaks identically on every retry.
742
+ try {
743
+ if (state.daemon !== "reuse")
744
+ deps.nukeStoreAsRoot(state.storeDir);
745
+ }
746
+ catch (e) {
747
+ deps.log(`nuke skipped: ${e instanceof Error ? e.message : String(e)}`);
748
+ }
738
749
  deps.state.remove(product);
739
750
  deps.log(`${product} demo torn down${state.daemon === "reuse" ? " (your daemon and its registration are untouched)" : ""}`);
740
751
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-test-harness",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/root-nuke.d.ts CHANGED
@@ -27,9 +27,14 @@ export declare function assertNukeTarget(target: string, allowedBase: string): v
27
27
  * directory called `x; rm -rf /base` would otherwise wipe every sibling --
28
28
  * including a live sibling run -- from inside a root container. */
29
29
  export declare function nukeArgv(target: string): string[];
30
- /** Remove `target` as root. Returns whether the nuke actually ran: `spawnSync`
31
- * reports a missing docker in its RESULT rather than throwing, and a caller
32
- * that retries a doomed spawn forever (silently) is the failure mode to avoid.
30
+ /** Remove `target` as root. Returns whether the nuke actually SUCCEEDED.
31
+ *
32
+ * Both halves matter. `spawnSync` reports a missing docker in its RESULT
33
+ * rather than throwing, and a docker that is present but failing -- daemon
34
+ * stopped, image unpullable, mount refused -- reports only a non-zero status
35
+ * with no `error` at all. Reading `error` alone calls that a success, so a
36
+ * caller latching on the return value never latches and re-spawns a doomed
37
+ * 60-second docker on every attempt, forever and silently.
33
38
  * Throws only for an unsafe target -- see {@link assertNukeTarget}. */
34
39
  export declare function nukePathAsRoot(target: string, allowedBase: string, spawn?: NukeSpawn): boolean;
35
40
  export interface OwnerScanDeps {
package/root-nuke.js CHANGED
@@ -72,14 +72,21 @@ export function nukeArgv(target) {
72
72
  basename(target),
73
73
  ];
74
74
  }
75
- /** Remove `target` as root. Returns whether the nuke actually ran: `spawnSync`
76
- * reports a missing docker in its RESULT rather than throwing, and a caller
77
- * that retries a doomed spawn forever (silently) is the failure mode to avoid.
75
+ /** Remove `target` as root. Returns whether the nuke actually SUCCEEDED.
76
+ *
77
+ * Both halves matter. `spawnSync` reports a missing docker in its RESULT
78
+ * rather than throwing, and a docker that is present but failing -- daemon
79
+ * stopped, image unpullable, mount refused -- reports only a non-zero status
80
+ * with no `error` at all. Reading `error` alone calls that a success, so a
81
+ * caller latching on the return value never latches and re-spawns a doomed
82
+ * 60-second docker on every attempt, forever and silently.
78
83
  * Throws only for an unsafe target -- see {@link assertNukeTarget}. */
79
84
  export function nukePathAsRoot(target, allowedBase, spawn = spawnSync) {
80
85
  assertNukeTarget(target, allowedBase);
81
86
  const r = spawn("docker", nukeArgv(target), { timeout: NUKE_TIMEOUT_MS });
82
- return !r?.error;
87
+ if (r?.error)
88
+ return false;
89
+ return r?.status === 0 || r?.status === undefined;
83
90
  }
84
91
  /** Paths under `dir` owned by a uid that is not `selfUid`, with the owning uid.
85
92
  *