atris 3.35.0 → 3.36.0

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 (133) hide show
  1. package/AGENTS.md +37 -0
  2. package/README.md +5 -3
  3. package/atris/GETTING_STARTED.md +1 -1
  4. package/atris/atris.md +3 -0
  5. package/atris/policies/day-loop-voice.md +102 -0
  6. package/atris/policies/outbound-artifact-gate.md +2 -0
  7. package/atris/skills/design/SKILL.md +56 -32
  8. package/atris/skills/endgame/SKILL.md +12 -6
  9. package/atris/skills/engines/SKILL.md +22 -4
  10. package/atris/skills/fable-method/SKILL.md +66 -0
  11. package/atris/skills/improve/SKILL.md +65 -45
  12. package/atris/skills/youtube/SKILL.md +10 -1
  13. package/atris.md +2 -0
  14. package/ax +147 -19
  15. package/bin/atris.js +565 -265
  16. package/commands/activate.js +194 -88
  17. package/commands/agents.js +166 -0
  18. package/commands/autoland.js +459 -107
  19. package/commands/autopilot-front.js +20 -2
  20. package/commands/autopilot.js +118 -2
  21. package/commands/avail.js +407 -0
  22. package/commands/bench.js +188 -0
  23. package/commands/brain.js +3 -0
  24. package/commands/brief.js +651 -0
  25. package/commands/business-sync.js +192 -6
  26. package/commands/clean.js +50 -24
  27. package/commands/close.js +1083 -0
  28. package/commands/cloud.js +245 -0
  29. package/commands/compile.js +292 -1
  30. package/commands/computer.js +150 -3
  31. package/commands/dream.js +365 -0
  32. package/commands/drill.js +371 -0
  33. package/commands/engine.js +993 -32
  34. package/commands/experiments.js +28 -0
  35. package/commands/feedback.js +34 -12
  36. package/commands/fleet-report.js +206 -0
  37. package/commands/gm.js +23 -0
  38. package/commands/goal.js +247 -0
  39. package/commands/improve.js +642 -26
  40. package/commands/init.js +72 -44
  41. package/commands/interview.js +67 -1
  42. package/commands/land.js +152 -52
  43. package/commands/lifecycle.js +39 -3
  44. package/commands/log.js +84 -1
  45. package/commands/loops.js +220 -16
  46. package/commands/meet.js +220 -0
  47. package/commands/member.js +511 -34
  48. package/commands/mission.js +3029 -339
  49. package/commands/next.js +137 -0
  50. package/commands/now.js +220 -25
  51. package/commands/one-lap.js +776 -0
  52. package/commands/orb.js +314 -0
  53. package/commands/pack-craft.js +179 -0
  54. package/commands/pack.js +823 -0
  55. package/commands/play.js +3 -2
  56. package/commands/probe.js +30 -3
  57. package/commands/pulse.js +241 -46
  58. package/commands/push.js +260 -82
  59. package/commands/rainmaker.js +49 -0
  60. package/commands/report.js +415 -0
  61. package/commands/scout.js +147 -0
  62. package/commands/search.js +363 -0
  63. package/commands/skill.js +47 -3
  64. package/commands/slop.js +50 -2
  65. package/commands/soul.js +1 -1
  66. package/commands/stream.js +861 -0
  67. package/commands/study.js +693 -0
  68. package/commands/sync.js +67 -54
  69. package/commands/task.js +1346 -117
  70. package/commands/team.js +73 -0
  71. package/commands/verify.js +96 -0
  72. package/commands/watch.js +303 -0
  73. package/commands/wish.js +500 -0
  74. package/commands/workflow.js +11 -5
  75. package/commands/worktree.js +234 -13
  76. package/commands/xp.js +29 -11
  77. package/lib/auto-accept-certified.js +331 -34
  78. package/lib/autoland.js +319 -54
  79. package/lib/ax-auto-lane.js +79 -0
  80. package/lib/bench/context.js +147 -0
  81. package/lib/bench/engines.js +141 -0
  82. package/lib/bench/report.js +140 -0
  83. package/lib/bench/runner.js +512 -0
  84. package/lib/brief-ledger.js +350 -0
  85. package/lib/cloud-mission.js +259 -0
  86. package/lib/codex-flight.js +154 -0
  87. package/lib/default-runner.js +45 -0
  88. package/lib/default-verifier.js +70 -0
  89. package/lib/engine-registry.js +232 -0
  90. package/lib/experiments/daily.js +640 -0
  91. package/lib/fleet.js +2219 -67
  92. package/lib/improve-vitals-html.js +171 -0
  93. package/lib/known-commands.js +58 -0
  94. package/lib/loop-doctor.js +416 -0
  95. package/lib/member-switches.js +144 -0
  96. package/lib/mission-room.js +1 -0
  97. package/lib/mission-root.js +52 -0
  98. package/lib/next-moves.js +327 -10
  99. package/lib/one-lap-validator.js +60 -0
  100. package/lib/orb-context.js +477 -0
  101. package/lib/orb-scorecard.js +224 -0
  102. package/lib/policy-lessons.js +52 -1
  103. package/lib/pulse.js +277 -3
  104. package/lib/receipt-block.js +168 -0
  105. package/lib/receipt-evidence.js +65 -4
  106. package/lib/router-brain.js +352 -0
  107. package/lib/runner-command.js +10 -0
  108. package/lib/self-drive.js +258 -0
  109. package/lib/short-name.js +103 -0
  110. package/lib/spawn-env.js +18 -0
  111. package/lib/state-detection.js +56 -1
  112. package/lib/sync-status.js +59 -0
  113. package/lib/task-db.js +108 -29
  114. package/lib/task-proof.js +23 -1
  115. package/lib/team-presence.js +260 -0
  116. package/lib/tool-result-encode.js +7 -0
  117. package/lib/trust-tiers.js +90 -0
  118. package/lib/usage.js +107 -0
  119. package/lib/voice-gate.js +163 -0
  120. package/lib/wish-audit.js +1368 -0
  121. package/lib/wish-delegate.js +1840 -0
  122. package/lib/wish-design.js +110 -0
  123. package/lib/wish-stats.js +183 -0
  124. package/lib/wish-store.js +354 -0
  125. package/lib/zip.js +221 -0
  126. package/package.json +3 -1
  127. package/templates/loops/atris/loops/LOOPS.md +55 -0
  128. package/templates/loops/atris/loops/TICK.md +24 -0
  129. package/templates/loops/atris/loops/feedback.md +22 -0
  130. package/templates/loops/atris/loops/quality.md +22 -0
  131. package/templates/loops/atris/wiki/systems/loops.md +41 -0
  132. package/utils/api.js +5 -1
  133. package/utils/auth.js +57 -21
package/lib/fleet.js CHANGED
@@ -12,14 +12,28 @@
12
12
  // is just missions in flight, resumable the normal way.
13
13
 
14
14
  const fs = require('fs');
15
+ const crypto = require('crypto');
16
+ const os = require('os');
15
17
  const path = require('path');
16
18
  const { spawnSync } = require('child_process');
19
+ const {
20
+ appendBriefRecord,
21
+ mirrorBriefRecord,
22
+ stampBriefOutcome,
23
+ worktreeBaseRef,
24
+ } = require('./brief-ledger');
17
25
  const { RUNNER_PROFILE_DEFS, buildRunnerCommand } = require('./runner-command');
26
+ const { resolveDefaultVerifier } = require('./default-verifier');
27
+ const { rankEngines } = require('./router-brain');
28
+ const {
29
+ buildOneLapValidatorPrompt,
30
+ parseOneLapValidatorVerdict,
31
+ } = require('./one-lap-validator');
18
32
  const { listWorktrees } = require('../commands/worktree');
19
33
 
20
34
  // Lanes a fleet may never staff on its own: the human keeps irreversible
21
35
  // calls. Mirrors the autoland denied lanes.
22
- const DENIED_TAGS = ['billing', 'deploy', 'security', 'customer', 'external', 'feedback', 'voice'];
36
+ const DENIED_TAGS = ['billing', 'deploy', 'security', 'customer', 'external', 'feedback'];
23
37
 
24
38
  // ---------------------------------------------------------------------------
25
39
  // T1 — dispatch primitive
@@ -37,12 +51,28 @@ function parseDoneCheck(text) {
37
51
  };
38
52
  }
39
53
 
54
+ // Working-method kernel every dispatched engine inherits, distilled from
55
+ // atris/skills/fable-method: verify unpiped, receipts, caller sweeps,
56
+ // hypothesis-driven unsticking, smallest diff.
57
+ const METHOD_KERNEL = [
58
+ 'Read this whole brief before acting; locate every file you will touch before editing any of them.',
59
+ 'Never pipe a verify or test command through tail/head/grep — run it bare and read the real exit code.',
60
+ 'Done requires a receipt: paste the exact verify command and its final output lines in your report.',
61
+ 'Before deleting or renaming any function or call site, grep the whole repo for its callers; a caller outside your change means the contract stays intact.',
62
+ 'Stuck? Never run the same failing command a third time — write 3 one-line hypotheses, then run the cheapest test that discriminates between them.',
63
+ 'Smallest diff that satisfies Done wins; prefer deleting code over adding it.',
64
+ ];
65
+
40
66
  // The bounded prompt every engine gets. Same contract the manual flight used:
41
67
  // isolated worktree, commit never push, MAP first, focused verify, report.
42
- function buildFleetPrompt(task, { worktreePath } = {}) {
68
+ function buildFleetPrompt(task, { worktreePath, yolo = false } = {}) {
43
69
  const ref = task.display_id || task.id || 'TASK';
44
70
  const title = String(task.title || '').trim();
45
- const { done, check } = parseDoneCheck(title);
71
+ const { done, check: declaredCheck } = parseDoneCheck(title);
72
+ const check = declaredCheck || resolveDefaultVerifier(worktreePath || process.cwd());
73
+ const commitRule = yolo
74
+ ? 'Commit on the current branch with a plain-English message, then land it yourself: run atris worktree ship --message "<msg>" --verify "npm run test:fast && node --test <focused files>" --merge and report the PR URL. If ship reports a rebase conflict or the verify fails, stop and report; never resolve conflicts yourself.'
75
+ : 'Commit on the current branch with a clear message. Do not push. Do not create branches.';
46
76
  const lines = [
47
77
  'First, run `atris worktree guard`; if it fails, stop immediately, report back, and do not edit anything. Do this before any file edit.',
48
78
  '',
@@ -52,13 +82,14 @@ function buildFleetPrompt(task, { worktreePath } = {}) {
52
82
  '',
53
83
  ];
54
84
  if (done) lines.push(`Done criteria: ${done}`, '');
55
- if (check) lines.push(`Check: ${check}`, '');
85
+ lines.push(`Check: ${check}`, '');
56
86
  lines.push(
57
87
  'Rules:',
58
88
  '- Read atris/MAP.md first to locate the code; never guess file locations.',
59
89
  '- Run git status first. Stage ONLY files you changed. Never revert or touch files another agent modified.',
60
- '- Include or update a focused regression test; run it with node --test before committing.',
61
- '- Commit on the current branch with a clear message. Do not push. Do not create branches.',
90
+ '- Include or update a focused regression test; run it with npm run test:fast && node --test <focused files> before committing.',
91
+ `- ${commitRule}`,
92
+ ...METHOD_KERNEL.map((rule) => `- ${rule}`),
62
93
  '',
63
94
  'Final report (plain text): files changed, test command + result, commit sha (or say the commit failed and why).'
64
95
  );
@@ -72,6 +103,17 @@ function buildFleetPrompt(task, { worktreePath } = {}) {
72
103
  // spawn; template engines (cursor/codex/devin) ignore it — their CLIs manage
73
104
  // their own permissions.
74
105
  const FLEET_ALLOWED_TOOLS = 'Bash,Read,Edit,Write,Grep,Glob';
106
+ const VALIDATOR_ALLOWED_TOOLS = 'Bash,Read,Grep,Glob';
107
+ const DEAD_ENGINE_OUTPUT_PATTERNS = Object.freeze([
108
+ 'usage limit',
109
+ 'purchase more credits',
110
+ 'rate limit',
111
+ ]);
112
+ const YOLO_ENGINE_FLAGS = Object.freeze({
113
+ codex: '--dangerously-bypass-approvals-and-sandbox',
114
+ claude: '--dangerously-skip-permissions',
115
+ });
116
+ const DISPATCH_SELF_LAND_TARGET = 'origin/master';
75
117
 
76
118
  function realpathOrResolve(value) {
77
119
  const resolved = path.resolve(String(value));
@@ -95,16 +137,29 @@ function assertIsolatedWorktree(worktreePath, root = process.cwd()) {
95
137
  return { worktreePath: resolvedWorktree, primaryRoot };
96
138
  }
97
139
 
98
- function buildEngineCommand(engineName, promptFile) {
140
+ function buildEngineCommand(engineName, promptFile, { yolo = false, sealed = false, allowedTools = FLEET_ALLOWED_TOOLS } = {}) {
99
141
  if (!RUNNER_PROFILE_DEFS[engineName]) throw new Error(`unknown engine "${engineName}"`);
100
142
  const prev = process.env.ATRIS_RUNNER_PROFILE;
101
143
  process.env.ATRIS_RUNNER_PROFILE = engineName;
102
144
  try {
103
- const cmd = buildRunnerCommand({ promptFile, allowedTools: FLEET_ALLOWED_TOOLS });
145
+ let cmd = buildRunnerCommand({ promptFile, allowedTools });
104
146
  // devin's default is read-only for writes; fleet builds ALWAYS run in an
105
147
  // isolated worktree, so the conductor grants write permission here and
106
148
  // only here (the profile itself stays safe for non-worktree ticks).
149
+ if (sealed && engineName === 'codex') {
150
+ cmd = cmd.replace(/\bexec\b/, 'exec --sandbox workspace-write --ephemeral --ignore-user-config --ignore-rules');
151
+ }
152
+ if (sealed && engineName === 'claude') {
153
+ cmd = `${cmd} --safe-mode --no-session-persistence --permission-mode acceptEdits --settings '${JSON.stringify({ sandbox: { enabled: true, autoAllowBashIfSandboxed: true } })}'`;
154
+ }
155
+ if (sealed && engineName === 'cursor') cmd = `${cmd} --sandbox enabled`;
156
+ if (sealed && engineName === 'devin') return cmd.replace(/^devin -p /, 'devin -p --sandbox --permission-mode accept-edits ');
157
+ if (sealed && engineName === 'grok') {
158
+ cmd = `${cmd.replace(/\s+--always-approve\b/, '')} --sandbox enabled --permission-mode acceptEdits --no-memory --no-subagents`;
159
+ }
107
160
  if (engineName === 'devin') return cmd.replace(/^devin -p /, 'devin -p --permission-mode dangerous ');
161
+ if (yolo && engineName === 'codex') cmd = cmd.replace(/\bexec\b/, `exec ${YOLO_ENGINE_FLAGS.codex}`);
162
+ if (yolo && engineName === 'claude') cmd = `${cmd} ${YOLO_ENGINE_FLAGS.claude}`;
108
163
  return cmd;
109
164
  } finally {
110
165
  if (prev === undefined) delete process.env.ATRIS_RUNNER_PROFILE;
@@ -112,28 +167,372 @@ function buildEngineCommand(engineName, promptFile) {
112
167
  }
113
168
  }
114
169
 
170
+ function trackedSandboxPids(stateFile, leaseFile) {
171
+ const pids = new Set();
172
+ let state = {};
173
+ try { state = JSON.parse(fs.readFileSync(stateFile, 'utf8')); } catch {}
174
+ const pgid = Number(state.pgid);
175
+ if (Number.isInteger(pgid) && pgid > 0) {
176
+ const grouped = spawnSync('/usr/bin/pgrep', ['-g', String(pgid)], { encoding: 'utf8' });
177
+ for (const value of String(grouped.stdout || '').trim().split(/\s+/)) {
178
+ const pid = Number(value);
179
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
180
+ }
181
+ }
182
+ const leased = spawnSync('/usr/sbin/lsof', ['-t', leaseFile], { encoding: 'utf8' });
183
+ for (const value of String(leased.stdout || '').trim().split(/\s+/)) {
184
+ const pid = Number(value);
185
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
186
+ }
187
+ if (state.cwd) {
188
+ const rooted = spawnSync('/usr/sbin/lsof', ['-a', '-d', 'cwd', '+D', String(state.cwd), '-t'], { encoding: 'utf8' });
189
+ for (const value of String(rooted.stdout || '').trim().split(/\s+/)) {
190
+ const pid = Number(value);
191
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
192
+ }
193
+ }
194
+ pids.delete(process.pid);
195
+ return { pgid, pids: [...pids] };
196
+ }
197
+
198
+ function terminateTrackedSandbox(stateFile, leaseFile) {
199
+ for (const signal of ['SIGTERM', 'SIGKILL', 'SIGKILL']) {
200
+ const tracked = trackedSandboxPids(stateFile, leaseFile);
201
+ if (tracked.pgid > 0) {
202
+ try { process.kill(-tracked.pgid, signal); } catch {}
203
+ }
204
+ for (const pid of tracked.pids) {
205
+ try { process.kill(pid, signal); } catch {}
206
+ }
207
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
208
+ }
209
+ }
210
+
211
+ function terminateSameSandboxProfile(executable, args, options) {
212
+ if (executable !== '/usr/bin/sandbox-exec' || args[0] !== '-p' || !args[1]) return;
213
+ spawnSync(executable, ['-p', args[1], '/bin/kill', '-KILL', '-1'], {
214
+ cwd: options.cwd,
215
+ env: options.env,
216
+ encoding: 'utf8',
217
+ stdio: 'ignore',
218
+ timeout: 5000,
219
+ });
220
+ }
221
+
222
+ function runInReapedProcessGroup(executable, args, options, controlDir, statusFile) {
223
+ if (!controlDir || !statusFile) throw new Error('sealed execution requires isolated control and status paths');
224
+ const supervisor = path.join(controlDir, 'sandbox-supervisor.js');
225
+ const stateFile = path.join(controlDir, 'sandbox-process.json');
226
+ const leaseFile = path.join(controlDir, 'sandbox-process.lease');
227
+ fs.writeFileSync(supervisor, [
228
+ "'use strict';",
229
+ "const fs = require('node:fs');",
230
+ "const { spawn, spawnSync } = require('node:child_process');",
231
+ "const [stateFile, leaseFile, statusFile, executable, ...args] = process.argv.slice(2);",
232
+ "const leaseFd = fs.openSync(leaseFile, 'w', 0o600);",
233
+ "const statusFd = fs.openSync(statusFile, 'w', 0o600);",
234
+ "const child = spawn(executable, args, { cwd: process.cwd(), env: process.env, detached: true, stdio: ['ignore', 'inherit', 'inherit', leaseFd, statusFd] });",
235
+ "fs.closeSync(leaseFd);",
236
+ "fs.closeSync(statusFd);",
237
+ "fs.writeFileSync(stateFile, JSON.stringify({ pgid: child.pid, cwd: process.cwd() }) + '\\n', { mode: 0o600 });",
238
+ "let stopping = false;",
239
+ "const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));",
240
+ "function trackedPids() {",
241
+ " const pids = new Set();",
242
+ " for (const [bin, argv] of [['/usr/bin/pgrep', ['-g', String(child.pid)]], ['/usr/sbin/lsof', ['-t', leaseFile]], ['/usr/sbin/lsof', ['-a', '-d', 'cwd', '+D', process.cwd(), '-t']]]) {",
243
+ " const found = spawnSync(bin, argv, { encoding: 'utf8' });",
244
+ " for (const value of String(found.stdout || '').trim().split(/\\s+/)) {",
245
+ " const pid = Number(value);",
246
+ " if (Number.isInteger(pid) && pid > 0) pids.add(pid);",
247
+ " }",
248
+ " }",
249
+ " pids.delete(process.pid);",
250
+ " return [...pids];",
251
+ "}",
252
+ "async function stop(code) {",
253
+ " if (stopping) return;",
254
+ " stopping = true;",
255
+ " if (executable === '/usr/bin/sandbox-exec' && args[0] === '-p' && args[1]) {",
256
+ " spawnSync(executable, ['-p', args[1], '/bin/kill', '-KILL', '-1'], { cwd: process.cwd(), env: process.env, stdio: 'ignore', timeout: 5000 });",
257
+ " }",
258
+ " for (const [signal, delay] of [['SIGTERM', 100], ['SIGKILL', 100], ['SIGKILL', 100]]) {",
259
+ " try { process.kill(-child.pid, signal); } catch {}",
260
+ " for (const pid of trackedPids()) { try { process.kill(pid, signal); } catch {} }",
261
+ " await wait(delay);",
262
+ " }",
263
+ " let exitCode = Number.isInteger(code) ? code : 128;",
264
+ " try {",
265
+ " const savedText = fs.readFileSync(statusFile, 'utf8').trim();",
266
+ " const saved = Number(savedText);",
267
+ " if (savedText && Number.isInteger(saved) && saved >= 0 && saved <= 255) exitCode = saved;",
268
+ " } catch {}",
269
+ " process.exit(exitCode);",
270
+ "}",
271
+ "child.once('error', () => { void stop(1); });",
272
+ "child.once('exit', (code) => { void stop(code); });",
273
+ "for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP']) process.on(signal, () => { void stop(143); });",
274
+ '',
275
+ ].join('\n'), { mode: 0o700 });
276
+ let result;
277
+ try {
278
+ result = spawnSync(process.execPath, [supervisor, stateFile, leaseFile, statusFile, executable, ...args], options);
279
+ } finally {
280
+ terminateSameSandboxProfile(executable, args, options);
281
+ terminateTrackedSandbox(stateFile, leaseFile);
282
+ }
283
+ return result;
284
+ }
285
+
286
+ function sandboxLifecycleWrapper(runtimeDir, controlDir) {
287
+ const wrapper = path.join(runtimeDir, 'sandbox-lifecycle.sh');
288
+ const statusFile = path.join(controlDir, 'sandbox-status');
289
+ fs.writeFileSync(wrapper, [
290
+ '#!/bin/sh',
291
+ '"$@" 4>&-',
292
+ 'status=$?',
293
+ 'printf "%s\\n" "$status" >&4',
294
+ "trap '' EXIT TERM INT HUP",
295
+ '/bin/kill -TERM -1 2>/dev/null || true',
296
+ '/bin/sleep 0.1',
297
+ '/bin/kill -KILL -1 2>/dev/null || true',
298
+ 'exit "$status"',
299
+ '',
300
+ ].join('\n'), { mode: 0o700 });
301
+ return { wrapper, statusFile };
302
+ }
303
+
304
+ function dispatchResultOutput(result) {
305
+ if (!result) return '';
306
+ return [
307
+ result.report,
308
+ result.stdout,
309
+ result.stderr,
310
+ ].map((value) => String(value || '')).filter(Boolean).join('\n');
311
+ }
312
+
313
+ function dispatchResultExitCode(result) {
314
+ if (!result || typeof result !== 'object') return 0;
315
+ if (Object.prototype.hasOwnProperty.call(result, 'exitCode')) return result.exitCode;
316
+ if (Object.prototype.hasOwnProperty.call(result, 'status')) return result.status;
317
+ return 0;
318
+ }
319
+
320
+ function dispatchTaskId(task) {
321
+ return task && (task.display_id || task.task_id || task.id) ? String(task.display_id || task.task_id || task.id) : '';
322
+ }
323
+
324
+ function dispatchBriefAuthor(worktreePath, fallback = 'orb') {
325
+ try {
326
+ const sidecar = JSON.parse(fs.readFileSync(path.join(worktreePath, '.atris', 'agent-worktree.json'), 'utf8'));
327
+ return String(sidecar.owner || sidecar.member || sidecar.agent || fallback).trim() || fallback;
328
+ } catch {
329
+ return fallback;
330
+ }
331
+ }
332
+
333
+ function captureDispatchBrief({ root, task, engine, worktreePath, prompt, missionId = '', author = '', yolo = false }) {
334
+ const promptText = prompt || buildFleetPrompt(task, { worktreePath, yolo });
335
+ const record = appendBriefRecord(root, {
336
+ author: author || dispatchBriefAuthor(worktreePath, 'orb'),
337
+ engine,
338
+ task_id: dispatchTaskId(task),
339
+ mission_id: missionId,
340
+ prompt_text: promptText,
341
+ context: {
342
+ worktree: worktreePath,
343
+ base_ref: worktreeBaseRef(worktreePath, ''),
344
+ },
345
+ });
346
+ if (path.resolve(root) !== path.resolve(worktreePath || root)) {
347
+ mirrorBriefRecord(worktreePath, record);
348
+ }
349
+ return record;
350
+ }
351
+
352
+ function stampDispatchBrief(root, briefId, result, note) {
353
+ if (!briefId) return { ok: false, error: 'missing brief id' };
354
+ try {
355
+ return stampBriefOutcome(root, briefId, { result, note });
356
+ } catch (err) {
357
+ return { ok: false, error: err.message };
358
+ }
359
+ }
360
+
361
+ function detectDeadEngineDispatch(result) {
362
+ const exitCode = dispatchResultExitCode(result);
363
+ if (exitCode === 0) return null;
364
+ const output = dispatchResultOutput(result).toLowerCase();
365
+ const pattern = DEAD_ENGINE_OUTPUT_PATTERNS.find((p) => output.includes(p));
366
+ if (pattern) return { reason: 'usage_limit', pattern };
367
+ return { reason: 'nonzero_exit', exitCode };
368
+ }
369
+
370
+ function normalizeInstalledEngines(engines) {
371
+ return [...new Set((engines || [])
372
+ .map((entry) => (typeof entry === 'string' ? entry : entry && entry.name))
373
+ .map((name) => String(name || '').trim())
374
+ .filter((name) => FLEET_CAPABLE.includes(name) && RUNNER_PROFILE_DEFS[name]))];
375
+ }
376
+
377
+ function installedFleetEngines(root) {
378
+ const { roster } = require('../commands/engine');
379
+ return normalizeInstalledEngines(roster(root).filter((e) => e.installed));
380
+ }
381
+
382
+ function rankFleetEngines(engines, root = process.cwd()) {
383
+ return rankEngines(normalizeInstalledEngines(engines), {
384
+ root,
385
+ taskType: 'executor',
386
+ });
387
+ }
388
+
389
+ function nextInstalledFleetEngine(current, { root = process.cwd(), installedEngines = null } = {}) {
390
+ const engines = installedEngines ? normalizeInstalledEngines(installedEngines) : installedFleetEngines(root);
391
+ const currentName = String(current || '').trim();
392
+ if (!engines.length) return '';
393
+ const index = engines.indexOf(currentName);
394
+ const ordered = index === -1
395
+ ? engines
396
+ : [...engines.slice(index + 1), ...engines.slice(0, index)];
397
+ const candidates = ordered.filter((name) => name && name !== currentName);
398
+ return rankFleetEngines(candidates, root)[0] || '';
399
+ }
400
+
401
+ function normalizeDispatchResult(result, engineName) {
402
+ const normalized = { ...(result || {}) };
403
+ normalized.engine = normalized.engine || engineName;
404
+ normalized.exitCode = dispatchResultExitCode(normalized);
405
+ return normalized;
406
+ }
407
+
408
+ function failedDispatchLeg(result, engineName) {
409
+ return {
410
+ engine: String(result && result.engine || engineName || ''),
411
+ exitCode: dispatchResultExitCode(result),
412
+ stderr: String(result && result.stderr || '').slice(-2000),
413
+ report: String(result && result.report || '').slice(-2000),
414
+ };
415
+ }
416
+
417
+ async function dispatchEntryWithRestaff({
418
+ entry,
419
+ engine,
420
+ root,
421
+ dispatch,
422
+ installedEngines = null,
423
+ restaffState,
424
+ }) {
425
+ const runOnce = async (engineName) => {
426
+ const prompt = entry.prompt || buildFleetPrompt(entry.task, { worktreePath: entry.worktreePath, yolo: entry.yolo });
427
+ const brief = captureDispatchBrief({
428
+ root,
429
+ task: entry.task,
430
+ engine: engineName,
431
+ worktreePath: entry.worktreePath,
432
+ prompt,
433
+ missionId: entry.mission_id || entry.missionId || '',
434
+ author: entry.author || '',
435
+ yolo: entry.yolo,
436
+ });
437
+ try {
438
+ const runResult = normalizeDispatchResult(await dispatch({ ...entry, engine: engineName, prompt, brief_id: brief.brief_id, briefId: brief.brief_id, skipBriefCapture: true }), engineName);
439
+ if (!runResult.brief_id) runResult.brief_id = brief.brief_id;
440
+ return runResult;
441
+ } catch (err) {
442
+ return normalizeDispatchResult({
443
+ brief_id: brief.brief_id,
444
+ exitCode: 1,
445
+ report: '',
446
+ stderr: String(err && err.message || err),
447
+ }, engineName);
448
+ }
449
+ };
450
+
451
+ const first = await runOnce(engine);
452
+ const deadEngine = detectDeadEngineDispatch(first);
453
+ if (!deadEngine) return first;
454
+ stampDispatchBrief(root, first.brief_id, 'fail', `restaffed from ${engine}: ${deadEngine.reason}`);
455
+
456
+ const outage = { from: engine, reason: deadEngine.reason };
457
+ if (restaffState.used) {
458
+ return { ...first, deadEngine: { ...outage, skipped: 'already_restaffed' } };
459
+ }
460
+
461
+ const fallback = nextInstalledFleetEngine(engine, { root, installedEngines });
462
+ if (!fallback) {
463
+ return { ...first, deadEngine: { ...outage, skipped: 'no_fallback_engine' } };
464
+ }
465
+
466
+ restaffState.used = true;
467
+ const fallbackResult = await runOnce(fallback);
468
+ return {
469
+ ...fallbackResult,
470
+ restaffed: {
471
+ from: engine,
472
+ to: fallback,
473
+ reason: deadEngine.reason,
474
+ failed_legs: [failedDispatchLeg(first, engine)],
475
+ },
476
+ };
477
+ }
478
+
115
479
  // Run one engine on one task in one worktree. Blocking; the conductor runs
116
480
  // dispatches in parallel via child processes, not threads. `runner` is
117
481
  // injectable for tests. `prompt` is injectable too: a caller-supplied prompt
118
482
  // (e.g. `atris engine dispatch --prompt-file`) skips the generated
119
483
  // buildFleetPrompt text entirely.
120
- function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), timeoutMs = 900000, runner = null, prompt: promptOverride = '' }) {
484
+ function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), timeoutMs = 900000, runner = null, prompt: promptOverride = '', yolo = false, sealed = false, briefId = '', skipBriefCapture = false, environment = null, allowedTools = FLEET_ALLOWED_TOOLS }) {
121
485
  assertIsolatedWorktree(worktreePath, root);
122
- const prompt = promptOverride || buildFleetPrompt(task, { worktreePath });
123
- const promptFile = path.join(worktreePath, '.atris', `fleet-prompt-${task.display_id || 'task'}.md`);
486
+ const prompt = promptOverride || buildFleetPrompt(task, { worktreePath, yolo });
487
+ let capturedBriefId = briefId;
488
+ if (!skipBriefCapture) {
489
+ capturedBriefId = captureDispatchBrief({ root, task, engine, worktreePath, prompt, yolo }).brief_id;
490
+ }
491
+ const runtimeDir = String(environment && environment.ATRIS_ONE_LAP_RUNTIME_DIR || '');
492
+ const promptFile = path.join(sealed && runtimeDir ? runtimeDir : path.join(worktreePath, '.atris'), `fleet-prompt-${task.display_id || 'task'}.md`);
124
493
  fs.mkdirSync(path.dirname(promptFile), { recursive: true });
125
494
  fs.writeFileSync(promptFile, prompt);
126
- const command = buildEngineCommand(engine, promptFile);
127
- const exec = runner || ((cmd) => spawnSync('sh', ['-c', cmd], {
128
- cwd: worktreePath,
129
- encoding: 'utf8',
130
- timeout: timeoutMs,
131
- }));
495
+ const command = buildEngineCommand(engine, promptFile, { yolo, sealed, allowedTools });
496
+ const exec = runner || ((cmd) => {
497
+ const childEnv = sealed && environment
498
+ ? { ...environment }
499
+ : (environment ? { ...process.env, ...environment } : { ...process.env });
500
+ const sandboxProfile = String(childEnv.ATRIS_ONE_LAP_SANDBOX_PROFILE || '');
501
+ const cleanupRuntimeDir = String(childEnv.ATRIS_ONE_LAP_RUNTIME_DIR || '');
502
+ const cleanupControlDir = String(childEnv.ATRIS_ONE_LAP_CONTROL_DIR || '');
503
+ const lifecycleWrapper = String(childEnv.ATRIS_ONE_LAP_LIFECYCLE_WRAPPER || '');
504
+ const statusFile = String(childEnv.ATRIS_ONE_LAP_STATUS_FILE || '');
505
+ delete childEnv.ATRIS_ONE_LAP_SANDBOX_PROFILE;
506
+ delete childEnv.ATRIS_ONE_LAP_RUNTIME_DIR;
507
+ delete childEnv.ATRIS_ONE_LAP_CONTROL_DIR;
508
+ delete childEnv.ATRIS_ONE_LAP_LIFECYCLE_WRAPPER;
509
+ delete childEnv.ATRIS_ONE_LAP_STATUS_FILE;
510
+ try {
511
+ const executable = sandboxProfile ? '/usr/bin/sandbox-exec' : 'sh';
512
+ const args = sandboxProfile
513
+ ? ['-p', sandboxProfile, lifecycleWrapper, '/bin/sh', '-c', cmd]
514
+ : ['-c', cmd];
515
+ const spawnOptions = {
516
+ cwd: worktreePath,
517
+ env: childEnv,
518
+ encoding: 'utf8',
519
+ timeout: timeoutMs,
520
+ };
521
+ return sandboxProfile
522
+ ? runInReapedProcessGroup(executable, args, spawnOptions, cleanupControlDir, statusFile)
523
+ : spawnSync(executable, args, spawnOptions);
524
+ } finally {
525
+ if (cleanupRuntimeDir) fs.rmSync(cleanupRuntimeDir, { recursive: true, force: true });
526
+ if (cleanupControlDir) fs.rmSync(cleanupControlDir, { recursive: true, force: true });
527
+ }
528
+ });
132
529
  const result = exec(command);
133
530
  return {
134
531
  task: task.display_id || task.id,
135
532
  engine,
136
533
  worktreePath,
534
+ promptFile,
535
+ brief_id: capturedBriefId || null,
137
536
  command,
138
537
  exitCode: result.status,
139
538
  report: String(result.stdout || '').slice(-8000),
@@ -146,12 +545,13 @@ function dispatchToEngine({ task, engine, worktreePath, root = process.cwd(), ti
146
545
 
147
546
  function taskTags(task) {
148
547
  const fromTags = Array.isArray(task.tags) ? task.tags : [];
548
+ const fromTag = task && task.tag ? [task.tag] : [];
149
549
  // Tags added after creation live in metadata.tags (`atris task tag`); a
150
550
  // fleet that only read task.tags/title hashtags would ignore an owner-hold
151
551
  // flag stamped on a live task and keep restaffing it (CLI-879).
152
552
  const fromMeta = task && task.metadata && Array.isArray(task.metadata.tags) ? task.metadata.tags : [];
153
553
  const fromTitle = (String(task.title || '').match(/#([a-z0-9-]+)/gi) || []).map((t) => t.slice(1));
154
- return [...fromTags, ...fromMeta, ...fromTitle].map((t) => String(t).toLowerCase());
554
+ return [...fromTag, ...fromTags, ...fromMeta, ...fromTitle].map((t) => String(t).toLowerCase());
155
555
  }
156
556
 
157
557
  // A task flagged for a human decision is never fleet-staffable, whatever its
@@ -250,15 +650,24 @@ function landArrival({ worktreePath, git = null }) {
250
650
 
251
651
  module.exports = {
252
652
  DENIED_TAGS,
653
+ DEAD_ENGINE_OUTPUT_PATTERNS,
253
654
  get FLEET_CAPABLE() { return FLEET_CAPABLE; },
254
655
  get runFleetFlight() { return runFleetFlight; },
255
656
  get focusedCheck() { return focusedCheck; },
256
657
  get dispatchCheck() { return dispatchCheck; },
257
658
  get runDispatchFlight() { return runDispatchFlight; },
659
+ YOLO_ENGINE_FLAGS,
660
+ DISPATCH_SELF_LAND_TARGET,
258
661
  parseDoneCheck,
662
+ METHOD_KERNEL,
259
663
  buildFleetPrompt,
260
664
  assertIsolatedWorktree,
261
665
  buildEngineCommand,
666
+ isSafeLane,
667
+ taskTags,
668
+ detectDeadEngineDispatch,
669
+ rankFleetEngines,
670
+ nextInstalledFleetEngine,
262
671
  dispatchToEngine,
263
672
  taskTags,
264
673
  isHumanHoldTag,
@@ -269,6 +678,7 @@ module.exports = {
269
678
  assignEngines,
270
679
  landArrival,
271
680
  fleetShipArgs,
681
+ defaultSelfLandCheck,
272
682
  };
273
683
 
274
684
  // ---------------------------------------------------------------------------
@@ -276,10 +686,1101 @@ module.exports = {
276
686
 
277
687
  // Engines that can edit a repo headlessly. atris-fast (ax) is a chat lane,
278
688
  // not a repo worker — it keeps owning normal mission ticks, not fleet builds.
279
- const FLEET_CAPABLE = ['claude', 'codex', 'cursor', 'devin'];
689
+ const FLEET_CAPABLE = ['claude', 'codex', 'cursor', 'devin', 'grok'];
280
690
 
691
+ let receiptSequence = 0;
281
692
  function nowStamp() {
282
- return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
693
+ receiptSequence += 1;
694
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-').replace(/Z$/, '');
695
+ return `${stamp}-p${process.pid}-${receiptSequence}`;
696
+ }
697
+
698
+ function shellSingleQuote(value) {
699
+ return `'${String(value).replace(/'/g, `'"'"'`)}'`;
700
+ }
701
+
702
+ function canonicalPath(value) {
703
+ const resolved = path.resolve(String(value || ''));
704
+ try { return fs.realpathSync(resolved); } catch { return resolved; }
705
+ }
706
+
707
+ function seatbeltRule(kind, value) {
708
+ return `(${kind} ${JSON.stringify(canonicalPath(value))})`;
709
+ }
710
+
711
+ function seatbeltAncestors(target) {
712
+ const out = [];
713
+ let current = path.dirname(canonicalPath(target));
714
+ while (current && current !== path.dirname(current)) {
715
+ out.push(current);
716
+ current = path.dirname(current);
717
+ }
718
+ out.push('/');
719
+ return out.reverse();
720
+ }
721
+
722
+ function reviewOnlySandboxProfile({ worktreePath, gitDir, quarantine, runtimeTmp, engine = '', network = false, writable = true }) {
723
+ if (process.platform !== 'darwin' || !fs.existsSync('/usr/bin/sandbox-exec')) {
724
+ throw new Error('review-only execution requires the macOS sandbox-exec isolation backend');
725
+ }
726
+ const home = String(process.env.HOME || '').trim();
727
+ const readSubpaths = [
728
+ '/System', '/usr', '/bin', '/sbin', '/Library', '/opt/homebrew', '/dev',
729
+ '/private/etc', '/private/var/db', '/private/var/run', '/private/var/select',
730
+ worktreePath, gitDir, quarantine, runtimeTmp,
731
+ ];
732
+ const writeSubpaths = ['/dev', runtimeTmp];
733
+ if (writable) writeSubpaths.push(worktreePath, gitDir, quarantine);
734
+ const installByEngine = {
735
+ codex: ['.bun'],
736
+ claude: ['.local/bin', '.local/share/claude'],
737
+ cursor: ['.local/bin', '.local/share/cursor-agent'],
738
+ devin: ['.local/bin', '.local/share/devin'],
739
+ grok: ['.local/bin', '.grok/downloads'],
740
+ };
741
+ if (home && engine) {
742
+ for (const relative of installByEngine[engine] || []) readSubpaths.push(path.join(home, relative));
743
+ }
744
+ if (engine && RUNNER_PROFILE_DEFS[engine]) {
745
+ const engineBin = String(RUNNER_PROFILE_DEFS[engine].bin || '').trim();
746
+ const located = engineBin
747
+ ? spawnSync('/bin/sh', ['-c', `command -v ${engineBin}`], { encoding: 'utf8' })
748
+ : null;
749
+ const executable = located && located.status === 0 ? String(located.stdout || '').trim() : '';
750
+ if (executable) {
751
+ readSubpaths.push(path.dirname(executable));
752
+ readSubpaths.push(path.dirname(canonicalPath(executable)));
753
+ }
754
+ }
755
+ const readLiterals = [];
756
+ const writeLiterals = [];
757
+ for (const [key, value] of Object.entries(process.env)) {
758
+ if (!/^ATRIS_(?:ENGINE|VALIDATOR|PUSH|BOUNDARY)_(?:COUNT|PROMPT|RESULT|URL|CONFIG|DUMP)$/.test(key)) continue;
759
+ if (!value || !path.isAbsolute(value)) continue;
760
+ readLiterals.push(value);
761
+ writeLiterals.push(value);
762
+ }
763
+ const ancestorLiterals = [...new Set([
764
+ ...readSubpaths,
765
+ ...readLiterals,
766
+ ...writeLiterals,
767
+ ].flatMap(seatbeltAncestors))];
768
+ const readRules = [
769
+ ...ancestorLiterals.map((value) => seatbeltRule('literal', value)),
770
+ ...[...new Set(readSubpaths)].map((value) => seatbeltRule('subpath', value)),
771
+ ...[...new Set(readLiterals)].map((value) => seatbeltRule('literal', value)),
772
+ ];
773
+ const writeRules = [
774
+ ...[...new Set(writeSubpaths)].map((value) => seatbeltRule('subpath', value)),
775
+ ...[...new Set(writeLiterals)].map((value) => seatbeltRule('literal', value)),
776
+ ];
777
+ return [
778
+ '(version 1)',
779
+ '(deny default)',
780
+ '(import "system.sb")',
781
+ '(import "com.apple.corefoundation.sb")',
782
+ '(allow process*)',
783
+ '(allow signal (target same-sandbox))',
784
+ ...(network ? ['(allow network*)'] : []),
785
+ '(allow sysctl-read)',
786
+ '(allow mach-lookup)',
787
+ '(allow ipc-posix*)',
788
+ `(allow file-read* ${readRules.join(' ')})`,
789
+ `(allow file-write* ${writeRules.join(' ')})`,
790
+ ].join('\n');
791
+ }
792
+
793
+ function prepareEphemeralEngineHome(runtimeTmp, engine) {
794
+ const sourceHome = String(process.env.HOME || '').trim();
795
+ const runtimeHome = path.join(runtimeTmp, `home-${engine || 'verifier'}`);
796
+ fs.mkdirSync(runtimeHome, { recursive: true, mode: 0o700 });
797
+ const copy = (sourceRelative, targetRelative, pickKeys = null) => {
798
+ if (!sourceHome) return;
799
+ const source = path.join(sourceHome, sourceRelative);
800
+ if (!fs.existsSync(source) || !fs.lstatSync(source).isFile()) return;
801
+ const target = path.join(runtimeHome, targetRelative);
802
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
803
+ if (pickKeys) {
804
+ let parsed;
805
+ try { parsed = JSON.parse(fs.readFileSync(source, 'utf8')); } catch { return; }
806
+ const selected = {};
807
+ for (const key of pickKeys) {
808
+ if (Object.prototype.hasOwnProperty.call(parsed, key)) selected[key] = parsed[key];
809
+ }
810
+ fs.writeFileSync(target, `${JSON.stringify(selected, null, 2)}\n`, { mode: 0o600 });
811
+ } else {
812
+ fs.copyFileSync(source, target);
813
+ fs.chmodSync(target, 0o600);
814
+ }
815
+ };
816
+ if (engine === 'codex') copy('.codex/auth.json', '.codex/auth.json');
817
+ if (engine === 'claude') {
818
+ copy('.claude.json', '.claude.json', [
819
+ 'oauthAccount', 'userID', 'anonymousId', 'machineID', 'hasCompletedOnboarding',
820
+ 'installMethod', 'claudeMaxTier', 'hasAvailableMaxSubscription', 'hasAvailableSubscription',
821
+ ]);
822
+ }
823
+ if (engine === 'cursor') {
824
+ copy('.cursor/cli-config.json', '.cursor/cli-config.json', ['authInfo', 'version', 'privacyCache', 'network']);
825
+ }
826
+ if (engine === 'devin') copy('.config/devin/config.json', '.config/devin/config.json');
827
+ if (engine === 'grok') {
828
+ copy('.grok/auth.json', '.grok/auth.json');
829
+ copy('.grok/config.toml', '.grok/config.toml');
830
+ }
831
+ return runtimeHome;
832
+ }
833
+
834
+ function reviewOnlyEngineEnvironment(worktreePath, options = {}) {
835
+ const selectedEngine = String(options.engine || '');
836
+ const selectedSecret = (engines, name) => engines.includes(selectedEngine) ? String(process.env[name] || '') : '';
837
+ const runtimeTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-one-lap-runtime-'));
838
+ const guardDir = path.join(runtimeTmp, 'bin');
839
+ fs.mkdirSync(guardDir, { recursive: true });
840
+ const locate = (name) => {
841
+ const result = spawnSync('sh', ['-c', `command -v ${name}`], { encoding: 'utf8' });
842
+ return result.status === 0 ? String(result.stdout || '').trim() : '';
843
+ };
844
+ const gitBin = locate('git');
845
+ if (!gitBin) throw new Error('review-only dispatch requires git');
846
+ fs.writeFileSync(path.join(guardDir, 'git'), [
847
+ '#!/bin/sh',
848
+ 'for arg in "$@"; do',
849
+ ' case "$arg" in',
850
+ ' push|send-pack) echo "one lap blocked git $arg" >&2; exit 2 ;;',
851
+ ' esac',
852
+ 'done',
853
+ `exec ${shellSingleQuote(gitBin)} "$@"`,
854
+ '',
855
+ ].join('\n'), { mode: 0o755 });
856
+
857
+ const npmBin = locate('npm');
858
+ if (npmBin) {
859
+ fs.writeFileSync(path.join(guardDir, 'npm'), [
860
+ '#!/bin/sh',
861
+ 'case "${1:-}" in',
862
+ ' publish|install|i|add|login|logout|owner|token|deprecate|unpublish|dist-tag|access|team|org) echo "one lap blocked npm $1" >&2; exit 2 ;;',
863
+ 'esac',
864
+ `exec ${shellSingleQuote(npmBin)} "$@"`,
865
+ '',
866
+ ].join('\n'), { mode: 0o755 });
867
+ }
868
+
869
+ for (const command of ['gh', 'curl', 'wget', 'ssh', 'scp', 'rsync', 'fly', 'flyctl', 'vercel', 'render', 'kubectl', 'aws', 'gcloud', 'az', 'terraform', 'stripe']) {
870
+ fs.writeFileSync(path.join(guardDir, command), `#!/bin/sh\necho "one lap blocked ${command}" >&2\nexit 2\n`, { mode: 0o755 });
871
+ }
872
+ const commonResult = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: worktreePath, encoding: 'utf8' });
873
+ const remoteResult = spawnSync('git', ['remote', 'get-url', 'origin'], { cwd: worktreePath, encoding: 'utf8' });
874
+ if (commonResult.status !== 0 || remoteResult.status !== 0) {
875
+ throw new Error('review-only execution could not resolve its sealed Git boundary');
876
+ }
877
+ const gitDir = canonicalPath(path.resolve(worktreePath, String(commonResult.stdout || '').trim()));
878
+ const quarantine = canonicalPath(path.resolve(worktreePath, String(remoteResult.stdout || '').trim()));
879
+ const runtimeHome = prepareEphemeralEngineHome(runtimeTmp, selectedEngine);
880
+ const controlTmp = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-one-lap-control-'));
881
+ const lifecycle = sandboxLifecycleWrapper(runtimeTmp, controlTmp);
882
+ let sandboxProfile;
883
+ try {
884
+ sandboxProfile = reviewOnlySandboxProfile({
885
+ worktreePath,
886
+ gitDir,
887
+ quarantine,
888
+ runtimeTmp,
889
+ engine: String(options.engine || ''),
890
+ network: options.network === true,
891
+ writable: options.writable !== false,
892
+ });
893
+ } catch (error) {
894
+ fs.rmSync(runtimeTmp, { recursive: true, force: true });
895
+ fs.rmSync(controlTmp, { recursive: true, force: true });
896
+ throw error;
897
+ }
898
+ const environment = {
899
+ PATH: `${guardDir}${path.delimiter}${process.env.PATH || ''}`,
900
+ GIT_CONFIG_COUNT: '2',
901
+ GIT_CONFIG_KEY_0: 'remote.origin.pushurl',
902
+ GIT_CONFIG_VALUE_0: path.join(guardDir, 'blocked-push'),
903
+ GIT_CONFIG_KEY_1: 'remote.origin.receivepack',
904
+ GIT_CONFIG_VALUE_1: 'false',
905
+ GH_TOKEN: '',
906
+ GITHUB_TOKEN: '',
907
+ NPM_TOKEN: '',
908
+ NODE_AUTH_TOKEN: '',
909
+ AWS_ACCESS_KEY_ID: '',
910
+ AWS_SECRET_ACCESS_KEY: '',
911
+ GOOGLE_APPLICATION_CREDENTIALS: '',
912
+ AZURE_CLIENT_SECRET: '',
913
+ STRIPE_SECRET_KEY: '',
914
+ OPENAI_API_KEY: selectedSecret(['codex'], 'OPENAI_API_KEY'),
915
+ CODEX_API_KEY: selectedSecret(['codex'], 'CODEX_API_KEY'),
916
+ ANTHROPIC_API_KEY: selectedSecret(['claude'], 'ANTHROPIC_API_KEY'),
917
+ CLAUDE_CODE_OAUTH_TOKEN: selectedSecret(['claude'], 'CLAUDE_CODE_OAUTH_TOKEN'),
918
+ CURSOR_API_KEY: selectedSecret(['cursor'], 'CURSOR_API_KEY'),
919
+ DEVIN_API_KEY: selectedSecret(['devin'], 'DEVIN_API_KEY'),
920
+ XAI_API_KEY: selectedSecret(['grok'], 'XAI_API_KEY'),
921
+ GROK_API_KEY: selectedSecret(['grok'], 'GROK_API_KEY'),
922
+ SSH_AUTH_SOCK: '',
923
+ TMPDIR: runtimeTmp,
924
+ HOME: runtimeHome,
925
+ XDG_CONFIG_HOME: path.join(runtimeHome, '.config'),
926
+ XDG_CACHE_HOME: path.join(runtimeTmp, 'cache'),
927
+ npm_config_cache: path.join(runtimeTmp, 'npm-cache'),
928
+ GIT_CONFIG_GLOBAL: path.join(runtimeTmp, 'gitconfig'),
929
+ GIT_CONFIG_NOSYSTEM: '1',
930
+ CODEX_HOME: path.join(runtimeHome, '.codex'),
931
+ CLAUDE_CONFIG_DIR: path.join(runtimeHome, '.claude'),
932
+ ATRIS_ONE_LAP_SANDBOX_PROFILE: sandboxProfile,
933
+ ATRIS_ONE_LAP_RUNTIME_DIR: runtimeTmp,
934
+ ATRIS_ONE_LAP_CONTROL_DIR: controlTmp,
935
+ ATRIS_ONE_LAP_LIFECYCLE_WRAPPER: lifecycle.wrapper,
936
+ ATRIS_ONE_LAP_STATUS_FILE: lifecycle.statusFile,
937
+ };
938
+ for (const key of [
939
+ 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'NO_COLOR', 'USER', 'LOGNAME', 'SHELL',
940
+ 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'SSL_CERT_FILE', 'SSL_CERT_DIR',
941
+ 'NODE_EXTRA_CA_CERTS', 'NODE_NO_WARNINGS', 'DISABLE_AUTOUPDATER',
942
+ 'ATRIS_ENGINE_MODE', 'ATRIS_ENGINE_DELAY', 'ATRIS_ENGINE_COUNT', 'ATRIS_ENGINE_PROMPT',
943
+ 'ATRIS_VALIDATOR_MODE', 'ATRIS_VALIDATOR_COUNT', 'ATRIS_VALIDATOR_PROMPT',
944
+ 'ATRIS_PUSH_RESULT', 'ATRIS_PUSH_URL', 'ATRIS_PUSH_CONFIG', 'ATRIS_BOUNDARY_DUMP',
945
+ 'ATRIS_REAL_GIT', 'ATRIS_TASKS_DB',
946
+ ]) {
947
+ if (process.env[key] !== undefined) environment[key] = String(process.env[key]);
948
+ }
949
+ return environment;
950
+ }
951
+
952
+ function conductorGitEnvironment() {
953
+ const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
954
+ for (const key of Object.keys(env)) {
955
+ if (/^GIT_CONFIG_(?:COUNT|KEY_\d+|VALUE_\d+)$/.test(key)) delete env[key];
956
+ }
957
+ for (const key of [
958
+ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',
959
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_CONFIG_PARAMETERS',
960
+ ]) delete env[key];
961
+ return env;
962
+ }
963
+
964
+ function remoteMasterOid(worktreePath, remote = 'origin') {
965
+ const result = spawnSync('git', ['ls-remote', '--exit-code', remote, 'refs/heads/master'], {
966
+ cwd: worktreePath,
967
+ encoding: 'utf8',
968
+ timeout: 15000,
969
+ env: conductorGitEnvironment(),
970
+ });
971
+ if (result.status !== 0) return '';
972
+ return String(result.stdout || '').trim().split(/\s+/)[0] || '';
973
+ }
974
+
975
+ function remoteRefsDigest(worktreePath, remote = 'origin') {
976
+ const result = spawnSync('git', ['ls-remote', remote], {
977
+ cwd: worktreePath,
978
+ encoding: 'utf8',
979
+ timeout: 30000,
980
+ env: conductorGitEnvironment(),
981
+ });
982
+ if (result.status !== 0) return '';
983
+ return crypto.createHash('sha256').update(String(result.stdout || '')).digest('hex');
984
+ }
985
+
986
+ function digestFiles(paths) {
987
+ const hash = crypto.createHash('sha256');
988
+ const visit = (target) => {
989
+ hash.update(`\0${target}\0`);
990
+ if (!fs.existsSync(target)) {
991
+ hash.update('missing');
992
+ return;
993
+ }
994
+ const stat = fs.lstatSync(target);
995
+ if (stat.isSymbolicLink()) {
996
+ hash.update(`link:${fs.readlinkSync(target)}`);
997
+ return;
998
+ }
999
+ if (stat.isDirectory()) {
1000
+ hash.update('directory');
1001
+ for (const name of fs.readdirSync(target).sort()) visit(path.join(target, name));
1002
+ return;
1003
+ }
1004
+ hash.update(`file:${stat.mode}:`);
1005
+ hash.update(fs.readFileSync(target));
1006
+ };
1007
+ for (const target of paths) visit(target);
1008
+ return hash.digest('hex');
1009
+ }
1010
+
1011
+ function reviewSandboxMetadataDigest(boundary) {
1012
+ return digestFiles([
1013
+ path.join(boundary.worktreePath, '.git'),
1014
+ path.join(boundary.gitDir, 'config'),
1015
+ path.join(boundary.gitDir, 'hooks'),
1016
+ path.join(boundary.gitDir, 'info'),
1017
+ path.join(boundary.gitDir, 'objects', 'info'),
1018
+ path.join(boundary.gitDir, 'shallow'),
1019
+ path.join(boundary.worktreeGitDir, 'config.worktree'),
1020
+ path.join(boundary.worktreeGitDir, 'commondir'),
1021
+ path.join(boundary.worktreeGitDir, 'gitdir'),
1022
+ path.join(boundary.worktreeGitDir, 'HEAD'),
1023
+ ]);
1024
+ }
1025
+
1026
+ function bareRefsDigest(gitDir) {
1027
+ const result = spawnSync('git', ['--git-dir', gitDir, 'show-ref', '--head'], { encoding: 'utf8', env: trustedGitEnvironment() });
1028
+ if (result.status !== 0) return '';
1029
+ return crypto.createHash('sha256').update(String(result.stdout || '')).digest('hex');
1030
+ }
1031
+
1032
+ function prepareReviewSandbox({ root, taskId, engine }) {
1033
+ const run = (args, options = {}) => spawnSync('git', args, {
1034
+ cwd: options.cwd || root,
1035
+ encoding: 'utf8',
1036
+ env: trustedGitEnvironment(),
1037
+ timeout: options.timeout || 30000,
1038
+ });
1039
+ const runConductorGit = (args) => spawnSync('git', args, {
1040
+ cwd: root,
1041
+ encoding: 'utf8',
1042
+ env: conductorGitEnvironment(),
1043
+ timeout: 30000,
1044
+ });
1045
+ const fetched = runConductorGit(['fetch', 'origin', 'master']);
1046
+ const original = runConductorGit(['remote', 'get-url', 'origin']);
1047
+ const base = run(['rev-parse', '--verify', 'origin/master^{commit}']);
1048
+ if (fetched.status !== 0 || original.status !== 0 || base.status !== 0) {
1049
+ return { ok: false, detail: 'review-only dispatch requires a current protected origin/master' };
1050
+ }
1051
+ const originalUrl = String(original.stdout || '').trim();
1052
+ const protectedMaster = remoteMasterOid(root, originalUrl);
1053
+ const protectedRefs = remoteRefsDigest(root, originalUrl);
1054
+ const baseOid = String(base.stdout || '').trim();
1055
+ if (!protectedMaster || !protectedRefs || baseOid !== protectedMaster) {
1056
+ return { ok: false, detail: 'review-only origin/master is not at the protected remote snapshot' };
1057
+ }
1058
+
1059
+ const token = `${String(taskId || 'task').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'task'}-${process.pid}-${crypto.randomBytes(5).toString('hex')}`;
1060
+ const sandboxRoot = fs.mkdtempSync(path.join(os.tmpdir(), `atris-one-lap-${token}-`));
1061
+ const worktreePath = path.join(sandboxRoot, 'worktree');
1062
+ const gitDir = path.join(sandboxRoot, 'objects.git');
1063
+ const quarantine = path.join(sandboxRoot, 'quarantine.git');
1064
+ const seedDir = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-one-lap-seed-'));
1065
+ const seedBundle = path.join(seedDir, 'seed.bundle');
1066
+ const branch = `one-lap-${token}`;
1067
+ const cleanup = () => {
1068
+ fs.rmSync(seedDir, { recursive: true, force: true });
1069
+ fs.rmSync(worktreePath, { recursive: true, force: true });
1070
+ fs.rmSync(gitDir, { recursive: true, force: true });
1071
+ fs.rmSync(quarantine, { recursive: true, force: true });
1072
+ fs.rmSync(sandboxRoot, { recursive: true, force: true });
1073
+ };
1074
+
1075
+ try {
1076
+ fs.mkdirSync(sandboxRoot, { recursive: true });
1077
+ let result = run(['bundle', 'create', seedBundle, 'origin/master']);
1078
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not create seed bundle').trim());
1079
+ result = run(['init', '--bare', '--initial-branch=master', gitDir], { cwd: sandboxRoot });
1080
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not initialize sandbox object store').trim());
1081
+ result = spawnSync('git', ['--git-dir', gitDir, 'bundle', 'unbundle', seedBundle], { encoding: 'utf8', timeout: 30000 });
1082
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not seed sandbox object store').trim());
1083
+ result = run(['init', '--bare', '--initial-branch=master', quarantine], { cwd: sandboxRoot });
1084
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not initialize quarantine remote').trim());
1085
+ result = spawnSync('git', ['--git-dir', quarantine, 'bundle', 'unbundle', seedBundle], { encoding: 'utf8', timeout: 30000 });
1086
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not seed quarantine remote').trim());
1087
+ for (const [repo, ref, oid] of [
1088
+ [gitDir, `refs/heads/${branch}`, baseOid],
1089
+ [gitDir, 'refs/remotes/origin/master', baseOid],
1090
+ [quarantine, 'refs/heads/master', baseOid],
1091
+ ]) {
1092
+ result = spawnSync('git', ['--git-dir', repo, 'update-ref', ref, oid], { encoding: 'utf8' });
1093
+ if (result.status !== 0) throw new Error(String(result.stderr || `could not seed ${ref}`).trim());
1094
+ }
1095
+ const configure = (args) => spawnSync('git', ['--git-dir', gitDir, 'config', ...args], { encoding: 'utf8' });
1096
+ for (const args of [
1097
+ ['extensions.worktreeConfig', 'true'],
1098
+ ['remote.origin.url', quarantine],
1099
+ ['remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*'],
1100
+ ['user.name', String(runConductorGit(['config', '--get', 'user.name']).stdout || 'Atris One Lap').trim() || 'Atris One Lap'],
1101
+ ['user.email', String(runConductorGit(['config', '--get', 'user.email']).stdout || 'one-lap@localhost').trim() || 'one-lap@localhost'],
1102
+ ]) {
1103
+ result = configure(args);
1104
+ if (result.status !== 0) throw new Error(String(result.stderr || 'could not configure sandbox').trim());
1105
+ }
1106
+ result = spawnSync('git', ['--git-dir', gitDir, 'worktree', 'add', '--no-checkout', worktreePath, branch], {
1107
+ encoding: 'utf8',
1108
+ env: trustedGitEnvironment(),
1109
+ timeout: 30000,
1110
+ });
1111
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not create sandbox worktree').trim());
1112
+ result = spawnSync('git', ['-C', worktreePath, 'config', '--worktree', 'core.bare', 'false'], { encoding: 'utf8' });
1113
+ if (result.status !== 0) throw new Error(String(result.stderr || 'could not activate sandbox worktree').trim());
1114
+ const worktreeGitDirResult = spawnSync('git', ['-C', worktreePath, 'rev-parse', '--absolute-git-dir'], { encoding: 'utf8' });
1115
+ if (worktreeGitDirResult.status !== 0) throw new Error(String(worktreeGitDirResult.stderr || 'could not locate sandbox worktree metadata').trim());
1116
+ const worktreeGitDir = String(worktreeGitDirResult.stdout || '').trim();
1117
+ result = spawnSync('git', ['-C', worktreePath, 'read-tree', 'HEAD'], { encoding: 'utf8', env: trustedGitEnvironment() });
1118
+ if (result.status !== 0) throw new Error(String(result.stderr || 'could not seed sandbox index').trim());
1119
+ materializeVerifiedTree(worktreePath, [], readGitTree(gitDir, baseOid));
1120
+ fs.mkdirSync(path.join(worktreePath, '.atris'), { recursive: true });
1121
+ fs.writeFileSync(path.join(worktreePath, '.atris', 'agent-worktree.json'), `${JSON.stringify({
1122
+ agent: engine || null,
1123
+ owner: engine || 'one-lap',
1124
+ task: String(taskId || ''),
1125
+ branch,
1126
+ base: 'origin/master',
1127
+ sealed_review_sandbox: true,
1128
+ created_at: new Date().toISOString(),
1129
+ }, null, 2)}\n`, 'utf8');
1130
+ fs.rmSync(seedDir, { recursive: true, force: true });
1131
+
1132
+ const boundary = {
1133
+ ok: true,
1134
+ trustedRoot: root,
1135
+ sandboxRoot,
1136
+ worktreePath,
1137
+ gitDir,
1138
+ worktreeGitDir,
1139
+ quarantine,
1140
+ originalUrl,
1141
+ protectedMaster,
1142
+ protectedRefs,
1143
+ quarantineRefs: bareRefsDigest(quarantine),
1144
+ branch,
1145
+ baseOid,
1146
+ };
1147
+ boundary.metadataDigest = reviewSandboxMetadataDigest(boundary);
1148
+ const configText = fs.readFileSync(path.join(gitDir, 'config'), 'utf8');
1149
+ const gitFile = fs.readFileSync(path.join(worktreePath, '.git'), 'utf8');
1150
+ if (!boundary.quarantineRefs || configText.includes(originalUrl) || configText.includes(root) || gitFile.includes(root)) {
1151
+ throw new Error('sealed sandbox leaked a protected repository locator');
1152
+ }
1153
+ return boundary;
1154
+ } catch (error) {
1155
+ cleanup();
1156
+ return { ok: false, detail: `review-only sandbox could not be prepared: ${error.message}` };
1157
+ }
1158
+ }
1159
+
1160
+ function reviewRemoteBoundaryState(worktreePath, boundary) {
1161
+ if (!boundary || !boundary.ok) {
1162
+ return { ok: false, stage: 'remote_quarantine', detail: 'review-only sealed sandbox is not armed' };
1163
+ }
1164
+ let metadataDigest = '';
1165
+ try { metadataDigest = reviewSandboxMetadataDigest(boundary); } catch {}
1166
+ if (!metadataDigest || metadataDigest !== boundary.metadataDigest) {
1167
+ return {
1168
+ ok: false,
1169
+ stage: 'sandbox_metadata_changed',
1170
+ detail: 'the worker changed sealed Git metadata',
1171
+ protected_master: boundary.protectedMaster,
1172
+ };
1173
+ }
1174
+ const protectedMaster = remoteMasterOid(boundary.trustedRoot, boundary.originalUrl);
1175
+ const protectedRefs = remoteRefsDigest(boundary.trustedRoot, boundary.originalUrl);
1176
+ const quarantineMasterResult = spawnSync('git', ['--git-dir', boundary.quarantine, 'rev-parse', '--verify', 'refs/heads/master^{commit}'], { encoding: 'utf8' });
1177
+ const quarantineMaster = quarantineMasterResult.status === 0 ? String(quarantineMasterResult.stdout || '').trim() : '';
1178
+ const quarantineRefs = bareRefsDigest(boundary.quarantine);
1179
+ if (!protectedMaster || !protectedRefs || protectedMaster !== boundary.protectedMaster || protectedRefs !== boundary.protectedRefs) {
1180
+ return {
1181
+ ok: false,
1182
+ stage: 'master_changed',
1183
+ detail: 'the protected remote changed during isolated execution',
1184
+ protected_master: protectedMaster || null,
1185
+ };
1186
+ }
1187
+ if (!quarantineMaster || !quarantineRefs || quarantineMaster !== boundary.protectedMaster || quarantineRefs !== boundary.quarantineRefs) {
1188
+ return {
1189
+ ok: false,
1190
+ stage: 'outbound_attempt',
1191
+ detail: 'the worker attempted to change the quarantined remote',
1192
+ protected_master: protectedMaster,
1193
+ };
1194
+ }
1195
+ return { ok: true, protected_master: protectedMaster };
1196
+ }
1197
+
1198
+ function reviewCandidateSnapshot(boundary, expected = null) {
1199
+ const fail = (detail, extra = {}) => ({ ok: false, stage: 'candidate_changed', detail, ...extra });
1200
+ const run = (args) => spawnSync('git', args, {
1201
+ cwd: boundary.worktreePath,
1202
+ encoding: 'utf8',
1203
+ env: trustedGitEnvironment(),
1204
+ timeout: 30000,
1205
+ });
1206
+ const firstHead = run(['rev-parse', '--verify', 'HEAD^{commit}']);
1207
+ const tree = run(['rev-parse', '--verify', 'HEAD^{tree}']);
1208
+ const status = run(['status', '--porcelain=v1', '--untracked-files=all']);
1209
+ const secondHead = run(['rev-parse', '--verify', 'HEAD^{commit}']);
1210
+ const failed = [firstHead, tree, status, secondHead].find((result) => result.status !== 0);
1211
+ if (failed) return fail(String(failed.stderr || failed.stdout || 'could not freeze the executor candidate').trim());
1212
+ const commit = String(firstHead.stdout || '').trim();
1213
+ const repeatedCommit = String(secondHead.stdout || '').trim();
1214
+ const treeOid = String(tree.stdout || '').trim();
1215
+ if (commit !== repeatedCommit) return fail('the executor changed HEAD while its candidate was being frozen');
1216
+ const conductorEntry = /^\?\? \.atris\/(?:agent-worktree\.json|state\/briefs\.jsonl)$/;
1217
+ const dirty = String(status.stdout || '').trim().split(/\r?\n/).filter(Boolean)
1218
+ .filter((line) => !conductorEntry.test(line));
1219
+ if (dirty.length) return fail(`the executor left uncommitted changes: ${dirty.slice(0, 10).join(', ')}`);
1220
+ if (expected && (commit !== expected.commit || treeOid !== expected.tree)) {
1221
+ return fail('the sealed candidate changed after the executor exited', { commit, tree: treeOid });
1222
+ }
1223
+ return { ok: true, stage: 'candidate_frozen', commit, tree: treeOid };
1224
+ }
1225
+
1226
+ function trustedGitEnvironment() {
1227
+ const env = {
1228
+ ...process.env,
1229
+ GIT_CONFIG_NOSYSTEM: '1',
1230
+ GIT_CONFIG_SYSTEM: '/dev/null',
1231
+ GIT_CONFIG_GLOBAL: '/dev/null',
1232
+ GIT_ATTR_NOSYSTEM: '1',
1233
+ GIT_TERMINAL_PROMPT: '0',
1234
+ };
1235
+ for (const key of Object.keys(env)) {
1236
+ if (/^GIT_CONFIG_(?:COUNT|KEY_\d+|VALUE_\d+)$/.test(key)) delete env[key];
1237
+ }
1238
+ for (const key of [
1239
+ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',
1240
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_CONFIG_PARAMETERS',
1241
+ ]) delete env[key];
1242
+ return env;
1243
+ }
1244
+
1245
+ function readGitTree(gitDir, ref) {
1246
+ const env = trustedGitEnvironment();
1247
+ const listed = spawnSync('git', ['--git-dir', gitDir, 'ls-tree', '-rz', '--full-tree', ref], {
1248
+ encoding: 'utf8',
1249
+ env,
1250
+ maxBuffer: 64 * 1024 * 1024,
1251
+ });
1252
+ if (listed.status !== 0) throw new Error(String(listed.stderr || 'could not read verified tree').trim());
1253
+ const entries = String(listed.stdout || '').split('\0').filter(Boolean).map((line) => {
1254
+ const match = line.match(/^([0-7]{6}) (blob|commit) ([0-9a-f]{40,64})\t([^]*)$/);
1255
+ if (!match) throw new Error('verified tree contains an unreadable entry');
1256
+ const [, mode, type, oid, relative] = match;
1257
+ if (type !== 'blob' || !['100644', '100755', '120000'].includes(mode)) {
1258
+ throw new Error(`verified tree entry is not a regular file or symlink: ${relative}`);
1259
+ }
1260
+ if (path.isAbsolute(relative) || relative.split('/').some((part) => !part || part === '.' || part === '..')) {
1261
+ throw new Error(`verified tree contains an unsafe path: ${relative}`);
1262
+ }
1263
+ if (!/^[\x20-\x7e]+$/.test(relative)) {
1264
+ throw new Error(`verified tree contains a non-ASCII path that cannot be imported safely: ${relative}`);
1265
+ }
1266
+ const folded = relative.toLowerCase();
1267
+ if (folded === '.atris' || folded === '.atris/agent-worktree.json' || folded === '.git' || folded.startsWith('.git/')) {
1268
+ throw new Error('verified tree conflicts with Atris worktree metadata');
1269
+ }
1270
+ return { mode, oid, relative };
1271
+ });
1272
+ const uniqueOids = [...new Set(entries.map((entry) => entry.oid))];
1273
+ const batch = spawnSync('git', ['--git-dir', gitDir, 'cat-file', '--batch'], {
1274
+ encoding: null,
1275
+ env,
1276
+ input: `${uniqueOids.join('\n')}\n`,
1277
+ maxBuffer: 256 * 1024 * 1024,
1278
+ });
1279
+ if (batch.status !== 0) throw new Error(String(batch.stderr || 'could not read verified blobs').trim());
1280
+ const blobs = new Map();
1281
+ let offset = 0;
1282
+ for (const requestedOid of uniqueOids) {
1283
+ const newline = batch.stdout.indexOf(0x0a, offset);
1284
+ if (newline === -1) throw new Error('verified blob batch ended before its header');
1285
+ const header = batch.stdout.subarray(offset, newline).toString('utf8').split(/\s+/);
1286
+ const size = Number(header[2]);
1287
+ if (header[0] !== requestedOid || header[1] !== 'blob' || !Number.isSafeInteger(size) || size < 0) {
1288
+ throw new Error('verified blob batch returned an invalid object');
1289
+ }
1290
+ const start = newline + 1;
1291
+ const end = start + size;
1292
+ if (end >= batch.stdout.length || batch.stdout[end] !== 0x0a) throw new Error('verified blob batch returned a truncated object');
1293
+ blobs.set(requestedOid, Buffer.from(batch.stdout.subarray(start, end)));
1294
+ offset = end + 1;
1295
+ }
1296
+ return entries.map((entry) => ({ ...entry, data: blobs.get(entry.oid) }));
1297
+ }
1298
+
1299
+ function materializeVerifiedTree(landingPath, baseEntries, sourceEntries) {
1300
+ const within = (relative) => {
1301
+ const absolute = path.resolve(landingPath, relative);
1302
+ if (absolute !== landingPath && !absolute.startsWith(`${path.resolve(landingPath)}${path.sep}`)) {
1303
+ throw new Error(`verified tree path escapes the landing worktree: ${relative}`);
1304
+ }
1305
+ return absolute;
1306
+ };
1307
+ for (const entry of [...baseEntries].sort((a, b) => b.relative.length - a.relative.length)) {
1308
+ fs.rmSync(within(entry.relative), { recursive: true, force: true });
1309
+ }
1310
+ for (const entry of sourceEntries) {
1311
+ const target = within(entry.relative);
1312
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1313
+ fs.rmSync(target, { recursive: true, force: true });
1314
+ if (entry.mode === '120000') {
1315
+ fs.symlinkSync(entry.data.toString('utf8'), target);
1316
+ } else {
1317
+ fs.writeFileSync(target, entry.data, { mode: entry.mode === '100755' ? 0o755 : 0o644 });
1318
+ fs.chmodSync(target, entry.mode === '100755' ? 0o755 : 0o644);
1319
+ }
1320
+ }
1321
+ const sourceByPath = new Map(sourceEntries.map((entry) => [entry.relative, entry]));
1322
+ for (const entry of sourceEntries) {
1323
+ const target = within(entry.relative);
1324
+ const stat = fs.lstatSync(target);
1325
+ if (entry.mode === '120000') {
1326
+ if (!stat.isSymbolicLink() || fs.readlinkSync(target) !== entry.data.toString('utf8')) {
1327
+ throw new Error(`materialized symlink does not match verified tree: ${entry.relative}`);
1328
+ }
1329
+ } else if (!stat.isFile() || !fs.readFileSync(target).equals(entry.data) || Boolean(stat.mode & 0o111) !== (entry.mode === '100755')) {
1330
+ throw new Error(`materialized file does not match verified tree: ${entry.relative}`);
1331
+ }
1332
+ }
1333
+ for (const entry of baseEntries) {
1334
+ if (!sourceByPath.has(entry.relative) && fs.existsSync(within(entry.relative))) {
1335
+ throw new Error(`deleted verified path remains in landing worktree: ${entry.relative}`);
1336
+ }
1337
+ }
1338
+ }
1339
+
1340
+ function persistVerifiedCommit(boundary, landingPath, headOid) {
1341
+ const transferDir = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-one-lap-proof-'));
1342
+ const bundle = path.join(transferDir, 'verified.bundle');
1343
+ const proofRef = `refs/atris/one-lap/${headOid}`;
1344
+ const env = trustedGitEnvironment();
1345
+ try {
1346
+ let result = spawnSync('git', ['bundle', 'create', bundle, 'HEAD', `^${boundary.baseOid}`], {
1347
+ cwd: boundary.worktreePath,
1348
+ encoding: 'utf8',
1349
+ env,
1350
+ timeout: 60000,
1351
+ });
1352
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not export proof objects').trim());
1353
+ result = spawnSync('git', ['bundle', 'unbundle', bundle], {
1354
+ cwd: landingPath,
1355
+ encoding: 'utf8',
1356
+ env,
1357
+ timeout: 60000,
1358
+ });
1359
+ if (result.status !== 0) throw new Error(String(result.stderr || result.stdout || 'could not import proof objects').trim());
1360
+ result = spawnSync('git', ['update-ref', proofRef, headOid], { cwd: landingPath, encoding: 'utf8', env });
1361
+ if (result.status !== 0) throw new Error(String(result.stderr || 'could not retain proof ref').trim());
1362
+ result = spawnSync('git', ['cat-file', '-e', `${headOid}^{commit}`], { cwd: landingPath, encoding: 'utf8', env });
1363
+ if (result.status !== 0) throw new Error('the retained proof commit is not readable from the review worktree');
1364
+ return proofRef;
1365
+ } finally {
1366
+ fs.rmSync(transferDir, { recursive: true, force: true });
1367
+ }
1368
+ }
1369
+
1370
+ function bindOneLapProof(worktreePath, proofRef, sourceCommit) {
1371
+ const env = trustedGitEnvironment();
1372
+ const branchResult = spawnSync('git', ['branch', '--show-current'], { cwd: worktreePath, encoding: 'utf8', env });
1373
+ const treeResult = spawnSync('git', ['rev-parse', '--verify', `${proofRef}^{tree}`], { cwd: worktreePath, encoding: 'utf8', env });
1374
+ const branch = String(branchResult.stdout || '').trim();
1375
+ const sourceTree = String(treeResult.stdout || '').trim();
1376
+ if (branchResult.status !== 0 || !branch || treeResult.status !== 0 || !/^[0-9a-f]{40,64}$/.test(sourceTree)) {
1377
+ throw new Error('could not bind the review worktree to its verified proof tree');
1378
+ }
1379
+ const sidecarPath = path.join(worktreePath, '.atris', 'agent-worktree.json');
1380
+ let sidecar;
1381
+ try {
1382
+ sidecar = JSON.parse(fs.readFileSync(sidecarPath, 'utf8'));
1383
+ } catch {
1384
+ throw new Error('could not read review worktree metadata for proof binding');
1385
+ }
1386
+ sidecar.one_lap_proof = {
1387
+ schema: 'atris.one_lap_proof.v1',
1388
+ ref: proofRef,
1389
+ commit: sourceCommit,
1390
+ tree: sourceTree,
1391
+ };
1392
+ fs.writeFileSync(sidecarPath, `${JSON.stringify(sidecar, null, 2)}\n`, 'utf8');
1393
+ const configured = spawnSync('git', ['config', `branch.${branch}.atris-proof-ref`, proofRef], {
1394
+ cwd: worktreePath,
1395
+ encoding: 'utf8',
1396
+ env,
1397
+ });
1398
+ if (configured.status !== 0) throw new Error(String(configured.stderr || 'could not persist one-lap proof binding').trim());
1399
+ return sourceTree;
1400
+ }
1401
+
1402
+ function importReviewCommit(boundary, { cli, taskId, engine, expectedCommit = '', expectedTree = '', startBaseArgs = [] } = {}) {
1403
+ const fail = (stage, detail, extra = {}) => ({ ok: false, stage, detail: String(detail || '').trim().slice(-500), ...extra });
1404
+ const runSandbox = (args) => spawnSync('git', args, {
1405
+ cwd: boundary.worktreePath,
1406
+ encoding: 'utf8',
1407
+ env: trustedGitEnvironment(),
1408
+ timeout: 30000,
1409
+ });
1410
+ const boundaryState = reviewRemoteBoundaryState(boundary.worktreePath, boundary);
1411
+ if (!boundaryState.ok) return boundaryState;
1412
+ if (!expectedCommit || !expectedTree) return fail('candidate_changed', 'the verified candidate identity was not frozen before import');
1413
+ const candidate = reviewCandidateSnapshot(boundary, { commit: expectedCommit, tree: expectedTree });
1414
+ if (!candidate.ok) return candidate;
1415
+ const status = runSandbox(['status', '--porcelain=v1', '--untracked-files=all']);
1416
+ if (status.status !== 0) return fail('review_import', status.stderr || 'could not inspect the sealed sandbox');
1417
+ const dirtyEntries = String(status.stdout || '').trim().split(/\r?\n/).filter(Boolean);
1418
+ const conductorEntries = /^\?\? \.atris\/(?:agent-worktree\.json|fleet-prompt-[^/]+\.md|runtime-tmp(?:\/.*)?|state\/briefs\.jsonl)$/;
1419
+ const uncommitted = dirtyEntries.filter((line) => !conductorEntries.test(line));
1420
+ if (uncommitted.length) return fail('uncommitted_change', `the sealed executor must commit every requested change before Review: ${uncommitted.join(', ')}`);
1421
+ const headOid = expectedCommit;
1422
+ if (headOid === boundary.baseOid) return fail('no_change', 'the executor produced no committed change');
1423
+ if (runSandbox(['merge-base', '--is-ancestor', boundary.baseOid, headOid]).status !== 0) {
1424
+ return fail('review_import', 'the verified commit is not descended from the protected snapshot');
1425
+ }
1426
+ const refs = spawnSync('git', ['--git-dir', boundary.gitDir, 'show-ref'], { encoding: 'utf8', env: trustedGitEnvironment() });
1427
+ if (refs.status !== 0) return fail('review_import', refs.stderr || 'could not inspect sealed refs');
1428
+ const allowedRefs = new Map([
1429
+ [`refs/heads/${boundary.branch}`, headOid],
1430
+ ['refs/remotes/origin/HEAD', boundary.baseOid],
1431
+ ['refs/remotes/origin/master', boundary.baseOid],
1432
+ ]);
1433
+ const refLines = String(refs.stdout || '').trim().split(/\r?\n/).filter(Boolean);
1434
+ const seenRefs = new Set();
1435
+ for (const line of refLines) {
1436
+ const [oid, ref] = line.split(/\s+/, 2);
1437
+ if (!allowedRefs.has(ref) || allowedRefs.get(ref) !== oid) return fail('sandbox_metadata_changed', `the worker created an unexpected Git ref (${ref || 'unknown'})`);
1438
+ seenRefs.add(ref);
1439
+ }
1440
+ for (const ref of [`refs/heads/${boundary.branch}`, 'refs/remotes/origin/master']) {
1441
+ if (!seenRefs.has(ref)) return fail('sandbox_metadata_changed', `the sealed sandbox is missing a required Git ref (${ref})`);
1442
+ }
1443
+
1444
+ let baseEntries;
1445
+ let sourceEntries;
1446
+ try {
1447
+ baseEntries = readGitTree(boundary.gitDir, boundary.baseOid);
1448
+ sourceEntries = readGitTree(boundary.gitDir, headOid);
1449
+ } catch (error) {
1450
+ return fail('review_import', error.message);
1451
+ }
1452
+ const baseByPath = new Map(baseEntries.map((entry) => [entry.relative, entry]));
1453
+ const sourceByPath = new Map(sourceEntries.map((entry) => [entry.relative, entry]));
1454
+ const changedEntries = [...new Set([...baseByPath.keys(), ...sourceByPath.keys()])]
1455
+ .filter((relative) => {
1456
+ const before = baseByPath.get(relative);
1457
+ const after = sourceByPath.get(relative);
1458
+ return !before || !after || before.oid !== after.oid || before.mode !== after.mode;
1459
+ })
1460
+ .sort()
1461
+ .map((relative) => `${baseByPath.has(relative) ? (sourceByPath.has(relative) ? 'M' : 'D') : 'A'} ${relative}`);
1462
+ if (!changedEntries.length) return fail('no_change', 'the verified commit has no tree change');
1463
+
1464
+ const started = cli(['worktree', 'start', '--agent', engine, '--task', `dispatch-${String(taskId).toLowerCase()}`, ...startBaseArgs]);
1465
+ const landingPath = (String(started && started.stdout || '').match(/next: cd (.+)/) || [])[1];
1466
+ if (!landingPath) return fail('worktree_start', String(started && (started.stderr || started.stdout) || 'could not create review worktree'));
1467
+ const worktreePath = landingPath.trim();
1468
+ const landingHead = spawnSync('git', ['rev-parse', '--verify', 'HEAD^{commit}'], {
1469
+ cwd: worktreePath,
1470
+ encoding: 'utf8',
1471
+ env: trustedGitEnvironment(),
1472
+ });
1473
+ if (landingHead.status !== 0 || String(landingHead.stdout || '').trim() !== boundary.baseOid) {
1474
+ return fail('review_import', 'the review worktree was not cut from the verified base', { worktreePath });
1475
+ }
1476
+ let proofRef;
1477
+ let proofTree;
1478
+ try {
1479
+ proofRef = persistVerifiedCommit(boundary, worktreePath, headOid);
1480
+ proofTree = bindOneLapProof(worktreePath, proofRef, headOid);
1481
+ materializeVerifiedTree(worktreePath, baseEntries, sourceEntries);
1482
+ } catch (error) {
1483
+ return fail('review_import', error.message, { worktreePath });
1484
+ }
1485
+ return {
1486
+ ok: true,
1487
+ stage: 'verified_for_review',
1488
+ worktreePath,
1489
+ sourceWorktreePath: boundary.worktreePath,
1490
+ head: headOid,
1491
+ proof_ref: proofRef,
1492
+ proof_tree: proofTree,
1493
+ protected_master: boundary.protectedMaster,
1494
+ change: {
1495
+ has_change: true,
1496
+ base: boundary.baseOid,
1497
+ head: boundary.baseOid,
1498
+ source_commit: headOid,
1499
+ proof_ref: proofRef,
1500
+ proof_tree: proofTree,
1501
+ commit: headOid,
1502
+ dirty: true,
1503
+ changed_entries: changedEntries.slice(0, 100),
1504
+ },
1505
+ };
1506
+ }
1507
+
1508
+ function disposeReviewSandbox(boundary) {
1509
+ spawnSync('git', ['--git-dir', boundary.gitDir, 'worktree', 'remove', '--force', boundary.worktreePath], { encoding: 'utf8' });
1510
+ fs.rmSync(boundary.worktreePath, { recursive: true, force: true });
1511
+ fs.rmSync(boundary.gitDir, { recursive: true, force: true });
1512
+ fs.rmSync(boundary.quarantine, { recursive: true, force: true });
1513
+ fs.rmSync(boundary.sandboxRoot, { recursive: true, force: true });
1514
+ }
1515
+
1516
+ function inspectReviewChange(worktreePath, baseRef = 'origin/master') {
1517
+ const run = (args) => spawnSync('git', args, { cwd: worktreePath, encoding: 'utf8', env: trustedGitEnvironment() });
1518
+ const base = run(['rev-parse', '--verify', `${baseRef}^{commit}`]);
1519
+ const head = run(['rev-parse', '--verify', 'HEAD^{commit}']);
1520
+ if (base.status !== 0 || head.status !== 0) {
1521
+ return {
1522
+ has_change: false,
1523
+ detail: String(base.stderr || head.stderr || 'could not resolve the review change boundary').trim(),
1524
+ };
1525
+ }
1526
+ const baseOid = String(base.stdout || '').trim();
1527
+ const headOid = String(head.stdout || '').trim();
1528
+ const committed = run(['diff', '--quiet', baseOid, headOid, '--']);
1529
+ if (committed.status !== 0 && committed.status !== 1) {
1530
+ return {
1531
+ has_change: false,
1532
+ base: baseOid,
1533
+ head: headOid,
1534
+ detail: String(committed.stderr || 'could not inspect committed changes').trim(),
1535
+ };
1536
+ }
1537
+ const status = run(['status', '--porcelain=v1', '--untracked-files=all']);
1538
+ if (status.status !== 0) {
1539
+ return {
1540
+ has_change: false,
1541
+ base: baseOid,
1542
+ head: headOid,
1543
+ detail: String(status.stderr || 'could not inspect worktree changes').trim(),
1544
+ };
1545
+ }
1546
+ const conductorEntry = /^\?\? \.atris\/(?:agent-worktree\.json|state\/briefs\.jsonl)$/;
1547
+ const dirty = String(status.stdout || '').trim().split('\n').filter(Boolean)
1548
+ .filter((line) => !conductorEntry.test(line));
1549
+ const hasCommittedDiff = committed.status === 1;
1550
+ return {
1551
+ has_change: hasCommittedDiff || dirty.length > 0,
1552
+ base: baseOid,
1553
+ head: headOid,
1554
+ commit: hasCommittedDiff ? headOid : null,
1555
+ dirty: dirty.length > 0,
1556
+ changed_entries: dirty.slice(0, 100),
1557
+ };
1558
+ }
1559
+
1560
+ function reviewWorktreeSnapshot(worktreePath) {
1561
+ const run = (args) => spawnSync('git', args, { cwd: worktreePath, encoding: 'utf8', env: trustedGitEnvironment() });
1562
+ const head = run(['rev-parse', '--verify', 'HEAD^{commit}']);
1563
+ const status = run(['status', '--porcelain=v1', '-z', '--untracked-files=all']);
1564
+ const diff = run(['diff', '--binary', 'HEAD', '--']);
1565
+ const untracked = run(['ls-files', '--others', '--exclude-standard', '-z']);
1566
+ const localConfig = run(['config', '--local', '--null', '--list', '--show-origin']);
1567
+ const worktreeConfig = run(['config', '--worktree', '--null', '--list', '--show-origin']);
1568
+ const refs = run(['show-ref', '--head']);
1569
+ const failed = [head, status, diff, untracked, localConfig, worktreeConfig, refs]
1570
+ .find((result) => result.status !== 0);
1571
+ if (failed) {
1572
+ return {
1573
+ ok: false,
1574
+ detail: String(failed.stderr || failed.stdout || 'could not snapshot validator worktree').trim(),
1575
+ };
1576
+ }
1577
+ const hash = crypto.createHash('sha256');
1578
+ hash.update(String(head.stdout || ''));
1579
+ hash.update('\0status\0');
1580
+ hash.update(String(status.stdout || ''));
1581
+ hash.update('\0diff\0');
1582
+ hash.update(String(diff.stdout || ''));
1583
+ hash.update('\0local-config\0');
1584
+ hash.update(String(localConfig.stdout || ''));
1585
+ hash.update('\0worktree-config\0');
1586
+ hash.update(String(worktreeConfig.stdout || ''));
1587
+ hash.update('\0refs\0');
1588
+ hash.update(String(refs.stdout || ''));
1589
+ for (const relative of String(untracked.stdout || '').split('\0').filter(Boolean).sort()) {
1590
+ hash.update('\0untracked\0');
1591
+ hash.update(relative);
1592
+ const absolute = path.resolve(worktreePath, relative);
1593
+ try {
1594
+ const info = fs.lstatSync(absolute);
1595
+ if (info.isSymbolicLink()) hash.update(`symlink:${fs.readlinkSync(absolute)}`);
1596
+ else if (info.isFile()) hash.update(fs.readFileSync(absolute));
1597
+ else hash.update(`mode:${info.mode}:size:${info.size}`);
1598
+ } catch (error) {
1599
+ return { ok: false, detail: `could not hash validator worktree entry ${relative}: ${error.message}` };
1600
+ }
1601
+ }
1602
+ return {
1603
+ ok: true,
1604
+ head: String(head.stdout || '').trim(),
1605
+ status: String(status.stdout || ''),
1606
+ digest: hash.digest('hex'),
1607
+ };
1608
+ }
1609
+
1610
+ async function runIndependentValidator({
1611
+ root,
1612
+ task,
1613
+ worktreePath,
1614
+ executorEngine,
1615
+ verifierCommand,
1616
+ validatorEngines,
1617
+ validatorDispatcher = null,
1618
+ stateInspector = reviewWorktreeSnapshot,
1619
+ }) {
1620
+ const candidates = [...new Set((validatorEngines || []).map((value) => String(value || '').trim()).filter(Boolean))]
1621
+ .filter((name) => RUNNER_PROFILE_DEFS[name] && name !== executorEngine);
1622
+ if (!candidates.length) {
1623
+ return {
1624
+ ok: false,
1625
+ stage: 'validator_unavailable',
1626
+ validator_result: {
1627
+ engine: null,
1628
+ executor_engine: executorEngine,
1629
+ independent: false,
1630
+ passed: false,
1631
+ verdict: 'unavailable',
1632
+ reason: 'no distinct ready validator is available',
1633
+ exit_code: null,
1634
+ output: '',
1635
+ brief_id: null,
1636
+ worktree_unchanged: null,
1637
+ },
1638
+ };
1639
+ }
1640
+ const dispatch = validatorDispatcher || ((entry) => Promise.resolve(dispatchToEngine({
1641
+ task,
1642
+ engine: entry.engine,
1643
+ worktreePath,
1644
+ root,
1645
+ prompt: entry.prompt,
1646
+ environment: reviewOnlyEngineEnvironment(worktreePath, {
1647
+ engine: entry.engine,
1648
+ network: true,
1649
+ writable: false,
1650
+ }),
1651
+ sealed: true,
1652
+ allowedTools: VALIDATOR_ALLOWED_TOOLS,
1653
+ skipBriefCapture: true,
1654
+ })));
1655
+
1656
+ for (let index = 0; index < candidates.length; index += 1) {
1657
+ const validatorEngine = candidates[index];
1658
+ const prompt = buildOneLapValidatorPrompt(task, { verifierCommand, executorEngine });
1659
+ const before = stateInspector(worktreePath);
1660
+ if (!before || before.ok !== true) {
1661
+ return {
1662
+ ok: false,
1663
+ stage: 'validator_snapshot_failed',
1664
+ validator_result: {
1665
+ engine: validatorEngine,
1666
+ executor_engine: executorEngine,
1667
+ independent: true,
1668
+ passed: false,
1669
+ verdict: 'invalid',
1670
+ reason: String(before && before.detail || 'could not snapshot worktree before validation'),
1671
+ exit_code: null,
1672
+ output: '',
1673
+ brief_id: null,
1674
+ worktree_unchanged: null,
1675
+ },
1676
+ };
1677
+ }
1678
+ let dispatched;
1679
+ try {
1680
+ dispatched = normalizeDispatchResult(await dispatch({
1681
+ task,
1682
+ engine: validatorEngine,
1683
+ worktreePath,
1684
+ prompt,
1685
+ }), validatorEngine);
1686
+ } catch (error) {
1687
+ dispatched = normalizeDispatchResult({ exitCode: 1, stderr: error.message || String(error) }, validatorEngine);
1688
+ }
1689
+ const after = stateInspector(worktreePath);
1690
+ const output = dispatchResultOutput(dispatched).slice(-8000);
1691
+ const verdictOutput = [dispatched.report, dispatched.stdout]
1692
+ .map((value) => String(value || ''))
1693
+ .filter(Boolean)
1694
+ .join('\n');
1695
+ const verdict = parseOneLapValidatorVerdict(verdictOutput);
1696
+ const unchanged = Boolean(after && after.ok === true && before.digest === after.digest);
1697
+ const exitCode = dispatchResultExitCode(dispatched);
1698
+ const validatorResult = {
1699
+ engine: validatorEngine,
1700
+ executor_engine: executorEngine,
1701
+ independent: validatorEngine !== executorEngine,
1702
+ passed: exitCode === 0 && verdict.passed === true && unchanged,
1703
+ verdict: verdict.verdict,
1704
+ reason: unchanged ? verdict.reason : String(after && after.detail || 'validator changed the worktree'),
1705
+ exit_code: exitCode,
1706
+ output,
1707
+ brief_id: dispatched.brief_id || null,
1708
+ worktree_unchanged: unchanged,
1709
+ };
1710
+ if (!unchanged) return { ok: false, stage: 'validator_mutated_worktree', validator_result: validatorResult };
1711
+ if (exitCode !== 0) {
1712
+ const outage = detectDeadEngineDispatch(dispatched);
1713
+ if (outage && outage.reason === 'usage_limit' && index + 1 < candidates.length) continue;
1714
+ validatorResult.reason = String(dispatched.stderr || verdict.reason || `validator exited ${exitCode}`).trim().slice(-500);
1715
+ return { ok: false, stage: 'validator_failed', validator_result: validatorResult };
1716
+ }
1717
+ if (verdict.verdict === 'reject') return { ok: false, stage: 'validation_rejected', validator_result: validatorResult };
1718
+ if (!verdict.passed) return { ok: false, stage: 'validator_failed', validator_result: validatorResult };
1719
+ return { ok: true, stage: 'validator_signed_off', validator_result: validatorResult };
1720
+ }
1721
+ return { ok: false, stage: 'validator_failed', validator_result: null };
1722
+ }
1723
+
1724
+ function dispatchReceiptResult(flight, { ids, reviewOnly, enforceRemoteBoundary }) {
1725
+ const validatorResult = flight.validator_result || flight.result?.validator_result;
1726
+ const successfulRows = reviewOnly ? flight.ready : flight.landed;
1727
+ const successfulVerifierRows = successfulRows
1728
+ .map((row) => row.verifier_result)
1729
+ .filter((row) => row && typeof row === 'object');
1730
+ const verifierRows = [
1731
+ ...successfulVerifierRows,
1732
+ ...flight.paused.map((row) => row.verifier_result).filter((row) => row && typeof row === 'object'),
1733
+ ];
1734
+ const basePassed = flight.paused.length === 0
1735
+ && successfulRows.length === ids.length
1736
+ && successfulVerifierRows.length === ids.length
1737
+ && successfulVerifierRows.every((row) => row.passed === true);
1738
+ const oneLapReview = reviewOnly && flight.context && flight.context.source === 'one_lap';
1739
+ const validatorPassed = validatorResult
1740
+ && validatorResult.passed === true
1741
+ && validatorResult.independent === true
1742
+ && validatorResult.worktree_unchanged === true
1743
+ && validatorResult.engine
1744
+ && validatorResult.executor_engine
1745
+ && validatorResult.engine !== validatorResult.executor_engine;
1746
+ const passed = basePassed && (!oneLapReview || validatorPassed === true);
1747
+ const result = {
1748
+ kind: reviewOnly ? 'dispatch_review_ready' : 'dispatch_landed',
1749
+ passed,
1750
+ verifier_result: verifierRows.length === 1
1751
+ ? verifierRows[0]
1752
+ : { passed, checks: verifierRows },
1753
+ };
1754
+ if (reviewOnly) {
1755
+ result.master_boundary_enforced = enforceRemoteBoundary;
1756
+ result.master_unchanged = enforceRemoteBoundary
1757
+ ? flight.ready.length === ids.length && flight.ready.every((row) => row.master_before && row.master_before === row.master_after)
1758
+ : null;
1759
+ }
1760
+ if (validatorResult && typeof validatorResult === 'object') {
1761
+ result.validator_result = validatorResult;
1762
+ }
1763
+ return result;
1764
+ }
1765
+
1766
+ function writeDispatchReceipt(flight, receiptPath, resultOptions) {
1767
+ flight.result = dispatchReceiptResult(flight, resultOptions);
1768
+ flight.receipt = receiptPath;
1769
+ fs.mkdirSync(path.dirname(receiptPath), { recursive: true });
1770
+ const tempPath = `${receiptPath}.tmp-${process.pid}`;
1771
+ fs.writeFileSync(tempPath, `${JSON.stringify(flight, null, 2)}\n`);
1772
+ fs.renameSync(tempPath, receiptPath);
1773
+ }
1774
+
1775
+ function stampDispatchResultVerification(flight, taskId, engine, startedAtMs, verifierResult) {
1776
+ if (!verifierResult || typeof verifierResult.passed !== 'boolean') return;
1777
+ const row = flight.results.find((result) => result.task === taskId);
1778
+ if (!row) return;
1779
+ const completedAtMs = Date.now();
1780
+ row.engine = engine;
1781
+ row.verified_passed = verifierResult.passed;
1782
+ row.duration_ms = Math.max(0, completedAtMs - startedAtMs);
1783
+ row.at = new Date(completedAtMs).toISOString();
283
1784
  }
284
1785
 
285
1786
  function readProjectionTasks(root) {
@@ -346,6 +1847,79 @@ function defaultVerifyRunner(command, cwd) {
346
1847
  return { status: result.status, stdout: String(result.stdout || ''), stderr: String(result.stderr || '') };
347
1848
  }
348
1849
 
1850
+ function defaultTrustedVerifyRunner(command, cwd) {
1851
+ const parsed = require('./auto-accept-certified').parseVerifyCommand(command);
1852
+ if (!parsed.ok) return { status: 2, stdout: '', stderr: parsed.reason };
1853
+ const worktreeRoot = path.resolve(cwd);
1854
+ const insideWorktree = (target) => target === worktreeRoot || target.startsWith(`${worktreeRoot}${path.sep}`);
1855
+ const commandCwd = parsed.cwd ? path.resolve(worktreeRoot, parsed.cwd) : worktreeRoot;
1856
+ if (!insideWorktree(commandCwd)) {
1857
+ return { status: 2, stdout: '', stderr: 'verify_workdir_outside_worktree' };
1858
+ }
1859
+ if (parsed.argv[0] === 'git' && parsed.argv[1] === '-C') {
1860
+ const gitCwd = path.resolve(commandCwd, parsed.argv[2]);
1861
+ if (!insideWorktree(gitCwd)) {
1862
+ return { status: 2, stdout: '', stderr: 'verify_git_path_outside_worktree' };
1863
+ }
1864
+ }
1865
+ let boundaryEnv;
1866
+ try {
1867
+ boundaryEnv = reviewOnlyEngineEnvironment(worktreeRoot);
1868
+ } catch (error) {
1869
+ return { status: 2, stdout: '', stderr: error.message || String(error) };
1870
+ }
1871
+ const sandboxProfile = String(boundaryEnv.ATRIS_ONE_LAP_SANDBOX_PROFILE || '');
1872
+ const runtimeDir = String(boundaryEnv.ATRIS_ONE_LAP_RUNTIME_DIR || '');
1873
+ const controlDir = String(boundaryEnv.ATRIS_ONE_LAP_CONTROL_DIR || '');
1874
+ const lifecycleWrapper = String(boundaryEnv.ATRIS_ONE_LAP_LIFECYCLE_WRAPPER || '');
1875
+ const statusFile = String(boundaryEnv.ATRIS_ONE_LAP_STATUS_FILE || '');
1876
+ delete boundaryEnv.ATRIS_ONE_LAP_SANDBOX_PROFILE;
1877
+ delete boundaryEnv.ATRIS_ONE_LAP_RUNTIME_DIR;
1878
+ delete boundaryEnv.ATRIS_ONE_LAP_CONTROL_DIR;
1879
+ delete boundaryEnv.ATRIS_ONE_LAP_LIFECYCLE_WRAPPER;
1880
+ delete boundaryEnv.ATRIS_ONE_LAP_STATUS_FILE;
1881
+ const executable = sandboxProfile ? '/usr/bin/sandbox-exec' : parsed.argv[0];
1882
+ const args = sandboxProfile
1883
+ ? ['-p', sandboxProfile, lifecycleWrapper, parsed.argv[0], ...parsed.argv.slice(1)]
1884
+ : parsed.argv.slice(1);
1885
+ let result;
1886
+ try {
1887
+ const spawnOptions = {
1888
+ cwd: commandCwd,
1889
+ env: { ...(parsed.env || {}), ...boundaryEnv },
1890
+ encoding: 'utf8',
1891
+ shell: false,
1892
+ timeout: 120000,
1893
+ };
1894
+ result = sandboxProfile
1895
+ ? runInReapedProcessGroup(executable, args, spawnOptions, controlDir, statusFile)
1896
+ : spawnSync(executable, args, spawnOptions);
1897
+ } finally {
1898
+ if (runtimeDir) fs.rmSync(runtimeDir, { recursive: true, force: true });
1899
+ if (controlDir) fs.rmSync(controlDir, { recursive: true, force: true });
1900
+ }
1901
+ return {
1902
+ status: Number.isInteger(result.status) ? result.status : 1,
1903
+ stdout: String(result.stdout || ''),
1904
+ stderr: String(result.stderr || result.error && result.error.message || ''),
1905
+ };
1906
+ }
1907
+
1908
+ function defaultSelfLandCheck({ worktreePath, targetRef = DISPATCH_SELF_LAND_TARGET, git = null } = {}) {
1909
+ const run = git || ((args) => spawnSync('git', args, { cwd: worktreePath, encoding: 'utf8' }));
1910
+ const branch = String(targetRef || '').startsWith('origin/') ? String(targetRef).slice('origin/'.length) : '';
1911
+ const fetch = run(branch ? ['fetch', 'origin', `${branch}:refs/remotes/origin/${branch}`] : ['fetch', 'origin']);
1912
+ if (fetch.status !== 0) {
1913
+ return { ok: false, stage: 'self_land_check', target: targetRef, detail: String(fetch.stderr || fetch.stdout || '').trim() };
1914
+ }
1915
+ const ancestor = run(['merge-base', '--is-ancestor', 'HEAD', targetRef]);
1916
+ if (ancestor.status === 0) return { ok: true, stage: 'self_landed', target: targetRef };
1917
+ if (ancestor.status === 1) {
1918
+ return { ok: false, stage: 'self_land_missing', target: targetRef, detail: `HEAD is not an ancestor of ${targetRef}` };
1919
+ }
1920
+ return { ok: false, stage: 'self_land_check', target: targetRef, detail: String(ancestor.stderr || ancestor.stdout || '').trim() };
1921
+ }
1922
+
349
1923
  // One flight. Staff -> dispatch in parallel -> land serially -> receipt.
350
1924
  // No fleet state file: progress is narrated via `log`, durability lives in
351
1925
  // worktrees, task claims, and the receipt written at the end.
@@ -363,7 +1937,10 @@ async function runFleetFlight({
363
1937
  const cli = ownCli || defaultOwnCli(root);
364
1938
  const roster = engines || (() => {
365
1939
  const { roster: fullRoster } = require('../commands/engine');
366
- return fullRoster(root).filter((e) => e.installed && FLEET_CAPABLE.includes(e.name)).map((e) => e.name);
1940
+ const installed = fullRoster(root)
1941
+ .filter((e) => e.installed && FLEET_CAPABLE.includes(e.name))
1942
+ .map((e) => e.name);
1943
+ return rankFleetEngines(installed, root);
367
1944
  })();
368
1945
 
369
1946
  const staffed = assignEngines(staffFlight(readProjectionTasks(root), { slots }), roster);
@@ -393,8 +1970,17 @@ async function runFleetFlight({
393
1970
 
394
1971
  // Claim + cut a worktree per assignment, then dispatch all in parallel.
395
1972
  const dispatch = dispatcher || ((entry) => new Promise((resolve) => {
396
- resolve(dispatchToEngine({ task: entry.task, engine: entry.engine, worktreePath: entry.worktreePath, root }));
1973
+ resolve(dispatchToEngine({
1974
+ task: entry.task,
1975
+ engine: entry.engine,
1976
+ worktreePath: entry.worktreePath,
1977
+ root,
1978
+ prompt: entry.prompt,
1979
+ briefId: entry.brief_id,
1980
+ skipBriefCapture: true,
1981
+ }));
397
1982
  }));
1983
+ const restaffState = { used: false };
398
1984
 
399
1985
  // Cut every build worktree from origin/master by default, not the launcher's
400
1986
  // HEAD. A flight launched from a long-lived feature-branch checkout would
@@ -417,9 +2003,22 @@ async function runFleetFlight({
417
2003
  }
418
2004
 
419
2005
  const results = await Promise.all(prepared.map((entry) =>
420
- dispatch(entry).then((r) => ({ entry, result: r })).catch((err) => ({ entry, result: { exitCode: 1, report: '', stderr: String(err && err.message || err) } }))
2006
+ dispatchEntryWithRestaff({
2007
+ entry,
2008
+ engine: entry.engine,
2009
+ root,
2010
+ dispatch,
2011
+ installedEngines: roster,
2012
+ restaffState,
2013
+ }).then((result) => ({ entry, result }))
421
2014
  ));
422
- flight.results = results.map(({ entry, result }) => ({ task: entry.task.display_id, engine: entry.engine, exitCode: result.exitCode }));
2015
+ flight.results = results.map(({ entry, result }) => {
2016
+ const row = { task: entry.task.display_id, engine: result.engine || entry.engine, exitCode: result.exitCode };
2017
+ if (result.brief_id) row.brief_id = result.brief_id;
2018
+ if (result.restaffed) row.restaffed = result.restaffed;
2019
+ if (result.deadEngine) row.deadEngine = result.deadEngine;
2020
+ return row;
2021
+ });
423
2022
 
424
2023
  // Land serially: rebase-before-ship, conflict pauses (never auto-resolve).
425
2024
  const land = lander || (({ entry }) => {
@@ -434,20 +2033,38 @@ async function runFleetFlight({
434
2033
  });
435
2034
 
436
2035
  for (const { entry, result } of results) {
2036
+ const activeEngine = result.engine || entry.engine;
2037
+ const landingEntry = { ...entry, engine: activeEngine };
2038
+ if (result.restaffed) log(` restaffed ${entry.task.display_id}: ${result.restaffed.from} -> ${result.restaffed.to} (${result.restaffed.reason})`);
437
2039
  if (result.exitCode !== 0) {
438
- flight.paused.push({ task: entry.task.display_id, engine: entry.engine, stage: 'build', detail: (result.stderr || '').slice(0, 200) });
439
- log(` ${entry.engine.padEnd(8)} ✗ build failed ${entry.task.display_id} — worktree kept for takeover`);
2040
+ const paused = { task: entry.task.display_id, engine: activeEngine, stage: 'build', detail: (result.stderr || '').slice(0, 200) };
2041
+ if (result.restaffed) paused.restaffed = result.restaffed;
2042
+ if (result.deadEngine) paused.deadEngine = result.deadEngine;
2043
+ flight.paused.push(paused);
2044
+ stampDispatchBrief(root, result.brief_id, 'fail', `build failed for ${entry.task.display_id}`);
2045
+ log(` ${activeEngine.padEnd(8)} ✗ build failed ${entry.task.display_id} — worktree kept for takeover`);
440
2046
  continue;
441
2047
  }
442
- log(` ${entry.engine.padEnd(8)} landing ${entry.task.display_id}...`);
443
- const landed = land({ entry, result });
2048
+ log(` ${activeEngine.padEnd(8)} landing ${entry.task.display_id}...`);
2049
+ const landed = land({ entry: landingEntry, result });
444
2050
  if (landed.ok) {
445
- flight.landed.push({ task: entry.task.display_id, engine: entry.engine });
446
- cli(['task', 'ready', String(entry.task.display_id), '--proof', `Built by ${entry.engine} engine in fleet flight, landed via worktree ship gate (rebase-before-ship, verify re-run). Receipt saved at ${path.relative(root, receiptPath)}. Report tail: ${String(result.report || '').slice(-300).replace(/\n/g, ' ')}`, '--as', `fleet-${entry.engine}`]);
447
- log(` ${entry.engine.padEnd(8)} ✓ landed ${entry.task.display_id}`);
2051
+ const landedRow = { task: entry.task.display_id, engine: activeEngine };
2052
+ if (result.brief_id) landedRow.brief_id = result.brief_id;
2053
+ if (result.restaffed) landedRow.restaffed = result.restaffed;
2054
+ flight.landed.push(landedRow);
2055
+ stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.task.display_id} via fleet ship`);
2056
+ const restaffProof = result.restaffed ? ` Restaffed from ${result.restaffed.from} to ${result.restaffed.to} (${result.restaffed.reason}).` : '';
2057
+ cli([
2058
+ 'task', 'ready', String(entry.task.display_id),
2059
+ '--proof', `Built by ${activeEngine} engine in fleet flight.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Receipt saved at ${path.relative(root, receiptPath)}. Report tail: ${String(result.report || '').slice(-300).replace(/\n/g, ' ')}`,
2060
+ '--result', 'Operators can now review fleet-shipped work faster because the worktree was verified before landing.',
2061
+ '--as', `fleet-${activeEngine}`,
2062
+ ]);
2063
+ log(` ${activeEngine.padEnd(8)} ✓ landed ${entry.task.display_id}`);
448
2064
  } else {
449
- flight.paused.push({ task: entry.task.display_id, engine: entry.engine, ...landed });
450
- log(` ${entry.engine.padEnd(8)} ⏸ paused ${entry.task.display_id} at ${landed.stage}${landed.conflicts ? ` (${landed.conflicts.join(', ')})` : ''} — worktree kept`);
2065
+ flight.paused.push({ task: entry.task.display_id, engine: activeEngine, ...(result.restaffed ? { restaffed: result.restaffed } : {}), ...landed });
2066
+ stampDispatchBrief(root, result.brief_id, 'partial', `paused ${entry.task.display_id} at ${landed.stage || 'landing'}`);
2067
+ log(` ${activeEngine.padEnd(8)} ⏸ paused ${entry.task.display_id} at ${landed.stage}${landed.conflicts ? ` (${landed.conflicts.join(', ')})` : ''} — worktree kept`);
451
2068
  }
452
2069
  }
453
2070
 
@@ -483,6 +2100,17 @@ async function runDispatchFlight({
483
2100
  verifier = null,
484
2101
  rebase = null,
485
2102
  checkoutBase = 'origin/master',
2103
+ installedEngines = null,
2104
+ selfLandCheck = null,
2105
+ yolo = false,
2106
+ reviewOnly = false,
2107
+ verifierCommand = '',
2108
+ receiptContext = null,
2109
+ actor = '',
2110
+ changeInspector = null,
2111
+ validatorEngines = null,
2112
+ validatorDispatcher = null,
2113
+ validatorStateInspector = null,
486
2114
  } = {}) {
487
2115
  if (!engine) throw new Error('runDispatchFlight: engine is required');
488
2116
  if (!FLEET_CAPABLE.includes(engine)) {
@@ -493,11 +2121,40 @@ async function runDispatchFlight({
493
2121
  if (promptOverride && ids.length > 1) {
494
2122
  throw new Error('runDispatchFlight: --prompt-file only supports a single task id');
495
2123
  }
2124
+ const trustedVerifier = String(verifierCommand || '').trim();
2125
+ if (trustedVerifier) {
2126
+ const parsedVerifier = require('./auto-accept-certified').parseVerifyCommand(trustedVerifier);
2127
+ if (!parsedVerifier.ok) {
2128
+ throw new Error(`runDispatchFlight: verifier command is not allowed (${parsedVerifier.reason})`);
2129
+ }
2130
+ }
2131
+ if (reviewOnly && !trustedVerifier) {
2132
+ throw new Error('runDispatchFlight: review-only dispatch requires an explicit verifier command');
2133
+ }
496
2134
 
497
2135
  const cli = ownCli || defaultOwnCli(root);
498
- const verify = verifier || defaultVerifyRunner;
2136
+ const verify = verifier || (trustedVerifier ? defaultTrustedVerifyRunner : defaultVerifyRunner);
2137
+ const inspectChange = changeInspector || inspectReviewChange;
2138
+ const enforceRemoteBoundary = reviewOnly && !dispatcher;
2139
+ const explicitActor = String(actor || '').trim();
2140
+ const taskActor = explicitActor || `fleet-${engine}`;
499
2141
  const receiptPath = path.join(root, 'atris', 'runs', `dispatch-${nowStamp()}.json`);
500
- const flight = { at: new Date().toISOString(), root, engine, tasks: ids, results: [], landed: [], paused: [] };
2142
+ const flight = {
2143
+ schema: 'atris.dispatch_receipt.v1',
2144
+ at: new Date().toISOString(),
2145
+ root,
2146
+ engine,
2147
+ tasks: ids,
2148
+ results: [],
2149
+ landed: [],
2150
+ ready: [],
2151
+ paused: [],
2152
+ };
2153
+ if (yolo) flight.yolo = true;
2154
+ if (reviewOnly) flight.review_only = true;
2155
+ flight.actor = taskActor;
2156
+ if (receiptContext && typeof receiptContext === 'object') flight.context = receiptContext;
2157
+ const requiresIndependentValidator = reviewOnly && flight.context && flight.context.source === 'one_lap';
501
2158
 
502
2159
  log('');
503
2160
  log(` dispatch — ${ids.length} task${ids.length === 1 ? '' : 's'} -> ${engine}`);
@@ -514,84 +2171,579 @@ async function runDispatchFlight({
514
2171
  log(` ✗ ${taskId} not found`);
515
2172
  continue;
516
2173
  }
517
- cli(['task', 'claim', taskId, '--as', `fleet-${engine}`]);
518
- const started = cli(['worktree', 'start', '--agent', engine, '--task', `dispatch-${taskId.toLowerCase()}`, ...startBaseArgs]);
519
- const wt = (started.stdout.match(/next: cd (.+)/) || [])[1];
520
- if (!wt) {
521
- flight.paused.push({ task: taskId, stage: 'worktree_start', detail: String(started.stderr || '').slice(0, 200) });
522
- log(` ✗ ${taskId} worktree start failed`);
2174
+ const claimed = cli(['task', 'claim', taskId, '--as', taskActor]);
2175
+ if (!claimed || claimed.status !== 0) {
2176
+ const detail = String(claimed && (claimed.stderr || claimed.stdout) || 'claim failed').trim().slice(0, 300);
2177
+ flight.paused.push({ task: taskId, stage: 'claim', detail });
2178
+ log(` x ${taskId} claim failed`);
523
2179
  continue;
524
2180
  }
525
- prepared.push({ task, taskId, worktreePath: wt.trim() });
526
- log(` building ${taskId} in ${path.basename(wt.trim())}`);
2181
+ let landingWorktreePath = '';
2182
+ let remoteBoundary = null;
2183
+ if (enforceRemoteBoundary) {
2184
+ remoteBoundary = prepareReviewSandbox({ root, taskId, engine });
2185
+ } else {
2186
+ const started = cli(['worktree', 'start', '--agent', engine, '--task', `dispatch-${taskId.toLowerCase()}`, ...startBaseArgs]);
2187
+ const wt = (started.stdout.match(/next: cd (.+)/) || [])[1];
2188
+ if (!wt) {
2189
+ flight.paused.push({ task: taskId, stage: 'worktree_start', detail: String(started.stderr || '').slice(0, 200) });
2190
+ log(` ✗ ${taskId} worktree start failed`);
2191
+ continue;
2192
+ }
2193
+ landingWorktreePath = wt.trim();
2194
+ }
2195
+ if (enforceRemoteBoundary && (!remoteBoundary || remoteBoundary.ok !== true)) {
2196
+ flight.paused.push({
2197
+ task: taskId,
2198
+ stage: 'remote_quarantine',
2199
+ detail: String(remoteBoundary && remoteBoundary.detail || 'could not prepare a sealed review sandbox').slice(-500),
2200
+ worktree: null,
2201
+ });
2202
+ log(` paused ${taskId} because its sealed review sandbox could not be prepared`);
2203
+ continue;
2204
+ }
2205
+ const worktreePath = enforceRemoteBoundary ? remoteBoundary.worktreePath : landingWorktreePath;
2206
+ const basePrompt = promptOverride || buildFleetPrompt(task, { worktreePath, yolo });
2207
+ const safetyPrompt = reviewOnly
2208
+ ? `${basePrompt}\n\nSafety boundary: edit and test this checkout only. Do not push, merge, deploy, publish, send messages, change cloud state, install dependencies, or use credentials.`
2209
+ : basePrompt;
2210
+ const trustedPrompt = trustedVerifier
2211
+ ? `${safetyPrompt}\n\nTrusted verifier (run it bare before reporting done): ${trustedVerifier}`
2212
+ : promptOverride;
2213
+ prepared.push({
2214
+ task,
2215
+ taskId,
2216
+ worktreePath,
2217
+ landingWorktreePath,
2218
+ engine,
2219
+ remoteBoundary,
2220
+ remoteMasterBefore: remoteBoundary ? remoteBoundary.protectedMaster : '',
2221
+ ...(trustedPrompt ? { prompt: trustedPrompt } : {}),
2222
+ });
2223
+ log(` building ${taskId} in ${path.basename(worktreePath)}`);
527
2224
  }
528
2225
 
529
2226
  const dispatch = dispatcher || ((entry) => new Promise((resolve) => {
530
2227
  resolve(dispatchToEngine({
531
2228
  task: entry.task,
532
- engine,
2229
+ engine: entry.engine,
533
2230
  worktreePath: entry.worktreePath,
534
2231
  root,
535
- prompt: promptOverride || undefined,
2232
+ prompt: entry.prompt || promptOverride || undefined,
2233
+ environment: enforceRemoteBoundary ? reviewOnlyEngineEnvironment(entry.worktreePath, {
2234
+ engine: entry.engine,
2235
+ network: true,
2236
+ writable: true,
2237
+ }) : null,
2238
+ sealed: enforceRemoteBoundary,
2239
+ yolo,
2240
+ briefId: entry.brief_id,
2241
+ skipBriefCapture: true,
536
2242
  }));
537
2243
  }));
2244
+ const restaffState = { used: false };
538
2245
 
539
- const results = await Promise.all(prepared.map((entry) =>
540
- dispatch(entry).then((r) => ({ entry, result: r })).catch((err) => ({ entry, result: { exitCode: 1, report: '', stderr: String(err && err.message || err) } }))
541
- ));
542
- flight.results = results.map(({ entry, result }) => ({ task: entry.taskId, exitCode: result.exitCode }));
2246
+ const results = await Promise.all(prepared.map((entry) => {
2247
+ const startedAtMs = Date.now();
2248
+ return dispatchEntryWithRestaff({
2249
+ entry,
2250
+ engine: entry.engine,
2251
+ root,
2252
+ dispatch,
2253
+ installedEngines,
2254
+ restaffState,
2255
+ }).then((result) => {
2256
+ const completedAtMs = Date.now();
2257
+ return {
2258
+ entry,
2259
+ result,
2260
+ startedAtMs,
2261
+ completedAtMs,
2262
+ candidate: enforceRemoteBoundary && result.exitCode === 0
2263
+ ? reviewCandidateSnapshot(entry.remoteBoundary)
2264
+ : null,
2265
+ };
2266
+ });
2267
+ }));
2268
+ flight.results = results.map(({ entry, result, candidate, startedAtMs, completedAtMs }) => {
2269
+ const row = {
2270
+ task: entry.taskId,
2271
+ engine: result.engine || entry.engine,
2272
+ task_type: 'executor',
2273
+ verified_passed: null,
2274
+ duration_ms: Math.max(0, completedAtMs - startedAtMs),
2275
+ at: new Date(completedAtMs).toISOString(),
2276
+ exitCode: result.exitCode,
2277
+ };
2278
+ if (result.brief_id) row.brief_id = result.brief_id;
2279
+ if (result.restaffed) row.restaffed = result.restaffed;
2280
+ if (result.deadEngine) row.deadEngine = result.deadEngine;
2281
+ if (candidate && candidate.ok) {
2282
+ row.candidate_commit = candidate.commit;
2283
+ row.candidate_tree = candidate.tree;
2284
+ }
2285
+ return row;
2286
+ });
543
2287
 
544
- // Land serially: rebase, re-run Check: for real, ship gate re-verifies,
545
- // conflict/verify failure pauses (never auto-resolve).
2288
+ // Land serially: rebase, re-run the trusted verifier for real, then either
2289
+ // stop proof-ready in Review or ship. Conflict/verify failure always pauses.
546
2290
  const rebaseArrival = rebase || landArrival;
2291
+ const checkSelfLand = selfLandCheck || defaultSelfLandCheck;
547
2292
  const land = lander || (({ entry }) => {
548
- const rebased = rebaseArrival({ worktreePath: entry.worktreePath });
2293
+ const rebased = enforceRemoteBoundary
2294
+ ? { ok: true, stage: 'frozen_base' }
2295
+ : rebaseArrival({ worktreePath: entry.worktreePath });
549
2296
  if (!rebased.ok) return rebased;
550
- const check = dispatchCheck(entry.task) || 'git log -1 --oneline';
2297
+ if (enforceRemoteBoundary) {
2298
+ const candidate = reviewCandidateSnapshot(entry.remoteBoundary, entry.candidate);
2299
+ if (!candidate.ok) return candidate;
2300
+ }
2301
+ const change = reviewOnly ? inspectChange(entry.worktreePath, checkoutBase || 'origin/master') : null;
2302
+ if (reviewOnly && (!change || change.has_change !== true)) {
2303
+ return {
2304
+ ok: false,
2305
+ stage: 'no_change',
2306
+ detail: String(change && change.detail || 'the engine produced no committed or worktree diff').slice(-500),
2307
+ change: change || null,
2308
+ };
2309
+ }
2310
+ if (enforceRemoteBoundary && (change.head !== entry.candidate.commit || change.dirty)) {
2311
+ return {
2312
+ ok: false,
2313
+ stage: 'candidate_changed',
2314
+ detail: 'the change selected for verification differs from the frozen executor commit',
2315
+ change,
2316
+ };
2317
+ }
2318
+ const check = trustedVerifier || dispatchCheck(entry.task) || 'git log -1 --oneline';
551
2319
  const verified = verify(check, entry.worktreePath);
2320
+ const verifierResult = {
2321
+ command: check,
2322
+ passed: verified.status === 0,
2323
+ status: verified.status,
2324
+ output: `${verified.stdout || ''}${verified.stderr || ''}`.slice(-4000),
2325
+ ...(entry.candidate && entry.candidate.ok ? {
2326
+ candidate_commit: entry.candidate.commit,
2327
+ candidate_tree: entry.candidate.tree,
2328
+ } : {}),
2329
+ };
552
2330
  if (verified.status !== 0) {
553
2331
  return {
554
2332
  ok: false,
555
2333
  stage: 'verify_failed',
556
2334
  detail: `${verified.stdout}${verified.stderr}`.slice(-500),
557
2335
  verifyOutput: `${verified.stdout}${verified.stderr}`,
2336
+ check,
2337
+ verifier_result: verifierResult,
2338
+ };
2339
+ }
2340
+ if (enforceRemoteBoundary) {
2341
+ const candidate = reviewCandidateSnapshot(entry.remoteBoundary, entry.candidate);
2342
+ if (!candidate.ok) return { ...candidate, check, verifier_result: verifierResult };
2343
+ }
2344
+ if (reviewOnly) {
2345
+ return {
2346
+ ok: true,
2347
+ stage: 'verified_for_review',
2348
+ check,
2349
+ verifyOutput: `${verified.stdout}${verified.stderr}`,
2350
+ verifier_result: verifierResult,
2351
+ change,
558
2352
  };
559
2353
  }
560
- const shipped = cli(fleetShipArgs({ task: entry.task, engine }, check), entry.worktreePath);
2354
+ const shipped = cli(fleetShipArgs({ task: entry.task, engine: entry.engine || engine }, check), entry.worktreePath);
561
2355
  if (shipped.status !== 0 || !/done: worktree shipped/.test(shipped.stdout)) {
562
2356
  return { ok: false, stage: 'ship', detail: (shipped.stderr || shipped.stdout).slice(-500) };
563
2357
  }
564
- return { ok: true, stage: 'shipped', check, verifyOutput: `${verified.stdout}${verified.stderr}` };
2358
+ return {
2359
+ ok: true,
2360
+ stage: 'shipped',
2361
+ check,
2362
+ verifyOutput: `${verified.stdout}${verified.stderr}`,
2363
+ verifier_result: verifierResult,
2364
+ };
565
2365
  });
566
2366
 
567
- for (const { entry, result } of results) {
2367
+ for (const { entry, result, candidate, startedAtMs } of results) {
2368
+ const activeEngine = result.engine || entry.engine || engine;
2369
+ const readyActor = explicitActor || `fleet-${activeEngine}`;
2370
+ const landingEntry = { ...entry, engine: activeEngine, candidate };
2371
+ if (result.restaffed) log(` restaffed ${entry.taskId}: ${result.restaffed.from} -> ${result.restaffed.to} (${result.restaffed.reason})`);
568
2372
  if (result.exitCode !== 0) {
569
- flight.paused.push({ task: entry.taskId, stage: 'build', detail: String(result.stderr || '').slice(0, 300) });
2373
+ const paused = {
2374
+ task: entry.taskId,
2375
+ engine: activeEngine,
2376
+ stage: 'build',
2377
+ detail: String(result.stderr || '').slice(0, 300),
2378
+ worktree: entry.worktreePath,
2379
+ };
2380
+ if (result.restaffed) paused.restaffed = result.restaffed;
2381
+ if (result.deadEngine) paused.deadEngine = result.deadEngine;
2382
+ flight.paused.push(paused);
2383
+ stampDispatchBrief(root, result.brief_id, 'fail', `build failed for ${entry.taskId}`);
570
2384
  log(` ✗ build failed ${entry.taskId} — worktree kept for takeover`);
571
2385
  continue;
572
2386
  }
573
- log(` landing ${entry.taskId}...`);
574
- const landed = land({ entry, result });
2387
+ if (enforceRemoteBoundary && (!candidate || candidate.ok !== true)) {
2388
+ const paused = {
2389
+ task: entry.taskId,
2390
+ engine: activeEngine,
2391
+ stage: candidate && candidate.stage || 'candidate_changed',
2392
+ detail: String(candidate && candidate.detail || 'the executor candidate could not be frozen').slice(-500),
2393
+ worktree: entry.worktreePath,
2394
+ };
2395
+ flight.paused.push(paused);
2396
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: candidate could not be frozen`);
2397
+ log(` paused ${entry.taskId} at ${paused.stage} - worktree kept`);
2398
+ continue;
2399
+ }
2400
+ if (enforceRemoteBoundary) {
2401
+ const boundaryAfterBuild = reviewRemoteBoundaryState(entry.worktreePath, entry.remoteBoundary);
2402
+ if (!boundaryAfterBuild.ok) {
2403
+ flight.paused.push({
2404
+ task: entry.taskId,
2405
+ engine: activeEngine,
2406
+ stage: boundaryAfterBuild.stage,
2407
+ detail: boundaryAfterBuild.detail,
2408
+ worktree: entry.worktreePath,
2409
+ master_before: entry.remoteMasterBefore || null,
2410
+ master_after: boundaryAfterBuild.protected_master || null,
2411
+ });
2412
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: ${boundaryAfterBuild.stage} during build`);
2413
+ log(` paused ${entry.taskId} at ${boundaryAfterBuild.stage} - worktree kept`);
2414
+ continue;
2415
+ }
2416
+ }
2417
+ if (yolo) {
2418
+ log(` checking self-land ${entry.taskId}...`);
2419
+ const selfLanded = checkSelfLand({ entry, result, worktreePath: entry.worktreePath, targetRef: DISPATCH_SELF_LAND_TARGET });
2420
+ if (selfLanded.ok) {
2421
+ const target = selfLanded.target || DISPATCH_SELF_LAND_TARGET;
2422
+ flight.landed.push({
2423
+ task: entry.taskId,
2424
+ engine: activeEngine,
2425
+ landing: 'self',
2426
+ target,
2427
+ verifier_result: {
2428
+ command: `git merge-base --is-ancestor HEAD ${target}`,
2429
+ passed: true,
2430
+ status: 0,
2431
+ output: `HEAD is an ancestor of ${target}`,
2432
+ },
2433
+ ...(result.brief_id ? { brief_id: result.brief_id } : {}),
2434
+ });
2435
+ stampDispatchBrief(root, result.brief_id, 'pass', `self-landed ${entry.taskId}`);
2436
+ log(` ✓ self-landed ${entry.taskId}`);
2437
+ } else {
2438
+ const stage = selfLanded.stage || 'self_land_missing';
2439
+ flight.paused.push({
2440
+ task: entry.taskId,
2441
+ engine,
2442
+ stage,
2443
+ target: selfLanded.target || DISPATCH_SELF_LAND_TARGET,
2444
+ detail: selfLanded.detail || '',
2445
+ });
2446
+ stampDispatchBrief(root, result.brief_id, 'partial', `self-land check paused ${entry.taskId} at ${stage}`);
2447
+ log(` ⏸ paused ${entry.taskId} at ${stage}`);
2448
+ }
2449
+ continue;
2450
+ }
2451
+ log(` ${reviewOnly ? 'checking' : 'landing'} ${entry.taskId}...`);
2452
+ const landed = land({ entry: landingEntry, result });
2453
+ stampDispatchResultVerification(
2454
+ flight,
2455
+ entry.taskId,
2456
+ activeEngine,
2457
+ startedAtMs,
2458
+ landed.verifier_result,
2459
+ );
575
2460
  if (landed.ok) {
576
- flight.landed.push({ task: entry.taskId, engine, check: landed.check });
577
2461
  const verifyTail = String(landed.verifyOutput || '').trim().slice(-1200).replace(/\n/g, ' ');
2462
+ const restaffProof = result.restaffed ? ` Restaffed from ${result.restaffed.from} to ${result.restaffed.to} (${result.restaffed.reason}).` : '';
2463
+ if (reviewOnly) {
2464
+ let finalMaster = entry.remoteMasterBefore;
2465
+ const boundaryBeforeReview = enforceRemoteBoundary
2466
+ ? reviewRemoteBoundaryState(entry.worktreePath, entry.remoteBoundary)
2467
+ : { ok: true, protected_master: '' };
2468
+ if (!boundaryBeforeReview.ok) {
2469
+ flight.paused.push({
2470
+ task: entry.taskId,
2471
+ engine: activeEngine,
2472
+ stage: boundaryBeforeReview.stage,
2473
+ detail: boundaryBeforeReview.detail,
2474
+ worktree: entry.worktreePath,
2475
+ master_before: entry.remoteMasterBefore,
2476
+ master_after: boundaryBeforeReview.protected_master || null,
2477
+ });
2478
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: ${boundaryBeforeReview.stage} before Review`);
2479
+ log(` paused ${entry.taskId} at ${boundaryBeforeReview.stage} - worktree kept`);
2480
+ continue;
2481
+ }
2482
+ finalMaster = boundaryBeforeReview.protected_master || finalMaster;
2483
+ const change = landed.change || inspectChange(entry.worktreePath, checkoutBase || 'origin/master');
2484
+ if (!change || change.has_change !== true) {
2485
+ flight.paused.push({
2486
+ task: entry.taskId,
2487
+ engine: activeEngine,
2488
+ stage: 'no_change',
2489
+ detail: String(change && change.detail || 'the engine produced no committed or worktree diff').slice(-500),
2490
+ check: landed.check,
2491
+ verifier_result: landed.verifier_result,
2492
+ worktree: entry.worktreePath,
2493
+ change: change || null,
2494
+ });
2495
+ stampDispatchBrief(root, result.brief_id, 'partial', `verified ${entry.taskId}, but no code change was found`);
2496
+ log(` paused ${entry.taskId} at no_change - worktree kept`);
2497
+ continue;
2498
+ }
2499
+ let validatorResult = null;
2500
+ if (requiresIndependentValidator) {
2501
+ log(` validating ${entry.taskId} in a fresh context...`);
2502
+ const validation = await runIndependentValidator({
2503
+ root,
2504
+ task: entry.task,
2505
+ worktreePath: entry.worktreePath,
2506
+ executorEngine: activeEngine,
2507
+ verifierCommand: landed.check,
2508
+ validatorEngines,
2509
+ validatorDispatcher,
2510
+ stateInspector: validatorStateInspector || reviewWorktreeSnapshot,
2511
+ });
2512
+ validatorResult = validation.validator_result;
2513
+ flight.validator_result = validatorResult;
2514
+ if (!validation.ok) {
2515
+ flight.paused.push({
2516
+ task: entry.taskId,
2517
+ engine: activeEngine,
2518
+ stage: validation.stage,
2519
+ detail: String(validatorResult && validatorResult.reason || 'independent validator failed').slice(-500),
2520
+ check: landed.check,
2521
+ verifier_result: landed.verifier_result,
2522
+ validator_result: validatorResult,
2523
+ worktree: entry.worktreePath,
2524
+ change,
2525
+ });
2526
+ stampDispatchBrief(root, result.brief_id, 'partial', `validator paused ${entry.taskId} at ${validation.stage}`);
2527
+ log(` paused ${entry.taskId} at ${validation.stage} - worktree kept`);
2528
+ continue;
2529
+ }
2530
+ if (enforceRemoteBoundary) {
2531
+ const candidateAfterValidator = reviewCandidateSnapshot(entry.remoteBoundary, candidate);
2532
+ if (!candidateAfterValidator.ok) {
2533
+ validatorResult.passed = false;
2534
+ validatorResult.reason = candidateAfterValidator.detail;
2535
+ flight.paused.push({
2536
+ task: entry.taskId,
2537
+ engine: activeEngine,
2538
+ stage: candidateAfterValidator.stage,
2539
+ detail: candidateAfterValidator.detail,
2540
+ check: landed.check,
2541
+ verifier_result: landed.verifier_result,
2542
+ validator_result: validatorResult,
2543
+ worktree: entry.worktreePath,
2544
+ change,
2545
+ });
2546
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: candidate changed during validation`);
2547
+ log(` paused ${entry.taskId} at ${candidateAfterValidator.stage} - worktree kept`);
2548
+ continue;
2549
+ }
2550
+ }
2551
+ if (enforceRemoteBoundary) {
2552
+ const boundaryAfterValidator = reviewRemoteBoundaryState(entry.worktreePath, entry.remoteBoundary);
2553
+ if (!boundaryAfterValidator.ok) {
2554
+ validatorResult.passed = false;
2555
+ validatorResult.reason = boundaryAfterValidator.detail;
2556
+ flight.paused.push({
2557
+ task: entry.taskId,
2558
+ engine: activeEngine,
2559
+ stage: boundaryAfterValidator.stage,
2560
+ detail: validatorResult.reason,
2561
+ check: landed.check,
2562
+ verifier_result: landed.verifier_result,
2563
+ validator_result: validatorResult,
2564
+ worktree: entry.worktreePath,
2565
+ master_before: entry.remoteMasterBefore,
2566
+ master_after: boundaryAfterValidator.protected_master || null,
2567
+ change,
2568
+ });
2569
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: ${boundaryAfterValidator.stage} during validation`);
2570
+ log(` paused ${entry.taskId} at ${boundaryAfterValidator.stage} - worktree kept`);
2571
+ continue;
2572
+ }
2573
+ finalMaster = boundaryAfterValidator.protected_master;
2574
+ }
2575
+ log(` validator ${validatorResult.engine} signed off ${entry.taskId}`);
2576
+ }
2577
+ let reviewWorktreePath = entry.worktreePath;
2578
+ let reviewChange = change;
2579
+ if (enforceRemoteBoundary) {
2580
+ const imported = importReviewCommit(entry.remoteBoundary, {
2581
+ cli,
2582
+ taskId: entry.taskId,
2583
+ engine: activeEngine,
2584
+ expectedCommit: candidate.commit,
2585
+ expectedTree: candidate.tree,
2586
+ startBaseArgs,
2587
+ });
2588
+ if (!imported.ok) {
2589
+ flight.paused.push({
2590
+ task: entry.taskId,
2591
+ engine: activeEngine,
2592
+ stage: imported.stage || 'review_import',
2593
+ detail: imported.detail || 'the verified commit could not be imported for Review',
2594
+ check: landed.check,
2595
+ verifier_result: landed.verifier_result,
2596
+ ...(validatorResult ? { validator_result: validatorResult } : {}),
2597
+ worktree: imported.worktreePath || entry.worktreePath,
2598
+ master_before: entry.remoteMasterBefore,
2599
+ master_after: imported.protected_master || finalMaster || null,
2600
+ change,
2601
+ });
2602
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: ${imported.stage || 'review_import'}`);
2603
+ log(` paused ${entry.taskId} at ${imported.stage || 'review_import'} - sandbox kept`);
2604
+ continue;
2605
+ }
2606
+ const boundaryAfterImport = reviewRemoteBoundaryState(entry.worktreePath, entry.remoteBoundary);
2607
+ if (!boundaryAfterImport.ok) {
2608
+ flight.paused.push({
2609
+ task: entry.taskId,
2610
+ engine: activeEngine,
2611
+ stage: boundaryAfterImport.stage,
2612
+ detail: boundaryAfterImport.detail,
2613
+ check: landed.check,
2614
+ verifier_result: landed.verifier_result,
2615
+ ...(validatorResult ? { validator_result: validatorResult } : {}),
2616
+ worktree: imported.worktreePath,
2617
+ master_before: entry.remoteMasterBefore,
2618
+ master_after: boundaryAfterImport.protected_master || null,
2619
+ change,
2620
+ });
2621
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: ${boundaryAfterImport.stage} after import`);
2622
+ log(` paused ${entry.taskId} at ${boundaryAfterImport.stage} after import`);
2623
+ continue;
2624
+ }
2625
+ reviewWorktreePath = imported.worktreePath;
2626
+ reviewChange = imported.change;
2627
+ if (!reviewChange || reviewChange.has_change !== true
2628
+ || reviewChange.source_commit !== candidate.commit
2629
+ || reviewChange.proof_tree !== candidate.tree
2630
+ || imported.head !== candidate.commit) {
2631
+ flight.paused.push({
2632
+ task: entry.taskId,
2633
+ engine: activeEngine,
2634
+ stage: 'review_import',
2635
+ detail: 'the imported review worktree did not preserve the verified change',
2636
+ check: landed.check,
2637
+ verifier_result: landed.verifier_result,
2638
+ ...(validatorResult ? { validator_result: validatorResult } : {}),
2639
+ worktree: reviewWorktreePath,
2640
+ master_before: entry.remoteMasterBefore,
2641
+ master_after: boundaryAfterImport.protected_master || null,
2642
+ change: reviewChange || null,
2643
+ });
2644
+ stampDispatchBrief(root, result.brief_id, 'fail', `blocked ${entry.taskId}: imported review mismatch`);
2645
+ log(` paused ${entry.taskId} at review_import - review worktree kept`);
2646
+ continue;
2647
+ }
2648
+ finalMaster = boundaryAfterImport.protected_master || finalMaster;
2649
+ disposeReviewSandbox(entry.remoteBoundary);
2650
+ }
2651
+ const shipArgs = fleetShipArgs({ task: entry.task, engine: activeEngine }, landed.check);
2652
+ const shellQuote = (value) => /^[A-Za-z0-9_./:-]+$/.test(String(value))
2653
+ ? String(value)
2654
+ : `'${String(value).replace(/'/g, `'"'"'`)}'`;
2655
+ const nextCommand = `cd ${shellQuote(reviewWorktreePath)} && atris ${shipArgs.map(shellQuote).join(' ')}`;
2656
+ const readyRow = {
2657
+ task: entry.taskId,
2658
+ engine: activeEngine,
2659
+ check: landed.check,
2660
+ verifier_result: landed.verifier_result,
2661
+ worktree: reviewWorktreePath,
2662
+ next_action: nextCommand,
2663
+ change: reviewChange,
2664
+ ...(validatorResult ? { validator_result: validatorResult } : {}),
2665
+ review_recorded: false,
2666
+ ...(enforceRemoteBoundary ? {
2667
+ master_before: entry.remoteMasterBefore,
2668
+ master_after: finalMaster,
2669
+ } : {}),
2670
+ };
2671
+ if (result.brief_id) readyRow.brief_id = result.brief_id;
2672
+ if (result.restaffed) readyRow.restaffed = result.restaffed;
2673
+
2674
+ flight.ready.push(readyRow);
2675
+ writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary });
2676
+ const verificationCommit = String(reviewChange.source_commit || reviewChange.commit || reviewChange.head || '').trim();
2677
+ const verificationCitation = verificationCommit
2678
+ ? ` Verification snapshot: commit ${verificationCommit}${reviewChange.dirty ? ' with a worktree diff' : ''}; ${landed.check} passed (exit 0).`
2679
+ : ` Verification snapshot: the worktree diff was present; ${landed.check} passed (exit 0).`;
2680
+ const validatorCitation = validatorResult
2681
+ ? ` Independent validator ${validatorResult.engine} signed off: ${validatorResult.reason}.`
2682
+ : '';
2683
+ const readyResult = cli([
2684
+ 'task', 'ready', entry.taskId,
2685
+ '--proof', `Built by ${activeEngine} engine via one-lap dispatch.${restaffProof} Preserved in isolated worktree ${reviewWorktreePath}. Check re-run: ${landed.check}.${verificationCitation}${validatorCitation} Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
2686
+ '--result', 'Operators can review a completed, verified change before it lands, reducing the risk of unreviewed changes reaching users.',
2687
+ '--landing', 'Operators can review the verified change before it reaches users, reducing the risk of an unreviewed release.',
2688
+ '--checked', `${landed.check} passed in the isolated worktree`,
2689
+ '--tested', 'The requested behavior passed its declared verifier.',
2690
+ '--as', readyActor,
2691
+ ]);
2692
+ if (!readyResult || readyResult.status !== 0) {
2693
+ flight.ready.splice(flight.ready.indexOf(readyRow), 1);
2694
+ flight.paused.push({
2695
+ task: entry.taskId,
2696
+ engine: activeEngine,
2697
+ stage: 'task_ready',
2698
+ detail: String(readyResult && (readyResult.stderr || readyResult.stdout) || 'task ready failed').slice(-500),
2699
+ check: landed.check,
2700
+ verifier_result: landed.verifier_result,
2701
+ worktree: reviewWorktreePath,
2702
+ change: reviewChange,
2703
+ });
2704
+ writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary });
2705
+ stampDispatchBrief(root, result.brief_id, 'partial', `verified ${entry.taskId}, but task ready failed`);
2706
+ log(` paused ${entry.taskId} at task_ready - worktree kept`);
2707
+ continue;
2708
+ }
2709
+ readyRow.review_recorded = true;
2710
+ stampDispatchBrief(root, result.brief_id, 'pass', `verified ${entry.taskId} for Review`);
2711
+ log(` proof ready ${entry.taskId}`);
2712
+ continue;
2713
+ }
2714
+ const landedRow = { task: entry.taskId, engine: activeEngine, check: landed.check };
2715
+ landedRow.verifier_result = landed.verifier_result;
2716
+ if (result.brief_id) landedRow.brief_id = result.brief_id;
2717
+ if (result.restaffed) landedRow.restaffed = result.restaffed;
2718
+ flight.landed.push(landedRow);
2719
+ stampDispatchBrief(root, result.brief_id, 'pass', `landed ${entry.taskId} via engine dispatch`);
578
2720
  cli([
579
2721
  'task', 'ready', entry.taskId,
580
- '--proof', `Built by ${engine} engine via atris engine dispatch, landed via worktree ship gate (rebase-before-ship, verify re-run). Check re-run: ${landed.check}. Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
581
- '--as', `fleet-${engine}`,
2722
+ '--proof', `Built by ${activeEngine} engine via atris engine dispatch.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Check re-run: ${landed.check}. Verify output: ${verifyTail || '(command produced no output, exit 0)'}. Receipt saved at ${path.relative(root, receiptPath)}.`,
2723
+ '--result', 'Operators can now review engine-built work faster because dispatch reran the verifier before landing.',
2724
+ '--landing', 'The verified change is on master and ready for review.',
2725
+ '--checked', `${landed.check} passed before landing`,
2726
+ '--tested', 'The requested behavior passed its declared verifier.',
2727
+ '--as', readyActor,
582
2728
  ]);
583
2729
  log(` ✓ landed ${entry.taskId}`);
584
2730
  } else {
585
- flight.paused.push({ task: entry.taskId, engine, ...landed });
2731
+ flight.paused.push({
2732
+ task: entry.taskId,
2733
+ engine: activeEngine,
2734
+ worktree: entry.worktreePath,
2735
+ ...(result.restaffed ? { restaffed: result.restaffed } : {}),
2736
+ ...landed,
2737
+ });
2738
+ stampDispatchBrief(root, result.brief_id, 'partial', `paused ${entry.taskId} at ${landed.stage || 'landing'}`);
586
2739
  log(` ⏸ paused ${entry.taskId} at ${landed.stage}${landed.conflicts ? ` (${landed.conflicts.join(', ')})` : ''} — worktree kept`);
587
2740
  }
588
2741
  }
589
2742
 
590
- fs.mkdirSync(path.dirname(receiptPath), { recursive: true });
591
- flight.receipt = receiptPath;
592
- fs.writeFileSync(flight.receipt, `${JSON.stringify(flight, null, 2)}\n`);
2743
+ writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly, enforceRemoteBoundary });
593
2744
  log('');
594
- log(` dispatch over: ${flight.landed.length} landed, ${flight.paused.length} paused · receipt: ${path.relative(root, flight.receipt)}`);
2745
+ const completedLabel = reviewOnly ? `${flight.ready.length} proof ready` : `${flight.landed.length} landed`;
2746
+ log(` dispatch over: ${completedLabel}, ${flight.paused.length} paused - receipt: ${path.relative(root, flight.receipt)}`);
595
2747
  log('');
596
2748
  return flight;
597
2749
  }