@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,16 +1,3 @@
1
- /**
2
- * v2 MVP M4 — leader endpoint wiring (V2-MVP-PLAN.md). `createLeaderRunner` turns a LeaderRequestBody into a
3
- * `runLeaderTask` invocation with REAL deps: per-worker E2B envs (leader-owned, static so they outlive the
4
- * task for diff-out) + a real ephemeral E2B integration sandbox + the Coordinator's credentialed
5
- * `git push --force-with-lease`. This is the productization of the joint dogfood (which validated the exact
6
- * logic on real infra) — gated behind LEADER_ENABLED (default off).
7
- *
8
- * 🔴 DEPLOY REQUIREMENTS (document — not satisfied by the default bun-slim image): the control plane (service
9
- * host) needs **git** on PATH for the Coordinator clone/apply/push, plus a reachable durable remote +
10
- * write creds (LEADER_GIT_TOKEN). E2B workers need outbound net only if the base repo is cloned over the
11
- * network; the MVP seeds the base from the Coordinator (no worker→remote net). MVP Planner = caller-provided
12
- * `subtasks` (an LLM DAG-planner is a follow-up).
13
- */
14
1
  import { parseNumOrFail } from "../config.js";
15
2
  import { execFileSync } from "node:child_process";
16
3
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
@@ -26,12 +13,6 @@ import { runLeaderTask } from "./leader.js";
26
13
  import { attachRepairLoopDeps } from "./repair-wire.js";
27
14
  import { routePlanWithFallback, validateSubtasks } from "./planner.js";
28
15
  import { createEngineNoticeSeat } from "../boot/runner-deps.js";
29
- /** The single-agent `runRepairLoop` is now WIRED LIVE (the canary-enable slice): when `LEADER_REPAIR_LOOP=true`
30
- * the wire provisions a DEDICATED oracle-seeded grader sandbox per solo worker and attaches a real async
31
- * `resolve` to `attachRepairLoopDeps` (below). With this `true`, flipping `LEADER_REPAIR_LOOP` engages BOTH the
32
- * repair loop AND the §10.5 resource-suspend mutual exclusion (a repair-owned worker never resource-suspends).
33
- * Still OFF-by-default — with the flag off, `resolve` is undefined → `attachRepairLoopDeps` is a no-op → the
34
- * default single-agent path is byte-identical to today. */
35
16
  const REPAIR_LOOP_WIRED = true;
36
17
  const sh = (env) => async (cmd) => {
37
18
  const r = await env.exec(`bash -lc ${JSON.stringify(cmd)}`);
@@ -49,95 +30,36 @@ const asIntegrationEnv = (env) => ({
49
30
  writeFile: async (p, c) => { const r = await env.writeFile(p, c); if (!r.ok)
50
31
  throw r.error; },
51
32
  });
52
- /**
53
- * Resume-safe worker staging (the design/68 F2(a) factory-env path). Runs adapter-side before the agent's
54
- * first workspace-touching call: seed the base repo at the worker branch + plant the upload script (so the
55
- * presigned URL never passes through the model).
56
- *
57
- * 🔴 BL-1 fix (design/74 Slice 6): on a RESOURCE-SUSPEND RESUME core re-invokes the `executionEnvFactory`,
58
- * producing a FRESH `withStaging` wrapper (`staged=undefined`) whose `stage()` runs AGAIN — AFTER core's
59
- * `resumeVM()` has already untar'd the snapshot onto the fresh pod (`resumeVM` is not a STAGED_METHOD, so it
60
- * is NOT intercepted by the staging proxy; the agent's first `exec` is what triggers `stage()`, lazily, and
61
- * by then the snapshot is restored). The old `stage()` ran the seed UNCONDITIONALLY:
62
- * `${seedCmd}; cd ${repo}; git checkout -q -b ${branch}`
63
- * and `seedCmd` begins with `rm -rf /workspace/repo` — so it WIPED the just-restored snapshot and re-seeded at
64
- * `baseSha`, then created the worker branch at base. The forensic proof (leaderRunId 019ecda7): the suspend
65
- * snapshot object DID contain the worker's commit (`git log` in the S3 tarball = the real progress), yet every
66
- * resumed slice's `git log` showed only the base commit — the worker rebuilt destroyed work for ~47 min. THIS
67
- * (a staging re-seed clobbering a correctly-restored snapshot) is the dominant cause of failed make-real runs.
68
- *
69
- * The fix = a RESTORED-SNAPSHOT SENTINEL that no-ops staging on resume. The robust signal is the upload-script
70
- * MARKER FILE (`uploadScriptPath`): staging itself plants it, so it is present in EVERY restored snapshot
71
- * (verified: it survives in both a base-state in-progress snapshot AND a post-commit one) and ABSENT on a
72
- * genuinely fresh pod — and it is INDEPENDENT of git/commit state. That last point matters: a worker can
73
- * suspend with work in progress but no commit yet (HEAD still == baseSha), so a "HEAD advanced past baseSha"
74
- * probe alone would WRONGLY re-seed+clobber that valid snapshot. The marker captures "staging already ran here"
75
- * regardless of whether a commit landed. We additionally accept the worker branch already existing as a
76
- * secondary restored-snapshot signal (belt-and-suspenders for the same git-state-independent reason).
77
- *
78
- * A FRESH pod has neither marker nor branch → full staging runs. A probe transport failure falls through to
79
- * full staging too (fails honestly via `sh()`, never silently skips). `branch`/paths are `validateSubtasks`-
80
- * clean / deployment config and are JSON-quoted under `bash -lc` regardless. Exported for the regression test.
81
- */
82
33
  export async function stageWorkerEnv(env, opts) {
83
34
  const { seedCmd, repo, branch, uploadScriptPath, uploadScript } = opts;
84
- // Restored-snapshot sentinel (non-throwing exec — we need the exit code, not an exception). Two independent
85
- // signals, EITHER of which proves "staging already ran here, this is a resumed snapshot, do NOT re-seed":
86
- // (1) the upload-script marker file is present — the definitive signal (planted by staging, git-state
87
- // independent, so it covers an in-progress-but-uncommitted snapshot where HEAD is still at base); and
88
- // (2) the worker branch already exists — secondary, also git-state independent.
89
- // Both probed in ONE round-trip; exit 0 ⇒ restored snapshot ⇒ NO-OP (never `rm -rf` the restored workspace).
90
- // A transport failure ({ok:false}) falls through to full staging (honest fail via sh()), never silent-skips.
91
35
  const probe = await env.exec(`bash -lc ${JSON.stringify(`if [ -f ${uploadScriptPath} ]; then exit 0; fi; ` +
92
36
  `cd ${repo} 2>/dev/null && git rev-parse --verify --quiet refs/heads/${branch} >/dev/null && exit 0; ` +
93
37
  `exit 1`)}`);
94
38
  if (probe.ok && probe.value.exitCode === 0)
95
- return; // resume — repo/branch/upload-script already in the snapshot
39
+ return;
96
40
  await sh(env)(`${seedCmd}; cd ${repo}; git checkout -q -b ${branch}`);
97
41
  const wr = await env.writeFile(uploadScriptPath, uploadScript);
98
42
  if (!wr.ok)
99
43
  throw new Error(`upload-script staging failed: ${wr.error.message}`);
100
44
  }
101
- /** Build the `runLeader(body)` the HTTP endpoint invokes. Constructs real per-task deps + runs the leader. */
102
- // 🔴 review-batch(重构池②,2026-08-01):`parseNumOrFail` alone still lets a NEGATIVE value through — a negative
103
- // number is JS-truthy, so every `parseNumOrFail(...) || default` chain in this file passed it straight through
104
- // instead of falling back (LEADER_WORKER_MAX_TURNS=-5 ⇒ workerMaxTurns=-5, unchanged by any later `|| default`).
105
- // This is the one extra domain bound (A2: illegal input INCLUDING negatives must fail loud) layered on the same
106
- // shared parse primitive — not a second parser. `undefined` (unset) stays NaN (`NaN < 0` is false) so the
107
- // existing `|| default` fallback chains are untouched; only "non-numeric" and "negative" newly throw.
108
45
  function parseNumOrFailNonNegative(name, raw) {
109
46
  const n = parseNumOrFail(name, raw);
110
47
  if (n < 0)
111
48
  throw new Error(`env ${name}=${n} must not be negative`);
112
49
  return n;
113
50
  }
114
- /**
115
- * Per-leader-run resource bounds derived from env (2026-06-14). Exported for unit tests. The worker sandbox
116
- * lifetime (BUG2), the worker's diff-upload presigned-URL TTL (BUG1), and the worker spec's triple bound
117
- * (maxTurns/timeoutSec/maxCostUsd, #15) ALL track one `leaderTimeoutMs` — a hardcoded 10min (sandbox) or 1h
118
- * (URL) silently killed long autonomous builds or expired the diff upload mid-run. core's turn cap is now a
119
- * high net (10000), so the leader gives each worker an explicit triple bound; a sub-task's own limits override.
120
- */
121
- // 🔴 2026-08-01 gap-sweep:四个旋钮原为 `Number(x) || default` —— 非法值(`"20usd"`/`"1800000ms"`)
122
- // 得 NaN 后**静默落到慷慨的默认值**,方向是**松绑**:运维想把 worker 预算从 $100 收到 $20,实际拿到
123
- // $100(5 倍);想把寿命从 24h 收到 30min,实际拿到 24h,而 leaderTimeoutMs 还级联 presignTtlSec
124
- // ⇒ diff 上传预签名 URL 的暴露窗口一起放大。改走与 numEnv 同源的 parseNumOrFail:非法值当场报错。
125
- // 刻意**零行为变更**:`|| default` 保留,所以未设与显式 0 的既有语义一个没动。
126
51
  export function leaderResourceConfig(env = process.env) {
127
- const leaderTimeoutMs = Math.max(600_000, Math.floor(parseNumOrFailNonNegative("LEADER_TIMEOUT_MS", env.LEADER_TIMEOUT_MS) || 0) || 86_400_000); // default 24h (was 10min)
52
+ const leaderTimeoutMs = Math.max(600_000, Math.floor(parseNumOrFailNonNegative("LEADER_TIMEOUT_MS", env.LEADER_TIMEOUT_MS) || 0) || 86_400_000);
128
53
  const workerMaxTurns = Math.floor(parseNumOrFailNonNegative("LEADER_WORKER_MAX_TURNS", env.LEADER_WORKER_MAX_TURNS) || 0) || 10_000;
129
54
  const workerBudgetUsd = (parseNumOrFailNonNegative("LEADER_WORKER_BUDGET_USD", env.LEADER_WORKER_BUDGET_USD) || 0) || 100;
130
55
  const resourceSuspendOn = String(env.LEADER_RESOURCE_SUSPEND ?? "").toLowerCase() === "true";
131
- // Per-slice window: a fraction of the total so a worker suspends+resumes several times across its budget
132
- // (default = total/4, min $1). Smaller window ⇒ more, shorter slices ⇒ progress saved more often.
133
56
  const sliceMaxCostUsd = (parseNumOrFailNonNegative("LEADER_WORKER_SLICE_BUDGET_USD", env.LEADER_WORKER_SLICE_BUDGET_USD) || 0) || Math.max(1, workerBudgetUsd / 4);
134
57
  return {
135
58
  leaderTimeoutMs,
136
59
  workerMaxTurns,
137
60
  workerBudgetUsd,
138
- // core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);walltime 原生毫秒不再除
139
61
  workerLimits: { limits: { maxTurns: workerMaxTurns, maxWalltimeMs: leaderTimeoutMs, maxCostUsd: workerBudgetUsd } },
140
- presignTtlSec: Math.ceil(leaderTimeoutMs / 1000) + 600, // URL/sandbox must outlive the worker (BUG1/BUG2)
62
+ presignTtlSec: Math.ceil(leaderTimeoutMs / 1000) + 600,
141
63
  ...(resourceSuspendOn
142
64
  ? {
143
65
  resourceSuspend: {
@@ -149,12 +71,6 @@ export function leaderResourceConfig(env = process.env) {
149
71
  : {}),
150
72
  };
151
73
  }
152
- // 🔴 review-batch(重构池②,2026-08-01): every knob below used to be a bare `Number(process.env.X ?? default)`
153
- // with ZERO fail-loud guard — worst case was `LEADER_BUDGET_USD="20usd"` silently becoming `budgetUsd: NaN`,
154
- // which makes the replan-lite budget gate's `spent > budgetUsd` comparison permanently `false` (the whole
155
- // gate goes dark, not "loose" — `NaN` compares false in every direction). Converged onto the same
156
- // `parseNumOrFailNonNegative` primitive leaderResourceConfig uses: non-numeric and negative now throw at
157
- // startup; `undefined`/explicit-`0` fallback semantics are byte-identical to before.
158
74
  export function leaderLoopConfig(cfg, env = process.env) {
159
75
  const repairRounds = Math.max(0, Math.floor(env.LEADER_REPAIR_ROUNDS !== undefined
160
76
  ? parseNumOrFailNonNegative("LEADER_REPAIR_ROUNDS", env.LEADER_REPAIR_ROUNDS)
@@ -163,12 +79,6 @@ export function leaderLoopConfig(cfg, env = process.env) {
163
79
  const conflictRounds = Math.max(0, Math.floor(env.LEADER_CONFLICT_ROUNDS !== undefined
164
80
  ? parseNumOrFailNonNegative("LEADER_CONFLICT_ROUNDS", env.LEADER_CONFLICT_ROUNDS)
165
81
  : (cfg.conflictRounds ?? 0)) || 0);
166
- // ── LEADER-REPAIRLOOP-INTEGRATION §10 env knobs (FRESH hunk, §10.8 — mirrors LEADER_REPAIR_ROUNDS above) ──
167
- // Every flag default-OFF so the whole feature is inert until flipped on a canary (the default path is
168
- // byte-identical to today). `LEADER_REPAIR_LOOP` is the MASTER (gates the single-agent `runRepairLoop` dep);
169
- // `LEADER_MEASURE_GATES` gates the merge push-hold-on-signal + measure-drives-repair (§10.3/§10.4);
170
- // `LEADER_REPAIR_LOOP_ATTEMPTS` is the in-loop attempt ceiling (validated 2-3, default 2 — design/78 §5);
171
- // `LEADER_ORACLE_FLAKY_K` is the flaky-settle re-isolation count (default 2 — §10.1 / repair-oracle.ts).
172
82
  const repairLoopOn = String(env.LEADER_REPAIR_LOOP ?? "").toLowerCase() === "true";
173
83
  const measureGatesOn = String(env.LEADER_MEASURE_GATES ?? "").toLowerCase() === "true";
174
84
  const repairLoopAttempts = Math.min(3, Math.max(2, Math.floor(parseNumOrFailNonNegative("LEADER_REPAIR_LOOP_ATTEMPTS", env.LEADER_REPAIR_LOOP_ATTEMPTS ?? "2")) || 2));
@@ -178,26 +88,11 @@ export function leaderLoopConfig(cfg, env = process.env) {
178
88
  ...(env.LEADER_BUDGET_USD ? { replanBudgetUsd: parseNumOrFailNonNegative("LEADER_BUDGET_USD", env.LEADER_BUDGET_USD) } : {}),
179
89
  };
180
90
  }
181
- // 鲁棒性批5 A4(2026-08-05):Coordinator 的六条 execFileSync(clone/config×2/am/ls-remote/push×2)此前零超时。
182
- // `execFileSync` 是**同步**调用——它挂起的是整个 Node 事件循环,不只是这一次 leader 跑;`durableRemote`
183
- // 是运维配的、可能半开/不可达的远端(DNS 黑洞、TCP SYN 丢、对端 git-upload-pack 卡死),一旦挂住,这一个
184
- // 副本上**所有并发请求**一起冻结,直到操作系统层 TCP 超时(可能几十分钟)才会松绑。
185
- // 上界两档:clone/push/ls-remote 是网络往返(仓可能较大,给 120s);config/am 是纯本地操作(不该合法挂起,
186
- // 但损坏的 object store / 文件锁可能卡住 `git am`),给 30s——同 fleet-client.ts `FLEET_CLIENT_FETCH_TIMEOUT_MS`
187
- // 判据的普适形(操作分类决定上界,不是一刀切)。
188
91
  export const LEADER_PUSH_NETWORK_TIMEOUT_MS = 120_000;
189
92
  export const LEADER_PUSH_LOCAL_TIMEOUT_MS = 30_000;
190
- /** Coordinator(control plane,持有推送凭据)的 git push 闭包——从 `createLeaderRunner` 抽出为独立工厂,
191
- * 仅依赖 durableRemote/targetRef/git 身份(不依赖 wire 内其余状态),因此可以脱离完整的
192
- * plan→provision→fan-out→merge 管线单测(那条管线需要真 brain + E2B/k8s env)。行为与抽出前逐字相同,
193
- * 唯一新增是六条 execFileSync 各自的 `timeout`。 */
194
93
  export function createCoordinatorPush(durableRemote, targetRef, ident) {
195
94
  return async (integratedPatch, baseSha) => {
196
- // Coordinator (control plane, creds): clone durable remote, apply integrated series, force-with-lease push.
197
- // 🔴 requires `git` on the service host PATH (deploy req).
198
95
  const dir = mkdtempSync(join(tmpdir(), "leader-coord-"));
199
- // Force C locale so the `raced` regex below matches git's (English) push-rejection messages regardless of
200
- // the host locale — otherwise a translated message → regex miss → a real race mislabeled non-retryable (council #5).
201
96
  const gitEnv = { ...process.env, LC_ALL: "C" };
202
97
  try {
203
98
  execFileSync("git", ["clone", "-q", durableRemote, dir], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
@@ -207,26 +102,12 @@ export function createCoordinatorPush(durableRemote, targetRef, ident) {
207
102
  execFileSync("git", ["-C", dir, "am", "--3way", ".leader.patch"], { env: gitEnv, timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
208
103
  rmSync(join(dir, ".leader.patch"));
209
104
  const ref = targetRef ?? "refs/heads/main";
210
- // CAS against the TARGET ref's ACTUAL current tip (not baseSha). The old `=ref:baseSha` lease assumed
211
- // the target ref already pointed at baseSha — but a FRESH ref (or one at any other commit) is never at
212
- // baseSha, so the lease rejected EVERY such push and mislabeled it "raced" (found live: auto1 reached
213
- // push after a clean 6-worker fan-out + green gradle, and failed only here). ls-remote gives the ref's
214
- // current oid (empty = it doesn't exist yet) → lease against THAT, which still detects a real concurrent
215
- // merge race (the ref moved since we read it) but lets a normal create/update through.
216
- // Does the target ref already exist? (exact full-ref match; --refs drops peeled annotated-tag lines.)
217
105
  const ls = execFileSync("git", ["-C", dir, "ls-remote", "--refs", "origin", ref], { encoding: "utf8", env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
218
106
  const exists = ls.split("\n").some((l) => l.split("\t")[1] === ref);
219
107
  if (exists) {
220
- // Existing ref → keep the original CAS against baseSha (detects a concurrent merge that moved the ref
221
- // off the base this run started from — council#2). NOT against a just-read end-of-run tip: that would
222
- // let leader B force-push over leader A's concurrent merge (Codex review #1). A ref that exists but
223
- // isn't at baseSha → refuse (use a fresh per-run targetRef to avoid that — the proving-ground does).
224
108
  execFileSync("git", ["-C", dir, "push", `--force-with-lease=${ref}:${baseSha}`, "origin", `HEAD:${ref}`], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
225
109
  }
226
110
  else {
227
- // Absent → create with a "must not exist" lease (empty expected oid): a concurrent create races safely
228
- // (rejected as stale info) instead of silently fast-forwarding. The original `:baseSha` lease wrongly
229
- // rejected a fresh ref (it's never at baseSha) — that was auto1's only failure after a green build.
230
111
  execFileSync("git", ["-C", dir, "push", `--force-with-lease=${ref}:`, "origin", `HEAD:${ref}`], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
231
112
  }
232
113
  return { ok: true, ref };
@@ -246,49 +127,21 @@ export function createLeaderRunner(cfg) {
246
127
  const ident = cfg.git ?? { name: "leader", email: "leader@local" };
247
128
  const { leaderTimeoutMs, workerLimits, presignTtlSec, resourceSuspend: resourceCfg } = leaderResourceConfig();
248
129
  const { repairRounds, repairBudgetUsd, conflictRounds, repairLoopOn, measureGatesOn, repairLoopAttempts, oracleFlakyK, replanBudgetUsd, } = leaderLoopConfig({ repairRounds: cfg.repairRounds, conflictRounds: cfg.conflictRounds });
249
- // #240② onNotice 席(合并码重扫):本车道的每一只 Runner 都要带,否则 core 的引擎通告在这条腿上落回
250
- // 裸 `console.warn`(主车道进结构化 `engine_notice` 行)—— 同一部署两条通告通道,按 code 采集的运维面
251
- // 只看得见一半。转发体取共享基座的**那一个**具名导出,禁在这里另写一份(两份必漂)。
252
- // ⚠️ 判据是 `warn` **在场**,不是 `cfg.logger` 在场:leader 的 logger 两格都可选,只装了 `info` 的部署
253
- // 铸这一席等于把通告吞掉 —— 那比不铸还坏(不铸时 core 至少还打自己的 `console.warn`)。
254
- // 🔴 这条判据现在**整条住在** `createEngineNoticeSeat` 里(codex 复审 R1-[medium],验真后修):此前它是
255
- // 这里的一行三元,于是只能靠「扫这段源码文本里有没有 `warn`」去守,而 `cfg.logger ? ((m,x)=>cfg.logger?.warn?.(m,x)) : undefined`
256
- // 这一族变异**文本上照样有 `warn`**、值上却是 truthy 闭包(info-only 部署照铸席、通告被可选链吞掉)。
257
- // 搬进函数之后判据可被**行为**测,门不再是关键字搜索。
258
130
  const onNoticeSeat = createEngineNoticeSeat(cfg.logger);
259
- // design/170 件B/C/D(#252,codex 对抗复审 R2-F1 验真后修):**leader 车道也是一个执行面**。
260
- // 修前这五处 Runner 一个治理座席都没有 —— 开了 leader 端点的部署因此有一条在部署治理**之外**的执行
261
- // 面:合规档位说禁 web_fetch,leader 的 worker 照挂;锁与留存声明同样够不着。与 `onNotice` 席当年的
262
- // 病同族(那次的修法也是「判据收进单一属主 + 五处同席」)。
263
- // 座席由 cfg 递进来(leader 绝不自己造一份治理判据 —— 一个进程里治理只能有一个属主);缺席 ⇒ 展开空
264
- // 对象 = 与修前逐字相同,不铸死键。
265
131
  const governanceSeat = cfg.governance ?? {};
266
- // worker resource bounds (limits/maxCostUsd) + presign ttl are derived in leaderResourceConfig(), above.
267
- // Bounded integration-repair (search 2026-06-14): when the merged tree compiles-clean-but-fails, run a strong
268
- // agent WITH HANDS in the live merged sandbox to fix the cross-worker integration, then mergeBranches re-runs
269
- // testCmd. OFF unless LEADER_REPAIR_ROUNDS>0 — closes over the RAW env (hands need the real RemoteExecutionEnv,
270
- // not the exec/writeFile-only IntegrationEnv wrapper). Strong model (default role); no durable wiring (short).
271
132
  const mkRepair = (rawEnv, repoDir, oracleFiles) => {
272
133
  if (repairRounds <= 0)
273
134
  return undefined;
274
135
  return {
275
136
  maxRounds: repairRounds,
276
137
  run: async ({ round, errorText, testCmd }) => {
277
- // 🔴 Measurement integrity (council blocker + partner-Codex gotcha): HIDE the hidden oracle so the repair
278
- // agent (rooted at the repo) can't `cat` the test it's graded on and cheat; RE-INJECT it in `finally` so
279
- // merge's re-test gate verifies the fix against the real oracle (and it's hidden again next round).
280
138
  for (const f of oracleFiles) {
281
- await rawEnv.remove(f.path).catch(() => { }); // best-effort delete (may be absent)
282
- // FAIL-CLOSED (partner-Codex): remove() returns a Result that does NOT throw on failure, so a silent
283
- // {ok:false} would leave the oracle readable and the repair model could `cat` it. VERIFY it's gone.
139
+ await rawEnv.remove(f.path).catch(() => { });
284
140
  const ex = await rawEnv.exists(f.path);
285
141
  if (ex.ok && ex.value)
286
142
  throw new Error(`oracle still present after remove (${f.path}) — refusing repair (measurement integrity)`);
287
143
  }
288
144
  try {
289
- // rootPath: repoDir (Codex review #4) — without it core roots the file tools at executionEnv.cwd
290
- // (/workspace on Kata), one level above the repo (/workspace/repo). bash can cd, but the dedicated
291
- // read/edit tools would start too high + lose containment.
292
145
  const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, executionEnv: rawEnv, rootPath: repoDir });
293
146
  const objective = [
294
147
  `The integrated project at ${repoDir} fails its build/test. Make the MINIMAL change to the working tree so this command exits 0 (cd into the repo and run it yourself to confirm):`,
@@ -296,8 +149,6 @@ export function createLeaderRunner(cfg) {
296
149
  `Several agents each ported a DISJOINT module group; the failure is almost certainly a cross-module integration mismatch (a shared interface, a missing/renamed symbol, a type/signature disagreement between two groups). Read the failing files, fix the smallest set of files, and re-run the command to verify it passes.`,
297
150
  `HARD RULES: do NOT git commit or git push; do NOT modify build configuration (gradle.properties / AGP / dependency versions / settings); do NOT add build-tooling files; do NOT call AskUserQuestion — decide autonomously. Preserve the ported semantics — fix the integration, never delete behavior just to make it pass.`,
298
151
  ``,
299
- // BL-32 — the failing output is worker/build-authored; delimit it so an injected instruction in a stack
300
- // trace / test message can't steer the repair agent (design/53 trifecta).
301
152
  delimitUntrusted("FAILING OUTPUT (untrusted)", errorText),
302
153
  ].join("\n");
303
154
  const res = await runner
@@ -307,16 +158,11 @@ export function createLeaderRunner(cfg) {
307
158
  limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
308
159
  })
309
160
  .result();
310
- // A non-completed repair (timeout/abort) may leave a process alive in the SAME Kata pod that would race
311
- // the merge re-test (Codex review #1) — surface it as a throw so mergeBranches does NOT re-test: it keeps
312
- // the original failure and the `finally` destroys the sandbox, killing any orphan.
313
161
  if (res.status !== "completed")
314
162
  throw new Error(`repair round ${round} did not complete (status=${res.status})`);
315
163
  return { costUsd: res.stats?.costMicroUsd !== undefined ? res.stats.costMicroUsd / 1e6 : undefined, note: res.result?.slice(0, 200) };
316
164
  }
317
165
  finally {
318
- // Re-inject the oracle so merge's re-test gate (run right after) verifies the fix against the REAL hidden
319
- // test — even if the repair model threw. It's hidden again at the top of the next round.
320
166
  for (const f of oracleFiles) {
321
167
  const wr = await rawEnv.writeFile(f.path, f.content);
322
168
  if (!wr.ok)
@@ -326,12 +172,6 @@ export function createLeaderRunner(cfg) {
326
172
  },
327
173
  };
328
174
  };
329
- // Bounded merge-conflict resolution (search 2026-06-16): a `git apply --3way` conflict means two workers touched
330
- // overlapping lines. Instead of the leader discarding ALL fan-out work + redoing single (collapseToSingle), run a
331
- // strong agent WITH HANDS in the conflicted sandbox to integrate the conflicting worker's patch (the others
332
- // already applied clean). OFF unless LEADER_CONFLICT_ROUNDS>0. Mirrors mkRepair: raw env for hands; hide the
333
- // hidden oracle so the resolver can't grade-peek; restore in `finally` so the downstream test gate sees the real
334
- // oracle. mergeBranches re-checks for leftover markers/.rej and degrades to collapse if the resolution is unclean.
335
175
  const mkConflictResolver = (rawEnv, repoDir, oracleFiles) => {
336
176
  if (conflictRounds <= 0)
337
177
  return undefined;
@@ -352,8 +192,6 @@ export function createLeaderRunner(cfg) {
352
192
  `When done, NO conflict markers and NO *.rej files may remain under ${repoDir}. Verify yourself before finishing: grep -rIlE '^(<<<<<<<|>>>>>>>)' . ; find . -name '*.rej' — BOTH must print nothing.`,
353
193
  `HARD RULES: do NOT git commit / git add / git push (the coordinator squashes at the end); do NOT modify build configuration; do NOT call AskUserQuestion — decide autonomously. Preserve both sides' ported semantics.`,
354
194
  ``,
355
- // BL-32 — the worker patch + conflict text are untrusted worker-adjacent content; delimit both so an
356
- // injected instruction can't steer the conflict-resolver agent (design/53 trifecta).
357
195
  delimitUntrusted("CONFLICTING WORKER PATCH (untrusted)", patch.slice(0, 6000)),
358
196
  delimitUntrusted("CONFLICT DETAILS (untrusted)", conflictText),
359
197
  ].join("\n");
@@ -383,22 +221,12 @@ export function createLeaderRunner(cfg) {
383
221
  if (!body.objective || !body.durableRemote || !body.testCmd || !body.seedCmd || !body.baseSha) {
384
222
  return { ok: false, reports: [], cancelled: false, error: "request must provide objective, durableRemote, baseSha, testCmd, seedCmd" };
385
223
  }
386
- // `durableRemote` is handed to `git clone` on the CONTROL PLANE (with the Coordinator's creds). git executes
387
- // transport-helper URLs (`ext::sh -c '…'`, `file://…`) as commands, so allow only a normal http(s)/git/ssh
388
- // URL or an scp-style user@host:path — reject the transport-helper class even though the caller is trusted.
389
224
  if (!/^(?:https?|git|ssh):\/\/[^\s]+$/i.test(body.durableRemote) && !/^[\w.-]+@[\w.-]+:[^\s]+$/.test(body.durableRemote)) {
390
225
  return { ok: false, reports: [], cancelled: false, error: "durableRemote must be an http(s)/git/ssh URL or user@host:path (no ext::/file:// transport helpers)" };
391
226
  }
392
- // baseSha is interpolated into worker-shell lines on BOTH env paths (static: format-patch via pullDiff,
393
- // which re-validates; factory: the staged upload script) — gate it here so the factory path is covered too.
394
227
  if (!safeRev(body.baseSha)) {
395
228
  return { ok: false, reports: [], cancelled: false, error: "baseSha must be a plain git rev (sha/branch token)" };
396
229
  }
397
- // B-2: write the hidden oracle into the integration sandbox (workers never see it) + local-exclude it so the
398
- // squash `git add -A` does NOT commit/push it into the durable integration branch (Codex review #1: else the
399
- // hidden test leaks into the repo, visible to any future worker seeded from it). gradle compiles src/test
400
- // regardless of git status, so the correctness gate still runs. NOTE: if integration-repair is enabled, the
401
- // repair agent (rooted at the repo) could still READ the on-disk oracle — delete it before repair runs then.
402
230
  const injectOracles = async (env) => {
403
231
  const ofs = body.oracleFiles ?? [];
404
232
  if (ofs.length === 0)
@@ -408,9 +236,6 @@ export function createLeaderRunner(cfg) {
408
236
  if (!wr.ok)
409
237
  throw wr.error;
410
238
  }
411
- // Local-exclude each oracle so `git add -A` never commits it (B-2 sandbox-only). Root-anchor with a leading
412
- // "/" + escape a leading "#"/"!" so the path is a literal exclude, not a gitignore comment/negation (council
413
- // #4). APPEND to (don't clobber) any seed-configured excludes — read the current file first (council #3).
414
239
  const anchor = (p) => "/" + (/^[#!]/.test(p) ? "\\" + p : p);
415
240
  const rel = ofs.map((f) => anchor(f.path.startsWith(`${repo}/`) ? f.path.slice(repo.length + 1) : f.path)).join("\n");
416
241
  const cur = await env.exec(`bash -lc ${JSON.stringify(`cat ${repo}/.git/info/exclude 2>/dev/null || true`)}`);
@@ -419,18 +244,8 @@ export function createLeaderRunner(cfg) {
419
244
  if (!ex.ok)
420
245
  throw ex.error;
421
246
  };
422
- // design/158 A13: the four LeaderDeps methods below (plan/provisionWorker/provisionIntegrationSandbox/push)
423
- // are named local functions — same closure scope, same capture, same execution order as when they were
424
- // inline object-literal properties; only the "anonymous inline closure" form changed (behavior identical).
425
- // caller-provided sub-tasks = an explicit FAN-OUT override; else the difficulty ROUTER decides (one model
426
- // completion): default single (a 1-subtask plan) and only fan out when clearly+economically decomposable
427
- // Either way → VALIDATED disjoint-file sub-tasks (planner.ts enforces the Coordinator's invariant).
428
247
  const plan = async () => {
429
248
  if (Array.isArray(body.subtasks) && body.subtasks.length > 0) {
430
- // Validate caller-supplied sub-tasks through the SAME deterministic checks as an LLM plan — the
431
- // branch is interpolated into a worker-container shell command (planner.validateSubtasks rejects a
432
- // shell-unsafe branch / leading "-" / "..") and the disjoint-file + unique-id invariants are what
433
- // the Coordinator's parallel merge relies on. Was a straight map() that skipped all of it.
434
249
  return validateSubtasks(body.subtasks);
435
250
  }
436
251
  const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat });
@@ -438,14 +253,10 @@ export function createLeaderRunner(cfg) {
438
253
  const res = await planRunner.runTaskStream({
439
254
  objective: prompt,
440
255
  sessionId: `leader-route-${Date.now()}`,
441
- // Pin the route to a cheap classifier model (see `routerModel` doc): the heavy reasoning model
442
- // returns an empty completion ~2/3 of the time on this prompt, collapsing every fan-out to single.
443
256
  ...(cfg.routerModel ? { model: cfg.routerModel } : {}),
444
257
  }).result();
445
258
  return res.result ?? "";
446
259
  };
447
- // §5 quality gate: a rejected/unparseable plan retries the LLM ≤1, then collapses to single (B1-B3) —
448
- // never plan-fails the task; the collapse reason is logged as a router-quality signal.
449
260
  const { subs, collapsed, rawSample } = await routePlanWithFallback(body.objective, runRoute, {
450
261
  ...(body.planContext ? { context: body.planContext } : {}),
451
262
  ...(cfg.fanoutEnabled === false ? { fanoutEnabled: false } : {}),
@@ -456,17 +267,10 @@ export function createLeaderRunner(cfg) {
456
267
  };
457
268
  const provisionWorker = async (sub) => {
458
269
  const sessionId = `leader-${sub.workerId}-${Date.now()}`;
459
- // design/68 C4: durable suspend bits per worker task (mirrors main.ts's per-task wiring) — a gated
460
- // ask/AskUserQuestion suspends the worker; ctx is seeded so the surfaced token resumes on the
461
- // single-agent path (the resume body re-runs the sub-objective on this worker).
462
270
  let durableSpec = {};
463
271
  if (cfg.durable) {
464
272
  const d = cfg.durable;
465
273
  durableSpec = {
466
- // AskUserQuestion 判决:本腿是 resolve-spec `createDurableQuestionGate` 单一属主声明的**登记豁免**
467
- // (复审车B 上抛/属主裁定 2026-08-05)——leader worker 无活体问答面可装(不是 HTTP 活流腿,
468
- // QuestionCoordinator 的 ALS 永不覆盖到这里),core 原形+下方 sentinel 即完整正确语义,且 leader
469
- // 子系统不 import boot 装配层(分层)。⚠️ 若 leader 腿将来接活体面,必须改调该工厂并撤本注。
470
274
  toolPolicy: combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({ requireApproval: d.requireApproval, ...(d.deny ? { deny: d.deny } : {}), ...(d.autoBudget ? { autoBudget: d.autoBudget } : {}), ...(d.neverAuto ? { neverAuto: d.neverAuto } : {}) })),
471
275
  checkpointStore: d.checkpointStore,
472
276
  durableApproval: { scope: "_leader", ...(d.ttlMs ? { ttlMs: d.ttlMs } : {}) },
@@ -476,27 +280,7 @@ export function createLeaderRunner(cfg) {
476
280
  cfg.logger?.warn?.("leader_putctx_failed", { workerId: sub.workerId, err: String(e) });
477
281
  });
478
282
  }
479
- // design/74 Slice 6: opt-in resource-slice suspend. A worker that overruns its per-slice budget window
480
- // SUSPENDS (resumable) instead of failing; the leader's runWorker auto-resumes until the TOTAL is spent.
481
- // Reuses the durable checkpoint store (so requires `cfg.durable` for the store + durable tool-results +
482
- // session — the same durable-resume prerequisites). The per-slice window OVERRIDES workerLimits.maxCostUsd
483
- // (spread last); the total + cumulative spend ride on the checkpoint's resource ledger (core debits it).
484
283
  let resourceSpec = {};
485
- // Slice-6 review BLOCKER: core's resource-suspend needs the FULL durable-resume prerequisites —
486
- // a checkpoint store (cfg.durable), a durable tool-result store (cfg.toolResultStore; an in-memory
487
- // offload makes core REFUSE to suspend), AND a durable session store (cfg.sessionStore; without it a
488
- // resumed slice dies with SessionError "Entry <leafId> not found" — the worker's progress is lost,
489
- // wire.ts:52-55). Require ALL THREE before applying the per-slice window: otherwise the worker's
490
- // maxCostUsd would be cut to a quarter WITHOUT the ability to suspend/resume → it fails at 1/4 its
491
- // budget with no warning. When opted in (resourceCfg) but a prerequisite is missing, leave the budget
492
- // intact + warn (which one).
493
- // §10.5 ENFORCED mutual exclusion: when the single-agent repair loop actually OWNS a worker, that worker
494
- // must NOT also resource-suspend — the repair loop owns its attempt budget, and a resource-suspend mid-loop
495
- // risks `attemptCount` corruption + budget overrun (§10.5: a doc aspiration is not enough, this drops
496
- // `resourceSpec` by construction so the two are NEVER on the same spec). Gated on the loop being WIRED
497
- // (`REPAIR_LOOP_WIRED=true` — the canary-enable slice attached a LIVE `resolveRepair`), so flipping
498
- // `LEADER_REPAIR_LOOP` now engages BOTH the single-agent repair loop AND this resource-suspend exclusion for
499
- // the worker the loop owns. (`REPAIR_LOOP_WIRED` is the live/inert switch — it shipped false pre-activation.)
500
284
  const repairLoopActive = repairLoopOn && REPAIR_LOOP_WIRED;
501
285
  if (repairLoopActive && resourceCfg) {
502
286
  cfg.logger?.warn?.("leader_resource_suspend_disabled_for_repair_loop", { workerId: sub.workerId, reason: "LEADER_REPAIR_LOOP active — resource-suspend mutually exclusive with the repair loop (§10.5)" });
@@ -505,9 +289,6 @@ export function createLeaderRunner(cfg) {
505
289
  resourceSpec = {
506
290
  checkpointStore: cfg.durable.checkpointStore,
507
291
  resourceSuspend: { scope: `_leader-resource-${sub.workerId}`, totalBudgetUsd: resourceCfg.totalBudgetUsd },
508
- // core 5.8.0:maxCostUsd 入 limits 后,本 spread(spec: {...workerLimits, ..., ...resourceSpec})的
509
- // limits 键是**整体覆盖**——必须显式并回 workerLimits.limits(maxTurns/maxWalltimeMs),否则
510
- // per-slice 覆盖会顺手把 worker 的轮数/墙钟界丢掉(5.7 时代顶层键覆盖顶层键,无此陷阱)。
511
292
  limits: { ...workerLimits.limits, maxCostUsd: resourceCfg.sliceMaxCostUsd },
512
293
  maxSuspends: resourceCfg.maxSuspends,
513
294
  };
@@ -522,12 +303,6 @@ export function createLeaderRunner(cfg) {
522
303
  : "no durable session store",
523
304
  });
524
305
  }
525
- // Keep the resume ctx warm for the WHOLE first leg (found live: task02b run1). putCtx stamps
526
- // `updated_at` once at provision, but reapCtx GC's any ctx older than runStaleSec that has no pending
527
- // checkpoint and no task_active claim — sub-workers hold no task_active row (that lock belongs to the
528
- // HTTP submit path), so a leg longer than the reap cutoff lost its ctx and the post-suspend /decide
529
- // died 409 "resume context missing". Refresh inside the run; once suspended, the pending checkpoint
530
- // itself shields the ctx from the reaper, and after a terminal result the ctx ages out as designed.
531
306
  const keepCtxWarm = (runner) => {
532
307
  const d = cfg.durable;
533
308
  if (!d)
@@ -544,53 +319,29 @@ export function createLeaderRunner(cfg) {
544
319
  };
545
320
  return runner;
546
321
  };
547
- // design/68 §8 F2(a) — factory-env worker + self-upload diff-out (preferred; static mode = compat below).
548
322
  if (cfg.s3 && cfg.envFactory) {
549
323
  const s3 = cfg.s3;
550
324
  const envFactory = cfg.envFactory;
551
- const baseSha = body.baseSha; // guarded non-empty at entry (narrowing is lost inside this closure)
325
+ const baseSha = body.baseSha;
552
326
  const seedCmd = body.seedCmd;
553
- // workerId is validateSubtasks-clean ([A-Za-z0-9._-]+) → safe as a key segment; the presigner
554
- // backstop-rejects traversal anyway. One object per worker per leader run; a repair re-run overwrites.
555
327
  const key = `${s3.keyPrefix ?? "leader-diff"}/${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}/${sub.workerId}.patch`;
556
- // The presigned URL must outlive the worker: it self-uploads its diff at the END of its run, which
557
- // can be the full leader deadline (24h). The old `Math.min(3600, …)` clamp expired mid-run for any
558
- // worker > 1h → the upload 403'd and the diff (progress) was lost. Track the worker deadline + slack.
559
- // (Codex: instant-signing at upload time, from the control plane, is the cleaner long-term fix.)
560
328
  const ttl = presignTtlSec;
561
329
  const base = { endpoint: s3.endpoint, bucket: s3.bucket, accessKey: s3.accessKey, secretKey: s3.secretKey, ...(s3.region ? { region: s3.region } : {}), key, expiresSec: ttl };
562
330
  const putUrl = presignS3Url({ ...base, method: "PUT" });
563
331
  const getUrl = presignS3Url({ ...base, method: "GET" });
564
332
  const script = buildUploadScript({ repoDir: repo, baseSha, putUrl, workDir: W });
565
- // Staging runs adapter-side before the agent's first workspace call: seed the base repo at the worker
566
- // branch + plant the upload script (the presigned URL never passes through the model).
567
- // Resume-safe (BL-1): stageWorkerEnv no-ops on a resumed snapshot (worker branch already present)
568
- // instead of re-running `git checkout -b` (which would fail "branch exists" and kill the resumed
569
- // worker). Now load-bearing because the prod auto-resume landed (leader.ts non-verify path, 9a91c60).
570
333
  const stage = async (env) => {
571
334
  await stageWorkerEnv(env, { seedCmd, repo, branch: sub.branch, uploadScriptPath: `${W}/.leader-upload.sh`, uploadScript: script });
572
- // #16 tail (search 2026-06-17): INCREMENTAL progress upload. The upload script is READ-ONLY git
573
- // (format-patch base..HEAD — no add/commit/index write), so a background loop re-running it keeps the
574
- // worker's fixed diff key at its latest COMMITTED progress. If the env HARD-CRASHES before its final /
575
- // belt upload, salvage (fetchUploadedDiff) then recovers up to the last commit instead of losing all
576
- // (run 019eca71's "4 commits vaporized" was exactly a failed worker that never uploaded). No git
577
- // concurrency (read-only); the teardown belt upload stays authoritative (same key, complete diff).
578
- // Fire-and-forget + NEVER awaited: the loop never exits, so staging must not block on it — whether the
579
- // env's exec detaches or blocks, the loop RUNS either way and the dangling promise is reaped when the
580
- // pod dies. Best-effort: a launch failure (no nohup, etc.) just falls back to today's end-only upload.
581
335
  void sh(env)(`nohup sh -c 'while true; do sleep 90; sh ${W}/.leader-upload.sh >/dev/null 2>&1 || true; done' </dev/null >/dev/null 2>&1 &`).catch(() => { });
582
336
  };
583
337
  return {
584
338
  workerId: sub.workerId, sessionId, branch: sub.branch,
585
- baseSha, // seedCmd reproduces the durable base — same sha for every worker
339
+ baseSha,
586
340
  runner: keepCtxWarm(new Runner({
587
341
  brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat,
588
342
  ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}),
589
343
  ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}),
590
344
  executionEnvFactory: (ctx) => withStaging(envFactory(ctx), stage, async (e) => {
591
- // Belt (task02b run6): workers that did the work but never `git commit`ed upload an EMPTY diff
592
- // (diff-out is committed-state vs base) — auto-commit any uncommitted work first. No-op for a
593
- // worker that committed properly; the commit identity marks belt-captured work for the reviewer.
594
345
  await sh(e)(`cd ${repo} && git add -A && git -c user.name='leader-belt' -c user.email='belt@leader' commit -qm 'belt: auto-commit uncommitted worker output' || true; sh ${W}/.leader-upload.sh`);
595
346
  }),
596
347
  })),
@@ -614,8 +365,6 @@ export function createLeaderRunner(cfg) {
614
365
  };
615
366
  };
616
367
  const provisionIntegrationSandbox = async () => {
617
- // Factory lane (k8s or e2b): one fresh env from the service factory; lazy-connect — first exec
618
- // provisions the pod/VM, destroy() reaps it. Static lane: the original explicit E2B env.
619
368
  if (cfg.envFactory) {
620
369
  const env = cfg.envFactory({ sessionId: `leader-integ-${Date.now()}` });
621
370
  try {
@@ -631,8 +380,6 @@ export function createLeaderRunner(cfg) {
631
380
  if (!cfg.e2bApiKey)
632
381
  throw new Error("leader wire: no envFactory and no e2bApiKey");
633
382
  const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
634
- // Once connected, the VM is billable — if seeding throws, destroy it HERE before propagating, otherwise
635
- // mergeBranches never receives the `destroy` closure and the sandbox leaks until E2B's timeout reaps it.
636
383
  if (!(await env.connect()).ok) {
637
384
  await env.destroy().catch(() => { });
638
385
  throw new Error("integ connect failed");
@@ -655,40 +402,14 @@ export function createLeaderRunner(cfg) {
655
402
  push,
656
403
  testCmd: body.testCmd,
657
404
  repoDir: repo, workerRepoDir: repo,
658
- // Per-wave fan-out deadline. LEADER_TIMEOUT_MS raises it (raise-only, parity with TASK_TIMEOUT_SEC —
659
- // found live: task02b run5, porting workers + collapse-solo all 'run aborted' at the 600s default while
660
- // mid-gradle; the per-worker presign ttl below tracks the same knob).
661
- timeoutMs: leaderTimeoutMs, maxConcurrency: 4, // Semaphore cap; the planner emits ≤6 sub-tasks
662
- // replan-lite (design/68 §6) always on in the wired leader; LEADER_BUDGET_USD bounds the fan-out spend
663
- // (parsed + fail-loud-guarded in leaderLoopConfig(), above — see LEADER_BUDGET_USD review-batch note).
405
+ timeoutMs: leaderTimeoutMs, maxConcurrency: 4,
664
406
  replan: { ...(replanBudgetUsd !== undefined ? { budgetUsd: replanBudgetUsd } : {}) },
665
- // LEADER-REPAIRLOOP-INTEGRATION §10.3 — the merge push-hold-on-signal (OFF unless LEADER_MEASURE_GATES). It
666
- // needs NO extra sandbox: `strongOracleSeeded` keys on whether a hidden held-out oracle was injected into the
667
- // integration sandbox (`injectOracles` runs `body.oracleFiles`), and the §10.3 hold combines it with the
668
- // existing observe-only `measureCmd` result inside `mergeBranches`. §10.4's out-of-process measure grader
669
- // (`measureDrivesRepair`) is a separate re-seedable sandbox — the canary-enable add-on (§6 step 5); it is
670
- // intentionally NOT wired here in v1 (a half-mint would be a stopgap), so `measureGates` ships the §10.3
671
- // signal-hold only. With LEADER_MEASURE_GATES off this whole block is absent → byte-identical merge path.
672
407
  ...(measureGatesOn
673
408
  ? { mergeGates: { measureGates: true, ...((body.oracleFiles?.length ?? 0) > 0 ? { strongOracleSeeded: true } : {}) } }
674
409
  : {}),
675
410
  ...(cfg.logger ? { logger: cfg.logger } : {}),
676
411
  };
677
- // §10.8 — the SINGLE wire-call helper for the opt-in single-agent repair loop (LEADER_REPAIR_LOOP). The
678
- // canary-enable slice attaches a LIVE async `resolve(solo, sub)` that, per solo worker, provisions a DEDICATED
679
- // oracle-seeded GRADER sandbox (a SEPARATE sandbox from the worker + the integration sandbox — distinct object/
680
- // sandboxId by construction, §10.2 structural isolation) and binds the per-worker diff source + the concrete
681
- // Runner. OFF-by-default: `resolve` is attached ONLY when `repairLoopOn` (LEADER_REPAIR_LOOP=true); with the
682
- // flag off `resolve` is undefined → `attachRepairLoopDeps` is a NO-OP → `deps.repairLoop` stays unset → the
683
- // single-agent default path is byte-identical to today. The §10.6 push chokepoint (merge.ts) is LIVE
684
- // regardless: once any report carries a `repairTerminal`, it holds.
685
- //
686
- // The grader is provisioned the SAME way as the integration sandbox (provisionIntegrationSandbox: raw env →
687
- // seed → injectOracles → integBase), then exposed as a `GraderHandle` (the raw RemoteExecutionEnv IS an
688
- // ExecutionEnv). `graderDestroy` reaps it in `runRepairLoopForLeader`'s `finally`. On a seed/inject throw the
689
- // env is destroyed before propagating (mirrors provisionIntegrationSandbox :587-590 / :604-606).
690
412
  const resolveRepair = async (solo, _sub) => {
691
- // Provision a DEDICATED grader: factory lane mints a fresh env (lazy-connect); static lane connects an E2B VM.
692
413
  let env;
693
414
  if (cfg.envFactory) {
694
415
  env = cfg.envFactory({ sessionId: `leader-grader-${Date.now()}` });
@@ -703,8 +424,6 @@ export function createLeaderRunner(cfg) {
703
424
  }
704
425
  env = e2b;
705
426
  }
706
- // Seed the base repo + inject the hidden oracle ONLY in this grader (never the worker env). On a throw,
707
- // destroy the billable env before propagating (else it leaks until the adapter's timeout reaps it).
708
427
  let integBase;
709
428
  try {
710
429
  await sh(env)(body.seedCmd);
@@ -722,33 +441,20 @@ export function createLeaderRunner(cfg) {
722
441
  repoDir: repo,
723
442
  integBase,
724
443
  };
725
- // The worker diff source: factory lane = the worker's self-upload (`solo.fetchDiff`); static lane = pull the
726
- // committed `base..HEAD` series straight out of the worker's still-alive env (the same merge `pullDiff` path).
727
444
  const workerRepoDir = repo;
728
445
  const pullWorkerDiff = solo.fetchDiff
729
- ? // 🔴 BL-33 factory-lane residual: the self-upload path captures only what the fix turn UPLOADED; core's
730
- // repairObjective carries no upload mandate, so an un-uploaded fix is missed. A deterministic belt needs
731
- // a worker-env exec handle the factory lane does not surface (env-recording factory = follow-up). The
732
- // fork2.1 stale-diff guard keeps this SAFE (no false candidate) until then.
446
+ ?
733
447
  solo.fetchDiff
734
448
  : async () => {
735
- // 🔴 BL-33 — the STATIC lane EXPOSES the worker env (`solo.diffEnv`), so deterministically belt-commit
736
- // the worker's UNCOMMITTED fix-turn work BEFORE the committed-only `format-patch base..HEAD` pull —
737
- // else attempts ≥2 grade a STALE diff (the fix never observed). Best-effort + only when there is a
738
- // change to commit (no empty commits polluting the series). Mirrors the teardown belt, run per-grade.
739
449
  const wenv = solo.diffEnv;
740
450
  await wenv
741
451
  .exec(`bash -lc ${JSON.stringify(`cd ${repo} && git add -A && (git diff --cached --quiet || git -c user.email=w@w.co -c user.name=worker commit -q -m repair-wip)`)}`)
742
452
  .catch(() => { });
743
453
  return pullDiff(solo.diffEnv, solo.baseSha, { ref: solo.branch, ...(workerRepoDir ? { cwd: workerRepoDir } : {}) });
744
454
  };
745
- // The trusted, spec-derived oracle steps (the integration testCmd) run in the grader; NEVER worker-authored.
746
455
  const oracleSteps = [{ command: body.testCmd, cwd: repo, label: "oracle-test" }];
747
- // tier: a seeded hidden held-out oracle clears to `trusted_hidden`; a compile-only gate is `property_harness_weak`.
748
456
  const oracleFiles = body.oracleFiles ?? [];
749
457
  const passTier = oracleFiles.length > 0 ? "trusted_hidden" : "property_harness_weak";
750
- // fork1.1 — make the INTENTIONAL factory-lane identity-check skip observable (distinct from an
751
- // accidental workerEnv omission, which onWarn's generic MINOR-2 warning can't tell apart).
752
458
  if (solo.fetchDiff)
753
459
  cfg.logger?.info?.("repair_loop_factory_lane_isolation", { workerId: solo.workerId, reason: "identity check intentionally skipped — isolation by independent grader sandbox construction (§10.2)" });
754
460
  return {
@@ -758,14 +464,9 @@ export function createLeaderRunner(cfg) {
758
464
  pullWorkerDiff,
759
465
  oracleSteps,
760
466
  passTier,
761
- // council 2026-06-17 — re-inject the seeded oracle after each hermetic reset (clean -fdx wipes it);
762
- // fork1.2 — the paths a candidate patch must not touch (the oracle files' own paths).
763
467
  ...(oracleFiles.length > 0 ? { oracleFiles, oraclePaths: oracleFiles.map((f) => f.path) } : {}),
764
468
  maxAttempts: repairLoopAttempts,
765
469
  flakyK: oracleFlakyK,
766
- // §10.2 — on the factory lane the worker's RAW env is not surfaced (only fetchDiff), so OMIT workerEnv
767
- // (runRepairLoop skips the identity check; the grader is a separate sandbox by construction). On the static
768
- // lane the leader owns the worker's RemoteExecutionEnv (`solo.diffEnv` is that env) → pass it so the check runs.
769
470
  ...(!solo.fetchDiff && solo.diffEnv ? { workerEnv: solo.diffEnv } : {}),
770
471
  log: (event, x) => event.includes("fail") || event.includes("warn") || event.includes("threw")
771
472
  ? cfg.logger?.warn?.(event, x)