From 5a68df7275b08af1b2a52e15b60aa441b98f7f32 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Wed, 14 Oct 2026 14:41:09 -0500 Subject: [PATCH] agents: darkwing row 118 round 1 packet (#1612) SAFE in packages/seat/src/checkout.mjs adds maintenance.auto=false and gc.auto=0; probe, tests, mutants m1-m3 (all killed), targeted gate (seat 132/0, cli 184/0) and the leftover sweep. Candidate is build.patch against 2727dd3ec; manifest digests check out. Co-Authored-By: Claude Opus 5.5 --- agents/darkwing/work/queue-118/r1/build.md | 75 +++++++ agents/darkwing/work/queue-118/r1/build.patch | 120 +++++++++++ .../queue-118/r1/candidate-manifest.sha256 | 3 + .../work/queue-118/r1/checkout-test.log | 90 ++++++++ .../darkwing/work/queue-118/r1/gate-run.log | 4 + agents/darkwing/work/queue-118/r1/gate.sh | 18 ++ .../work/queue-118/r1/gate/pkg-cli.log | 194 ++++++++++++++++++ .../work/queue-118/r1/gate/pkg-seat.log | 145 +++++++++++++ .../work/queue-118/r1/mutants-run.log | 8 + .../work/queue-118/r1/mutants-run1.log | 4 + agents/darkwing/work/queue-118/r1/mutants.py | 71 +++++++ .../mutant-m1-drop-maintenance-auto.log | 106 ++++++++++ .../r1/mutants/mutant-m2-drop-gc-auto.log | 106 ++++++++++ .../r1/mutants/mutant-m3-drop-both.log | 130 ++++++++++++ .../work/queue-118/r1/probe/loose.mjs | 15 ++ .../work/queue-118/r1/probe/probe-run.log | 92 +++++++++ .../darkwing/work/queue-118/r1/probe/probe.sh | 28 +++ agents/darkwing/work/queue-118/r1/repro.sh | 12 ++ agents/darkwing/work/queue-118/r1/sweep.log | 48 +++++ 19 files changed, 1269 insertions(+) create mode 100644 agents/darkwing/work/queue-118/r1/build.md create mode 100644 agents/darkwing/work/queue-118/r1/build.patch create mode 100644 agents/darkwing/work/queue-118/r1/candidate-manifest.sha256 create mode 100644 agents/darkwing/work/queue-118/r1/checkout-test.log create mode 100644 agents/darkwing/work/queue-118/r1/gate-run.log create mode 100755 agents/darkwing/work/queue-118/r1/gate.sh create mode 100644 agents/darkwing/work/queue-118/r1/gate/pkg-cli.log create mode 100644 agents/darkwing/work/queue-118/r1/gate/pkg-seat.log create mode 100644 agents/darkwing/work/queue-118/r1/mutants-run.log create mode 100644 agents/darkwing/work/queue-118/r1/mutants-run1.log create mode 100755 agents/darkwing/work/queue-118/r1/mutants.py create mode 100644 agents/darkwing/work/queue-118/r1/mutants/mutant-m1-drop-maintenance-auto.log create mode 100644 agents/darkwing/work/queue-118/r1/mutants/mutant-m2-drop-gc-auto.log create mode 100644 agents/darkwing/work/queue-118/r1/mutants/mutant-m3-drop-both.log create mode 100644 agents/darkwing/work/queue-118/r1/probe/loose.mjs create mode 100644 agents/darkwing/work/queue-118/r1/probe/probe-run.log create mode 100755 agents/darkwing/work/queue-118/r1/probe/probe.sh create mode 100755 agents/darkwing/work/queue-118/r1/repro.sh create mode 100644 agents/darkwing/work/queue-118/r1/sweep.log diff --git a/agents/darkwing/work/queue-118/r1/build.md b/agents/darkwing/work/queue-118/r1/build.md new file mode 100644 index 000000000..7064511e1 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/build.md @@ -0,0 +1,75 @@ +# Row 118 (#1612) round 1: no auto-maintenance from the host's git in a role clone + +Brief: docs/plans/2026-10-14_clone-fetch-no-maintenance.md (blob a4f38943f). Sage's sage-0214 briefed the row, and sage-0215 granted the slot and accepted the plan below. + +BASE: 2727dd3ec. The candidate is build.patch against BASE, and candidate-manifest.sha256 lists its 3 files. `repro.sh` exports BASE with `git archive`, applies build.patch and checks every manifest digest: DIGESTS_OK. + +| File | Change | +|---|---| +| packages/seat/src/checkout.mjs | `SAFE` adds `-c maintenance.auto=false` and `-c gc.auto=0`, with a comment saying why. Nothing else in the file changes | +| packages/seat/tests/checkout.test.mjs | two tests and two helpers (`loggedAll`, `loose`), and a `node:zlib` import. No existing test changes | +| packages/seat/README.md | the forced-config paragraph names both keys and the reason | + +## What git 2.55.0 does + +I probed this by hand before writing the tests (probe/probe.sh, probe/loose.mjs, output in probe/probe-run.log; not a suite). The probe makes a root and a clone with the host's `SAFE`, adds loose objects to the clone, commits to the root, then runs the host's fetch and `merge --ff-only` in the clone. Every git goes through a logger that also sees the gits git starts itself (below). The modes: + +| Mode | Extra keys | Loose objects in objects/17 | What ran after fetch and merge | objects/17 after | +|---|---|---|---|---| +| base 40 | none | 40 | `maintenance run --auto --quiet --detach` after the fetch and again after the merge; the detached run repacked and wrote a multi-pack-index | 0 loose, 2 packs | +| base 0 | none | 0 | `maintenance run --auto` after each, which found nothing to do | 0, 0 packs | +| maonly 40 | `maintenance.auto=false` | 40 | no maintenance | 40 loose | +| gconly 40 | `gc.auto=0` | 40 | no maintenance | 40 loose | +| keys 40 | both | 40 | no maintenance | 40 loose | + +So the merge starts maintenance as well as the fetch, and the detached run outlives the call. runGit's timeout signals the call's process group, and `--detach` leaves that group, so the host's time limit never reaches it. The clone itself starts no maintenance. + +Either key alone stops the start. The reason gc.auto=0 does is in git's run-command.c, `prepare_auto_maintenance` (tag v2.55.0): when `maintenance.auto` is unset, it reads `gc.auto` and enables auto-maintenance only if that is above 0. A clone can't set `maintenance.auto=true` in its own config, because allowed() keeps a clone with any key a clone doesn't write. Both keys still go on the command line, so the outcome doesn't rest on that fallback, and gc.auto=0 also stops a `gc --auto` that runs anyway. No third key is needed on git 2.55.0. + +## Tests + +checkout.test.mjs, 77 of 77 passing on its own (checkout-test.log). + +1. "every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0". It uses the existing `logged()` wrapper, which logs every git the host starts. It runs a clone, then an update that fetches and fast-forwards, checks that a clone, a fetch and a merge each ran, and checks that every logged call has `-c maintenance.auto=false` and `-c gc.auto=0`. That covers the root reads as well as the calls in the clone. +2. "a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge". The clone's config can't turn maintenance on, so the trigger is the default config plus 40 valid loose blobs in objects/17 (`loose()`), over gc's default limit of 6700. `loggedAll()` puts a logger on PATH whose own `GIT_EXEC_PATH` is a copy of git's exec path with the logger as its `git`. git puts its exec path first on PATH for its children, so the log also shows the gits git starts. The test asserts that the log has such a child call, so it could show maintenance, that no call is `maintenance`, `gc` or `repack`, and that all 40 objects are still loose. + +The rigged-clone test ("a clone the session rigged is kept...") and the row 117 tests are unchanged and pass. + +The brief allows pinning the arguments where behaviour can't be observed. Here behaviour can be observed, but only when both keys are missing, because either one alone stops the start. So test 2 kills only the mutant that drops both keys, and test 1 kills each single-key mutant. Sage accepted this in sage-0215. + +## Mutants + +mutants.py drops keys from `SAFE`, runs checkout.test.mjs, restores the file from `.orig` with a sha256 check, and then kills any process whose cwd is under this row's TMPDIR. mutants-run.log has the run and the failing tests. All 3 are killed, and the run ended `restored: True`. + +| Mutant | Change | Result | +|---|---|---| +| m1-drop-maintenance-auto | drop `-c maintenance.auto=false` | killed, 1 of 77 fail: test 1 | +| m2-drop-gc-auto | drop `-c gc.auto=0` | killed, 1 of 77 fail: test 1 | +| m3-drop-both | drop both | killed, 2 of 77 fail: tests 1 and 2 | + +Under m3, test 2's log shows `maintenance run --auto --quiet --detach` twice (fetch and merge) and the detached `repack` (mutants/mutant-m3-drop-both.log). + +I ran the mutants twice. The first run's kills were the same, but its failing-test extraction looked for TAP lines and the reporter prints spec lines, so it listed no test names (mutants-run1.log). I fixed the extraction and reran all three; mutants-run.log is the second run. + +### Leftovers (sweep.log) + +No process was left: neither run's sweep killed anything, a /proc cwd scan under the TMPDIR found none, and no `maintenance run` process of mine was running afterwards. Two `mosaic-checkout-*` directories were left under the TMPDIR, one from each m3 run. Each held only `ws/stack/.git/objects/pack/tmp_rev_*`, so the detached repack wrote into the clone after the test had removed it. That is the defect this row fixes, and it appears only under the mutant. Three `mosaic-launch-*` directories, timed inside the gate's cli step, came from packages/cli's own tests and are unrelated to this change. sweep.log lists all five with their contents and mtimes, then records that I removed them and the TMPDIR was empty. A follow-up could have the cli tests clean up their launch directories; it is outside this row. + +## Gate + +A targeted builder gate under #271 and decision 136 (gate.sh, logs in gate/). Sage's landing gate stays full. + +| Step | Result | +|---|---| +| packages/seat `node --test tests/` | 132 pass, 0 fail | +| packages/cli `node --test tests/*.test.mjs` | 184 pass, 0 fail | + +GATE_PASSED (gate-run.log). cli/src/launcher.mjs is the one caller of ensureCheckouts. No scripts/test-*.sh names a changed path. webui/src/reads.mjs imports `readNotifyConfig` from cli.mjs, and cli.mjs imports launcher.mjs, so webui loads checkout.mjs but never calls ensureCheckouts. harness/src/bundle.mjs and git-tools.mjs name checkout.mjs only in comments. working.mjs imports `runGit`, `gitEnv` and `GIT_LIMITS` from checkout.mjs, not `SAFE`, and the seat step runs its tests. Sage's full gate covers webui and harness. No step failed, so the flake rule had nothing to count. + +## working.mjs + +No change, and none needed in my reading. Its SAFE at line 76 is its own. All three of its fetches (lines 147, 192 and 200) already pass `--no-auto-maintenance`, and nothing else it runs (`init --bare`, `bundle`, `merge-base`, `update-ref`, `rev-parse`) starts auto-maintenance. My slot request said two fetches; Sage corrected it to three in sage-0215. + +## Going live + +Nothing goes live in this row. The launcher runs from the pinned copy, so the change reaches launches only through a re-pin on Mos's go (decision 109), after Dewey's approval and Sage's landing. diff --git a/agents/darkwing/work/queue-118/r1/build.patch b/agents/darkwing/work/queue-118/r1/build.patch new file mode 100644 index 000000000..b5a4ed932 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/build.patch @@ -0,0 +1,120 @@ +diff --git a/packages/seat/README.md b/packages/seat/README.md +index ee7afbeb6..7796d8887 100644 +--- a/packages/seat/README.md ++++ b/packages/seat/README.md +@@ -304,7 +304,13 @@ only the `file` transport. A submodule has its own config, which none of + those checks reads and which can name a filter that `git status` would run, + so the host never recurses into one: a clone with a submodule is kept, and + `status` runs with `--ignore-submodules=all` and every call with +-`submodule.recurse=false`. The root is read with `GIT_CEILING_DIRECTORIES` ++`submodule.recurse=false`. Every call also carries `maintenance.auto=false` ++and `gc.auto=0`. Without them a fetch or a fast-forward merge starts ++`git maintenance run --auto`, which detaches from the call's process group ++and can repack the clone after the call has returned, where the time limit ++below no longer reaches it. On git 2.55 either key alone stops that start; ++`maintenance.auto=false` is the documented switch, and `gc.auto=0` also ++stops a `gc --auto` that runs anyway. The root is read with `GIT_CEILING_DIRECTORIES` + at its parent, so a root that isn't a repository is never taken for the + one above it. Each git call gets SIGTERM after 100 s and SIGKILL 5 s + later, as below. +diff --git a/packages/seat/src/checkout.mjs b/packages/seat/src/checkout.mjs +index 03081f5b5..5b8fe60a1 100644 +--- a/packages/seat/src/checkout.mjs ++++ b/packages/seat/src/checkout.mjs +@@ -67,6 +67,11 @@ const SAFE = [ + "-c", "protocol.allow=never", + "-c", "protocol.file.allow=always", + "-c", "submodule.recurse=false", ++ // A fetch or a merge would start `git maintenance run --auto`, which ++ // detaches from the call's process group and repacks the clone after the ++ // call has returned. gc.auto=0 also stops a gc --auto that runs anyway. ++ "-c", "maintenance.auto=false", ++ "-c", "gc.auto=0", + ]; + + // The environment git runs with: the host's PATH and nothing else of it. +diff --git a/packages/seat/tests/checkout.test.mjs b/packages/seat/tests/checkout.test.mjs +index a3751df01..d93788482 100644 +--- a/packages/seat/tests/checkout.test.mjs ++++ b/packages/seat/tests/checkout.test.mjs +@@ -9,6 +9,7 @@ import { createHash } from "node:crypto"; + import { chmodSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; + import { tmpdir } from "node:os"; + import { join } from "node:path"; ++import { deflateSync } from "node:zlib"; + import { CONFIG_MAX_BYTES, CheckoutError, ensureCheckouts, gitEnv } from "../src/checkout.mjs"; + + function tmp(t) { +@@ -240,6 +241,72 @@ test("a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was", + assert.ok(!l.inClone().includes("merge")); + }); + ++// Row 118 (#1612): every host git call carries maintenance.auto=false and ++// gc.auto=0, so a fetch or a merge in a clone starts no maintenance. ++ ++// Whether a logged call has `-c ` among its arguments. ++const carries = (call, kv) => call.split(" ").some((a, i, all) => a === "-c" && all[i + 1] === kv); ++ ++test("every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0", async (t) => { ++ const w = world(t); ++ const l = logged(w); ++ const ensure = () => ensureCheckouts({ workspace: w.workspace, projects: { stack: { root: w.root } }, env: l.env }); ++ assert.equal((await ensure())[0].outcome, "cloned"); ++ const two = commit(w.root, "two\n"); ++ assert.deepEqual(await ensure(), [{ project: "stack", path: w.clone, outcome: "updated", branch: "main", commit: two }]); ++ const calls = readFileSync(join(w.dir, "calls"), "utf8").split("\n").filter(Boolean); ++ for (const sub of ["clone", "fetch", "merge"]) assert.ok(calls.some((c) => c.split(" ").includes(sub)), `a ${sub} ran`); ++ for (const c of calls) for (const kv of ["maintenance.auto=false", "gc.auto=0"]) assert.ok(carries(c, kv), `${kv} is on: ${c}`); ++}); ++ ++// A git on PATH that logs every git run, the ones git starts itself ++// included. git puts its exec path first on PATH for its children, so the ++// exec path here is a copy of the real one whose git is this logger. ++function loggedAll(w) { ++ const bin = join(w.dir, "exec"); ++ mkdirSync(bin); ++ const real = spawnSync("sh", ["-c", "command -v git"], { encoding: "utf8" }).stdout.trim(); ++ const libexec = spawnSync(real, ["--exec-path"], { encoding: "utf8" }).stdout.trim(); ++ for (const name of readdirSync(libexec)) if (name !== "git") symlinkSync(join(libexec, name), join(bin, name)); ++ const calls = join(w.dir, "calls"); ++ writeFileSync(join(bin, "git"), `#!/bin/sh\nprintf '%s\\n' "$*" >> '${calls}'\nGIT_EXEC_PATH='${bin}' exec '${real}' "$@"\n`, { mode: 0o755 }); ++ return { env: { PATH: `${bin}:${process.env.PATH}` }, calls: () => readFileSync(calls, "utf8").split("\n").filter(Boolean) }; ++} ++ ++// `n` loose blobs in the clone, each with an id starting 17. gc --auto ++// counts the loose objects in objects/17 only, and more than 27 there is ++// over its default limit of 6700. ++function loose(clone, n) { ++ const dir = join(clone, ".git", "objects", "17"); ++ mkdirSync(dir, { recursive: true }); ++ for (let i = 0, made = 0; made < n; i++) { ++ const body = Buffer.from(`loose ${i}\n`); ++ const raw = Buffer.concat([Buffer.from(`blob ${body.length}\0`), body]); ++ const id = createHash("sha1").update(raw).digest("hex"); ++ if (!id.startsWith("17")) continue; ++ writeFileSync(join(dir, id.slice(2)), deflateSync(raw)); ++ made++; ++ } ++ return readdirSync(dir).map((name) => join(dir, name)); ++} ++ ++// The clone's config can't turn maintenance on (allowed() keeps such a ++// clone), so the trigger is the default config plus loose objects enough ++// for gc --auto. Without both keys the fetch and the merge each start ++// `maintenance run --auto`, which repacks them. ++test("a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge", async (t) => { ++ const w = world(t); ++ await once(w); ++ const objects = loose(w.clone, 40); ++ const two = commit(w.root, "two\n"); ++ const l = loggedAll(w); ++ assert.deepEqual(await ensureCheckouts({ workspace: w.workspace, projects: { stack: { root: w.root } }, env: l.env }), [{ project: "stack", path: w.clone, outcome: "updated", branch: "main", commit: two }]); ++ const calls = l.calls(); ++ assert.ok(calls.some((c) => !c.startsWith("-c core.hooksPath=")), "the log shows a git that git started"); ++ assert.deepEqual(calls.filter((c) => ["maintenance", "gc", "repack"].some((sub) => c.split(" ").includes(sub))), [], "no maintenance, gc or repack ran"); ++ for (const o of objects) assert.ok(existsSync(o), `${o} is still loose`); ++}); ++ + // Row 104 (#1598): a session that sets its own git identity in the clone's + // config gets the clone kept at every later launch. The host only reads the + // clone's config file and runs no git in it, so the kept result has no commit. diff --git a/agents/darkwing/work/queue-118/r1/candidate-manifest.sha256 b/agents/darkwing/work/queue-118/r1/candidate-manifest.sha256 new file mode 100644 index 000000000..5b5219708 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/candidate-manifest.sha256 @@ -0,0 +1,3 @@ +050cfa5e3c8b045b49e51bb0583ae4ace60795972deaa8f2a0f778bdacc8284e packages/seat/README.md +a1c9b4abf4a2e4f6e1a8cb3a3721c82e7f91bb7aa00eec0a1dd5bcdcbbfa056b packages/seat/src/checkout.mjs +dbf49f246cbac72507ab989b4af536d46fbb8dca06a049eabf704c5fa5fbbbca packages/seat/tests/checkout.test.mjs diff --git a/agents/darkwing/work/queue-118/r1/checkout-test.log b/agents/darkwing/work/queue-118/r1/checkout-test.log new file mode 100644 index 000000000..0197b0d5a --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/checkout-test.log @@ -0,0 +1,90 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (37.901349ms) +✔ the clone is not at its place until git clone has finished (19.340591ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (54.600852ms) +✔ a clone ahead of the root keeps its commits and counts as current (34.760051ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (41.810589ms) + ✔ an untracked file (42.691626ms) + ✔ a staged file (44.091635ms) + ✔ another branch (40.323758ms) + ✔ a detached HEAD (40.088162ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (210.228833ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (41.248715ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (22.590888ms) + ✔ hooks-present (24.386165ms) + ✔ submodule (29.966132ms) + ✔ config-changed (27.041938ms) + ✔ root-unreadable (27.772932ms) + ✔ root-detached (32.42569ms) + ✔ status-failed, on the root's branch and clean (34.029369ms) + ✔ status-failed, dirty (34.29624ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (233.737558ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (37.249461ms) +✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (45.198425ms) +✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (72.021094ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (25.167173ms) + ✔ user.email (24.940799ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (50.561812ms) +✔ a commit git can't read after the fast-forward is null (45.708066ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (19.924793ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (33.599139ms) + ✔ core.hooksPath (33.306678ms) + ✔ a hook file (30.687437ms) + ✔ include.path (32.830901ms) + ✔ includeIf (32.832815ms) + ✔ a filter driver (32.692744ms) + ✔ origin moved (33.441634ms) + ✔ a second origin url (33.076638ms) + ✔ the origin url twice, both the root (34.253254ms) + ✔ an ext:: origin (32.907349ms) + ✔ a branch tracking another remote (33.268704ms) + ✔ a branch merging a tag (32.699419ms) + ✔ a fetch line onto local branches (33.342363ms) + ✔ no origin (33.758252ms) + ✔ a config file over the size cap (30.796174ms) + ✔ config a symlink to /dev/zero (30.318363ms) + ✔ the clone a symlink to the bait (30.372512ms) + ✔ the clone a file (30.161655ms) + ✔ the clone a plain directory (30.050441ms) + ✔ .git a gitdir: file (31.191007ms) + ✔ .git a symlink (29.974552ms) + ✔ .git/logs a symlink (30.28925ms) + ✔ .git/refs/heads a symlink (31.077999ms) + ✔ .git/objects a symlink (31.183755ms) + ✔ a reflog hard-linked to a file outside (31.736856ms) + ✔ HEAD a fifo (31.686035ms) + ✔ a commondir file (30.592265ms) + ✔ an alternates file (30.077393ms) + ✔ a gitlink staged, no .gitmodules (43.64755ms) + ✔ a gitlink and .gitmodules staged (44.830457ms) + ✔ a gitlink to a repository with its own gitlink (53.16234ms) + ✔ a gitlink and .gitmodules committed (48.496336ms) + ✔ a gitlink and submodule.active in config (45.110636ms) + ✔ a .git/modules with no gitlink (32.764679ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1164.346634ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (9.953521ms) + ✔ not a repository (10.038379ms) + ✔ a plain directory inside another repository (14.558265ms) + ✔ unreadable (12.626487ms) + ✔ on no branch (11.503149ms) + ✔ missing an object (15.129292ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (74.538392ms) +✔ a clone whose root is on no branch, or gone, is kept and named (27.860283ms) +✔ no credential or forge remote of the root reaches a clone (48.215685ms) +✔ every project in the business file gets its own clone at its own branch (35.76246ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (513.670918ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1025.487379ms) +✔ a timeout signals git's process group, so a process git started dies with it (522.133597ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (522.001379ms) +ℹ tests 77 +ℹ suites 0 +ℹ pass 77 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4886.426242 diff --git a/agents/darkwing/work/queue-118/r1/gate-run.log b/agents/darkwing/work/queue-118/r1/gate-run.log new file mode 100644 index 000000000..4437e08f6 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/gate-run.log @@ -0,0 +1,4 @@ +base 2727dd3ec start 19:37:09 +pkg-seat rc=0 ℹ pass 132 ℹ fail 0 +pkg-cli rc=0 ℹ pass 184 ℹ fail 0 +GATE_PASSED 19:37:33 diff --git a/agents/darkwing/work/queue-118/r1/gate.sh b/agents/darkwing/work/queue-118/r1/gate.sh new file mode 100755 index 000000000..850551752 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/gate.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Row 118 targeted builder gate (#271, decision 136): packages/seat, and +# packages/cli, whose launcher.mjs is the one caller of ensureCheckouts. No +# scripts/test-*.sh reads a changed path. +set -o pipefail +G=~/darkwing-scratch/r118/wt +OUT=~/darkwing-scratch/r118/pkt/gate +mkdir -p "$OUT" +cd "$G" || exit 9 +[ -d "$G/node_modules" ] || { echo "STOP no node_modules in $G"; exit 8; } +echo "base $(git rev-parse --short HEAD) start $(date -u +%H:%M:%S)" +run() { local name="$1" dir="$2" cmd="$3" + (cd "$G/$dir" && TMPDIR=~/darkwing-scratch/r118/tmp bash -c "$cmd") > "$OUT/$name.log" 2>&1; local rc=$? + echo "$name rc=$rc $(grep -E '^ℹ (pass|fail) [0-9]+' "$OUT/$name.log" | tr '\n' ' ')" + [ $rc -eq 0 ] || { echo "STOP $name"; exit $rc; }; } +run pkg-seat packages/seat 'node --test tests/' +run pkg-cli packages/cli 'node --test tests/*.test.mjs' +echo "GATE_PASSED $(date -u +%H:%M:%S)" diff --git a/agents/darkwing/work/queue-118/r1/gate/pkg-cli.log b/agents/darkwing/work/queue-118/r1/gate/pkg-cli.log new file mode 100644 index 000000000..7054ac3b4 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/gate/pkg-cli.log @@ -0,0 +1,194 @@ +✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1320.954027ms) +✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (72.489457ms) +✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (159.406777ms) +✔ NO_COLOR: no escape codes, except reverse video on the selected row (221.28439ms) +✔ 1-9 shows the decide flow's own lines and question; only y resolves (183.919212ms) +✔ the app shows the same decide text as mosaic decide (112.902825ms) +✔ any key but y or Y at the question answers No; nothing is sent (2807.643417ms) +✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (646.645649ms) +✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (1892.390051ms) +✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (248.095551ms) +✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (127.382293ms) +✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (789.150232ms) +✔ quitting at the question answers No and sends nothing, armed or not (177.897241ms) +✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (143.079446ms) +✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (123.837352ms) +✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (1009.276114ms) +✔ at every size the decide box shows each line y depends on, or says it is hidden (8220.568036ms) +✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (147.770173ms) +✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (115.415814ms) +✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (57.361436ms) +✔ keys: views, moving, trail of a selection, the command line, help and quit (239.366377ms) +✔ one option: the status line says Press 1, not 1-1 (101.007511ms) +✔ no business: the app says so and :business picks one (54.786343ms) +✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.705178ms) +✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (162.476148ms) +✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.440523ms) +✔ decide with --note still asks; a No sends nothing (69.063043ms) +✔ the app's business comes from a live bus host only (0.899544ms) +✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (6.15014ms) +✔ draw is pure: same state, same frame (0.720298ms) +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (131.986181ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (121.799101ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (110.577846ms) +✔ decide prints a declining choice as declining (119.764946ms) +✔ an unknown outcome is reported once and never resent (136.764457ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (97.961309ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (88.760417ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (93.444711ms) +✔ every human command refuses inside an agent run before it touches the bus (136.723199ms) +✔ usage errors exit 4; no business and no host is a usage error (90.068346ms) +✔ agents and tasks print through the broker (92.010653ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.386066ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (59.029674ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (71.47218ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (38.327924ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (32.190255ms) +✔ a business without tracker.baseUrl gets no trackers entry (32.287267ms) +✔ an unknown business and a broken system config refuse with exit 3 (61.603792ms) +✔ follow prints an unsolicited message after plain talk returned (178.840557ms) +✔ talk --follow continues after reply and prints a later unsolicited message (190.758798ms) +✔ inbox lists all unread human messages under decisions, does not deliver or read them (3217.56083ms) +✔ follow refuses agent context and bad arguments (309.183815ms) +✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (652.330074ms) +✔ plain talk leaves the process SIGINT handlers unchanged (192.829061ms) +✔ empty views say so (0.963229ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.171878ms) +✔ tasks print the tracker fields the snapshot carries (0.209073ms) +✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (680.657909ms) +✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (631.524581ms) +✔ the size cap refuses a push before any byte (379.011859ms) +✔ a fetch over the cap refuses before any byte reaches the reviewer (629.839508ms) +✔ the socket refuses a request with no capability, an unknown one, and a bad request line (385.113825ms) +✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (1986.669742ms) +✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (392.543459ms) +✔ a review of a project the business no longer has refuses unknown-project (579.310362ms) +✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (573.418242ms) +✔ close() answers host-closing to a connection still sending its request line (279.840499ms) +✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (803.602135ms) +✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (725.7926ms) +✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (638.799783ms) +✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (41.458492ms) +✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (581.092063ms) +✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (43.429795ms) +✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (301.807841ms) +✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (468.865094ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (920.998786ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (250.871157ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (171.828191ms) +✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (569.897712ms) +✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.182231ms) +✔ a host.lock that goes between the check and the read is tried again; one that can't be parsed still refuses as unreadable (0.691024ms) +✔ a live host's lock stays, however old the file is (0.497763ms) +✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (870.11269ms) +✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (222.3529ms) +✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (27.610726ms) +✔ a broker fork that throws leaves no host.lock and no host.json (32.762297ms) +✔ a broker send that throws leaves no host.lock and no host.json (28.767102ms) +✔ a broker that misses its boot wait leaves no host.lock and no host.json (58.417657ms) +✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (435.338333ms) +✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (698.783871ms) +✔ a boot reply with no reader for the business refuses with exit 3 naming it, before the notifier starts, and leaves no host.lock and no live broker (361.085396ms) +✔ a boot reply with no reader for the business refuses with exit 3 naming it, with no notifier, and leaves no host.lock and no live broker (164.831153ms) +✔ a host.json that can't be written stops both children and leaves no host.lock (257.109717ms) +✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (27.414927ms) +✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (170.485753ms) +✔ an error event on the broker while the notifier starts fails the start once the notifier replies (546.568424ms) +✔ an error event on the broker once the host is up stops the host with exit 1, and leaves no host.lock and no host.json (422.60805ms) +✔ an error event on the notifier once the host is up stops the host with exit 1, and leaves no host.lock and no host.json (293.244655ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1246.748047ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (326.30761ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (173.288676ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (185.774615ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (177.468925ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (139.220527ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (187.644604ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (130.275233ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (199.802478ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (20.265388ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.299759ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (234.053286ms) +✔ bus start refuses with exit 3 without a notifier config (99.687584ms) +✔ bus start runs until bus stop; status reports it while it runs (681.886902ms) +✔ bus-service.sh renders the unit and installs it into a given directory (52.471165ms) +✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (67.782994ms) +✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (13.756157ms) +✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (8.54067ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1340.513602ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (798.81903ms) +✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (2502.247716ms) +✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (239.648604ms) +✔ a runner that stops at once ends its launch with the runner's reason (263.097039ms) +✔ a Claude Code launch with no session credential refuses before it starts (293.403342ms) +✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (617.023203ms) +✔ a runner killed from the host ends its launch as killed, not failed (329.568963ms) +✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (355.770285ms) +✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1570.987665ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (744.434817ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3680.397086ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (122.766837ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (137.917786ms) +✔ a launch client that never closes its side doesn't hold the host's close (144.630649ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1267.979555ms) +✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.364429ms) +✔ zoned uses the IANA zone across DST (22.43091ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (135.315893ms) +✔ two blocking decisions get two DMs with different nonces (122.970123ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.1995ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (113.761965ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (107.147792ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (137.299665ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (106.561952ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (164.772782ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (179.602776ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (113.278849ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (94.419202ms) +✔ an inbox read failure is logged and the next poll retries (0.762903ms) +✔ no Discord id reaches the journal or the log (150.347612ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (34.538943ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (51.017238ms) +✔ the journal: a whole file that is one torn line truncates to empty (25.344443ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.008643ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.618791ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.832905ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.362479ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.498529ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.932232ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.43433ms) +✔ digest content stays within Discord's 2000 characters (0.277579ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.794134ms) +✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (513.534982ms) +✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (430.672898ms) +✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (446.87159ms) +✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (284.647433ms) +✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (4214.366992ms) +✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (596.790248ms) +✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (205.309958ms) +✔ install never shows a template name on systemd 261; template drop-ins still refuse (207.134912ms) +✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (232.155204ms) +✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (154.4437ms) +✔ talk --tester refuses text without the [mos test] prefix before any call (104.340822ms) +✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (115.206459ms) +✔ decide, stop and launches take no --tester (122.994692ms) +✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.346562ms) +✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.5681ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (391.918505ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (46.008039ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2154.12241ms) +✔ busExit and refuseInsideAgent (0.462089ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (220.429872ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1123.607523ms) +✔ a late reply the Console has listed still reaches the next talk (2187.288781ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (242.260172ms) +✔ launches off and on go to the broker and change the business's launch state (760.397036ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (163.844791ms) +ℹ tests 184 +ℹ suites 0 +ℹ pass 184 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 19349.49889 diff --git a/agents/darkwing/work/queue-118/r1/gate/pkg-seat.log b/agents/darkwing/work/queue-118/r1/gate/pkg-seat.log new file mode 100644 index 000000000..869c461ae --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/gate/pkg-seat.log @@ -0,0 +1,145 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (38.856883ms) +✔ the clone is not at its place until git clone has finished (26.681064ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (65.003845ms) +✔ a clone ahead of the root keeps its commits and counts as current (43.588913ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (43.59014ms) + ✔ an untracked file (46.353906ms) + ✔ a staged file (45.144416ms) + ✔ another branch (46.323667ms) + ✔ a detached HEAD (43.262676ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (225.792698ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (46.71297ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (25.418385ms) + ✔ hooks-present (26.96287ms) + ✔ submodule (31.158156ms) + ✔ config-changed (31.003008ms) + ✔ root-unreadable (33.883339ms) + ✔ root-detached (34.840723ms) + ✔ status-failed, on the root's branch and clean (37.123614ms) + ✔ status-failed, dirty (34.796853ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (256.432842ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (39.110443ms) +✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (46.38127ms) +✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (72.35308ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (30.031569ms) + ✔ user.email (27.092928ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (57.587545ms) +✔ a commit git can't read after the fast-forward is null (46.67885ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (18.166255ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (38.291224ms) + ✔ core.hooksPath (35.130754ms) + ✔ a hook file (31.259291ms) + ✔ include.path (34.364781ms) + ✔ includeIf (33.19955ms) + ✔ a filter driver (33.206437ms) + ✔ origin moved (34.825767ms) + ✔ a second origin url (35.411536ms) + ✔ the origin url twice, both the root (34.44014ms) + ✔ an ext:: origin (37.112465ms) + ✔ a branch tracking another remote (33.830367ms) + ✔ a branch merging a tag (36.363934ms) + ✔ a fetch line onto local branches (37.121619ms) + ✔ no origin (35.349362ms) + ✔ a config file over the size cap (33.558181ms) + ✔ config a symlink to /dev/zero (31.741421ms) + ✔ the clone a symlink to the bait (30.165908ms) + ✔ the clone a file (34.021833ms) + ✔ the clone a plain directory (32.528989ms) + ✔ .git a gitdir: file (31.491629ms) + ✔ .git a symlink (30.568452ms) + ✔ .git/logs a symlink (32.79718ms) + ✔ .git/refs/heads a symlink (33.79864ms) + ✔ .git/objects a symlink (30.544951ms) + ✔ a reflog hard-linked to a file outside (31.746166ms) + ✔ HEAD a fifo (37.807773ms) + ✔ a commondir file (33.851191ms) + ✔ an alternates file (31.76746ms) + ✔ a gitlink staged, no .gitmodules (44.208713ms) + ✔ a gitlink and .gitmodules staged (47.458381ms) + ✔ a gitlink to a repository with its own gitlink (54.114978ms) + ✔ a gitlink and .gitmodules committed (50.772084ms) + ✔ a gitlink and submodule.active in config (47.177316ms) + ✔ a .git/modules with no gitlink (33.088615ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1227.373124ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.047063ms) + ✔ not a repository (9.972891ms) + ✔ a plain directory inside another repository (15.182941ms) + ✔ unreadable (10.203505ms) + ✔ on no branch (12.189808ms) + ✔ missing an object (15.832228ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (74.23913ms) +✔ a clone whose root is on no branch, or gone, is kept and named (28.363215ms) +✔ no credential or forge remote of the root reaches a clone (46.756517ms) +✔ every project in the business file gets its own clone at its own branch (39.119871ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (512.810147ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1015.938178ms) +✔ a timeout signals git's process group, so a process git started dies with it (522.393262ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (514.046312ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.561636ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (7.523347ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (1.298638ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (0.636453ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.812129ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1818.252112ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2376.032124ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (260.623472ms) +✔ resolveSeat: by name under --repo resolves the repo layout (1.22391ms) +✔ resolveSeat: by path resolves the fleet layout (0.315484ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.735879ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.644483ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.766621ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.317188ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.141678ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (28.777961ms) +✔ CLI launch: --harness lands in the record (28.525843ms) +✔ CLI launch: the launch script's own exit code passes through (29.527322ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (58.798431ms) +✔ CLI launch: omitting --task records an empty string, not null (27.650307ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (77.666467ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (127.052069ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.40771ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.585365ms) +✔ validateRegistration/readRegistration: taskSetBy is optional; a record without it (written before #1511) still loads unchanged; an invalid one is refused; the version does not change (0.702248ms) +✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (1.770202ms) +✔ CLI seat task: --by beats MOSAIC_AGENT_NAME, the environment beats nothing, empty environment is unknown, invalid --by or environment refuses with exit 4 and leaves the record byte for byte (247.243152ms) +✔ family: exactly one launch.max key in the model name, else null (0.699196ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.711437ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.244137ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.470746ms) +✔ session file and launch log: 0600, the session file written once (0.989883ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.110097ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.180158ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.924026ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (293.479937ms) +✔ workRef and validBranch take only what a ref name can hold (1.089132ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (138.466934ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (124.930652ms) +✔ a branch with no history in common with main refuses unrelated-history (61.897414ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (38.18099ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (136.584847ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (92.1072ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (84.568147ms) +✔ only a v2 bundle with the named branch is read (44.366232ms) +✔ a branch named main, or named for another task, moves only this task's work ref (95.828823ms) +✔ a record that throws moves the work ref back, or removes a new one (148.659086ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (401.02082ms) +✔ a root that can't be read refuses root-unreadable (30.522807ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2155.923232ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1028.891118ms) +✔ a working repository path that isn't a directory refuses (31.202906ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (118.963519ms) +✔ review of a project with no working repository refuses unknown-commit (13.249003ms) +✔ the runner's git tools hold the host's limits and branch rule (1.177297ms) +ℹ tests 132 +ℹ suites 0 +ℹ pass 132 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 5014.739489 diff --git a/agents/darkwing/work/queue-118/r1/mutants-run.log b/agents/darkwing/work/queue-118/r1/mutants-run.log new file mode 100644 index 000000000..615dbb88f --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/mutants-run.log @@ -0,0 +1,8 @@ +m1-drop-maintenance-auto: killed (rc 1, pass 76, fail 1) + fail: every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (44.581078ms) +m2-drop-gc-auto: killed (rc 1, pass 76, fail 1) + fail: every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (54.540118ms) +m3-drop-both: killed (rc 1, pass 75, fail 2) + fail: a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (76.158789ms) + fail: every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (46.395338ms) +restored: True diff --git a/agents/darkwing/work/queue-118/r1/mutants-run1.log b/agents/darkwing/work/queue-118/r1/mutants-run1.log new file mode 100644 index 000000000..b47eac5a5 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/mutants-run1.log @@ -0,0 +1,4 @@ +m1-drop-maintenance-auto: killed (rc 1, pass 76, fail 1) +m2-drop-gc-auto: killed (rc 1, pass 76, fail 1) +m3-drop-both: killed (rc 1, pass 75, fail 2) +restored: True diff --git a/agents/darkwing/work/queue-118/r1/mutants.py b/agents/darkwing/work/queue-118/r1/mutants.py new file mode 100755 index 000000000..258c7159c --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/mutants.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Row 118 mutants: drop each new SAFE key, and both. Each edits checkout.mjs, +runs packages/seat/tests/checkout.test.mjs, and restores the file from .orig.""" +import hashlib, os, re, shutil, signal, subprocess, sys + +WT = os.path.expanduser('~/darkwing-scratch/r118/wt') +PKT = os.path.expanduser('~/darkwing-scratch/r118/pkt') +TMP = os.path.expanduser('~/darkwing-scratch/r118/tmp') +F = 'packages/seat/src/checkout.mjs' +MA = ' "-c", "maintenance.auto=false",\n' +GC = ' "-c", "gc.auto=0",\n' +M = [ + ('m1-drop-maintenance-auto', [MA]), + ('m2-drop-gc-auto', [GC]), + ('m3-drop-both', [MA, GC]), +] + +def sha(p): + return hashlib.sha256(open(p, 'rb').read()).hexdigest() + +# A detached `maintenance run` a mutant started runs in a test's temp dir; +# kill only those, by cwd under this row's TMPDIR. +def sweep(): + left = [] + for pid in filter(str.isdigit, os.listdir('/proc')): + try: + if os.readlink(f'/proc/{pid}/cwd').startswith(TMP + '/'): + left.append(int(pid)); os.kill(int(pid), signal.SIGKILL) + except OSError: + pass + return left + +only = set(sys.argv[1:]) +os.chdir(WT) +env = dict(os.environ, TMPDIR=TMP, NO_COLOR='1') +base = sha(F) +os.makedirs(os.path.join(PKT, 'mutants'), exist_ok=True) +log = open(os.path.join(PKT, 'mutants-run.log'), 'a') +for name, drops in M: + if only and name not in only: + continue + src = open(F).read() + if any(src.count(d) != 1 for d in drops): + print(f'{name}: pattern not found once, skipped', file=log, flush=True); print(name, 'BAD PATTERN'); continue + shutil.copy2(F, F + '.orig') + try: + out = src + for d in drops: + out = out.replace(d, '') + open(F, 'w').write(out) + p = subprocess.Popen(['node', '--test', 'tests/checkout.test.mjs'], cwd='packages/seat', stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=env, start_new_session=True, text=True) + try: + res, _ = p.communicate(timeout=600) + except subprocess.TimeoutExpired: + os.killpg(p.pid, signal.SIGKILL); res, _ = p.communicate(); res += '\nTIMEOUT\n' + open(os.path.join(PKT, 'mutants', f'mutant-{name}.log'), 'w').write(res) + fails = sorted({l[2:] for l in res.splitlines() if l.startswith('✖ ') and l != '✖ failing tests:'}) + tally = dict(re.findall(r'^ℹ (pass|fail) (\d+)$', res, re.M)) + verdict = 'killed' if p.returncode != 0 else 'SURVIVED' + print(f'{name}: {verdict} (rc {p.returncode}, pass {tally.get("pass", "?")}, fail {tally.get("fail", "?")})', file=log, flush=True) + for l in fails: + print(f' fail: {l}', file=log, flush=True) + left = sweep() + if left: + print(f' swept {len(left)} leftover process(es) under TMPDIR: {left}', file=log, flush=True) + print(name, verdict, tally, flush=True) + finally: + shutil.move(F + '.orig', F) + assert sha(F) == base, f'{F} not restored' +print('restored:', sha(F) == base, file=log, flush=True) diff --git a/agents/darkwing/work/queue-118/r1/mutants/mutant-m1-drop-maintenance-auto.log b/agents/darkwing/work/queue-118/r1/mutants/mutant-m1-drop-maintenance-auto.log new file mode 100644 index 000000000..3c872c06a --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/mutants/mutant-m1-drop-maintenance-auto.log @@ -0,0 +1,106 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (30.874412ms) +✔ the clone is not at its place until git clone has finished (19.97585ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (57.019597ms) +✔ a clone ahead of the root keeps its commits and counts as current (36.475994ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (41.670939ms) + ✔ an untracked file (42.840623ms) + ✔ a staged file (42.727131ms) + ✔ another branch (43.582205ms) + ✔ a detached HEAD (41.966874ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (213.888462ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (42.256177ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (22.885781ms) + ✔ hooks-present (25.423393ms) + ✔ submodule (29.941278ms) + ✔ config-changed (27.089927ms) + ✔ root-unreadable (25.925444ms) + ✔ root-detached (33.148868ms) + ✔ status-failed, on the root's branch and clean (34.537694ms) + ✔ status-failed, dirty (34.382806ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (234.554914ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (37.0695ms) +✖ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (44.581078ms) +✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (71.826799ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (25.183784ms) + ✔ user.email (25.033575ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (50.635304ms) +✔ a commit git can't read after the fast-forward is null (45.238287ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (18.4517ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (33.473377ms) + ✔ core.hooksPath (33.07263ms) + ✔ a hook file (31.393694ms) + ✔ include.path (32.692382ms) + ✔ includeIf (32.955461ms) + ✔ a filter driver (33.077587ms) + ✔ origin moved (32.888028ms) + ✔ a second origin url (32.761249ms) + ✔ the origin url twice, both the root (34.24756ms) + ✔ an ext:: origin (33.669912ms) + ✔ a branch tracking another remote (33.226371ms) + ✔ a branch merging a tag (33.703833ms) + ✔ a fetch line onto local branches (33.507316ms) + ✔ no origin (33.409032ms) + ✔ a config file over the size cap (31.151607ms) + ✔ config a symlink to /dev/zero (30.190501ms) + ✔ the clone a symlink to the bait (29.388649ms) + ✔ the clone a file (31.25766ms) + ✔ the clone a plain directory (31.661211ms) + ✔ .git a gitdir: file (30.859142ms) + ✔ .git a symlink (30.403559ms) + ✔ .git/logs a symlink (30.660517ms) + ✔ .git/refs/heads a symlink (31.036368ms) + ✔ .git/objects a symlink (31.261603ms) + ✔ a reflog hard-linked to a file outside (31.162747ms) + ✔ HEAD a fifo (32.121293ms) + ✔ a commondir file (31.288835ms) + ✔ an alternates file (30.880443ms) + ✔ a gitlink staged, no .gitmodules (43.725997ms) + ✔ a gitlink and .gitmodules staged (46.683461ms) + ✔ a gitlink to a repository with its own gitlink (53.302618ms) + ✔ a gitlink and .gitmodules committed (47.426481ms) + ✔ a gitlink and submodule.active in config (44.606661ms) + ✔ a .git/modules with no gitlink (34.219262ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1171.629158ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.271156ms) + ✔ not a repository (10.18577ms) + ✔ a plain directory inside another repository (14.828244ms) + ✔ unreadable (10.310556ms) + ✔ on no branch (11.877636ms) + ✔ missing an object (15.220837ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (73.444798ms) +✔ a clone whose root is on no branch, or gone, is kept and named (27.967553ms) +✔ no credential or forge remote of the root reaches a clone (47.555766ms) +✔ every project in the business file gets its own clone at its own branch (35.846973ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (515.113712ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1025.935833ms) +✔ a timeout signals git's process group, so a process git started dies with it (521.820328ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (521.17861ms) +ℹ tests 77 +ℹ suites 0 +ℹ pass 76 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4892.902941 + +✖ failing tests: + +test at tests/checkout.test.mjs:250:1 +✖ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (44.581078ms) + AssertionError [ERR_ASSERTION]: maintenance.auto=false is on: -c core.hooksPath=/dev/null -c core.fsmonitor=false -c protocol.allow=never -c protocol.file.allow=always -c submodule.recurse=false -c gc.auto=0 -C /home/jwoltje/darkwing-scratch/r118/tmp/mosaic-checkout-OzLxPx/root symbolic-ref -q --short HEAD + at TestContext. (file:///home/jwoltje/darkwing-scratch/r118/wt/packages/seat/tests/checkout.test.mjs:259:91) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-118/r1/mutants/mutant-m2-drop-gc-auto.log b/agents/darkwing/work/queue-118/r1/mutants/mutant-m2-drop-gc-auto.log new file mode 100644 index 000000000..0dcf73c9a --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/mutants/mutant-m2-drop-gc-auto.log @@ -0,0 +1,106 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (37.342293ms) +✔ the clone is not at its place until git clone has finished (22.837938ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (63.225911ms) +✔ a clone ahead of the root keeps its commits and counts as current (43.697808ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (51.56499ms) + ✔ an untracked file (54.23654ms) + ✔ a staged file (62.118516ms) + ✔ another branch (47.793494ms) + ✔ a detached HEAD (47.95716ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (265.353716ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (53.27812ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (27.638435ms) + ✔ hooks-present (26.85727ms) + ✔ submodule (35.15431ms) + ✔ config-changed (31.740862ms) + ✔ root-unreadable (31.648068ms) + ✔ root-detached (37.5055ms) + ✔ status-failed, on the root's branch and clean (42.847219ms) + ✔ status-failed, dirty (41.804376ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (276.601062ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (43.645402ms) +✖ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (54.540118ms) +✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (83.668939ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (29.456739ms) + ✔ user.email (31.187861ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (61.186105ms) +✔ a commit git can't read after the fast-forward is null (59.538829ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (23.395089ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (37.561278ms) + ✔ core.hooksPath (37.126822ms) + ✔ a hook file (37.613838ms) + ✔ include.path (39.871059ms) + ✔ includeIf (38.838183ms) + ✔ a filter driver (38.382135ms) + ✔ origin moved (45.923765ms) + ✔ a second origin url (37.070869ms) + ✔ the origin url twice, both the root (45.978348ms) + ✔ an ext:: origin (46.964036ms) + ✔ a branch tracking another remote (43.274526ms) + ✔ a branch merging a tag (40.383824ms) + ✔ a fetch line onto local branches (41.637266ms) + ✔ no origin (44.447967ms) + ✔ a config file over the size cap (43.824204ms) + ✔ config a symlink to /dev/zero (38.56111ms) + ✔ the clone a symlink to the bait (39.959494ms) + ✔ the clone a file (40.276246ms) + ✔ the clone a plain directory (38.220059ms) + ✔ .git a gitdir: file (41.719073ms) + ✔ .git a symlink (41.6241ms) + ✔ .git/logs a symlink (37.197767ms) + ✔ .git/refs/heads a symlink (34.945767ms) + ✔ .git/objects a symlink (32.071945ms) + ✔ a reflog hard-linked to a file outside (31.214951ms) + ✔ HEAD a fifo (31.218192ms) + ✔ a commondir file (30.637126ms) + ✔ an alternates file (31.657492ms) + ✔ a gitlink staged, no .gitmodules (43.941368ms) + ✔ a gitlink and .gitmodules staged (45.770805ms) + ✔ a gitlink to a repository with its own gitlink (53.125891ms) + ✔ a gitlink and .gitmodules committed (48.586932ms) + ✔ a gitlink and submodule.active in config (46.169756ms) + ✔ a .git/modules with no gitlink (33.897139ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1364.983114ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.295228ms) + ✔ not a repository (10.449314ms) + ✔ a plain directory inside another repository (15.190708ms) + ✔ unreadable (10.120343ms) + ✔ on no branch (11.837038ms) + ✔ missing an object (15.081714ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (73.752868ms) +✔ a clone whose root is on no branch, or gone, is kept and named (29.078317ms) +✔ no credential or forge remote of the root reaches a clone (48.507008ms) +✔ every project in the business file gets its own clone at its own branch (35.283376ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (513.63614ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1026.114416ms) +✔ a timeout signals git's process group, so a process git started dies with it (522.053104ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (521.882393ms) +ℹ tests 77 +ℹ suites 0 +ℹ pass 76 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 5277.432104 + +✖ failing tests: + +test at tests/checkout.test.mjs:250:1 +✖ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (54.540118ms) + AssertionError [ERR_ASSERTION]: gc.auto=0 is on: -c core.hooksPath=/dev/null -c core.fsmonitor=false -c protocol.allow=never -c protocol.file.allow=always -c submodule.recurse=false -c maintenance.auto=false -C /home/jwoltje/darkwing-scratch/r118/tmp/mosaic-checkout-z7P0T4/root symbolic-ref -q --short HEAD + at TestContext. (file:///home/jwoltje/darkwing-scratch/r118/wt/packages/seat/tests/checkout.test.mjs:259:91) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-118/r1/mutants/mutant-m3-drop-both.log b/agents/darkwing/work/queue-118/r1/mutants/mutant-m3-drop-both.log new file mode 100644 index 000000000..632c45a03 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/mutants/mutant-m3-drop-both.log @@ -0,0 +1,130 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (31.713803ms) +✔ the clone is not at its place until git clone has finished (19.380207ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (59.384826ms) +✔ a clone ahead of the root keeps its commits and counts as current (36.37111ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (43.000708ms) + ✔ an untracked file (43.391326ms) + ✔ a staged file (44.974986ms) + ✔ another branch (41.616863ms) + ✔ a detached HEAD (42.255725ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (216.358973ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (43.46542ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (22.681116ms) + ✔ hooks-present (23.81415ms) + ✔ submodule (29.136121ms) + ✔ config-changed (27.029866ms) + ✔ root-unreadable (26.884885ms) + ✔ root-detached (31.530676ms) + ✔ status-failed, on the root's branch and clean (33.471935ms) + ✔ status-failed, dirty (34.086325ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (229.762484ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (37.616828ms) +✖ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (46.395338ms) +✖ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (76.158789ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (27.696637ms) + ✔ user.email (24.657424ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (52.750908ms) +✔ a commit git can't read after the fast-forward is null (47.271021ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (18.766847ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (33.429717ms) + ✔ core.hooksPath (32.679529ms) + ✔ a hook file (31.548218ms) + ✔ include.path (32.997767ms) + ✔ includeIf (33.189477ms) + ✔ a filter driver (33.727493ms) + ✔ origin moved (33.237946ms) + ✔ a second origin url (33.528393ms) + ✔ the origin url twice, both the root (34.535689ms) + ✔ an ext:: origin (33.156381ms) + ✔ a branch tracking another remote (33.675418ms) + ✔ a branch merging a tag (33.093986ms) + ✔ a fetch line onto local branches (34.439342ms) + ✔ no origin (33.77151ms) + ✔ a config file over the size cap (31.192549ms) + ✔ config a symlink to /dev/zero (30.977553ms) + ✔ the clone a symlink to the bait (30.370214ms) + ✔ the clone a file (31.629761ms) + ✔ the clone a plain directory (31.273996ms) + ✔ .git a gitdir: file (31.098844ms) + ✔ .git a symlink (30.303962ms) + ✔ .git/logs a symlink (30.502035ms) + ✔ .git/refs/heads a symlink (31.409257ms) + ✔ .git/objects a symlink (30.985466ms) + ✔ a reflog hard-linked to a file outside (31.629158ms) + ✔ HEAD a fifo (31.412827ms) + ✔ a commondir file (30.476576ms) + ✔ an alternates file (30.496569ms) + ✔ a gitlink staged, no .gitmodules (42.918893ms) + ✔ a gitlink and .gitmodules staged (44.806785ms) + ✔ a gitlink to a repository with its own gitlink (51.699245ms) + ✔ a gitlink and .gitmodules committed (47.443196ms) + ✔ a gitlink and submodule.active in config (45.14899ms) + ✔ a .git/modules with no gitlink (33.821136ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1170.680411ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.324468ms) + ✔ not a repository (10.20573ms) + ✔ a plain directory inside another repository (14.777298ms) + ✔ unreadable (10.499932ms) + ✔ on no branch (12.171015ms) + ✔ missing an object (15.382982ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (74.207311ms) +✔ a clone whose root is on no branch, or gone, is kept and named (28.12392ms) +✔ no credential or forge remote of the root reaches a clone (53.662842ms) +✔ every project in the business file gets its own clone at its own branch (37.1437ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (513.800956ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1021.902335ms) +✔ a timeout signals git's process group, so a process git started dies with it (522.385626ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (515.467774ms) +ℹ tests 77 +ℹ suites 0 +ℹ pass 75 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4901.369326 + +✖ failing tests: + +test at tests/checkout.test.mjs:250:1 +✖ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (46.395338ms) + AssertionError [ERR_ASSERTION]: maintenance.auto=false is on: -c core.hooksPath=/dev/null -c core.fsmonitor=false -c protocol.allow=never -c protocol.file.allow=always -c submodule.recurse=false -C /home/jwoltje/darkwing-scratch/r118/tmp/mosaic-checkout-eEs4f9/root symbolic-ref -q --short HEAD + at TestContext. (file:///home/jwoltje/darkwing-scratch/r118/wt/packages/seat/tests/checkout.test.mjs:259:91) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at tests/checkout.test.mjs:297:1 +✖ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (76.158789ms) + AssertionError [ERR_ASSERTION]: no maintenance, gc or repack ran + + actual - expected + + + [ + + 'maintenance run --auto --quiet --detach', + + 'repack -d -l --cruft --cruft-expiration=2.weeks.ago --quiet --write-midx', + + 'maintenance run --auto --quiet --detach' + + ] + - [] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r118/wt/packages/seat/tests/checkout.test.mjs:306:10) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: [ 'maintenance run --auto --quiet --detach', 'repack -d -l --cruft --cruft-expiration=2.weeks.ago --quiet --write-midx', 'maintenance run --auto --quiet --detach' ], + expected: [], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-118/r1/probe/loose.mjs b/agents/darkwing/work/queue-118/r1/probe/loose.mjs new file mode 100644 index 000000000..2efb957f0 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/probe/loose.mjs @@ -0,0 +1,15 @@ +// Write n valid loose blobs whose ids start with 17 into /17. +import { createHash } from "node:crypto"; +import { deflateSync } from "node:zlib"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +const [objects, n] = [process.argv[2], Number(process.argv[3])]; +mkdirSync(join(objects, "17"), { recursive: true }); +for (let i = 0, made = 0; made < n; i++) { + const body = Buffer.from(`loose ${i}\n`); + const raw = Buffer.concat([Buffer.from(`blob ${body.length}\0`), body]); + const id = createHash("sha1").update(raw).digest("hex"); + if (!id.startsWith("17")) continue; + writeFileSync(join(objects, "17", id.slice(2)), deflateSync(raw)); + made++; +} diff --git a/agents/darkwing/work/queue-118/r1/probe/probe-run.log b/agents/darkwing/work/queue-118/r1/probe/probe-run.log new file mode 100644 index 000000000..846adf28b --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/probe/probe-run.log @@ -0,0 +1,92 @@ +git version 2.55.0 +######## probe.sh base 40 +fetch rc=0 +merge rc=0 +== clone +clone -q --no-hardlinks --template= --branch main /home/jwoltje/darkwing-scratch/r118/probe/run/root clone +== fetch +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone fetch -q --no-tags --no-recurse-submodules origin +pack-objects --revs --thin --stdout --delta-base-offset +unpack-objects -q --pack_header=2,3 +rev-list --objects --stdin --not --exclude-hidden=fetch --all --quiet --alternate-refs +maintenance run --auto --quiet --detach +repack -d -l --cruft --cruft-expiration=2.weeks.ago --quiet --write-midx +== merge +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +pack-objects --local --quiet --delta-base-offset --honor-pack-keep /home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git/objects/pack/.tmp-1051911-pack --keep-true-parents --non-empty --all --reflog --indexed-objects +maintenance run --auto --quiet --detach +waited 1 polls +== after wait +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +pack-objects --local --quiet --delta-base-offset --honor-pack-keep /home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git/objects/pack/.tmp-1051911-pack --keep-true-parents --non-empty --all --reflog --indexed-objects +maintenance run --auto --quiet --detach +pack-objects --local --quiet --delta-base-offset --honor-pack-keep /home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git/objects/pack/.tmp-1051911-pack --cruft --cruft-expiration=2.weeks.ago --non-empty +multi-pack-index write --no-progress --preferred-pack=pack-278d5b90829f42e6533f3a5ef862fe82968c7163.pack --refs-snapshot=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git/objects/bitmap-ref-tips_BxpKAa --stdin-packs +loose in 17: 0 packs: 2 +######## probe.sh base 0 +fetch rc=0 +merge rc=0 +== clone +clone -q --no-hardlinks --template= --branch main /home/jwoltje/darkwing-scratch/r118/probe/run/root clone +== fetch +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone fetch -q --no-tags --no-recurse-submodules origin +pack-objects --revs --thin --stdout --delta-base-offset +unpack-objects -q --pack_header=2,3 +rev-list --objects --stdin --not --exclude-hidden=fetch --all --quiet --alternate-refs +maintenance run --auto --quiet --detach +== merge +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +maintenance run --auto --quiet --detach +waited 1 polls +== after wait +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +maintenance run --auto --quiet --detach +loose in 17: 0 packs: 0 +######## probe.sh maonly 40 +fetch rc=0 +merge rc=0 +== clone +clone -q --no-hardlinks --template= --branch main /home/jwoltje/darkwing-scratch/r118/probe/run/root clone +== fetch +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone fetch -q --no-tags --no-recurse-submodules origin +pack-objects --revs --thin --stdout --delta-base-offset +unpack-objects -q --pack_header=2,3 +rev-list --objects --stdin --not --exclude-hidden=fetch --all --quiet --alternate-refs +== merge +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +waited 1 polls +== after wait +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +loose in 17: 40 packs: 0 +######## probe.sh gconly 40 +fetch rc=0 +merge rc=0 +== clone +clone -q --no-hardlinks --template= --branch main /home/jwoltje/darkwing-scratch/r118/probe/run/root clone +== fetch +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone fetch -q --no-tags --no-recurse-submodules origin +pack-objects --revs --thin --stdout --delta-base-offset +unpack-objects -q --pack_header=2,3 +rev-list --objects --stdin --not --exclude-hidden=fetch --all --quiet --alternate-refs +== merge +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +waited 1 polls +== after wait +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +loose in 17: 40 packs: 0 +######## probe.sh keys 40 +fetch rc=0 +merge rc=0 +== clone +clone -q --no-hardlinks --template= --branch main /home/jwoltje/darkwing-scratch/r118/probe/run/root clone +== fetch +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone fetch -q --no-tags --no-recurse-submodules origin +pack-objects --revs --thin --stdout --delta-base-offset +unpack-objects -q --pack_header=2,3 +rev-list --objects --stdin --not --exclude-hidden=fetch --all --quiet --alternate-refs +== merge +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +waited 1 polls +== after wait +--git-dir=/home/jwoltje/darkwing-scratch/r118/probe/run/clone/.git --work-tree=/home/jwoltje/darkwing-scratch/r118/probe/run/clone merge --ff-only -q origin/main +loose in 17: 40 packs: 0 diff --git a/agents/darkwing/work/queue-118/r1/probe/probe.sh b/agents/darkwing/work/queue-118/r1/probe/probe.sh new file mode 100755 index 000000000..54a0d620d --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/probe/probe.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Manual probe, not a suite: which host git calls in a clone start +# auto-maintenance on this git, and do the two -c keys stop it. +set -u +P=~/darkwing-scratch/r118/probe; R=$P/run; rm -rf "$R"; mkdir -p "$R"; cd "$R" || exit 9 +REAL=$(command -v git); EXEC=$(git --exec-path); W=$R/w; LOG=$R/calls; mkdir "$W" +for f in "$EXEC"/*; do b=${f##*/}; [ "$b" = git ] || ln -s "$f" "$W/$b"; done +printf '#!/bin/sh\nprintf "%%s\\n" "$*" >> %s\nGIT_EXEC_PATH=%s exec %s "$@"\n' "'$LOG'" "'$W'" "'$REAL'" > "$W/git"; chmod +x "$W/git" +E=(env -i "PATH=$W:/usr/bin:/bin" HOME=/nonexistent GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null) +SAFE=(-c core.hooksPath=/dev/null -c core.fsmonitor=false -c protocol.allow=never -c protocol.file.allow=always -c submodule.recurse=false) +U=(-c user.name=t -c user.email=t@x.invalid) +case "${1:-}" in maonly) SAFE+=(-c maintenance.auto=false);; keys) SAFE+=(-c maintenance.auto=false -c gc.auto=0);; gconly) SAFE+=(-c gc.auto=0);; esac +"${E[@]}" "$REAL" init -q -b main root; echo one > root/R; "${E[@]}" "$REAL" -C root add R; "${E[@]}" "$REAL" "${U[@]}" -C root commit -qm one +"${E[@]}" git "${SAFE[@]}" clone -q --no-hardlinks --template= --branch main "$R/root" clone +node "$P/loose.mjs" clone/.git/objects "${2:-40}" +echo two > root/R; "${E[@]}" "$REAL" "${U[@]}" -C root commit -qam two +: > "$LOG.before"; cp "$LOG" "$LOG.clone"; : > "$LOG" +G=(--git-dir="$R/clone/.git" --work-tree="$R/clone") +"${E[@]}" git "${SAFE[@]}" "${G[@]}" fetch -q --no-tags --no-recurse-submodules origin; echo "fetch rc=$?" +cp "$LOG" "$LOG.fetch"; : > "$LOG" +"${E[@]}" git "${SAFE[@]}" "${G[@]}" merge --ff-only -q origin/main; echo "merge rc=$?" +cp "$LOG" "$LOG.merge" +for s in clone fetch merge; do echo "== $s"; sed -E 's/-c [^ ]+ //g' "$LOG.$s"; done +# A detached maintenance may still be running; give it a moment by polling, not sleep-chaining. +busy() { for p in /proc/[0-9]*; do [ "$(readlink $p/cwd 2>/dev/null)" = "$R/clone" ] && return 0; done; return 1; } +for i in $(seq 300); do busy || break; read -t 0.1 <> <(:) 2>/dev/null || true; done; echo "waited ${i} polls" +echo "== after wait"; sed -E 's/-c [^ ]+ //g' "$LOG" +echo "loose in 17: $(ls clone/.git/objects/17 2>/dev/null | wc -l) packs: $(ls clone/.git/objects/pack/*.pack 2>/dev/null | wc -l)" diff --git a/agents/darkwing/work/queue-118/r1/repro.sh b/agents/darkwing/work/queue-118/r1/repro.sh new file mode 100755 index 000000000..bcef03640 --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/repro.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Rebuild row 118's candidate: export BASE, apply build.patch, check every +# digest in candidate-manifest.sha256. Run from the packet directory. +set -euo pipefail +BASE=2727dd3ec4526fe899893ea15f04fa1b9bf0033c +HERE=$(cd "$(dirname "$0")" && pwd) +X=$(mktemp -d) +trap 'rm -rf "$X"' EXIT +git -C /mnt/storage/src/mosaic-stack archive "$BASE" | tar -x -C "$X" +cd "$X" +git apply "$HERE/build.patch" +sha256sum -c --quiet "$HERE/candidate-manifest.sha256" && echo DIGESTS_OK diff --git a/agents/darkwing/work/queue-118/r1/sweep.log b/agents/darkwing/work/queue-118/r1/sweep.log new file mode 100644 index 000000000..e9758662a --- /dev/null +++ b/agents/darkwing/work/queue-118/r1/sweep.log @@ -0,0 +1,48 @@ +2026-10-14T19:38:08Z leftover check after both mutant runs +mutants.py sweep lines (processes with cwd under ~/darkwing-scratch/r118/tmp killed after each mutant): + none in mutants-run1.log or mutants-run.log +/proc cwd scan under ~/darkwing-scratch/r118/tmp: + 0 found +pgrep -u 1000 -f 'maintenance run': + none + +2026-10-14T19:38:23Z temp dirs left under ~/darkwing-scratch/r118/tmp (mtimes UTC): +== mosaic-checkout-9uvaVf 2026-10-14 19:37:00 + mosaic-checkout-9uvaVf + mosaic-checkout-9uvaVf/ws + mosaic-checkout-9uvaVf/ws/stack + mosaic-checkout-9uvaVf/ws/stack/.git + mosaic-checkout-9uvaVf/ws/stack/.git/objects + mosaic-checkout-9uvaVf/ws/stack/.git/objects/pack + mosaic-checkout-9uvaVf/ws/stack/.git/objects/pack/tmp_rev_fZDR1P +== mosaic-checkout-ukID6e 2026-10-14 19:36:28 + mosaic-checkout-ukID6e + mosaic-checkout-ukID6e/ws + mosaic-checkout-ukID6e/ws/stack + mosaic-checkout-ukID6e/ws/stack/.git + mosaic-checkout-ukID6e/ws/stack/.git/objects + mosaic-checkout-ukID6e/ws/stack/.git/objects/pack + mosaic-checkout-ukID6e/ws/stack/.git/objects/pack/tmp_rev_GKsy0V +== mosaic-launch-akvkAj 2026-10-14 19:37:16 + mosaic-launch-akvkAj + mosaic-launch-akvkAj/data + mosaic-launch-akvkAj/data/bus-host + mosaic-launch-akvkAj/data/launches + mosaic-launch-akvkAj/data/bus-host/sessions.json + mosaic-launch-akvkAj/data/launches/acme.jsonl +== mosaic-launch-ss1V05 2026-10-14 19:37:20 + mosaic-launch-ss1V05 + mosaic-launch-ss1V05/data + mosaic-launch-ss1V05/data/bus-host + mosaic-launch-ss1V05/data/launches + mosaic-launch-ss1V05/data/bus-host/sessions.json + mosaic-launch-ss1V05/data/launches/acme.jsonl +== mosaic-launch-vievmU 2026-10-14 19:37:22 + mosaic-launch-vievmU + mosaic-launch-vievmU/data + mosaic-launch-vievmU/data/bus-host + mosaic-launch-vievmU/data/launches + mosaic-launch-vievmU/data/bus-host/sessions.json + mosaic-launch-vievmU/data/launches/acme.jsonl + +2026-10-14T19:40:50Z (time of this note) the 5 directories above were removed after the listing; ~/darkwing-scratch/r118/tmp now holds 0 entries