@feigi/fleet-ctl 3.23.26 → 3.23.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.23.26",
3
+ "version": "3.23.28",
4
4
  "description": "Agent fleet: run-team controller, merge bot, PR reviewer, and the ticket pipeline they share",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -57,10 +57,11 @@
57
57
  // repository, origin/main missing, the worktree could not be made), or
58
58
  // a git or filesystem fault stopped it before the cache was settled
59
59
  // (a git command could not be started, the reader's own git
60
- // included; the reader's `sh`, `node`, `mktemp` or `cat` could not be
61
- // started, or its `node` died mid-read — a `cat` that ran and failed
62
- // stays the reader's refusal, exit 1; the cache or its temp file
63
- // could not be written). The log directory under $TMPDIR is removed.
60
+ // included, or that git started and could not run; the reader's `sh`,
61
+ // `node`, `mktemp` or `cat` could not be started, or its `node` died
62
+ // mid-read — a `cat` that ran and failed stays the reader's refusal,
63
+ // exit 1; the cache or its temp file could not be written). The log
64
+ // directory under $TMPDIR is removed.
64
65
 
65
66
  import { spawnSync } from "node:child_process";
66
67
  import { closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
@@ -287,9 +288,12 @@ function prove(o, wt, logs) {
287
288
  // cache is undone just the same when the read-back is no verdict: an `sh` that
288
289
  // could not be started read nothing; a reader that exits READER_COULD_NOT_RUN
289
290
  // could not start its `node`, `mktemp` or `cat`, or lost its `node` mid-read;
290
- // and the reader runs git itself and reads a git that will not start as no
291
+ // and the reader runs git itself and reads a git that will not run as no
291
292
  // repository at all, so its refusal is a verdict on what was proven only while
292
- // git still starts.
293
+ // git still runs. `git --version` is the probe: a working git always passes
294
+ // it, so a git that cannot be started fails it, and so does one that starts
295
+ // and cannot run — a wrapper whose target is gone exits 126 or 127, a killed
296
+ // git has no exit status at all.
293
297
  function writeCache(cache, recipe, repo) {
294
298
  mkdirSync(dirname(cache), { recursive: true });
295
299
  const prior = existsSync(cache) ? readFileSync(cache) : null;
@@ -308,12 +312,15 @@ function writeCache(cache, recipe, repo) {
308
312
  else writeFileSync(cache, prior);
309
313
  if (r.error && !r.pid) throw cannot(`could not start sh to read the Recipe cache back: ${r.error.message}`);
310
314
  if (r.status === READER_COULD_NOT_RUN) throw cannot(`${r.stderr.trim()} — the Recipe cache reader could not run a tool it needs, so its refusal is no verdict on what was proven`);
315
+ const noVerdict = (why) => cannot(`${why} — the Recipe cache reader runs git, so its refusal is no verdict on what was proven`);
316
+ let probe;
311
317
  try {
312
- git(["--version"]);
318
+ probe = git(["--version"]);
313
319
  } catch (e) {
314
320
  if (!(e instanceof Refusal)) throw e;
315
- throw cannot(`${e.message} — the Recipe cache reader runs git, so its refusal is no verdict on what was proven`);
321
+ throw noVerdict(e.message);
316
322
  }
323
+ if (!probe.ok) throw noVerdict(`git started but does not run: \`git --version\` did not exit 0${probe.err ? `: ${probe.err}` : " and wrote nothing to stderr"}`);
317
324
  throw notProven(`the Recipe cache reader refuses what was proven: ${(r.stderr || r.stdout).trim()}`);
318
325
  }
319
326
  }
@@ -665,6 +665,35 @@ test("a git that stops starting before the cache is read back is no verdict, nev
665
665
  assert.equal(readFileSync(cachePath(dir), "utf8"), "prior bytes", "a cache the reader never settled is rolled back");
666
666
  });
667
667
 
668
+ // A git that starts and cannot run fails the reader's own `git rev-parse` just
669
+ // as a git that will not start does, so its refusal is no verdict either. Each
670
+ // stub stands in for one such git: a wrapper whose target is gone exits 126 or
671
+ // 127, and a killed git has no exit status at all. The stubs differ in what
672
+ // they write to stderr, which the message quotes when there is anything to
673
+ // quote. The control — the same PATH with git left in place proves — is the
674
+ // unstartable-git test's. The Test entrypoint rewrites the stub's body, never
675
+ // the stub: that is a hard link to exec-stub.mjs's shared, read-only
676
+ // trampoline.
677
+ const GIT_THAT_CANNOT_RUN = [
678
+ { name: "exits 126 and writes nothing", body: "exit 126", cause: " and wrote nothing to stderr" },
679
+ { name: "exits 127 and writes to stderr", body: "echo boom >&2; exit 127", cause: ": boom" },
680
+ { name: "is killed by a signal", body: "kill -9 $$", cause: ": git was killed by SIGKILL" },
681
+ ];
682
+ for (const c of GIT_THAT_CANNOT_RUN) {
683
+ test(`a git that starts but ${c.name} before the cache is read back is no verdict, never NOT PROVEN — and the prior cache is restored`, () => {
684
+ const { dir } = repo(MAVEN_FILES);
685
+ mkdirSync(join(dir, ".fleet"));
686
+ writeFileSync(cachePath(dir), "prior bytes");
687
+ const bin = readerPath();
688
+ const r = prove(dir, ["--install", "true", "--test", `printf '#!/bin/sh\\n${c.body}\\n' > '${join(bin, ".stub-git")}'; echo 'tests 1'`, "--count-line", "tests 1", "--test-count", "1"], { env: { PATH: bin } });
689
+ assert.equal(r.status, 2, r.err);
690
+ assert.ok(r.err.includes(`recipe-prove: git started but does not run: \`git --version\` did not exit 0${c.cause} — the Recipe cache reader runs git, so its refusal is no verdict on what was proven`), r.err);
691
+ assert.doesNotMatch(r.err, /NOT PROVEN/);
692
+ assert.doesNotMatch(r.err, /not a git repository/);
693
+ assert.equal(readFileSync(cachePath(dir), "utf8"), "prior bytes", "a cache the reader never settled is rolled back");
694
+ });
695
+ }
696
+
668
697
  // The reader's own `sh`, taken away the same way: a read-back that never ran
669
698
  // read nothing, so it is no verdict either.
670
699
  test("an sh that stops starting before the cache is read back is no verdict, never NOT PROVEN — and no cache is left", () => {