Files
stack/packages/seat/tests/working.test.mjs
T
jason.woltjeandClaude Opus 5.5 4da6815293 review loop: working repositories, git socket, push and review verdicts, row 93 (#1587)
Commits move as v2 bundles over bus-host/git.sock (decision 135). The bus
host keeps one bare working repository per business and project, seeds main
from the project root under a per-repository lock, fetches a pushed bundle
with fsck into a temp ref, requires history shared with main, and moves the
task's work ref only as a fast-forward. Host and runner git run with no
system or global config, hooks at /dev/null and local transports only. The
socket binds only for roles holding git.push.working or review.verdict.

The broker records a push as the action.allowed for git.push.working with
its commit, takes review.request for the latest push and one review.verdict
per request, and refuses task.close on a pushed task without an approve
verdict for the latest push. recordEvent reserves both shapes, so agents
can't forge either.

Reviewed by Dewey (28196) and Sage (28197), both approve. Gate on 7a729aef:
14 node suites and 9 scripts green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 10:48:34 -05:00

281 lines
13 KiB
JavaScript

// The working repositories (src/working.mjs, #1587), against real git
// repositories in a temp directory. The bundles are made the way a
// session's runner makes them (packages/harness/src/git-tools.mjs). Every
// case also checks that the root's files and refs are byte for byte what
// they were, that no temp ref is left, and that only the task's own ref moved.
import { test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, readlinkSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { gitEnv } from "../src/checkout.mjs";
import { push, review, validBranch, workingRepo, workRef, WorkingError } from "../src/working.mjs";
const TASK = "vikunja:1/2";
const REF = "refs/heads/work/vikunja-1-2";
function tmp(t) {
const dir = mkdtempSync(join(tmpdir(), "mosaic-working-"));
t.after(() => rmSync(dir, { recursive: true, force: true }));
return dir;
}
function git(cwd, ...args) {
const r = spawnSync("git", ["-c", "user.name=t", "-c", "[email protected]", ...args], { cwd, encoding: "utf8", env: gitEnv() });
assert.equal(r.status, 0, `git ${args.join(" ")}: ${r.stderr}`);
return r.stdout.trim();
}
function commit(dir, text) {
writeFileSync(join(dir, "README"), `${text}\n`);
git(dir, "add", "README");
git(dir, "commit", "-q", "-m", text);
return git(dir, "rev-parse", "HEAD");
}
function fingerprint(dir) {
const h = createHash("sha256");
const walk = (d) => {
for (const name of readdirSync(d).sort()) {
const p = join(d, name);
const st = lstatSync(p);
h.update(`${p}\0${st.mode}\0`);
if (st.isDirectory()) walk(p);
else if (st.isSymbolicLink()) h.update(readlinkSync(p));
else if (st.isFile()) h.update(readFileSync(p));
}
};
walk(dir);
return h.digest("hex");
}
// A root on main with one commit, a coder's clone of it, and the data root.
function world(t) {
const dir = tmp(t);
const root = join(dir, "root");
mkdirSync(root);
git(root, "init", "-q", "-b", "main");
const first = commit(root, "one");
const clone = join(dir, "clone");
git(dir, "clone", "-q", root, clone);
const dataRoot = join(dir, "data");
mkdirSync(dataRoot, { mode: 0o700 });
const repo = workingRepo(dataRoot, "acme", "stack");
let n = 0;
// A bundle of `branch` the way the runner makes one.
const bundle = (branch = "feat", from = clone) => {
const file = join(dir, `b${n++}.bundle`);
git(from, "bundle", "create", "-q", file, `refs/heads/${branch}`, "--not", "--remotes=origin", "--remotes=working");
return file;
};
const refs = () => (existsSync(repo) && lstatSync(repo).isDirectory() ? git(dir, `--git-dir=${repo}`, "for-each-ref", "--format=%(refname) %(objectname)") : "");
const go = (file, extra = {}) => push({ dataRoot, business: "acme", project: "stack", root, taskRef: TASK, branch: "feat", bundle: file, ...extra });
return { dir, root, first, clone, dataRoot, repo, bundle, refs, go };
}
// A refusal with `code`, and the root, the working repository's refs and its
// temp refs as they were.
async function refuses(w, fn, code) {
const root = fingerprint(w.root);
const refs = w.refs();
await assert.rejects(async () => fn(), (e) => e instanceof WorkingError && e.code === code);
assert.equal(fingerprint(w.root), root, "the root is unchanged");
if (refs) assert.equal(w.refs(), refs, "no ref of the working repository moved");
assert.doesNotMatch(w.refs(), /refs\/mosaic\//, "no temp ref is left");
}
async function lands(w, fn) {
const root = fingerprint(w.root);
const r = await fn();
assert.equal(fingerprint(w.root), root, "the root is unchanged");
assert.doesNotMatch(w.refs(), /refs\/mosaic\//, "no temp ref is left");
return r;
}
test("workRef and validBranch take only what a ref name can hold", () => {
assert.equal(workRef("vikunja:12/345"), "work/vikunja-12-345");
for (const bad of ["vikunja:0/1", "vikunja:1", "gitea:1/2", "vikunja:1/2\n", null]) assert.throws(() => workRef(bad), WorkingError);
for (const ok of ["feat", "fix/thing-2", "a.b_c"]) assert.equal(validBranch(ok), true, ok);
for (const bad of ["", "-x", "a..b", "a//b", "a/.b", "a@{1}", "x.lock", "a/", "a.", "a b", "a:b", "a~1", "a".repeat(201)]) assert.equal(validBranch(bad), false, bad);
});
test("a push lands as the task's work ref, a fast-forward lands after it, and main is the root's", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
const c1 = commit(w.clone, "two");
const seen = [];
const r1 = await lands(w, () => w.go(w.bundle(), { record: async (r) => seen.push(r) }));
assert.deepEqual(r1, { ref: "work/vikunja-1-2", commit: c1, previous: null });
assert.deepEqual(seen, [r1]);
assert.equal(w.refs(), `refs/heads/main ${w.first}\n${REF} ${c1}`);
// The runner's clone remembers the push, so the next bundle carries only c2.
git(w.clone, "update-ref", "refs/remotes/working/work/vikunja-1-2", c1);
const c2 = commit(w.clone, "three");
const file = w.bundle();
assert.match(git(w.dir, "bundle", "list-heads", file), new RegExp(c2));
const r2 = await lands(w, () => w.go(file));
assert.deepEqual(r2, { ref: "work/vikunja-1-2", commit: c2, previous: c1 });
assert.equal(w.refs(), `refs/heads/main ${w.first}\n${REF} ${c2}`);
});
test("a rewritten branch refuses non-fast-forward and the work ref stays", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
commit(w.clone, "two");
await w.go(w.bundle());
git(w.clone, "reset", "-q", "--hard", w.first);
commit(w.clone, "other two");
await refuses(w, () => w.go(w.bundle()), "non-fast-forward");
});
test("a branch with no history in common with main refuses unrelated-history", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "--orphan", "feat");
git(w.clone, "rm", "-q", "-rf", ".");
commit(w.clone, "alone");
await refuses(w, () => w.go(w.bundle()), "unrelated-history");
assert.equal(w.refs(), `refs/heads/main ${w.first}`);
});
test("a bundle that needs commits the working repository lacks refuses missing-prerequisite", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
const local = commit(w.clone, "never pushed");
commit(w.clone, "on top");
const file = join(w.dir, "partial.bundle");
git(w.clone, "bundle", "create", "-q", file, "refs/heads/feat", "--not", local);
await refuses(w, () => w.go(file), "missing-prerequisite");
});
test("main is fetched from the root again before each push, so a clone of a newer root pushes", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
commit(w.clone, "two");
await w.go(w.bundle());
// The root moves on; a second coder clones it and branches from there.
const newer = commit(w.root, "root two");
const other = join(w.dir, "other");
git(w.dir, "clone", "-q", w.root, other);
git(other, "checkout", "-q", "-b", "feat");
const c = commit(other, "from newer");
const r = await lands(w, () => push({ dataRoot: w.dataRoot, business: "acme", project: "stack", root: w.root, taskRef: "vikunja:1/3", branch: "feat", bundle: w.bundle("feat", other) }));
assert.equal(r.commit, c);
assert.match(w.refs(), new RegExp(`refs/heads/main ${newer}`));
});
test("a root moved back after the clone leaves the push's base unknown: missing-prerequisite", async (t) => {
const w = world(t);
commit(w.root, "root two");
git(w.clone, "pull", "-q", "--ff-only");
git(w.root, "reset", "-q", "--hard", w.first);
git(w.root, "reflog", "expire", "--expire=now", "--all");
git(w.root, "gc", "-q", "--prune=now");
git(w.clone, "checkout", "-q", "-b", "feat");
commit(w.clone, "three");
await refuses(w, () => w.go(w.bundle()), "missing-prerequisite");
});
test("a commit with a broken object refuses invalid-bundle, and only because fsck is on", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
const tree = git(w.clone, "rev-parse", "HEAD^{tree}");
const raw = `tree ${tree}\nparent ${w.first}\nauthor nobody\ncommitter t <[email protected]> 1700000000 +0000\n\nbad\n`;
const r = spawnSync("git", ["hash-object", "-t", "commit", "-w", "--literally", "--stdin"], { cwd: w.clone, input: raw, encoding: "utf8", env: gitEnv() });
assert.equal(r.status, 0, r.stderr);
git(w.clone, "update-ref", "refs/heads/feat", r.stdout.trim());
const file = w.bundle();
await refuses(w, () => w.go(file), "invalid-bundle");
assert.doesNotMatch(w.refs(), /work\//);
// The same bundle with no fsck lands: the refusal above is fsck's.
const landed = await w.go(file, { fsck: [] });
assert.equal(landed.commit, r.stdout.trim());
});
test("only a v2 bundle with the named branch is read", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
commit(w.clone, "two");
const v2 = w.bundle();
const v3 = join(w.dir, "v3.bundle");
writeFileSync(v3, readFileSync(v2, "latin1").replace("# v2 git bundle\n", "# v3 git bundle\n@object-format=sha1\n"), "latin1");
await refuses(w, () => w.go(v3), "invalid-bundle");
const junk = join(w.dir, "junk.bundle");
writeFileSync(junk, "# v2 git bundle\nnot a bundle\n");
await refuses(w, () => w.go(junk), "invalid-bundle");
await refuses(w, () => push({ dataRoot: w.dataRoot, business: "acme", project: "stack", root: w.root, taskRef: TASK, branch: "other", bundle: v2 }), "branch-not-in-bundle");
});
test("a branch named main, or named for another task, moves only this task's work ref", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "work/vikunja-9-9");
const c = commit(w.clone, "two");
git(w.clone, "branch", "-f", "main", c);
for (const branch of ["main", "work/vikunja-9-9"]) {
const r = await lands(w, () => push({ dataRoot: w.dataRoot, business: "acme", project: "stack", root: w.root, taskRef: TASK, branch, bundle: w.bundle(branch) }));
assert.equal(r.ref, "work/vikunja-1-2");
}
assert.equal(w.refs(), `refs/heads/main ${w.first}\n${REF} ${c}`);
});
test("a record that throws moves the work ref back, or removes a new one", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
commit(w.clone, "two");
const boom = async () => {
throw new Error("not recorded");
};
await assert.rejects(w.go(w.bundle(), { record: boom }), /not recorded/);
assert.equal(w.refs(), `refs/heads/main ${w.first}`);
const c1 = (await w.go(w.bundle())).commit;
commit(w.clone, "three");
await assert.rejects(w.go(w.bundle(), { record: boom }), /not recorded/);
assert.equal(w.refs(), `refs/heads/main ${w.first}\n${REF} ${c1}`);
});
test("a working repository path that isn't a directory refuses", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
commit(w.clone, "two");
mkdirSync(join(w.dataRoot, "working", "acme"), { recursive: true });
writeFileSync(w.repo, "");
await refuses(w, () => w.go(w.bundle()), "working-repo-invalid");
});
test("review bundles a pushed commit as refs/mosaic/review, leaving out what the reviewer has", async (t) => {
const w = world(t);
git(w.clone, "checkout", "-q", "-b", "feat");
const c1 = commit(w.clone, "two");
await w.go(w.bundle());
const out = join(w.dir, "review.bundle");
const args = { dataRoot: w.dataRoot, business: "acme", project: "stack", root: w.root, taskRef: TASK, commit: c1, out };
assert.deepEqual(await lands(w, () => review({ ...args, have: [w.first, "f".repeat(40)] })), { ref: "work/vikunja-1-2", commit: c1 });
assert.equal(git(w.dir, "bundle", "list-heads", out), `${c1} refs/mosaic/review`);
// Only the one commit: the reviewer has main.
assert.match(git(w.dir, `--git-dir=${w.repo}`, "bundle", "verify", out), /requires this ref/);
rmSync(out);
await refuses(w, () => review({ ...args, commit: "e".repeat(40) }), "unknown-commit");
await refuses(w, () => review({ ...args, have: ["x"] }), "invalid-request");
await refuses(w, () => review({ ...args, have: Array(33).fill(w.first) }), "invalid-request");
await refuses(w, () => review({ ...args, have: [c1] }), "nothing-to-fetch");
assert.equal(existsSync(out), false);
assert.doesNotMatch(w.refs(), /refs\/mosaic/);
});
test("review of a project with no working repository refuses unknown-commit", async (t) => {
const w = world(t);
await refuses(w, () => review({ dataRoot: w.dataRoot, business: "acme", project: "stack", root: w.root, taskRef: TASK, commit: w.first, out: join(w.dir, "x.bundle") }), "unknown-commit");
assert.equal(existsSync(w.repo), false);
});
test("the runner's git tools hold the host's limits and branch rule", async () => {
const tools = await import("../../harness/src/git-tools.mjs");
const working = await import("../src/working.mjs");
assert.equal(tools.BUNDLE_MAX, working.BUNDLE_MAX);
assert.equal(tools.HAVE_MAX, working.HAVE_MAX);
for (const b of ["feat", "a/b", "-x", "a..b", "a.lock", "a/", "x".repeat(200), "x".repeat(201)]) assert.equal(tools.validBranch(b), working.validBranch(b), b);
});