diff --git a/agents/darkwing/work/queue-117/r1/build.md b/agents/darkwing/work/queue-117/r1/build.md new file mode 100644 index 000000000..babefd3e9 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/build.md @@ -0,0 +1,114 @@ +# Row 117 (#1611) round 1: fetch a role clone that is on another branch or dirty + +Brief: docs/plans/2026-10-14_clone-fetch-off-branch.md (559ef2287), "Fetch a role clone that is on another branch or dirty". On 2026-10-14 the coder sat on a work branch and its clone's `origin/refactor` stayed several landings old, because `update()` ran no git at all in a clone it kept for `other-branch` or `dirty`. + +BASE: 559ef2287. The candidate is build.patch against BASE, and candidate-manifest.sha256 lists the 8 files. I applied the patch to a fresh `git archive 559ef2287` export and every digest matched (DIGESTS_OK). The worktree still matched the manifest after the mutants and before the gate. + +## What changed + +### update(), packages/seat/src/checkout.mjs + +Everything up to the root-detached check is unchanged: `unsafe()`, the `ls-files` gitlink check and the root reads. Nothing in `unsafe()`, `allowed()`, `gitEnv()` or `SAFE` changed. After the root checks, the new tail does this: + +1. It reads the branch with `symbolic-ref -q --short HEAD`. A detached HEAD fails that call, so `branch` is null, and null is never the root's branch, so the reason is `other-branch`. +2. On the root's branch it runs the same `status` call as before. A failed status still returns `kept`, `status-failed`, with no fetch. Output from status means `dirty`. +3. It reads HEAD, then runs the clean path's fetch with the same arguments and environment. A failed fetch is `kept`, `fetch-failed`, for any clone. +4. With a reason set, it reads `refs/remotes/origin/` and returns `fetched`. No merge runs. +5. A clean clone on the root's branch merges `--ff-only` exactly as before. + +The `fetched` result is `{ outcome: "fetched", reason, branch, commit, origin: { branch, commit } }`: + +| Field | Value | +|---|---| +| `reason` | `other-branch` or `dirty` | +| `branch` | the clone's branch, null on a detached HEAD | +| `commit` | the clone's HEAD, read before the fetch (a fetch doesn't move HEAD), null when unreadable | +| `origin.branch` | the root's branch | +| `origin.commit` | `refs/remotes/origin/` after the fetch, null when unreadable | + +Choices I made: + +- I used the brief's shape. `origin` is always an object, because by this point the root's branch is known. Only `origin.commit` can be null. +- `commit` is now null when `rev-parse HEAD` fails. Before this change the code used `.out.trim()` without checking `.ok`, and git prints the unresolved argument on a failure. That could only matter for an unborn HEAD, which a clone made by `git clone` of a root with a commit doesn't have. +- A failed status stays `kept` with no fetch. It isn't one of the brief's two reasons, and "Only those two reasons fetch". + +### The launcher, packages/cli/src/launcher.mjs + +After `ensureCheckouts`, a `fetched` clone writes `{ event: "checkout-fetched", instance, project, reason }` to the launch log, next to `checkout-kept`. The header comment says so. + +### The prompt, packages/harness/src/bundle.mjs + +`checkoutLines()` gives a fetched clone the started line, then `, fetched but not fast-forwarded at this launch (), with origin/ at commit `. A null branch reads "on no branch", and a null origin commit reads "at a commit the host couldn't read", using the same `at()` helper as before. The shared sentence now says the host fetches each clone it can safely run git in and fast-forwards only a clean clone on the root's branch. The rest of it (never during a session, the origin is outside the sandbox, no git config, identity from the environment) is unchanged. + +### READMEs + +- packages/seat/README.md, "The project clones". The outcome table gets the `fetched` row, and the result paragraph describes `origin`. The reason table marks `other-branch` and `dirty` as fetched and says every other reason runs no fetch. A failed fetch on either of those clones is `kept`, `fetch-failed`. +- packages/cli/README.md, "Sessions". A `checkout-fetched` line with the project and the reason, then the existing `checkout-kept` sentence. + +packages/cli/tests/host.test.mjs:826 reads packages/cli/README.md, so that README is not doc-only under decision 137. `unslop-check` is clean on both READMEs. + +## Tests + +### packages/seat/tests/checkout.test.mjs + +The old "a dirty clone, one on another branch and one diverged are kept as they are" test becomes the first test below, as the brief asks. Its diverged case moves to its own test, because a diverged clone is still `kept`. + +| Test | Checks | +|---|---| +| a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves | 5 cases: a modified, an untracked and a staged file (`dirty`, branch main), another branch (`other-branch`, work), a detached HEAD (`other-branch`, null). After a commit to the root: the whole result by deepEqual, `refs/remotes/origin/main` is the root's new commit, and `work()` is unchanged. `work()` hashes `.git/HEAD`, every `refs/heads/*` ref, `.git/index` and every file outside `.git`, the brief's narrowed fingerprint | +| a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward | `kept`, `not-fast-forward`, and `refs/remotes/origin/main` is the root's new commit | +| a clone kept before the branch check, or for a failed status, gets no fetch | 8 cases through a PATH git wrapper that logs each call run in the clone. `not-a-clone`, `hooks-present` and `config-changed` run nothing in the clone. `submodule`, `root-unreadable` and `root-detached` run only `ls-files`. Both `status-failed` cases, clean and dirty, run `ls-files`, `symbolic-ref`, `status`. Each case checks the reason and the exact call list, and that `refs/remotes/origin/main` didn't move | +| a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was | the wrapper fails `fetch`. `kept`, `fetch-failed`, no merge call, and the full fingerprint, `.git` included, is unchanged | + +The rigged-clone test is unchanged and passes. + +How I read the brief on "still runs no git, the fetch included": the gitlink check is itself the `ls-files` call, so `submodule` can't be found without it. `root-unreadable` and `root-detached` come after it, and their own reads run in the root, not the clone. Those three run `ls-files` and nothing else, as they did at BASE. The test pins the exact call list so a fetch, or any other call, fails it. If Dewey reads the brief as asking for literally zero calls in those three cases, that's a change to the check order, which the brief rules out. + +The fingerprint helper gets a `skip` list of top-level names, so `work()` can hash the files outside `.git`. + +### packages/cli/tests/launcher.test.mjs + +"each launch makes sure the workspace has a clone of the project" now ends "cloned, fast-forwarded, or fetched and logged". The first two launches log no `checkout-fetched`. The third launch has a dirty `notes.txt`. It now checks that HEAD stays at two while `refs/remotes/origin/main` is three, that the log has `checkout-fetched` `[{ instance: "pm", project: "stack", reason: "dirty" }]` and no `checkout-kept`, and that the prompt has the fetched line with origin/main at three. + +### packages/harness/tests/bundle.test.mjs + +The checkout test adds two entries. `lib` is fetched, dirty, on main, with an origin commit. `app` is fetched, other-branch, with a null branch and a null origin commit. It checks the new shared sentence and both new lines exactly. + +## Mutants + +mutants.py runs each mutant against the full seat suite (`node --test tests/*.test.mjs` in packages/seat, 130 tests) and restores checkout.mjs from a `.orig` copy after each. Log: mutants-run.log. Each mutant's full output is in mutants/mutant-.log, and the failing test names are listed under each mutant in mutants-run.log. + +| Mutant | Change | Result | +|---|---|---| +| m1, fetch for a third reason | a failed status sets `reason = "status-failed"` instead of returning `kept` | killed, 3 fail: both `status-failed` cases of "gets no fetch" (they see `fetch`) and the parent test | +| m2, merge after the fetch | inside `if (reason)`, the clean path's `merge --ff-only` runs before the return | killed, 5 fail: untracked, staged, another branch and detached, plus the parent. The modified-file case passes, because git refuses that merge over a local change to README | +| m3, drop the fetch | the fetch runs only when `reason` is unset | killed, 7 fail: all 5 fetched cases and the parent (`origin/main` stays at the old commit), and "whose fetch fails" (no fetch, so the result is `fetched`) | +| m4, wrong origin commit | `origin.commit` is the clone's HEAD (`before`) | killed, 6 fail: all 5 fetched cases and the parent, on the deepEqual | +| m5, fetch before the gitlink check | the fetch runs right after `unsafe()`, before `ls-files` | killed, 6 fail: `submodule`, `root-unreadable`, `root-detached`, both `status-failed` cases and the parent, on the call list | + +All five were restored, and `git diff --stat` matched the candidate afterwards (8 files, 183+, 52-). + +## Gate + +This is a targeted builder round under Sage's rule (#271, sage-0180): the changed packages, the packages that import them, and every scripts/test-*.sh that reads a changed path. Sage's landing gate stays full. + +- Changed packages: seat, cli, harness. +- Importers: none outside those three. Nothing else references checkout.mjs, launcher.mjs or bundle.mjs (grep over packages/*/src, packages/*/tests and scripts; Sage checked this too, sage-0188). +- Scripts: no scripts/test-*.sh reads a changed path. test-task.sh says "seat" only inside skill names. test-conductor.sh and test-extension-package.sh match the basename README.md, but only for their own sandbox files. Sage asked me to keep those two anyway, so they ran. +- No pkg-queue step, per #271. + +gate.sh ran these steps in order, each teed. GATE_PASSED at 17:30:33Z, run 17:29:52 to 17:30:33. Log: gate-run.log, per-step logs under gate/. + +| Step | Pass | +|---|---| +| seat | 130 (120 at BASE: the old test and its 6 cases are 7, the four new tests and their cases are 17) | +| cli | 184 | +| harness | 81 | +| scripts/test-conductor.sh | 17 | +| scripts/test-extension-package.sh | 18 | + +There were no failures and no flakes. The busy check was clear before the mutants and before the gate. Afterwards no process had a cwd under my r117 directory, and nothing created a Docker network. + +## Going live + +This change runs in the host at launch, so decision 109 applies. It goes live only through a stack copy and a re-pin on Mos's go. I start, stop and restart nothing. diff --git a/agents/darkwing/work/queue-117/r1/build.patch b/agents/darkwing/work/queue-117/r1/build.patch new file mode 100644 index 000000000..9244ebbc2 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/build.patch @@ -0,0 +1,413 @@ +diff --git a/packages/cli/README.md b/packages/cli/README.md +index e23d8fb0d..4c05b4213 100644 +--- a/packages/cli/README.md ++++ b/packages/cli/README.md +@@ -273,8 +273,10 @@ the launcher reads the instance's credential status from the broker + (metadata only) and makes sure the instance's workspace has a clone of each + project in the business file (`packages/seat/README.md`, "The project + clones"). It makes a missing clone and fast-forwards a clean one. A clone +-it leaves alone is a `checkout-kept` line in the launch log with the +-project and the reason, and the launch goes on. The bundle's prompt names ++on another branch or dirty is fetched and not merged, a `checkout-fetched` ++line in the launch log with the project and the reason (#1611). A clone it ++leaves alone is a `checkout-kept` line with the project and the reason. ++Either way the launch goes on. The bundle's prompt names + each clone's path. Then the launcher builds the bundle in the run + directory, spawns the runner in its bwrap sandbox (`packages/seat/README.md`, + "The sandbox"), binds the outer bwrap process's pid and start time, adds the +diff --git a/packages/cli/src/launcher.mjs b/packages/cli/src/launcher.mjs +index 8eb6d902a..574614a64 100644 +--- a/packages/cli/src/launcher.mjs ++++ b/packages/cli/src/launcher.mjs +@@ -26,11 +26,12 @@ + // action.refused event; step 9's refusals the broker records itself. Then + // the host makes sure the workspace has a clone of each project + // (packages/seat/src/checkout.mjs): one it can't make refuses +-// checkout-failed, and one it leaves alone is a checkout-kept record in the +-// launch log. Then it builds the bundle, spawns the runner in its sandbox +-// (packages/seat/src/sandbox.mjs) with the paths it may see, binds the run's +-// process identity (the outer bwrap pid) and writes the new capability on +-// the runner's stdin. When the runner exits, the host ends the launch. ++// checkout-failed, one it fetches but doesn't fast-forward is a ++// checkout-fetched record in the launch log, and one it leaves alone is a ++// checkout-kept record. Then it builds the bundle, spawns the runner in its ++// sandbox (packages/seat/src/sandbox.mjs) with the paths it may see, binds ++// the run's process identity (the outer bwrap pid) and writes the new ++// capability on the runner's stdin. When the runner exits, the host ends the launch. + // + // `bus start --pm` launches launch.by the same way with + // launchedBy = the business's human and no caller capability. +@@ -196,7 +197,10 @@ export function createLauncher({ + if (e instanceof CheckoutError) throw new Refusal("checkout-failed", `${e.project}: ${e.message}`); + throw e; + } +- for (const c of checkouts) if (c.outcome === "kept") record({ event: "checkout-kept", instance, project: c.project, reason: c.reason }); ++ for (const c of checkouts) { ++ if (c.outcome === "kept") record({ event: "checkout-kept", instance, project: c.project, reason: c.reason }); ++ else if (c.outcome === "fetched") record({ event: "checkout-fetched", instance, project: c.project, reason: c.reason }); ++ } + const { run, runDir } = newRun(dataRoot, b.id); + const sessionDir = join(runDir, "session"); + mkdirSync(sessionDir, { mode: 0o700 }); +diff --git a/packages/cli/tests/launcher.test.mjs b/packages/cli/tests/launcher.test.mjs +index 80d945dc2..b7fef78ad 100644 +--- a/packages/cli/tests/launcher.test.mjs ++++ b/packages/cli/tests/launcher.test.mjs +@@ -281,12 +281,14 @@ test("the PM launches a coder through its launch tool; the coder answers; refusa + }); + + // Row 91 (#1585): the project clone in each workspace. +-test("each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or kept and logged", { skip, timeout: 60000 }, async (t) => { ++// Row 117 (#1611): and a dirty one is fetched, not merged, and logged. ++test("each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged", { skip, timeout: 60000 }, async (t) => { + const given = prepare(t); + const project = given.f.doc.projects.stack.root; + const clone = join(workspaceDir(given.f.dataRoot, "acme", "pm"), "stack"); + const prompt = (run) => readFileSync(join(given.f.dataRoot, "launches", "acme", run, "bundle", "prompt.md"), "utf8"); + const kept = () => log(given.f).filter((e) => e.event === "checkout-kept"); ++ const fetched = () => log(given.f).filter((e) => e.event === "checkout-fetched"); + + // The first launch clones the root's branch, with the root as origin. + const first = await setup(t, undefined, given); +@@ -311,18 +313,22 @@ test("each launch makes sure the workspace has a clone of the project: cloned, f + assert.equal(gitIn(clone, "rev-parse", "HEAD"), two); + assert.ok(prompt(pm2.run).includes(`- Project clones: stack at ${clone}, on branch main at commit ${two} when this session started. `)); + assert.deepEqual(kept(), []); ++ assert.deepEqual(fetched(), []); + await second.client.call("role.release"); + assert.equal(await second.close(), 0); + +- // A clone with the session's own work in it is kept, and the log says why. ++ // A clone with the session's own work in it is fetched and not merged, ++ // and the log says why. + writeFileSync(join(clone, "notes.txt"), "mine\n"); + const three = commit(project, "three"); + const third = await setup(t, undefined, given); + const pm3 = await third.launcher.launchPm(); + assert.equal(gitIn(clone, "rev-parse", "HEAD"), two); ++ assert.equal(gitIn(clone, "rev-parse", "refs/remotes/origin/main"), three); + assert.equal(readFileSync(join(clone, "notes.txt"), "utf8"), "mine\n"); +- assert.deepEqual(kept().map(({ instance, project: p, reason }) => ({ instance, project: p, reason })), [{ instance: "pm", project: "stack", reason: "dirty" }]); +- assert.ok(prompt(pm3.run).includes(`- Project clones: stack at ${clone}, not updated at this launch (dirty). `)); ++ assert.deepEqual(fetched().map(({ instance, project: p, reason }) => ({ instance, project: p, reason })), [{ instance: "pm", project: "stack", reason: "dirty" }]); ++ assert.deepEqual(kept(), []); ++ assert.ok(prompt(pm3.run).includes(`- Project clones: stack at ${clone}, on branch main at commit ${two} when this session started, fetched but not fast-forwarded at this launch (dirty), with origin/main at commit ${three}. `)); + assert.equal(await third.close(), 0); + + // The root was never written: its branch, head and tree are the test's. +diff --git a/packages/harness/src/bundle.mjs b/packages/harness/src/bundle.mjs +index 478ef0a03..0e4d63148 100644 +--- a/packages/harness/src/bundle.mjs ++++ b/packages/harness/src/bundle.mjs +@@ -58,11 +58,18 @@ export function sessionModel(resolved) { + // Row 91 (#1585): the project clones the host made or kept in the workspace. + // Row 104 (#1598): the commit each was at when the session started, when it + // refreshes, and why the session must not set git config in it. ++// Row 117 (#1611): a fetched clone's branch, why it wasn't fast-forwarded, ++// and where origin/ is. + function checkoutLines(checkouts) { + if (!checkouts.length) return []; + const at = (c) => (c.commit ? `commit ${c.commit}` : "a commit the host couldn't read"); +- const each = checkouts.map((c) => (c.outcome === "kept" ? `${c.project} at ${c.path}, not updated at this launch (${c.reason})` : `${c.project} at ${c.path}, on branch ${c.branch} at ${at(c)} when this session started`)); +- return [`- Project clones: ${each.join("; ")}. Each is a git clone of the project. The host fetches from its origin and fast-forwards a clean clone only at a launch, never during a session; the origin is outside this sandbox. Don't set git config in a clone. Any key that \`git clone\` didn't write, user.name and user.email included, makes every later launch keep that clone as it is. Your commits already carry your role instance's name and address from the environment.`]; ++ const one = (c) => { ++ if (c.outcome === "kept") return `${c.project} at ${c.path}, not updated at this launch (${c.reason})`; ++ const started = `${c.project} at ${c.path}, ${c.branch ? `on branch ${c.branch}` : "on no branch"} at ${at(c)} when this session started`; ++ if (c.outcome !== "fetched") return started; ++ return `${started}, fetched but not fast-forwarded at this launch (${c.reason}), with origin/${c.origin.branch} at ${at(c.origin)}`; ++ }; ++ return [`- Project clones: ${checkouts.map(one).join("; ")}. Each is a git clone of the project. At a launch the host fetches each clone it can safely run git in from its origin, and fast-forwards only a clean clone on the root's branch, never during a session; the origin is outside this sandbox. Don't set git config in a clone. Any key that \`git clone\` didn't write, user.name and user.email included, makes every later launch keep that clone as it is. Your commits already carry your role instance's name and address from the environment.`]; + } + + function prompt({ contract, resolved, session, tools, untyped }) { +diff --git a/packages/harness/tests/bundle.test.mjs b/packages/harness/tests/bundle.test.mjs +index 51555415f..b9330030e 100644 +--- a/packages/harness/tests/bundle.test.mjs ++++ b/packages/harness/tests/bundle.test.mjs +@@ -105,6 +105,7 @@ test("the prompt's built-in tools line is the harness's own names, as the manife + + // Row 91 (#1585): the prompt names each project clone, and why a kept one wasn't updated. + // Row 104 (#1598): and the commit each was at, when it refreshes, and the git config warning. ++// Row 117 (#1611): and a fetched clone's branch, reason and origin/. + test("the prompt names the project clones in the workspace, and none when there are none", (t) => { + const line = (b) => readFileSync(b.files.prompt, "utf8").split("\n").filter((l) => l.startsWith("- Project clones: ")); + const plain = build(t, resolvedFor(t, "pm")); +@@ -115,9 +116,11 @@ test("the prompt names the project clones in the workspace, and none when there + { project: "stack", path: join(dir, "ws", "stack"), outcome: "updated", branch: "main", commit: "a".repeat(40) }, + { project: "site", path: join(dir, "ws", "site"), outcome: "kept", reason: "dirty" }, + { project: "docs", path: join(dir, "ws", "docs"), outcome: "current", branch: "dev", commit: null }, ++ { project: "lib", path: join(dir, "ws", "lib"), outcome: "fetched", reason: "dirty", branch: "main", commit: "b".repeat(40), origin: { branch: "main", commit: "c".repeat(40) } }, ++ { project: "app", path: join(dir, "ws", "app"), outcome: "fetched", reason: "other-branch", branch: null, commit: "d".repeat(40), origin: { branch: "main", commit: null } }, + ]) }); +- const after = "Each is a git clone of the project. The host fetches from its origin and fast-forwards a clean clone only at a launch, never during a session; the origin is outside this sandbox. Don't set git config in a clone. Any key that `git clone` didn't write, user.name and user.email included, makes every later launch keep that clone as it is. Your commits already carry your role instance's name and address from the environment."; +- assert.deepEqual(line(two), [`- Project clones: stack at ${join(dir, "ws", "stack")}, on branch main at commit ${"a".repeat(40)} when this session started; site at ${join(dir, "ws", "site")}, not updated at this launch (dirty); docs at ${join(dir, "ws", "docs")}, on branch dev at a commit the host couldn't read when this session started. ${after}`]); ++ const after = "Each is a git clone of the project. At a launch the host fetches each clone it can safely run git in from its origin, and fast-forwards only a clean clone on the root's branch, never during a session; the origin is outside this sandbox. Don't set git config in a clone. Any key that `git clone` didn't write, user.name and user.email included, makes every later launch keep that clone as it is. Your commits already carry your role instance's name and address from the environment."; ++ assert.deepEqual(line(two), [`- Project clones: stack at ${join(dir, "ws", "stack")}, on branch main at commit ${"a".repeat(40)} when this session started; site at ${join(dir, "ws", "site")}, not updated at this launch (dirty); docs at ${join(dir, "ws", "docs")}, on branch dev at a commit the host couldn't read when this session started; lib at ${join(dir, "ws", "lib")}, on branch main at commit ${"b".repeat(40)} when this session started, fetched but not fast-forwarded at this launch (dirty), with origin/main at commit ${"c".repeat(40)}; app at ${join(dir, "ws", "app")}, on no branch at commit ${"d".repeat(40)} when this session started, fetched but not fast-forwarded at this launch (other-branch), with origin/main at a commit the host couldn't read. ${after}`]); + const prompt = readFileSync(two.files.prompt, "utf8"); + assert.ok(prompt.indexOf("- Workspace: ") < prompt.indexOf("- Project clones: ")); + assert.deepEqual(line(build(t, resolvedFor(t, "pm"), { session: session([]) })), []); +diff --git a/packages/seat/README.md b/packages/seat/README.md +index 7ef200400..ee7afbeb6 100644 +--- a/packages/seat/README.md ++++ b/packages/seat/README.md +@@ -259,6 +259,7 @@ outside the sandbox: + |---|---|---| + | doesn't exist | `git clone --no-hardlinks --template=` of the root, at the branch the root has checked out, into `..clone-` beside it, then renamed into place | `cloned` | + | is clean, on the root's branch | fetches its origin and fast-forwards | `updated`, or `current` when nothing moved | ++| passes the checks below, but is on another branch or none, or dirty | fetches its origin and doesn't merge, so only `refs/remotes` moves (#1611) | `fetched`, with the reason | + | anything else | nothing | `kept`, with a reason | + + The host never resets, cleans, deletes or force-updates a clone, and never +@@ -268,12 +269,19 @@ reach it. + + Each result also has the clone's branch and, for `cloned`, `updated` and + `current`, the commit it is at after the launch (`null` when git can't read +-it). A kept clone has neither, and the host runs no more git in it to find +-one. The bundle's prompt names the branch and commit, says the host +-refreshes a clone only at a launch, and tells the session not to set git +-config in its clone (#1598). +- +-A kept clone's reason is one of: ++it). A fetched clone has its own branch (`null` on a detached HEAD), its ++commit, its reason, and `origin`: the root's branch and the commit ++`refs/remotes/origin/` is at after the fetch (`null` when git can't ++read it). A kept clone has only its reason, and the host runs no more git ++in it to find more. The bundle's prompt names the branch and commit, says ++why a fetched clone wasn't fast-forwarded and where `origin/` is, ++says the host fetches every clone it can safely run git in at a launch and ++fast-forwards only a clean one on the root's branch, and tells the session ++not to set git config in its clone (#1598). ++ ++A kept or fetched clone's reason is one of the following. Only ++`other-branch` and `dirty` fetch, and only once every check above them has ++passed; every other reason runs no fetch. + + | Reason | The clone | + |---|---| +@@ -282,9 +290,9 @@ A kept clone's reason is one of: + | `submodule` | has a `.git/modules`, or a gitlink (a submodule entry) in its index | + | `config-changed` | has a `.git/config` that isn't a regular file of at most 64 KiB, or that holds a key a clone doesn't write: anything beyond `core.repositoryformatversion`, `core.filemode`, `core.bare`, `core.logallrefupdates`, the origin's one URL (the root) and fetch line, and `branch..remote` (`origin`) and `.merge` | + | `root-unreadable`, `root-detached` | is fine, but the root can't be read or has no branch checked out | +-| `other-branch` | is on another branch, or on none | +-| `dirty` | has a change, staged or not, or an untracked file | +-| `status-failed`, `fetch-failed` | made `git ls-files`, `git status` or the fetch fail | ++| `other-branch` | is on another branch, or on none; `fetched` | ++| `dirty` | has a change, staged or not, or an untracked file; `fetched` | ++| `status-failed`, `fetch-failed` | made `git ls-files`, `git status` or the fetch fail; a clone on another branch or dirty whose fetch fails is `kept`, `fetch-failed` | + | `not-fast-forward` | has commits the root's branch doesn't | + + The session can write its clone, so the host treats every clone as +diff --git a/packages/seat/src/checkout.mjs b/packages/seat/src/checkout.mjs +index 20c767082..03081f5b5 100644 +--- a/packages/seat/src/checkout.mjs ++++ b/packages/seat/src/checkout.mjs +@@ -9,6 +9,8 @@ + // clone that fails is removed and refuses the launch + // a clean clone on fetched from its origin and fast-forwarded + // the root's branch ++// a clone on another fetched from its origin and not merged: only ++// branch, or dirty refs/remotes moves, with the reason (#1611) + // anything else left exactly as it is, with the reason + // + // The host never resets, cleans, deletes or force-updates a clone, and +@@ -252,13 +254,25 @@ async function update(root, dest, g) { + const at = await rootBranch(root, g); + if (at.error) return { outcome: "kept", reason: "root-unreadable" }; + if (!at.branch) return { outcome: "kept", reason: "root-detached" }; ++ // Row 117 (#1611): a clone on another branch, or dirty, still gets the ++ // fetch, so origin/ is the root's at every launch. Only the merge ++ // is left out. symbolic-ref -q fails on a detached HEAD: branch null. + const head = await g([...inClone(dest), "symbolic-ref", "-q", "--short", "HEAD"]); +- if (!head.ok || head.out.trim() !== at.branch) return { outcome: "kept", reason: "other-branch" }; +- const status = await g(["--no-optional-locks", ...inClone(dest), "status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=all"]); +- if (!status.ok) return { outcome: "kept", reason: "status-failed" }; +- if (status.out !== "") return { outcome: "kept", reason: "dirty" }; +- const before = (await g([...inClone(dest), "rev-parse", "HEAD"])).out.trim(); ++ const branch = head.ok ? head.out.trim() : null; ++ let reason = null; ++ if (branch !== at.branch) reason = "other-branch"; ++ else { ++ const status = await g(["--no-optional-locks", ...inClone(dest), "status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignore-submodules=all"]); ++ if (!status.ok) return { outcome: "kept", reason: "status-failed" }; ++ if (status.out !== "") reason = "dirty"; ++ } ++ const first = await g([...inClone(dest), "rev-parse", "HEAD"]); ++ const before = first.ok ? first.out.trim() : null; + if (!(await g([...inClone(dest), "fetch", "-q", "--no-tags", "--no-recurse-submodules", "origin"])).ok) return { outcome: "kept", reason: "fetch-failed" }; ++ if (reason) { ++ const origin = await g([...inClone(dest), "rev-parse", "--verify", "-q", `refs/remotes/origin/${at.branch}`]); ++ return { outcome: "fetched", reason, branch, commit: before, origin: { branch: at.branch, commit: origin.ok ? origin.out.trim() : null } }; ++ } + if (!(await g([...inClone(dest), "merge", "-q", "--ff-only", "--no-edit", `refs/remotes/origin/${at.branch}`])).ok) return { outcome: "kept", reason: "not-fast-forward" }; + const read = await g([...inClone(dest), "rev-parse", "HEAD"]); + const after = read.out.trim(); +@@ -267,12 +281,15 @@ async function update(root, dest, g) { + + // await ensureCheckouts({ workspace, projects, env, limits }) for the + // business file's projects ({ : { root } }). Returns one { project, +-// path, outcome, reason?, branch?, commit? } per project, outcome cloned, +-// updated, current or kept. branch and commit are the clone's after this +-// launch (commit null when git couldn't read it); a kept clone has neither, +-// since git may not be safe to run there. Rejects with CheckoutError when a +-// missing clone can't be made. `limits` ({ timeoutMs, graceMs }) is for the +-// tests. ++// path, outcome, reason?, branch?, commit?, origin? } per project, outcome ++// cloned, updated, current, fetched or kept. branch and commit are the ++// clone's after this launch (commit null when git couldn't read it). A ++// fetched clone also has a reason (other-branch or dirty), branch null when ++// its HEAD is detached, and origin { branch, commit }: the root's branch and ++// refs/remotes/origin/ after the fetch, commit null when unreadable. ++// A kept clone has only a reason, since git may not be safe to run there. ++// Rejects with CheckoutError when a missing clone can't be made. `limits` ++// ({ timeoutMs, graceMs }) is for the tests. + export async function ensureCheckouts({ workspace, projects, env = process.env, limits = GIT_LIMITS }) { + const g = (args, extra) => git(args, env, extra, limits); + const out = []; +diff --git a/packages/seat/tests/checkout.test.mjs b/packages/seat/tests/checkout.test.mjs +index 6e06f23a8..a3751df01 100644 +--- a/packages/seat/tests/checkout.test.mjs ++++ b/packages/seat/tests/checkout.test.mjs +@@ -45,11 +45,12 @@ function commit(root, text) { + } + + // Every path under `dir` with its type, mode, link count and contents or +-// link target. +-function fingerprint(dir) { ++// link target, but for any `skip` names at its top. ++function fingerprint(dir, skip = []) { + const h = createHash("sha256"); + const walk = (d) => { + for (const name of readdirSync(d).sort()) { ++ if (d === dir && skip.includes(name)) continue; + const p = join(d, name); + const st = lstatSync(p); + h.update(`${p}\0${st.mode}\0${st.nlink}\0`); +@@ -131,31 +132,114 @@ test("a clone ahead of the root keeps its commits and counts as current", async + assert.equal(git(w.clone, "rev-parse", "HEAD"), mine); + }); + +-test("a dirty clone, one on another branch and one diverged are kept as they are", async (t) => { +- for (const [name, edit, reason] of [ +- ["a modified file", (c) => writeFileSync(join(c, "README"), "mine\n"), "dirty"], +- ["an untracked file", (c) => writeFileSync(join(c, "NEW"), "mine\n"), "dirty"], +- ["a staged file", (c) => (writeFileSync(join(c, "NEW"), "x"), git(c, "add", "NEW")), "dirty"], +- ["another branch", (c) => git(c, "checkout", "-q", "-b", "work"), "other-branch"], +- ["a detached HEAD", (c) => git(c, "checkout", "-q", "--detach"), "other-branch"], +- ["a diverged branch", (c) => commit(c, "mine\n"), "not-fast-forward"], ++// HEAD, every local branch, the index and every path outside .git. A fetch ++// may write objects, refs/remotes, FETCH_HEAD and packed-refs, none of these. ++function work(clone) { ++ const h = createHash("sha256"); ++ h.update(readFileSync(join(clone, ".git", "HEAD"))); ++ h.update(git(clone, "for-each-ref", "--format=%(refname) %(objectname)", "refs/heads")); ++ h.update(readFileSync(join(clone, ".git", "index"))); ++ h.update(fingerprint(clone, [".git"])); ++ return h.digest("hex"); ++} ++ ++// Row 117 (#1611): a clone on another branch, or dirty, is fetched and not ++// merged. Only its refs/remotes moves. ++test("a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves", async (t) => { ++ for (const [name, edit, reason, branch] of [ ++ ["a modified file", (c) => writeFileSync(join(c, "README"), "mine\n"), "dirty", "main"], ++ ["an untracked file", (c) => writeFileSync(join(c, "NEW"), "mine\n"), "dirty", "main"], ++ ["a staged file", (c) => (writeFileSync(join(c, "NEW"), "x"), git(c, "add", "NEW")), "dirty", "main"], ++ ["another branch", (c) => git(c, "checkout", "-q", "-b", "work"), "other-branch", "work"], ++ ["a detached HEAD", (c) => git(c, "checkout", "-q", "--detach"), "other-branch", null], + ]) { + await t.test(name, async (t) => { + const w = world(t); + await once(w); + edit(w.clone); +- commit(w.root, "two\n"); ++ const two = commit(w.root, "two\n"); + const head = git(w.clone, "rev-parse", "HEAD"); +- const before = reason === "not-fast-forward" ? null : fingerprint(w.clone); +- assert.deepEqual(await once(w), { project: "stack", path: w.clone, outcome: "kept", reason }); ++ const before = work(w.clone); ++ assert.deepEqual(await once(w), { project: "stack", path: w.clone, outcome: "fetched", reason, branch, commit: head, origin: { branch: "main", commit: two } }); ++ assert.equal(git(w.clone, "rev-parse", "refs/remotes/origin/main"), two, "the fetch brought the root's commit"); + assert.equal(git(w.clone, "rev-parse", "HEAD"), head, "HEAD didn't move"); +- // A diverged clone gets the fetch into refs/remotes, and nothing else. +- if (before) assert.equal(fingerprint(w.clone), before, "the clone is exactly as it was"); +- else assert.equal(readFileSync(join(w.clone, "README"), "utf8"), "mine\n"); ++ assert.equal(work(w.clone), before, "HEAD, the branches, the index and the files are exactly as they were"); ++ }); ++ } ++}); ++ ++test("a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward", async (t) => { ++ const w = world(t); ++ await once(w); ++ const mine = commit(w.clone, "mine\n"); ++ const two = commit(w.root, "two\n"); ++ assert.deepEqual(await once(w), { project: "stack", path: w.clone, outcome: "kept", reason: "not-fast-forward" }); ++ assert.equal(git(w.clone, "rev-parse", "HEAD"), mine); ++ assert.equal(git(w.clone, "rev-parse", "refs/remotes/origin/main"), two); ++ assert.equal(readFileSync(join(w.clone, "README"), "utf8"), "mine\n"); ++}); ++ ++// A git on PATH that logs every call to /calls, and fails a call ++// whose subcommand is in /fail. ++function logged(w) { ++ const bin = join(w.dir, "bin"); ++ mkdirSync(bin); ++ const real = spawnSync("sh", ["-c", "command -v git"], { encoding: "utf8" }).stdout.trim(); ++ const calls = join(w.dir, "calls"); ++ const fail = join(w.dir, "fail"); ++ writeFileSync(join(bin, "git"), `#!/bin/sh\nprintf '%s\\n' "$*" >> '${calls}'\nif [ -f '${fail}' ]; then case " $* " in *" $(cat '${fail}') "*) exit 1;; esac; fi\nexec '${real}' "$@"\n`, { mode: 0o755 }); ++ return { ++ env: { PATH: `${bin}:${process.env.PATH}` }, ++ fail: (sub) => writeFileSync(fail, sub), ++ // The subcommand of each call that ran git in the clone. ++ inClone: () => (existsSync(calls) ? readFileSync(calls, "utf8").split("\n").filter((l) => l.includes(`--git-dir=${join(w.clone, ".git")}`)).map((l) => l.split(" ").find((a, i, all) => i > 0 && all[i - 1].startsWith("--work-tree="))) : []), ++ }; ++} ++ ++// Every reason found before the branch check keeps the clone with no fetch, ++// and so does a status that fails. The root moves on; the clone's ++// refs/remotes doesn't. ++test("a clone kept before the branch check, or for a failed status, gets no fetch", async (t) => { ++ for (const [name, edit, reason, ran] of [ ++ ["not-a-clone", (w) => rmSync(join(w.clone, ".git"), { recursive: true }), "not-a-clone", []], ++ ["hooks-present", (w) => (mkdirSync(join(w.clone, ".git", "hooks")), writeFileSync(join(w.clone, ".git", "hooks", "post-merge"), "#!/bin/sh\n")), "hooks-present", []], ++ ["submodule", (w) => git(w.clone, "update-index", "--add", "--cacheinfo", `160000,${git(w.root, "rev-parse", "HEAD")},sub`), "submodule", ["ls-files"]], ++ ["config-changed", (w) => git(w.clone, "config", "user.name", "someone"), "config-changed", []], ++ ["root-unreadable", (w) => rmSync(join(w.root, ".git"), { recursive: true }), "root-unreadable", ["ls-files"]], ++ ["root-detached", (w) => git(w.root, "checkout", "-q", "--detach"), "root-detached", ["ls-files"]], ++ ["status-failed, on the root's branch and clean", (w, l) => l.fail("status"), "status-failed", ["ls-files", "symbolic-ref", "status"]], ++ ["status-failed, dirty", (w, l) => (writeFileSync(join(w.clone, "README"), "mine\n"), l.fail("status")), "status-failed", ["ls-files", "symbolic-ref", "status"]], ++ ]) { ++ await t.test(name, async (t) => { ++ const w = world(t); ++ await once(w); ++ const one = git(w.clone, "rev-parse", "refs/remotes/origin/main"); ++ // Every case but root-unreadable has a newer root commit to fetch. ++ if (reason !== "root-unreadable") commit(w.root, "two\n"); ++ const l = logged(w); ++ edit(w, l); ++ const list = await ensureCheckouts({ workspace: w.workspace, projects: { stack: { root: w.root } }, env: l.env }); ++ assert.deepEqual(list, [{ project: "stack", path: w.clone, outcome: "kept", reason }]); ++ assert.deepEqual(l.inClone(), ran, "no fetch, and no git past the check that kept it"); ++ if (existsSync(join(w.clone, ".git", "refs"))) assert.equal(git(w.clone, "rev-parse", "refs/remotes/origin/main"), one, "refs/remotes didn't move"); + }); + } + }); + ++test("a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was", async (t) => { ++ const w = world(t); ++ await once(w); ++ writeFileSync(join(w.clone, "README"), "mine\n"); ++ commit(w.root, "two\n"); ++ const l = logged(w); ++ l.fail("fetch"); ++ const before = fingerprint(w.clone); ++ const list = await ensureCheckouts({ workspace: w.workspace, projects: { stack: { root: w.root } }, env: l.env }); ++ assert.deepEqual(list, [{ project: "stack", path: w.clone, outcome: "kept", reason: "fetch-failed" }]); ++ assert.equal(fingerprint(w.clone), before); ++ assert.ok(!l.inClone().includes("merge")); ++}); ++ + // 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-117/r1/candidate-manifest.sha256 b/agents/darkwing/work/queue-117/r1/candidate-manifest.sha256 new file mode 100644 index 000000000..5f1431eb5 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/candidate-manifest.sha256 @@ -0,0 +1,8 @@ +76d6e9e57e85769a8c1f7d802518443d3f9051a72bd36fa6481a5bfac7d9a910 packages/cli/README.md +4fccc5844041ff03240dc0029d25e6222be6087992a61a5ca205f41a8b105508 packages/cli/src/launcher.mjs +050e6ea3098946ff547ce0df77284468d0c2553445d49a772a8a5d0ab720b8fb packages/cli/tests/launcher.test.mjs +40102df1a273a753a0a730f1c89cfddbd650bd50acdb16b7cfd37c23ca193855 packages/harness/src/bundle.mjs +f4943446fafde437895875deb51ab85e023a23e975eab2645492b02743c8f979 packages/harness/tests/bundle.test.mjs +762f98f87bf29d73393927f1f7eddad3ebddac19ecb76def8261d71d510503a8 packages/seat/README.md +0c8e93c2a169a11c35af6a69677dc08f954f227ae1b5cee5327a40de051431f9 packages/seat/src/checkout.mjs +ec4bd69cd159fb8f0f71701a5730aebed23eb7ac9c91172881a0f211d662d581 packages/seat/tests/checkout.test.mjs diff --git a/agents/darkwing/work/queue-117/r1/gate-run.log b/agents/darkwing/work/queue-117/r1/gate-run.log new file mode 100644 index 000000000..af314078a --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate-run.log @@ -0,0 +1,7 @@ +base 559ef2287 start 17:29:52 +pkg-seat rc=0 17:29:52-17:29:57 ℹ pass 130 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 4942.972655 +pkg-cli rc=0 17:29:57-17:30:15 ℹ pass 184 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 18601.359229 +pkg-harness rc=0 17:30:15-17:30:26 ℹ pass 81 ℹ fail 0 ℹ skipped 0 | ℹ duration_ms 11306.047886 +script-conductor rc=0 17:30:26-17:30:32 | selftest: 17 passed, 0 failed +script-extension-package rc=0 17:30:32-17:30:33 | extension package selftest: 18 passed, 0 failed +GATE_PASSED 17:30:33 diff --git a/agents/darkwing/work/queue-117/r1/gate.sh b/agents/darkwing/work/queue-117/r1/gate.sh new file mode 100755 index 000000000..66fe5d374 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -o pipefail +# Darkwing targeted builder gate, row 117 (#1611), candidate in the r117 +# worktree on base 559ef2287 (Sage's sage-0180 rule: the changed packages, +# their importers, and every scripts/test-*.sh that reads a changed path). +# seat, cli and harness; no other package imports checkout.mjs, launcher.mjs +# or bundle.mjs. No test-*.sh reads a changed path; test-conductor.sh and +# test-extension-package.sh match the basename README.md for their own +# sandbox files and run anyway. Sequential, each teed; stops at the first failure. +G=$HOME/darkwing-scratch/r117/wt +OUT=$HOME/darkwing-scratch/r117/gate +export TMPDIR=$HOME/darkwing-scratch/r117/tmp +mkdir -p "$OUT" "$TMPDIR" +cd "$G" || exit 9 +[ -e "$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" t0; t0=$(date -u +%H:%M:%S) + (cd "$G/$dir" && bash -c "$cmd") 2>&1 | tee "$OUT/$name.log" > /dev/null; local rc=$? + echo "$name rc=$rc $t0-$(date -u +%H:%M:%S) $(grep -E '^ℹ (pass|fail|skipped) [0-9]+' "$OUT/$name.log" | tr '\n' ' ') | $(tail -n1 "$OUT/$name.log")" + [ $rc -eq 0 ] || { echo "STOP $name"; exit $rc; }; } +for p in seat cli harness; do run "pkg-$p" "packages/$p" 'node --test tests/*.test.mjs'; done +for n in conductor extension-package; do run "script-$n" . "bash scripts/test-$n.sh"; done +echo "GATE_PASSED $(date -u +%H:%M:%S)" diff --git a/agents/darkwing/work/queue-117/r1/gate/pkg-cli.log b/agents/darkwing/work/queue-117/r1/gate/pkg-cli.log new file mode 100644 index 000000000..47794a8c8 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate/pkg-cli.log @@ -0,0 +1,194 @@ +✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1297.769166ms) +✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (76.055354ms) +✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (161.032341ms) +✔ NO_COLOR: no escape codes, except reverse video on the selected row (257.477535ms) +✔ 1-9 shows the decide flow's own lines and question; only y resolves (167.127927ms) +✔ the app shows the same decide text as mosaic decide (119.377802ms) +✔ any key but y or Y at the question answers No; nothing is sent (3098.594191ms) +✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (428.395964ms) +✔ under 40 columns a tall terminal shows the too-small screen even where the decide box would fit; y sends nothing (Filbert N10) (1655.973482ms) +✔ a hidden decide box names the way out, and the status line under it does not promise y (Filbert N12) (86.63173ms) +✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (80.290654ms) +✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (736.941704ms) +✔ quitting at the question answers No and sends nothing, armed or not (213.586143ms) +✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (195.330071ms) +✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (148.013344ms) +✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (866.337422ms) +✔ at every size the decide box shows each line y depends on, or says it is hidden (7975.119945ms) +✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (155.748436ms) +✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (103.800872ms) +✔ help or a detail box cut to fit keeps its last line, Esc included (Dewey) (51.276225ms) +✔ keys: views, moving, trail of a selection, the command line, help and quit (218.07848ms) +✔ one option: the status line says Press 1, not 1-1 (89.660225ms) +✔ no business: the app says so and :business picks one (48.871323ms) +✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (1.647005ms) +✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (160.283616ms) +✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.410196ms) +✔ decide with --note still asks; a No sends nothing (55.662519ms) +✔ the app's business comes from a live bus host only (1.048979ms) +✔ 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.633126ms) +✔ draw is pure: same state, same frame (0.700415ms) +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (166.312268ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (129.921679ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (135.689531ms) +✔ decide prints a declining choice as declining (162.315128ms) +✔ an unknown outcome is reported once and never resent (123.576084ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (110.989634ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (177.176498ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (98.353632ms) +✔ every human command refuses inside an agent run before it touches the bus (106.557747ms) +✔ usage errors exit 4; no business and no host is a usage error (160.03649ms) +✔ agents and tasks print through the broker (118.967233ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.320635ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (60.177609ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (74.743218ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (31.84458ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (32.892998ms) +✔ a business without tracker.baseUrl gets no trackers entry (33.30163ms) +✔ an unknown business and a broken system config refuse with exit 3 (71.439029ms) +✔ follow prints an unsolicited message after plain talk returned (210.203394ms) +✔ talk --follow continues after reply and prints a later unsolicited message (198.468758ms) +✔ inbox lists all unread human messages under decisions, does not deliver or read them (4258.814721ms) +✔ follow refuses agent context and bad arguments (232.164326ms) +✔ follow Ctrl-C wakes the poll sleep and removes its handler; transport errors also clean up (158.000275ms) +✔ plain talk leaves the process SIGINT handlers unchanged (145.472504ms) +✔ empty views say so (1.096908ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.454589ms) +✔ tasks print the tracker fields the snapshot carries (0.218056ms) +✔ the review loop: the coder pushes and asks, the reviewer fetches and approves, and the task's trail holds each step (751.438041ms) +✔ refusals: no action, non-fast-forward, an unknown commit or request, and a push for another task moves only that task's ref (700.986675ms) +✔ the size cap refuses a push before any byte (389.748282ms) +✔ a fetch over the cap refuses before any byte reaches the reviewer (566.546836ms) +✔ the socket refuses a request with no capability, an unknown one, and a bad request line (604.447238ms) +✔ a rigged clone runs nothing: hooks, fsmonitor, a filter, a gitlink and a symlinked .git/config leave no marker (2081.612245ms) +✔ an identity with no role, or of another business, refuses not-a-role and moves no ref (381.547585ms) +✔ a review of a project the business no longer has refuses unknown-project (498.365177ms) +✔ past maxActive connections the socket refuses git-busy, and a hung-up connection frees its slot (372.204713ms) +✔ close() answers host-closing to a connection still sending its request line (314.803841ms) +✔ a bundle that trickles in refuses body-timeout at its deadline and frees its slot (819.5815ms) +✔ a reviewer gone before its bundle was sent is filed fetch-aborted, not as the host's fault (575.645084ms) +✔ a reviewer that hangs up the moment it has the whole bundle was served, not aborted (630.489991ms) +✔ send: a close after every byte left is served; one that fails a queued write is fetch-aborted (42.474272ms) +✔ a bundle with no bytes for bodyIdle refuses body-timeout long before its deadline (593.494396ms) +✔ send: a close once the loop is past its last chunk is served, and a failed write there is fetch-aborted (24.314476ms) +✔ letGo waits for an answer to leave for at most its cap, then destroys the socket (301.908402ms) +✔ a record that fails and can't be undone refuses rollback-failed, which the host logs (451.427485ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (943.767541ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (258.486582ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (233.919349ms) +✔ two bus starts racing for one data root leave one host running; the other refuses that a host already runs (630.358224ms) +✔ a host takes over a host.lock its dead host left, and puts back a live claim that replaced it meanwhile (1.226596ms) +✔ 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.710478ms) +✔ a live host's lock stays, however old the file is (0.751026ms) +✔ a host.lock left by a host killed with SIGKILL doesn't stop the next bus start (853.825172ms) +✔ while a host boots, a reader sees no host: the human commands still ask for --business, stop and status find none (248.095539ms) +✔ a host started before host.lock existed, holding only host.json, still refuses a second start, which leaves no lock (42.097293ms) +✔ a broker fork that throws leaves no host.lock and no host.json (35.795626ms) +✔ a broker send that throws leaves no host.lock and no host.json (38.41758ms) +✔ a broker that misses its boot wait leaves no host.lock and no host.json (40.656129ms) +✔ a notifier fork that throws stops the broker and leaves no host.lock and no host.json (803.112497ms) +✔ a notifier send that throws stops the broker and leaves no host.lock and no host.json (749.739573ms) +✔ 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 (211.66214ms) +✔ 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 (159.567753ms) +✔ a host.json that can't be written stops both children and leaves no host.lock (245.781889ms) +✔ an error event on the broker before it replies fails the start at once, and leaves no host.lock and no live child (31.395584ms) +✔ an error event on the notifier before it replies fails the start at once, and leaves no host.lock and no live child (141.75736ms) +✔ an error event on the broker while the notifier starts fails the start once the notifier replies (452.306183ms) +✔ 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 (251.320476ms) +✔ 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 (274.053297ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1204.446473ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (295.172261ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (165.372544ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (226.195726ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (196.335184ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (155.436986ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (170.416947ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (136.350215ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (187.102571ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.395801ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.379941ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (257.181452ms) +✔ bus start refuses with exit 3 without a notifier config (106.262056ms) +✔ bus start runs until bus stop; status reports it while it runs (693.466589ms) +✔ bus-service.sh renders the unit and installs it into a given directory (52.155458ms) +✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (67.531699ms) +✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (13.231327ms) +✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (8.473345ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1368.659329ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (853.940681ms) +✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (1395.207121ms) +✔ a project clone that can't be made refuses checkout-failed, from --pm and from a session, with no run and no clone left (1249.093643ms) +✔ a runner that stops at once ends its launch with the runner's reason (322.818338ms) +✔ a Claude Code launch with no session credential refuses before it starts (147.11971ms) +✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (229.008962ms) +✔ a runner killed from the host ends its launch as killed, not failed (577.421422ms) +✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (177.534988ms) +✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1451.711751ms) +✔ 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 (860.487875ms) +✔ 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 (3684.675652ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (113.944091ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (128.844064ms) +✔ a launch client that never closes its side doesn't hold the host's close (135.143944ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1268.687111ms) +✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (4.353961ms) +✔ zoned uses the IANA zone across DST (24.742784ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (169.494366ms) +✔ two blocking decisions get two DMs with different nonces (130.839147ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.312376ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (127.527204ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (161.021939ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (136.563862ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (108.822025ms) +✔ 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 (230.896714ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (152.819527ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (165.45275ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (126.709184ms) +✔ an inbox read failure is logged and the next poll retries (0.75125ms) +✔ no Discord id reaches the journal or the log (100.24316ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (29.445487ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (98.754002ms) +✔ the journal: a whole file that is one torn line truncates to empty (39.309185ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (12.582971ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.662909ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (14.682345ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.407509ms) +✔ 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.884123ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.425108ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (2.375108ms) +✔ digest content stays within Discord's 2000 characters (0.322607ms) +✔ runLoop never overlaps ticks and stops after the one in flight (125.585347ms) +✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (487.896752ms) +✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (411.591739ms) +✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (442.222215ms) +✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (308.360601ms) +✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (4419.728472ms) +✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (501.196219ms) +✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (179.491976ms) +✔ install never shows a template name on systemd 261; template drop-ins still refuse (166.994452ms) +✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (187.421706ms) +✔ talk --tester sends a [mos test] message as tester from inside an agent and prints the role's reply (179.023228ms) +✔ talk --tester refuses text without the [mos test] prefix before any call (99.532364ms) +✔ the read verbs take --tester inside an agent, and the trail shows the tester as the sender (137.815368ms) +✔ decide, stop and launches take no --tester (144.045786ms) +✔ the tester transport runs tester-cli.mjs, and the tester's refusals exit 3 (0.389466ms) +✔ with no stand-in transport, --tester runs tester-cli.mjs on the data root's socket (0.598755ms) +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 (430.250441ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (46.028246ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2147.963578ms) +✔ busExit and refuseInsideAgent (0.489448ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (233.885944ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1135.631129ms) +✔ a late reply the Console has listed still reaches the next talk (2203.667391ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (1019.557357ms) +✔ launches off and on go to the broker and change the business's launch state (320.134693ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (161.112914ms) +ℹ tests 184 +ℹ suites 0 +ℹ pass 184 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 18601.359229 diff --git a/agents/darkwing/work/queue-117/r1/gate/pkg-harness.log b/agents/darkwing/work/queue-117/r1/gate/pkg-harness.log new file mode 100644 index 000000000..885a51b50 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate/pkg-harness.log @@ -0,0 +1,89 @@ +✔ sessionModel: agent vars win, then the system's execution settings (13.388919ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.957742ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.179899ms) +✔ the prompt's built-in tools line is the harness's own names, as the manifest lists them (10.941028ms) +✔ the prompt names the project clones in the workspace, and none when there are none (5.284068ms) +✔ the prompt explains the review loop to a role with push_working, and reviews to one with fetch_review (8.229744ms) +✔ a bundle is written once: an existing file refuses (1.940532ms) +✔ a path with a single quote can't go into the hook command (2.289958ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (122.992526ms) +✔ a missing or wrong policy, or a bad event, exits 2 (76.129354ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1084.828943ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (729.576255ms) +✔ claude: the hook alone blocks a path outside the workspace (430.511651ms) +✔ claude: a second turn resumes the first turn's session (753.852659ms) +✔ claude adapter: --restricted is always passed (5.289167ms) +✔ claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable (16.337151ms) +✔ claude adapter: a directory pin exits 2 before it runs; a pin with a space runs (5.820861ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (686.587216ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.02235ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.113982ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.020364ms) +✔ file tool paths must resolve inside the workspace (1.831233ms) +✔ pi's own path normalisation can't be used to step out (1.373022ms) +✔ a symlink inside the workspace that points out is outside (1.153946ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.671755ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (18.394307ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.964912ms) +✔ a relative path climbs from the workspace's real path, in both harnesses (4.49529ms) +✔ claude path fields per tool (0.718145ms) +✔ glob patterns stay inside the workspace (0.703993ms) +✔ a path that can't be checked is blocked (0.424947ms) +✔ fetch_review's have lists the working refs before origin's, and no more than HAVE_MAX (196.618766ms) +✔ fetch_review's have orders the working refs newest first within each clone, one clone after another (119.099552ms) +✔ a runner git past its time: SIGTERM reaches its process group, and SIGKILL follows after the grace period (1809.646242ms) +✔ once a signalled runner git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (304.003536ms) +✔ initialize, ping and tools/list (46.906846ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (35.349736ms) +✔ replies are compact JSON; over the cap a tool error names the size and the cap (35.879984ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (28.76647ms) +✔ a missing argument is a usage error (25.024264ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (349.344908ms) +✔ pi: a reply over the cap is a tool error naming the size and the cap; list_tasks asks for open tasks (319.354969ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (308.562565ms) +✔ pi: a read is refused when pi would open another spelling outside (314.151651ms) +✔ pi: a relative path climbs from the real path of a workspace behind a symlink (334.644419ms) +✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (306.580142ms) +✔ pi: a missing extension refuses before any model call (5.456278ms) +✔ pi: an extension without its configuration fails pi's start (246.034351ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.422188ms) +✔ turnRequest names the sender, class, reply and decision (0.197563ms) +✔ turnRequest says a tester's message is a test and never Jason's instruction (0.231275ms) +✔ turnRequest shows the message's own sent time, a day old as well as new (0.147162ms) +✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.252965ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (255.888805ms) +✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (257.756985ms) +✔ a task closed on the tracker is missing from the next turn's open list (219.445431ms) +✔ a message read a day after it was sent shows the time it was sent (167.336712ms) +✔ a decision the human resolves becomes a turn citing the decision and the answer (203.891433ms) +✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (115.1135ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (108.725818ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (419.152882ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1288.462209ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (152.260188ms) +✔ founder credentials stop before the claim (20) (166.766001ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (188.897227ms) +✔ the launch ending under a running session exits 22 (140.151923ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (245.04999ms) +✔ a broker that is down at the claim exits 23, not 21 (82.772596ms) +✔ no capability, or a malformed one, on stdin exits 2 (185.203599ms) +✔ a missing or malformed policy exits 2 before the claim (127.284485ms) +✔ the PM gets launch, its task verbs and the reads (7.927721ms) +✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (6.071965ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.510724ms) +✔ a coder with a tracker gets its own task tools and no task_reassign (2.103048ms) +✔ launch only when the business's launch block names the instance as launcher (1.951486ms) +✔ an action outside the instance's authority has no tool (2.221092ms) +✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (5.375561ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (9.511834ms) +✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (2.599688ms) +✔ callTool sends list_tasks done false unless the session names done; other tools as given (2.608257ms) +✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.719176ms) +ℹ tests 81 +ℹ suites 0 +ℹ pass 81 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 11306.047886 diff --git a/agents/darkwing/work/queue-117/r1/gate/pkg-seat.log b/agents/darkwing/work/queue-117/r1/gate/pkg-seat.log new file mode 100644 index 000000000..04b9cd12e --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate/pkg-seat.log @@ -0,0 +1,143 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (30.895238ms) +✔ the clone is not at its place until git clone has finished (28.632102ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (68.83608ms) +✔ a clone ahead of the root keeps its commits and counts as current (43.95082ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (70.521595ms) + ✔ an untracked file (51.979582ms) + ✔ a staged file (48.346271ms) + ✔ another branch (44.59529ms) + ✔ a detached HEAD (45.04417ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (261.543051ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (43.317053ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (25.468593ms) + ✔ hooks-present (28.133805ms) + ✔ submodule (32.763822ms) + ✔ config-changed (28.234047ms) + ✔ root-unreadable (36.041341ms) + ✔ root-detached (41.096123ms) + ✔ status-failed, on the root's branch and clean (38.180009ms) + ✔ status-failed, dirty (37.255307ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (268.338357ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (39.393821ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (25.696736ms) + ✔ user.email (24.413323ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (50.558986ms) +✔ a commit git can't read after the fast-forward is null (48.418224ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (19.752773ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (34.756872ms) + ✔ core.hooksPath (39.30805ms) + ✔ a hook file (49.852256ms) + ✔ include.path (34.126525ms) + ✔ includeIf (35.466173ms) + ✔ a filter driver (34.51697ms) + ✔ origin moved (34.995846ms) + ✔ a second origin url (36.362753ms) + ✔ the origin url twice, both the root (34.077802ms) + ✔ an ext:: origin (33.188866ms) + ✔ a branch tracking another remote (37.674806ms) + ✔ a branch merging a tag (32.700647ms) + ✔ a fetch line onto local branches (36.076178ms) + ✔ no origin (33.429941ms) + ✔ a config file over the size cap (32.777487ms) + ✔ config a symlink to /dev/zero (29.195944ms) + ✔ the clone a symlink to the bait (31.031292ms) + ✔ the clone a file (30.424286ms) + ✔ the clone a plain directory (29.476047ms) + ✔ .git a gitdir: file (29.649923ms) + ✔ .git a symlink (31.036501ms) + ✔ .git/logs a symlink (31.50997ms) + ✔ .git/refs/heads a symlink (29.929358ms) + ✔ .git/objects a symlink (33.428763ms) + ✔ a reflog hard-linked to a file outside (33.561572ms) + ✔ HEAD a fifo (33.117356ms) + ✔ a commondir file (33.615877ms) + ✔ an alternates file (31.36129ms) + ✔ a gitlink staged, no .gitmodules (44.676985ms) + ✔ a gitlink and .gitmodules staged (50.831998ms) + ✔ a gitlink to a repository with its own gitlink (49.887602ms) + ✔ a gitlink and .gitmodules committed (49.336938ms) + ✔ a gitlink and submodule.active in config (43.355356ms) + ✔ a .git/modules with no gitlink (37.383484ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1226.456018ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.700407ms) + ✔ not a repository (9.862037ms) + ✔ a plain directory inside another repository (14.462316ms) + ✔ unreadable (10.402918ms) + ✔ on no branch (11.371923ms) + ✔ missing an object (14.681548ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (72.296437ms) +✔ a clone whose root is on no branch, or gone, is kept and named (26.591431ms) +✔ no credential or forge remote of the root reaches a clone (50.383536ms) +✔ every project in the business file gets its own clone at its own branch (36.833838ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (514.295251ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1015.614639ms) +✔ a timeout signals git's process group, so a process git started dies with it (513.302433ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (513.612092ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.559541ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (7.617117ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (1.306852ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (0.63643ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.042692ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1806.020225ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2461.599378ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (271.827683ms) +✔ resolveSeat: by name under --repo resolves the repo layout (1.208564ms) +✔ resolveSeat: by path resolves the fleet layout (0.332828ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.767441ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.646181ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.773438ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.233739ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.108082ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (28.997402ms) +✔ CLI launch: --harness lands in the record (26.929385ms) +✔ CLI launch: the launch script's own exit code passes through (27.25117ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (57.773112ms) +✔ CLI launch: omitting --task records an empty string, not null (29.256615ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (79.247876ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (138.514094ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.37431ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.567626ms) +✔ 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.704164ms) +✔ 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.083432ms) +✔ 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 (266.18735ms) +✔ family: exactly one launch.max key in the model name, else null (0.689259ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.667974ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.245214ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.417364ms) +✔ session file and launch log: 0600, the session file written once (0.99507ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.114255ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.224828ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.962113ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (352.550277ms) +✔ workRef and validBranch take only what a ref name can hold (1.121086ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (138.115238ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (164.002289ms) +✔ a branch with no history in common with main refuses unrelated-history (79.872923ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (47.07848ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (161.561785ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (104.039593ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (82.69685ms) +✔ only a v2 bundle with the named branch is read (44.405073ms) +✔ a branch named main, or named for another task, moves only this task's work ref (91.196722ms) +✔ a record that throws moves the work ref back, or removes a new one (177.501837ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (414.31536ms) +✔ a root that can't be read refuses root-unreadable (28.47547ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2168.609493ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1027.991269ms) +✔ a working repository path that isn't a directory refuses (23.846404ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (115.708768ms) +✔ review of a project with no working repository refuses unknown-commit (12.812905ms) +✔ the runner's git tools hold the host's limits and branch rule (1.140592ms) +ℹ tests 130 +ℹ suites 0 +ℹ pass 130 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4942.972655 diff --git a/agents/darkwing/work/queue-117/r1/gate/script-conductor.log b/agents/darkwing/work/queue-117/r1/gate/script-conductor.log new file mode 100644 index 000000000..0c8ab0b17 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate/script-conductor.log @@ -0,0 +1,55 @@ +Note: switching to '559ef228753a96d6d50fa31b2838bf62e327ca0b'. + +You are in 'detached HEAD' state. You can look around, make experimental +changes and commit them, and you can discard any commits you make in this +state without impacting any branches by switching back to a branch. + +If you want to create a new branch to retain commits you create, you may +do so (now or later) by using -c with the switch command. Example: + + git switch -c + +Or undo this operation with: + + git switch - + +Turn off this advice by setting config variable advice.detachedHead to false + +Not currently on any branch. +nothing to commit, working tree clean +Note: switching to '559ef228753a96d6d50fa31b2838bf62e327ca0b'. + +You are in 'detached HEAD' state. You can look around, make experimental +changes and commit them, and you can discard any commits you make in this +state without impacting any branches by switching back to a branch. + +If you want to create a new branch to retain commits you create, you may +do so (now or later) by using -c with the switch command. Example: + + git switch -c + +Or undo this operation with: + + git switch - + +Turn off this advice by setting config variable advice.detachedHead to false + +OK dry-run: allowed change, exit 0, nothing committed (exit 0) +OK dry-run committed nothing +OK apply: allowed change exits 0 (exit 0) +OK apply: attribution in commit subject +OK apply: target tree clean after commit +OK disallowed path refused (exit 1) +OK disallowed path: target untouched +OK syntax gate refused broken .mjs (exit 1) +OK syntax gate: target untouched +OK suite failure refused (exit 1) +OK suite failure: target reverted to clean +OK disabled policy refused (exit 2) +OK disabled policy: target untouched +OK failed run refused (exit 1) +OK failed run: target untouched +OK missing run exits 4 (exit 4) +OK invalid policy exits 2 (exit 2) + +selftest: 17 passed, 0 failed diff --git a/agents/darkwing/work/queue-117/r1/gate/script-extension-package.log b/agents/darkwing/work/queue-117/r1/gate/script-extension-package.log new file mode 100644 index 000000000..ba8a03f56 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/gate/script-extension-package.log @@ -0,0 +1,21 @@ +OK initial ordinary-file install +OK installed tree matches canonical source +OK installed tree has no symlinks +OK check detects installation drift +OK sync refuses to overwrite installation drift +OK check detects an extra destination file +OK check detects an extra destination directory +OK check rejects a destination symlink +OK sync accepts a canonical source update +OK updated installation matches canonical source +scripts/test-extension-package.sh: line 14: 2956563 Killed "$@" > /dev/null 2>&1 +OK forced interruption kills the replacing process +OK next invocation recovers old consistent installation +OK interrupted replacement rolled back +OK sync succeeds after interruption recovery +OK unlocked stale lock file does not block +OK active lock refuses a concurrent sync +OK source symlink fails closed +OK nested second entrypoint fails closed + +extension package selftest: 18 passed, 0 failed diff --git a/agents/darkwing/work/queue-117/r1/mutants-run.log b/agents/darkwing/work/queue-117/r1/mutants-run.log new file mode 100644 index 000000000..97c633e34 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants-run.log @@ -0,0 +1,37 @@ +mutant m1 rc=1 ℹ pass 127 ℹ fail 3 ℹ skipped 0 restored=True + ✖ a clone kept before the branch check, or for a failed status, gets no fetch (282.565799ms) + ✖ status-failed, dirty (50.380409ms) + ✖ status-failed, on the root's branch and clean (52.311566ms) +KILLED +mutant m2 rc=1 ℹ pass 125 ℹ fail 5 ℹ skipped 0 restored=True + ✖ a detached HEAD (52.398743ms) + ✖ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (250.290674ms) + ✖ a staged file (50.774577ms) + ✖ an untracked file (48.381552ms) + ✖ another branch (47.49936ms) +KILLED +mutant m3 rc=1 ℹ pass 123 ℹ fail 7 ℹ skipped 0 restored=True + ✖ a detached HEAD (32.202485ms) + ✖ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (178.716659ms) + ✖ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (43.42291ms) + ✖ a modified file (35.036054ms) + ✖ a staged file (35.342157ms) + ✖ an untracked file (36.35993ms) + ✖ another branch (38.526769ms) +KILLED +mutant m4 rc=1 ℹ pass 124 ℹ fail 6 ℹ skipped 0 restored=True + ✖ a detached HEAD (44.212281ms) + ✖ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (222.779496ms) + ✖ a modified file (43.573265ms) + ✖ a staged file (45.694781ms) + ✖ an untracked file (42.434504ms) + ✖ another branch (45.576595ms) +KILLED +mutant m5 rc=1 ℹ pass 124 ℹ fail 6 ℹ skipped 0 restored=True + ✖ a clone kept before the branch check, or for a failed status, gets no fetch (414.568082ms) + ✖ root-detached (53.434429ms) + ✖ root-unreadable (41.325292ms) + ✖ status-failed, dirty (69.250121ms) + ✖ status-failed, on the root's branch and clean (64.900111ms) + ✖ submodule (53.196042ms) +KILLED diff --git a/agents/darkwing/work/queue-117/r1/mutants.py b/agents/darkwing/work/queue-117/r1/mutants.py new file mode 100755 index 000000000..b67f596cd --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Row 117 (#1611): the brief's five mutants of the fetched outcome, one per check. +# m1 fetches for a third reason: a failed status becomes a fetched reason. +# m2 merges after the fetch in a fetched clone. +# m3 drops the fetch for a fetched clone. +# m4 reports the clone's HEAD as the origin commit. +# m5 fetches before the gitlink check (and so before the root checks too). +# Each runs the FULL seat suite (node --test tests/*.test.mjs in packages/seat) +# and restores the source from the .orig copy whatever happens. +# Usage: mutants.py +import os, shutil, subprocess, sys, signal + +WT, LOGS = sys.argv[1], sys.argv[2] +SRC = 'packages/seat/src/checkout.mjs' +FETCH = '[...inClone(dest), "fetch", "-q", "--no-tags", "--no-recurse-submodules", "origin"]' +MERGE = '[...inClone(dest), "merge", "-q", "--ff-only", "--no-edit", `refs/remotes/origin/${at.branch}`]' +MUTANTS = { + 'm1': (SRC, + ' if (!status.ok) return { outcome: "kept", reason: "status-failed" };\n', + ' if (!status.ok) reason = "status-failed";\n'), + 'm2': (SRC, + ' if (reason) {\n', + f' if (reason) {{\n await g({MERGE});\n'), + 'm3': (SRC, + f' if (!(await g({FETCH})).ok)', + f' if (!reason && !(await g({FETCH})).ok)'), + 'm4': (SRC, + 'origin: { branch: at.branch, commit: origin.ok ? origin.out.trim() : null } };', + 'origin: { branch: at.branch, commit: before } };'), + 'm5': (SRC, + ' // ls-files reads only the index: no filter, hook or submodule runs.\n', + f' await g({FETCH});\n // ls-files reads only the index: no filter, hook or submodule runs.\n'), +} +os.makedirs(LOGS, exist_ok=True) +env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r117/tmp')) +for name, (rel, cur, new) in MUTANTS.items(): + src = os.path.join(WT, rel) + text = open(src).read() + if text.count(cur) != 1: + sys.exit(f'STOP {name}: the target text in {rel} is not unique or has moved') + if os.path.exists(src + '.orig'): + sys.exit(f'STOP {rel}.orig already exists') + log_path = os.path.join(LOGS, f'mutant-{name}.log') + shutil.copy2(src, src + '.orig') + try: + open(src, 'w').write(text.replace(cur, new)) + with open(log_path, 'w') as out: + p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd=os.path.join(WT, 'packages/seat'), + stdout=out, stderr=subprocess.STDOUT, env=env, start_new_session=True) + try: + rc = p.wait(timeout=600) + except subprocess.TimeoutExpired: + os.killpg(p.pid, signal.SIGKILL) + p.wait() + rc = 'timeout' + finally: + shutil.move(src + '.orig', src) + restored = open(src).read() == text + log = open(log_path).read() + counts = [l for l in log.splitlines() if l.startswith(('ℹ pass', 'ℹ fail', 'ℹ skipped'))] + failed = [l.strip() for l in log.splitlines() if l.lstrip().startswith('✖') and 'failing tests' not in l] + print(f'mutant {name} rc={rc} {" ".join(counts)} restored={restored}') + for f in sorted(set(failed)): + print(' ' + f) + print('KILLED' if rc != 0 else 'SURVIVED') + if not restored: + sys.exit(f'STOP {rel} was not restored') diff --git a/agents/darkwing/work/queue-117/r1/mutants/mutant-m1.log b/agents/darkwing/work/queue-117/r1/mutants/mutant-m1.log new file mode 100644 index 000000000..94f9fbc4f --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants/mutant-m1.log @@ -0,0 +1,213 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (33.994223ms) +✔ the clone is not at its place until git clone has finished (28.218241ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (73.711234ms) +✔ a clone ahead of the root keeps its commits and counts as current (41.218335ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (47.292094ms) + ✔ an untracked file (46.446238ms) + ✔ a staged file (52.448412ms) + ✔ another branch (42.847974ms) + ✔ a detached HEAD (43.986651ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (234.195521ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (55.66351ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (26.473922ms) + ✔ hooks-present (25.485304ms) + ✔ submodule (34.359636ms) + ✔ config-changed (27.250416ms) + ✔ root-unreadable (29.286832ms) + ✔ root-detached (35.500351ms) + ✖ status-failed, on the root's branch and clean (52.311566ms) + ✖ status-failed, dirty (50.380409ms) +✖ a clone kept before the branch check, or for a failed status, gets no fetch (282.565799ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (39.867994ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (24.781248ms) + ✔ user.email (26.599328ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (51.811513ms) +✔ a commit git can't read after the fast-forward is null (46.108236ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (19.725285ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (35.199163ms) + ✔ core.hooksPath (36.311817ms) + ✔ a hook file (34.000247ms) + ✔ include.path (56.459638ms) + ✔ includeIf (37.839858ms) + ✔ a filter driver (32.752295ms) + ✔ origin moved (36.99831ms) + ✔ a second origin url (34.261032ms) + ✔ the origin url twice, both the root (33.038567ms) + ✔ an ext:: origin (34.936354ms) + ✔ a branch tracking another remote (33.099426ms) + ✔ a branch merging a tag (31.930941ms) + ✔ a fetch line onto local branches (32.385708ms) + ✔ no origin (32.901609ms) + ✔ a config file over the size cap (30.648657ms) + ✔ config a symlink to /dev/zero (30.167454ms) + ✔ the clone a symlink to the bait (32.593208ms) + ✔ the clone a file (34.792668ms) + ✔ the clone a plain directory (34.032501ms) + ✔ .git a gitdir: file (32.242446ms) + ✔ .git a symlink (32.043749ms) + ✔ .git/logs a symlink (31.612961ms) + ✔ .git/refs/heads a symlink (32.442258ms) + ✔ .git/objects a symlink (30.904598ms) + ✔ a reflog hard-linked to a file outside (34.647482ms) + ✔ HEAD a fifo (33.820095ms) + ✔ a commondir file (33.093815ms) + ✔ an alternates file (32.549122ms) + ✔ a gitlink staged, no .gitmodules (44.292427ms) + ✔ a gitlink and .gitmodules staged (43.67268ms) + ✔ a gitlink to a repository with its own gitlink (52.193517ms) + ✔ a gitlink and .gitmodules committed (52.839047ms) + ✔ a gitlink and submodule.active in config (43.839732ms) + ✔ a .git/modules with no gitlink (34.934742ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1233.91522ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (9.777116ms) + ✔ not a repository (9.885976ms) + ✔ a plain directory inside another repository (14.820738ms) + ✔ unreadable (9.895377ms) + ✔ on no branch (11.15725ms) + ✔ missing an object (18.457524ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (74.801654ms) +✔ a clone whose root is on no branch, or gone, is kept and named (27.94436ms) +✔ no credential or forge remote of the root reaches a clone (49.579123ms) +✔ every project in the business file gets its own clone at its own branch (35.704432ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (514.773205ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1015.714164ms) +✔ a timeout signals git's process group, so a process git started dies with it (522.62216ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (518.224577ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.662101ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (8.116802ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (1.358952ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (0.658052ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.123214ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1713.585803ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2437.285155ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (262.70017ms) +✔ resolveSeat: by name under --repo resolves the repo layout (1.216334ms) +✔ resolveSeat: by path resolves the fleet layout (0.350165ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.721798ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.658815ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.823961ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.339424ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.214684ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (27.924898ms) +✔ CLI launch: --harness lands in the record (29.91138ms) +✔ CLI launch: the launch script's own exit code passes through (29.964651ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (58.324405ms) +✔ CLI launch: omitting --task records an empty string, not null (28.18643ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (81.526292ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (134.382281ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.399073ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.555749ms) +✔ 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.683862ms) +✔ 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.176646ms) +✔ 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 (270.353429ms) +✔ family: exactly one launch.max key in the model name, else null (0.703108ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.710347ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.259442ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.537327ms) +✔ session file and launch log: 0600, the session file written once (1.019107ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.119687ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.20278ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.971459ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (291.017185ms) +✔ workRef and validBranch take only what a ref name can hold (1.135591ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (157.147758ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (140.140008ms) +✔ a branch with no history in common with main refuses unrelated-history (59.162247ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (39.062334ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (143.471254ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (89.939452ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (83.477378ms) +✔ only a v2 bundle with the named branch is read (46.00937ms) +✔ a branch named main, or named for another task, moves only this task's work ref (89.394264ms) +✔ a record that throws moves the work ref back, or removes a new one (148.713859ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (430.771163ms) +✔ a root that can't be read refuses root-unreadable (28.223612ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2158.827894ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1028.789652ms) +✔ a working repository path that isn't a directory refuses (24.082555ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (111.095713ms) +✔ review of a project with no working repository refuses unknown-commit (13.096343ms) +✔ the runner's git tools hold the host's limits and branch rule (1.161534ms) +ℹ tests 130 +ℹ suites 0 +ℹ pass 127 +ℹ fail 3 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4951.880504 + +✖ failing tests: + +test at tests/checkout.test.mjs:213:13 +✖ status-failed, on the root's branch and clean (52.311566ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + { + + branch: 'main', + + commit: 'a3254c32b37be28c6d258c7f4d45451521059d82', + + origin: { + + branch: 'main', + + commit: '3445eca058627c8f1d556e595b2fa7c647a07544' + + }, + + outcome: 'fetched', + - outcome: 'kept', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-O22JbG/ws/stack', + project: 'stack', + reason: 'status-failed' + } + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:222:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: [ { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-O22JbG/ws/stack', outcome: 'fetched', reason: 'status-failed', branch: 'main', commit: 'a3254c32b37be28c6d258c7f4d45451521059d82', origin: [Object] } ], + expected: [ { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-O22JbG/ws/stack', outcome: 'kept', reason: 'status-failed' } ], + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:213:13 +✖ status-failed, dirty (50.380409ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + { + + branch: 'main', + + commit: 'a3254c32b37be28c6d258c7f4d45451521059d82', + + origin: { + + branch: 'main', + + commit: '3445eca058627c8f1d556e595b2fa7c647a07544' + + }, + + outcome: 'fetched', + - outcome: 'kept', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-kucP9u/ws/stack', + project: 'stack', + reason: 'status-failed' + } + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:222:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: [ { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-kucP9u/ws/stack', outcome: 'fetched', reason: 'status-failed', branch: 'main', commit: 'a3254c32b37be28c6d258c7f4d45451521059d82', origin: [Object] } ], + expected: [ { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-kucP9u/ws/stack', outcome: 'kept', reason: 'status-failed' } ], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-117/r1/mutants/mutant-m2.log b/agents/darkwing/work/queue-117/r1/mutants/mutant-m2.log new file mode 100644 index 000000000..939b5e55d --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants/mutant-m2.log @@ -0,0 +1,231 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (37.911937ms) +✔ the clone is not at its place until git clone has finished (23.348273ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (74.017497ms) +✔ a clone ahead of the root keeps its commits and counts as current (42.381602ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (49.91695ms) + ✖ an untracked file (48.381552ms) + ✖ a staged file (50.774577ms) + ✖ another branch (47.49936ms) + ✖ a detached HEAD (52.398743ms) +✖ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (250.290674ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (47.84099ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (25.634653ms) + ✔ hooks-present (24.656056ms) + ✔ submodule (32.634715ms) + ✔ config-changed (26.879958ms) + ✔ root-unreadable (28.063758ms) + ✔ root-detached (35.945984ms) + ✔ status-failed, on the root's branch and clean (41.251295ms) + ✔ status-failed, dirty (38.514628ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (254.757318ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (37.714522ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (25.983402ms) + ✔ user.email (24.224578ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (50.64629ms) +✔ a commit git can't read after the fast-forward is null (50.356481ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (20.382342ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (37.057876ms) + ✔ core.hooksPath (31.51684ms) + ✔ a hook file (31.877205ms) + ✔ include.path (35.082499ms) + ✔ includeIf (34.430344ms) + ✔ a filter driver (33.815957ms) + ✔ origin moved (35.210338ms) + ✔ a second origin url (33.419612ms) + ✔ the origin url twice, both the root (40.272277ms) + ✔ an ext:: origin (51.191844ms) + ✔ a branch tracking another remote (37.586685ms) + ✔ a branch merging a tag (33.775562ms) + ✔ a fetch line onto local branches (57.239011ms) + ✔ no origin (41.818695ms) + ✔ a config file over the size cap (33.909846ms) + ✔ config a symlink to /dev/zero (34.539408ms) + ✔ the clone a symlink to the bait (33.695647ms) + ✔ the clone a file (35.717634ms) + ✔ the clone a plain directory (30.852079ms) + ✔ .git a gitdir: file (30.953143ms) + ✔ .git a symlink (29.502296ms) + ✔ .git/logs a symlink (29.547998ms) + ✔ .git/refs/heads a symlink (33.802764ms) + ✔ .git/objects a symlink (30.352677ms) + ✔ a reflog hard-linked to a file outside (29.803753ms) + ✔ HEAD a fifo (31.671108ms) + ✔ a commondir file (31.747607ms) + ✔ an alternates file (31.473604ms) + ✔ a gitlink staged, no .gitmodules (49.275451ms) + ✔ a gitlink and .gitmodules staged (45.528851ms) + ✔ a gitlink to a repository with its own gitlink (61.640257ms) + ✔ a gitlink and .gitmodules committed (48.031603ms) + ✔ a gitlink and submodule.active in config (53.200434ms) + ✔ a .git/modules with no gitlink (39.387864ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1283.541991ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.950238ms) + ✔ not a repository (10.084568ms) + ✔ a plain directory inside another repository (15.125812ms) + ✔ unreadable (10.365243ms) + ✔ on no branch (12.253999ms) + ✔ missing an object (17.29748ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (76.963791ms) +✔ a clone whose root is on no branch, or gone, is kept and named (31.591119ms) +✔ no credential or forge remote of the root reaches a clone (51.068592ms) +✔ every project in the business file gets its own clone at its own branch (38.744623ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (513.904044ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1016.432181ms) +✔ a timeout signals git's process group, so a process git started dies with it (514.231298ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (515.15436ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.547473ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (7.915644ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (1.33295ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (0.680216ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.021499ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1772.097245ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2500.769537ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (256.830924ms) +✔ resolveSeat: by name under --repo resolves the repo layout (1.233288ms) +✔ resolveSeat: by path resolves the fleet layout (0.316823ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.708441ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.652875ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.780014ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.260564ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.086415ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (27.906474ms) +✔ CLI launch: --harness lands in the record (25.774854ms) +✔ CLI launch: the launch script's own exit code passes through (30.250186ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (59.7835ms) +✔ CLI launch: omitting --task records an empty string, not null (27.230144ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (80.908401ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (133.19824ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.417494ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.608529ms) +✔ 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.777844ms) +✔ 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 (3.210242ms) +✔ 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 (263.146285ms) +✔ family: exactly one launch.max key in the model name, else null (0.656971ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.665091ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.242239ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.404056ms) +✔ session file and launch log: 0600, the session file written once (0.974039ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.111857ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.177266ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.927349ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (298.707948ms) +✔ workRef and validBranch take only what a ref name can hold (1.084642ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (156.176384ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (136.772071ms) +✔ a branch with no history in common with main refuses unrelated-history (59.849107ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (45.128822ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (137.440137ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (90.772069ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (86.66617ms) +✔ only a v2 bundle with the named branch is read (45.09574ms) +✔ a branch named main, or named for another task, moves only this task's work ref (91.697014ms) +✔ a record that throws moves the work ref back, or removes a new one (157.082973ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (395.0959ms) +✔ a root that can't be read refuses root-unreadable (31.098499ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2172.075577ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1027.226948ms) +✔ a working repository path that isn't a directory refuses (31.2116ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (111.862729ms) +✔ review of a project with no working repository refuses unknown-commit (12.903988ms) +✔ the runner's git tools hold the host's limits and branch rule (1.146038ms) +ℹ tests 130 +ℹ suites 0 +ℹ pass 125 +ℹ fail 5 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4978.740974 + +✖ failing tests: + +test at tests/checkout.test.mjs:156:13 +✖ an untracked file (48.381552ms) + AssertionError [ERR_ASSERTION]: HEAD didn't move + + actual - expected + + + '850b80d2dca1b6131375eda3b0426e25c8c5963e' + - '66f9e27bc4398f2a98de25bbe563e070e1d887ab' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:165:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156: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: '850b80d2dca1b6131375eda3b0426e25c8c5963e', + expected: '66f9e27bc4398f2a98de25bbe563e070e1d887ab', + operator: 'strictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ a staged file (50.774577ms) + AssertionError [ERR_ASSERTION]: HEAD didn't move + + actual - expected + + + '850b80d2dca1b6131375eda3b0426e25c8c5963e' + - '66f9e27bc4398f2a98de25bbe563e070e1d887ab' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:165:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156: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: '850b80d2dca1b6131375eda3b0426e25c8c5963e', + expected: '66f9e27bc4398f2a98de25bbe563e070e1d887ab', + operator: 'strictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ another branch (47.49936ms) + AssertionError [ERR_ASSERTION]: HEAD didn't move + + actual - expected + + + '850b80d2dca1b6131375eda3b0426e25c8c5963e' + - '66f9e27bc4398f2a98de25bbe563e070e1d887ab' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:165:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156: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: '850b80d2dca1b6131375eda3b0426e25c8c5963e', + expected: '66f9e27bc4398f2a98de25bbe563e070e1d887ab', + operator: 'strictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ a detached HEAD (52.398743ms) + AssertionError [ERR_ASSERTION]: HEAD didn't move + + actual - expected + + + '850b80d2dca1b6131375eda3b0426e25c8c5963e' + - '66f9e27bc4398f2a98de25bbe563e070e1d887ab' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:165:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156: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: '850b80d2dca1b6131375eda3b0426e25c8c5963e', + expected: '66f9e27bc4398f2a98de25bbe563e070e1d887ab', + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-117/r1/mutants/mutant-m3.log b/agents/darkwing/work/queue-117/r1/mutants/mutant-m3.log new file mode 100644 index 000000000..de5c94d83 --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants/mutant-m3.log @@ -0,0 +1,338 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (32.422371ms) +✔ the clone is not at its place until git clone has finished (23.734352ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (70.039991ms) +✔ a clone ahead of the root keeps its commits and counts as current (41.235356ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✖ a modified file (35.036054ms) + ✖ an untracked file (36.35993ms) + ✖ a staged file (35.342157ms) + ✖ another branch (38.526769ms) + ✖ a detached HEAD (32.202485ms) +✖ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (178.716659ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (46.029752ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (23.731593ms) + ✔ hooks-present (30.135891ms) + ✔ submodule (31.870731ms) + ✔ config-changed (27.616265ms) + ✔ root-unreadable (30.303697ms) + ✔ root-detached (33.831402ms) + ✔ status-failed, on the root's branch and clean (36.143813ms) + ✔ status-failed, dirty (36.137271ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (251.017655ms) +✖ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (43.42291ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (27.987488ms) + ✔ user.email (27.548155ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (56.008181ms) +✔ a commit git can't read after the fast-forward is null (45.904141ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (20.176657ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (33.567677ms) + ✔ core.hooksPath (35.682293ms) + ✔ a hook file (32.848006ms) + ✔ include.path (31.984353ms) + ✔ includeIf (34.956942ms) + ✔ a filter driver (35.12301ms) + ✔ origin moved (34.967369ms) + ✔ a second origin url (32.382951ms) + ✔ the origin url twice, both the root (33.848494ms) + ✔ an ext:: origin (34.633625ms) + ✔ a branch tracking another remote (36.564908ms) + ✔ a branch merging a tag (36.693997ms) + ✔ a fetch line onto local branches (36.429279ms) + ✔ no origin (35.378934ms) + ✔ a config file over the size cap (39.127482ms) + ✔ config a symlink to /dev/zero (34.98588ms) + ✔ the clone a symlink to the bait (40.911547ms) + ✔ the clone a file (40.22042ms) + ✔ the clone a plain directory (31.872593ms) + ✔ .git a gitdir: file (37.914763ms) + ✔ .git a symlink (36.566486ms) + ✔ .git/logs a symlink (32.741395ms) + ✔ .git/refs/heads a symlink (33.229072ms) + ✔ .git/objects a symlink (33.742728ms) + ✔ a reflog hard-linked to a file outside (33.372463ms) + ✔ HEAD a fifo (42.940848ms) + ✔ a commondir file (44.660163ms) + ✔ an alternates file (40.053438ms) + ✔ a gitlink staged, no .gitmodules (50.958132ms) + ✔ a gitlink and .gitmodules staged (60.314558ms) + ✔ a gitlink to a repository with its own gitlink (61.359686ms) + ✔ a gitlink and .gitmodules committed (52.283208ms) + ✔ a gitlink and submodule.active in config (43.602039ms) + ✔ a .git/modules with no gitlink (36.491613ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1316.954576ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.883744ms) + ✔ not a repository (9.753019ms) + ✔ a plain directory inside another repository (16.308156ms) + ✔ unreadable (10.107674ms) + ✔ on no branch (11.658397ms) + ✔ missing an object (14.916928ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (74.441329ms) +✔ a clone whose root is on no branch, or gone, is kept and named (29.938141ms) +✔ no credential or forge remote of the root reaches a clone (53.204083ms) +✔ every project in the business file gets its own clone at its own branch (35.041182ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (513.801205ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1020.339202ms) +✔ a timeout signals git's process group, so a process git started dies with it (521.809823ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (514.868006ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.568443ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (7.848833ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (1.479795ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (0.666903ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.066463ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1805.273153ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2508.517517ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (261.920744ms) +✔ resolveSeat: by name under --repo resolves the repo layout (1.27774ms) +✔ resolveSeat: by path resolves the fleet layout (0.306119ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.702128ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.646637ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.78287ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.274049ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.110304ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (29.801462ms) +✔ CLI launch: --harness lands in the record (27.644946ms) +✔ CLI launch: the launch script's own exit code passes through (27.069949ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (56.732882ms) +✔ CLI launch: omitting --task records an empty string, not null (28.850269ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (81.943786ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (138.262709ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.392364ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.56812ms) +✔ 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.669122ms) +✔ 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.187524ms) +✔ 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 (271.120495ms) +✔ family: exactly one launch.max key in the model name, else null (0.676046ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.669847ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.244141ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.438292ms) +✔ session file and launch log: 0600, the session file written once (0.996117ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.116533ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.175266ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.940241ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (343.442053ms) +✔ workRef and validBranch take only what a ref name can hold (1.109176ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (154.384844ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (145.480342ms) +✔ a branch with no history in common with main refuses unrelated-history (61.246495ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (42.253325ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (145.436075ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (82.080485ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (79.870336ms) +✔ only a v2 bundle with the named branch is read (46.169751ms) +✔ a branch named main, or named for another task, moves only this task's work ref (90.289544ms) +✔ a record that throws moves the work ref back, or removes a new one (152.038053ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (390.022289ms) +✔ a root that can't be read refuses root-unreadable (41.953805ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2173.207202ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1028.882202ms) +✔ a working repository path that isn't a directory refuses (24.966327ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (114.471066ms) +✔ review of a project with no working repository refuses unknown-commit (12.936522ms) +✔ the runner's git tools hold the host's limits and branch rule (1.137398ms) +ℹ tests 130 +ℹ suites 0 +ℹ pass 123 +ℹ fail 7 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4941.188529 + +✖ failing tests: + +test at tests/checkout.test.mjs:156:13 +✖ a modified file (35.036054ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'main', + commit: 'c67742b9449c0902831b482e9367878f802ff5a5', + origin: { + branch: 'main', + + commit: 'c67742b9449c0902831b482e9367878f802ff5a5' + - commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-z3Rxrl/ws/stack', + project: 'stack', + reason: 'dirty' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-z3Rxrl/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-z3Rxrl/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ an untracked file (36.35993ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'main', + commit: 'c67742b9449c0902831b482e9367878f802ff5a5', + origin: { + branch: 'main', + + commit: 'c67742b9449c0902831b482e9367878f802ff5a5' + - commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-glAUqj/ws/stack', + project: 'stack', + reason: 'dirty' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-glAUqj/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-glAUqj/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ a staged file (35.342157ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'main', + commit: 'c67742b9449c0902831b482e9367878f802ff5a5', + origin: { + branch: 'main', + + commit: 'c67742b9449c0902831b482e9367878f802ff5a5' + - commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-qoaSps/ws/stack', + project: 'stack', + reason: 'dirty' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-qoaSps/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-qoaSps/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ another branch (38.526769ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'work', + commit: 'c67742b9449c0902831b482e9367878f802ff5a5', + origin: { + branch: 'main', + + commit: 'c67742b9449c0902831b482e9367878f802ff5a5' + - commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-WPod67/ws/stack', + project: 'stack', + reason: 'other-branch' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-WPod67/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: 'work', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-WPod67/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: 'work', commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ a detached HEAD (32.202485ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: null, + commit: 'c67742b9449c0902831b482e9367878f802ff5a5', + origin: { + branch: 'main', + + commit: 'c67742b9449c0902831b482e9367878f802ff5a5' + - commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-KnVXqg/ws/stack', + project: 'stack', + reason: 'other-branch' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-KnVXqg/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: null, commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: 'c67742b9449c0902831b482e9367878f802ff5a5' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-KnVXqg/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: null, commit: 'c67742b9449c0902831b482e9367878f802ff5a5', origin: { branch: 'main', commit: '9f0d6d9d564072bbbd5a33345a43504cb5122f44' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:229:1 +✖ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (43.42291ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + { + + branch: 'main', + + commit: '442c8937416e622cc350d0dadaf05413ebe0bfc9', + + origin: { + + branch: 'main', + + commit: '442c8937416e622cc350d0dadaf05413ebe0bfc9' + + }, + + outcome: 'fetched', + - outcome: 'kept', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-SRpWE8/ws/stack', + project: 'stack', + + reason: 'dirty' + - reason: 'fetch-failed' + } + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:238: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: true, + code: 'ERR_ASSERTION', + actual: [ { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-SRpWE8/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: '442c8937416e622cc350d0dadaf05413ebe0bfc9', origin: [Object] } ], + expected: [ { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-SRpWE8/ws/stack', outcome: 'kept', reason: 'fetch-failed' } ], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-117/r1/mutants/mutant-m4.log b/agents/darkwing/work/queue-117/r1/mutants/mutant-m4.log new file mode 100644 index 000000000..90be3a50f --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants/mutant-m4.log @@ -0,0 +1,303 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (39.324711ms) +✔ the clone is not at its place until git clone has finished (23.636226ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (78.710083ms) +✔ a clone ahead of the root keeps its commits and counts as current (47.053666ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✖ a modified file (43.573265ms) + ✖ an untracked file (42.434504ms) + ✖ a staged file (45.694781ms) + ✖ another branch (45.576595ms) + ✖ a detached HEAD (44.212281ms) +✖ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (222.779496ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (46.760589ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (27.534123ms) + ✔ hooks-present (24.025041ms) + ✔ submodule (30.868885ms) + ✔ config-changed (31.604066ms) + ✔ root-unreadable (27.761625ms) + ✔ root-detached (35.490432ms) + ✔ status-failed, on the root's branch and clean (37.234007ms) + ✔ status-failed, dirty (40.068785ms) +✔ a clone kept before the branch check, or for a failed status, gets no fetch (255.819703ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (36.888307ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (26.660548ms) + ✔ user.email (26.090804ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (53.224564ms) +✔ a commit git can't read after the fast-forward is null (48.118811ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (20.641037ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (32.430284ms) + ✔ core.hooksPath (33.631744ms) + ✔ a hook file (31.690423ms) + ✔ include.path (32.728677ms) + ✔ includeIf (32.577378ms) + ✔ a filter driver (34.830418ms) + ✔ origin moved (33.27252ms) + ✔ a second origin url (33.283119ms) + ✔ the origin url twice, both the root (36.840815ms) + ✔ an ext:: origin (31.086241ms) + ✔ a branch tracking another remote (31.69866ms) + ✔ a branch merging a tag (32.179755ms) + ✔ a fetch line onto local branches (37.52461ms) + ✔ no origin (36.015933ms) + ✔ a config file over the size cap (32.741931ms) + ✔ config a symlink to /dev/zero (29.931967ms) + ✔ the clone a symlink to the bait (29.905236ms) + ✔ the clone a file (42.725683ms) + ✔ the clone a plain directory (43.649632ms) + ✔ .git a gitdir: file (45.796795ms) + ✔ .git a symlink (40.980634ms) + ✔ .git/logs a symlink (33.589349ms) + ✔ .git/refs/heads a symlink (31.816209ms) + ✔ .git/objects a symlink (30.862204ms) + ✔ a reflog hard-linked to a file outside (31.00904ms) + ✔ HEAD a fifo (32.059038ms) + ✔ a commondir file (30.617635ms) + ✔ an alternates file (32.736399ms) + ✔ a gitlink staged, no .gitmodules (102.033554ms) + ✔ a gitlink and .gitmodules staged (50.414983ms) + ✔ a gitlink to a repository with its own gitlink (55.662337ms) + ✔ a gitlink and .gitmodules committed (47.598547ms) + ✔ a gitlink and submodule.active in config (46.109249ms) + ✔ a .git/modules with no gitlink (33.082086ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1297.546351ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (12.87466ms) + ✔ not a repository (10.589764ms) + ✔ a plain directory inside another repository (16.960167ms) + ✔ unreadable (10.139431ms) + ✔ on no branch (11.651429ms) + ✔ missing an object (17.01585ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (80.098369ms) +✔ a clone whose root is on no branch, or gone, is kept and named (28.741289ms) +✔ no credential or forge remote of the root reaches a clone (50.921463ms) +✔ every project in the business file gets its own clone at its own branch (36.997869ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (518.235428ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1017.027319ms) +✔ a timeout signals git's process group, so a process git started dies with it (516.067976ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (519.895069ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (1.701181ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (8.127546ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (1.339912ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (0.702713ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.073803ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1814.820402ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2719.345628ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (283.302021ms) +✔ resolveSeat: by name under --repo resolves the repo layout (1.346606ms) +✔ resolveSeat: by path resolves the fleet layout (0.366924ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.806509ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.902343ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.124741ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (2.276711ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.92332ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (29.893449ms) +✔ CLI launch: --harness lands in the record (27.634505ms) +✔ CLI launch: the launch script's own exit code passes through (28.947418ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (73.517187ms) +✔ CLI launch: omitting --task records an empty string, not null (27.000497ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (80.987481ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (129.928178ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.405292ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.571428ms) +✔ 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.755602ms) +✔ 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.202885ms) +✔ 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 (267.09476ms) +✔ family: exactly one launch.max key in the model name, else null (0.686229ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.690599ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.307979ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.453992ms) +✔ session file and launch log: 0600, the session file written once (1.023817ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.111435ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (3.639612ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.999118ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (304.043598ms) +✔ workRef and validBranch take only what a ref name can hold (1.140622ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (163.761375ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (137.180228ms) +✔ a branch with no history in common with main refuses unrelated-history (69.303041ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (39.603898ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (149.741556ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (99.729272ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (77.007304ms) +✔ only a v2 bundle with the named branch is read (50.160654ms) +✔ a branch named main, or named for another task, moves only this task's work ref (92.683766ms) +✔ a record that throws moves the work ref back, or removes a new one (144.366856ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (412.871579ms) +✔ a root that can't be read refuses root-unreadable (46.193169ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2193.691433ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1029.288816ms) +✔ a working repository path that isn't a directory refuses (26.198965ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (145.001707ms) +✔ review of a project with no working repository refuses unknown-commit (17.228548ms) +✔ the runner's git tools hold the host's limits and branch rule (1.438535ms) +ℹ tests 130 +ℹ suites 0 +ℹ pass 124 +ℹ fail 6 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 5001.593137 + +✖ failing tests: + +test at tests/checkout.test.mjs:156:13 +✖ a modified file (43.573265ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'main', + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', + origin: { + branch: 'main', + + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' + - commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-jHmC0L/ws/stack', + project: 'stack', + reason: 'dirty' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-jHmC0L/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-jHmC0L/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ an untracked file (42.434504ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'main', + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', + origin: { + branch: 'main', + + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' + - commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-bxr5Qj/ws/stack', + project: 'stack', + reason: 'dirty' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-bxr5Qj/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-bxr5Qj/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ a staged file (45.694781ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'main', + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', + origin: { + branch: 'main', + + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' + - commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-XaLl38/ws/stack', + project: 'stack', + reason: 'dirty' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-XaLl38/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-XaLl38/ws/stack', outcome: 'fetched', reason: 'dirty', branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ another branch (45.576595ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: 'work', + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', + origin: { + branch: 'main', + + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' + - commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-RpvBCm/ws/stack', + project: 'stack', + reason: 'other-branch' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-RpvBCm/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: 'work', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-RpvBCm/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: 'work', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:156:13 +✖ a detached HEAD (44.212281ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + branch: null, + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', + origin: { + branch: 'main', + + commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' + - commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' + }, + outcome: 'fetched', + path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-ndZLbL/ws/stack', + project: 'stack', + reason: 'other-branch' + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:163:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:156:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-ndZLbL/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: null, commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a' } }, + expected: { project: 'stack', path: '/home/jwoltje/darkwing-scratch/r117/tmp/mosaic-checkout-ndZLbL/ws/stack', outcome: 'fetched', reason: 'other-branch', branch: null, commit: 'f090f4135f67d0071795fea0c2df56e82b7c4e8a', origin: { branch: 'main', commit: 'c31e0e282d8ca409326dbfc49b48dd295fe370fd' } }, + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/queue-117/r1/mutants/mutant-m5.log b/agents/darkwing/work/queue-117/r1/mutants/mutant-m5.log new file mode 100644 index 000000000..5b1d59b0b --- /dev/null +++ b/agents/darkwing/work/queue-117/r1/mutants/mutant-m5.log @@ -0,0 +1,267 @@ +✔ a first launch clones the root at its branch, with the root's path as origin (60.113765ms) +✔ the clone is not at its place until git clone has finished (28.119496ms) +✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (98.456581ms) +✔ a clone ahead of the root keeps its commits and counts as current (65.128961ms) +▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves + ✔ a modified file (90.443065ms) + ✔ an untracked file (63.427935ms) + ✔ a staged file (77.673844ms) + ✔ another branch (59.458881ms) + ✔ a detached HEAD (72.020436ms) +✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (364.895255ms) +✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (122.839918ms) +▶ a clone kept before the branch check, or for a failed status, gets no fetch + ✔ not-a-clone (55.421185ms) + ✔ hooks-present (34.062835ms) + ✖ submodule (53.196042ms) + ✔ config-changed (41.044582ms) + ✖ root-unreadable (41.325292ms) + ✖ root-detached (53.434429ms) + ✖ status-failed, on the root's branch and clean (64.900111ms) + ✖ status-failed, dirty (69.250121ms) +✖ a clone kept before the branch check, or for a failed status, gets no fetch (414.568082ms) +✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (61.081166ms) +▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it + ✔ user.name (39.675005ms) + ✔ user.email (41.990397ms) +✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (82.390681ms) +✔ a commit git can't read after the fast-forward is null (85.0704ms) +✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (29.23853ms) +▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it + ✔ core.fsmonitor (50.42918ms) + ✔ core.hooksPath (37.470859ms) + ✔ a hook file (30.764092ms) + ✔ include.path (32.554397ms) + ✔ includeIf (33.112383ms) + ✔ a filter driver (32.947284ms) + ✔ origin moved (33.236103ms) + ✔ a second origin url (34.176264ms) + ✔ the origin url twice, both the root (33.69893ms) + ✔ an ext:: origin (34.489267ms) + ✔ a branch tracking another remote (35.471006ms) + ✔ a branch merging a tag (33.481371ms) + ✔ a fetch line onto local branches (36.652386ms) + ✔ no origin (41.699092ms) + ✔ a config file over the size cap (35.121935ms) + ✔ config a symlink to /dev/zero (34.431201ms) + ✔ the clone a symlink to the bait (31.588121ms) + ✔ the clone a file (29.566531ms) + ✔ the clone a plain directory (30.794251ms) + ✔ .git a gitdir: file (31.110095ms) + ✔ .git a symlink (28.727853ms) + ✔ .git/logs a symlink (31.397765ms) + ✔ .git/refs/heads a symlink (31.731121ms) + ✔ .git/objects a symlink (31.906175ms) + ✔ a reflog hard-linked to a file outside (32.032149ms) + ✔ HEAD a fifo (31.511203ms) + ✔ a commondir file (37.320484ms) + ✔ an alternates file (32.796885ms) + ✔ a gitlink staged, no .gitmodules (52.165917ms) + ✔ a gitlink and .gitmodules staged (56.287397ms) + ✔ a gitlink to a repository with its own gitlink (62.376895ms) + ✔ a gitlink and .gitmodules committed (53.499042ms) + ✔ a gitlink and submodule.active in config (46.90971ms) + ✔ a .git/modules with no gitlink (33.962005ms) +✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1259.891978ms) +▶ a root that can't be cloned refuses with the project id and leaves nothing behind + ✔ missing (10.162642ms) + ✔ not a repository (9.70007ms) + ✔ a plain directory inside another repository (14.175099ms) + ✔ unreadable (10.547341ms) + ✔ on no branch (11.549974ms) + ✔ missing an object (15.261059ms) +✔ a root that can't be cloned refuses with the project id and leaves nothing behind (72.19684ms) +✔ a clone whose root is on no branch, or gone, is kept and named (36.862621ms) +✔ no credential or forge remote of the root reaches a clone (54.208341ms) +✔ every project in the business file gets its own clone at its own branch (35.544126ms) +✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (514.774723ms) +✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1017.716397ms) +✔ a timeout signals git's process group, so a process git started dies with it (516.346504ms) +✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (514.85433ms) +✔ RUNNER_FILES is exactly what the runner and the harness sources it starts import (2.979345ms) +✔ sandboxProblem names a missing bwrap and a bwrap that fails (15.158928ms) +✔ every bind says why; the bundle and session file stay read-only over the run directory (2.324857ms) +✔ the host's git socket is bound only when given, read-only and before the writable binds (1.062074ms) +✔ the git identity is the instance's, at @.mosaic.invalid, and sandboxArgv refuses without it (1.65171ms) +✔ a confined session's shell reaches its own workspace and run directory and nothing of the founder's or another session's (1804.262635ms) +✔ a bind that exposes a stand-in, or a missing workspace bind, fails the matching check (2673.865664ms) +✔ a confined session that runs tester-cli.mjs is refused as tester, and the same shim outside the sandbox binds (258.400861ms) +✔ resolveSeat: by name under --repo resolves the repo layout (2.505098ms) +✔ resolveSeat: by path resolves the fleet layout (0.625646ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (1.361282ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (1.166557ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.393855ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.880853ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (3.084632ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (47.423354ms) +✔ CLI launch: --harness lands in the record (35.84288ms) +✔ CLI launch: the launch script's own exit code passes through (35.714261ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (76.682585ms) +✔ CLI launch: omitting --task records an empty string, not null (39.022668ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (119.697211ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (174.380005ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.464981ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.641704ms) +✔ 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.8301ms) +✔ 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.320691ms) +✔ 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 (359.488393ms) +✔ family: exactly one launch.max key in the model name, else null (1.446364ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (1.638238ms) +✔ sessionExit gives a runner's signal death back as the signal; a session credential is one whole set of nonempty variables (0.535712ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (2.956422ms) +✔ session file and launch log: 0600, the session file written once (2.053443ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.233688ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (2.604399ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.858788ms) +✔ a session runs in its sandbox as pid 1 of its namespace, claims, answers, and stops on mosaic stop (368.186072ms) +✔ workRef and validBranch take only what a ref name can hold (2.221759ms) +✔ a push lands as the task's work ref, a fast-forward lands after it, and main is the root's (196.873211ms) +✔ a rewritten branch refuses non-fast-forward and the work ref stays (182.400319ms) +✔ a branch with no history in common with main refuses unrelated-history (93.239013ms) +✔ a bundle that needs commits the working repository lacks refuses missing-prerequisite (46.157976ms) +✔ main is fetched from the root again before each push, so a clone of a newer root pushes (272.364925ms) +✔ a root moved back after the clone leaves the push's base unknown: missing-prerequisite (113.327989ms) +✔ a commit with a broken object refuses invalid-bundle, and only because fsck is on (103.726819ms) +✔ only a v2 bundle with the named branch is read (66.443151ms) +✔ a branch named main, or named for another task, moves only this task's work ref (130.976917ms) +✔ a record that throws moves the work ref back, or removes a new one (195.355547ms) +✔ a work ref that can't go back refuses rollback-failed, naming the ref and the commit (410.97453ms) +✔ a root that can't be read refuses root-unreadable (32.611766ms) +✔ a git past its time gets SIGTERM, leaves no ref lock, and the next push lands (2182.446857ms) +✔ a git that ignores SIGTERM gets SIGKILL once the grace period is over (1029.632319ms) +✔ a working repository path that isn't a directory refuses (22.986674ms) +✔ review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has (112.242936ms) +✔ review of a project with no working repository refuses unknown-commit (12.846414ms) +✔ the runner's git tools hold the host's limits and branch rule (1.13945ms) +ℹ tests 130 +ℹ suites 0 +ℹ pass 124 +ℹ fail 6 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 5522.214178 + +✖ failing tests: + +test at tests/checkout.test.mjs:213:13 +✖ submodule (53.196042ms) + AssertionError [ERR_ASSERTION]: no fetch, and no git past the check that kept it + + actual - expected + + [ + + 'fetch', + 'ls-files' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:223:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213: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: [ 'fetch', 'ls-files' ], + expected: [ 'ls-files' ], + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:213:13 +✖ root-unreadable (41.325292ms) + AssertionError [ERR_ASSERTION]: no fetch, and no git past the check that kept it + + actual - expected + + [ + + 'fetch', + 'ls-files' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:223:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213: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: [ 'fetch', 'ls-files' ], + expected: [ 'ls-files' ], + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:213:13 +✖ root-detached (53.434429ms) + AssertionError [ERR_ASSERTION]: no fetch, and no git past the check that kept it + + actual - expected + + [ + + 'fetch', + 'ls-files' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:223:14) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213: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: [ 'fetch', 'ls-files' ], + expected: [ 'ls-files' ], + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:213:13 +✖ status-failed, on the root's branch and clean (64.900111ms) + AssertionError [ERR_ASSERTION]: no fetch, and no git past the check that kept it + + actual - expected + + [ + + 'fetch', + 'ls-files', + 'symbolic-ref', + 'status' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:223:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213: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: [ 'fetch', 'ls-files', 'symbolic-ref', 'status' ], + expected: [ 'ls-files', 'symbolic-ref', 'status' ], + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at tests/checkout.test.mjs:213:13 +✖ status-failed, dirty (69.250121ms) + AssertionError [ERR_ASSERTION]: no fetch, and no git past the check that kept it + + actual - expected + + [ + + 'fetch', + 'ls-files', + 'symbolic-ref', + 'status' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:223:14) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r117/wt/packages/seat/tests/checkout.test.mjs:213: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: [ 'fetch', 'ls-files', 'symbolic-ref', 'status' ], + expected: [ 'ls-files', 'symbolic-ref', 'status' ], + operator: 'deepStrictEqual', + diff: 'simple' + }