toolroll 0.9.49 → 0.9.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli-http.js +3 -0
  3. package/dist/flow-triggers.js +7 -0
  4. package/dist/mcp-http.js +6 -11
  5. package/dist/mcp-oauth.js +5 -21
  6. package/dist/mcp-person.d.ts +0 -9
  7. package/dist/mcp-person.js +2 -1
  8. package/dist/operate.js +4 -1
  9. package/dist/serve.d.ts +2 -605
  10. package/dist/serve.js +423 -23783
  11. package/dist/server/chat.d.ts +41 -0
  12. package/dist/server/chat.js +1458 -0
  13. package/dist/server/edge-refusal.d.ts +5 -0
  14. package/dist/server/edge-refusal.js +56 -0
  15. package/dist/server/flows.d.ts +12 -0
  16. package/dist/server/flows.js +832 -0
  17. package/dist/server/guards.d.ts +72 -0
  18. package/dist/server/guards.js +357 -0
  19. package/dist/server/handler-context.d.ts +22 -0
  20. package/dist/server/handler-context.js +1 -0
  21. package/dist/server/handler-registry.d.ts +21 -0
  22. package/dist/server/handler-registry.js +34 -0
  23. package/dist/server/pages-assets.d.ts +10 -0
  24. package/dist/server/pages-assets.js +103 -0
  25. package/dist/server/pages.d.ts +11 -0
  26. package/dist/server/pages.js +910 -0
  27. package/dist/server/people-tokens.d.ts +20 -0
  28. package/dist/server/people-tokens.js +694 -0
  29. package/dist/server/remote-hooks.d.ts +29 -0
  30. package/dist/server/remote-hooks.js +130 -0
  31. package/dist/server/remote.d.ts +59 -0
  32. package/dist/server/remote.js +185 -0
  33. package/dist/server/route-policy.d.ts +18 -0
  34. package/dist/server/route-policy.js +30 -0
  35. package/dist/server/route-table.d.ts +74 -0
  36. package/dist/server/route-table.js +350 -0
  37. package/dist/server/runtime.d.ts +280 -0
  38. package/dist/server/runtime.js +25 -0
  39. package/dist/server/settings.d.ts +22 -0
  40. package/dist/server/settings.js +2388 -0
  41. package/dist/server/shared.d.ts +3334 -0
  42. package/dist/server/shared.js +12708 -0
  43. package/dist/server/task-views.d.ts +82 -0
  44. package/dist/server/task-views.js +1099 -0
  45. package/dist/server/tasks.d.ts +87 -0
  46. package/dist/server/tasks.js +3579 -0
  47. package/dist/session-http.js +3 -0
  48. package/dist/team-http.d.ts +5 -1
  49. package/dist/team-http.js +6 -2
  50. package/dist/teams.js +3 -0
  51. package/package.json +1 -1
@@ -0,0 +1,3579 @@
1
+ /** tasks handlers, moved without changing their route bodies. */
2
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
3
+ import { lstatSync, realpathSync } from "node:fs";
4
+ import {} from "node:http";
5
+ import { withActor } from "../actor.js";
6
+ import { gateWords } from "../approval-policy.js";
7
+ import { acceptAndCompleteAsOperator, assignmentOf, checkAssignmentAsOperator } from '../assignment.js';
8
+ import { attentionCardForUnverifiedDone, classify } from "../board.js";
9
+ import { codingHandoffPreview, createCodingHandoff } from '../coding-handoff.js';
10
+ import { codingShippingHtml } from '../coding-shipping-ui.js';
11
+ import { codingWorkspaceHtml, codingWorkspaceScript } from '../coding-ui.js';
12
+ import { CodingActionError } from '../coding-workspace.js';
13
+ import { abandonContest, buildPickView, computePickPlan, finalizeContestPick, nonceHashOf, pickTupleDigest, planComparison, planTournament } from "../contest.js";
14
+ import { checkResponse, CONSOLE_FORMS, readForm } from "../contracts/console-api.js";
15
+ import { hasForbiddenControls, validateNote } from "../decision.js";
16
+ import { diagnoseTaskDispatch } from "../dispatch.js";
17
+ import { evidencePack, evidencePackHtml } from "../evidence-pack.js";
18
+ import { readVerifiedArtifact, readVerifiedReport, writeEvidenceFile } from "../evidence.js";
19
+ import { computeGaps } from "../gaps.js";
20
+ import { limitsView } from "../limits-ui.js";
21
+ import { readLiveWindow } from "../live.js";
22
+ import { modeTermsFromJson } from "../modes.js";
23
+ import { latestFinishedAttempt, retryNoteOf } from '../needs-you.js';
24
+ import { REMOTE_MESSAGES } from "../operate-remote.js";
25
+ import { aggregateNewNames, observeWorktree, parseBaseTreeSnapshot, PEEK_LIMITS } from "../peek.js";
26
+ import { isRiskLevel, isTaskSize, PHASES as ROUTE_PHASES } from "../phase-routing.js";
27
+ import { applyModeToNewFiling, authorizePlanUnderMode } from "../plan-auto.js";
28
+ import { milestonesOf, parseExecutionPlanDocument } from "../plan.js";
29
+ import { levelOfProfile } from "../policy.js";
30
+ import { authorizedProject, canonicalProject, projectName, rowVisible } from "../project.js";
31
+ import { fileRoutineProposal, fileTaskProposal } from "../proposal.js";
32
+ import { isProviderId, validateSpec } from "../provider.js";
33
+ import { completeAndOpenPullRequest, mergeAsPerson } from '../pull-request-flow.js';
34
+ import { isQualityMode } from "../quality.js";
35
+ import { createResultRevision, requestResultChanges } from "../result-actions.js";
36
+ import { structuredHandoffView, terminalDiffView } from "../result-evidence-readers.js";
37
+ import { fileAddTestsTask, requestFollowUpChecks } from '../result-follow-ups.js';
38
+ import { commentSourceKey, isRevisionFeedback, parseResultTab, resultReturnTarget } from "../result-review.js";
39
+ import { approveRoutine, fireRoutine, refreshRoutineAgents, routineAgentsState } from "../routine.js";
40
+ import { isAlive as runnerAlive } from "../runner.js";
41
+ import { acceptanceLinesToInput, acceptanceToLines, approvalOf, approve as approveScope, attendedDigestOf, attendedTermsJson, canonicalProfileJson, hasFreshIdentitySignIn, modeFilingCoverage, PLACEHOLDER_RUBRIC, profileDigestOf, profileFromJson, proposeGuarded } from "../scope.js";
42
+ import { teammateNames as teammateNamesOf } from "../spend.js";
43
+ import { verifiedAuthor } from "../store.js";
44
+ import { runCostWords } from "../summary.js";
45
+ import { composerSchedule } from "../task-composer.js";
46
+ import { requestTaskStop, resumeTaskStop } from "../task-control.js";
47
+ import { templateByName } from "../templates.js";
48
+ import { TEXT_LIMITS } from "../text-limits.js";
49
+ import { WorkIndexCursorError, workIndexPage } from "../work-index.js";
50
+ import { buildProgressOf, parseWorkView } from "../workspace-ui.js";
51
+ import { ACCEPT_ANYWAY_NEEDS_REASON, approvalFormDigest, approveRefusalWords, attendedWatchWords, boardBody, chatReturnWithSaid, checkProgressHtml, consentDoorOf, contestCeremonyPage, contestPage, decisionPage, donePage, editorFileHref, escape, inboxFingerprints, inboxPage, matchTaskPath, newTaskPage, nextPage, oneLineOf, parseInboxTab, PermissionsWouldChange, proofBundleView, QUEUE_FRONT, QUEUE_VIEW, queueBody, queueScript, rankReviewQueue, redirect, refuse, regionScript, requestContext, requirementsFromEditor, respond, RESULT_REFUSALS, resumeDigestOf, REVIEW_QUEUE_CAP, reviewCockpitPage, reviewHref, reviewPriorityOf, routineScreenPage, routinesPage, runFactsFragment, runOutcomeBadge, runPage, RUNS_PAGE, runsPage, safeChatReturn, safeReturn, SAFETY, screen, TASK_STATES, taskChatHref, taskHref, taskOf, tasksPage, transcriptScript, whenTime, withRefusal, workPage } from "./shared.js";
52
+ export function createTasksHandlers(runtime) {
53
+ const { store, peekSay, peekCache, PEEK_CACHE_TTL_MS, peekInFlight, PEEK_GLOBAL_INFLIGHT, peekBySession, PEEK_SESSION_INFLIGHT, clock, peekName, PEEK_FRAGMENT_BYTES, peekEvict, evidenceRoot, sendScreen, chromeFor, visible, consumeApprovalNonce, authenticateApprover, allowedHost, options, mintApprovalNonce, taskScreen, unscopedMode, admissionList, planViewOf, revisionViewOf, runIsTaskResult, matePrincipal, familyOf, armTaskResume, runIsLive, revisionLedgerOf, resultDetailOf, reviewFactsFor, taskRepoOf, runVisible, restricted, codingActorAllowed, codingProjectAllowed, managedRepos, routeViewOf, workAccess, firstRunStepsNow, revisionDocOf, failureOf, dockedConversation, planContractViewOf, familiesInView, explainAttempt, taskChatFocus, familyTasksInView, taskRooms, identify, liveCeiling, projectOf, ceiling, bustBadge, revisionDestination } = runtime;
54
+ async function get(ctx) {
55
+ const { url, who, request, response, now, project, chosenProject, posted, route } = ctx;
56
+ if (url.pathname === '/code' || url.pathname.startsWith('/code/')) {
57
+ if (who.via !== 'cookie' || !codingActorAllowed({ name: who.name, generation: who.session.generation }))
58
+ return refuse(response, who, 403, 'Coding sessions require installation-wide operator access. You can manage project tasks in Work.', '/work');
59
+ const actor = { name: who.name, generation: who.session.generation };
60
+ const match = /^\/code\/([a-f0-9]{32})(?:\/(state|changes|ship))?$/.exec(url.pathname);
61
+ try {
62
+ const selected = match && runtime.coding ? runtime.coding.get(match[1], actor) : null;
63
+ if (selected && !codingProjectAllowed(selected.repo))
64
+ return refuse(response, who, 403, 'That project is outside your access.', '/projects');
65
+ if (match?.[2] === 'ship' && selected) {
66
+ const preview = codingHandoffPreview(store, { sessionId: selected.id, actor: who.name });
67
+ return sendScreen(response, 200, screen('Review for shipping', codingShippingHtml(preview, who.session.csrf), { chrome: chromeFor(selected.repo, 'code') }));
68
+ }
69
+ if (match?.[2]) {
70
+ if (!selected || !runtime.coding)
71
+ throw Error('The coding session is unavailable.');
72
+ if (match[2] === 'state' && url.searchParams.get('revision') === String(runtime.coding.revision(selected.id, actor)))
73
+ return respond(response, 200, 'application/json', JSON.stringify({ unchanged: true }));
74
+ const result = match[2] === 'changes' ? await runtime.coding.changes(selected.id, actor) : runtime.coding.snapshot(selected.id, actor);
75
+ if (!codingActorAllowed(actor) || !codingProjectAllowed(selected.repo))
76
+ return respond(response, 403, 'application/json', JSON.stringify({ ok: false, error: 'Your access changed. Sign in again.' }));
77
+ return respond(response, 200, 'application/json', JSON.stringify(result));
78
+ }
79
+ if (url.pathname !== '/code' && !match)
80
+ return refuse(response, who, 404, 'Coding session not found.', '/code');
81
+ const requestedProject = url.searchParams.get('project');
82
+ if (requestedProject !== null && (!visible(requestedProject) || ![...managedRepos(), ...store.listProjects().map(p => p.path)].includes(requestedProject)))
83
+ return refuse(response, who, 403, 'That project is outside your access.', '/projects');
84
+ const codeProject = selected?.repo ?? requestedProject ?? project;
85
+ const chrome = chromeFor(codeProject, 'code');
86
+ const content = codingWorkspaceHtml({ owner: who.name, projects: chrome.projects ?? [], sessions: runtime.coding?.list(actor).filter(s => codingProjectAllowed(s.repo)) ?? [], selected: selected && runtime.coding ? runtime.coding.snapshot(selected.id, actor) : null, csrf: who.session.csrf, project: codeProject, available: runtime.coding !== null, ...(runtime.codingProblem ? { error: runtime.codingProblem } : {}) });
87
+ return sendScreen(response, 200, screen('Code', content, { chrome, functional: { script: codingWorkspaceScript(), fetches: true } }));
88
+ }
89
+ catch (error) {
90
+ if (!codingActorAllowed(actor))
91
+ return match?.[2] && match[2] !== 'ship'
92
+ ? respond(response, 403, 'application/json', JSON.stringify({ ok: false, error: 'Your access changed. Sign in again.' }))
93
+ : refuse(response, who, 403, 'Your access changed. Sign in again.', '/work');
94
+ const message = error instanceof Error ? error.message : 'The coding workspace is unavailable.';
95
+ if (match?.[2] === 'ship')
96
+ return sendScreen(response, 409, screen('Review for shipping', `<h1>Prepare this result</h1><p>${escape(message)}</p><a href="/code/${match[1]}">Back to coding</a>`, { chrome: chromeFor(project, 'code') }));
97
+ if (match?.[2])
98
+ return respond(response, 409, 'application/json', JSON.stringify({ ok: false, error: message }));
99
+ return sendScreen(response, 404, screen('Coding session unavailable', `<h1>Session unavailable</h1><p>${escape(message)}</p><a href="/code">Open coding workspace</a>`, { chrome: chromeFor(project, 'code') }));
100
+ }
101
+ }
102
+ if (url.pathname === "/inbox") {
103
+ // With no project open in scoped mode this is the ROLL-UP inbox:
104
+ // admission binds inside the bounded queries, every row's repo is
105
+ // re-proved here, and rows render as links only (Codex roll-up
106
+ // review, findings 6–8). Gaps stay per-project — they are derived
107
+ // against one repo's capabilities and roll up dishonestly.
108
+ const rollup = project === null && !unscopedMode;
109
+ const admission = rollup ? admissionList() : null;
110
+ const cancelled = store
111
+ .listCancelledBlockersScoped(project, 10, admission)
112
+ .filter(one => visible(one.repo) && visible(one.blockerRepo));
113
+ const inboxData = {
114
+ csrf: who.via === "cookie" ? who.session.csrf : "",
115
+ revision: who.via === "cookie" ? who.session.projectRevision : 0,
116
+ rollup,
117
+ interactive: project !== null,
118
+ decisions: store.listDecisionsScoped(project).filter(one => visible(one.repo)).slice(0, 10),
119
+ // Each waiting scope wears its consent door (v48 authority repair): a scope whose
120
+ // approval is closed — unreadable route, a route that cannot run,
121
+ // a pre-routing row whose approval lapsed — reads as needing
122
+ // attention, never as something to approve.
123
+ approvals: store.scopesAwaitingApproval(project, 10, admission).filter(one => visible(one.repo)).map(one => {
124
+ const ref = store.lookupRef(one.taskId);
125
+ const scope = store.getScope(one.taskId);
126
+ const door = consentDoorOf(scope, routeViewOf(one.taskId, ref, scope, now, who));
127
+ return { ...one, closed: door.open ? null : door.title };
128
+ }),
129
+ requeueables: store.listRequeueablesScoped(project, now, 10, admission).filter(one => visible(one.repo)),
130
+ needsVerification: store
131
+ .listCompletedWorkScoped(project, 10, admission)
132
+ .filter(one => visible(one.repo) && (one.proofVerdict === "short" || one.proofVerdict === "refuted") && !one.proofAccepted &&
133
+ assignmentOf(store, one.taskId, now, workAccess(), evidenceRoot) === null)
134
+ .map(one => ({
135
+ taskId: one.taskId,
136
+ title: one.title,
137
+ verdict: one.proofVerdict,
138
+ repo: one.repo,
139
+ matrix: one.proofMatrix,
140
+ repairChain: one.runId === null ? null : store.repairChainFor(one.runId),
141
+ })),
142
+ cancelledBlockers: cancelled,
143
+ gaps: project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0).slice(0, 10),
144
+ wizard: firstRunStepsNow(now),
145
+ worker: (() => {
146
+ // The one fact the inbox must never hide (install review): with
147
+ // no worker answering, nothing here will ever build, and every
148
+ // approval below is a promise nobody is there to keep.
149
+ const runners = store.listRunners().filter(one => one.retiredAt === null && (!restricted() || one.repos.some(visible)));
150
+ const answering = runners.filter(one => runnerAlive(one, now));
151
+ const lastHeard = runners.map(one => one.heartbeatAt).sort().at(-1) ?? null;
152
+ return { answering: answering.length, registered: runners.length, lastHeard };
153
+ })(),
154
+ now,
155
+ // Console v2: results ready to review and work running now, from the same admitted work index.
156
+ ...(() => {
157
+ const row = (one) => ({ taskId: one.rootId, title: one.title, detail: one.status.detail, repo: one.repo });
158
+ const needsYou = workIndexPage(store, now, workAccess(), { view: "needs-you", limit: 40, project });
159
+ const runningNow = workIndexPage(store, now, workAccess(), { view: "running", limit: 40, project });
160
+ return { ready: needsYou.items.filter(one => one.assignmentState === "ready-to-check").map(row), running: runningNow.items.map(row) };
161
+ })(),
162
+ };
163
+ // Which tabs hold something new since this browser last looked (the dots on a phone).
164
+ const tab = parseInboxTab(url.searchParams.get("tab")) ?? "all";
165
+ const prints = inboxFingerprints(inboxData);
166
+ const seenWords = /(?:^|;\s*)so-inbox-seen=([0-9a-f]{8}(?:\.[0-9a-f]{8}){3})(?:;|$)/.exec(request.headers.cookie ?? "")?.[1]?.split(".") ?? null;
167
+ const order = ["needs-you", "ready", "running", "all"];
168
+ const seen = Object.fromEntries(order.map((one, index) => [one, seenWords?.[index] ?? ""]));
169
+ const unread = order.filter(one => seenWords !== null && seen[one] !== prints[one]);
170
+ for (const one of order)
171
+ if (tab === "all" || one === tab)
172
+ seen[one] = prints[one];
173
+ response.setHeader("Set-Cookie", `so-inbox-seen=${order.map(one => seen[one] || prints[one]).join(".")}; SameSite=Lax; Path=/; Max-Age=31536000`);
174
+ return sendScreen(response, 200, inboxPage(chromeFor(project, "inbox"), { ...inboxData, tab, unread }));
175
+ }
176
+ if (url.pathname === "/work") {
177
+ // The Work destination (workspace package 1): every task in view as
178
+ // one row wearing the shared status projection, with All, Needs you,
179
+ // Running, and Completed as shortcuts over the same rows — never a
180
+ // persisted state. With no project open in scoped mode this rolls
181
+ // up like the inbox: admission binds the query and every row's repo
182
+ // is re-proved here.
183
+ const view = parseWorkView(url.searchParams.get("view"));
184
+ const rollup = project === null && !unscopedMode;
185
+ let work;
186
+ try {
187
+ work = workIndexPage(store, now, workAccess(), { view, limit: 40, cursor: url.searchParams.get('cursor'), project });
188
+ const facts = requestContext.getStore();
189
+ if (facts !== undefined) {
190
+ // These counts cover the exact admitted project lens, including
191
+ // unplaced tasks where permitted. Crew excludes unplaced rows.
192
+ if (project === null)
193
+ facts.workCounts = work.projects;
194
+ const admitted = workAccess().repos;
195
+ const crewLens = project !== null || admitted !== null && JSON.stringify([...admitted].sort()) === JSON.stringify(managedRepos().sort()) && !work.projects.some(one => one.repo === null);
196
+ if (view === 'all' && !url.searchParams.has('cursor') && crewLens)
197
+ facts.workCrew = { project, page: work };
198
+ }
199
+ }
200
+ catch (error) {
201
+ if (error instanceof WorkIndexCursorError)
202
+ return refuse(response, who, 400, 'This task page has expired. Open the first page.', '/work');
203
+ throw error;
204
+ }
205
+ // A failed row says what its latest attempt missed, as its task page does (a stop reason already reads so), from
206
+ // the database alone: a row reads no check log. A live build's row says which step it is on, or the step it is
207
+ // stuck on, from that live attempt's own progress only, never a stopped attempt's; the step's words come from its
208
+ // current plan (one saved file, for live rows only), and without it the row names the step by number.
209
+ work = { ...work, items: work.items.map((item) => {
210
+ if (item.liveRunId !== null) {
211
+ const ref = store.lookupRef(item.activeTaskId);
212
+ const recorded = ref === null ? null : store.latestCheckpointForRun(item.liveRunId);
213
+ if (ref === null || recorded === null || recorded.taskRef !== ref.id)
214
+ return item;
215
+ const plan = store.currentPlanRevision(ref.id);
216
+ const parsed = plan === null ? null : (() => { const doc = revisionDocOf(plan); return doc === null ? null : parseExecutionPlanDocument(doc.document); })();
217
+ const words = new Map(parsed?.ok === true ? milestonesOf(parsed.document).map(one => [one.id, one.description]) : []);
218
+ const steps = buildProgressOf(recorded.snapshot.milestones.map(one => ({ description: words.get(one.id) ?? null, state: one.state, note: one.note ?? null })));
219
+ return steps === null ? item : { ...item, progress: steps.line };
220
+ }
221
+ if (item.state !== "failed")
222
+ return item;
223
+ const family = familyOf(item.activeTaskId);
224
+ if (family === null || family.problem !== null)
225
+ return item;
226
+ const failure = failureOf(family.versions.flatMap(version => store.runsFor(version.refId)), null, false);
227
+ return failure.kind === "reason" ? item : { ...item, status: { ...item.status, detail: failure.line } };
228
+ }) };
229
+ const page = workPage(chromeFor(project, "work", undefined, rollup ? "all" : undefined), {
230
+ view,
231
+ ...(url.searchParams.has('project') && project !== null ? { projectFilter: project } : {}),
232
+ work,
233
+ previous: url.searchParams.has('cursor'),
234
+ multiProject: new Set(work.items.map(task => task.repo ?? "")).size > 1,
235
+ now,
236
+ // v105: subscription windows and monthly budgets, for whoever runs the installation.
237
+ limits: store.isInstanceOperator(who.name)
238
+ ? limitsView(store.providerLimits(), store.budgets().length === 0 ? [] : store.monthSpendCached(now).budgets, { project: projectName, teammate: id => teammateNamesOf(store.handle).get(id) ?? `Teammate ${id}` }, now)
239
+ : null,
240
+ });
241
+ // One project's Tasks dock that project's own conversation (v77).
242
+ const projectThread = url.searchParams.has('project') && project !== null && managedRepos().includes(project) ? project : null;
243
+ const docked = projectThread === null ? null : dockedConversation(who, null, projectThread, now, `/work?project=${encodeURIComponent(projectThread)}`);
244
+ if (docked !== null)
245
+ page.workspace = { ...page.workspace, conversation: docked, pageHtml: page.body };
246
+ // Without a docked chat the list reads itself: 10 s while any of its tasks is building, else 30 s.
247
+ page.refreshSeconds = work.totals.running > 0 ? 10 : 30;
248
+ return sendScreen(response, 200, page);
249
+ }
250
+ if (url.pathname === "/next") {
251
+ // Triage: everything waiting on a person, one at a time, hardest-
252
+ // blocked first — oldest question, then plans and scopes to approve,
253
+ // then stalled work to retry, then requirement gaps. `skip` is a
254
+ // bounded, session-free cursor of keys the operator set aside; every
255
+ // act 303s back here, which is what makes it a flow and not a list.
256
+ const skipped = new Set((url.searchParams.get("skip") ?? "").split(",").filter(one => /^[darg]:[A-Za-z0-9._:-]{1,80}$/.test(one)).slice(0, 20));
257
+ const decisions = store.listDecisionsScoped(project).filter(one => one.state !== "answered");
258
+ const approvals = store.scopesAwaitingApproval(project, 20);
259
+ const requeueables = store.listRequeueablesScoped(project, now, 20);
260
+ const gaps = project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0);
261
+ const queue = [
262
+ ...decisions.map(decision => ({ key: `d:${decision.id}`, kind: "decision", decision })),
263
+ ...approvals.map(approval => ({ key: `a:${approval.taskId}`, kind: "approval", approval })),
264
+ ...requeueables.map(stalled => ({ key: `r:${stalled.taskId}`, kind: "requeue", stalled })),
265
+ ...gaps.map(gap => ({ key: `g:${gap.key.replace(/[^A-Za-z0-9._:-]/g, "_")}`, kind: "gap", gap })),
266
+ ];
267
+ const remaining = queue.filter(one => !skipped.has(one.key));
268
+ const item = remaining[0] ?? null;
269
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
270
+ if (item !== null && item.kind === "approval") {
271
+ // The card restates the digest-bound terms, so the nonce may be
272
+ // minted here — same rule as the task screen, same binding.
273
+ const ref = store.lookupRef(item.approval.taskId);
274
+ const scope = store.getScope(item.approval.taskId);
275
+ const planView = ref !== null && ref.plan === "drafted" ? planViewOf(ref.id) : null;
276
+ const raceTerms = ref === null ? null : store.activeTournamentTerms(ref.id);
277
+ const approvalDigest = approvalFormDigest(item.approval.digest, raceTerms?.raceDigest ?? null, planView?.sha256 ?? null);
278
+ // The same agents block the task page and chat sign under (v48),
279
+ // and the same consent door: closed, it mints no nonce.
280
+ const route = routeViewOf(item.approval.taskId, ref, scope, now, who);
281
+ const nonce = who.via === "cookie" && consentDoorOf(scope, route).open ? mintApprovalNonce(who.name, item.approval.taskId, approvalDigest) : "";
282
+ return sendScreen(response, 200, nextPage(chromeFor(project, "inbox"), {
283
+ item, scope,
284
+ planDocument: planView?.document ?? null,
285
+ planContract: ref === null || planView === null ? null : planContractViewOf(ref.id, scope),
286
+ approvalDigest,
287
+ raceTerms,
288
+ deliverable: ref?.deliverable ?? "branch",
289
+ route,
290
+ csrf, nonce, remaining: remaining.length, skipped: [...skipped], now,
291
+ }));
292
+ }
293
+ return sendScreen(response, 200, nextPage(chromeFor(project, "inbox"), {
294
+ item, scope: null, planDocument: null, planContract: null, csrf, nonce: "",
295
+ approvalDigest: null, raceTerms: null, route: null,
296
+ remaining: remaining.length, skipped: [...skipped], now,
297
+ }));
298
+ }
299
+ if (url.pathname === "/board") {
300
+ // scope=all is the rolled-up view: every project this server was
301
+ // allowed to serve, on one board. The ceiling still rules row by row
302
+ // (rowVisible, the same predicate as every list) — a repo outside
303
+ // the server's configuration never renders a card, whatever the
304
+ // database holds. Unplaced work (repo NULL) appears: it dispatches
305
+ // anywhere, so every board honestly owns it.
306
+ const all = url.searchParams.get("scope") === "all";
307
+ // Roll-up admission happens BEFORE the query limit (Codex round 2,
308
+ // finding 11) — and root ceilings enumerate too, through the STORED
309
+ // repos that pass the ceiling (attended review, finding 3); the
310
+ // per-row visible() re-check below stays either way.
311
+ const admission = all ? admissionList() : null;
312
+ const snapshot = store.boardScoped(all ? null : project, now, 200, admission);
313
+ const admitted = all
314
+ ? snapshot.tasks.filter(facts => facts.repo === null || visible(facts.repo))
315
+ : snapshot.tasks;
316
+ const done = all
317
+ ? snapshot.done.filter(row => row.repo === null || visible(row.repo))
318
+ : snapshot.done;
319
+ // A blocker may live in a repo this server must not speak about —
320
+ // redact its state before the pure classifier composes a sentence
321
+ // from it (Codex round 2, finding 12). The dependency's NAME stays:
322
+ // the edge belongs to the visible task; the other project's live
323
+ // status does not.
324
+ const cards = admitted.map(facts => classify(facts.blockerRepo !== null && !visible(facts.blockerRepo)
325
+ ? { ...facts, blockerState: null }
326
+ : facts, now));
327
+ // "Since you last looked": what concluded between this session's
328
+ // previous full board read and now. Fragment polls never move the
329
+ // anchor — an open tab is not a person looking.
330
+ let delta = null;
331
+ if (who.via === "cookie" && url.searchParams.get("fragment") !== "1") {
332
+ const prev = who.session.sawBoardAt;
333
+ if (prev !== null && now.getTime() - prev > 5 * 60_000) {
334
+ const sinceIso = new Date(prev).toISOString();
335
+ const runs = store.runsSinceScoped(sinceIso, all ? null : project).filter(one => all ? one.taskId !== "" : true);
336
+ delta = {
337
+ agoMinutes: Math.round((now.getTime() - prev) / 60_000),
338
+ built: runs.filter(one => one.outcome === "built" || one.outcome === "no-change").length,
339
+ failed: runs.filter(one => one.outcome === "failed").length,
340
+ questions: store.listDecisionsScoped(all ? null : project).filter(one => one.createdAt >= sinceIso && one.state !== "answered").length,
341
+ };
342
+ if (delta.built === 0 && delta.failed === 0 && delta.questions === 0)
343
+ delta = null;
344
+ }
345
+ who.session.sawBoardAt = now.getTime();
346
+ }
347
+ const buildingCount = cards.filter(card => card.lane === "building").length;
348
+ // A completed task whose proof is short or refuted and not yet
349
+ // accepted (Priority 2) reads "needs verification", not done — the
350
+ // board's once-and-only-once rule holds because this split is the
351
+ // ONE place a done row becomes either lane; the done-lane render
352
+ // below never sees the rows filtered out here.
353
+ const unverifiedDone = done.filter(row => (row.proofVerdict === "short" || row.proofVerdict === "refuted") && !row.proofAccepted);
354
+ const verifiedDone = done.filter(row => !unverifiedDone.includes(row));
355
+ const unverifiedCards = unverifiedDone.map(row => {
356
+ // v40: the SAME chip gains one more word when a repair chain
357
+ // exists for this row's own run — never a second card.
358
+ const chain = row.runId === null ? null : store.repairChainFor(row.runId);
359
+ const repairChain = chain === null
360
+ ? null
361
+ : {
362
+ attempt: chain.attempt,
363
+ outcome: chain.outcome,
364
+ approved: chain.draftTask !== null && (store.getScope(chain.draftTask)?.approvedAt ?? null) !== null,
365
+ };
366
+ return attentionCardForUnverifiedDone({
367
+ taskId: row.taskId,
368
+ title: row.title,
369
+ repo: row.repo,
370
+ completedAt: row.completedAt,
371
+ proofVerdict: row.proofVerdict,
372
+ proofMatrix: row.proofMatrix,
373
+ repairChain,
374
+ });
375
+ });
376
+ // Instances belong to their track row, not the main lanes — the board
377
+ // is for one-off work; tracks are the heartbeat. The one exception is
378
+ // attention: anything needing a person surfaces, wearing its routine.
379
+ const laneCards = [...cards.filter(card => card.routineName === null || card.lane === "attention"), ...unverifiedCards];
380
+ const tracks = store
381
+ .routineTracks(all ? null : project, now, admission)
382
+ .filter(track => visible(track.routine.repo));
383
+ const body = boardBody({ cards: laneCards, tracks, done: verifiedDone, saturated: snapshot.saturated, now, all, project, delta }, pr => store.ciFailureObserved(pr));
384
+ if (url.searchParams.get("fragment") === "1") {
385
+ // The live region alone — the in-page swapper's diet. Same auth,
386
+ // same ceiling, no shell, no scripts (finding 2).
387
+ return respond(response, 200, "text/html; charset=utf-8", body);
388
+ }
389
+ if (url.searchParams.get("view") === "order") {
390
+ // The board's ORDER view (operator request): the same screen, flipped
391
+ // to dispatch order with the queue's drag handles. Reordering and
392
+ // reserving are scheduling, not authority, so this is the one view
393
+ // where a drag does anything; the state view stays a view.
394
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
395
+ const revision = who.via === "cookie" ? who.session.projectRevision : 0;
396
+ const region = queueRegionFor(project, csrf, revision, now);
397
+ return sendScreen(response, 200, screen("board", [
398
+ `<h1>Board</h1>`,
399
+ `<p class="meta board-view"><a href="/board">state</a> \u00b7 <strong>order</strong> <span class="meta">\u2014 drag to reorder, or onto a worker to reserve; the state view is where cards move on their own</span></p>`,
400
+ `<div id="queue-region">${region}</div>`,
401
+ `<p class="meta" id="queue-region-stamp"></p>`,
402
+ ].join("\n"), { chrome: chromeFor(project, "board"), functional: { script: queueScript(), fetches: true } }));
403
+ }
404
+ const regionBody = `<div id="board-region">${body}</div><p class="meta" id="board-region-stamp"></p>`;
405
+ return sendScreen(response, 200, screen("board", regionBody, {
406
+ chrome: chromeFor(project, "board", undefined, all ? "board-all" : undefined),
407
+ functional: { script: regionScript("board-region", "1", buildingCount > 0 ? 10 : 30), fetches: true },
408
+ }));
409
+ }
410
+ if (url.pathname === "/review") {
411
+ // The review cockpit (Priority 5): every visible COMPLETED task,
412
+ // ranked by review priority, with one selected result projected
413
+ // from the records the task, run, and done pages already read —
414
+ // scope, plan, run, artifacts, verdict, contest, publication. The
415
+ // ranking is a labeled presentation aid: it never rewrites the
416
+ // stored verdict, and the sealed patch downloads exactly as stored.
417
+ // Admission binds BEFORE the SQL limit (the done page's own rule),
418
+ // and every row is re-proved against the ceiling before ranking.
419
+ const resultRow = (row) => {
420
+ const assignment = assignmentOf(store, row.taskId, now, workAccess(), evidenceRoot);
421
+ return { ...row, proofReasons: row.runId === null ? [] : store.proofVerdictFor(row.runId)?.reasons ?? [], ciFailing: ciFailingFor(row.runId, row.prNumber),
422
+ assignment: assignment?.activeTaskId === row.taskId && (assignment.receipt?.runId ?? null) === row.runId ? assignment : null };
423
+ };
424
+ const rows = familiesInView(project, { states: ["done"], limit: REVIEW_QUEUE_CAP }).flatMap(family => {
425
+ const row = completedRowFor(family.current.id, project);
426
+ return row === null ? [] : [{ ...resultRow(row), title: family.root.title }];
427
+ });
428
+ const ranked = rankReviewQueue(rows);
429
+ const wanted = url.searchParams.get("result");
430
+ const wantedId = wanted === null || wanted.length === 0 || wanted.length > 64 || hasForbiddenControls(wanted) ? null : wanted;
431
+ const inQueue = wantedId === null ? null : ranked.find(one => one.taskId === wantedId) ?? null;
432
+ // A stable deep link outlives the queue window (v2 review, comment
433
+ // 1): a completion older than the newest REVIEW_QUEUE_CAP resolves
434
+ // directly — re-proved as done, in this project, and inside the
435
+ // ceiling — so an old receipt never reads as unfinished or foreign.
436
+ // The ranked queue itself stays bounded and says so.
437
+ const beyond = wantedId === null || inQueue !== null ? null : completedRowFor(wantedId, project);
438
+ const beyondRow = beyond === null ? null : resultRow(beyond);
439
+ const completedChoice = inQueue ?? (beyondRow === null ? null : { ...beyondRow, priority: reviewPriorityOf(beyondRow) });
440
+ const expectedRun = url.searchParams.get("run");
441
+ const namedRun = expectedRun !== null && /^[1-9]\d*$/.test(expectedRun) ? Number(expectedRun) : null;
442
+ // A build that failed (or stopped) has its own result page too: the run named, or a failed task's latest attempt.
443
+ const attempt = wantedId === null || (completedChoice !== null && (namedRun === null || completedChoice.runId === namedRun)) ? null : attemptRowFor(wantedId, namedRun, project);
444
+ const attemptRow = attempt === null ? null : resultRow(attempt);
445
+ const chosen = attemptRow !== null ? { ...attemptRow, priority: reviewPriorityOf(attemptRow) } : completedChoice;
446
+ const selectedRow = wantedId === null ? ranked[0] ?? null : chosen;
447
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
448
+ // An exact link to a result this person can't see (or that never existed) reads the same either way.
449
+ if (expectedRun !== null && wantedId !== null && chosen === null)
450
+ return refuse(response, who, 404, "No such result in your projects.", "/work");
451
+ if (expectedRun !== null && (!/^[1-9]\d*$/.test(expectedRun) || selectedRow?.runId !== Number(expectedRun))) {
452
+ return sendScreen(response, 409, screen("Result changed", '<h1>Result changed</h1><p>This acceptance link no longer matches the current result. Review the current task before accepting.</p><p class="refusal-back"><a class="button-link" href="/review">Review results</a></p>', { chrome: chromeFor(project, "runs") }));
453
+ }
454
+ const selected = selectedRow === null ? null : reviewCockpitViewOf(selectedRow, who, now);
455
+ // A build that delivered nothing reads Failed here, and so does a failed task's delivered result: what went wrong
456
+ // is the card and Retry the ink act. That result may still be accepted, but only in outline, as Accept anyway, with
457
+ // a reason.
458
+ const delivered = selected?.run?.outcome === "built" || selected?.run?.outcome === "no-change";
459
+ const failedTask = selected === null ? null : store.getTask(selected.taskId);
460
+ if (selected !== null && attemptRow !== null && selected.run !== null && (!delivered || failedTask?.state === "failed")) {
461
+ const attemptRun = store.getRun(selected.run.id);
462
+ const runs = attemptRun === null ? [] : store.runsFor(attemptRun.taskRef);
463
+ const latest = latestFinishedAttempt(runs);
464
+ // Retry from here only when the task failed, nothing holds it, and this is its latest attempt or a result it delivered.
465
+ const retry = failedTask?.state === "failed" && (latest?.id === selected.run.id || delivered) && csrf !== "" && who.role === "approver" && store.currentLiveLease(attemptRun.taskRef, now) === null
466
+ ? { action: `${taskHref(selected.taskId)}/requeue` } : null;
467
+ // Accept anyway: the same acceptance this task's latest result takes, never for an older one or one already accepted.
468
+ const family = delivered ? familyOf(selected.taskId) : null;
469
+ const acceptAnyway = delivered && csrf !== "" && who.role === "approver" && family?.current.id === selected.taskId && family.problem === null &&
470
+ runs.find(runIsTaskResult)?.id === selected.run.id && store.proofAcceptance(selected.run.id) === null
471
+ ? { action: `${taskHref(selected.taskId)}/accept-proof`, run: selected.run.id } : null;
472
+ if (attemptRun !== null) {
473
+ // A delivered result didn't fail itself: the task's failure is its latest attempt's.
474
+ const failure = delivered ? failureOf(runs, null) : explainAttempt(attemptRun, null);
475
+ selected.failure = { ...failure, retry: retry === null ? null : { ...retry, note: retryNoteOf(failure.suggestion) }, ...(acceptAnyway === null ? {} : { acceptAnyway }) };
476
+ }
477
+ }
478
+ const reviewPage = reviewCockpitPage(chromeFor(wantedId === null ? project : chosenProject, "runs"), {
479
+ queue: ranked,
480
+ queueCap: REVIEW_QUEUE_CAP,
481
+ selected,
482
+ beyondQueue: beyond !== null && attemptRow === null,
483
+ // A deep link to a result this console cannot show — not done,
484
+ // not admitted, or never existed — says so in one sentence and
485
+ // shows the top of the queue; the three cases read identically.
486
+ missing: wantedId !== null && chosen === null ? wantedId : null,
487
+ csrf,
488
+ canRetryReview: who.via === "cookie" && who.role === "approver",
489
+ noted: url.searchParams.get("noted") !== null,
490
+ refusal: Object.hasOwn(RESULT_REFUSALS, url.searchParams.get("refused") ?? "") ? RESULT_REFUSALS[url.searchParams.get("refused")] : null,
491
+ tab: parseResultTab(url.searchParams.get("tab")),
492
+ user: who.name,
493
+ now,
494
+ });
495
+ // The result's task conversation, docked beside it (v77); messages
496
+ // sent here carry the result being viewed.
497
+ const reviewFocus = selected === null ? null : taskChatFocus(selected.taskId, now, who, { mintNonce: false });
498
+ const reviewDocked = reviewFocus === null || selected === null ? null
499
+ : dockedConversation(who, reviewFocus, null, now, reviewHref(selected.taskId), selected.run !== null && reviewFocus.family.versions.some(one => one.refId === store.getRun(selected.run.id)?.taskRef) ? selected.run.id : null);
500
+ if (reviewDocked !== null)
501
+ reviewPage.workspace = { ...reviewPage.workspace, conversation: reviewDocked, pageHtml: reviewPage.body };
502
+ return sendScreen(response, 200, reviewPage);
503
+ }
504
+ if (url.pathname === "/done") {
505
+ return sendScreen(response, 200, donePage(chromeFor(project, "runs"), store.listCompletedWorkScoped(project, 50), pr => store.ciFailureObserved(pr)));
506
+ }
507
+ if (url.pathname === "/tasks") {
508
+ const wanted = url.searchParams.get("state");
509
+ if (wanted !== null && !TASK_STATES.includes(wanted)) {
510
+ return refuse(response, who, 400, "no such state", "/tasks");
511
+ }
512
+ // ?template=<name> pre-fills the add form from the shipped library —
513
+ // a pre-filled form and nothing more; the submission path is the
514
+ // same guarded handler either way.
515
+ const fromTemplate = url.searchParams.get("template");
516
+ const picked = fromTemplate === null ? null : templateByName(fromTemplate);
517
+ const prefill = picked !== null && picked.kind === "task"
518
+ ? {
519
+ title: picked.title, goal: picked.goal, not: picked.outOfScope ?? "", touches: picked.touches.join(", "),
520
+ acceptance: acceptanceToLines(picked.acceptance).join("\n"),
521
+ }
522
+ : null;
523
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
524
+ return sendScreen(response, 200, tasksPage(chromeFor(project, "tasks"), familyTasksInView(project, wanted === null ? undefined : wanted).slice(0, 200), wanted, csrf, null, project, prefill, store.permissionDefault().mode, store.qualityDefault().mode, store.replacements()));
525
+ }
526
+ if (url.pathname === "/queue") {
527
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
528
+ const revision = who.via === "cookie" ? who.session.projectRevision : 0;
529
+ // Column headers read one thing beyond the queue snapshot: live claims
530
+ // in THIS project, per worker (the capacity is global; attended claims
531
+ // may exceed it), so the header names both and never a ratio.
532
+ if (url.searchParams.get("fragment") === "1") {
533
+ return respond(response, 200, "text/html; charset=utf-8", queueRegionFor(project, csrf, revision, clock()));
534
+ }
535
+ // The queue is the board's order view (reduction pass §1): the URL
536
+ // keeps answering, as a redirect, so nothing anyone bookmarked 404s.
537
+ return redirect(response, QUEUE_VIEW);
538
+ }
539
+ if (url.pathname === "/tasks/new") {
540
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
541
+ const revision = who.via === "cookie" ? who.session.projectRevision : 0;
542
+ const chainable = store
543
+ .listTasksScoped(project, undefined, 100, null)
544
+ .filter(one => one.state !== "done" && one.state !== "cancelled" && visible(one.repo))
545
+ .map(one => ({ id: one.id, title: one.title }));
546
+ const chrome = chromeFor(project, "tasks");
547
+ return sendScreen(response, 200, newTaskPage(chrome, project, csrf, revision, null, chainable, store.permissionDefault().mode, store.qualityDefault().mode, chrome.projects ?? []));
548
+ }
549
+ const taskLive = matchTaskPath(url.pathname, "/live$");
550
+ if (taskLive !== null) {
551
+ // The live task page: a nudge the moment the task changes, and who else
552
+ // has it open. Hints only — the page reads the task the usual way.
553
+ if (who.via !== "cookie")
554
+ return respond(response, 403, "application/json", JSON.stringify({ error: "session" }));
555
+ const family = familyOf(taskLive.taskId);
556
+ if (family === null || family.problem !== null)
557
+ return respond(response, 404, "application/json", JSON.stringify({ error: "task" }));
558
+ response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-store", "x-content-type-options": "nosniff", "x-accel-buffering": "no" });
559
+ const name = who.name, repo = family.root.repo;
560
+ taskRooms.join(family.root.id, {
561
+ name, response,
562
+ // Rechecked while open, from the cookie and the account (never a request's context): a sign-out or a narrowed account stops it.
563
+ valid: () => { const again = identify(request, false); return again !== null && again.name === name && rowVisible(liveCeiling(), repo) && store.accountCanAccess(name, repo); },
564
+ });
565
+ request.once("close", () => { if (!response.writableEnded)
566
+ response.end(); });
567
+ return;
568
+ }
569
+ const task = matchTaskPath(url.pathname, "");
570
+ if (task !== null) {
571
+ const family = familyOf(task.taskId);
572
+ if (family === null)
573
+ return refuse(response, who, 404, "no such task", "/tasks");
574
+ const version = url.searchParams.get("version");
575
+ if (version !== null && !family.versions.some(one => one.id === version))
576
+ return refuse(response, who, 404, "That version is not available for this task.", taskHref(family.root.id));
577
+ // A version's own address lands on its family's page, still asking for the plan editor when it did (Chat's Edit plan).
578
+ if (task.taskId !== family.root.id)
579
+ return redirect(response, `${taskHref(family.root.id)}?version=${encodeURIComponent(task.taskId)}${url.searchParams.get("edit") === "plan" ? "&edit=plan" : ""}`);
580
+ return taskScreen(response, who, version ?? family.current.id, null, 200, undefined, undefined, url.searchParams.get("edit") === "plan");
581
+ }
582
+ // v103: a task's evidence pack, as a printable page or JSON (the whole family, sealed ledger entries included).
583
+ const evidence = matchTaskPath(url.pathname, "/evidence$");
584
+ if (evidence !== null) {
585
+ const access = workAccess();
586
+ const family = store.taskFamilyOf(evidence.taskId, access.repos, access.includeUnplaced);
587
+ if (family === null || !visible(family.root.repo))
588
+ return refuse(response, who, 404, "no such task", "/tasks");
589
+ if (evidence.taskId !== family.root.id)
590
+ return redirect(response, `${taskHref(family.root.id)}/evidence${url.search}`);
591
+ const pack = evidencePack(store, family.root.id, access, who.name, now, evidenceRoot);
592
+ if (pack === null)
593
+ return refuse(response, who, 404, "no such task", "/tasks");
594
+ if (url.searchParams.get("format") === "json") {
595
+ response.writeHead(200, { "content-type": "application/json; charset=utf-8", "content-disposition": `attachment; filename="evidence-${family.root.id.replace(/[^A-Za-z0-9._-]/g, "_")}.json"`,
596
+ "cache-control": "no-store", "x-content-type-options": "nosniff" });
597
+ return void response.end(JSON.stringify(checkResponse("evidencePack", pack), null, 2));
598
+ }
599
+ return sendScreen(response, 200, screen(`Evidence pack · ${family.root.id}`, evidencePackHtml(pack), { chrome: chromeFor(family.root.repo, "tasks") }));
600
+ }
601
+ if (url.pathname === "/runs") {
602
+ const raw = url.searchParams.get("before");
603
+ let before = null;
604
+ if (raw !== null) {
605
+ if (!/^[1-9][0-9]{0,14}$/.test(raw) || !Number.isSafeInteger(Number(raw))) {
606
+ return refuse(response, who, 400, "that cursor is not a page", "/runs");
607
+ }
608
+ before = Number(raw);
609
+ }
610
+ const rows = store.listRunsBefore(before, RUNS_PAGE, project);
611
+ const verdicts = store.proofVerdictsFor(rows.map(one => one.id));
612
+ const accepted = new Set(rows.filter(one => store.proofAcceptance(one.id) !== null).map(one => one.id));
613
+ return sendScreen(response, 200, runsPage(chromeFor(project, "runs"), rows, liveRunIds(rows), rows.length === RUNS_PAGE ? rows[rows.length - 1]?.id ?? null : null, verdicts, accepted));
614
+ }
615
+ const run = /^\/r\/([0-9]{1,15})$/.exec(url.pathname);
616
+ if (run !== null) {
617
+ const found = store.getRun(Number(run[1]));
618
+ if (found === null || !runVisible(found)) {
619
+ return refuse(response, who, 404, "no such run", "/runs");
620
+ }
621
+ const taskId = store.externalIdFor(found.taskRef) ?? "?";
622
+ const running = runIsLive(found);
623
+ if (url.searchParams.get("fragment") === "check") {
624
+ response.setHeader("cache-control", "no-store");
625
+ return respond(response, 200, "text/html; charset=utf-8", checkProgressHtml(store.checkProgress(found.id)));
626
+ }
627
+ if (url.searchParams.get("fragment") === "facts") {
628
+ // The facts region alone — same auth and ceiling as the page; a
629
+ // finished or no-longer-live run says so rather than growing forms
630
+ // (finding 5; round-4 finding 15).
631
+ return respond(response, 200, "text/html; charset=utf-8", runFactsFragment(found, taskId, running, store.runRoute(found.id)));
632
+ }
633
+ if (url.searchParams.get("fragment") === "peek") {
634
+ // The live peek: cookie sessions only (v2 §3), never stored, never
635
+ // cached by anything downstream.
636
+ if (who.via !== "cookie")
637
+ return respond(response, 403, "text/plain; charset=utf-8", "the live peek is a browser session's view");
638
+ const peeked = await peekFragment(found.id, who.session.csrf, options.editorLinks !== undefined && found.runner === options.localRunner && who.session.editorLinks === true);
639
+ response.setHeader("cache-control", "no-store");
640
+ if (peeked.retryAfter !== undefined)
641
+ response.setHeader("retry-after", String(peeked.retryAfter));
642
+ return respond(response, peeked.status, "text/html; charset=utf-8", peeked.body);
643
+ }
644
+ if (url.searchParams.get("fragment") === "transcript") {
645
+ // The live transcript window (arc 1): raw sanitized TEXT as JSON —
646
+ // the sink is textContent, never innerHTML. Guards, enumerated:
647
+ // cookie session; run visible (proved above); this machine's
648
+ // runner asserted; byte offsets validated; the file read through
649
+ // its own descriptor with the exact numeric-id name. Display
650
+ // state, not evidence — and never cached.
651
+ response.setHeader("cache-control", "no-store");
652
+ if (who.via !== "cookie")
653
+ return respond(response, 403, "application/json", JSON.stringify({ error: "session" }));
654
+ if (options.localRunner === undefined) {
655
+ return respond(response, 200, "application/json", JSON.stringify({ error: "off" }));
656
+ }
657
+ const fromRaw = url.searchParams.get("from") ?? "0";
658
+ const from = /^[0-9]{1,15}$/.test(fromRaw) ? Number(fromRaw) : Number.NaN;
659
+ if (!Number.isSafeInteger(from) || from < 0) {
660
+ return respond(response, 400, "application/json", JSON.stringify({ error: "offset" }));
661
+ }
662
+ const live = runIsLive(found);
663
+ // The final drain: a finished run's tail — including a torn last
664
+ // line — may be read until the sweep removes the file.
665
+ const window = readLiveWindow(evidenceRoot, found.id, from, !live);
666
+ if (!window.ok) {
667
+ if (window.reason === "replaced") {
668
+ return respond(response, 409, "application/json", JSON.stringify({ error: "replaced" }));
669
+ }
670
+ // Missing or unreadable: nothing to show. Final only when the
671
+ // run can never write again.
672
+ return respond(response, 200, "application/json", JSON.stringify({ text: "", nextOffset: from, final: !live }));
673
+ }
674
+ // Re-proof after the read (peek discipline): the run row still says
675
+ // what admission said, or nothing is shown.
676
+ const again = store.getRun(found.id);
677
+ if (again === null || !runVisible(again)) {
678
+ return respond(response, 200, "application/json", JSON.stringify({ text: "", nextOffset: from, final: true }));
679
+ }
680
+ return respond(response, 200, "application/json", JSON.stringify({ text: window.text, nextOffset: window.nextOffset, final: !live && window.eof }));
681
+ }
682
+ // One result page (2026-10-02): a builder's finished result opens on
683
+ // its task's result page, titled with the task. The raw run record
684
+ // waits there under Details, and here with ?record=1.
685
+ if (url.searchParams.get("record") === null && !running && found.role === "builder" && (found.outcome === "built" || found.outcome === "no-change") &&
686
+ completedRowFor(taskId, null)?.runId === found.id) {
687
+ const tab = parseResultTab(url.searchParams.get("tab"));
688
+ return redirect(response, `${reviewHref(taskId, found.id)}${tab === "summary" ? "" : `&tab=${tab}`}`);
689
+ }
690
+ // A build that failed or stopped opens on its own result page too: its changes, its checks and what went wrong.
691
+ if (url.searchParams.get("record") === null && !running && (found.role === "builder" || found.role === "scout") && attemptRowFor(taskId, found.id, null) !== null) {
692
+ const tab = parseResultTab(url.searchParams.get("tab"));
693
+ return redirect(response, `${reviewHref(taskId, found.id)}${tab === "summary" ? "" : `&tab=${tab}`}`);
694
+ }
695
+ // Pollers exist only for a LIVE run — an orphaned null-outcome run
696
+ // would otherwise be refetched forever (finding 15). The nonce is
697
+ // sendScreen's business now.
698
+ const artifacts = store.artifactsFor(found.id);
699
+ return sendScreen(response, 200, runPage(chromeFor(project, "runs", runListPane(project, found.id)), found, taskId, artifacts, terminalDiffView(artifacts, evidenceRoot), store.notesForRun(found.id), who.via === "cookie" ? who.session.csrf : "", store.liveDiffComments(found.id).filter(isRevisionFeedback), (() => {
700
+ const publication = store.publicationForRun(found.id);
701
+ return publication !== null && publication.prNumber !== null && store.hasOpenCiEpisode(publication.githubRepo, publication.prNumber)
702
+ ? { pr: publication.prNumber }
703
+ : null;
704
+ })(), !running
705
+ ? undefined
706
+ : regionScript("run-facts", "facts", 10) +
707
+ (options.localRunner === undefined ? "" : regionScript("run-peek", "peek", 15)) +
708
+ (options.localRunner === undefined ? "" : transcriptScript()), options.localRunner !== undefined, running,
709
+ // Editor links (arc 6): three statements align or nothing renders —
710
+ // the deployment capability, THIS machine's runner owning the run,
711
+ // and the session's own device-side yes.
712
+ options.editorLinks !== undefined &&
713
+ options.localRunner !== undefined &&
714
+ found.runner === options.localRunner &&
715
+ who.via === "cookie" &&
716
+ who.session.editorLinks === true &&
717
+ // A reviewer run (v29) never had a checkout — no files to open.
718
+ found.worktree !== null
719
+ ? { worktree: found.worktree }
720
+ : null, options.editorLinks !== undefined &&
721
+ options.localRunner !== undefined &&
722
+ found.runner === options.localRunner &&
723
+ who.via === "cookie"
724
+ ? { on: who.session.editorLinks === true }
725
+ : null, url.searchParams.get("noted") !== null, (() => {
726
+ const held = store.heldSessionOf(found.id);
727
+ if (held === null)
728
+ return null;
729
+ const authorization = store.readAuthorization(held.authorizationId);
730
+ return {
731
+ turns: store.sessionTurnsOf(found.id),
732
+ open: held.endedAt === null && held.state === "open",
733
+ state: authorization === null
734
+ ? "session record"
735
+ : attendedWatchWords(authorization.lastBeatAt, now, authorization.absoluteExpiry),
736
+ cap: authorization?.maxSessionTurns ?? 0,
737
+ };
738
+ })(), options.attended !== undefined &&
739
+ who.via === "cookie" &&
740
+ !store.isDemo() &&
741
+ (found.outcome === "built" || found.outcome === "no-change") &&
742
+ store.openAuthorizationFor(found.taskRef) === null
743
+ ? { taskId }
744
+ : null, structuredHandoffView(artifacts, evidenceRoot), proofBundleView(store, found, artifacts, evidenceRoot), store.runRoute(found.id), store.publicationForRun(found.id), reviewFactsFor(found.id),
745
+ // The shared result presentation (package 3) for a finished result.
746
+ !running && (found.outcome === "built" || found.outcome === "no-change")
747
+ ? { detail: resultDetailOf(found, who, now), tab: parseResultTab(url.searchParams.get("tab")), user: who.name, requestToken: randomBytes(16).toString("hex") }
748
+ : null, store.getScope(taskId)?.digest ?? null));
749
+ }
750
+ const runArtifact = /^\/r\/([0-9]{1,15})\/evidence\/([0-9]{1,15})$/.exec(url.pathname);
751
+ if (runArtifact !== null) {
752
+ return runEvidence(response, Number(runArtifact[1]), Number(runArtifact[2]));
753
+ }
754
+ const contestPath = /^\/contest\/([0-9]{1,15})$/.exec(url.pathname);
755
+ if (contestPath !== null) {
756
+ return contestScreen(response, who, Number(contestPath[1]), null, 200);
757
+ }
758
+ if (url.pathname === "/routines") {
759
+ // The ceiling row by row, exactly as everywhere: a routine placed in
760
+ // a repo this server may not serve does not exist here.
761
+ const tracks = store
762
+ .routineTracks(project, now)
763
+ .filter(track => visible(track.routine.repo));
764
+ // ?template=<name> pre-fills the filing form from the shipped
765
+ // library — a pre-filled form, same guarded submission path.
766
+ const fromTemplate = url.searchParams.get("template");
767
+ const picked = fromTemplate === null ? null : templateByName(fromTemplate);
768
+ return sendScreen(response, 200, routinesPage(chromeFor(project, "routines"), tracks, {
769
+ csrf: who.via === "cookie" ? who.session.csrf : "",
770
+ revision: who.via === "cookie" ? who.session.projectRevision : 0,
771
+ problem: null,
772
+ prefill: picked !== null && picked.kind === "routine"
773
+ ? {
774
+ name: picked.routineName,
775
+ goal: picked.goal,
776
+ not: picked.outOfScope ?? "",
777
+ touches: picked.touches.join(", "),
778
+ schedule: picked.schedule,
779
+ acceptance: acceptanceToLines(picked.acceptance).join("\n"),
780
+ }
781
+ : null,
782
+ }));
783
+ }
784
+ const routineScreen = /^\/routines\/([0-9]{1,15})$/.exec(url.pathname);
785
+ if (routineScreen !== null) {
786
+ const routine = store.getRoutine(Number(routineScreen[1]));
787
+ if (routine === null || !visible(routine.repo)) {
788
+ return refuse(response, who, 404, "no such routine", "/routines");
789
+ }
790
+ return routinePage(response, who, routine.id, null, 200);
791
+ }
792
+ const one = /^\/d\/([0-9]{1,15})$/.exec(url.pathname);
793
+ if (one !== null) {
794
+ const decision = store.getDecision(Number(one[1]));
795
+ if (decision === null)
796
+ return refuse(response, who, 404, "no such decision");
797
+ const run = store.getRun(decision.run);
798
+ if (run !== null && !visible(taskRepoOf(run.taskRef))) {
799
+ return refuse(response, who, 404, "no such decision");
800
+ }
801
+ const taskId = taskOf(store, decision);
802
+ const decisionReturn = url.searchParams.get("return");
803
+ const back = decisionReturn === null ? null : safeChatReturn(decisionReturn);
804
+ return sendScreen(response, 200, decisionPage(chromeFor(project, "none"), decision, taskId, store.evidenceFor(decision.id), who, now, back));
805
+ }
806
+ const artifact = /^\/d\/([0-9]{1,15})\/evidence\/([0-9]{1,15})$/.exec(url.pathname);
807
+ if (artifact !== null) {
808
+ return decisionEvidence(response, Number(artifact[1]), Number(artifact[2]));
809
+ }
810
+ return refuse(response, who, 404, "There's no page at this address.", "/chat");
811
+ }
812
+ async function post(ctx) {
813
+ const { url, who, request, response, now, project, chosenProject, posted, route } = ctx;
814
+ // The attended beat answers BEFORE the shared mutation guard (v28): it
815
+ // carries no parameters, so there is no csrf token to check — its OWN
816
+ // guard is complete and STRICTER for the browsers this console
817
+ // supports: cookie session, form content-type (form() enforced it),
818
+ // and `Sec-Fetch-Site: same-origin`, which cross-site POSTs cannot
819
+ // send and same-origin fetch always does. Renewal-only: an attacker
820
+ // who somehow posted could only keep the operator's OWN sessions from
821
+ // lapsing — no mint, no spend, no read.
822
+ if (url.pathname === "/session/attended-beats") {
823
+ return attendedBeats(who, request, response, clock());
824
+ }
825
+ if (url.pathname === '/code' || url.pathname.startsWith('/code/')) {
826
+ const body = readForm(posted, CONSOLE_FORMS.code);
827
+ const wantsJson = request.headers.accept?.includes('application/json') === true;
828
+ const fail = (status, message, delivery = 'rejected', sessionId) => wantsJson
829
+ ? respond(response, status, 'application/json', JSON.stringify({ ok: false, error: message, delivery, ...(sessionId ? { sessionId } : {}) }))
830
+ : refuse(response, who, status, message, '/code');
831
+ if (who.via !== 'cookie' || !codingActorAllowed({ name: who.name, generation: who.session.generation }) || store.isDemo())
832
+ return fail(403, 'Coding sessions require installation-wide operator access. You can manage project tasks in Work.');
833
+ if (!runtime.coding)
834
+ return fail(409, runtime.codingProblem || 'Open Toolroll on the machine with your installed coding agent.');
835
+ if ([...new Set(body.keys())].some(key => body.sent.getAll(key).length !== 1))
836
+ return fail(400, 'Submit one value for each field.');
837
+ const actor = { name: who.name, generation: who.session.generation };
838
+ const permitted = (repo) => visible(repo) && codingProjectAllowed(repo);
839
+ // Sprint 8: a coding session is Codex taking turns: the organisation policy decides whether it may (provider,
840
+ // model, and a ceiling Codex can't run under), when it starts and on every action that makes it take another turn.
841
+ const turnAction = /^\/code\/[a-f0-9]{32}\/(send|resume|recover|continue|answer)$/.test(url.pathname);
842
+ if (url.pathname === '/code/start' || turnAction) {
843
+ const stopped = store.sessionPolicyRefusal('codex', url.pathname === '/code/start' ? body.get('model')?.trim() || null : null, 'coding sessions');
844
+ if (stopped !== null)
845
+ return fail(403, stopped);
846
+ }
847
+ try {
848
+ let id;
849
+ if (url.pathname === '/code/start') {
850
+ const repo = body.get('repo') ?? '';
851
+ if (!permitted(repo))
852
+ return fail(403, 'Choose a project available to your account.');
853
+ if (!authenticateApprover(who, body.get('password') ?? '').ok)
854
+ return fail(403, 'Enter your Toolroll password to authorize this coding session.');
855
+ const session = await runtime.coding.start(actor, { repo, title: body.get('title') ?? '', model: body.get('model')?.trim() || null, prompt: body.get('prompt') ?? '', requestId: body.get('requestId') ?? '' });
856
+ id = session.id;
857
+ }
858
+ else {
859
+ const match = /^\/code\/([a-f0-9]{32})\/(send|stop|resume|recover|continue|ship|answer)$/.exec(url.pathname);
860
+ if (!match)
861
+ return fail(404, 'Coding action not found.');
862
+ id = match[1];
863
+ const session = runtime.coding.get(id, actor);
864
+ if (!permitted(session.repo))
865
+ return fail(403, 'That project is outside your access.');
866
+ if (match[2] === 'ship') {
867
+ const made = createCodingHandoff(store, { sessionId: id, actor: who.name, repo: session.repo, base: body.get('base') ?? '', candidate: body.get('candidate') ?? '', title: body.get('title') ?? '', goal: body.get('goal') ?? '', acceptance: (body.get('acceptance') ?? '').split('\n').map(line => line.trim()).filter(Boolean).map((statement, index) => ({ id: `c${index + 1}`, statement, evidence: ['check', 'changed-path', ...(body.get('visual') === 'yes' ? ['screenshot'] : [])] })) });
868
+ return wantsJson ? respond(response, 200, 'application/json', JSON.stringify({ ok: true, taskId: made.taskId })) : redirect(response, `/t/${encodeURIComponent(made.taskId)}`);
869
+ }
870
+ if (match[2] === 'send')
871
+ await runtime.coding.send(id, actor, body.get('prompt') ?? '', body.get('requestId') ?? '');
872
+ else if (match[2] === 'stop')
873
+ await runtime.coding.stop(id, actor);
874
+ else if (match[2] === 'resume')
875
+ await runtime.coding.resume(id, actor);
876
+ else if (match[2] === 'recover')
877
+ await runtime.coding.recover(id, actor);
878
+ else if (match[2] === 'continue')
879
+ await runtime.coding.continueSaved(id, actor);
880
+ else {
881
+ const token = body.get('requestId') ?? '';
882
+ const pending = runtime.coding.snapshot(id, actor).requests.find(r => r.id === token);
883
+ if (!pending)
884
+ return fail(409, 'This request is no longer waiting for an answer.');
885
+ const decision = body.get('decision');
886
+ if (decision !== 'accept' && decision !== 'decline' && decision !== 'cancel')
887
+ return fail(400, 'Choose an explicit decision for this request.');
888
+ if (pending.kind !== 'questions' && decision === 'accept' && !authenticateApprover(who, body.get('password') ?? '').ok)
889
+ return fail(403, 'Enter your password to approve this additional access.');
890
+ const answers = body.has('answers') ? JSON.parse(body.get('answers')) : Object.fromEntries(pending.questions.map(q => [q.id, { answers: [body.get(`question:${q.id}`) ?? ''] }]));
891
+ runtime.coding.answer(id, actor, token, decision, answers);
892
+ }
893
+ }
894
+ // Authorization is checked again after asynchronous startup/transport work.
895
+ if (!codingActorAllowed(actor))
896
+ return fail(403, 'Your access changed. Sign in again.', 'unknown', id);
897
+ return wantsJson ? respond(response, 200, 'application/json', JSON.stringify({ ok: true, id })) : redirect(response, `/code/${id}`);
898
+ }
899
+ catch (error) {
900
+ if (!codingActorAllowed(actor))
901
+ return fail(403, 'Your access changed. Sign in again.', error instanceof CodingActionError ? error.delivery : 'rejected', error instanceof CodingActionError ? error.sessionId : undefined);
902
+ const message = error instanceof Error ? error.message : 'The coding action could not finish. Your draft is preserved.';
903
+ const ship = /^\/code\/([a-f0-9]{32})\/ship$/.exec(url.pathname);
904
+ if (ship && !wantsJson) {
905
+ try {
906
+ const preview = codingHandoffPreview(store, { sessionId: ship[1], actor: who.name });
907
+ return sendScreen(response, 409, screen('Review for shipping', codingShippingHtml(preview, who.session.csrf, body.sent, message), { chrome: chromeFor(preview.repo, 'code') }));
908
+ }
909
+ catch { }
910
+ }
911
+ return fail(409, message, error instanceof CodingActionError ? error.delivery : 'rejected', error instanceof CodingActionError ? error.sessionId : undefined);
912
+ }
913
+ }
914
+ if (url.pathname === "/tasks/add") {
915
+ const body = readForm(posted, CONSOLE_FORMS.tasksAdd);
916
+ const project = projectOf(who, request);
917
+ if (project === undefined) {
918
+ return refuse(response, who, 403, "that project is outside what this server was configured to show");
919
+ }
920
+ // Stale-tab guard: a form rendered under one open project must not
921
+ // create into a different one switched-to since (finding 6).
922
+ if (who.via === "cookie") {
923
+ const seen = body.get("projectRevision");
924
+ if (seen !== null && seen !== String(who.session.projectRevision)) {
925
+ return refuse(response, who, 409, "the open project changed since this form was rendered — reload and try again", "/tasks");
926
+ }
927
+ }
928
+ const id = (body.get("id") ?? "").trim();
929
+ const title = body.get("title") ?? "";
930
+ // The EFFECTIVE placement (repo onboarding, findings 15/35): the
931
+ // trimmed, nonempty posted repo, else the open project — an empty
932
+ // input falls through correctly. In root mode the effective path is
933
+ // proved by authorizedProject EXACTLY as typed-then-canonicalized,
934
+ // and [canonical] is the admitted list — a fresh clone under a root
935
+ // must be able to receive its first task without waiting to appear
936
+ // in any table.
937
+ const repoGiven = (body.get("repo") ?? "").trim();
938
+ const effective = repoGiven !== "" ? repoGiven : (project ?? "");
939
+ if (restricted() && !visible(effective === "" ? null : effective))
940
+ return refuse(response, who, 403, "That project is outside your access.", "/projects");
941
+ // A scoped console refuses an EMPTY placement server-side (verification
942
+ // finding 1): the form's `required` is a courtesy, not the guard — a
943
+ // direct POST with no project open must not mint an unplaced task
944
+ // under a ceiling. Unscoped mode keeps its historic unplaced filings.
945
+ if (!unscopedMode && effective === "") {
946
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
947
+ return sendScreen(response, 400, tasksPage(chromeFor(project, "tasks"), familyTasksInView(project).slice(0, 200), null, csrf, "name a repository — no project is open, so the task must say where it belongs", project, null, store.permissionDefault().mode, store.qualityDefault().mode, store.replacements()));
948
+ }
949
+ let repo = effective;
950
+ let admitted = unscopedMode ? null : admissionList() ?? [];
951
+ const rootMode = !unscopedMode && ceiling.roots.length > 0;
952
+ if (rootMode && effective !== "") {
953
+ const canonical = (await authorizedProject(liveCeiling(), effective)) ? canonicalProject(effective) : null;
954
+ if (canonical === null || canonical === undefined) {
955
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
956
+ return sendScreen(response, 403, tasksPage(chromeFor(project, "tasks"), familyTasksInView(project).slice(0, 200), null, csrf, `${effective} is outside what this server was configured to show`, project, null, store.permissionDefault().mode, store.qualityDefault().mode, store.replacements()));
957
+ }
958
+ repo = canonical;
959
+ admitted = [canonical];
960
+ }
961
+ const goal = body.get("goal") ?? "";
962
+ const notThis = body.get("not") ?? "";
963
+ const touchesGiven = (body.get("touches") ?? "")
964
+ .split(/[\n,]/)
965
+ .map(one => one.trim())
966
+ .filter(one => one !== "");
967
+ const scout = body.get("scout") === "1";
968
+ const permissionMode = body.get("permission-mode");
969
+ if (permissionMode !== null && permissionMode !== "auto" && permissionMode !== "bypassPermissions") {
970
+ return refuse(response, who, 400, "permissions must be Auto or Full access", "/tasks/new");
971
+ }
972
+ const qualityMode = body.get("quality-mode");
973
+ if (qualityMode !== null && !isQualityMode(qualityMode)) {
974
+ return refuse(response, who, 400, "quality must be Default or Strict / release", "/tasks/new");
975
+ }
976
+ // One filing door for every surface (Codex adoption review, finding 7).
977
+ const after = (body.get("after") ?? "").trim();
978
+ let chainProblem = null;
979
+ const made = store.transact(() => {
980
+ const filed = fileTaskProposal(store, {
981
+ ...(id === "" ? {} : { id }),
982
+ title,
983
+ ...(repo === "" ? {} : { repo }),
984
+ ...(goal === "" ? {} : { goal, acceptance: acceptanceLinesToInput((body.get("acceptance") ?? "").split("\n")) }),
985
+ outOfScope: notThis === "" ? null : notThis,
986
+ touches: touchesGiven,
987
+ ...(permissionMode === null ? {} : { permissionMode }),
988
+ ...(qualityMode === null ? {} : { qualityMode }),
989
+ ...(scout ? { deliverable: "report" } : {}),
990
+ planning: scout
991
+ ? "skip"
992
+ : body.get("planning-policy") === "choice"
993
+ ? body.get("plan-first") === "1" ? "required" : "skip"
994
+ : "auto",
995
+ filedVia: "console", filedBy: { name: who.name, kind: "person" },
996
+ ...(admitted === null ? {} : { admittedRepos: admitted }),
997
+ }, now);
998
+ if (filed.ok && after !== "") {
999
+ const afterRef = store.lookupRef(after);
1000
+ const chained = store.getTask(after) !== null && afterRef !== null && visible(afterRef.repo)
1001
+ ? store.addEdge(filed.id, after) : { ok: false, reason: "that task does not exist here" };
1002
+ if (!chained.ok)
1003
+ chainProblem = chained.reason;
1004
+ }
1005
+ // Missing dependencies leave a reviewable, inert task. They must
1006
+ // never be silently dropped before automatic approval starts work.
1007
+ if (filed.ok && chainProblem === null && who.via === "cookie")
1008
+ applyModeToNewFiling(store, filed.id, who.name, now);
1009
+ return filed;
1010
+ });
1011
+ if (!made.ok) {
1012
+ const csrf = who.via === "cookie" ? who.session.csrf : "";
1013
+ return sendScreen(response, made.reason === "backlog-full" ? 429 : 400, tasksPage(chromeFor(project, "tasks"), familyTasksInView(project).slice(0, 200), null, csrf, made.message, project, { title, goal, not: notThis, touches: body.get("touches") ?? "", acceptance: body.get("acceptance") ?? "", values: body.sent }, store.permissionDefault().mode, store.qualityDefault().mode, store.replacements()));
1014
+ }
1015
+ const actionContext = requestContext.getStore();
1016
+ if (actionContext !== undefined)
1017
+ actionContext.createdTask = made.id;
1018
+ // A proved root-mode placement joins the project table (finding 15):
1019
+ // the new task's home is openable and admissible from now on.
1020
+ if (rootMode && repo !== "")
1021
+ store.upsertProject(repo, projectName(repo), now);
1022
+ if (chainProblem !== null)
1023
+ return taskScreen(response, who, made.id, `the task was created, but could not be made to wait for ${after} — ${chainProblem}`, 200);
1024
+ return redirect(response, taskHref(made.id));
1025
+ }
1026
+ if (url.pathname === "/queue/move" || url.pathname === "/queue/note") {
1027
+ const body = readForm(posted, CONSOLE_FORMS.queue);
1028
+ const project = projectOf(who, request);
1029
+ if (project === undefined) {
1030
+ return refuse(response, who, 403, "that project is outside what this server was configured to show");
1031
+ }
1032
+ // The note is a global runner label, not project-scoped work: the
1033
+ // fleet screen (project-less) posts it too, so the null-project gate
1034
+ // applies only to moves, which the task-level check below re-proves.
1035
+ if (url.pathname === "/queue/note") {
1036
+ const worker = (body.get("runner") ?? "").trim();
1037
+ const note = (body.get("note") ?? "").trim();
1038
+ if (note.length > 200 || hasForbiddenControls(note)) {
1039
+ return refuse(response, who, 400, "that note will not render, so it will not store", QUEUE_VIEW);
1040
+ }
1041
+ const set = store.setRunnerQueueNote(worker, note === "" ? null : note);
1042
+ if (!set.ok)
1043
+ return refuse(response, who, 404, "no such worker", QUEUE_VIEW);
1044
+ return redirect(response, body.get("from") === "fleet" ? "/fleet" : QUEUE_VIEW);
1045
+ }
1046
+ if (who.via === "cookie") {
1047
+ const seen = body.get("projectRevision");
1048
+ if (seen !== null && seen !== String(who.session.projectRevision)) {
1049
+ return refuse(response, who, 409, "the open project changed since this form was rendered — reload and try again", QUEUE_VIEW);
1050
+ }
1051
+ }
1052
+ // A drag posts in place (fetch) when it can; the page re-renders its
1053
+ // own fragment on success. A plain form still works with no script.
1054
+ const inPlace = body.get("respond") === "fragment";
1055
+ const fromFleet = who.via === "cookie" && body.get("projectRevision") === null;
1056
+ const respondMove = (status, message) => {
1057
+ if (!inPlace)
1058
+ return status === 409 ? refuse(response, who, 409, message, QUEUE_VIEW) : redirect(response, QUEUE_VIEW);
1059
+ respond(response, status, "text/plain; charset=utf-8", message);
1060
+ };
1061
+ const moveReason = (reason) => reason === "stale"
1062
+ ? "the queue moved underneath you — it just reloaded"
1063
+ : reason === "claimed" || reason === "contest-open"
1064
+ ? "that task is being taken right now — it keeps its claim"
1065
+ : reason === "worker-retired"
1066
+ ? "that worker is retired — drag its work elsewhere, or register the name again"
1067
+ : reason === "no-such-worker"
1068
+ ? "no such worker"
1069
+ : "that task is not in this queue any more";
1070
+ const taskId = (body.get("task") ?? "").trim();
1071
+ const columnGiven = (body.get("column") ?? "").trim();
1072
+ const toRunner = columnGiven === "" || columnGiven === "anyone" ? null : columnGiven;
1073
+ const beforeGiven = (body.get("before") ?? "").trim();
1074
+ const revisionGiven = Number(body.get("queueRevision") ?? "");
1075
+ // The queue's own screen enforces the open project; the fleet screen
1076
+ // is cross-project, so the ceiling is the only wall it needs.
1077
+ const belongs = (id) => {
1078
+ const ref = store.lookupRef(id);
1079
+ return ref !== null && visible(ref.repo) && (fromFleet || project === null || ref.repo === null || ref.repo === project);
1080
+ };
1081
+ if (!belongs(taskId) || (beforeGiven !== "" && beforeGiven !== QUEUE_FRONT && !belongs(beforeGiven))) {
1082
+ return respondMove(404, "that task is not in this queue");
1083
+ }
1084
+ // The no-script "move to the front" button cannot name the front: the
1085
+ // front of a task's partition is decided by the store's exact repo AND
1086
+ // assignment, and the page's snapshot is bounded — so the sentinel is
1087
+ // resolved HERE, against a fresh snapshot, into a real task id (slice
1088
+ // 1b, fix 1). It is honored only within the task's own column; a
1089
+ // cross-column front is not provable from a bounded snapshot.
1090
+ let beforeTaskId = beforeGiven === "" ? null : beforeGiven;
1091
+ if (beforeGiven === QUEUE_FRONT) {
1092
+ const ref = store.lookupRef(taskId);
1093
+ if (ref === null)
1094
+ return respondMove(404, "that task is not in this queue");
1095
+ if (ref.assignedRunner !== toRunner) {
1096
+ return respondMove(409, "move to the front works inside a task's own column — drag it across to reserve it elsewhere");
1097
+ }
1098
+ const now = clock();
1099
+ const snapshot = store.queueScoped(ref.repo, now);
1100
+ const self = snapshot.find(one => one.id === taskId);
1101
+ // A claim or a contest can land after the form rendered WITHOUT
1102
+ // bumping queueRevision, so the snapshot — not the form — decides
1103
+ // whether the card is still free. A taken or vanished card is the
1104
+ // typed refusal, never a silent no-op that would skip moveTask()'s
1105
+ // own claimed/contest recheck.
1106
+ if (self === undefined)
1107
+ return respondMove(409, moveReason("unknown-task"));
1108
+ if (self.taken)
1109
+ return respondMove(409, moveReason("claimed"));
1110
+ const partition = snapshot.filter(one => one.repo === ref.repo && one.assignedRunner === ref.assignedRunner && !one.taken);
1111
+ const front = partition[0];
1112
+ if (front === undefined)
1113
+ return respondMove(409, moveReason("unknown-task"));
1114
+ if (front.id === taskId) {
1115
+ // Already the front: a no-op, but only against the revision the
1116
+ // form was rendered with — this branch never reaches moveTask()'s
1117
+ // CAS, so the check is made here.
1118
+ if (!Number.isSafeInteger(revisionGiven) || revisionGiven !== store.queueRevision()) {
1119
+ return respondMove(409, moveReason("stale"));
1120
+ }
1121
+ return respondMove(200, "already at the front");
1122
+ }
1123
+ beforeTaskId = front.id;
1124
+ }
1125
+ const moved = store.moveTask({
1126
+ taskId,
1127
+ toRunner,
1128
+ beforeTaskId,
1129
+ ...(Number.isSafeInteger(revisionGiven) ? { queueRevision: revisionGiven } : {}),
1130
+ }, clock());
1131
+ if (!moved.ok) {
1132
+ return respondMove(409, moveReason(moved.reason));
1133
+ }
1134
+ return respondMove(200, "moved");
1135
+ }
1136
+ const answer = /^\/d\/([0-9]{1,15})\/answer$/.exec(url.pathname);
1137
+ if (answer !== null) {
1138
+ const body = readForm(posted, CONSOLE_FORMS.decisionAnswer);
1139
+ const id = Number(answer[1]);
1140
+ const requestedReturn = body.get("return");
1141
+ const decisionBack = requestedReturn === "next"
1142
+ ? "/next"
1143
+ : requestedReturn === null
1144
+ ? `/d/${id}`
1145
+ : safeChatReturn(requestedReturn);
1146
+ const decision = store.getDecision(id);
1147
+ if (decision === null)
1148
+ return refuse(response, who, 404, "no such decision");
1149
+ const answeringRun = store.getRun(decision.run);
1150
+ if (answeringRun !== null && !visible(taskRepoOf(answeringRun.taskRef))) {
1151
+ return refuse(response, who, 404, "no such decision");
1152
+ }
1153
+ const choice = body.get("choice") ?? "";
1154
+ const chosen = decision.options.find(option => option.id === choice);
1155
+ // Irreversible options never ride one accidental tap: the form arms
1156
+ // them behind an explicit confirmation field, and the server checks —
1157
+ // the client rendering is convenience, this is the rule.
1158
+ if (chosen !== undefined && !chosen.reversible && body.get("confirm") !== "yes") {
1159
+ return refuse(response, who, 400, "an irreversible choice must be confirmed", requestedReturn === null ? `/d/${id}` : `/d/${id}?return=${encodeURIComponent(decisionBack)}`);
1160
+ }
1161
+ const note = body.get("note");
1162
+ const answered = store.answerDecision({
1163
+ id,
1164
+ choice,
1165
+ by: who.name,
1166
+ via: "web",
1167
+ ...(note === null || note === "" ? {} : { note }),
1168
+ }, now);
1169
+ if (!answered.ok) {
1170
+ const status = answered.reason === "bad-option" || answered.reason === "bad-note" ? 400 : 409;
1171
+ const why = answered.reason === "already-answered" ? "already answered — somebody got there first" : answered.reason;
1172
+ return refuse(response, who, status, why, requestedReturn === null ? `/d/${id}` : decisionBack);
1173
+ }
1174
+ return redirect(response, decisionBack);
1175
+ }
1176
+ const contestAct = /^\/contest\/([0-9]{1,15})\/(arm|pick|abandon)$/.exec(url.pathname);
1177
+ if (contestAct !== null) {
1178
+ if (who.via !== "cookie")
1179
+ return refuse(response, who, 403, REMOTE_MESSAGES["step-up"]);
1180
+ const body = readForm(posted, CONSOLE_FORMS.contest);
1181
+ const contestId = Number(contestAct[1]);
1182
+ const verb = contestAct[2];
1183
+ const data = contestData(contestId);
1184
+ if (data === null)
1185
+ return refuse(response, who, 404, "no such tournament", "/board");
1186
+ const { view } = data;
1187
+ if (verb === "arm") {
1188
+ // The ceremony's nonce is minted by THIS POST, never by a GET
1189
+ // (round-3 finding 30): a prefetched or crawled page must not mint
1190
+ // anything. The response is the ceremony form itself, carrying the
1191
+ // nonce value; its hash lives in a durable row bound to the exact
1192
+ // tuple digest being restated.
1193
+ const abandoning = body.get("act") === "abandon";
1194
+ if (abandoning) {
1195
+ if (!["pick-wait", "exhausted", "interrupted", "decision-wait"].includes(view.contest.state)) {
1196
+ return contestScreen(response, who, contestId, "this tournament is not in a state an operator can abandon", 409);
1197
+ }
1198
+ const digest = pickTupleDigest(view, { abandon: true }, { grant: null, head: null });
1199
+ const nonceValue = randomBytes(18).toString("base64url");
1200
+ const minted = store.mintCeremonyNonce({ hash: nonceHashOf(nonceValue), approver: who.name, subject: "contest-abandon", subjectId: contestId, digest, ttlMs: 15 * 60_000 }, now);
1201
+ if (!minted.ok)
1202
+ return contestScreen(response, who, contestId, "too many unfinished confirmations are open — finish or let them expire", 429);
1203
+ return sendScreen(response, 200, contestCeremonyPage(chromeFor(who.via === "cookie" ? who.session.project : null, "tasks"), {
1204
+ kind: "abandon", contestKind: view.contest.kind, contestId, taskId: data.taskId, taskTitle: data.taskTitle,
1205
+ agents: view.agents.length, totalMicrousd: data.totalMicrousd, anyUnknown: data.anyUnknown,
1206
+ nonceValue, csrf: who.via === "cookie" ? who.session.csrf : "",
1207
+ }));
1208
+ }
1209
+ if (view.contest.state !== "pick-wait") {
1210
+ return contestScreen(response, who, contestId, "this tournament is not waiting for a pick", 409);
1211
+ }
1212
+ const choice = Number(body.get("choice") ?? "");
1213
+ const plan = computePickPlan(store, view, choice, data.repo, data.refOrigin);
1214
+ if (!plan.ok)
1215
+ return contestScreen(response, who, contestId, plan.message, 409);
1216
+ const nonceValue = randomBytes(18).toString("base64url");
1217
+ const minted = store.mintCeremonyNonce({ hash: nonceHashOf(nonceValue), approver: who.name, subject: "contest-pick", subjectId: contestId, digest: plan.digest, ttlMs: 15 * 60_000 }, now);
1218
+ if (!minted.ok)
1219
+ return contestScreen(response, who, contestId, "too many unfinished confirmations are open — finish or let them expire", 429);
1220
+ return sendScreen(response, 200, contestCeremonyPage(chromeFor(who.via === "cookie" ? who.session.project : null, "tasks"), {
1221
+ kind: "pick", contestKind: view.contest.kind, contestId, taskId: data.taskId, taskTitle: data.taskTitle,
1222
+ agents: view.agents.length, totalMicrousd: data.totalMicrousd, anyUnknown: data.anyUnknown,
1223
+ chosen: plan.chosen,
1224
+ publication: plan.publishable && plan.grant !== null && plan.chosen.run.branch !== null ? { githubRepo: plan.grant.githubRepo, branch: plan.chosen.run.branch, draft: plan.grant.draft } : null,
1225
+ nonceValue, csrf: who.via === "cookie" ? who.session.csrf : "",
1226
+ }));
1227
+ }
1228
+ // pick and abandon: the password, typed again, plus the nonce the arm
1229
+ // POST minted. The store consumes the nonce conditionally inside the
1230
+ // same transaction that moves the tournament — replay finds it gone.
1231
+ const token = body.get("token") ?? "";
1232
+ if (!authenticateApprover(who, token).ok) {
1233
+ return contestScreen(response, who, contestId, "that decision takes your password, typed again", 403);
1234
+ }
1235
+ const nonceValue = body.get("nonce") ?? "";
1236
+ if (nonceValue === "")
1237
+ return contestScreen(response, who, contestId, "that confirmation form was incomplete — start again", 400);
1238
+ if (verb === "pick") {
1239
+ const choice = Number(body.get("choice") ?? "");
1240
+ const picked = finalizeContestPick(store, {
1241
+ contestId, contestantId: choice, approver: who.name, nonceValue,
1242
+ evidenceRoot, repo: data.repo, taskId: data.taskId, refOrigin: data.refOrigin,
1243
+ }, now);
1244
+ if (!picked.ok)
1245
+ return contestScreen(response, who, contestId, picked.message, 409);
1246
+ return redirect(response, `/contest/${contestId}`);
1247
+ }
1248
+ const gone = abandonContest(store, { contestId, approver: who.name, nonceValue, evidenceRoot, taskId: data.taskId }, now);
1249
+ if (!gone.ok)
1250
+ return contestScreen(response, who, contestId, gone.message, 409);
1251
+ return redirect(response, `/contest/${contestId}`);
1252
+ }
1253
+ const attendAct = matchTaskPath(url.pathname, "/(attend-preview|attend|attend-revoke)$");
1254
+ if (attendAct !== null) {
1255
+ return attendMutation(response, who, attendAct.taskId, attendAct.verb, readForm(posted, CONSOLE_FORMS.attend), now);
1256
+ }
1257
+ const act = matchTaskPath(url.pathname, "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|block|unblock|repair-dependency|next|reopen|steer|follow-up|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|confirm-stopped|stop|resume-arm|resume)$");
1258
+ if (act !== null && act.verb === "confirm-stopped") {
1259
+ const body = readForm(posted, CONSOLE_FORMS.confirmStopped);
1260
+ // Confirming a finished build stopped is an approver's act behind the password, typed again — the same
1261
+ // record as `toolroll run settle`, refused while anything of the run may still be running.
1262
+ const back = taskHref(act.taskId);
1263
+ if (who.via !== "cookie" || who.role !== "approver")
1264
+ return refuse(response, who, 403, "Only an approver can confirm a build stopped.", back);
1265
+ const ref = store.lookupRef(act.taskId);
1266
+ if (ref === null || !visible(ref.repo))
1267
+ return refuse(response, who, 404, "no such task", "/tasks");
1268
+ const named = (body.get("run") ?? "").trim();
1269
+ const run = /^[0-9]{1,15}$/.test(named) ? store.getRun(Number(named)) : null;
1270
+ const runTask = run === null ? null : store.externalIdFor(run.taskRef);
1271
+ if (run === null || runTask === null || familyOf(runTask)?.root.id !== (familyOf(act.taskId)?.root.id ?? act.taskId)) {
1272
+ return taskScreen(response, who, act.taskId, "That build isn't part of this task.", 409);
1273
+ }
1274
+ if (!authenticateApprover(who, body.get("token") ?? "", ref.repo).ok || !store.accountCanAccess(who.name, ref.repo)) {
1275
+ return taskScreen(response, who, act.taskId, "That password didn't match. Nothing changed.", 403);
1276
+ }
1277
+ // When Toolroll can't check at all, the approver also says they checked: never a one-click confirmation.
1278
+ if (store.stopQuiescenceFact(run.id)?.kind === "unknown" && body.get("checked") !== "yes") {
1279
+ return taskScreen(response, who, act.taskId, `Make sure nothing from build #${run.id} is running, then tick the box to confirm.`, 409);
1280
+ }
1281
+ const settled = store.settleRunWitnessesByApprover({ runId: run.id, by: who.name, why: "Confirmed in the console that nothing from this build is running." }, now);
1282
+ if (!settled.ok) {
1283
+ return taskScreen(response, who, act.taskId, settled.reason === "alive" || settled.reason === "still-running"
1284
+ ? `Something from build #${run.id} may still be running, so it can't be confirmed stopped yet.` : `Build #${run.id} can't be confirmed stopped.`, 409);
1285
+ }
1286
+ bustBadge();
1287
+ const to = resultReturnTarget(body.get("return"), run.id);
1288
+ return redirect(response, body.get("return") ? to : back);
1289
+ }
1290
+ if (act !== null && act.verb === "merge") {
1291
+ const body = readForm(posted, CONSOLE_FORMS.merge);
1292
+ // Merging is a person's act behind their password, typed again: a browser session, an approver, the exact
1293
+ // result's pull request. The flow re-reads GitHub before merging and records who merged in the ledger.
1294
+ if (who.via !== "cookie" || who.role !== "approver")
1295
+ return refuse(response, who, 403, "Only an approver can merge.");
1296
+ if (store.isDemo())
1297
+ return taskScreen(response, who, act.taskId, "The demo merges nothing.", 403);
1298
+ const ref = store.lookupRef(act.taskId);
1299
+ if (ref === null || !visible(ref.repo))
1300
+ return refuse(response, who, 404, "no such task", "/tasks");
1301
+ const named = (body.get("run") ?? "").trim();
1302
+ const run = /^[0-9]{1,15}$/.test(named) ? store.getRun(Number(named)) : null;
1303
+ const runTask = run === null ? null : store.externalIdFor(run.taskRef);
1304
+ if (run === null || runTask === null || familyOf(runTask)?.root.id !== (familyOf(act.taskId)?.root.id ?? act.taskId)) {
1305
+ return taskScreen(response, who, act.taskId, "That pull request isn't part of this task.", 409);
1306
+ }
1307
+ const merged = await mergeAsPerson(store, { runId: run.id, name: who.name, password: body.get("token") ?? "", ...(body.get("anyway") === "1" ? { anyway: true } : {}), ...(options.publishExec === undefined ? {} : { exec: options.publishExec }), clock });
1308
+ if (!merged.ok)
1309
+ return taskScreen(response, who, act.taskId, merged.message, merged.reason === "password" ? 403 : 409);
1310
+ return redirect(response, `${taskHref(act.taskId)}#merge`);
1311
+ }
1312
+ if (act !== null) {
1313
+ return taskMutation(response, who, act.taskId, act.verb, posted, now);
1314
+ }
1315
+ if (url.pathname === "/routines/add") {
1316
+ const body = readForm(posted, CONSOLE_FORMS.routinesAdd);
1317
+ // A standing order files into the OPEN project — never a typed path,
1318
+ // so the ceiling question never even arises — and lands on its own
1319
+ // screen where the approval step-up already lives: filing is cheap,
1320
+ // the yes is the ceremony.
1321
+ const project = projectOf(who, request);
1322
+ if (project === undefined || project === null) {
1323
+ return refuse(response, who, 403, "open a project first — a standing order lives somewhere specific");
1324
+ }
1325
+ if (who.via === "cookie") {
1326
+ const seen = body.get("projectRevision");
1327
+ if (seen !== null && seen !== String(who.session.projectRevision)) {
1328
+ return refuse(response, who, 409, "the open project changed since this form was rendered — reload and try again", "/routines");
1329
+ }
1330
+ }
1331
+ const name = (body.get("name") ?? "").trim();
1332
+ const ceilingGiven = (body.get("ceiling") ?? "").trim();
1333
+ // Root mode proves the CURRENT project exactly (repo onboarding,
1334
+ // finding 24): canonicalize, authorize, and pass [canonical] as the
1335
+ // admitted list — the same discipline as task filing, so a routine
1336
+ // can land in a fresh clone too.
1337
+ const routineRootMode = !unscopedMode && ceiling.roots.length > 0;
1338
+ let routineRepo = project;
1339
+ let routineAdmitted = unscopedMode ? null : admissionList() ?? [];
1340
+ if (routineRootMode) {
1341
+ const canonical = (await authorizedProject(liveCeiling(), project)) ? canonicalProject(project) : null;
1342
+ if (canonical === null || canonical === undefined) {
1343
+ return refuse(response, who, 403, "the open project is outside what this server was configured to show", "/routines");
1344
+ }
1345
+ routineRepo = canonical;
1346
+ routineAdmitted = [canonical];
1347
+ }
1348
+ // One filing door for every surface (Codex adoption review, finding
1349
+ // 7): the service validates, canonicalizes, digests, and stamps
1350
+ // provenance; the admission list makes the ceiling explicit even
1351
+ // though `project` was already proved inside it.
1352
+ const scheduled = body.has("repeat") ? composerSchedule(body.sent) : { ok: true, schedule: (body.get("schedule") ?? "").trim() };
1353
+ if (!scheduled.ok || scheduled.schedule === null) {
1354
+ return sendScreen(response, 400, routinesPage(chromeFor(project, "routines"), store.routineTracks(project, now).filter(track => visible(track.routine.repo)), {
1355
+ csrf: who.via === "cookie" ? who.session.csrf : "", revision: who.via === "cookie" ? who.session.projectRevision : 0,
1356
+ problem: scheduled.ok ? "Choose a recurring schedule." : scheduled.message, values: body.sent,
1357
+ }));
1358
+ }
1359
+ const created = fileRoutineProposal(store, {
1360
+ name,
1361
+ repo: routineRepo,
1362
+ goal: (body.get("goal") ?? "").trim(),
1363
+ outOfScope: (body.get("not") ?? "").trim() || null,
1364
+ touches: (body.get("touches") ?? "").split(/[\n,]/).map(one => one.trim()).filter(one => one !== ""),
1365
+ acceptance: acceptanceLinesToInput((body.get("acceptance") ?? "").split("\n")),
1366
+ requirements: [],
1367
+ schedule: scheduled.schedule,
1368
+ costCeilingUsd: ceilingGiven === "" ? null : Number(ceilingGiven),
1369
+ filedVia: "console", createdBy: who.name,
1370
+ ...(routineAdmitted === null ? {} : { admittedRepos: routineAdmitted }),
1371
+ }, now);
1372
+ if (created.ok && routineRootMode)
1373
+ store.upsertProject(routineRepo, projectName(routineRepo), now);
1374
+ if (!created.ok) {
1375
+ const tracks = store.routineTracks(project, now).filter(track => visible(track.routine.repo));
1376
+ return sendScreen(response, created.reason === "duplicate" ? 409 : 400, routinesPage(chromeFor(project, "routines"), tracks, {
1377
+ csrf: who.via === "cookie" ? who.session.csrf : "",
1378
+ revision: who.via === "cookie" ? who.session.projectRevision : 0,
1379
+ problem: created.message, values: body.sent,
1380
+ }));
1381
+ }
1382
+ return redirect(response, `/routines/${created.id}`);
1383
+ }
1384
+ const routineAct = /^\/routines\/([0-9]{1,15})\/(approve|refresh|pause|resume|run-now)$/.exec(url.pathname);
1385
+ if (routineAct !== null) {
1386
+ return routineMutation(response, who, Number(routineAct[1]), routineAct[2], readForm(posted, CONSOLE_FORMS.routine), now);
1387
+ }
1388
+ const runNote = /^\/r\/([0-9]{1,15})\/note$/.exec(url.pathname);
1389
+ if (runNote !== null) {
1390
+ const body = readForm(posted, CONSOLE_FORMS.runNote);
1391
+ // An operator's verdict beside the machine's record (M6): immutable,
1392
+ // bounded by the same validator as decision notes, ceiling-checked
1393
+ // like every run resource. Ordinary authenticated mutation — no
1394
+ // nonce, because nothing here approves anything.
1395
+ const id = Number(runNote[1]);
1396
+ const found = store.getRun(id);
1397
+ if (found === null || !visible(taskRepoOf(found.taskRef))) {
1398
+ return refuse(response, who, 404, "no such run");
1399
+ }
1400
+ const note = validateNote(body.get("note") ?? "");
1401
+ if (!note.ok)
1402
+ return refuse(response, who, 400, note.problem);
1403
+ store.addRunNote(id, who.name, note.note, now);
1404
+ return redirect(response, `/r/${id}`);
1405
+ }
1406
+ const diffComment = /^\/r\/([0-9]{1,15})\/comment$/.exec(url.pathname);
1407
+ if (diffComment !== null) {
1408
+ const body = readForm(posted, CONSOLE_FORMS.diffComment);
1409
+ // A review comment on the IMMUTABLE terminal diff (M6.8): bound to
1410
+ // the exact artifact and its hash. Ordinary authenticated mutation —
1411
+ // the nonce belongs to the approval screen that later restates the
1412
+ // batch, never to the comment box.
1413
+ const id = Number(diffComment[1]);
1414
+ const found = store.getRun(id);
1415
+ if (found === null || !visible(taskRepoOf(found.taskRef))) {
1416
+ return refuse(response, who, 404, "no such run");
1417
+ }
1418
+ // Where the reader came from (package 3): the cockpit's deep link,
1419
+ // the chat's result view, or the run page — validated to those exact
1420
+ // shapes. A refusal sends them back THERE, where the browser has kept
1421
+ // their draft, so a failed submission is recoverable in place.
1422
+ const returnTo = resultReturnTarget(body.get("return"), id);
1423
+ const selectedTab = parseResultTab(body.get("tab"));
1424
+ const back = selectedTab === "summary" ? returnTo : `${returnTo}${returnTo.includes("?") ? "&" : "?"}tab=${selectedTab}`;
1425
+ if (body.get("intent") === "revise") {
1426
+ const result = requestResultChanges(store, evidenceRoot, {
1427
+ run: id, batch: body.get("batch"), source: body.get("source"), actor: who.name,
1428
+ repos: admissionList(), includeUnplaced: visible(null), allowMode: who.via === "cookie",
1429
+ note: body.get("note") ?? "", path: body.get("path") ?? "", line: body.get("line") ?? "", request: body.get("request"),
1430
+ }, now);
1431
+ if (!result.ok) {
1432
+ const request = body.get("request") ?? "";
1433
+ const retryBack = /^[a-f0-9]{32}$/.test(request) ? `${back}${back.includes("?") ? "&" : "?"}conflict=${request}#request-changes` : back;
1434
+ return refuse(response, who, result.status, result.message, retryBack);
1435
+ }
1436
+ return redirect(response, revisionDestination(result.id, returnTo));
1437
+ }
1438
+ const terminal = store.artifactsFor(id).find(one => one.kind === "terminal-diff");
1439
+ if (terminal === undefined) {
1440
+ return refuse(response, who, 400, "this run has no terminal diff to comment on", back);
1441
+ }
1442
+ // The bytes must VERIFY before words attach to them (audit IV-10):
1443
+ // "a comment on the exact reviewed bytes" is a lie if the bytes are
1444
+ // gone or no longer hash to their record.
1445
+ const proven = readVerifiedArtifact(evidenceRoot, terminal);
1446
+ if (!proven.ok) {
1447
+ return refuse(response, who, 409, `the terminal diff no longer verifies (${proven.problem}) — nothing to comment on`, back);
1448
+ }
1449
+ const note = validateNote(body.get("note") ?? "");
1450
+ if (!note.ok)
1451
+ return refuse(response, who, 400, note.problem, back);
1452
+ const rawPath = (body.get("path") ?? "").trim();
1453
+ if (rawPath.length > 300 || hasForbiddenControls(rawPath)) {
1454
+ return refuse(response, who, 400, "Enter a file path of up to 300 characters, without control characters.", back);
1455
+ }
1456
+ const rawLine = (body.get("line") ?? "").trim();
1457
+ const line = rawLine === "" ? null : Number(rawLine);
1458
+ if (line !== null && (!Number.isInteger(line) || line < 1 || line > 1_000_000)) {
1459
+ return refuse(response, who, 400, "Enter a whole line number from 1 to 1,000,000, or leave it blank.", back);
1460
+ }
1461
+ // The form's own request token (package 3) is the dedupe key: a
1462
+ // replayed or double submission finds its note already recorded and
1463
+ // lands on the same receipt instead of a second note. The token is
1464
+ // bound to the account, so nobody else's replay can consume it.
1465
+ const request = body.get("request");
1466
+ const sourceKey = commentSourceKey(who.name, request);
1467
+ const path = rawPath === "" ? null : rawPath;
1468
+ const inserted = store.addDiffComment({ artifactId: terminal.id, runId: id, path, line, note: note.note, author: who.name, ...(sourceKey === undefined ? {} : { sourceKey }) }, now);
1469
+ if (inserted === null && sourceKey !== undefined) {
1470
+ // The token was used before (repair 2026-09-14, finding 1). A
1471
+ // request identity is IMMUTABLE: it names exactly one note on
1472
+ // exactly one result. The unchanged retry — same run, same file,
1473
+ // line and words — lands on its original receipt; any other reuse
1474
+ // is refused, and the refusal's way back carries `conflict=<token>`
1475
+ // so the browser mints a fresh identity for the words it still
1476
+ // holds. Nothing typed is lost either way.
1477
+ const earlier = store.diffCommentBySourceKey(sourceKey);
1478
+ const conflictBack = `${back}${back.includes("?") ? "&" : "?"}conflict=${request}#request-changes`;
1479
+ if (earlier === null || earlier.run !== id) {
1480
+ return refuse(response, who, 409, `this note's request identity was already used on another result (build #${earlier === null ? "?" : earlier.run}) — go back; your words are kept, and the form will carry a new identity`, conflictBack);
1481
+ }
1482
+ if (earlier.path !== path || earlier.line !== line || earlier.note !== note.note) {
1483
+ return refuse(response, who, 409, `this request identity already recorded a different note on this result (${earlier.path === null ? "no file" : `${earlier.path}${earlier.line === null ? "" : `:${earlier.line}`}`}: "${oneLineOf(earlier.note, 80)}") — go back; your edited words are kept as a new note, and the form will carry a new identity`, conflictBack);
1484
+ }
1485
+ }
1486
+ // Land back AT the request-changes form with the note field ready —
1487
+ // writing five notes in a row must cost five keystrokes of
1488
+ // navigation, not five scrolls (arc 6, finding 5/6). The receipt
1489
+ // names the request token, so the browser clears exactly the draft
1490
+ // that landed and no other.
1491
+ const receipt = sourceKey === undefined ? "1" : request;
1492
+ return redirect(response, `${back}${back.includes("?") ? "&" : "?"}noted=${receipt}#request-changes`);
1493
+ }
1494
+ if (url.pathname === "/session/editor-links") {
1495
+ const body = readForm(posted, CONSOLE_FORMS.editorLinks);
1496
+ // The session half of the editor-link activation (arc 6, finding 1):
1497
+ // only the person at the browser can say "this device holds the
1498
+ // worktrees". Per-session, dies with the session, grants nothing —
1499
+ // it only lets already-authorized pages RENDER vscode links.
1500
+ if (who.via !== "cookie")
1501
+ return refuse(response, who, 403, "editor links are a browser session's choice");
1502
+ if (options.editorLinks === undefined)
1503
+ return refuse(response, who, 404, "editor links are not enabled on this server");
1504
+ who.session.editorLinks = body.get("on") === "1";
1505
+ const back = body.get("return") ?? "/";
1506
+ return redirect(response, /^\/[a-z0-9/_-]*$/i.test(back) ? back : "/");
1507
+ }
1508
+ const turnAct = /^\/r\/([0-9]{1,15})\/turn$/.exec(url.pathname);
1509
+ if (turnAct !== null) {
1510
+ const body = readForm(posted, CONSOLE_FORMS.turn);
1511
+ // The operator's turn (Phase 2E, v2 S1g): cookie-only — a watching
1512
+ // person, never a bearer machine — and every hard gate (custody,
1513
+ // lease, cap, budget, open decision, single flight) re-proves
1514
+ // ATOMICALLY inside the recording transaction. Words here only map
1515
+ // the refusal tokens to sentences.
1516
+ if (who.via !== "cookie")
1517
+ return refuse(response, who, 403, "turns are a browser session's act");
1518
+ const id = Number(turnAct[1]);
1519
+ const found = store.getRun(id);
1520
+ if (found === null || !visible(taskRepoOf(found.taskRef))) {
1521
+ return refuse(response, who, 404, "no such run");
1522
+ }
1523
+ const text = (body.get("text") ?? "").trim();
1524
+ if (text === "" || text.length > 500) {
1525
+ return refuse(response, who, 400, "a turn is 1 to 500 characters", `/r/${id}`);
1526
+ }
1527
+ const coordinator = options.attended?.coordinator;
1528
+ if (coordinator === undefined)
1529
+ return refuse(response, who, 409, "this console is not holding the session", `/r/${id}`);
1530
+ const injected = coordinator.injectOperatorTurn(id, who.name, text);
1531
+ if (!injected.ok) {
1532
+ const words = {
1533
+ "no-held-session": "the session is not held here anymore",
1534
+ fenced: "the session is winding down — nothing more reaches it",
1535
+ "turn-open": "the agent is still working on the last message — wait for it to settle",
1536
+ "turn-cap": "the session's message cap is reached — authorize a new session for more",
1537
+ "budget-exhausted": "the session's budget is spent",
1538
+ "decision-open": "answer the waiting question first — it is on this page",
1539
+ "write-failed": "the message could not reach the agent — it was not charged",
1540
+ };
1541
+ return refuse(response, who, 409, words[injected.reason] ?? injected.reason, `/r/${id}`);
1542
+ }
1543
+ return redirect(response, `/r/${id}`);
1544
+ }
1545
+ const revise = /^\/r\/([0-9]{1,15})\/revise$/.exec(url.pathname);
1546
+ if (revise !== null) {
1547
+ const body = readForm(posted, CONSOLE_FORMS.revise);
1548
+ // Seal the live comment batch into ONE unapproved revision task with
1549
+ // an immutable brief (M6.8). Deterministic — no model reads anything
1550
+ // here — and every revision takes its own approval: comments can
1551
+ // semantically widen work, and no path check can prove they did not.
1552
+ const id = Number(revise[1]);
1553
+ const found = store.getRun(id);
1554
+ if (found === null || !visible(taskRepoOf(found.taskRef))) {
1555
+ return refuse(response, who, 404, "no such run");
1556
+ }
1557
+ const back = resultReturnTarget(body.get("return"), id);
1558
+ const result = createResultRevision(store, evidenceRoot, {
1559
+ run: id, batch: body.get("batch"), source: body.get("source"), actor: who.name,
1560
+ repos: admissionList(), includeUnplaced: visible(null), allowMode: who.via === "cookie",
1561
+ }, now);
1562
+ if (!result.ok)
1563
+ return refuse(response, who, result.status, result.message, back);
1564
+ return redirect(response, revisionDestination(result.id, back));
1565
+ }
1566
+ // Follow-ups on a result: Run checks on its exact commit (a worker runs the
1567
+ // approved command once and seals the log), or file a task to add tests.
1568
+ const followUp = /^\/r\/([0-9]{1,15})\/(checks|add-tests)$/.exec(url.pathname);
1569
+ if (followUp !== null) {
1570
+ const body = readForm(posted, CONSOLE_FORMS.followUp);
1571
+ const id = Number(followUp[1]);
1572
+ const found = store.getRun(id);
1573
+ if (found === null || !visible(taskRepoOf(found.taskRef)))
1574
+ return refuse(response, who, 404, "no such run");
1575
+ // The task page that offered Run checks in place is a return target too: this run's own task, or its family's root.
1576
+ const runTask = store.externalIdFor(found.taskRef);
1577
+ const back = resultReturnTarget(body.get("return"), id, runTask === null ? [] : [runTask, familyOf(runTask)?.root.id ?? runTask]);
1578
+ if (who.via !== "cookie")
1579
+ return refuse(response, who, 403, "Sign in with a browser session to do this.", back);
1580
+ if (followUp[2] === "checks") {
1581
+ if (who.role !== "approver")
1582
+ return refuse(response, who, 403, "An approver runs checks.", back);
1583
+ if (store.isDemo())
1584
+ return refuse(response, who, 403, "The demo runs no checks.", back);
1585
+ const level = body.get("level") === "full" ? "full" : "quick";
1586
+ const asked = requestFollowUpChecks(store, { runId: id, level, actor: who.name }, now);
1587
+ if (!asked.ok)
1588
+ return refuse(response, who, 409, asked.message, back);
1589
+ // Back where it was asked: the task page's status row says the checks are running; a result page shows them under #follow-ups.
1590
+ return redirect(response, back.startsWith("/t/") ? back : `${back.split("#")[0]}#follow-ups`);
1591
+ }
1592
+ const filed = fileAddTestsTask(store, evidenceRoot, { runId: id, actor: who.name, filedVia: "console", ...(admissionList() === null ? {} : { admittedRepos: admissionList() }) }, now);
1593
+ if (!filed.ok)
1594
+ return refuse(response, who, 409, filed.message, back);
1595
+ return redirect(response, taskHref(filed.id));
1596
+ }
1597
+ const draftRepair = /^\/r\/([0-9]{1,15})\/draft-repair$/.exec(url.pathname);
1598
+ if (draftRepair !== null) {
1599
+ // CI repair, suggestion-first (M8.18): a red episode never spawns an
1600
+ // agent by itself — it EARNS a button, and the button creates one
1601
+ // unapproved task through the same revision machinery as review
1602
+ // comments. Deterministic id = one draft per task/PR, ever.
1603
+ const id = Number(draftRepair[1]);
1604
+ const found = store.getRun(id);
1605
+ if (found === null || !visible(taskRepoOf(found.taskRef))) {
1606
+ return refuse(response, who, 404, "no such run");
1607
+ }
1608
+ const publication = store.publicationForRun(id);
1609
+ if (publication === null || publication.prNumber === null) {
1610
+ return refuse(response, who, 400, "this run published no pull request", `/r/${id}`);
1611
+ }
1612
+ if (!store.hasOpenCiEpisode(publication.githubRepo, publication.prNumber)) {
1613
+ return refuse(response, who, 400, "no failing CI is observed on this PR right now", `/r/${id}`);
1614
+ }
1615
+ const sourceTaskId = store.externalIdFor(found.taskRef) ?? "?";
1616
+ const sourceScope = store.getScope(sourceTaskId);
1617
+ // The observed episode, not the click: the brief binds the head the
1618
+ // failure was SEEN on and when (audit C-2) — a PR that advanced since
1619
+ // is a different failure, and the click time is not an observation.
1620
+ const episode = store.latestOpenCiEpisode(publication.githubRepo, publication.prNumber);
1621
+ // Suffixes survive truncation (audit C-7): the prefix gives way, the
1622
+ // identity-bearing tail never does.
1623
+ const suffix = `-ci-${publication.prNumber}`;
1624
+ const draftId = `${sourceTaskId.slice(0, 64 - suffix.length)}${suffix}`;
1625
+ const brief = {
1626
+ schema: 1,
1627
+ kind: "ci-repair",
1628
+ sourceTask: sourceTaskId,
1629
+ sourceRun: id,
1630
+ sourceScopeDigest: sourceScope?.digest ?? null,
1631
+ head: found.headRevision,
1632
+ pr: publication.prNumber,
1633
+ prUrl: publication.prUrl,
1634
+ publishedHeadSha: publication.headSha,
1635
+ observedFailingHead: episode?.headSha ?? null,
1636
+ observedAt: episode?.createdAt ?? null,
1637
+ };
1638
+ const briefBytes = Buffer.from(JSON.stringify(brief, null, 2), "utf8");
1639
+ const briefName = `ci-repair-brief-${randomBytes(6).toString("hex")}.json`;
1640
+ const key = writeEvidenceFile(evidenceRoot, id, briefName, briefBytes);
1641
+ // The SAME revision boundary the annotation road and the criterion
1642
+ // repair use (contract handoff task 2): the inherited terms come from
1643
+ // the source rows inside the seal; the scope read above only names
1644
+ // the digest this draft was composed against.
1645
+ const sealed = store.sealRevision({
1646
+ source: { task: sourceTaskId, run: id, scopeDigest: sourceScope?.digest ?? null },
1647
+ brief: { evidenceRoot, key, sha256: createHash("sha256").update(briefBytes).digest("hex"), bytes: briefBytes.length, capture: "machine-authored ci-repair brief (exit 0)" },
1648
+ child: {
1649
+ id: draftId,
1650
+ title: `repair ${sourceTaskId}: CI failing on PR #${publication.prNumber}`,
1651
+ repair: `repair the failing CI on PR #${publication.prNumber} (failing head ${(episode?.headSha ?? publication.headSha).slice(0, 12)}). ` +
1652
+ `Read the failing checks on GitHub before approving; this draft carries no log content.`,
1653
+ },
1654
+ commentIds: null,
1655
+ requestedBy: who.name,
1656
+ }, now);
1657
+ if (!sealed.ok) {
1658
+ return refuse(response, who, sealed.reason === "duplicate" ? 409 : sealed.reason === "stale-source" || sealed.reason === "comments-taken" ? 409 : 400, sealed.reason === "duplicate" ? `already drafted as ${draftId}` : `could not draft: ${sealed.detail}`, `/r/${id}`);
1659
+ }
1660
+ // The merge blocker rides the SAME breath as the draft (merge grant,
1661
+ // findings 3/12/13): while this repair exists, the source PR merges
1662
+ // NOTHING — sticky until the operator's unblock act or the PR closes.
1663
+ store.createMergeBlocker(publication.id, sealed.id, now);
1664
+ return redirect(response, taskHref(sealed.id));
1665
+ }
1666
+ const resolve = /^\/i\/([0-9]{1,15})\/resolve$/.exec(url.pathname);
1667
+ if (resolve !== null) {
1668
+ const body = readForm(posted, CONSOLE_FORMS.resolveIncident);
1669
+ const id = Number(resolve[1]);
1670
+ // The ceiling applies to incident mutation exactly as to every other
1671
+ // resource (v3 review, finding 3): resolve incident → run → task, and
1672
+ // an incident outside this server's scope does not exist here. The
1673
+ // task id is captured BEFORE resolving — afterwards the incident is
1674
+ // no longer open and could not be found again.
1675
+ const openRow = store.openIncidents().find(one => one.id === id);
1676
+ const incidentRun = openRow === undefined ? null : store.getRun(openRow.run);
1677
+ if (openRow !== undefined && incidentRun !== null && !visible(taskRepoOf(incidentRun.taskRef))) {
1678
+ return refuse(response, who, 404, "no such incident");
1679
+ }
1680
+ const resolved = store.resolveIncident(id, who.name, now);
1681
+ if (!resolved)
1682
+ return refuse(response, who, 409, "already resolved, or never open");
1683
+ const back = body.get("return") === "inbox" ? "/inbox" : openRow === undefined ? "/" : taskHref(openRow.taskId);
1684
+ return redirect(response, back);
1685
+ }
1686
+ return refuse(response, who, 404, "There's no page at this address.", "/chat");
1687
+ }
1688
+ /** The compact recent-runs list for the master pane. */
1689
+ function runListPane(project, currentId) {
1690
+ const rows = store.listRunsBefore(null, 50, project);
1691
+ const live = liveRunIds(rows);
1692
+ const items = rows
1693
+ .map(run => `<a class="item${run.id === currentId ? " current" : ""}" href="/r/${run.id}">` +
1694
+ `<span class="t">#${run.id} \u00b7 ${escape(run.taskId)}</span>` +
1695
+ `<span class="m">${runOutcomeBadge(run, live.has(run.id))}` +
1696
+ `<span class="mono">${whenTime(run.startedAt)}</span></span></a>`)
1697
+ .join("\n");
1698
+ return `<h2>Builds</h2>\n${items === "" ? `<p class="meta">None yet</p>` : items}`;
1699
+ }
1700
+ async function peekFragment(runId, sessionKey, editorMode = false) {
1701
+ const guarded = peekGuards(runId);
1702
+ if (!guarded.ok)
1703
+ return { status: 200, body: peekSay(guarded.message, guarded.final === true) };
1704
+ const { run, worktree, epoch, entries } = guarded.admit;
1705
+ // The cache and the in-flight coalescer both vary by LINK MODE (arc 6,
1706
+ // finding 3): a linked fragment rendered for one session must never be
1707
+ // served to a session that has not activated links on its device.
1708
+ const key = `${runId}:${run.baseRevision}:${epoch}:${editorMode ? "links" : "plain"}`;
1709
+ const cached = peekCache.get(key);
1710
+ if (cached !== undefined && Date.now() - cached.at <= PEEK_CACHE_TTL_MS) {
1711
+ return { status: 200, body: cached.fragment };
1712
+ }
1713
+ if (cached !== undefined) {
1714
+ peekCache.delete(key);
1715
+ runtime.peekCacheBytes -= Buffer.byteLength(cached.fragment);
1716
+ }
1717
+ // Coalesce per run; bound per session and globally (finding 10).
1718
+ const flightKey = `${runId}:${editorMode ? "links" : "plain"}`;
1719
+ const inFlight = peekInFlight.get(flightKey);
1720
+ if (inFlight !== undefined)
1721
+ return { status: 200, body: await inFlight };
1722
+ if (peekInFlight.size >= PEEK_GLOBAL_INFLIGHT)
1723
+ return { status: 429, body: peekSay("the live view is busy — it retries by itself"), retryAfter: 10 };
1724
+ if ((peekBySession.get(sessionKey) ?? 0) >= PEEK_SESSION_INFLIGHT) {
1725
+ return { status: 429, body: peekSay("too many live views from this session"), retryAfter: 10 };
1726
+ }
1727
+ peekBySession.set(sessionKey, (peekBySession.get(sessionKey) ?? 0) + 1);
1728
+ const work = (async () => {
1729
+ const seen = await observeWorktree(worktree, entries.entries, PEEK_LIMITS);
1730
+ // The fence, proved AGAIN after the walk (findings 16/28): the same
1731
+ // run still open, the same claim, the SAME epoch — or the whole
1732
+ // observation is discarded, never rendered, never cached.
1733
+ const after = peekGuards(runId);
1734
+ if (!after.ok || after.admit.epoch !== epoch) {
1735
+ return peekSay("the checkout changed hands mid-look — nothing is shown");
1736
+ }
1737
+ if (!seen.ok)
1738
+ return peekSay(seen.reason);
1739
+ const stamp = clock().toISOString().slice(11, 19);
1740
+ const parts = [
1741
+ `<p class="meta">Best-effort look at ${escape(stamp)} UTC — files can change mid-read</p>`,
1742
+ ];
1743
+ const changed = seen.rows.filter(one => one.kind === "changed");
1744
+ const deleted = seen.rows.filter(one => one.kind === "deleted");
1745
+ const unchecked = seen.rows.filter(one => one.kind === "unchecked");
1746
+ const fresh = aggregateNewNames(seen.newPaths);
1747
+ if (changed.length === 0 && deleted.length === 0 && fresh.total === 0) {
1748
+ parts.push(`<p class="row">nothing has changed against the starting point yet</p>`);
1749
+ }
1750
+ // A name is linked ONLY when sanitize provably changed nothing (arc 6,
1751
+ // finding 3): a masked or normalized label must never carry an href
1752
+ // that discloses what the mask hid. Collapsed labels never link.
1753
+ const linkedName = (path) => {
1754
+ const shown = peekName(path);
1755
+ if (!editorMode || shown !== escape(path))
1756
+ return shown;
1757
+ const href = editorFileHref(worktree, path);
1758
+ return href === null ? shown : `<a href="${escape(href)}">${shown}</a>`;
1759
+ };
1760
+ const line = (row, mark) => `<p class="row mono">${mark} ${linkedName(row.path)} <span class="meta">${escape(row.detail)}</span></p>`;
1761
+ for (const row of changed)
1762
+ parts.push(line(row, "~"));
1763
+ for (const row of deleted)
1764
+ parts.push(line(row, "−"));
1765
+ if (fresh.total > 0) {
1766
+ parts.push(`<p class="meta">New files · ${fresh.total}</p>`);
1767
+ for (const row of fresh.rows) {
1768
+ parts.push(row.collapsed
1769
+ ? `<p class="row mono">+ ${peekName(row.label)} <span class="meta">collapsed names — ${row.count} files</span></p>`
1770
+ : `<p class="row mono">+ ${peekName(row.label)}</p>`);
1771
+ }
1772
+ if (fresh.renderedFiles < fresh.total) {
1773
+ parts.push(`<p class="meta">…and ${fresh.total - fresh.renderedFiles} more (${fresh.total} new files total)</p>`);
1774
+ }
1775
+ }
1776
+ if (unchecked.length > 0) {
1777
+ parts.push(`<p class="meta">Not verified this look — absence above does not mean unchanged:</p>`);
1778
+ for (const row of unchecked)
1779
+ parts.push(line(row, "?"));
1780
+ }
1781
+ if (seen.partial !== null)
1782
+ parts.push(`<p class="meta">${escape(seen.partial)}</p>`);
1783
+ let fragment = parts.join("\n");
1784
+ if (Buffer.byteLength(fragment) > PEEK_FRAGMENT_BYTES) {
1785
+ // The byte cap is enforced AFTER escaping (finding 32): an oversize
1786
+ // rendering is replaced whole by its exact counts.
1787
+ fragment =
1788
+ `<p class="meta">Best-effort look at ${escape(stamp)} UTC</p>` +
1789
+ `<p class="row">${changed.length} changed · ${deleted.length} deleted · ${fresh.total} new · ${unchecked.length} unverified — too much to render live; the final diff will hold the detail</p>`;
1790
+ }
1791
+ peekCache.set(key, { fragment, at: Date.now() });
1792
+ runtime.peekCacheBytes += Buffer.byteLength(fragment);
1793
+ peekEvict();
1794
+ return fragment;
1795
+ })();
1796
+ peekInFlight.set(flightKey, work);
1797
+ try {
1798
+ return { status: 200, body: await work };
1799
+ }
1800
+ finally {
1801
+ peekInFlight.delete(flightKey);
1802
+ const left = (peekBySession.get(sessionKey) ?? 1) - 1;
1803
+ if (left <= 0)
1804
+ peekBySession.delete(sessionKey);
1805
+ else
1806
+ peekBySession.set(sessionKey, left);
1807
+ }
1808
+ }
1809
+ function contestScreen(response, who, contestId, problem, status) {
1810
+ const data = contestData(contestId);
1811
+ if (data === null)
1812
+ return refuse(response, who, 404, "no such tournament", "/board");
1813
+ const paneProject = who.via === "cookie" ? who.session.project : null;
1814
+ const diffs = new Map();
1815
+ for (const agent of data.view.agents) {
1816
+ if (agent.run !== null)
1817
+ diffs.set(agent.contestant.id, terminalDiffView(store.artifactsFor(agent.run.id), evidenceRoot));
1818
+ }
1819
+ return sendScreen(response, status, contestPage(chromeFor(paneProject, "tasks"), {
1820
+ ...data,
1821
+ diffs,
1822
+ csrf: who.via === "cookie" ? who.session.csrf : "",
1823
+ problem,
1824
+ }));
1825
+ }
1826
+ /**
1827
+ * Routine verbs. The ceiling check is independent of authorizeMutation
1828
+ * (Codex round 2, finding 7): the routine's repository is resolved
1829
+ * server-side and proved against this server's configuration before any
1830
+ * verb runs, whatever the request named.
1831
+ */
1832
+ function routineMutation(response, who, routineId, verb, body, now) {
1833
+ const routine = store.getRoutine(routineId);
1834
+ if (routine === null || !visible(routine.repo)) {
1835
+ return refuse(response, who, 404, "no such routine", "/routines");
1836
+ }
1837
+ switch (verb) {
1838
+ case "approve": {
1839
+ if (who.via !== "cookie")
1840
+ return refuse(response, who, 403, REMOTE_MESSAGES["step-up"]);
1841
+ // Step-up, identical to a scope's: the session got you here; only
1842
+ // the password agrees. The digest names what was seen.
1843
+ const digest = body.get("digest") ?? "";
1844
+ const token = body.get("token") ?? "";
1845
+ const nonce = body.get("nonce") ?? "";
1846
+ if (!consumeApprovalNonce(nonce, who.name, `routine:${routine.id}`, digest)) {
1847
+ return routinePage(response, who, routineId, "that approval form is stale — read it again", 409);
1848
+ }
1849
+ if (token === "" && !hasFreshIdentitySignIn(who.name)) {
1850
+ return routinePage(response, who, routineId, "approval requires your password, typed again", 400);
1851
+ }
1852
+ const approved = approveRoutine(store, routineId, who.name, now, digest, token);
1853
+ if (!approved.ok) {
1854
+ const status = approved.reason === "changed" ? 409 : 403;
1855
+ const words = approved.reason === "profile-unresolved"
1856
+ ? "not approved: the routine cannot name an exact agent for every role — configure the project's agents, then file the standing order again"
1857
+ : approved.reason === "requester"
1858
+ ? "You made this standing order, and this project needs someone else to approve it."
1859
+ : `not approved: ${approved.reason}`;
1860
+ return routinePage(response, who, routineId, words, status);
1861
+ }
1862
+ return redirect(response, `/routines/${routineId}`);
1863
+ }
1864
+ case "refresh": {
1865
+ // THE RECOVERY ROAD (v48): re-resolve the agents from today's
1866
+ // configuration and file them as the order's working agents. This
1867
+ // approves nothing — the page then shows the exact agents and asks
1868
+ // for the password again.
1869
+ const refreshed = refreshRoutineAgents(store, routineId, now);
1870
+ if (!refreshed.ok) {
1871
+ return routinePage(response, who, routineId, `agents not refreshed: ${refreshed.problem}`, 409);
1872
+ }
1873
+ return redirect(response, `/routines/${routineId}`);
1874
+ }
1875
+ case "pause":
1876
+ case "resume": {
1877
+ store.setRoutinePaused(routineId, verb === "pause", now);
1878
+ return redirect(response, `/routines/${routineId}`);
1879
+ }
1880
+ case "run-now": {
1881
+ if (who.via !== "cookie")
1882
+ return refuse(response, who, 403, REMOTE_MESSAGES["step-up"]);
1883
+ // Step-up (Codex Phase C review, M3): run-now is spend outside the
1884
+ // approved schedule, so a session alone cannot ask for it — the
1885
+ // password is typed again, like an approval.
1886
+ const token = body.get("token") ?? "";
1887
+ if (token === "" && !hasFreshIdentitySignIn(who.name)) {
1888
+ return routinePage(response, who, routineId, "run now requires your password, typed again", 400);
1889
+ }
1890
+ const authenticated = authenticateApprover(who, token);
1891
+ if (!authenticated.ok) {
1892
+ return routinePage(response, who, routineId, "that is not your password", 403);
1893
+ }
1894
+ const outcome = fireRoutine(store, routineId, now, { manual: true });
1895
+ if (!outcome.ok) {
1896
+ return routinePage(response, who, routineId, `not fired: ${outcome.detail ?? outcome.reason}`, 409);
1897
+ }
1898
+ return redirect(response, `/routines/${routineId}`);
1899
+ }
1900
+ default:
1901
+ return refuse(response, who, 404, "That action isn't available here.", `/routines/${routineId}`);
1902
+ }
1903
+ }
1904
+ // ---- mutations -----------------------------------------------------------
1905
+ /** v28: the console-scoped liveness beat — see the route note in
1906
+ * handlePost. Supersedes v2 S2f's per-page id binding, reversed
1907
+ * KNOWINGLY: with parallel sessions one foregrounded tab per session is
1908
+ * impossible, and page-binding was attention theater over what was
1909
+ * always renewal-of-use. Approver-bound; never extends cookies, never
1910
+ * touches absolute expiry, never mints. */
1911
+ function attendedBeats(who, request, response, now) {
1912
+ if (who.via !== "cookie")
1913
+ return refuse(response, who, 403, "watching is a browser session's act");
1914
+ // Round-2 finding 2's named miss: renewal is an approver's act — a
1915
+ // viewer's open tab keeps nothing alive.
1916
+ if (who.role !== "approver")
1917
+ return refuse(response, who, 403, "your login can watch, not keep sessions alive");
1918
+ if (request.headers["sec-fetch-site"] !== "same-origin") {
1919
+ return refuse(response, who, 403, "the beat only answers this console's own pages");
1920
+ }
1921
+ // Belt with the braces: a PRESENT Origin/Referer must also name this
1922
+ // server — the same allowlist the shared guard applies.
1923
+ const namedOrigin = typeof request.headers.origin === "string" && request.headers.origin !== "null"
1924
+ ? request.headers.origin
1925
+ : typeof request.headers.referer === "string"
1926
+ ? request.headers.referer
1927
+ : null;
1928
+ if (namedOrigin !== null && !allowedHost(namedOrigin.replace(/^https?:[/][/]/, "").split("/")[0])) {
1929
+ return refuse(response, who, 403, "origin not allowed");
1930
+ }
1931
+ const attendedRunner = options.attended?.runner;
1932
+ let beaten = 0;
1933
+ if (attendedRunner !== undefined) {
1934
+ for (const open of store.openAuthorizationsOf(attendedRunner)) {
1935
+ if (open.approver !== who.name)
1936
+ continue;
1937
+ if (Date.parse(open.absoluteExpiry) <= now.getTime())
1938
+ continue;
1939
+ // Only terms SIGNED for console-wide renewal are renewed here
1940
+ // (round-1 finding 2): a legacy page-bound signature never
1941
+ // silently acquires the wider mode — it simply lapses.
1942
+ try {
1943
+ const signedMode = JSON.parse(open.termsJson).attentionMode;
1944
+ if (signedMode !== "console-visible")
1945
+ continue;
1946
+ }
1947
+ catch {
1948
+ continue;
1949
+ }
1950
+ const beatRef = store.refForId(open.taskRef);
1951
+ if (beatRef === null || !visible(beatRef.repo))
1952
+ continue;
1953
+ store.beatAuthorization(open.id, now);
1954
+ if (open.attemptRun !== null)
1955
+ options.attended?.coordinator?.poke(open.attemptRun);
1956
+ beaten++;
1957
+ }
1958
+ }
1959
+ return respond(response, 200, "application/json; charset=utf-8", JSON.stringify({ ok: true, beaten }));
1960
+ }
1961
+ async function attendMutation(response, who, taskId, verb, body, now) {
1962
+ const ref = store.lookupRef(taskId);
1963
+ if (ref === null || store.getTask(taskId) === null || !visible(ref.repo)) {
1964
+ return refuse(response, who, 404, "no such task", "/tasks");
1965
+ }
1966
+ if (who.via !== "cookie")
1967
+ return refuse(response, who, 403, "watching is a browser session's act");
1968
+ if (store.isDemo())
1969
+ return refuse(response, who, 403, "the demo authorizes nothing");
1970
+ if (verb === "attend-revoke") {
1971
+ const open = store.openAuthorizationFor(ref.id);
1972
+ if (open === null)
1973
+ return refuse(response, who, 409, "nothing to revoke", taskHref(taskId));
1974
+ store.closeAuthorization(open.id, "revoked", now);
1975
+ if (open.attemptRun !== null)
1976
+ options.attended?.coordinator?.poke(open.attemptRun);
1977
+ return redirect(response, taskHref(taskId));
1978
+ }
1979
+ const parentGiven = body.get("parent");
1980
+ const followupGiven = body.get("followup");
1981
+ const inputs = {
1982
+ minutes: Number(body.get("minutes") ?? "60"),
1983
+ turns: Number(body.get("turns") ?? "20"),
1984
+ budgetMicrousd: Number(body.get("budget") ?? String(store.getScope(taskId)?.budgetMicrousd ?? 2_000_000)),
1985
+ ...(body.get("expiry") === null ? {} : { expiry: body.get("expiry") }),
1986
+ ...(parentGiven === null || parentGiven === "" ? {} : { parent: Number(parentGiven) }),
1987
+ ...(followupGiven === null ? {} : { followup: followupGiven }),
1988
+ ...(body.get("model") === null ? {} : { model: body.get("model") }),
1989
+ ...(body.get("posture") === null ? {} : { posture: body.get("posture") }),
1990
+ };
1991
+ if (store.openAuthorizationFor(ref.id) !== null) {
1992
+ return refuse(response, who, 409, "an authorization is already open — revoke it first", taskHref(taskId));
1993
+ }
1994
+ const live = await liveAttendedTerms(taskId, inputs, now);
1995
+ if (!live.ok)
1996
+ return refuse(response, who, 409, live.problem, taskHref(taskId));
1997
+ // Sprint 8: an attended session runs at exactly what the person signs, so the organisation policy refuses it
1998
+ // outright (a provider, a model, or a posture above the permission ceiling) before anyone signs.
1999
+ {
2000
+ const pinned = profileFromJson(live.terms.profileJson);
2001
+ const refused = pinned === null ? null : store.attendedPolicyRefusal(pinned);
2002
+ if (refused !== null)
2003
+ return refuse(response, who, 403, refused, taskHref(taskId));
2004
+ }
2005
+ const digest = attendedDigestOf(live.terms);
2006
+ if (verb === "attend-preview") {
2007
+ const nonce = mintApprovalNonce(who.name, `attend-${taskId}`, digest);
2008
+ return attendConfirmScreen(response, who, live.terms, inputs, digest, nonce, who.session.csrf);
2009
+ }
2010
+ // attend: the yes. The nonce proves THIS form; the digest re-derived
2011
+ // from live state proves the world held between reading and signing.
2012
+ const nonce = body.get("nonce") ?? "";
2013
+ if (!consumeApprovalNonce(nonce, who.name, `attend-${taskId}`, body.get("digest") ?? "")) {
2014
+ return refuse(response, who, 409, "that form is stale — read it again", taskHref(taskId));
2015
+ }
2016
+ if (digest !== (body.get("digest") ?? "")) {
2017
+ return refuse(response, who, 409, "the world moved while you were reading (scope, model, or head) — read it again", taskHref(taskId));
2018
+ }
2019
+ const token = body.get("token") ?? "";
2020
+ let basis;
2021
+ if (token === "" && !hasFreshIdentitySignIn(who.name)) {
2022
+ // Quick mint (C2/M4): no password typed — valid ONLY when a live
2023
+ // mode with quickMint was signed by THIS session's person. The mint
2024
+ // transaction re-proves it; this pre-check only shapes the refusal.
2025
+ const mode = ref.repo === null ? null : store.activeMode(ref.repo, now);
2026
+ const modeTerms = mode === null ? null : modeTermsFromJson(mode.termsJson);
2027
+ if (mode === null || modeTerms === null || !modeTerms.quickMint || mode.signedBy !== who.name) {
2028
+ return refuse(response, who, 403, "authorizing takes your password, typed again", taskHref(taskId));
2029
+ }
2030
+ basis = { kind: "mode", digest: mode.digest };
2031
+ }
2032
+ else if (!authenticateApprover(who, token).ok) {
2033
+ return refuse(response, who, 403, "authorizing takes your password, typed again", taskHref(taskId));
2034
+ }
2035
+ const minted = store.mintAttendedAuthorization({
2036
+ id: randomUUID(),
2037
+ taskRef: ref.id,
2038
+ approver: who.name,
2039
+ runner: live.terms.runner,
2040
+ runnerGeneration: live.terms.runnerGeneration,
2041
+ compositeDigest: digest,
2042
+ termsJson: attendedTermsJson(live.terms),
2043
+ maxSessionTurns: live.terms.maxSessionTurns,
2044
+ budgetMicrousd: live.terms.budgetMicrousd,
2045
+ ...(live.terms.parentRun == null ? {} : { parentRun: live.terms.parentRun }),
2046
+ ...(live.terms.followup == null ? {} : { followup: live.terms.followup }),
2047
+ absoluteExpiry: live.terms.absoluteExpiry,
2048
+ ...(basis === undefined ? {} : { basis }),
2049
+ now,
2050
+ });
2051
+ if (!minted.ok) {
2052
+ return refuse(response, who, minted.reason === "authorization-open" ? 409 : 403, minted.reason === "mode-ended"
2053
+ ? "the mode that covered quick minting has ended — your password, typed again, still works"
2054
+ : minted.reason === "approval-rules"
2055
+ ? "this project's approval rules need someone else's approval (or two people's) — a watched run can't stand in for it"
2056
+ : "an authorization is already open — revoke it first", taskHref(taskId));
2057
+ }
2058
+ // The first beat is the mint itself: the person is visibly here.
2059
+ store.beatAuthorization(minted.authorization.id, now);
2060
+ return redirect(response, taskHref(taskId));
2061
+ }
2062
+ function taskMutation(response, who, taskId, verb, posted, now) {
2063
+ const ref = store.lookupRef(taskId);
2064
+ if (ref === null || store.getTask(taskId) === null) {
2065
+ return refuse(response, who, 404, "no such task", "/tasks");
2066
+ }
2067
+ if (!visible(ref.repo)) {
2068
+ return refuse(response, who, 404, "no such task", "/tasks");
2069
+ }
2070
+ switch (verb) {
2071
+ case "steer": {
2072
+ const body = readForm(posted, CONSOLE_FORMS.taskSteer);
2073
+ // Steering is a browser session's act, explicitly (arc 1 v2 §3):
2074
+ // identify() accepts bearer credentials generically, and those are
2075
+ // for machines — a person watching steers, cookie + CSRF only.
2076
+ if (who.via !== "cookie") {
2077
+ return refuse(response, who, 403, "steering is a browser session's act");
2078
+ }
2079
+ // The session IS the verified principal here — cookie + CSRF proved it.
2080
+ const filed = store.fileSteerNote(taskId, verifiedAuthor(who.name), body.get("note") ?? "", now);
2081
+ if (!filed.ok) {
2082
+ const said = filed.reason === "contest-open"
2083
+ ? "agents are racing on this task — steering waits until the tournament settles"
2084
+ : filed.reason === "task-finished"
2085
+ ? "this task is finished — a note has no next attempt to reach"
2086
+ : filed.reason === "invalid-note"
2087
+ ? (filed.problem ?? "that note will not store")
2088
+ : "no such task";
2089
+ return taskScreen(response, who, taskId, said, 400);
2090
+ }
2091
+ return redirect(response, taskHref(taskId));
2092
+ }
2093
+ case "hold": {
2094
+ const body = readForm(posted, CONSOLE_FORMS.taskHold);
2095
+ const reason = (body.get("reason") ?? "").trim() || "held from the console";
2096
+ if (reason.length > 200 || hasForbiddenControls(reason)) {
2097
+ return taskScreen(response, who, taskId, "that reason will not render, so it will not store", 400);
2098
+ }
2099
+ // Operator-owned only, always: the form supplies a reason, never an
2100
+ // owner. Decision, incident, and backoff holds are not reachable
2101
+ // from here, whatever a request claims.
2102
+ store.hold(ref.id, reason, null, now);
2103
+ return redirect(response, taskHref(taskId));
2104
+ }
2105
+ case "block": {
2106
+ const body = readForm(posted, CONSOLE_FORMS.taskBlock);
2107
+ // Chains are scheduling, not authority (chains-and-next review,
2108
+ // finding 2): the edge decides WHEN the ready set admits the task;
2109
+ // approval still decides WHAT may build. Both ends are re-proved
2110
+ // here — existence, ceiling, and the tournament guard — and the
2111
+ // cycle refusal comes from the store's own closure check.
2112
+ const on = (body.get("on") ?? "").trim();
2113
+ const blocker = on === "" ? null : store.getTask(on);
2114
+ const blockerRef = on === "" ? null : store.lookupRef(on);
2115
+ if (blocker === null || blockerRef === null || !visible(blockerRef.repo)) {
2116
+ return taskScreen(response, who, taskId, "that task to wait for does not exist here", 404);
2117
+ }
2118
+ if (store.openContestFor(ref.id) !== null) {
2119
+ return taskScreen(response, who, taskId, "a tournament is running on this task — let it finish, then pick or abandon it", 409);
2120
+ }
2121
+ const added = store.addEdge(taskId, on);
2122
+ if (!added.ok) {
2123
+ return taskScreen(response, who, taskId, `could not make ${taskId} wait for ${on} — ${added.reason}`, 409);
2124
+ }
2125
+ return redirect(response, taskHref(taskId));
2126
+ }
2127
+ case "unblock": {
2128
+ const body = readForm(posted, CONSOLE_FORMS.taskBlock);
2129
+ const on = (body.get("on") ?? "").trim();
2130
+ if (store.openContestFor(ref.id) !== null) {
2131
+ return taskScreen(response, who, taskId, "a tournament is running on this task — let it finish, then pick or abandon it", 409);
2132
+ }
2133
+ const removed = store.removeEdge(taskId, on);
2134
+ if (!removed.ok) {
2135
+ return taskScreen(response, who, taskId, `${taskId} was not waiting on ${on}`, 409);
2136
+ }
2137
+ return redirect(response, taskHref(taskId));
2138
+ }
2139
+ case "repair-dependency": {
2140
+ const body = readForm(posted, CONSOLE_FORMS.taskRepairDependency);
2141
+ const blocker = (body.get("blocker") ?? "").trim();
2142
+ const operation = (body.get("operation") ?? "").trim();
2143
+ const blockerTask = blocker === "" || !store.blockers(taskId).includes(blocker) ? null : store.getTask(blocker);
2144
+ if (blockerTask === null || (blockerTask.state !== "failed" && blockerTask.state !== "cancelled")) {
2145
+ return taskScreen(response, who, taskId, "what this task waits for changed — refresh the page and choose again", 409);
2146
+ }
2147
+ if (store.openContestFor(ref.id) !== null) {
2148
+ return taskScreen(response, who, taskId, "a tournament is running on this task — let it finish before changing its dependencies", 409);
2149
+ }
2150
+ if (operation === "retry") {
2151
+ const blockerRef = store.lookupRef(blocker);
2152
+ if (blockerTask.state !== "failed" || blockerRef === null || !visible(blockerRef.repo)) {
2153
+ return taskScreen(response, who, taskId, "that failed task cannot be tried again here — wait for a different task or continue without it", 409);
2154
+ }
2155
+ const retried = store.requeueTask(blocker, who.name, now);
2156
+ if (!retried.ok)
2157
+ return taskScreen(response, who, taskId, `that task could not be queued again — ${retried.reason}`, 409);
2158
+ return redirect(response, taskHref(taskId));
2159
+ }
2160
+ if (operation === "unlink") {
2161
+ const removed = store.removeEdge(taskId, blocker);
2162
+ if (!removed.ok)
2163
+ return taskScreen(response, who, taskId, "what this task waits for changed — refresh the page and choose again", 409);
2164
+ return redirect(response, taskHref(taskId));
2165
+ }
2166
+ if (operation === "replace") {
2167
+ const replacement = (body.get("replacement") ?? "").trim();
2168
+ const replacementTask = replacement === "" ? null : store.getTask(replacement);
2169
+ const replacementRef = replacement === "" ? null : store.lookupRef(replacement);
2170
+ if (replacementTask === null ||
2171
+ replacementRef === null ||
2172
+ !visible(replacementRef.repo) ||
2173
+ (replacementTask.state !== "queued" && replacementTask.state !== "running")) {
2174
+ return taskScreen(response, who, taskId, "choose another unfinished task from a project you can manage", 409);
2175
+ }
2176
+ const replaced = store.replaceEdge(taskId, blocker, replacement);
2177
+ if (!replaced.ok)
2178
+ return taskScreen(response, who, taskId, `this task could not wait for the selected work — ${replaced.reason}`, 409);
2179
+ return redirect(response, taskHref(taskId));
2180
+ }
2181
+ return taskScreen(response, who, taskId, "choose whether to try that task again, wait for a different task, or continue without it", 400);
2182
+ }
2183
+ case "next": {
2184
+ const body = readForm(posted, CONSOLE_FORMS.taskNext);
2185
+ if (body.get("undo") !== null) {
2186
+ const cleared = store.clearTaskPriority(taskId);
2187
+ if (!cleared.ok)
2188
+ return taskScreen(response, who, taskId, "this task could not be put back in filing order", 409);
2189
+ return redirect(response, taskHref(taskId));
2190
+ }
2191
+ const moved = store.moveTaskNext(taskId, now);
2192
+ if (!moved.ok) {
2193
+ const said = moved.reason === "not-queued"
2194
+ ? "only queued work can move up — this task is not waiting in the queue"
2195
+ : moved.reason === "claimed"
2196
+ ? "this task is being built right now — it needs no place in line"
2197
+ : moved.reason === "contest-open"
2198
+ ? "a tournament is running on this task — let it finish, then pick or abandon it"
2199
+ : "the queue rank could not be raised";
2200
+ return taskScreen(response, who, taskId, said, 409);
2201
+ }
2202
+ return redirect(response, taskHref(taskId));
2203
+ }
2204
+ case "reopen": {
2205
+ const body = readForm(posted, CONSOLE_FORMS.taskReopen);
2206
+ // Authenticated like every approving act: the session alone may
2207
+ // read; resuming external work takes the password, typed again.
2208
+ const token = (body.get("token") ?? "").trim();
2209
+ const authenticated = token !== "" ? authenticateApprover(who, token) : null;
2210
+ if (authenticated === null || !authenticated.ok) {
2211
+ return taskScreen(response, who, taskId, "reopening takes your password, typed again", 403);
2212
+ }
2213
+ const reopened = store.reopenMirror(taskId, who.name, now);
2214
+ if (!reopened.ok) {
2215
+ const said = {
2216
+ "unknown-task": "this task is not external work",
2217
+ "not-latched": "the tracker never closed this — there is nothing to reopen",
2218
+ "not-seen-open": "the tracker has not been seen open again since the close — reopen it there first; the next sync notices",
2219
+ claimed: "this task is being built right now",
2220
+ "contest-open": "a tournament is open on this task — decide it first",
2221
+ held: "a hold stands — lift it first",
2222
+ "question-open": "an unanswered question stands — answer or close it first",
2223
+ "incident-open": "an unresolved incident stands — resolve it first",
2224
+ "bad-state": "this task is not in a state reopen can take",
2225
+ };
2226
+ return taskScreen(response, who, taskId, said[reopened.reason] ?? "the task could not be reopened", 409);
2227
+ }
2228
+ return redirect(response, taskHref(taskId));
2229
+ }
2230
+ case "unhold": {
2231
+ store.unhold(ref.id);
2232
+ return redirect(response, taskHref(taskId));
2233
+ }
2234
+ case "requeue": {
2235
+ const body = readForm(posted, CONSOLE_FORMS.taskRequeue);
2236
+ // Retry may carry a note for the next attempt: the same steering note, checked before anything changes.
2237
+ const note = (body.get("note") ?? "").trim();
2238
+ if (note !== "" && who.via !== "cookie")
2239
+ return refuse(response, who, 403, "steering is a browser session's act");
2240
+ if (note !== "" && !validateNote(note).ok)
2241
+ return taskScreen(response, who, taskId, "that note will not store, so nothing was retried", 400);
2242
+ const requeued = store.requeueTask(taskId, who.name, now);
2243
+ if (!requeued.ok) {
2244
+ return taskScreen(response, who, taskId, `not requeued: ${requeued.reason}`, 409);
2245
+ }
2246
+ if (note !== "") {
2247
+ const filed = store.fileSteerNote(taskId, verifiedAuthor(who.name), note, now);
2248
+ if (!filed.ok)
2249
+ return taskScreen(response, who, taskId, "Retried. The note wasn't saved; add it under Steering.", 409);
2250
+ }
2251
+ // Allow-listed return only — never an arbitrary URL from the form.
2252
+ return redirect(response, body.get("return") === "inbox" ? "/inbox" : body.get("return") === "next" ? "/next" : taskHref(taskId));
2253
+ }
2254
+ case "follow-up": {
2255
+ const body = readForm(posted, CONSOLE_FORMS.taskFollowUp);
2256
+ // A scout's proposed follow-up, filed by the operator's tap (mate
2257
+ // arc §10): the ONE filing door, this task's repository, the scope
2258
+ // text stamped as the scout's — mode coverage never seals it.
2259
+ const indexRaw = body.get("index") ?? "";
2260
+ if (!/^[0-9]{1,2}$/.test(indexRaw))
2261
+ return taskScreen(response, who, taskId, "which follow-up?", 400);
2262
+ const view = readVerifiedReport(store, evidenceRoot, ref.id);
2263
+ if (view === null || !view.ok)
2264
+ return taskScreen(response, who, taskId, "this task has no report to file from", 409);
2265
+ const followUp = view.report.followUps[Number(indexRaw)];
2266
+ if (followUp === undefined)
2267
+ return taskScreen(response, who, taskId, "the report proposes no such follow-up", 409);
2268
+ if (ref.repo === null)
2269
+ return taskScreen(response, who, taskId, "this task has no repository — file the follow-up by hand", 409);
2270
+ if (!visible(ref.repo))
2271
+ return taskScreen(response, who, taskId, "that repository is outside what this server shows", 403);
2272
+ // Idempotent by construction (v4 review, finding 10): the filing's
2273
+ // id is derived from the source task, the report's run, and the
2274
+ // follow-up's place — a retried POST lands on the task the first
2275
+ // one filed instead of minting a twin.
2276
+ const followUpId = `${taskId.slice(0, 40)}-r${view.run}-f${Number(indexRaw) + 1}`;
2277
+ if (store.getTask(followUpId) !== null)
2278
+ return redirect(response, taskHref(followUpId));
2279
+ const filed = fileTaskProposal(store, {
2280
+ id: followUpId,
2281
+ title: followUp.title,
2282
+ repo: ref.repo,
2283
+ goal: followUp.goal,
2284
+ // v39: the scout's report format does not yet draft a rubric
2285
+ // per follow-up — a placeholder names the operator's own
2286
+ // review as the outstanding work, the same posture a
2287
+ // coordinator's bare intent takes.
2288
+ acceptance: PLACEHOLDER_RUBRIC,
2289
+ filedVia: "console", filedBy: { name: who.name, kind: "person" },
2290
+ proposedVia: "scout",
2291
+ ...(unscopedMode ? {} : { admittedRepos: admissionList() ?? [] }),
2292
+ }, now);
2293
+ if (!filed.ok) {
2294
+ if (filed.reason === "duplicate")
2295
+ return redirect(response, taskHref(followUpId));
2296
+ return taskScreen(response, who, taskId, `not filed: ${filed.message}`, filed.reason === "backlog-full" ? 429 : 400);
2297
+ }
2298
+ return redirect(response, taskHref(filed.id));
2299
+ }
2300
+ case "plan": {
2301
+ // The operator's explicit ask, refused transactionally when the
2302
+ // moment has passed — approved scope, live claim, or a plan
2303
+ // already under way (Codex planning review's requestPlan guard).
2304
+ const asked = store.requestPlan(ref.id, now);
2305
+ if (!asked.ok) {
2306
+ return taskScreen(response, who, taskId, `not planned: ${asked.reason}`, 409);
2307
+ }
2308
+ if (who.via === "cookie")
2309
+ authorizePlanUnderMode(store, taskId, who.name, now);
2310
+ return redirect(response, taskHref(taskId));
2311
+ }
2312
+ case "plan-edit": {
2313
+ const body = readForm(posted, CONSOLE_FORMS.taskPlanEdit);
2314
+ if (who.via !== "cookie") {
2315
+ return refuse(response, who, 403, "editing a plan is a browser session's act");
2316
+ }
2317
+ const scope = store.getScope(taskId);
2318
+ const current = planViewOf(ref.id);
2319
+ if (ref.plan !== "drafted" || scope === null || current === null) {
2320
+ return taskScreen(response, who, taskId, "there is no editable plan on this task", 409);
2321
+ }
2322
+ if (approvalOf(scope).approved) {
2323
+ return taskScreen(response, who, taskId, "this plan is already approved and locked — file a revision instead", 409);
2324
+ }
2325
+ if (store.hasLiveClaim(ref.id, now)) {
2326
+ return taskScreen(response, who, taskId, "this task is running — its plan cannot change underneath the agent", 409);
2327
+ }
2328
+ if ((body.get("saw-plan") ?? "") !== current.sha256) {
2329
+ return taskScreen(response, who, taskId, "the plan changed while this editor was open — read the latest version and try again", 409);
2330
+ }
2331
+ const document = (body.get("plan-document") ?? "").replace(/\r\n?/g, "\n").trim();
2332
+ const parsed = parseExecutionPlanDocument(document);
2333
+ if (!parsed.ok) {
2334
+ return taskScreen(response, who, taskId, `plan not saved: ${parsed.problems.map(one => one.message).join("; ")}`, 400);
2335
+ }
2336
+ const proof = parsed.document.proof.join("\n");
2337
+ const missing = scope.acceptance.filter(criterion => {
2338
+ const id = criterion.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2339
+ return !new RegExp(`(^|[^A-Za-z0-9_-])${id}([^A-Za-z0-9_-]|$)`).test(proof);
2340
+ });
2341
+ if (missing.length > 0) {
2342
+ return taskScreen(response, who, taskId, `plan not saved: Proof must name ${missing.map(one => one.id).join(", ")}`, 400);
2343
+ }
2344
+ if (document === current.document.trim())
2345
+ return redirect(response, taskHref(taskId));
2346
+ const content = Buffer.from(`${document}\n`, "utf8");
2347
+ if (content.length > TEXT_LIMITS.planDocumentBytes) {
2348
+ return taskScreen(response, who, taskId, `plan not saved: it is over ${TEXT_LIMITS.planDocumentBytes} bytes`, 400);
2349
+ }
2350
+ try {
2351
+ const name = `plan-edit-${randomUUID().replace(/-/g, "")}.md`;
2352
+ const key = writeEvidenceFile(evidenceRoot, current.run, name, content);
2353
+ store.saveArtifact({
2354
+ run: current.run,
2355
+ kind: "plan",
2356
+ key,
2357
+ bytesOriginal: content.length,
2358
+ bytesStored: content.length,
2359
+ truncated: false,
2360
+ sha256: createHash("sha256").update(content).digest("hex"),
2361
+ capture: `operator edit by ${who.name} (verified session)`,
2362
+ }, now);
2363
+ }
2364
+ catch {
2365
+ return taskScreen(response, who, taskId, "the plan could not be saved safely — nothing changed", 500);
2366
+ }
2367
+ return redirect(response, taskHref(taskId));
2368
+ }
2369
+ case "accept-revision":
2370
+ case "reject-revision": {
2371
+ const body = readForm(posted, CONSOLE_FORMS.taskRevision);
2372
+ // Adaptive execution plans (v44): resolving a revision the builder
2373
+ // filed while its authority snapshot no longer matched the run it
2374
+ // started under — the ONLY road a 'blocked' plan_revision reaches,
2375
+ // since a plan-only proposal auto-applies without ever pausing here.
2376
+ if (who.via !== "cookie") {
2377
+ return refuse(response, who, 403, "resolving a plan revision is a browser session's act");
2378
+ }
2379
+ const revisionId = Number(body.get("revision-id") ?? "");
2380
+ const revision = Number.isFinite(revisionId) ? store.getPlanRevision(revisionId) : null;
2381
+ if (revision === null || revision.taskRef !== ref.id || revision.status !== "blocked") {
2382
+ return taskScreen(response, who, taskId, "that plan revision is no longer waiting on a decision — refresh and try again", 409);
2383
+ }
2384
+ const outcome = verb === "accept-revision" ? "applied" : "rejected";
2385
+ if (outcome === "applied") {
2386
+ // Accepting a revision that changed signed scope or publication
2387
+ // authority takes the password ceremony again — the same act
2388
+ // that would be required to approve that authority from scratch.
2389
+ const token = (body.get("token") ?? "").trim();
2390
+ if (!authenticateApprover(who, token).ok) {
2391
+ return taskScreen(response, who, taskId, "accepting this revision takes your password, typed again", 403);
2392
+ }
2393
+ }
2394
+ const resolved = store.resolvePlanRevision(revision.id, outcome, who.name, now);
2395
+ if (!resolved) {
2396
+ return taskScreen(response, who, taskId, "that plan revision is no longer waiting on a decision — refresh and try again", 409);
2397
+ }
2398
+ store.releaseOwnedHold("revision", String(revision.id));
2399
+ return redirect(response, taskHref(taskId));
2400
+ }
2401
+ case "cancel": {
2402
+ const body = readForm(posted, CONSOLE_FORMS.taskCancel);
2403
+ const reason = body.get("reason") ?? undefined;
2404
+ const cancelled = withActor({ account: who.name, lead: false }, () => store.cancelTask(taskId, now, reason));
2405
+ if (!cancelled.ok) {
2406
+ if (cancelled.reason === "reason-required" || cancelled.reason === "bad-reason") {
2407
+ return taskScreen(response, who, taskId, cancelled.reason === "reason-required"
2408
+ ? "Enter a reason for cancelling this coordinator filing."
2409
+ : "Use at most 500 plain characters, without hidden or control characters.", 400, undefined, reason ?? "");
2410
+ }
2411
+ return taskScreen(response, who, taskId, `not cancelled: ${cancelled.reason}`, 409);
2412
+ }
2413
+ return redirect(response, taskHref(taskId));
2414
+ }
2415
+ case "route": {
2416
+ const body = readForm(posted, CONSOLE_FORMS.taskRoute);
2417
+ // THE AGENTS EDIT (v47): an approver's session declares the risk,
2418
+ // overrides one role to an exact agent, or clears an override — ONE
2419
+ // authenticated transaction in the store that CAS-checks the scope
2420
+ // digest the form rendered (empty = "I saw no scope"), records the
2421
+ // change under this name, reconciles the planner pin, re-files the
2422
+ // scope so a sealed route goes visibly stale, and re-requests a
2423
+ // drafted plan when the planner changed. The viewer gate above
2424
+ // already refused a watcher; a live claim or a tournament refuses
2425
+ // inside the transaction.
2426
+ const riskGiven = body.get("risk");
2427
+ const sizeGiven = body.get("size");
2428
+ const riskyGiven = body.getAll("risky");
2429
+ const phaseGiven = body.get("phase");
2430
+ const clearGiven = body.get("clear-phase");
2431
+ // The agent arrives as the form's `provider|model` choice (v48) or,
2432
+ // for older clients, as separate fields — either way it must be one
2433
+ // of the CONFIGURED, role-valid choices right now: the form offers
2434
+ // nothing else, and the server believes nothing else.
2435
+ const agentGiven = (body.get("agent") ?? "").trim();
2436
+ const providerGiven = agentGiven === "" ? body.get("provider") : agentGiven.slice(0, agentGiven.indexOf("|") === -1 ? agentGiven.length : agentGiven.indexOf("|"));
2437
+ const modelGiven = agentGiven === "" ? (body.get("model") ?? "").trim() : agentGiven.indexOf("|") === -1 ? "" : agentGiven.slice(agentGiven.indexOf("|") + 1).trim();
2438
+ const sawDigest = body.get("sawDigest");
2439
+ if (riskGiven !== null && !isRiskLevel(riskGiven))
2440
+ return taskScreen(response, who, taskId, "risk is routine, elevated, or high", 400);
2441
+ const phase = phaseGiven ?? clearGiven;
2442
+ if (phase !== null && !ROUTE_PHASES.includes(phase))
2443
+ return taskScreen(response, who, taskId, "the role is planner, builder, or revision", 400);
2444
+ if (sizeGiven !== null && !isTaskSize(sizeGiven))
2445
+ return taskScreen(response, who, taskId, "size is small, medium, or large", 400);
2446
+ if (riskyGiven.some(one => one !== "yes" && one !== "no"))
2447
+ return taskScreen(response, who, taskId, "risky is yes or no", 400);
2448
+ // A size keeps the task's risky flag unless the form says otherwise; marking risky keeps the size.
2449
+ const sizeEdit = sizeGiven === null && riskyGiven.length === 0 ? undefined : {
2450
+ size: sizeGiven !== null && isTaskSize(sizeGiven) ? sizeGiven : ref.sizing?.size ?? "medium",
2451
+ risky: riskyGiven.includes("yes") ? true : riskyGiven.includes("no") ? false : ref.sizing?.risky ?? false,
2452
+ };
2453
+ if (riskGiven === null && phase === null && sizeEdit === undefined)
2454
+ return taskScreen(response, who, taskId, "nothing to change about the agents", 400);
2455
+ if (phaseGiven !== null) {
2456
+ if (providerGiven === null || !isProviderId(providerGiven))
2457
+ return taskScreen(response, who, taskId, "provider is claude, codex, openrouter, or gemini", 400);
2458
+ if (modelGiven === "")
2459
+ return taskScreen(response, who, taskId, "name the exact model id — approvals bind exact agents", 400);
2460
+ const valid = validateSpec({ provider: providerGiven, model: modelGiven });
2461
+ if (!valid.ok)
2462
+ return taskScreen(response, who, taskId, `agents not changed: ${valid.problem}`, 400);
2463
+ if (phaseGiven === "review" && providerGiven === "gemini")
2464
+ return taskScreen(response, who, taskId, "gemini cannot review yet — choose claude or codex", 400);
2465
+ }
2466
+ // The configured-choice proof lives INSIDE the edit transaction
2467
+ // (`configured`): the pair is held to the role's choices under the
2468
+ // configuration standing at that instant, and a stale choice
2469
+ // mutates nothing.
2470
+ const edited = store.editTaskRoute(ref.id, {
2471
+ by: who.name,
2472
+ authenticate: () => {
2473
+ const account = store.accountOf(who.name);
2474
+ return account !== null && account.revokedAt === null && account.role === "approver" ? { ok: true } : { ok: false, reason: "your login is no longer an approver" };
2475
+ },
2476
+ ...(riskGiven === null ? {} : { risk: riskGiven }),
2477
+ // A person's size replaces the classifier's; the risky flag rides with it.
2478
+ ...(sizeEdit === undefined ? {} : { size: sizeEdit }),
2479
+ ...(phase === null
2480
+ ? {}
2481
+ : { override: phaseGiven !== null ? { phase: phase, provider: providerGiven, model: modelGiven } : { phase: phase, clear: true } }),
2482
+ ...(sawDigest === null ? {} : { expectDigest: sawDigest === "" ? null : sawDigest }),
2483
+ configured: true,
2484
+ }, now);
2485
+ if (!edited.ok) {
2486
+ return taskScreen(response, who, taskId, edited.reason === "not-configured" ? `agents not changed: ${edited.detail}` : edited.detail, edited.reason === "unauthenticated" ? 403 : edited.reason === "nothing" || edited.reason === "not-configured" ? 400 : 409);
2487
+ }
2488
+ // v102: changing how a task runs is authoring it — the requester rule then refuses this person too.
2489
+ const routed = store.getScope(taskId);
2490
+ if (routed !== null)
2491
+ store.recordScopeAuthor(taskId, routed.digest, who.name, now);
2492
+ const toChat = body.get("return") === "chat";
2493
+ const back = toChat ? taskChatHref(taskId) : taskHref(taskId);
2494
+ const said = [
2495
+ ...(edited.staled ? ["agents changed — the earlier approval no longer covers this task; approve it again"] : []),
2496
+ ...(edited.replanned ? ["the planner changed after a draft landed — a new plan was requested"] : []),
2497
+ ];
2498
+ const target = said.length === 0 ? back : chatReturnWithSaid(back, said.join("; "));
2499
+ return redirect(response, toChat ? target : `${target}#agents`);
2500
+ }
2501
+ case "scope": {
2502
+ const body = readForm(posted, CONSOLE_FORMS.taskScope);
2503
+ const sawDigest = body.get("sawDigest");
2504
+ const permissionGiven = body.get("permission-mode");
2505
+ if (permissionGiven !== null && permissionGiven !== "" && permissionGiven !== "auto" && permissionGiven !== "bypassPermissions") {
2506
+ return taskScreen(response, who, taskId, "permissions must be Auto or Full access", 400);
2507
+ }
2508
+ const permissionMode = permissionGiven === "bypassPermissions"
2509
+ ? "bypassPermissions"
2510
+ : permissionGiven === "auto"
2511
+ ? "auto"
2512
+ : ref.permissionMode ?? store.permissionDefault().mode;
2513
+ const qualityGiven = body.get("quality-mode");
2514
+ if (qualityGiven !== null && qualityGiven !== "" && !isQualityMode(qualityGiven)) {
2515
+ return taskScreen(response, who, taskId, "quality must be Default or Strict / release", 400);
2516
+ }
2517
+ const qualityMode = qualityGiven === "strict"
2518
+ ? "strict"
2519
+ : qualityGiven === "default"
2520
+ ? "default"
2521
+ : ref.qualityMode ?? store.qualityDefault().mode;
2522
+ // The optional per-attempt dollar cap (v15) rides the same form.
2523
+ const budgetGiven = (body.get("budget-usd") ?? "").trim();
2524
+ const budgetUsd = budgetGiven === "" ? null : Number(budgetGiven);
2525
+ if (budgetUsd !== null && (!Number.isFinite(budgetUsd) || budgetUsd <= 0)) {
2526
+ return taskScreen(response, who, taskId, "the dollar cap is a positive amount", 400);
2527
+ }
2528
+ // The approval sheet's in-place editor carries the cap it was shown
2529
+ // exactly, in millionths, and "none" for no limit (never a default).
2530
+ const exactBudgetGiven = body.get("budget-microusd");
2531
+ const exactBudget = exactBudgetGiven === null ? undefined : exactBudgetGiven === "none" ? null : /^[1-9][0-9]{0,14}$/.test(exactBudgetGiven) ? Number(exactBudgetGiven) : NaN;
2532
+ if (Number.isNaN(exactBudget))
2533
+ return taskScreen(response, who, taskId, "the dollar cap is a positive amount", 400);
2534
+ const inPlace = body.has("requirement-new");
2535
+ // The tournament controls (operator request): a count of 2–4 files
2536
+ // race terms BESIDE the scope — validated and priced BEFORE anything
2537
+ // saves, so a bad tournament never half-lands on a good scope.
2538
+ const raceCountGiven = (body.get("race-count") ?? "").trim();
2539
+ // The comparison lanes (slice B): any filled row files a comparison
2540
+ // INSTEAD of a tournament — planned and refused BEFORE the save,
2541
+ // filed INSIDE the same transaction (round-6 finding 5 discipline).
2542
+ const comparisonLanes = [1, 2, 3, 4]
2543
+ .map(lane => ({
2544
+ provider: (body.get(`compare-provider-${lane}`) ?? "").trim(),
2545
+ model: (body.get(`compare-model-${lane}`) ?? "").trim(),
2546
+ permissionMode,
2547
+ }))
2548
+ .filter(lane => lane.provider !== "" || lane.model !== "");
2549
+ let plannedComparison = null;
2550
+ if (comparisonLanes.length > 0) {
2551
+ if (raceCountGiven !== "") {
2552
+ return taskScreen(response, who, taskId, "a tournament and a comparison are different ceremonies — file one or the other", 400);
2553
+ }
2554
+ if (store.mirrorByTask(taskId) !== null) {
2555
+ return taskScreen(response, who, taskId, "external work compares in a follow-up release — file the comparison on a local task", 409);
2556
+ }
2557
+ plannedComparison = planComparison({ agents: comparisonLanes });
2558
+ if (!plannedComparison.ok) {
2559
+ return taskScreen(response, who, taskId, `comparison not filed: ${plannedComparison.message}`, 400);
2560
+ }
2561
+ }
2562
+ let plannedRace = null;
2563
+ if (raceCountGiven !== "") {
2564
+ const count = Number(raceCountGiven);
2565
+ const model = body.get("race-model") ?? "";
2566
+ if (!Number.isInteger(count) || count < 2 || count > 4) {
2567
+ return taskScreen(response, who, taskId, "a tournament races 2 to 4 agents", 400);
2568
+ }
2569
+ const perUsd = Number((body.get("race-per-usd") ?? "").trim());
2570
+ const totalUsd = Number((body.get("race-total-usd") ?? "").trim());
2571
+ const planned = planTournament({
2572
+ agents: Array.from({ length: count }, () => ({ provider: "claude", model, permissionMode })),
2573
+ perAgentBudgetUsd: perUsd,
2574
+ totalBudgetUsd: totalUsd,
2575
+ });
2576
+ if (!planned.ok) {
2577
+ return taskScreen(response, who, taskId, `tournament not filed: ${planned.message}`, 400);
2578
+ }
2579
+ plannedRace = planned.plan;
2580
+ }
2581
+ // Refusals that must hold ATOMICALLY with the save (round-6 finding
2582
+ // 5): a race request refused AFTER proposeGuarded would still have
2583
+ // rewritten the scope — so proposal, the attended exclusion, and
2584
+ // race-term filing share one transaction, and every refusal inside
2585
+ // it rolls the whole act back.
2586
+ if (plannedRace !== null && store.mirrorByTask(taskId) !== null) {
2587
+ return taskScreen(response, who, taskId, "external work races in a follow-up release — file the tournament on a local task", 409);
2588
+ }
2589
+ let saved;
2590
+ try {
2591
+ saved = store.transact(() => {
2592
+ if ((plannedRace !== null || plannedComparison !== null) && store.openAuthorizationFor(ref.id) !== null) {
2593
+ return { ok: false, status: 409, message: "an attended authorization is open on this task — revoke it before filing a tournament or comparison" };
2594
+ }
2595
+ // C1/M3: the signer's own credentialed filing auto-approves —
2596
+ // coverage is asked INSIDE this transaction, the escalated
2597
+ // default and budget ride the filing, and the seal commits
2598
+ // atomically with it. Plain scopes only: a tournament or
2599
+ // comparison keeps its own human ceremony.
2600
+ // COOKIE ONLY (C1's channel table; surfaces round 1, finding 1):
2601
+ // bearer credentials are for machines, and a machine road must
2602
+ // never spend the signer's mode.
2603
+ const coverage = who.via === "cookie" && plannedRace === null && plannedComparison === null
2604
+ ? modeFilingCoverage(store, ref.repo, who.name, now)
2605
+ : null;
2606
+ const before = store.getScope(taskId);
2607
+ const proposed = proposeGuarded(store, {
2608
+ taskId, author: who.name,
2609
+ goal: body.has("goal-brief") ? `${(body.get("goal") ?? "").trim()}${body.get("goal-brief")}` : body.get("goal") ?? "",
2610
+ outOfScope: body.get("not") ?? null,
2611
+ touches: (body.get("touches") ?? "").split(/[\n,]/),
2612
+ // The approval sheet's in-place editor sends Done when line by line.
2613
+ acceptance: inPlace
2614
+ ? requirementsFromEditor(body, before?.acceptance ?? [])
2615
+ : acceptanceLinesToInput((body.get("acceptance") ?? "").split("\n")),
2616
+ permissionMode,
2617
+ qualityMode,
2618
+ ...(exactBudget !== undefined
2619
+ ? exactBudget === null ? {} : { budgetMicrousd: exactBudget }
2620
+ : budgetUsd !== null
2621
+ ? { budgetMicrousd: Math.round(budgetUsd * 1_000_000) }
2622
+ : coverage?.defaultBudgetMicrousd != null
2623
+ ? { budgetMicrousd: coverage.defaultBudgetMicrousd }
2624
+ : {}),
2625
+ ...(coverage?.escalated === true ? { posture: "escalated" } : {}),
2626
+ sawDigest: sawDigest === null || sawDigest === "" ? null : sawDigest,
2627
+ taskRef: ref.id,
2628
+ now,
2629
+ });
2630
+ if (!proposed.ok) {
2631
+ return {
2632
+ ok: false,
2633
+ status: proposed.reason === "changed" || proposed.reason === "claimed" ? 409 : 400,
2634
+ message: proposed.message ?? (proposed.reason === "acceptance-required" ? "Not saved: add at least one requirement under Done when." : `scope not saved: ${proposed.reason}`),
2635
+ };
2636
+ }
2637
+ // Editing the plan in place never changes what the agent may do:
2638
+ // a save that would (a legacy setting, a newer policy or mode)
2639
+ // rolls back whole.
2640
+ if (inPlace && before?.profile != null && proposed.scope.profile != null && levelOfProfile(before.profile) !== levelOfProfile(proposed.scope.profile)) {
2641
+ throw new PermissionsWouldChange();
2642
+ }
2643
+ if (coverage !== null && ref.plan === "requested") {
2644
+ authorizePlanUnderMode(store, taskId, who.name, now);
2645
+ }
2646
+ else if (coverage !== null) {
2647
+ const filedScope = store.getScope(taskId);
2648
+ if (filedScope?.profileState === "resolved") {
2649
+ const sealedUnderMode = store.sealScopeApproval(taskId, who.name, now, {}, { kind: "mode", modeDigest: coverage.digest });
2650
+ // The quarantine speaks at the caller (review finding 7).
2651
+ if (!sealedUnderMode) {
2652
+ return { ok: false, status: 200, message: "scope saved — coordinator-filed: mode coverage cannot admit it; sign the scope" };
2653
+ }
2654
+ }
2655
+ }
2656
+ if (plannedComparison !== null && plannedComparison.ok) {
2657
+ store.fileTournamentTerms({
2658
+ taskRef: ref.id,
2659
+ kind: "comparison",
2660
+ raceDigest: plannedComparison.plan.comparisonDigest,
2661
+ agents: plannedComparison.plan.agents,
2662
+ perAgentBudgetMicrousd: 0,
2663
+ overrunReserveMicrousd: 0,
2664
+ totalBudgetMicrousd: 0,
2665
+ priceVersion: 0,
2666
+ publicationPolicy: plannedComparison.plan.publicationPolicy,
2667
+ }, now);
2668
+ return { ok: true };
2669
+ }
2670
+ if (plannedRace === null) {
2671
+ // Switching back to "one agent" withdraws a standing race OR
2672
+ // comparison — the deactivated row survives as history, and
2673
+ // the approval card returns to the scope alone.
2674
+ store.retractTournamentTerms(ref.id);
2675
+ return { ok: true };
2676
+ }
2677
+ store.fileTournamentTerms({
2678
+ taskRef: ref.id,
2679
+ raceDigest: plannedRace.raceDigest,
2680
+ agents: plannedRace.agents,
2681
+ perAgentBudgetMicrousd: plannedRace.perAgentBudgetMicrousd,
2682
+ overrunReserveMicrousd: plannedRace.overrunReserveMicrousd,
2683
+ totalBudgetMicrousd: plannedRace.totalBudgetMicrousd,
2684
+ priceVersion: plannedRace.priceVersion,
2685
+ publicationPolicy: plannedRace.publicationPolicy,
2686
+ }, now);
2687
+ return { ok: true };
2688
+ });
2689
+ }
2690
+ catch (error) {
2691
+ if (!(error instanceof PermissionsWouldChange))
2692
+ throw error;
2693
+ saved = { ok: false, status: 409, message: "Not saved: this would change what the agent may do. Change permissions in Details, under edit the scope." };
2694
+ }
2695
+ if (!saved.ok) {
2696
+ // Refused because the plan moved on: the draft comes back with the version now on file, so saving it
2697
+ // again (having read the reason) can succeed instead of being refused the same way.
2698
+ const draft = new URLSearchParams(body.sent);
2699
+ if (saved.status === 409)
2700
+ draft.set("sawDigest", store.getScope(taskId)?.digest ?? "");
2701
+ return taskScreen(response, who, taskId, saved.message, saved.status, draft);
2702
+ }
2703
+ return redirect(response, taskHref(taskId));
2704
+ }
2705
+ case "approve": {
2706
+ if (who.via !== "cookie")
2707
+ return refuse(response, who, 403, REMOTE_MESSAGES["step-up"]);
2708
+ const body = readForm(posted, CONSOLE_FORMS.taskApprove);
2709
+ const requestedReturn = body.get("return");
2710
+ const approvalBack = requestedReturn === "next"
2711
+ ? "/next"
2712
+ : requestedReturn === null
2713
+ ? taskHref(taskId)
2714
+ : safeChatReturn(requestedReturn);
2715
+ const approvalProblem = (message, status) => {
2716
+ if (requestedReturn !== null && requestedReturn !== "next") {
2717
+ return redirect(response, chatReturnWithSaid(approvalBack, message));
2718
+ }
2719
+ return taskScreen(response, who, taskId, message, status);
2720
+ };
2721
+ const approvingRef = store.lookupRef(taskId);
2722
+ if (approvingRef?.plan === "requested") {
2723
+ return approvalProblem("approval is blocked while planning is in progress — review the drafted plan first", 409);
2724
+ }
2725
+ // Step-up: the session got you here; only the token agrees. The
2726
+ // digest names what was seen; the nonce proves this exact form was
2727
+ // rendered to this approver and is spent either way.
2728
+ const digest = body.get("digest") ?? "";
2729
+ const token = body.get("token") ?? "";
2730
+ const nonce = body.get("nonce") ?? "";
2731
+ if (!consumeApprovalNonce(nonce, who.name, taskId, digest)) {
2732
+ return approvalProblem("that approval form is stale — read it again", 409);
2733
+ }
2734
+ if (token === "" && !hasFreshIdentitySignIn(who.name)) {
2735
+ return approvalProblem("approval requires your password, typed again", 400);
2736
+ }
2737
+ const scopeRow = store.getScope(taskId);
2738
+ if (scopeRow === null)
2739
+ return approvalProblem("this task has no scope to approve", 409);
2740
+ const raceTerms = store.activeTournamentTerms(ref.id);
2741
+ const planView = planViewOf(ref.id);
2742
+ const expectedDigest = approvalFormDigest(scopeRow.digest, raceTerms?.raceDigest ?? null, planView?.sha256 ?? null);
2743
+ if (digest !== expectedDigest) {
2744
+ return approvalProblem("the scope or plan changed while this form was open — read the latest version and approve again", 409);
2745
+ }
2746
+ // A revision approves ONLY against a brief that still verifies
2747
+ // (Codex M5-M8 audit, IV-3): the batch the screen restated must be
2748
+ // provably the batch on disk at the moment of the yes — a brief
2749
+ // deleted or corrupted between render and click blocks the
2750
+ // approval instead of silently approving comment-free work.
2751
+ if (approvingRef !== null && approvingRef.revisionBriefArtifact !== null) {
2752
+ const view = revisionViewOf(approvingRef);
2753
+ if (view !== null && "problem" in view) {
2754
+ return approvalProblem(`approval is blocked: ${view.problem}`, 409);
2755
+ }
2756
+ }
2757
+ // A tournament task's yes covers BOTH documents (finding 31): the
2758
+ // form bound the joint fingerprint, and the scope and race terms
2759
+ // approve together, in one transaction, or not at all.
2760
+ if (raceTerms !== null) {
2761
+ const both = store.transact(() => {
2762
+ const scopeApproved = approveScope(store, taskId, who.name, now, scopeRow.digest, token);
2763
+ if (!scopeApproved.ok)
2764
+ return scopeApproved;
2765
+ if (!store.approveTournamentTerms(raceTerms.id, who.name, raceTerms.raceDigest, now)) {
2766
+ throw new Error("the race terms changed while you were reading — nothing was approved");
2767
+ }
2768
+ return scopeApproved;
2769
+ });
2770
+ if (!both.ok) {
2771
+ if (both.reason === "second-approver")
2772
+ return approvalProblem(gateWords({ verdict: "vote", have: both.have, need: 2, already: both.already }), 200);
2773
+ if (both.reason === "policy")
2774
+ return approvalProblem(both.message, 403);
2775
+ const status = both.reason === "changed" || both.reason === "unrouted" ? 409 : 403;
2776
+ return approvalProblem(approveRefusalWords(both.reason), status);
2777
+ }
2778
+ return redirect(response, approvalBack);
2779
+ }
2780
+ const approved = approveScope(store, taskId, who.name, now, scopeRow.digest, token);
2781
+ if (!approved.ok) {
2782
+ // v102: a first yes on protected work is recorded, not refused — the page says who else is needed.
2783
+ if (approved.reason === "second-approver")
2784
+ return approvalProblem(gateWords({ verdict: "vote", have: approved.have, need: 2, already: approved.already }), 200);
2785
+ if (approved.reason === "policy")
2786
+ return approvalProblem(approved.message, 403);
2787
+ const status = approved.reason === "changed" || approved.reason === "unrouted" ? 409 : 403;
2788
+ return approvalProblem(approveRefusalWords(approved.reason), status);
2789
+ }
2790
+ return redirect(response, approvalBack);
2791
+ }
2792
+ case "accept-proof": {
2793
+ const body = readForm(posted, CONSOLE_FORMS.taskAcceptProof);
2794
+ // Accepting is a person's act, like approving a scope (Priority
2795
+ // 2): a cookie session only, never a bearer credential.
2796
+ if (who.via !== "cookie") {
2797
+ return refuse(response, who, 403, "accepting a proof is a browser session's act");
2798
+ }
2799
+ // v40 fix: a reviewer run is never the attempt whose proof is
2800
+ // being accepted.
2801
+ const latest = store.runsFor(ref.id).find(runIsTaskResult);
2802
+ if (latest === undefined) {
2803
+ return taskScreen(response, who, taskId, "this task has no finished attempt to accept", 404);
2804
+ }
2805
+ const family = store.taskFamilyOf(taskId, admissionList(), false);
2806
+ if (Number(body.get("run")) !== latest.id || family?.current.id !== taskId || family.problem !== null) {
2807
+ return taskScreen(response, who, taskId, "This result changed. Review the current result before accepting.", 409);
2808
+ }
2809
+ const rawNote = (body.get("note") ?? "").trim();
2810
+ let note = null;
2811
+ if (rawNote !== "") {
2812
+ const validated = validateNote(rawNote);
2813
+ if (!validated.ok) {
2814
+ return taskScreen(response, who, taskId, validated.problem, 400);
2815
+ }
2816
+ note = validated.note;
2817
+ }
2818
+ // A failed task's result is accepted only anyway, on purpose: with a reason.
2819
+ if (note === null && store.getTask(taskId)?.state === "failed") {
2820
+ // Posted from the result page: back there, where the reason field is, with the refusal said on it.
2821
+ const back = body.get("return") === null ? null : safeReturn(body.get("return"));
2822
+ if (back !== null && back.startsWith("/review?"))
2823
+ return redirect(response, withRefusal(back, "reason"));
2824
+ return taskScreen(response, who, taskId, ACCEPT_ANYWAY_NEEDS_REASON, 400);
2825
+ }
2826
+ store.acceptProof(latest.id, verifiedAuthor(who.name), note, now);
2827
+ return redirect(response, body.get("return") === null ? taskHref(taskId) : safeReturn(body.get("return")));
2828
+ }
2829
+ case "complete": {
2830
+ const body = readForm(posted, CONSOLE_FORMS.taskComplete);
2831
+ if (who.via !== "cookie" || who.role !== "approver")
2832
+ return refuse(response, who, 403, "Only an approver can mark a result complete.");
2833
+ const principal = matePrincipal(who);
2834
+ if (principal === null)
2835
+ return refuse(response, who, 403, "Your access changed. Sign in again.");
2836
+ const digest = body.get("receipt") ?? "";
2837
+ const namedRun = body.get("run") ?? "";
2838
+ const current = assignmentOf(store, taskId, now, { principal: "operator", repos: principal.repos }, evidenceRoot);
2839
+ if (current === null || current.receipt?.taskId !== taskId || !/^[0-9]{1,15}$/.test(namedRun) || current.receipt.runId !== Number(namedRun)) {
2840
+ return taskScreen(response, who, taskId, "This result changed. Open the current result before marking it complete.", 409);
2841
+ }
2842
+ if (body.get("accept") === "1") {
2843
+ // Accept and finish: the person's acceptance and the completion of what it accepted, in one request
2844
+ // and one transaction. The receipt named is the one read, before accepting.
2845
+ const rawNote = (body.get("note") ?? "").trim();
2846
+ let note = null;
2847
+ if (rawNote !== "") {
2848
+ const validated = validateNote(rawNote);
2849
+ if (!validated.ok)
2850
+ return taskScreen(response, who, taskId, validated.problem, 400);
2851
+ note = validated.note;
2852
+ }
2853
+ const publish = body.get("publish") === "1";
2854
+ const finished = acceptAndCompleteAsOperator(store, taskId, { runId: Number(namedRun), receiptDigest: digest, note }, principal, now, evidenceRoot, publish ? accepted => completeAndOpenPullRequest(store, { taskId, digest: accepted, runId: Number(namedRun), who: principal, root: evidenceRoot }, now) : undefined);
2855
+ if (!finished.ok)
2856
+ return taskScreen(response, who, taskId, finished.message, finished.reason === "needs-reason" ? 400 : 409);
2857
+ return redirect(response, publish ? `${taskHref(familyOf(taskId)?.root.id ?? taskId)}#merge` : body.get("return") === null ? reviewHref(taskId, Number(namedRun)) : safeReturn(body.get("return")));
2858
+ }
2859
+ if (body.get("publish") === "1") {
2860
+ // "Complete and open a pull request": the same exact-receipt completion, plus the owed PR for this
2861
+ // exact commit in one transaction. The watch process pushes and opens it; the task shows its progress.
2862
+ const opened = completeAndOpenPullRequest(store, { taskId, digest, runId: Number(namedRun), who: principal, root: evidenceRoot }, now);
2863
+ if (!opened.ok)
2864
+ return taskScreen(response, who, taskId, opened.message, 409);
2865
+ return redirect(response, `${taskHref(familyOf(taskId)?.root.id ?? taskId)}#merge`);
2866
+ }
2867
+ const completed = checkAssignmentAsOperator(store, taskId, digest, principal, now, evidenceRoot);
2868
+ if (!completed.ok)
2869
+ return taskScreen(response, who, taskId, completed.message, 409);
2870
+ return redirect(response, reviewHref(taskId, Number(namedRun)));
2871
+ }
2872
+ case "retry-review": {
2873
+ return taskScreen(response, who, taskId, "Separate agent reviews have retired. Open the saved result to mark it complete or request a revision.", 410);
2874
+ }
2875
+ case "stop": {
2876
+ const body = readForm(posted, CONSOLE_FORMS.taskStop);
2877
+ // The exact-run stop (v52): a browser session's act, an approver's
2878
+ // act, naming ONE run. The store's transaction proves the run is
2879
+ // this task's current live attempt and records who asked before
2880
+ // any process is signalled; the answer is "stopping", never
2881
+ // "stopped". A repeated post is the same request; a stale one (a
2882
+ // finished run, a successor) is refused in words.
2883
+ if (who.via !== "cookie")
2884
+ return refuse(response, who, 403, "stopping a task is a browser session's act");
2885
+ if (who.role !== "approver")
2886
+ return taskScreen(response, who, taskId, "your login can watch — stopping an attempt is an approver's act", 403);
2887
+ if (store.isDemo())
2888
+ return taskScreen(response, who, taskId, "the demo authorizes nothing", 403);
2889
+ const named = (body.get("run") ?? "").trim();
2890
+ if (!/^[0-9]{1,15}$/.test(named))
2891
+ return taskScreen(response, who, taskId, "which attempt? the stop names one exact run", 400);
2892
+ const asked = requestTaskStop(store, { taskId, runId: Number(named), by: verifiedAuthor(who.name), via: "web", held: options.attended?.coordinator }, now);
2893
+ if (!asked.ok) {
2894
+ const words = {
2895
+ "no-run": `there is no run #${escape(named)}`,
2896
+ "wrong-task": `run #${escape(named)} is not one of this task's attempts`,
2897
+ finished: `run #${escape(named)} already ended before the stop — nothing rewrites a finished attempt; reload to see the current state`,
2898
+ "not-live": `run #${escape(named)} is not the attempt holding this task now — reload and decide against the current attempt`,
2899
+ tournament: "this run is a racing lane — stop the tournament through its own pick or abandon controls",
2900
+ publication: `run #${escape(named)} already admitted its publication — an external request in flight is not recalled by a stop`,
2901
+ };
2902
+ return taskScreen(response, who, taskId, words[asked.reason] ?? asked.detail, 409);
2903
+ }
2904
+ const back = body.get("return") ?? "";
2905
+ return redirect(response, back === "chat" ? `${taskChatHref(taskId)}#task-control` : `${taskHref(taskId)}#task-control`);
2906
+ }
2907
+ case "resume-arm": {
2908
+ const body = readForm(posted, CONSOLE_FORMS.taskResume);
2909
+ return armTaskResume(response, who, taskId, (body.get("run") ?? "").trim(), body.get("return") === "chat" ? "chat" : "task", now);
2910
+ }
2911
+ case "resume": {
2912
+ const body = readForm(posted, CONSOLE_FORMS.taskResume);
2913
+ // The resume itself (v52): password typed again + the durable nonce
2914
+ // consumed INSIDE the store's resume transaction against the digest
2915
+ // re-derived from live state. A replay finds the nonce spent; a
2916
+ // changed approval finds the digest moved; a successor attempt finds
2917
+ // the run superseded — each refuses without starting work; a submitted nonce remains one-shot.
2918
+ if (who.via !== "cookie")
2919
+ return refuse(response, who, 403, "resuming a task is a browser session's act");
2920
+ if (who.role !== "approver")
2921
+ return taskScreen(response, who, taskId, "your login can watch — resuming an attempt is an approver's act", 403);
2922
+ if (store.isDemo())
2923
+ return taskScreen(response, who, taskId, "the demo authorizes nothing", 403);
2924
+ const named = (body.get("run") ?? "").trim();
2925
+ if (!/^[0-9]{1,15}$/.test(named))
2926
+ return taskScreen(response, who, taskId, "which attempt? the resume names the exact stopped run", 400);
2927
+ const runId = Number(named);
2928
+ const token = body.get("token") ?? "";
2929
+ if (!authenticateApprover(who, token).ok) {
2930
+ return taskScreen(response, who, taskId, "resuming takes your password, typed again", 403);
2931
+ }
2932
+ const nonceValue = body.get("nonce") ?? "";
2933
+ if (nonceValue === "")
2934
+ return taskScreen(response, who, taskId, "that confirmation form was incomplete — start again", 400);
2935
+ const resumed = store.transact(() => {
2936
+ const stop = store.stopOf(runId);
2937
+ if (stop === null || stop.taskRef !== ref.id)
2938
+ return { ok: false, reason: "no-stop", detail: `run #${runId} is not a stopped attempt of this task` };
2939
+ const digest = resumeDigestOf(taskId, runId, stop.settledAt ?? "", store.getScope(taskId));
2940
+ if (!store.consumeCeremonyNonce(nonceHashOf(nonceValue), who.name, "run-resume", runId, digest, now)) {
2941
+ return { ok: false, reason: "stale", detail: "that confirmation is stale, already used, or the approval changed since you read it — start again from the task" };
2942
+ }
2943
+ return resumeTaskStop(store, { taskId, runId, by: verifiedAuthor(who.name), via: "web" }, now);
2944
+ });
2945
+ if (!resumed.ok) {
2946
+ const words = {
2947
+ stale: resumed.detail,
2948
+ "no-stop": resumed.detail,
2949
+ stopping: `not yet — ${resumed.detail}`,
2950
+ occupied: `not yet — ${resumed.detail}`,
2951
+ "already-resumed": resumed.detail,
2952
+ superseded: resumed.detail,
2953
+ busy: resumed.detail,
2954
+ review: `run #${runId} is a review — use Review again, which keeps its bounded attempts`,
2955
+ };
2956
+ return taskScreen(response, who, taskId, words[resumed.reason] ?? resumed.detail, 409);
2957
+ }
2958
+ const back = body.get("return") ?? "";
2959
+ return redirect(response, back === "chat" ? `${taskChatHref(taskId)}#task-control` : `${taskHref(taskId)}#task-control`);
2960
+ }
2961
+ default:
2962
+ return refuse(response, who, 404, "That action isn't available here.", taskHref(taskId));
2963
+ }
2964
+ }
2965
+ /** The queue as a region: dispatch order per column, drag handles and
2966
+ * all — the /queue page's body, and the board's "order" view (the one
2967
+ * place dragging exists, because reordering and reserving are scheduling,
2968
+ * never authority). */
2969
+ function queueRegionFor(project, csrf, revision, now) {
2970
+ const tasks = store.queueScoped(project, now).map(one => ({
2971
+ ...one,
2972
+ dispatch: diagnoseTaskDispatch(store, one.id, now),
2973
+ }));
2974
+ const owned = new Set(tasks.map(one => one.assignedRunner).filter((one) => one !== null));
2975
+ const building = new Map();
2976
+ for (const claim of store.liveClaims(project, now)) {
2977
+ building.set(claim.runner, (building.get(claim.runner) ?? 0) + 1);
2978
+ }
2979
+ const workers = store
2980
+ .listRunners()
2981
+ .filter(one => one.retiredAt === null || owned.has(one.name))
2982
+ .map(one => ({
2983
+ name: one.name,
2984
+ retired: one.retiredAt !== null,
2985
+ note: one.queueNote ?? null,
2986
+ capacity: one.capacity,
2987
+ building: building.get(one.name) ?? 0,
2988
+ }));
2989
+ return queueBody(tasks, workers, csrf, revision, store.queueRevision());
2990
+ }
2991
+ /** An OBSERVED CI failure on a completed row's PR — the open episode the
2992
+ * watcher recorded for exactly that repository and number; never a guess
2993
+ * from quiet. One indexed publication read per row that has a PR. */
2994
+ function ciFailingFor(runId, prNumber) {
2995
+ if (runId === null || prNumber === null)
2996
+ return false;
2997
+ const publication = store.publicationForRun(runId);
2998
+ return publication !== null && publication.prNumber === prNumber && store.hasOpenCiEpisode(publication.githubRepo, prNumber);
2999
+ }
3000
+ /**
3001
+ * One completed row by task id, for a `?result=` deep link the bounded
3002
+ * queue could not show — the done list's own predicate, re-proved per
3003
+ * read: the task is done, sits in this project (or in none), and its
3004
+ * repository passes the ceiling; the result run is the newest finished
3005
+ * built/no-change builder or scout attempt. Anything else is
3006
+ * null, and null reads exactly like a task that never existed.
3007
+ */
3008
+ function completedRowFor(taskId, project) {
3009
+ const family = familyOf(taskId);
3010
+ if (family === null)
3011
+ return null;
3012
+ const task = store.getTask(taskId);
3013
+ const ref = store.lookupRef(taskId);
3014
+ if (task === null || ref === null || task.state !== "done")
3015
+ return null;
3016
+ if (!(project === null || ref.repo === null || ref.repo === project) || !visible(ref.repo))
3017
+ return null;
3018
+ const run = store.runsFor(ref.id).find(one => (one.outcome === "built" || one.outcome === "no-change") &&
3019
+ one.finishedAt !== null &&
3020
+ (one.role === "builder" || one.role === "scout")) ?? null;
3021
+ return resultRowOf(task, ref.repo, family.problem, run);
3022
+ }
3023
+ /**
3024
+ * A build attempt that didn't deliver a result (failed, refused, stopped,
3025
+ * never finished) still has a result page: its changes, its checks and what
3026
+ * went wrong. The row for one: the run named by `runId` when it is an ended
3027
+ * builder or scout attempt of this task that isn't a delivered result, or a
3028
+ * failed task's latest attempt; with no run named, that latest attempt. The
3029
+ * same admission as a completed row: in this project (or none) and inside
3030
+ * the ceiling. Null otherwise, which reads like a task that never existed.
3031
+ */
3032
+ function attemptRowFor(taskId, runId, project) {
3033
+ const family = familyOf(taskId);
3034
+ const task = store.getTask(taskId);
3035
+ const ref = store.lookupRef(taskId);
3036
+ if (family === null || task === null || ref === null)
3037
+ return null;
3038
+ if (!(project === null || ref.repo === null || ref.repo === project) || !visible(ref.repo))
3039
+ return null;
3040
+ const attempts = store.runsFor(ref.id).filter(one => (one.role === "builder" || one.role === "scout") && (one.finishedAt !== null || one.outcome !== null));
3041
+ const latest = task.state === "failed" ? latestFinishedAttempt(attempts) : null;
3042
+ const run = runId !== null ? attempts.find(one => one.id === runId) ?? null : latest;
3043
+ // A delivered result has its completed row; a failed task's latest attempt is what failed, whatever it ended as.
3044
+ if (run === null || runIsLive(run) || ((run.outcome === "built" || run.outcome === "no-change") && run.id !== latest?.id))
3045
+ return null;
3046
+ return resultRowOf(task, ref.repo, family.problem, run);
3047
+ }
3048
+ function resultRowOf(task, repo, historyProblem, run) {
3049
+ const ref = { repo };
3050
+ const family = { problem: historyProblem };
3051
+ const publication = run === null ? null : store.publicationForRun(run.id);
3052
+ const verdict = run === null ? null : store.proofVerdictFor(run.id);
3053
+ return {
3054
+ taskId: task.id,
3055
+ title: task.title,
3056
+ repo: ref.repo,
3057
+ completedAt: task.updatedAt,
3058
+ historyProblem: family.problem,
3059
+ outcome: run?.outcome ?? null,
3060
+ handoff: run?.handoff ?? null,
3061
+ costUsd: run?.costUsd ?? null,
3062
+ provider: run?.provider ?? null,
3063
+ authMode: run?.authMode === "subscription" || run?.authMode === "api-key" ? run.authMode : null,
3064
+ ranMinutes: run === null || run.finishedAt === null
3065
+ ? null
3066
+ : Math.max(1, Math.round((new Date(run.finishedAt).getTime() - new Date(run.startedAt).getTime()) / 60_000)),
3067
+ prNumber: publication?.prNumber ?? null,
3068
+ prUrl: publication?.prUrl ?? null,
3069
+ publicationState: publication?.state ?? null,
3070
+ runId: run?.id ?? null,
3071
+ proofVerdict: verdict?.verdict ?? null,
3072
+ proofMatrix: verdict?.matrix ?? [],
3073
+ proofAccepted: run !== null && store.proofAcceptance(run.id) !== null,
3074
+ };
3075
+ }
3076
+ function reviewCockpitViewOf(row, who, now) {
3077
+ const ref = store.lookupRef(row.taskId);
3078
+ const scope = store.getScope(row.taskId);
3079
+ // The result run, read once: the contest road and the shared result
3080
+ // detail below both hang off this one record.
3081
+ const run = row.runId === null ? null : store.getRun(row.runId);
3082
+ const intent = scope === null
3083
+ ? null
3084
+ : {
3085
+ goal: scope.goal,
3086
+ outOfScope: scope.outOfScope,
3087
+ touches: scope.touches,
3088
+ acceptance: scope.acceptance,
3089
+ approval: approvalOf(scope),
3090
+ approvedBy: scope.approvedBy,
3091
+ };
3092
+ const plan = (() => {
3093
+ if (ref === null)
3094
+ return null;
3095
+ const current = revisionLedgerOf(ref.id).current;
3096
+ if (current === null)
3097
+ return null;
3098
+ const parsed = parseExecutionPlanDocument(current.document);
3099
+ return {
3100
+ revision: current.revision,
3101
+ sha256: current.sha256,
3102
+ approach: parsed.ok ? oneLineOf(parsed.document.approach, 600) : null,
3103
+ };
3104
+ })();
3105
+ const contestOf = () => {
3106
+ // Either road to a tournament: the result run was one of its
3107
+ // contestants, or the task still owes an operator a comparison.
3108
+ const viaRun = run?.contestant === null || run?.contestant === undefined ? null : store.getContestant(run.contestant);
3109
+ const contest = viaRun === null ? (ref === null ? null : store.contestNeedingOperator(ref.id)) : store.getContest(viaRun.contest);
3110
+ return contest === null ? null : { id: contest.id, state: contest.state, kind: contest.kind, agents: store.contestants(contest.id).length };
3111
+ };
3112
+ const base = {
3113
+ taskId: row.taskId,
3114
+ title: row.title,
3115
+ repo: row.repo,
3116
+ completedAt: row.completedAt,
3117
+ historyProblem: row.historyProblem ?? null,
3118
+ priority: row.priority,
3119
+ assignment: row.assignment ?? null,
3120
+ intent,
3121
+ plan,
3122
+ contest: contestOf(),
3123
+ ...(store.isDemo() ? { demo: true } : {}),
3124
+ };
3125
+ if (run === null) {
3126
+ return { ...base, run: null, detail: null, reviewRetry: null, review: null, notes: [] };
3127
+ }
3128
+ const detail = resultDetailOf(run, who, now);
3129
+ return {
3130
+ ...base,
3131
+ run: {
3132
+ id: run.id,
3133
+ role: run.role,
3134
+ outcome: run.outcome,
3135
+ runner: run.runner,
3136
+ provider: run.provider,
3137
+ model: run.model,
3138
+ finishedAt: run.finishedAt,
3139
+ branch: run.branch,
3140
+ headRevision: run.headRevision,
3141
+ ranMinutes: row.ranMinutes,
3142
+ cost: runCostWords(run, false),
3143
+ summary: detail.handoff?.conclusion ?? run.handoff,
3144
+ },
3145
+ detail,
3146
+ reviewRetry: store.reviewRetryStateOf(run.id),
3147
+ review: reviewFactsFor(run.id),
3148
+ notes: store.notesForRun(run.id),
3149
+ };
3150
+ }
3151
+ // ---- evidence ------------------------------------------------------------
3152
+ function decisionEvidence(response, decisionId, artifactId) {
3153
+ // The ceiling applies to decision evidence exactly as to run evidence
3154
+ // (v2 review, finding 2): a decision whose task belongs to a repo this
3155
+ // server may not serve does not exist here, and neither do its bytes.
3156
+ const decision = store.getDecision(decisionId);
3157
+ const decisionRun = decision === null ? null : store.getRun(decision.run);
3158
+ if (decisionRun !== null && !visible(taskRepoOf(decisionRun.taskRef))) {
3159
+ return respond(response, 404, "text/plain; charset=utf-8", "no such evidence");
3160
+ }
3161
+ // Only through the decision's own relation — an artifact id from another
3162
+ // run simply is not in this list, whatever the URL claims.
3163
+ const linked = store.evidenceFor(decisionId).find(one => one.id === artifactId);
3164
+ if (linked === undefined) {
3165
+ return respond(response, 404, "text/plain; charset=utf-8", "no such evidence");
3166
+ }
3167
+ return sendArtifact(response, linked);
3168
+ }
3169
+ function runEvidence(response, runId, artifactId) {
3170
+ const run = store.getRun(runId);
3171
+ if (run === null || !runVisible(run)) {
3172
+ return respond(response, 404, "text/plain; charset=utf-8", "no such run");
3173
+ }
3174
+ // Membership is the lookup's own predicate — there is no way to check
3175
+ // the run and fetch the artifact as two separate acts here.
3176
+ const linked = store.artifactForRun(runId, artifactId);
3177
+ if (linked === null) {
3178
+ return respond(response, 404, "text/plain; charset=utf-8", "no such evidence");
3179
+ }
3180
+ return sendArtifact(response, linked);
3181
+ }
3182
+ function sendArtifact(response, linked) {
3183
+ const read = readVerifiedArtifact(evidenceRoot, linked);
3184
+ if (!read.ok) {
3185
+ return respond(response, 410, "text/plain; charset=utf-8", "the evidence no longer matches its record");
3186
+ }
3187
+ // A validated screenshot is the one evidence kind meant to render
3188
+ // inline (thumbnails, full-image links) — every other kind stays a
3189
+ // downloaded text record, exactly as before.
3190
+ if (linked.kind === "screenshot") {
3191
+ const imageType = linked.key.endsWith(".png") ? "image/png" : linked.key.endsWith(".jpg") ? "image/jpeg" : null;
3192
+ if (imageType !== null) {
3193
+ response.writeHead(200, { ...SAFETY, "Content-Type": imageType, "Cache-Control": "private, max-age=31536000, immutable" });
3194
+ response.end(read.content);
3195
+ return;
3196
+ }
3197
+ }
3198
+ response.writeHead(200, {
3199
+ ...SAFETY,
3200
+ "Content-Type": "text/plain; charset=utf-8",
3201
+ "Content-Disposition": `attachment; filename="evidence-${linked.id}.txt"`,
3202
+ });
3203
+ response.end(read.content);
3204
+ }
3205
+ /** The routine screen: the standing order restated, its verbs, its ledger. */
3206
+ function routinePage(response, who, routineId, problem, status) {
3207
+ const routine = store.getRoutine(routineId);
3208
+ if (routine === null || !visible(routine.repo)) {
3209
+ return refuse(response, who, 404, "no such routine", "/routines");
3210
+ }
3211
+ // Same rule as scope approval: the nonce exists only where the exact
3212
+ // terms are restated, bound to who saw which digest of which order —
3213
+ // and only where a yes could bind them (v48): an order whose agents
3214
+ // are unresolved, unreadable, or never frozen gets the recovery road,
3215
+ // never a password field.
3216
+ const nonce = who.via === "cookie" && routineAgentsState(routine).approvable
3217
+ ? mintApprovalNonce(who.name, `routine:${routine.id}`, routine.digest)
3218
+ : "";
3219
+ const paneProject = restricted() ? routine.repo : who.via === "cookie" ? who.session.project : null;
3220
+ return sendScreen(response, status, routineScreenPage(chromeFor(paneProject, "routines"), {
3221
+ routine,
3222
+ fires: store.routineFires(routine.id, 14),
3223
+ spend: store.routineSpend(routine.id, new Date(clock().getTime() - 7 * 24 * 60 * 60_000).toISOString()),
3224
+ blocker: store.routineBlocker(routine.id, clock()),
3225
+ csrf: who.via === "cookie" ? who.session.csrf : "",
3226
+ nonce,
3227
+ problem,
3228
+ now: clock(),
3229
+ }));
3230
+ }
3231
+ /** The full guard list (v3 §4) — run on every request, hit or miss. */
3232
+ function peekGuards(runId) {
3233
+ if (options.localRunner === undefined || options.poolRoot === undefined) {
3234
+ return { ok: false, message: "live peek is off — start serve with --runner <name> naming this machine's runner", final: true };
3235
+ }
3236
+ const run = store.getRun(runId);
3237
+ if (run === null || !runVisible(run))
3238
+ return { ok: false, message: "no such build", final: true };
3239
+ if (run.outcome !== null)
3240
+ return { ok: false, message: "this build has finished — the final diff below is the record", final: true };
3241
+ // The reviewer role (v29): artifact-only, honestly — there is no
3242
+ // checkout anywhere to watch, and there never was.
3243
+ if (run.worktree === null) {
3244
+ return { ok: false, message: "this run reviewed the sealed diff — no workspace existed to watch", final: true };
3245
+ }
3246
+ if (run.baseRevision === null)
3247
+ return { ok: false, message: "the build has not settled its starting point yet" };
3248
+ // The run's lease must be the task's CURRENT live lease — max generation,
3249
+ // unreleased, strictly unexpired. liveClaimByLease proves only that the
3250
+ // lease exists; a superseded lease would still pass it (round-4
3251
+ // finding 15), and superseded is forever, so the poller may stop.
3252
+ if (store.currentLiveLease(run.taskRef, clock()) !== run.leaseId) {
3253
+ return { ok: false, message: "the build is not actively running right now", final: true };
3254
+ }
3255
+ const row = store.getWorktree(run.worktree);
3256
+ if (row === null || row.taskRef !== run.taskRef)
3257
+ return { ok: false, message: "the checkout is not where the record says" };
3258
+ if (row.runner !== options.localRunner) {
3259
+ return { ok: false, message: "this build runs on another machine — open the console there to watch it", final: true };
3260
+ }
3261
+ // Adoption-path rows can carry no epoch (round-3 finding 37): no fence,
3262
+ // no peek — never a guess.
3263
+ if (row.leaseEpoch === null || row.leaseEpoch === undefined) {
3264
+ return { ok: false, message: "this checkout was set up before live watching existed — the next fresh build can be watched", final: true };
3265
+ }
3266
+ try {
3267
+ const real = realpathSync(run.worktree);
3268
+ const pool = realpathSync(options.poolRoot);
3269
+ if (real !== run.worktree || !(real === pool || real.startsWith(`${pool}/`)) || !lstatSync(run.worktree).isDirectory()) {
3270
+ return { ok: false, message: "the checkout is not inside this machine's pool" };
3271
+ }
3272
+ }
3273
+ catch {
3274
+ return { ok: false, message: "the checkout could not be found on this machine" };
3275
+ }
3276
+ // The snapshot: exactly one successful, untruncated base-tree artifact
3277
+ // whose envelope binds THIS run and THIS base (round-3 finding 42).
3278
+ const artifact = store.artifactsFor(runId).find(one => one.kind === "base-tree");
3279
+ if (artifact === undefined)
3280
+ return { ok: false, message: "no base snapshot was captured for this build" };
3281
+ if (artifact.captureStatus !== "ok" || artifact.truncated) {
3282
+ return { ok: false, message: "the base snapshot did not capture cleanly — this build cannot be watched live" };
3283
+ }
3284
+ const read = readVerifiedArtifact(evidenceRoot, artifact);
3285
+ if (!read.ok)
3286
+ return { ok: false, message: "the base snapshot no longer verifies" };
3287
+ const snapshot = parseBaseTreeSnapshot(read.content.toString("utf8"));
3288
+ if (snapshot === null || snapshot.run !== runId || snapshot.base !== run.baseRevision) {
3289
+ return { ok: false, message: "the base snapshot does not match this build" };
3290
+ }
3291
+ return { ok: true, admit: { run, worktree: run.worktree, epoch: row.leaseEpoch, entries: snapshot } };
3292
+ }
3293
+ /**
3294
+ * The tournament's comparison data: the pick view plus everything the
3295
+ * page states — the task behind it, per-agent question counts, and the
3296
+ * money totals. The ceiling is proved here, on the server-resolved repo,
3297
+ * whatever the request named.
3298
+ */
3299
+ function contestData(contestId) {
3300
+ const view = buildPickView(store, evidenceRoot, contestId);
3301
+ if (view === null)
3302
+ return null;
3303
+ const ref = store.refForId(view.contest.taskRef);
3304
+ if (ref === null || (ref.repo !== null && !visible(ref.repo)))
3305
+ return null;
3306
+ const found = store.getTask(ref.externalId);
3307
+ if (found === null)
3308
+ return null;
3309
+ // Question counts follow run lineage: every run belonging to the agent,
3310
+ // not just its final one — a parked question is part of its story.
3311
+ const byRun = new Map();
3312
+ for (const one of store.runsFor(view.contest.taskRef)) {
3313
+ if (one.contestant !== null)
3314
+ byRun.set(one.id, one.contestant);
3315
+ }
3316
+ const questions = new Map();
3317
+ for (const decision of store.decisionsForTask(view.contest.taskRef)) {
3318
+ const owner = byRun.get(decision.run);
3319
+ if (owner !== undefined)
3320
+ questions.set(owner, (questions.get(owner) ?? 0) + 1);
3321
+ }
3322
+ return {
3323
+ rollups: new Map(view.agents.map(agent => [agent.contestant.id, store.contestantSpendRollup(agent.contestant.id)])),
3324
+ view,
3325
+ taskId: ref.externalId,
3326
+ taskTitle: found.title,
3327
+ repo: ref.repo,
3328
+ refOrigin: ref.origin,
3329
+ questions,
3330
+ // An interrupted tournament's agents are STOPPED, not "still
3331
+ // working" — their run records stay unfinished, so the card must
3332
+ // prove liveness the same way every other surface does (round-4
3333
+ // finding 16).
3334
+ liveRuns: liveRunIds(view.agents.flatMap(agent => (agent.run === null ? [] : [agent.run]))),
3335
+ totalMicrousd: view.agents.reduce((sum, agent) => sum + agent.contestant.accountedMicrousd, 0),
3336
+ anyUnknown: view.agents.some(agent => agent.contestant.unknownSpend),
3337
+ };
3338
+ }
3339
+ /**
3340
+ * Assemble the LIVE rendered terms of one watched attempt, or say in
3341
+ * words why there are none (Phase 2E, ruling 12). Everything here is
3342
+ * re-read at confirm time — the digest is the proof the world held.
3343
+ */
3344
+ async function liveAttendedTerms(taskId, inputs, now) {
3345
+ if (options.attended === undefined)
3346
+ return { ok: false, problem: "this console cannot hold a session — start it with `toolroll up`" };
3347
+ const ref = store.lookupRef(taskId);
3348
+ if (ref === null)
3349
+ return { ok: false, problem: "no such task" };
3350
+ const scope = store.getScope(taskId);
3351
+ if (scope === null)
3352
+ return { ok: false, problem: "file a scope first — the terms come from it" };
3353
+ // CONTINUATION (A4): the parent attempt and the follow-up enter the
3354
+ // SIGNED terms; the head is the parent's accepted commit per outcome;
3355
+ // a moving publication blocks; failed parents are refused outright for
3356
+ // now (a dirty preserved tree cannot promise a clean continuation) —
3357
+ // stated, not hidden.
3358
+ let continuation = null;
3359
+ if (inputs.parent !== undefined) {
3360
+ const parent = store.getRun(inputs.parent);
3361
+ if (parent === null || parent.taskRef !== ref.id)
3362
+ return { ok: false, problem: "no such finished attempt on this task" };
3363
+ const followup = (inputs.followup ?? "").trim();
3364
+ if (followup === "" || followup.length > 2000) {
3365
+ return { ok: false, problem: "say what to do next — 1 to 2000 characters" };
3366
+ }
3367
+ if (parent.outcome !== "built" && parent.outcome !== "no-change") {
3368
+ return { ok: false, problem: "only a built or no-change attempt can be continued — for a failed one, file a follow-up task" };
3369
+ }
3370
+ const accepted = parent.outcome === "built" ? parent.headRevision : parent.baseRevision;
3371
+ if (accepted === null)
3372
+ return { ok: false, problem: "the attempt's accepted head was never recorded — file a follow-up task" };
3373
+ const blocked = store.continuationBlockOf(parent.id);
3374
+ if (blocked !== null)
3375
+ return { ok: false, problem: blocked };
3376
+ continuation = { parentRun: parent.id, followup, head: accepted };
3377
+ }
3378
+ else if (approvalOf(scope).approved) {
3379
+ return { ok: false, problem: "the scope is approved — it already dispatches unattended" };
3380
+ }
3381
+ let pinned = scope.profileState === "resolved" ? (scope.profile ?? null) : null;
3382
+ if (pinned === null) {
3383
+ return { ok: false, problem: "the scope cannot say exactly what runs — name a model (task scope --model, or config set build)" };
3384
+ }
3385
+ if (pinned.provider !== "claude") {
3386
+ return { ok: false, problem: `${pinned.provider} cannot hold a watched session yet — this road is claude-only for now` };
3387
+ }
3388
+ // THE MINT PICKER (P1/C7): a chosen model or posture rebuilds the
3389
+ // WHOLE ClaudeProfile — the digest signs the profile that actually
3390
+ // runs, never a partial edit. Absent choices keep the scope's pin.
3391
+ const chosenModel = (inputs.model ?? "").trim();
3392
+ const chosenPosture = inputs.posture === "bypassPermissions"
3393
+ ? "bypassPermissions"
3394
+ : inputs.posture === "auto"
3395
+ ? "auto"
3396
+ : inputs.posture === "acceptEdits"
3397
+ ? "acceptEdits"
3398
+ : null;
3399
+ if (chosenModel !== "" || chosenPosture !== null) {
3400
+ pinned = {
3401
+ ...pinned,
3402
+ ...(chosenModel === "" ? {} : { model: chosenModel }),
3403
+ ...(chosenPosture === null ? {} : { permissionArgv: chosenPosture }),
3404
+ };
3405
+ }
3406
+ if (store.activeTournamentTerms(ref.id) !== null) {
3407
+ return { ok: false, problem: "this task races a tournament — one attempt cannot authorize N racers" };
3408
+ }
3409
+ const repo = ref.repo;
3410
+ if (repo === null)
3411
+ return { ok: false, problem: "place the task in a repository first — the terms pin the exact head" };
3412
+ const head = continuation !== null ? continuation.head : await options.attended.headOf(repo);
3413
+ if (head === null)
3414
+ return { ok: false, problem: `the head of ${repo} cannot be read right now` };
3415
+ const minutes = Math.max(5, Math.min(240, Math.floor(inputs.minutes) || 60));
3416
+ const turns = Math.max(1, Math.min(100, Math.floor(inputs.turns) || 20));
3417
+ const budget = Math.max(100_000, Math.min(50_000_000, Math.floor(inputs.budgetMicrousd) || 2_000_000));
3418
+ return {
3419
+ ok: true,
3420
+ terms: {
3421
+ taskId,
3422
+ attentionMode: "console-visible",
3423
+ scopeDigest: scope.digest,
3424
+ profileDigest: profileDigestOf(pinned),
3425
+ profileJson: canonicalProfileJson(pinned),
3426
+ repo,
3427
+ runner: options.attended.runner,
3428
+ runnerGeneration: 0,
3429
+ head,
3430
+ maxSessionTurns: turns,
3431
+ budgetMicrousd: budget,
3432
+ turnTimeoutSeconds: pinned.timeoutSeconds,
3433
+ ...(continuation === null ? {} : { parentRun: continuation.parentRun, followup: continuation.followup }),
3434
+ // The expiry is FIXED at preview and carried through the confirm —
3435
+ // a timestamp recomputed at signing time would change the digest
3436
+ // every millisecond and make the proof unmatchable. The confirm's
3437
+ // bound check keeps a stale form honest.
3438
+ absoluteExpiry: inputs.expiry !== undefined &&
3439
+ Date.parse(inputs.expiry) > now.getTime() &&
3440
+ Date.parse(inputs.expiry) <= now.getTime() + 241 * 60_000
3441
+ ? inputs.expiry
3442
+ : new Date(now.getTime() + minutes * 60_000).toISOString(),
3443
+ },
3444
+ };
3445
+ }
3446
+ /** The signed form, rendered from EXACT terms — what you read is what the password signs. */
3447
+ function attendConfirmScreen(response, who, terms, inputs, digest, nonce, csrf) {
3448
+ const scope = store.getScope(terms.taskId);
3449
+ // Quick mint (M4): the mode signature substitutes for the password —
3450
+ // the confirm screen still shows EVERY term; only the credential line
3451
+ // changes. The mint transaction re-proves the mode either way.
3452
+ const quickRef = store.lookupRef(terms.taskId);
3453
+ const quickMode = quickRef?.repo == null ? null : store.activeMode(quickRef.repo, clock());
3454
+ const quickTerms = quickMode === null ? null : modeTermsFromJson(quickMode.termsJson);
3455
+ const quick = quickTerms?.quickMint === true && quickMode !== null && quickMode.signedBy === who.name;
3456
+ const body = `<h1>Run ${escape(terms.taskId)} once, while you watch</h1>` +
3457
+ `<form method="post" action="${taskHref(terms.taskId)}/attend" class="card approve-form">` +
3458
+ `<input type="hidden" name="csrf" value="${escape(csrf)}">` +
3459
+ `<input type="hidden" name="nonce" value="${escape(nonce)}">` +
3460
+ `<input type="hidden" name="digest" value="${escape(digest)}">` +
3461
+ `<input type="hidden" name="minutes" value="${inputs.minutes}">` +
3462
+ `<input type="hidden" name="turns" value="${inputs.turns}">` +
3463
+ `<input type="hidden" name="budget" value="${inputs.budgetMicrousd}">` +
3464
+ `<input type="hidden" name="expiry" value="${escape(terms.absoluteExpiry)}">` +
3465
+ (inputs.model === undefined ? "" : `<input type="hidden" name="model" value="${escape(inputs.model)}">`) +
3466
+ (inputs.posture === undefined ? "" : `<input type="hidden" name="posture" value="${escape(inputs.posture)}">`) +
3467
+ (terms.parentRun == null ? "" : `<input type="hidden" name="parent" value="${terms.parentRun}">`) +
3468
+ (terms.followup == null ? "" : `<input type="hidden" name="followup" value="${escape(terms.followup)}">`) +
3469
+ `<p><strong>Your password signs exactly this:</strong></p>` +
3470
+ `<p class="meta">Goal</p><p class="recap" style="margin-top:0">${escape(scope?.goal ?? "")}</p>` +
3471
+ `<p class="meta">Not this</p><p class="recap" style="margin-top:0">${scope?.outOfScope == null ? "<em>no exclusions</em>" : escape(scope.outOfScope)}</p>` +
3472
+ `<p class="meta">Touches · ${scope === null || scope.touches.length === 0 ? "anything" : scope.touches.map(one => escape(one)).join(", ")}</p>` +
3473
+ (() => {
3474
+ const profile = JSON.parse(terms.profileJson).profile;
3475
+ const posture = profile?.permissionArgv === "bypassPermissions"
3476
+ ? "FULL permissions — claude runs with --dangerously-skip-permissions; nothing asks"
3477
+ : profile?.permissionArgv === "auto"
3478
+ ? "safe unattended permissions — routine project commands and edits proceed; risky acts stop"
3479
+ : "legacy acceptEdits — edits proceed, commands that ask are denied unattended";
3480
+ return `<p class="meta">Runs on</p><p class="recap" style="margin-top:0">claude · ${escape(String(profile?.model ?? ""))} — ${posture}</p>`;
3481
+ })() +
3482
+ (terms.parentRun == null
3483
+ ? ""
3484
+ : `<p class="meta">Continues</p><p class="recap" style="margin-top:0">attempt <a href="/r/${terms.parentRun}" class="mono">#${terms.parentRun}</a>, from exactly where it finished</p>` +
3485
+ `<p class="meta">The follow-up — this is the instruction</p><p class="recap" style="margin-top:0">${escape(terms.followup ?? "")}</p>`) +
3486
+ `<p class="meta">Repository · head</p><p class="recap mono" style="margin-top:0">${escape(terms.repo)} @ ${escape(terms.head.slice(0, 12))}</p>` +
3487
+ `<p class="meta">Worker</p><p class="recap" style="margin-top:0">${escape(terms.runner)} (this machine)</p>` +
3488
+ `<p class="meta">Spending</p><p class="recap" style="margin-top:0">up to about $${(terms.budgetMicrousd / 1_000_000).toFixed(2)} — the agent stops as soon as its total crosses this; the final step may run a little past it</p>` +
3489
+ `<p class="meta">Conversation</p><p class="recap" style="margin-top:0">at most ${terms.maxSessionTurns} messages to the agent, this whole session; each may work up to ${Math.round(terms.turnTimeoutSeconds / 60)} minutes — one that runs past that ends the whole session. If it needs a repair, the repair uses the same session, model, and clock.</p>` +
3490
+ `<p class="meta">While you watch — a signed term</p><p class="recap" style="margin-top:0">your console being open is what keeps it running — any page of it, this one included; close the console and the session winds down within a minute. Everything ends by ${whenTime(terms.absoluteExpiry)} regardless. One attempt; it never converts into unattended work.</p>` +
3491
+ (quick
3492
+ ? `<p class="meta">Your signed mode covers this mint — no password; the mode is re-proved as you confirm, and the session is stamped with its signature</p>`
3493
+ : `<label>Your password, typed again — a signed-in session alone cannot authorize work<input type="password" name="token" autocomplete="current-password"></label>`) +
3494
+ `<div class="sticky-actions"><button type="submit">Run it while I watch</button></div>` +
3495
+ `</form>` +
3496
+ `<p class="meta"><a href="${taskHref(terms.taskId)}">back to the task</a></p>`;
3497
+ // The chrome binds to the AUTHORITY repository (surfaces round 1,
3498
+ // finding 5): the banner on this screen is the mode that would cover
3499
+ // a quick mint here, never the session's open-project filter.
3500
+ return sendScreen(response, 200, screen(`attend \u00b7 ${terms.taskId}`, body, { chrome: chromeFor(terms.repo, "tasks") }));
3501
+ }
3502
+ /** The live subset of a bounded run page \u2014 one indexed lookup per row. */
3503
+ function liveRunIds(rows) {
3504
+ const live = new Set();
3505
+ for (const run of rows)
3506
+ if (runIsLive(run))
3507
+ live.add(run.id);
3508
+ return live;
3509
+ }
3510
+ const registrations = [
3511
+ { id: "code.page", domain: "tasks", stage: "console", method: "GET", handle: get },
3512
+ { id: "inbox", domain: "tasks", stage: "console", method: "GET", handle: get },
3513
+ { id: "work", domain: "tasks", stage: "console", method: "GET", handle: get },
3514
+ { id: "next", domain: "tasks", stage: "console", method: "GET", handle: get },
3515
+ { id: "board", domain: "tasks", stage: "console", method: "GET", handle: get },
3516
+ { id: "review", domain: "tasks", stage: "console", method: "GET", handle: get },
3517
+ { id: "done", domain: "tasks", stage: "console", method: "GET", handle: get },
3518
+ { id: "tasks", domain: "tasks", stage: "console", method: "GET", handle: get },
3519
+ { id: "queue", domain: "tasks", stage: "console", method: "GET", handle: get },
3520
+ { id: "tasks.new", domain: "tasks", stage: "console", method: "GET", handle: get },
3521
+ { id: "task.live", domain: "tasks", stage: "console", method: "GET", handle: get },
3522
+ { id: "task.page", domain: "tasks", stage: "console", method: "GET", handle: get },
3523
+ { id: "task.evidence", domain: "tasks", stage: "console", method: "GET", handle: get },
3524
+ { id: "runs", domain: "tasks", stage: "console", method: "GET", handle: get },
3525
+ { id: "run.page", domain: "tasks", stage: "console", method: "GET", handle: get },
3526
+ { id: "run.evidence", domain: "tasks", stage: "console", method: "GET", handle: get },
3527
+ { id: "contest.page", domain: "tasks", stage: "console", method: "GET", handle: get },
3528
+ { id: "routines", domain: "tasks", stage: "console", method: "GET", handle: get },
3529
+ { id: "routine.page", domain: "tasks", stage: "console", method: "GET", handle: get },
3530
+ { id: "decision.page", domain: "tasks", stage: "console", method: "GET", handle: get },
3531
+ { id: "decision.evidence", domain: "tasks", stage: "console", method: "GET", handle: get },
3532
+ { id: "session.attended-beats", domain: "tasks", stage: "console", method: "POST", handle: post },
3533
+ { id: "code.act", domain: "tasks", stage: "console", method: "POST", handle: post },
3534
+ { id: "tasks.add", domain: "tasks", stage: "console", method: "POST", handle: post },
3535
+ { id: "queue.move", domain: "tasks", stage: "console", method: "POST", handle: post },
3536
+ { id: "queue.note", domain: "tasks", stage: "console", method: "POST", handle: post },
3537
+ { id: "decision.answer", domain: "tasks", stage: "console", method: "POST", handle: post },
3538
+ { id: "contest.act.arm", domain: "tasks", stage: "console", method: "POST", handle: post },
3539
+ { id: "contest.act.pick", domain: "tasks", stage: "console", method: "POST", handle: post },
3540
+ { id: "contest.act.abandon", domain: "tasks", stage: "console", method: "POST", handle: post },
3541
+ { id: "task.attend.attend-preview", domain: "tasks", stage: "console", method: "POST", handle: post },
3542
+ { id: "task.attend.attend", domain: "tasks", stage: "console", method: "POST", handle: post },
3543
+ { id: "task.attend.attend-revoke", domain: "tasks", stage: "console", method: "POST", handle: post },
3544
+ { id: "task.act.hold", domain: "tasks", stage: "console", method: "POST", handle: post },
3545
+ { id: "task.act.unhold", domain: "tasks", stage: "console", method: "POST", handle: post },
3546
+ { id: "task.act.requeue", domain: "tasks", stage: "console", method: "POST", handle: post },
3547
+ { id: "task.act.cancel", domain: "tasks", stage: "console", method: "POST", handle: post },
3548
+ { id: "task.act.scope", domain: "tasks", stage: "console", method: "POST", handle: post },
3549
+ { id: "task.act.approve", domain: "tasks", stage: "console", method: "POST", handle: post },
3550
+ { id: "task.act.plan", domain: "tasks", stage: "console", method: "POST", handle: post },
3551
+ { id: "task.act.plan-edit", domain: "tasks", stage: "console", method: "POST", handle: post },
3552
+ { id: "task.act.next", domain: "tasks", stage: "console", method: "POST", handle: post },
3553
+ { id: "task.act.reopen", domain: "tasks", stage: "console", method: "POST", handle: post },
3554
+ { id: "task.act.steer", domain: "tasks", stage: "console", method: "POST", handle: post },
3555
+ { id: "task.act.accept-proof", domain: "tasks", stage: "console", method: "POST", handle: post },
3556
+ { id: "task.act.accept-revision", domain: "tasks", stage: "console", method: "POST", handle: post },
3557
+ { id: "task.act.reject-revision", domain: "tasks", stage: "console", method: "POST", handle: post },
3558
+ { id: "task.act.route", domain: "tasks", stage: "console", method: "POST", handle: post },
3559
+ { id: "task.act.retry-review", domain: "tasks", stage: "console", method: "POST", handle: post },
3560
+ { id: "task.act.complete", domain: "tasks", stage: "console", method: "POST", handle: post },
3561
+ { id: "task.act.merge", domain: "tasks", stage: "console", method: "POST", handle: post },
3562
+ { id: "task.act.confirm-stopped", domain: "tasks", stage: "console", method: "POST", handle: post },
3563
+ { id: "task.act.stop", domain: "tasks", stage: "console", method: "POST", handle: post },
3564
+ { id: "task.act.resume-arm", domain: "tasks", stage: "console", method: "POST", handle: post },
3565
+ { id: "task.act.resume", domain: "tasks", stage: "console", method: "POST", handle: post },
3566
+ { id: "task.instance-act", domain: "tasks", stage: "console", method: "POST", handle: post },
3567
+ { id: "routines.add", domain: "tasks", stage: "console", method: "POST", handle: post },
3568
+ { id: "routine.act.approve", domain: "tasks", stage: "console", method: "POST", handle: post },
3569
+ { id: "routine.act.refresh", domain: "tasks", stage: "console", method: "POST", handle: post },
3570
+ { id: "routine.act.pause", domain: "tasks", stage: "console", method: "POST", handle: post },
3571
+ { id: "routine.act.resume", domain: "tasks", stage: "console", method: "POST", handle: post },
3572
+ { id: "routine.act.run-now", domain: "tasks", stage: "console", method: "POST", handle: post },
3573
+ { id: "run.act", domain: "tasks", stage: "console", method: "POST", handle: post },
3574
+ { id: "session.editor-links", domain: "tasks", stage: "console", method: "POST", handle: post },
3575
+ { id: "run.turn", domain: "tasks", stage: "console", method: "POST", handle: post },
3576
+ { id: "incident.resolve", domain: "tasks", stage: "console", method: "POST", handle: post },
3577
+ ];
3578
+ return { registrations, get, post, runListPane, peekFragment, contestScreen, routineMutation, attendedBeats, attendMutation, taskMutation, queueRegionFor, ciFailingFor, completedRowFor, attemptRowFor, resultRowOf, reviewCockpitViewOf, decisionEvidence, runEvidence, sendArtifact, routinePage, peekGuards, contestData, liveAttendedTerms, attendConfirmScreen, liveRunIds };
3579
+ }