src/jobs-seat.ts
917 lines and 23 functions in DrDustinEdwards/capsid, at commit daa875b on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| adminFailJob | 59 to 121 | 14 | 2% | CriticalCRAP 200.7 | No data: mutation testing has not run on this repo yet |
| failAsCaller | 129 to 152 | 7 | 4% | CriticalCRAP 50.1 | No data: mutation testing has not run on this repo yet |
| adminCompleteJob | 165 to 184 | 4 | 0% | WatchCRAP 20 | No data: mutation testing has not run on this repo yet |
| recordAsCaller | 190 to 199 | 4 | 100% | OkCRAP 4 | No data: mutation testing has not run on this repo yet |
| completeUnclaimed | 217 to 250 | 10 | 100% | WatchCRAP 10 | No data: mutation testing has not run on this repo yet |
| <anonymous:221> | 221 to 221 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| <anonymous:222> | 222 to 222 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| <anonymous:222> | 222 to 222 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| prBodyNames | 253 to 262 | 4 | 80% | OkCRAP 4.1 | No data: mutation testing has not run on this repo yet |
| completeAsCaller | 268 to 301 | 12 | 59% | WatchCRAP 22.1 | No data: mutation testing has not run on this repo yet |
| releaseJob | 315 to 367 | 11 | 2% | CriticalCRAP 125.2 | No data: mutation testing has not run on this repo yet |
| workRecorded | 387 to 394 | 9 | 13% | CriticalCRAP 63.3 | No data: mutation testing has not run on this repo yet |
| supersedeJob | 398 to 493 | 23 | 32% | CriticalCRAP 187.1 | No data: mutation testing has not run on this repo yet |
| isMintedActor | 557 to 557 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| resumeDestination | 563 to 574 | 4 | 100% | OkCRAP 4 | No data: mutation testing has not run on this repo yet |
| isRevokedActor | 577 to 585 | 2 | 100% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| jobBranchHead | 591 to 614 | 9 | 0% | CriticalCRAP 90 | No data: mutation testing has not run on this repo yet |
| <anonymous:596> | 596 to 596 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| resumeJob | 616 to 879 | 59 | 56% | CriticalCRAP 357.6 | No data: mutation testing has not run on this repo yet |
| <anonymous:742> | 742 to 742 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| <anonymous:758> | 758 to 763 | 3 | 0% | WatchCRAP 12 | No data: mutation testing has not run on this repo yet |
| <anonymous:770> | 770 to 770 | 1 | 0% | OkCRAP 2 | No data: mutation testing has not run on this repo yet |
| autoResumeJob | 891 to 951 | 10 | 2% | CriticalCRAP 105.3 | No data: mutation testing has not run on this repo yet |
Imports
It imports src/agents.ts, src/bench-run-key.ts, src/env.ts, src/gate-policy.ts, src/github/client.ts, src/github/contents.ts, src/job-claims.ts, src/job-outcomes.ts, src/job-signing.ts, src/job-touches.ts, src/jobs-holder.ts, src/jobs-mirror.ts, src/jobs-schema.ts, src/jobs-transition.ts, src/signed-docs.ts. Imported by src/bench.ts, src/controls-perform.ts, src/controls-plan.ts, src/jobs.ts, src/merge-resume.ts, src/stale-jobs.ts.
Source
import type { Env } from "./env";
import type { Agent } from "./agents";
import {
corruptRequirement,
missingForJob,
outsideJobNamespace,
swallowedParamTag,
swallowedTagRefusal,
type JobRow,
CORRECTION_CAP,
RETRY_CAP_REASON,
atCorrectionCap,
} from "./jobs-schema";
import { approveByPolicy, classifyCommand, type GateClass } from "./gate-policy";
import { readRepoFile } from "./github/contents";
import { ghFetch, parsePrUrl, resolveRepo, type PrUrl } from "./github/client";
import { verifySignedBody } from "./signed-docs";
import { outcomeFrom, outcomeStatement, readJobUsage, verifyEvidence } from "./job-outcomes";
import { jobAudit, mirrorStatements, resumeNoteFields, type ResumeNote } from "./jobs-mirror";
import { signJobText } from "./job-signing";
import { commandFromSummary, completeAsSeat, completeJob, completeWithoutClaim, failJob, recordAsSeat } from "./jobs-holder";
import { actorKind, touchStatement } from "./job-touches";
import type { ClaimInput, ClaimRaw } from "./job-claims";
import { readReproReceipt } from "./bench-run-key";
import {
actorShapeRefusal,
callerIsSeat,
correctionsForWork,
guardedTransition,
markJobFailed,
movedBeforeFailing,
readJob,
recordShortfall,
refuse,
type JobResult,
} from "./jobs-transition";
// The transitions made on a job the caller does not hold: the seat's fail and release,
// supersede and resume.
// The seat stepping in, on a job it does not hold.
//
// Every other transition keys on `claimed_by = <caller>`, which stops two drivers
// treading on each other but leaves no way to close a job whose driver is gone: the
// machine was turned off, the session died, the work was superseded from a chat.
//
// The seat only: the admin, or a caller holding can_merge (callerIsSeat), the rule
// supersede and resume use. The seat that decides is routinely not the session that
// held the job, and the seat key holds can_merge without being the admin. No driver
// holds can_merge, so a driver still cannot fail another driver's job.
//
// It refuses a job that is already finished rather than rewriting one, and says so
// rather than reporting a no-op as success. The mirror and audit row ride in the same
// guarded batch as every other transition.
//
// `opts.extra` rides in the same guarded batch, so a caller's own write commits only
// with the fail; `opts.outcome: false` writes no outcome row, for a fail that is not the
// holder's (the bench score step, src/bench.ts finishRun).
export async function adminFailJob(
env: Env,
agent: Agent,
now: Date,
id: string,
reason: string,
opts: { extra?: D1PreparedStatement[]; outcome?: boolean } = {}
): Promise<JobResult> {
if (!callerIsSeat(agent)) {
return refuse(
"admin-fail",
`${agent.actor} may only fail a job it holds. Failing somebody else's job is the seat's act, and this caller holds neither the admin identity nor can_merge.`
);
}
if (!reason?.trim()) return refuse("admin-fail", "fail needs a reason. A failed job with no reason is one nobody can retry or rule on.");
const current = await readJob(env.DB, id);
if (!current) return refuse("admin-fail", `no job ${id}.`);
// The job's own namespace, since a seat key may be scoped narrower than the admin.
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) return refuse("admin-fail", `${agent.actor} cannot fail ${id} ('${current.title}'): ${outside}`);
if (current.status !== "queued" && current.status !== "claimed" && current.status !== "blocked") {
return refuse("admin-fail", `${id} is already ${current.status}; there is nothing to fail.`);
}
const job: JobRow = { ...current, status: "failed", result_summary: reason, updated_at: now.toISOString() };
const statements = [
env.DB.prepare(
`UPDATE jobs SET status = 'failed', result_summary = ?3, summary_sig = NULL, updated_at = ?2
WHERE id = ?1 AND status IN ('queued', 'claimed', 'blocked') RETURNING id`
).bind(id, now.toISOString(), reason),
...(await mirrorStatements(env, job, "job-admin-fail", agent.actor)),
jobAudit(env.DB, agent.actor, "job-admin-fail", job, { status: job.status, reason, held_by: job.claimed_by }),
// The seat ending a job is a touch, and ends a gate's wait when the job is blocked;
// a gate a resume already answered is not this touch's wait.
touchStatement(env.DB, {
job_id: id,
namespace: job.namespace,
kind: "admin_fail",
actor: agent.actor,
actor_kind: actorKind(agent.actor, { seat: true }),
detail: { reason, from: current.status, held_by: current.claimed_by },
sinceGate: current.status === "blocked",
at: now.toISOString(),
}),
];
// An outcome row, for the same reason `fail` writes one: a job the seat had to close
// because its driver never came back is the kind of ending the record should show.
//
// Only when somebody held it. A queued job the seat cancelled was never worked, and
// inventing an agent would put a failure on a credential that had not touched the
// job. No evidence argument either: the seat did not do the work.
if (job.claimed_by && opts.outcome !== false) {
const verdict = await verifyEvidence(env, job.namespace, undefined);
statements.push(outcomeStatement(env.DB, outcomeFrom(job, verdict, now, await readJobUsage(env.DB, job.id))));
}
if (!(await guardedTransition(env, current, [...statements, ...(opts.extra ?? [])]))) {
const moved = await readJob(env.DB, id);
return refuse(
"admin-fail",
`${id} changed between reading it and failing it: it is now ${moved?.status ?? "gone"}${moved?.claimed_by ? `, held by ${moved.claimed_by}` : ""}. Nothing was written; look again before failing it.`
);
}
return { ok: true, action: "admin-fail", job };
}
// fail through the capsid_work_jobs tool. The holder failing its own job is an ordinary fail. The
// seat failing a job somebody else holds, or nobody holds, is adminFailJob. Anyone else
// gets failJob's own refusal for a job it does not hold.
//
// The claim goes to the holder path only. The seat failing somebody else's job made no
// claim about that work, so adminFailJob records none.
export async function failAsCaller(
env: Env,
agent: Agent,
now: Date,
id: string,
reason: string,
said: { claim?: ClaimInput; raw?: ClaimRaw } = {}
): Promise<JobResult> {
const current = await readJob(env.DB, id);
if (current && current.claimed_by !== agent.actor && callerIsSeat(agent)) {
// A claim is the holder's account of its own work. The seat failing a job it does
// not hold has none to give, and one sent anyway is refused rather than dropped, so
// nobody reads a silence as a recorded claim.
const sent = typeof said.claim === "string" ? said.claim.trim() !== "" : said.claim !== undefined;
if (sent) {
return refuse(
"fail",
`${id} is held by ${current.claimed_by ?? "nobody"}, so this is the seat's fail and takes no claim: a claim is the holder's account of its own work. Send it without claim.`
);
}
return adminFailJob(env, agent, now, id, reason);
}
return failJob(env, agent, now, id, reason, said);
}
// The seat completing a job another credential blocked, the way it can fail one. The job
// stays held by the driver that did the work, so job_outcomes.agent credits that driver;
// resume with take then complete makes the seat the holder and credits the seat. The
// outcome is verified through the same path as a holder's complete (the pull request in
// result_ref or evidence.prs is read from GitHub), the audit row `job-admin-complete`
// names the seat as actor and the holder in held_by, and the seat's claim is recorded
// under the seat, not the driver. Blocked only: a queued job was never worked, a claimed
// one has a holder who completes it, and a finished one is not reopened.
//
// The close is a human touch (kind admin_complete, migrations/0031), written in the same
// batch as the audit row, so the human-effort log shows the job needed the seat.
async function adminCompleteJob(
env: Env,
agent: Agent,
now: Date,
id: string,
args: Parameters<typeof completeJob>[4]
): Promise<JobResult> {
if (!callerIsSeat(agent)) {
return refuse(
"complete",
`${agent.actor} may only complete a job it holds. Completing somebody else's blocked job is the seat's act, and this caller holds neither the admin identity nor can_merge.`
);
}
const current = await readJob(env.DB, id);
if (!current) return refuse("complete", `no job ${id}.`);
// The job's own namespace, since a seat key may be scoped narrower than the admin.
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) return refuse("complete", `${agent.actor} cannot complete ${id} ('${current.title}'): ${outside}`);
return completeAsSeat(env, agent, now, id, args);
}
// record through the work queue tool: the seat writing the outcome of work done on a
// queued job without a claim. The seat only, as for completing a job
// somebody else blocked, because an outcome credits work and a driver could otherwise
// record work it did not do. The evidence is verified against GitHub as for a complete.
export async function recordAsCaller(env: Env, agent: Agent, now: Date, id: string, args: Parameters<typeof completeJob>[4]): Promise<JobResult> {
if (!callerIsSeat(agent)) {
return refuse("record", `${agent.actor} may not record an outcome without a claim. That is the seat's act (the admin identity or can_merge); a driver claims the job and completes it.`);
}
const current = await readJob(env.DB, id);
if (!current) return refuse("record", `no job ${id}.`);
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) return refuse("record", `${agent.actor} cannot record ${id} ('${current.title}'): ${outside}`);
return recordAsSeat(env, agent, now, id, args);
}
// The pull request is the claim (job_e68de044f8a4; Dustin, 2026-10-10, plan step 2). A
// cloud session signs in as the admin login, which may hold one claim, so it works a job
// without claiming it, the way Copilot, Codex and Claude Code Projects work a task: one
// branch and one pull request. Any caller holding the write grant on the job's
// namespace completes the QUEUED job by naming that pull request, and the outcome credits
// the caller. It runs the record path (completeWithoutClaim): the same review gate, GitHub
// verification, outcome row and overlap check as a complete. record stays the seat's, for
// closing work somebody else did.
//
// What a claim would have asked of the caller is asked here, since no claim was made: the
// job's required_flags and min_record. Every named pull request must be on one of the
// namespace's repos, and at least one must be named, because without one nothing a
// reviewer or auto-merge can read ties the work to the job. And at least one named pull
// request's body must name this job id, as auto-merge's body_names_job asks: otherwise any
// pull request on a mapped repo would close any queued job (Grok audit 2026-10-21, CA-3).
// GitHub unreachable refuses, since nothing then shows the pull request is this job's.
async function completeUnclaimed(env: Env, agent: Agent, now: Date, current: JobRow, args: Parameters<typeof completeJob>[4]): Promise<JobResult> {
const id = current.id;
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) return refuse("complete", `${agent.actor} cannot complete ${id} ('${current.title}'): ${outside}`);
const refs = [args.result_ref, ...(args.evidence?.prs ?? [])].filter((ref): ref is string => Boolean(ref));
const prs = refs.map((ref) => ({ ref, pr: parsePrUrl(ref) })).filter((p): p is { ref: string; pr: PrUrl } => p.pr !== null);
if (prs.length === 0) {
return refuse(
"complete",
`${id} is queued and nobody holds it. A job worked without a claim is completed by naming its pull request (a pull request URL in result_ref or evidence.prs): the pull request is the claim. Nothing was written.`
);
}
let named = false;
for (const { ref, pr } of prs) {
let mapped: { owner: string; repo: string };
try {
mapped = await resolveRepo(env, current.namespace, `${pr.owner}/${pr.repo}`);
} catch {
return refuse("complete", `${ref} is not on a repo mapped to ${current.namespace}, so it is not shown to be ${id}'s work. Nothing was written.`);
}
if (!named) named = await prBodyNames(env, mapped.owner, mapped.repo, pr.number, id);
}
if (!named) {
return refuse(
"complete",
`none of the named pull requests has ${id} in its description (or GitHub could not be read), so none is shown to be this job's work. Put the job id in the pull request body and complete again. Nothing was written.`
);
}
const missing = missingForJob(agent, current.namespace, current.required_scopes);
if (missing) return refuse("complete", `${agent.actor} cannot complete ${id} ('${current.title}') without a claim: ${missing} A claim would have refused it too. Nothing was written.`);
const shortfall = await recordShortfall(env.DB, agent.actor, current);
if (shortfall) return refuse("complete", `${agent.actor} cannot complete ${id} ('${current.title}') without a claim: ${shortfall} A claim would have refused it too. Nothing was written.`);
return completeWithoutClaim(env, agent, now, id, args);
}
// Whether the pull request's body names the job id. A failed read is false.
async function prBodyNames(env: Env, owner: string, repo: string, number: number, id: string): Promise<boolean> {
try {
const resp = await ghFetch(env, owner, repo, `/repos/${owner}/${repo}/pulls/${number}`);
if (!resp.ok) return false;
const body = ((await resp.json()) as { body?: string | null }).body ?? "";
return new RegExp(`\\b${id}\\b`).test(body);
} catch {
return false;
}
}
// complete through the capsid_work_jobs tool. The holder completing its own job is an ordinary
// complete. A queued job nobody holds is completed without a claim (completeUnclaimed). The
// seat completing a job somebody else holds is adminCompleteJob. Anyone else gets
// completeJob's own refusal for a job it does not hold.
export async function completeAsCaller(
env: Env,
agent: Agent,
now: Date,
id: string,
args: Parameters<typeof completeJob>[4]
): Promise<JobResult> {
// An argument completeJob refuses before it reads anything is refused the same way here.
if (!args.result_summary?.trim()) return completeJob(env, agent, now, id, args);
// A bench run proves the fix before it may call its job done (the methods paper's
// protocol, section 2): the harness ran the test the agent named on the base and on
// its diff, and saw it fail and then pass (src/bench-run-key.ts readReproReceipt). What
// the agent says about its own test counts for nothing. Without that receipt, "not
// done, here's why" is the outcome the job asks for, and fail records it.
if (agent.benchRun) {
const receipt = await readReproReceipt(env.DB, agent.benchRun.run);
const missing = !receipt
? "the harness has no check of a reproduction test for this run (name the test with bench-repro before phase one ends)"
: receipt.passed
? null
: `the harness's check of your reproduction test did not pass: ${receipt.reason}`;
if (missing) {
return refuse(
"complete",
`${id} was not completed: ${missing}. A bench run completes its job only when the harness saw its test fail on the base and pass on its diff. ` +
`Call fail with the reason instead; "not done, here's why" is a good outcome.`
);
}
}
const current = await readJob(env.DB, id);
if (current && current.status === "queued" && current.claimed_by === null) return completeUnclaimed(env, agent, now, current, args);
if (current && current.claimed_by !== agent.actor && callerIsSeat(agent)) return adminCompleteJob(env, agent, now, id, args);
return completeJob(env, agent, now, id, args);
}
// Release: the seat returning a claimed job to the queue when its holder is gone (the
// session ended, the machine was turned off). A claim has no lease and never expires,
// so without it the job stays held by a holder that is gone.
//
// Not an ending, so no outcome row: the job is worked again and ends with a real one.
// A job whose work already landed is released too, and the next session completes it
// with the pull request as evidence, which the Worker verifies. The seat did not do
// the work, so it does not report it done.
//
// claimed_at is kept, so the job's duration runs from its first claim. The UPDATE is
// keyed on the holder read, so a claim that changed hands in between is not released
// out from under the new holder.
export async function releaseJob(env: Env, agent: Agent, now: Date, id: string, reason: string): Promise<JobResult> {
if (!callerIsSeat(agent)) {
return refuse(
"release",
`${agent.actor} cannot release a job. Releasing a claim somebody else holds is the seat's act, and this caller holds neither the admin identity nor can_merge.`
);
}
if (!reason?.trim()) {
return refuse("release", "release needs a reason: why the holder is not coming back. It is recorded in the audit row.");
}
const swallowed = swallowedParamTag(reason);
if (swallowed) return refuse("release", swallowedTagRefusal("reason", swallowed));
const current = await readJob(env.DB, id);
if (!current) return refuse("release", `no job ${id}.`);
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) return refuse("release", `${agent.actor} cannot release ${id} ('${current.title}'): ${outside}`);
if (current.status !== "claimed") {
return refuse(
"release",
`${id} is ${current.status}, not claimed. Release returns a claimed job to the queue; a blocked job is resumed, a queued one is already free, and a finished one is not reopened.`
);
}
if (current.claimed_by === agent.actor) {
return refuse("release", `${id} is held by ${agent.actor} itself. A holder ends its own claim with fail or block.`);
}
const job: JobRow = { ...current, status: "queued", claimed_by: null, updated_at: now.toISOString() };
const won = await guardedTransition(env, current, [
env.DB.prepare(
`UPDATE jobs SET status = 'queued', claimed_by = NULL, updated_at = ?2
WHERE id = ?1 AND status = 'claimed' AND claimed_by = ?3 RETURNING id`
).bind(id, now.toISOString(), current.claimed_by),
...(await mirrorStatements(env, job, "job-released", agent.actor)),
jobAudit(env.DB, agent.actor, "job-released", job, { reason, held_by: current.claimed_by }),
touchStatement(env.DB, {
job_id: id,
namespace: job.namespace,
kind: "release",
actor: agent.actor,
actor_kind: actorKind(agent.actor, { seat: true }),
detail: { reason, held_by: current.claimed_by },
sinceGate: false, // only a claimed job is released, so no gate is open
at: now.toISOString(),
}),
]);
if (!won) {
const moved = await readJob(env.DB, id);
return refuse(
"release",
`${id} changed between reading it and releasing it: it is now ${moved?.status ?? "gone"}${moved?.claimed_by ? `, held by ${moved.claimed_by}` : ""}. Nothing was written.`
);
}
return { ok: true, action: "release", job };
}
// Supersede: the seat replacing a job before any work was done on it, for a corrected
// or reposted body, a reorder or a withdrawal. Without it the only way to close such a
// job is to claim it and fail it, and the history fills with failures that never
// happened.
//
// No outcome row, no pull request rows, no attribution. Nothing was attempted, so a
// row here would put a failure on a credential that did no work.
//
// Allowed from `queued` by any caller that may write the job's namespace, since nobody
// holds it. From `claimed`, only while the row records no work (no gate hit, no
// resume, no correction, no result_ref), and only by the holder or the seat
// (callerIsSeat). Anything later is ended by `fail` or the Portal's Mark failed.
//
// The whole rule is in the one keyed UPDATE. The checks before it only choose the
// refusal message; a driver that hits a gate between the read and the write leaves no
// row to supersede, rather than being superseded out from under its work.
/** Why a job's row shows work was done on it, or null. */
export function workRecorded(job: JobRow): string | null {
const done: string[] = [];
if (job.blocked_count > 0) done.push(`it has hit a gate ${job.blocked_count} time${job.blocked_count === 1 ? "" : "s"}`);
if (job.resumed_count > 0) done.push(`it has been resumed ${job.resumed_count} time${job.resumed_count === 1 ? "" : "s"}`);
if (job.corrections_count > 0) done.push(`it has been sent back for correction ${job.corrections_count} time${job.corrections_count === 1 ? "" : "s"}`);
if (job.result_ref) done.push(`it records a result_ref (${job.result_ref})`);
return done.length ? done.join(", ") : null;
}
// `extra` rides in the same guarded batch, so a caller's own write commits only with the
// supersede (the bench score step, src/bench.ts finishRun).
export async function supersedeJob(
env: Env,
agent: Agent,
now: Date,
id: string,
args: { reason: string; replaced_by?: string },
extra: D1PreparedStatement[] = []
): Promise<JobResult> {
const reason = args.reason?.trim();
if (!reason) {
return refuse("supersede", "supersede needs a reason: what replaced this job, or why it was withdrawn.");
}
const swallowed = swallowedParamTag(args.reason);
if (swallowed) return refuse("supersede", swallowedTagRefusal("reason", swallowed));
const current = await readJob(env.DB, id);
if (!current) return refuse("supersede", `no job ${id}.`);
// The job's own namespace, asked here for the reason resume asks it: the tool's
// check saw only the namespace argument, which an id-based call may omit.
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) return refuse("supersede", `${agent.actor} cannot supersede ${id} ('${current.title}'): ${outside}`);
const replacedBy = args.replaced_by?.trim() || null;
if (replacedBy) {
if (replacedBy === id) return refuse("supersede", `${id} cannot be replaced by itself.`);
const replacement = await env.DB.prepare("SELECT id, namespace FROM jobs WHERE id = ?1")
.bind(replacedBy)
.first<{ id: string; namespace: string }>();
if (!replacement) return refuse("supersede", `no job ${replacedBy}. replaced_by names the job that replaces this one, and it must exist.`);
if (replacement.namespace !== current.namespace) {
return refuse(
"supersede",
`${replacedBy} is in ${replacement.namespace} and ${id} is in ${current.namespace}. A job is replaced by work in its own namespace.`
);
}
}
if (current.status !== "queued" && current.status !== "claimed") {
return refuse(
"supersede",
`${id} is ${current.status}. Supersede closes a queued job, or a claimed one with no work recorded; ${current.status === "blocked" ? "a blocked job has hit a gate, so it is failed instead" : "a finished job is not rewritten"}.`
);
}
if (current.status === "claimed") {
const isSeat = callerIsSeat(agent);
if (current.claimed_by !== agent.actor && !isSeat) {
return refuse(
"supersede",
`${id} is held by ${current.claimed_by}, not by ${agent.actor}. Superseding a job somebody else holds is the seat's act, and this caller holds neither the admin identity nor can_merge.`
);
}
const work = workRecorded(current);
if (work) return refuse("supersede", `${id} has work recorded on it: ${work}. Fail it instead; supersede is for a job replaced before any work was done.`);
}
const summary = replacedBy ? `Superseded by ${replacedBy}: ${reason}` : `Superseded: ${reason}`;
// ?4 is the holder read above, so a claim that went to another driver between the
// read and this write is not superseded out from under the new one.
const job: JobRow = { ...current, status: "superseded", result_summary: summary, updated_at: now.toISOString() };
// The guard makes the mirror and audit row commit only with the UPDATE: a row that
// changed since the read (a gate hit, a claim) aborts all three.
const won = await guardedTransition(env, current, [
env.DB.prepare(
`UPDATE jobs SET status = 'superseded', result_summary = ?2, summary_sig = NULL, updated_at = ?3
WHERE id = ?1 AND (status = 'queued' OR (status = 'claimed' AND claimed_by = ?4 AND blocked_count = 0
AND resumed_count = 0 AND corrections_count = 0 AND result_ref IS NULL)) RETURNING id`
).bind(id, summary, now.toISOString(), current.claimed_by),
...(await mirrorStatements(env, job, "job-superseded", agent.actor)),
jobAudit(env.DB, agent.actor, "job-superseded", job, {
reason,
replaced_by: replacedBy,
from: current.status,
held_by: current.claimed_by,
}),
touchStatement(env.DB, {
job_id: id,
namespace: job.namespace,
kind: "supersede",
actor: agent.actor,
actor_kind: actorKind(agent.actor, { seat: callerIsSeat(agent) }),
detail: { reason, replaced_by: replacedBy, from: current.status, held_by: current.claimed_by },
sinceGate: false, // only a queued or claimed job is superseded, so no gate is open
at: now.toISOString(),
}),
...extra,
]);
if (!won) {
const moved = await readJob(env.DB, id);
const work = moved ? workRecorded(moved) : null;
return refuse(
"supersede",
`${id} changed between reading it and superseding it: it is now ${moved?.status ?? "gone"}${moved?.claimed_by ? `, held by ${moved.claimed_by}` : ""}${work ? `, and ${work}` : ""}. Nothing was written.`
);
}
return { ok: true, action: "supersede", job };
}
// Resume. A gate is a pause, not an ending. If `blocked` were terminal, a job the
// driver stopped at a gate could never be picked back up: the human would run the
// command, the work would land, and the row would still describe the state before the
// gate, because `claim` refuses anything that is not queued.
//
// The signature is checked again. `claim` verifies the body before handing a job to a
// driver, but a blocked job then sits in the table for as long as a human takes, which
// is the window in which a row could be edited. Resume hands that body back to a
// session holding local shell and repo credentials, so it re-verifies on the same
// terms and marks a tampered job failed rather than returning it.
//
// A plain resume is the seat's act (callerIsSeat: the admin or a can_merge caller). It is
// not restricted to the original claimer, because the point is that a human approved
// something, and the seat that approves is routinely not the session that blocked. The
// reason is required and lands in the audit row, so what was approved is recorded
// rather than implied.
//
// A driver, the claimant or a sibling in the namespace, may not plain-resume. A driver
// that blocked a job on a deploy, a secret or a force push could otherwise resume it
// with any reason, or have another driver with the namespace's write grant do it, and
// the audit row would read "approved: <reason>" as though a human had said yes (Grok
// audit 2026-10, S1). The claimant may still resume its own job through
// approved_by_policy for a branch push or a pull request.
//
// The claim goes back to the driver that blocked it, not to the caller that resumed
// it: a blocked row keeps claimed_by. A seat that took the claim would hold a job it
// has no shell to finish. `take` is the explicit way for a resumer to acquire the job
// instead, and it runs every check a claim runs.
//
// Unless that credential cannot take it back. A credential that is not a minted agent
// (the admin identity every chat tab connects as, or a legacy operator key) names no
// particular session, so a claim given to it is worked by nobody. The same holds for a
// runner whose session ended and a revoked agent. The job then goes back to the queue
// with its approval, and whichever session claims it next gets the resume note. The job's flags
// and record bar are asked at that claim, as for any queued job.
export interface ResumeOptions {
// The pre-approved gate. When set, this resume is approved on the signed gate policy
// rather than on a human having said yes, and the value is the policy version the
// caller read. The blocked command is matched against the policy classes and the
// resume is refused when it matches none, so this narrows what the caller may do on
// its own rather than widening it.
approvedByPolicy?: string;
// The resumer acquires the job rather than returning it to the driver that blocked.
take?: boolean;
// This resume sends the work back to be corrected, so it spends from the retry
// cap's budget. A plain resume does not, because an ordinary push through a gate
// corrects nothing.
correction?: boolean;
// The seat's full note, beside the one-line reason. Recorded in the audit row as
// `note` and handed on whole in resume_note and the mirror document.
note?: string;
}
// What a driver may approve for itself: pushing its own branch and opening its own
// pull request. Not a migration, which stays a human gate, so `additive_migration`
// stays the seat's to approve. The classes and the never list are the policy's own;
// this only narrows which of them a driver may use.
const DRIVER_SELF_APPROVED: readonly GateClass[] = ["push_branch", "open_pr"];
// A minted agent's actor is `agent:<name>`: one credential, one driver. The admin
// identity (`access:<email>`, or `github:<login>` from the old console) and a legacy
// operator key (`opkey:<fingerprint>`) are shared by whatever sessions connect with them.
const isMintedActor = (actor: string): boolean => actor.startsWith("agent:");
/** Where a resume sends a blocked job: back to `holder` (toQueue null), or to the queue
* with the reason, when the holder cannot act on it. With take the resumer acquires the
* job, so it never goes to the queue. resumeJob acts on it and the Portal's preview
* (src/controls.ts) states it, so the two agree. */
export async function resumeDestination(env: Env, holder: string, job: JobRow, take: boolean): Promise<{ toQueue: string | null }> {
const id = job.id;
const db = env.DB;
const toQueue: string | null = take
? null
: !isMintedActor(holder)
? `${holder} is a shared identity rather than one driver's session, so ${id} went back to the queue for the next free session.`
: (await isRevokedActor(db, holder))
? `${holder} is revoked and can no longer claim, so ${id} went back to the queue for the next session.`
: null;
return { toQueue };
}
/** A minted agent whose key was revoked: it can never act on a job handed back to it. */
async function isRevokedActor(db: D1Database, actor: string): Promise<boolean> {
if (!actor.startsWith("agent:")) return false;
// An archived agent is a revoked one (src/agents-archive.ts).
const row = await db
.prepare("SELECT revoked_at FROM agents WHERE name = ?1 UNION ALL SELECT revoked_at FROM agents_archive WHERE name = ?1")
.bind(actor.slice("agent:".length))
.first<{ revoked_at: string | null }>();
return row?.revoked_at != null;
}
/** The head commit of the job's own pull request, and the mapped repo it is on.
* The pull request is the one the job records: its result_ref, or its job_outcome_prs
* rows. Throws with the reason when there is none, more than one, one outside the
* namespace's mapping, or one GitHub cannot read. */
async function jobBranchHead(env: Env, job: JobRow): Promise<{ repo: string; sha: string }> {
const recorded = await env.DB.prepare("SELECT pr_url FROM job_outcome_prs WHERE job_id = ?1")
.bind(job.id)
.all<{ pr_url: string }>();
const byKey = new Map<string, PrUrl>();
for (const ref of [job.result_ref, ...(recorded.results ?? []).map((r) => r.pr_url)]) {
const pr = parsePrUrl(ref);
if (pr) byKey.set(`${pr.owner}/${pr.repo}#${pr.number}`.toLowerCase(), pr);
}
if (byKey.size === 0) {
throw new Error(`${job.id} records no pull request, so there is no branch head to read the migration at`);
}
if (byKey.size > 1) {
throw new Error(`${job.id} records more than one pull request (${[...byKey.keys()].join(", ")}), so which head runs the migration is not known`);
}
const [pr] = [...byKey.values()];
// resolveRepo refuses a repo the namespace does not map, before GitHub is asked.
const { owner, repo, full } = await resolveRepo(env, job.namespace, `${pr.owner}/${pr.repo}`);
const resp = await ghFetch(env, owner, repo, `/repos/${owner}/${repo}/pulls/${pr.number}`);
if (!resp.ok) throw new Error(`reading pull request #${pr.number} failed (${resp.status})`);
const sha = ((await resp.json()) as { head?: { sha?: string } }).head?.sha ?? "";
if (!/^[0-9a-f]{40}$/i.test(sha)) throw new Error(`pull request #${pr.number} reported no head commit`);
return { repo: full, sha };
}
export async function resumeJob(
env: Env,
agent: Agent,
now: Date,
id: string,
reason: string,
opts: ResumeOptions = {}
): Promise<JobResult> {
const { approvedByPolicy, take = false, correction = false } = opts;
// A blank note is no note, so resume_note does not carry an empty block.
const fullNote = opts.note?.trim() ? opts.note : undefined;
const actor = agent.actor;
const badActor = actorShapeRefusal("resume", actor);
if (badActor) return badActor;
if (!reason?.trim()) {
return refuse("resume", "resume needs a reason: what the human approved. A job that came back off a gate with no record of who cleared it is a gate that did not happen.");
}
const current = await readJob(env.DB, id);
if (!current) return refuse("resume", `no job ${id}.`);
if (current.status !== "blocked") {
return refuse(
"resume",
`${id} is ${current.status}, not blocked. Resume is how a job comes back off a gate; a queued job is claimed and a done or failed one is finished.`
);
}
// A plain resume is a record that the seat approved the gate, and a driver approving
// a gate, its own or a sibling's, is no approval. `take` does not change that, so a
// driver passing take is refused the same way.
const isSeat = callerIsSeat(agent);
if (approvedByPolicy === undefined && !isSeat) {
const own = current.claimed_by === actor;
return refuse(
"resume",
`${actor} ${own ? `blocked ${id} and cannot approve its own gate` : `cannot approve ${id}'s gate`} with a plain resume: that is the seat's act. ` +
`The seat (admin or can_merge) resumes it once the command is approved, ` +
`or, for a branch push or a pull request only, the driver that blocked it resumes with approved_by_policy. It stays blocked.`
);
}
// The claimant the claim goes to. A blocked row with no claimant (none should
// exist, since block keys on claimed_by) goes to the caller.
const holder = take || !current.claimed_by ? actor : current.claimed_by;
const { toQueue } = await resumeDestination(env, holder, current, take);
const acquiring = !toQueue && holder === actor;
// A resume is a claim when the caller acquires the job, so it asks the same scope
// question a claim asks: a driver that could not have claimed this job must not
// acquire it by resuming it. When the job goes back to its own claimant, that
// claimant passed these checks at its claim, and the resumer is not taking anything.
const corrupt = corruptRequirement(current);
if (corrupt) {
return refuse("resume", `${id} has a corrupt requirement: ${corrupt}. It stays blocked; a requirement that cannot be read is not the same as none.`);
}
// The namespace is asked either way. A resume that returns the job to its own
// claimant still moves a job, so a caller that cannot write the job's namespace may
// not do it; only the job's flags are left to the claimant it goes back to.
const outside = outsideJobNamespace(agent, current.namespace);
if (outside) {
return refuse("resume", `${actor} cannot resume ${id} ('${current.title}'): ${outside} It stays blocked.`);
}
if (acquiring) {
const missing = missingForJob(agent, current.namespace, current.required_scopes);
if (missing) {
return refuse("resume", `${actor} cannot resume ${id} ('${current.title}'): ${missing} It stays blocked for a driver that can finish it.`);
}
// And the record question, on the same reasoning.
const resumeShortfall = await recordShortfall(env.DB, actor, current);
if (resumeShortfall) {
return refuse("resume", `${actor} cannot resume ${id} ('${current.title}'): ${resumeShortfall} It stays blocked for a driver that can finish it.`);
}
}
// The retry cap, checked before anything is spent. A job sent back twice already is
// one where each further correction has stopped being progress, and what to do next
// belongs to a person. An admin is that person arriving, so an admin resume passes
// and does not spend the budget.
const spentOnWork = await correctionsForWork(env.DB, current.namespace, current.title);
if (!agent.admin && atCorrectionCap(spentOnWork)) {
return refuse(
"resume",
`the work titled '${current.title}' has been corrected ${spentOnWork} times across every job posted for it, which is the cap of ${CORRECTION_CAP}: ${RETRY_CAP_REASON}. ` +
`The count is per (namespace, title) rather than per row, so failing this job and posting it again does not reset it. ` +
`Every resume so far was defensible on its own, which is why the ceiling is counted rather than argued. ` +
`An admin caller may resume it; a driver or the seat may not.`
);
}
const verdict = await verifySignedBody(env.IMPROVE_SCORE_SECRET, current.body, "job body");
if (!verdict.ok) {
const marked = await markJobFailed(env, current, "blocked", verdict.reason, "job-signature-refused", actor, { reason: verdict.reason, at: "resume" }, now);
if (!marked.failed) return movedBeforeFailing("resume", id, marked.current, "failed its signature check");
return refuse("resume", `${id} failed its signature check and has been marked failed: ${verdict.reason}`);
}
// The policy check runs before the claim is given, so a refusal leaves the job
// blocked as it was rather than claimed by a caller whose approval did not hold.
let policyMatch: { klass: string; detail: string; version: string } | null = null;
if (approvedByPolicy !== undefined) {
// Approving on policy is the seat's act. `resume` takes the write grant every
// driver holds, so without this check any driver could pass the policy version and
// approve its own blocked command. The policy's shape is "what the seat may approve
// alone", and a check nobody performs would make that meaningless.
//
// A driver may approve its own branch push and pull request, and nothing wider:
// only on a job it blocked itself and still holds, and only when every class the
// command matched is in DRIVER_SELF_APPROVED. The classification is still the
// Worker's, through the same approveByPolicy call the seat's approval makes, so the
// never list still runs first and a force push still waits.
if (!isSeat && (current.claimed_by !== actor || take)) {
return refuse(
"resume",
`${id} cannot be approved by ${agent.actor} on the gate policy alone. A driver may approve only a job it blocked itself, ` +
`and ${id} was blocked by ${current.claimed_by ?? "nobody on record"}. Approving somebody else's blocked command is the seat's act, ` +
`and this caller holds neither the admin identity nor can_merge.`
);
}
const command = commandFromSummary(current.result_summary);
// The driver's narrower list is checked before the policy, over the same
// classifier, so a driver's migration is refused without a repo read on its behalf.
// A command that classifies as nothing falls through to approveByPolicy, which
// refuses it for its own reason.
if (!isSeat && command) {
const match = classifyCommand(command);
const beyond = "klasses" in match ? match.klasses.filter((k) => !DRIVER_SELF_APPROVED.includes(k)) : [];
if (beyond.length > 0) {
return refuse(
"resume",
`${id} is not pre-approved for a driver: it matched ${beyond.join(", ")}, which a driver may not approve for itself. ` +
`A driver approves only ${DRIVER_SELF_APPROVED.join(" and ")}. It stays blocked for the human.`
);
}
}
// A migration is read at the job's branch head, not on the default branch. The
// approved command runs the file in the driver's checkout, so reading the default
// branch would refuse a new migration and could approve a same-named file whose
// content is not what runs. Read once, and only when the command names a
// migration. A read that fails throws its reason, which approveByPolicy puts in the
// refusal.
let head: Promise<{ repo: string; sha: string }> | null = null;
const verdict = await approveByPolicy(env, approvedByPolicy, command, async (path) => {
head ??= jobBranchHead(env, current);
const at = await head;
const file = await readRepoFile(env, current.namespace, path, at.sha, at.repo);
return (file as { content?: string }).content ?? null;
});
if (!verdict.approved) {
return refuse("resume", id + " is not pre-approved: " + verdict.reason);
}
// The approved verdict is the one that counts, so the driver's list is asserted on
// it too. Unreachable unless the two classifications disagree, which is the case
// this line exists for.
if (!isSeat && verdict.klasses.some((k) => !DRIVER_SELF_APPROVED.includes(k))) {
return refuse("resume", `${id} is not pre-approved for a driver: it matched ${verdict.klass}. It stays blocked for the human.`);
}
policyMatch = { klass: verdict.klass, detail: verdict.detail, version: verdict.policyVersion };
}
// Only a correction spends the budget, and never an admin's. The 0 or 1 is bound
// rather than interpolated for the same reason bumpBlocked is: the statement stays
// one static string that the source guards can read and the query-plan test can
// EXPLAIN.
//
// claimed_at is not touched. It is the first claim, and duration_minutes measures
// from it, so a job that waited at a gate is measured over its whole working life
// rather than over the stretch after its last resume.
const spend = correction && !agent.admin ? 1 : 0;
const job: JobRow = {
...current,
status: toQueue ? "queued" : "claimed",
claimed_by: toQueue ? null : holder,
resumed_count: current.resumed_count + 1,
corrections_count: current.corrections_count + spend,
updated_at: now.toISOString(),
};
// Signed with the job body's key, over the approval, the full note and who gave it,
// bound to this job (src/job-signing.ts); latestResumeNote checks it on every read.
const noteSig = await signJobText(env.IMPROVE_SCORE_SECRET, "resume-note", id, resumeNoteFields(reason, fullNote ?? null, actor));
const resumeNote: ResumeNote = {
reason,
...(fullNote ? { note: fullNote } : {}),
by: actor,
at: now.toISOString(),
...(policyMatch ? { approved_by_policy: policyMatch.version, policy_class: policyMatch.klass } : {}),
...(spend ? { correction: true as const } : {}),
signature: noteSig ? "verified" : "unconfigured",
};
// The touch this resume records. A correction sends the work back; an approval clears
// the gate, on the signed policy or by the seat (admin or can_merge). "resume" was a
// plain resume by another write-grant caller, which is now refused above; older rows
// still carry it. A driver approving its own branch push
// or pull request on the policy is the policy's approval, not a person's. The note, when
// there is one, is a row of its own, so a count of approvals is not a count of notes.
const touchKind = correction ? "correction" : policyMatch || isSeat ? "approval" : "resume";
const touchActorKind = actorKind(actor, { policy: policyMatch !== null && !isSeat, seat: isSeat });
const touches = [
touchStatement(env.DB, {
job_id: id,
namespace: current.namespace,
kind: touchKind,
actor,
actor_kind: touchActorKind,
detail: {
reason,
command: commandFromSummary(current.result_summary),
...(policyMatch ? { approved_by_policy: policyMatch.version, policy_class: policyMatch.klass } : {}),
...(take ? { taken: true } : {}),
...(toQueue ? { returned_to: "queued" } : {}),
},
sinceGate: true,
at: now.toISOString(),
}),
...(fullNote
? [
touchStatement(env.DB, {
job_id: id,
namespace: current.namespace,
kind: "note",
actor,
actor_kind: touchActorKind,
detail: { note: fullNote },
sinceGate: false,
at: now.toISOString(),
}),
]
: []),
];
// One static statement for both returns, so the source guards and the query-plan
// test read one shape: ?5 is 'claimed' or 'queued', and ?2 is NULL for the
// queue.
const won = await guardedTransition(env, current, [
env.DB.prepare(
`UPDATE jobs SET status = ?5, claimed_by = ?2,
resumed_count = resumed_count + 1, corrections_count = corrections_count + ?4, updated_at = ?3
WHERE id = ?1 AND status = 'blocked' RETURNING id`
).bind(id, job.claimed_by, now.toISOString(), spend, job.status),
...(await mirrorStatements(env, job, "job-resumed", actor, resumeNote)),
jobAudit(env.DB, actor, "job-resumed", job, {
approved: reason,
...(fullNote ? { note: fullNote } : {}),
...(noteSig ? { sig: noteSig } : {}),
held_by: job.claimed_by,
...(toQueue ? { returned_to: "queued", previous_holder: holder } : {}),
...(take ? { taken: true } : {}),
...(spend ? { correction: true } : {}),
resumed_count: job.resumed_count,
corrections_count: job.corrections_count,
...(agent.admin ? { cap_lifted_by_admin: true } : {}),
// Which class matched, not merely that one did. A row saying "approved by policy"
// cannot be checked against the policy afterwards; one naming the class and what
// it matched can.
...(policyMatch
? { approved_by_policy: policyMatch.version, policy_class: policyMatch.klass, policy_detail: policyMatch.detail }
: {}),
}),
...touches,
]);
if (!won) {
return refuse("resume", `${id} left blocked between reading it and resuming it. Nothing was written; ask again.`);
}
return { ok: true, action: "resume", job, resume_note: resumeNote, ...(toQueue ? { note: toQueue } : {}) };
}
/** The actor that returns a blocked job whose pull requests have all merged
* (src/merge-resume.ts). A system actor, not a caller: it holds no grant, passes no
* checkScope, and can do exactly what autoResumeJob does. */
export const MERGE_RESUME_ACTOR = "system:merge-resume";
/** Resume a BLOCKED job to its holder, or to the queue when the holder cannot act, on
* the strength of one fact the caller has read: every pull request the job names has
* merged. Never completes, never takes the claim, never approves a gate command; the
* holder's own claim still finishes the job. Returns false when the job moved between
* the caller's read and this write, in which case nothing was written. */
export async function autoResumeJob(env: Env, now: Date, job: JobRow, note: string): Promise<{ resumed: boolean; to: "holder" | "queue" }> {
const holder = job.claimed_by;
if (job.status !== "blocked" || !holder) return { resumed: false, to: "holder" };
// Without take, a holder that cannot take the job back already yields toQueue.
const { toQueue } = await resumeDestination(env, holder, job, false);
const queued = toQueue !== null;
const reason = "every pull request this job names has merged";
const next: JobRow = {
...job,
status: queued ? "queued" : "claimed",
claimed_by: queued ? null : holder,
resumed_count: job.resumed_count + 1,
updated_at: now.toISOString(),
};
const noteSig = await signJobText(env.IMPROVE_SCORE_SECRET, "resume-note", job.id, resumeNoteFields(reason, note, MERGE_RESUME_ACTOR));
const resumeNote: ResumeNote = {
reason,
note,
by: MERGE_RESUME_ACTOR,
at: now.toISOString(),
signature: noteSig ? "verified" : "unconfigured",
};
const kind = actorKind(MERGE_RESUME_ACTOR);
const won = await guardedTransition(env, job, [
env.DB.prepare(
`UPDATE jobs SET status = ?5, claimed_by = ?2,
resumed_count = resumed_count + 1, corrections_count = corrections_count + ?4, updated_at = ?3
WHERE id = ?1 AND status = 'blocked' RETURNING id`
).bind(job.id, next.claimed_by, now.toISOString(), 0, next.status),
...(await mirrorStatements(env, next, "job-resumed", MERGE_RESUME_ACTOR, resumeNote)),
jobAudit(env.DB, MERGE_RESUME_ACTOR, "job-resumed", next, {
approved: reason,
note,
...(noteSig ? { sig: noteSig } : {}),
held_by: next.claimed_by,
...(queued ? { returned_to: "queued", previous_holder: holder } : {}),
resumed_count: next.resumed_count,
}),
touchStatement(env.DB, {
job_id: job.id,
namespace: job.namespace,
kind: "resume",
actor: MERGE_RESUME_ACTOR,
actor_kind: kind,
detail: { reason, ...(queued ? { returned_to: "queued" } : {}) },
sinceGate: true,
at: now.toISOString(),
}),
touchStatement(env.DB, {
job_id: job.id,
namespace: job.namespace,
kind: "note",
actor: MERGE_RESUME_ACTOR,
actor_kind: kind,
detail: { note },
sinceGate: false,
at: now.toISOString(),
}),
]);
return { resumed: won, to: queued ? "queue" : "holder" };
}