seat: fetch a role clone that is on another branch or dirty (#1611)
Row 117. At a launch, update() kept a clone that was on another branch
or dirty and ran no git in it, so its refs/remotes/origin/* stayed as
old as the last clean launch. Now those two reasons get the clean
path's fetch, with the same arguments and environment, and no merge.
The result is a new outcome, fetched, with the reason, the clone's
branch and HEAD, and origin { branch, commit } after the fetch. Every
earlier check still keeps the clone with no fetch. The launcher writes
a checkout-fetched launch-log line, and the bundle prompt names a
fetched clone's branch, commit and origin. Nothing in unsafe(),
allowed(), gitEnv() or SAFE changed.
Candidate manifest 9fa839edfb2bd04771bbac1913b142cb16aef227a1265120e0d318fb64d6a502
from Darkwing's round 1 packet (17d801f48), on BASE 559ef2287. Dewey
approved in 28443. Sage reran the full gate at the landing target with
the candidate applied.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <business> --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 });
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user