@feigi/fleet-ctl 3.17.6 → 3.17.7

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.17.6",
3
+ "version": "3.17.7",
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": {
@@ -755,7 +755,7 @@ export async function runReview(host, args) {
755
755
  mkdir -p "${runRootParent}" || { echo SNAPSHOT_RUNROOT_FAILED; exit 1; }
756
756
  RUN=$(mktemp -d "${runRootPrefix}XXXXXXXX") || { echo SNAPSHOT_RUNROOT_FAILED; exit 1; }
757
757
  echo SNAPSHOT_RUN_ROOT="$RUN"
758
- find "${runRootParent}" -maxdepth 1 -type d -name 'run-*' -mtime +7 -exec sh -c 'rc=0; for d; do chmod -R u+rwx "$d" 2>/dev/null; if command -v chflags >/dev/null 2>&1; then chflags -R nouchg "$d" 2>/dev/null; chmod -R u+rwx "$d" 2>/dev/null; fi; rm -rf "$d" || rc=1; done; exit $rc' sh {} + || echo SNAPSHOT_PRUNE_FAILED
758
+ find "${runRootParent}" -maxdepth 1 -type d -name 'run-*' -mtime +7 -exec sh -c 'rc=0; ph="{""}"; for d; do chmod -R u+rwx "$d" 2>/dev/null; m=$?; if command -v chflags >/dev/null 2>&1; then [ $m -eq 0 ] || { chflags nouchg,nouappnd "$d"; chmod u+rwx "$d"; find "$d" -type d -exec chflags nouchg,nouappnd "$ph" ";" -exec chmod u+rwx "$ph" ";"; }; chflags -R nouchg,nouappnd "$d" 2>/dev/null; fi; rm -rf "$d" || rc=1; done; exit $rc' sh {} + || echo SNAPSHOT_PRUNE_FAILED
759
759
  SHA=$(git -C ${worktree} rev-parse --short HEAD) || { echo SNAPSHOT_REVPARSE_FAILED; exit 1; }
760
760
  SNAP="$RUN/snapshot-$SHA"
761
761
  echo SNAPSHOT_DEST="$SNAP"
@@ -164,31 +164,48 @@ const renderPrune = (path, runRootParent) =>
164
164
 
165
165
  /**
166
166
  * A `run-` root under `parent` holding a `locked` directory with no permission
167
- * bits at all, aged `days`. On darwin, optionally, the file inside it carries
168
- * `uchg` (`immutableFile`), or `locked` itself does (`immutableDir`) — set
169
- * after the mode, so the directory's mode cannot be changed back until the
170
- * flag is gone. Aged last: creating children sets the root's mtime to now.
167
+ * bits at all, aged `days`. `nested` puts a second such directory, `deeper`,
168
+ * inside `locked`, and the file moves into it. On darwin, optionally, the file
169
+ * carries `fileFlag` (`uchg` or `uappnd`), and every locked directory carries
170
+ * `dirFlag` — set after the mode, so a directory's mode cannot be changed back
171
+ * until the flag is gone. `rootFlag` locks the run root itself the same way,
172
+ * with no permission bits and that flag. `fileMode` is the file's own mode:
173
+ * one that already grants owner rwx leaves `chmod -R u+rwx` nothing to change
174
+ * on the file, so that pass exits 0 over a flagged file. Aged before the root
175
+ * is locked: creating children sets the root's mtime to now, and a flagged
176
+ * root refuses `utimes`.
171
177
  */
172
- function lockedRunRoot(parent, name, days, { immutableFile = false, immutableDir = false } = {}) {
178
+ function lockedRunRoot(parent, name, days, { fileFlag = null, fileMode = 0o644, dirFlag = null, rootFlag = null, nested = false } = {}) {
173
179
  const root = join(parent, name);
174
180
  const locked = join(root, "locked");
175
- mkdirSync(locked, { recursive: true });
176
- writeFileSync(join(locked, "inner.txt"), "");
177
- if (immutableFile) execFileSync("chflags", ["uchg", join(locked, "inner.txt")]);
178
- chmodSync(locked, 0);
179
- if (immutableDir) execFileSync("chflags", ["uchg", locked]);
181
+ const dirs = nested ? [join(locked, "deeper"), locked] : [locked];
182
+ mkdirSync(dirs[0], { recursive: true });
183
+ const file = join(dirs[0], "inner.txt");
184
+ writeFileSync(file, "");
185
+ chmodSync(file, fileMode);
186
+ if (fileFlag) execFileSync("chflags", [fileFlag, file]);
187
+ // Deepest first: once a directory is 000, nothing below it can be reached.
188
+ for (const dir of dirs) {
189
+ chmodSync(dir, 0);
190
+ if (dirFlag) execFileSync("chflags", [dirFlag, dir]);
191
+ }
180
192
  const when = (Date.now() - days * DAY) / 1000;
181
193
  utimesSync(root, when, when);
194
+ if (rootFlag) {
195
+ chmodSync(root, 0);
196
+ execFileSync("chflags", [rootFlag, root]);
197
+ }
182
198
  return root;
183
199
  }
184
200
 
185
201
  /**
186
202
  * Undoes `lockedRunRoot` so the scratch teardown can remove what a prune left.
187
- * The second `chmod` is for `immutableDir`: its mode is only changeable once
188
- * `chflags` has cleared the flag.
203
+ * A flagged directory's mode is only changeable once its flag is gone, and a
204
+ * directory below it only reachable once that mode is back, so each directory
205
+ * is unlocked before `find` descends into it.
189
206
  */
190
207
  function unlock(parent) {
191
- spawnSync("sh", ["-c", 'chmod -R u+rwx "$1"; if command -v chflags >/dev/null; then chflags -R nouchg "$1"; chmod -R u+rwx "$1"; fi; :', "sh", parent], { env: ENV });
208
+ spawnSync("sh", ["-c", 'chmod -R u+rwx "$1"; if command -v chflags >/dev/null; then find "$1" -type d -exec chflags nouchg,nouappnd {} ";" -exec chmod u+rwx {} ";"; chflags -R nouchg,nouappnd "$1"; fi; :', "sh", parent], { env: ENV });
192
209
  }
193
210
 
194
211
  /**
@@ -596,8 +613,8 @@ for (const [name, path] of SOURCES) {
596
613
  // #2322. A fixture that drops its own read bit, or a darwin file carrying the
597
614
  // user-immutable flag, made the plain `rm -rf` fail on that root — so it
598
615
  // stayed forever and every later review of the PR printed
599
- // SNAPSHOT_PRUNE_FAILED. The prune now restores owner rwx and clears `uchg`
600
- // on the roots it SELECTED, then removes them.
616
+ // SNAPSHOT_PRUNE_FAILED. The prune now restores owner rwx and clears darwin's
617
+ // user flags on the roots it SELECTED, then removes them.
601
618
  test(`${name}: the prune removes an aged run root a chmod 000 directory used to wedge`, (t) => {
602
619
  const parent = join(scratch(t, "snapshot-repo-prune-perm-"), "pr7");
603
620
  const stale = lockedRunRoot(parent, "run-staleaa", 9);
@@ -640,31 +657,75 @@ for (const [name, path] of SOURCES) {
640
657
  }
641
658
  });
642
659
 
643
- test(`${name}: on darwin the prune removes an aged run root holding a uchg file inside a chmod 000 directory`, { skip: process.platform !== "darwin" && "chflags/uchg is darwin's" }, (t) => {
644
- const parent = join(scratch(t, "snapshot-repo-prune-uchg-"), "pr7");
645
- const stale = lockedRunRoot(parent, "run-staleaa", 9, { immutableFile: true });
646
- try {
647
- const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env: ENV, encoding: "utf8" });
648
-
649
- assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `the prune named a failure on a root it can normalise: ${r.stderr}`);
650
- assert.ok(!existsSync(stale), "an aged run root holding a uchg file survived the prune — the immutable flag wedges `rm -rf` (EPERM) and the root stays forever");
651
- } finally {
652
- unlock(parent);
653
- }
654
- });
660
+ // #2322, #2370. On darwin a user flag wedges the removal too: `uchg` refuses
661
+ // every change and `uappnd` refuses the unlink, and on a directory either
662
+ // one also refuses the `chmod` that makes a `000` directory readable again —
663
+ // while `chflags -R` cannot read a `000` directory it has just unflagged. So
664
+ // a directory's flag and mode have to come off together, before anything
665
+ // inside it is read, at every depth and on the run root itself. The `uappnd`
666
+ // file whose mode already grants owner rwx is the shape where `chmod -R`
667
+ // exits 0 over a flag.
668
+ for (const [shape, opts] of [
669
+ ["a uchg file inside a chmod 000 directory", { fileFlag: "uchg" }],
670
+ ["a uappnd file whose own mode leaves chmod nothing to change", { fileFlag: "uappnd", fileMode: 0o700 }],
671
+ ["a directory that is both uchg and chmod 000", { dirFlag: "uchg" }],
672
+ ["a directory that is both uappnd and chmod 000", { dirFlag: "uappnd" }],
673
+ ["a uchg file inside a directory that is both uchg and chmod 000", { fileFlag: "uchg", dirFlag: "uchg" }],
674
+ ["a uchg, chmod 000 directory inside another", { dirFlag: "uchg", nested: true }],
675
+ ["its own uchg and chmod 000 bits", { rootFlag: "uchg" }],
676
+ ["its own uappnd and chmod 000 bits", { rootFlag: "uappnd" }],
677
+ ["its own uchg and chmod 000 bits over a uchg, chmod 000 directory", { rootFlag: "uchg", dirFlag: "uchg" }],
678
+ ]) {
679
+ test(`${name}: on darwin the prune removes an aged run root with ${shape}`, { skip: process.platform !== "darwin" && "chflags/uchg/uappnd are darwin's" }, (t) => {
680
+ const parent = join(scratch(t, "snapshot-repo-prune-flags-"), "pr7");
681
+ const stale = lockedRunRoot(parent, "run-staleaa", 9, opts);
682
+ try {
683
+ const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env: ENV, encoding: "utf8" });
684
+
685
+ assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `the prune named a failure on a root it can normalise: ${r.stderr}`);
686
+ assert.equal(r.status, 0, `the prune exited non-zero: ${r.stderr}`);
687
+ assert.ok(!existsSync(stale), `an aged run root with ${shape} survived the prune — it now stays forever and every later review prints SNAPSHOT_PRUNE_FAILED`);
688
+ } finally {
689
+ unlock(parent);
690
+ }
691
+ });
692
+ }
655
693
 
656
- // The flag on the DIRECTORY rather than on a file in it: `chmod -R` cannot
657
- // restore a `uchg` directory's mode, and `chflags -R` clears the flag but
658
- // cannot read the `000` directory it just unflagged — so the mode is only
659
- // restorable after the flag step, which is why the prune runs `chmod` again.
660
- test(`${name}: on darwin the prune removes an aged run root holding a directory that is both uchg and chmod 000`, { skip: process.platform !== "darwin" && "chflags/uchg is darwin's" }, (t) => {
661
- const parent = join(scratch(t, "snapshot-repo-prune-uchgdir-"), "pr7");
662
- const stale = lockedRunRoot(parent, "run-staleaa", 9, { immutableDir: true });
694
+ // GNU find (Linux) refuses an `-exec ... {} +` whose argument list holds the
695
+ // `{}` placeholder more than once — and counts a `{}` written inside an
696
+ // `sh -c` script argument as one. BSD find (darwin) does not, so a dev
697
+ // machine passes a prune that fails every Linux CI run. This shim enforces
698
+ // GNU's rule over the real `find`, so the case holds on either host.
699
+ test(`${name}: the prune's find invocation keeps to GNU find's one-placeholder rule for -exec ... +`, (t) => {
700
+ const parent = join(scratch(t, "snapshot-repo-prune-gnu-"), "pr7");
701
+ const stale = lockedRunRoot(parent, "run-staleaa", 9);
702
+ const bin = scratch(t, "snapshot-repo-prune-gnu-bin-");
703
+ const realFind = execFileSync("sh", ["-c", "command -v find"], { env: ENV, encoding: "utf8" }).trim();
704
+ writeFileSync(
705
+ join(bin, "find"),
706
+ `#!/bin/sh
707
+ # GNU find: with a terminating '+', only one argument may contain {}.
708
+ n=0; plus=0; prev=
709
+ for a in "$@"; do
710
+ case "$a" in *'{}'*) n=$((n + 1));; esac
711
+ [ "$a" = "+" ] && [ "$prev" = '{}' ] && plus=1
712
+ prev=$a
713
+ done
714
+ if [ "$plus" = 1 ] && [ "$n" -gt 1 ]; then
715
+ echo "find: Only one instance of {} is supported with -exec ... +" >&2
716
+ exit 1
717
+ fi
718
+ exec "${realFind}" "$@"
719
+ `,
720
+ { mode: 0o755 },
721
+ );
722
+ const env = { ...ENV, PATH: `${bin}:${ENV.PATH}` };
663
723
  try {
664
- const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env: ENV, encoding: "utf8" });
724
+ const r = spawnSync("sh", ["-c", renderPrune(path, parent)], { env, encoding: "utf8" });
665
725
 
666
- assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `the prune named a failure on a root it can normalise: ${r.stderr}`);
667
- assert.ok(!existsSync(stale), "an aged run root holding a uchg, chmod 000 directory survived the prune — its mode was never restored after the flag was cleared, so `rm -rf` cannot read it");
726
+ assert.doesNotMatch(r.stdout ?? "", /SNAPSHOT_PRUNE_FAILED/, `a GNU-rule find rejected the prune: ${r.stderr}`);
727
+ assert.doesNotMatch(r.stderr ?? "", /Only one instance of \{\}/, "the prune's find tripped GNU's one-placeholder rule");
728
+ assert.ok(!existsSync(stale), "the aged run root survived a find that enforces GNU's placeholder rule");
668
729
  } finally {
669
730
  unlock(parent);
670
731
  }