auditor-lambda 0.10.3 → 0.10.8

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 (183) hide show
  1. package/audit-code-wrapper-build.mjs +198 -0
  2. package/audit-code-wrapper-install-hosts.mjs +1140 -0
  3. package/audit-code-wrapper-io.mjs +155 -0
  4. package/audit-code-wrapper-legacy.mjs +125 -0
  5. package/audit-code-wrapper-lib.mjs +22 -1806
  6. package/audit-code-wrapper-opencode.mjs +255 -0
  7. package/dispatch/merge-results.mjs +5 -3
  8. package/dispatch/validate-result.mjs +2 -2
  9. package/dist/adapters/coverageSummary.js +6 -2
  10. package/dist/adapters/normalizeExternal.js +16 -1
  11. package/dist/adapters/npmAudit.js +20 -9
  12. package/dist/adapters/semgrep.js +26 -1
  13. package/dist/cli/advanceAuditCommand.d.ts +1 -0
  14. package/dist/cli/advanceAuditCommand.js +95 -0
  15. package/dist/cli/args.js +2 -3
  16. package/dist/cli/auditStep.js +2 -2
  17. package/dist/cli/cleanup.d.ts +11 -1
  18. package/dist/cli/cleanup.js +25 -5
  19. package/dist/cli/cleanupCommand.d.ts +1 -0
  20. package/dist/cli/cleanupCommand.js +24 -0
  21. package/dist/cli/dispatch.d.ts +55 -31
  22. package/dist/cli/dispatch.js +298 -241
  23. package/dist/cli/dispatchStatusCommand.d.ts +1 -0
  24. package/dist/cli/dispatchStatusCommand.js +68 -0
  25. package/dist/cli/explainTaskCommand.d.ts +1 -0
  26. package/dist/cli/explainTaskCommand.js +33 -0
  27. package/dist/cli/importExternalAnalyzerCommand.d.ts +1 -0
  28. package/dist/cli/importExternalAnalyzerCommand.js +20 -0
  29. package/dist/cli/ingestResultsCommand.d.ts +1 -0
  30. package/dist/cli/ingestResultsCommand.js +34 -0
  31. package/dist/cli/intakeCommand.d.ts +1 -0
  32. package/dist/cli/intakeCommand.js +17 -0
  33. package/dist/cli/lineIndex.js +19 -12
  34. package/dist/cli/nextStepCommand.d.ts +139 -0
  35. package/dist/cli/nextStepCommand.js +281 -234
  36. package/dist/cli/planCommand.d.ts +1 -0
  37. package/dist/cli/planCommand.js +16 -0
  38. package/dist/cli/prepareDispatchCommand.d.ts +1 -0
  39. package/dist/cli/prepareDispatchCommand.js +25 -0
  40. package/dist/cli/quotaCommand.d.ts +1 -0
  41. package/dist/cli/quotaCommand.js +56 -0
  42. package/dist/cli/requeueCommand.d.ts +1 -0
  43. package/dist/cli/requeueCommand.js +10 -0
  44. package/dist/cli/runToCompletion.js +451 -412
  45. package/dist/cli/sampleRunCommand.d.ts +1 -0
  46. package/dist/cli/sampleRunCommand.js +93 -0
  47. package/dist/cli/statusCommand.js +1 -1
  48. package/dist/cli/steps.js +4 -1
  49. package/dist/cli/submitPacketCommand.js +16 -15
  50. package/dist/cli/synthesizeCommand.d.ts +1 -0
  51. package/dist/cli/synthesizeCommand.js +15 -0
  52. package/dist/cli/updateRuntimeValidationCommand.d.ts +1 -0
  53. package/dist/cli/updateRuntimeValidationCommand.js +16 -0
  54. package/dist/cli/validateCommand.d.ts +1 -0
  55. package/dist/cli/validateCommand.js +41 -0
  56. package/dist/cli/validateResultCommand.d.ts +1 -0
  57. package/dist/cli/validateResultCommand.js +63 -0
  58. package/dist/cli/validateResultsCommand.d.ts +1 -0
  59. package/dist/cli/validateResultsCommand.js +31 -0
  60. package/dist/cli/workerRunCommand.d.ts +15 -1
  61. package/dist/cli/workerRunCommand.js +40 -4
  62. package/dist/cli.d.ts +3 -2
  63. package/dist/cli.js +21 -628
  64. package/dist/coverage.js +7 -3
  65. package/dist/extractors/analyzers/css.js +2 -2
  66. package/dist/extractors/analyzers/html.js +2 -2
  67. package/dist/extractors/analyzers/python.js +2 -2
  68. package/dist/extractors/analyzers/registry.js +17 -36
  69. package/dist/extractors/analyzers/treeSitter.d.ts +10 -1
  70. package/dist/extractors/analyzers/treeSitter.js +28 -6
  71. package/dist/extractors/analyzers/typescript.js +104 -85
  72. package/dist/extractors/browserExtension.js +4 -1
  73. package/dist/extractors/designAssessment.js +21 -21
  74. package/dist/extractors/fsIntake.js +35 -11
  75. package/dist/extractors/graph.js +17 -7
  76. package/dist/extractors/graphManifestEdges/cargo.d.ts +4 -0
  77. package/dist/extractors/graphManifestEdges/cargo.js +107 -0
  78. package/dist/extractors/graphManifestEdges/go.d.ts +5 -0
  79. package/dist/extractors/graphManifestEdges/go.js +151 -0
  80. package/dist/extractors/graphManifestEdges/index.d.ts +8 -0
  81. package/dist/extractors/graphManifestEdges/index.js +11 -0
  82. package/dist/extractors/graphManifestEdges/jsonc.d.ts +3 -0
  83. package/dist/extractors/graphManifestEdges/jsonc.js +97 -0
  84. package/dist/extractors/graphManifestEdges/maven.d.ts +3 -0
  85. package/dist/extractors/graphManifestEdges/maven.js +73 -0
  86. package/dist/extractors/graphManifestEdges/packageJson.d.ts +19 -0
  87. package/dist/extractors/graphManifestEdges/packageJson.js +204 -0
  88. package/dist/extractors/graphManifestEdges/pnpm.d.ts +2 -0
  89. package/dist/extractors/graphManifestEdges/pnpm.js +42 -0
  90. package/dist/extractors/graphManifestEdges/pyproject.d.ts +3 -0
  91. package/dist/extractors/graphManifestEdges/pyproject.js +83 -0
  92. package/dist/extractors/graphManifestEdges/toml.d.ts +4 -0
  93. package/dist/extractors/graphManifestEdges/toml.js +68 -0
  94. package/dist/extractors/graphManifestEdges/typescript.d.ts +3 -0
  95. package/dist/extractors/graphManifestEdges/typescript.js +56 -0
  96. package/dist/extractors/graphManifestEdges/workspace.d.ts +10 -0
  97. package/dist/extractors/graphManifestEdges/workspace.js +72 -0
  98. package/dist/extractors/graphManifestEdges/yaml.d.ts +3 -0
  99. package/dist/extractors/graphManifestEdges/yaml.js +59 -0
  100. package/dist/extractors/graphManifestEdges/yamlPaths.d.ts +4 -0
  101. package/dist/extractors/graphManifestEdges/yamlPaths.js +89 -0
  102. package/dist/extractors/graphPythonImports.js +4 -20
  103. package/dist/extractors/pathPatterns.js +3 -13
  104. package/dist/io/artifacts.d.ts +1 -2
  105. package/dist/io/artifacts.js +8 -4
  106. package/dist/io/runArtifacts.d.ts +8 -2
  107. package/dist/io/runArtifacts.js +103 -69
  108. package/dist/io/toolingManifest.js +2 -1
  109. package/dist/orchestrator/advance.js +36 -0
  110. package/dist/orchestrator/artifactFreshness.d.ts +1 -1
  111. package/dist/orchestrator/artifactFreshness.js +1 -1
  112. package/dist/orchestrator/artifactMetadata.js +5 -5
  113. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  114. package/dist/orchestrator/auditTaskUtils.js +8 -12
  115. package/dist/orchestrator/autoFixExecutor.js +40 -26
  116. package/dist/orchestrator/dependencyMap.js +1 -1
  117. package/dist/orchestrator/executorResult.d.ts +33 -0
  118. package/dist/orchestrator/executors.d.ts +7 -0
  119. package/dist/orchestrator/executors.js +24 -0
  120. package/dist/orchestrator/fileAnchors.js +42 -29
  121. package/dist/orchestrator/fileIntegrity.js +6 -1
  122. package/dist/orchestrator/flowCoverage.js +1 -2
  123. package/dist/orchestrator/flowPlanning.js +8 -4
  124. package/dist/orchestrator/graphEnrichmentExecutor.js +67 -45
  125. package/dist/orchestrator/ingestionExecutors.js +9 -1
  126. package/dist/orchestrator/intakeExecutors.d.ts +0 -4
  127. package/dist/orchestrator/intakeExecutors.js +24 -14
  128. package/dist/orchestrator/localCommands.d.ts +1 -0
  129. package/dist/orchestrator/localCommands.js +10 -17
  130. package/dist/orchestrator/nextStep.js +3 -1
  131. package/dist/orchestrator/requeueCommand.js +4 -0
  132. package/dist/orchestrator/reviewPacketGraph.js +50 -18
  133. package/dist/orchestrator/reviewPackets.js +10 -8
  134. package/dist/orchestrator/runtimeCommand.js +35 -7
  135. package/dist/orchestrator/runtimeValidationUpdate.js +6 -0
  136. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +3 -2
  137. package/dist/orchestrator/selectiveDeepening/lensVerification.js +44 -18
  138. package/dist/orchestrator/staleness.js +3 -3
  139. package/dist/orchestrator/state.js +1 -1
  140. package/dist/orchestrator/syntaxResolutionExecutor.js +17 -24
  141. package/dist/orchestrator/synthesisExecutors.js +1 -0
  142. package/dist/orchestrator/taskBuilder.js +5 -4
  143. package/dist/providers/claudeCodeProvider.js +5 -2
  144. package/dist/providers/opencodeProvider.js +4 -1
  145. package/dist/quota/discoveredLimits.js +3 -3
  146. package/dist/quota/headerExtraction.js +5 -2
  147. package/dist/quota/headerExtractors/claudeCodeHeaderExtractor.js +3 -0
  148. package/dist/quota/headerExtractors/index.js +3 -3
  149. package/dist/quota/index.d.ts +3 -1
  150. package/dist/quota/index.js +3 -0
  151. package/dist/reporting/findingRanks.d.ts +3 -0
  152. package/dist/reporting/findingRanks.js +24 -0
  153. package/dist/reporting/mergeFindings.js +1 -24
  154. package/dist/reporting/synthesis.d.ts +3 -1
  155. package/dist/reporting/synthesis.js +30 -6
  156. package/dist/reporting/synthesisNarrativePrompt.js +3 -0
  157. package/dist/reporting/workBlocks.js +1 -14
  158. package/dist/supervisor/operatorHandoff.js +2 -6
  159. package/dist/supervisor/runLedger.js +30 -41
  160. package/dist/types/activeDispatch.d.ts +31 -0
  161. package/dist/types/activeDispatch.js +2 -0
  162. package/dist/types.d.ts +21 -4
  163. package/dist/types.js +24 -16
  164. package/dist/validation/artifacts.js +3 -0
  165. package/dist/validation/auditResults.js +8 -2
  166. package/package.json +2 -2
  167. package/schemas/audit_findings.schema.json +5 -1
  168. package/schemas/audit_plan_metrics.schema.json +1 -1
  169. package/schemas/audit_result.schema.json +5 -6
  170. package/schemas/audit_task.schema.json +1 -4
  171. package/schemas/blind_spot_register.schema.json +1 -1
  172. package/schemas/coverage_matrix.schema.json +2 -8
  173. package/schemas/finding.schema.json +1 -16
  174. package/schemas/flow_coverage.schema.json +2 -8
  175. package/schemas/graph_bundle.schema.json +31 -0
  176. package/schemas/lens.schema.json +7 -0
  177. package/schemas/review_packets.schema.json +6 -17
  178. package/schemas/step_contract.schema.json +8 -2
  179. package/schemas/unit_manifest.schema.json +1 -4
  180. package/scripts/postinstall.mjs +4 -3
  181. package/skills/audit-code/audit-code.prompt.md +3 -4
  182. package/dist/extractors/graphManifestEdges.d.ts +0 -12
  183. package/dist/extractors/graphManifestEdges.js +0 -1135
@@ -247,55 +247,13 @@ async function recordWaveQuota(params) {
247
247
  }
248
248
  }
249
249
  }
250
- async function runInlineStep(params) {
251
- const { root, artifactsDir, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, analyzers, since, artifactsWritten, providerName, decision, pendingBatchAuditResults, } = params;
250
+ async function applyWorkerResult(params) {
251
+ const { root, artifactsDir, runId, obligationId, providerName, preferredExecutor, workerResult, paths, startedAt, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, decision, pendingBatchAuditResults, } = params;
252
252
  let anyProgress = params.anyProgress;
253
253
  let pendingAuditResultsPath = params.pendingAuditResultsPath;
254
254
  let pendingRuntimeUpdatesPath = params.pendingRuntimeUpdatesPath;
255
255
  let pendingExternalAnalyzerPath = params.pendingExternalAnalyzerPath;
256
- await clearDispatchFiles(artifactsDir);
257
- const startedAt = new Date().toISOString();
258
- let workerResult;
259
- try {
260
- const result = await runAuditStep({
261
- root,
262
- artifactsDir,
263
- preferredExecutor,
264
- auditResultsPath,
265
- runtimeUpdatesPath,
266
- externalAnalyzerPath,
267
- analyzers,
268
- since,
269
- });
270
- workerResult = {
271
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
272
- run_id: runId,
273
- obligation_id: obligationId,
274
- status: result.progress_made ? "completed" : "no_progress",
275
- progress_made: result.progress_made,
276
- selected_executor: result.selected_executor,
277
- artifacts_written: result.artifacts_written,
278
- summary: result.progress_summary,
279
- next_likely_step: result.next_likely_step,
280
- errors: [],
281
- };
282
- }
283
- catch (error) {
284
- const message = error instanceof Error ? error.message : String(error);
285
- workerResult = {
286
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
287
- run_id: runId,
288
- obligation_id: obligationId,
289
- status: "failed",
290
- progress_made: false,
291
- selected_executor: preferredExecutor,
292
- artifacts_written: [],
293
- summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
294
- next_likely_step: decision.selected_obligation,
295
- errors: [message],
296
- };
297
- }
298
- await persistWorkerRunArtifacts(paths, workerResult, "inline");
256
+ const artifactsWritten = params.artifactsWritten;
299
257
  await appendRunLedgerEntry(artifactsDir, {
300
258
  run_id: runId,
301
259
  provider: providerName,
@@ -339,7 +297,7 @@ async function runInlineStep(params) {
339
297
  executor: workerResult.selected_executor,
340
298
  blocker: buildWorkerFailureBlocker(workerResult),
341
299
  })
342
- : bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
300
+ : (bundleAfter.audit_state ?? deriveAuditState(bundleAfter));
343
301
  if (shouldBlock) {
344
302
  await writeCoreArtifacts(artifactsDir, {
345
303
  ...bundleAfter,
@@ -385,6 +343,77 @@ async function runInlineStep(params) {
385
343
  pendingExternalAnalyzerPath,
386
344
  };
387
345
  }
346
+ async function runInlineStep(params) {
347
+ const { root, artifactsDir, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, analyzers, since, artifactsWritten, providerName, decision, pendingBatchAuditResults, } = params;
348
+ let anyProgress = params.anyProgress;
349
+ let pendingAuditResultsPath = params.pendingAuditResultsPath;
350
+ let pendingRuntimeUpdatesPath = params.pendingRuntimeUpdatesPath;
351
+ let pendingExternalAnalyzerPath = params.pendingExternalAnalyzerPath;
352
+ await clearDispatchFiles(artifactsDir);
353
+ const startedAt = new Date().toISOString();
354
+ let workerResult;
355
+ try {
356
+ const result = await runAuditStep({
357
+ root,
358
+ artifactsDir,
359
+ preferredExecutor,
360
+ auditResultsPath,
361
+ runtimeUpdatesPath,
362
+ externalAnalyzerPath,
363
+ analyzers,
364
+ since,
365
+ });
366
+ workerResult = {
367
+ contract_version: WORKER_RESULT_CONTRACT_VERSION,
368
+ run_id: runId,
369
+ obligation_id: obligationId,
370
+ status: result.progress_made ? "completed" : "no_progress",
371
+ progress_made: result.progress_made,
372
+ selected_executor: result.selected_executor,
373
+ artifacts_written: result.artifacts_written,
374
+ summary: result.progress_summary,
375
+ next_likely_step: result.next_likely_step,
376
+ errors: [],
377
+ };
378
+ }
379
+ catch (error) {
380
+ const message = error instanceof Error ? error.message : String(error);
381
+ workerResult = {
382
+ contract_version: WORKER_RESULT_CONTRACT_VERSION,
383
+ run_id: runId,
384
+ obligation_id: obligationId,
385
+ status: "failed",
386
+ progress_made: false,
387
+ selected_executor: preferredExecutor,
388
+ artifacts_written: [],
389
+ summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
390
+ next_likely_step: decision.selected_obligation,
391
+ errors: [message],
392
+ };
393
+ }
394
+ await persistWorkerRunArtifacts(paths, workerResult, "inline");
395
+ return applyWorkerResult({
396
+ root,
397
+ artifactsDir,
398
+ runId,
399
+ obligationId,
400
+ providerName,
401
+ preferredExecutor,
402
+ workerResult,
403
+ paths,
404
+ startedAt,
405
+ anyProgress,
406
+ artifactsWritten,
407
+ pendingBatchAuditResults,
408
+ pendingAuditResultsPath,
409
+ pendingRuntimeUpdatesPath,
410
+ pendingExternalAnalyzerPath,
411
+ auditResultsPath,
412
+ runtimeUpdatesPath,
413
+ externalAnalyzerPath,
414
+ decision,
415
+ });
416
+ }
388
417
  async function runSingleWorkerStep(params) {
389
418
  const { root, artifactsDir, selfCliPath, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, bundle, timeoutMs, uiMode, provider, artifactsWritten, decision, pendingBatchAuditResults, } = params;
390
419
  let anyProgress = params.anyProgress;
@@ -493,94 +522,355 @@ async function runSingleWorkerStep(params) {
493
522
  };
494
523
  await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
495
524
  }
496
- await appendRunLedgerEntry(artifactsDir, {
497
- run_id: runId,
498
- provider: provider.name,
525
+ return applyWorkerResult({
526
+ root,
527
+ artifactsDir,
528
+ runId,
529
+ obligationId,
530
+ providerName: provider.name,
531
+ preferredExecutor,
532
+ workerResult,
533
+ paths,
534
+ startedAt,
535
+ anyProgress,
536
+ artifactsWritten,
537
+ pendingBatchAuditResults,
538
+ pendingAuditResultsPath,
539
+ pendingRuntimeUpdatesPath,
540
+ pendingExternalAnalyzerPath,
541
+ auditResultsPath: providerAuditResultsPath,
542
+ runtimeUpdatesPath,
543
+ externalAnalyzerPath,
544
+ decision,
545
+ });
546
+ }
547
+ async function handleLocalSubprocessBlock(params) {
548
+ const { root, artifactsDir, selfCliPath, bundle, argv, obligationId, preferredExecutor, provider, sessionConfig, timeoutMs, runCount, anyProgress, artifactsWritten, } = params;
549
+ if (preferredExecutor !== "agent" || provider.name !== LOCAL_SUBPROCESS_PROVIDER_NAME) {
550
+ return false;
551
+ }
552
+ const blocker = buildManualReviewBlocker(provider.name);
553
+ const blockedState = buildBlockedAuditState({
554
+ state: bundle.audit_state ?? deriveAuditState(bundle),
555
+ obligationId,
556
+ executor: preferredExecutor,
557
+ blocker,
558
+ });
559
+ await writeCoreArtifacts(artifactsDir, {
560
+ ...bundle,
561
+ audit_state: blockedState,
562
+ });
563
+ const blockRunId = buildRunId(obligationId, runCount + 1);
564
+ const blockPaths = getRunPaths(artifactsDir, blockRunId);
565
+ const blockPendingTasks = await addFileLineCountHints(root, buildPendingAuditTasks(bundle));
566
+ const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
567
+ const blockAuditResultsPath = join(blockPaths.runDir, "run-results.json");
568
+ const blockReadPaths = new Set();
569
+ for (const pt of blockPendingTasks) {
570
+ for (const fp of pt.file_paths)
571
+ blockReadPaths.add(fp);
572
+ }
573
+ const blockTask = {
574
+ contract_version: "audit-code-worker/v1alpha1",
575
+ run_id: blockRunId,
576
+ repo_root: root,
577
+ artifacts_dir: artifactsDir,
499
578
  obligation_id: obligationId,
500
- selected_executor: workerResult.selected_executor,
501
- status: workerResult.status,
502
- started_at: startedAt,
503
- ended_at: new Date().toISOString(),
504
- result_path: paths.resultPath,
579
+ preferred_executor: preferredExecutor,
580
+ result_path: blockPaths.resultPath,
581
+ worker_command: [
582
+ process.execPath,
583
+ selfCliPath,
584
+ "worker-run",
585
+ "--task",
586
+ blockPaths.taskPath,
587
+ ],
588
+ audit_results_path: blockAuditResultsPath,
589
+ pending_audit_tasks_path: blockPendingTasksPath,
590
+ timeout_ms: timeoutMs,
591
+ max_retries: 0,
592
+ access: {
593
+ read_paths: [...blockReadPaths],
594
+ write_paths: [blockAuditResultsPath, blockPaths.resultPath],
595
+ },
596
+ };
597
+ const blockPrompt = renderWorkerPrompt(blockTask);
598
+ await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
599
+ await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
600
+ const reviewRun = {
601
+ run_id: blockRunId,
602
+ task_path: blockPaths.taskPath,
603
+ prompt_path: blockPaths.promptPath,
604
+ pending_audit_tasks_path: blockPendingTasksPath,
605
+ audit_results_path: blockAuditResultsPath,
606
+ worker_command: blockTask.worker_command,
607
+ };
608
+ // Render the actionable dispatch / single-task step here instead of
609
+ // leaving the host to issue next-step as a second command. Capability is
610
+ // resolved from flags/config/env with a sane default, so nothing is
611
+ // required from the host to make progress. If rendering fails we still
612
+ // emit the hand-off below — run-to-completion is never worse than before,
613
+ // and next-step will re-render and surface the error loudly.
614
+ try {
615
+ await renderSemanticReviewStep({
616
+ root,
617
+ artifactsDir,
618
+ activeReviewRun: reviewRun,
619
+ hostCanDispatch: resolveHostDispatchCapability({
620
+ explicit: getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents"),
621
+ sessionConfig,
622
+ }),
623
+ hostMaxActiveSubagents: getHostMaxActiveSubagents(argv),
624
+ hostCanRestrictSubagentTools: getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ?? false,
625
+ hostCanSelectSubagentModel: getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false,
626
+ });
627
+ }
628
+ catch (stepError) {
629
+ process.stderr.write(`[audit-code] Could not pre-render the review step; the operator hand-off points to next-step instead. ${stepError instanceof Error ? stepError.message : String(stepError)}\n`);
630
+ }
631
+ await emitEnvelope({
632
+ root,
633
+ artifactsDir,
634
+ bundle: {
635
+ ...bundle,
636
+ audit_state: blockedState,
637
+ },
638
+ audit_state: blockedState,
639
+ selected_obligation: obligationId,
640
+ selected_executor: preferredExecutor,
641
+ progress_made: anyProgress,
642
+ artifacts_written: Array.from(new Set([...artifactsWritten, "audit_state.json"])),
643
+ progress_summary: blocker,
644
+ next_likely_step: null,
645
+ providerName: provider.name,
646
+ activeReviewRun: reviewRun,
505
647
  });
506
- const lastResult = workerResult;
507
- if (workerResult.progress_made) {
508
- anyProgress = true;
648
+ return true;
649
+ }
650
+ async function handleNoExecutor(params) {
651
+ const { root, artifactsDir, bundle, decision, lastResult, anyProgress, artifactsWritten, runCount, providerName, } = params;
652
+ const state = decision.state;
653
+ await clearDispatchFiles(artifactsDir);
654
+ await emitEnvelope({
655
+ root,
656
+ artifactsDir,
657
+ bundle,
658
+ audit_state: state,
659
+ selected_obligation: anyProgress
660
+ ? (lastResult?.obligation_id ?? null)
661
+ : null,
662
+ selected_executor: anyProgress
663
+ ? (lastResult?.selected_executor ?? null)
664
+ : null,
665
+ progress_made: anyProgress,
666
+ artifacts_written: Array.from(artifactsWritten),
667
+ progress_summary: anyProgress && state.status === "complete"
668
+ ? `Completed audit in ${runCount} fresh worker runs.`
669
+ : decision.reason,
670
+ next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
671
+ providerName,
672
+ });
673
+ if (state.status === "complete") {
674
+ await promoteFinalAuditReport({ artifactsDir });
509
675
  }
510
- for (const artifact of workerResult.artifacts_written) {
511
- artifactsWritten.add(artifact);
676
+ }
677
+ async function runParallelWaveStep(params) {
678
+ const { root, artifactsDir, selfCliPath, bundle, argv, sessionConfig, provider, hostModel, obligationId, parallelWorkers, agentBatchSize, timeoutMs, uiMode, } = params;
679
+ let runCount = params.runCount;
680
+ let anyProgress = params.anyProgress;
681
+ const artifactsWritten = params.artifactsWritten;
682
+ const quotaState = await readQuotaState();
683
+ const normalizedHostModel = hostModel ?? null;
684
+ const providerModelKey = buildProviderModelKey(provider.name, normalizedHostModel);
685
+ const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
686
+ const allCandidateTasks = buildPendingAuditTasks(bundle);
687
+ const candidateGroups = chunkArray(allCandidateTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
688
+ const candidateSizeIndex = sizeIndexFromManifest(bundle.repo_manifest);
689
+ const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g, candidateSizeIndex));
690
+ const providerLimits = await provider.queryLimits?.(normalizedHostModel)
691
+ .then((r) => r ? { ...r, source: "provider_query" } : null)
692
+ .catch(() => null)
693
+ ?? null;
694
+ const cachedLimits = await lookupDiscoveredLimits(providerModelKey).catch(() => null);
695
+ const discoveredLimits = mergeDiscoveredLimits(providerLimits, cachedLimits);
696
+ const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
697
+ const quotaSource = buildQuotaSource({ halfLifeHours });
698
+ const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(providerModelKey).catch(() => null);
699
+ const hostConcurrencyLimit = resolveHostActiveSubagentLimit({ sessionConfig });
700
+ const waveSchedule = scheduleWave({
701
+ providerName: resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig),
702
+ sessionConfig,
703
+ hostModel: normalizedHostModel,
704
+ requestedConcurrency: parallelWorkers,
705
+ estimatedSlotTokens: slotTokenEstimates,
706
+ quotaStateEntry,
707
+ hostConcurrencyLimit,
708
+ quotaSourceSnapshot,
709
+ discoveredLimits,
710
+ });
711
+ const waveSize = waveSchedule.wave_size;
712
+ if (waveSchedule.cooldown_until) {
713
+ const waitMs = new Date(waveSchedule.cooldown_until).getTime() - Date.now();
714
+ if (waitMs > 0) {
715
+ const cappedWait = Math.min(waitMs, 120_000);
716
+ process.stderr.write(`[quota] Cooldown active — waiting ${Math.ceil(cappedWait / 1000)}s before next wave.\n`);
717
+ await new Promise((r) => setTimeout(r, cappedWait));
718
+ }
512
719
  }
513
- artifactsWritten.add("run-ledger.json");
514
- if (externalAnalyzerPath)
515
- pendingExternalAnalyzerPath = undefined;
516
- if (auditResultsPath &&
517
- pendingBatchAuditResults[0] === auditResultsPath &&
518
- preferredExecutor === "result_ingestion_executor" &&
519
- workerResult.status !== "failed" &&
520
- workerResult.status !== "blocked") {
521
- pendingBatchAuditResults.shift();
720
+ const taskGroups = candidateGroups.slice(0, waveSize);
721
+ const { slots: workerSlots, runCountAfter } = await buildParallelWaveSlots({
722
+ root,
723
+ artifactsDir,
724
+ selfCliPath,
725
+ taskGroups,
726
+ obligationId,
727
+ runCountStart: runCount,
728
+ timeoutMs,
729
+ });
730
+ runCount = runCountAfter;
731
+ const parallelStartedAt = new Date().toISOString();
732
+ await writeWaveManifest(artifactsDir, {
733
+ obligation_id: obligationId ?? "unknown",
734
+ started_at: parallelStartedAt,
735
+ pid: process.pid,
736
+ slots: workerSlots.map(buildWaveSlotEntry),
737
+ });
738
+ const { results: launchResults } = await runSlidingWindow(workerSlots.map((slot) => () => provider.launch({
739
+ repoRoot: root,
740
+ runId: slot.runId,
741
+ obligationId,
742
+ promptPath: slot.paths.promptPath,
743
+ taskPath: slot.paths.taskPath,
744
+ resultPath: slot.paths.resultPath,
745
+ stdoutPath: slot.paths.stdoutPath,
746
+ stderrPath: slot.paths.stderrPath,
747
+ uiMode,
748
+ timeoutMs,
749
+ })), waveSize);
750
+ const launchErrorsByRunId = new Map();
751
+ for (let index = 0; index < launchResults.length; index++) {
752
+ const outcome = launchResults[index];
753
+ if (outcome?.status === "rejected") {
754
+ launchErrorsByRunId.set(workerSlots[index].runId, outcome.reason instanceof Error
755
+ ? outcome.reason.message
756
+ : String(outcome.reason));
757
+ }
758
+ else if (outcome?.status === "fulfilled") {
759
+ const launchExitSummary = summarizeLaunchExit(outcome.value);
760
+ if (launchExitSummary) {
761
+ launchErrorsByRunId.set(workerSlots[index].runId, launchExitSummary);
762
+ }
763
+ }
522
764
  }
523
- if (providerAuditResultsPath)
524
- pendingAuditResultsPath = undefined;
525
- if (runtimeUpdatesPath)
526
- pendingRuntimeUpdatesPath = undefined;
527
- if (workerResult.status === "failed" ||
528
- workerResult.status === "blocked" ||
529
- workerResult.status === "no_progress") {
765
+ const ingestion = await ingestParallelWaveResults({
766
+ root,
767
+ artifactsDir,
768
+ workerSlots,
769
+ launchErrorsByRunId,
770
+ obligationId,
771
+ parallelStartedAt,
772
+ providerName: provider.name,
773
+ anyProgress,
774
+ artifactsWritten,
775
+ });
776
+ const batchProgress = ingestion.batchProgress;
777
+ const batchErrors = ingestion.batchErrors;
778
+ anyProgress = ingestion.anyProgress;
779
+ await recordWaveQuota({
780
+ providerModelKey,
781
+ providerName: provider.name,
782
+ workerSlots,
783
+ slotTokenEstimates,
784
+ batchErrors,
785
+ halfLifeHours: sessionConfig.quota?.empirical_half_life_hours ?? 24,
786
+ });
787
+ await removeWaveManifest(artifactsDir);
788
+ if (batchErrors.length > 0) {
530
789
  const bundleAfter = await loadArtifactBundle(artifactsDir);
531
- const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
532
- const state = shouldBlock
533
- ? buildBlockedAuditState({
534
- state: deriveAuditState(bundleAfter),
535
- obligationId: workerResult.obligation_id,
536
- executor: workerResult.selected_executor,
537
- blocker: buildWorkerFailureBlocker(workerResult),
538
- })
539
- : deriveAuditState(bundleAfter);
540
- if (shouldBlock) {
541
- await writeCoreArtifacts(artifactsDir, {
542
- ...bundleAfter,
543
- audit_state: state,
544
- });
545
- }
790
+ const blockedState = buildBlockedAuditState({
791
+ state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
792
+ obligationId,
793
+ executor: "agent",
794
+ blocker: `Parallel worker batch failed for ${batchErrors.length} run(s). ` +
795
+ batchErrors.slice(0, 3).join(" | "),
796
+ });
797
+ await writeCoreArtifacts(artifactsDir, {
798
+ ...bundleAfter,
799
+ audit_state: blockedState,
800
+ });
546
801
  await emitEnvelope({
547
802
  root,
548
803
  artifactsDir,
549
- bundle: shouldBlock
550
- ? { ...bundleAfter, audit_state: state }
551
- : bundleAfter,
804
+ bundle: { ...bundleAfter, audit_state: blockedState },
805
+ audit_state: blockedState,
806
+ selected_obligation: obligationId,
807
+ selected_executor: "agent",
808
+ progress_made: anyProgress,
809
+ artifacts_written: Array.from(new Set([...ingestion.artifactsWritten, "audit_state.json"])),
810
+ progress_summary: `Parallel worker batch failed for ${batchErrors.length} run(s).\n` +
811
+ batchErrors.join("\n"),
812
+ next_likely_step: null,
813
+ providerName: provider.name,
814
+ });
815
+ return { done: true, runCount, anyProgress, artifactsWritten: ingestion.artifactsWritten };
816
+ }
817
+ if (!batchProgress) {
818
+ const bundleAfter = await loadArtifactBundle(artifactsDir);
819
+ const state = bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
820
+ await emitEnvelope({
821
+ root,
822
+ artifactsDir,
823
+ bundle: bundleAfter,
552
824
  audit_state: state,
553
- selected_obligation: workerResult.obligation_id,
554
- selected_executor: workerResult.selected_executor,
825
+ selected_obligation: obligationId,
826
+ selected_executor: "agent",
555
827
  progress_made: anyProgress,
556
- artifacts_written: Array.from(shouldBlock
557
- ? new Set([...artifactsWritten, "audit_state.json"])
558
- : artifactsWritten),
559
- progress_summary: buildWorkerFailureBlocker(workerResult),
560
- next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
828
+ artifacts_written: Array.from(ingestion.artifactsWritten),
829
+ progress_summary: "Parallel worker batch made no progress.",
830
+ next_likely_step: obligationId,
561
831
  providerName: provider.name,
562
832
  });
563
- return {
564
- done: true,
565
- lastResult,
566
- anyProgress,
567
- artifactsWritten,
568
- pendingBatchAuditResults,
569
- pendingAuditResultsPath,
570
- pendingRuntimeUpdatesPath,
571
- pendingExternalAnalyzerPath,
572
- };
833
+ return { done: true, runCount, anyProgress, artifactsWritten: ingestion.artifactsWritten };
834
+ }
835
+ return { done: false, runCount, anyProgress, artifactsWritten: ingestion.artifactsWritten };
836
+ }
837
+ async function handleMaxRunsReached(params) {
838
+ const { root, artifactsDir, lastResult, anyProgress, artifactsWritten, maxRuns, providerName, } = params;
839
+ const bundle = await loadArtifactBundle(artifactsDir);
840
+ const decision = decideNextStep(bundle);
841
+ const state = decision.state;
842
+ // A rendered report is the deliverable: if synthesis already produced one (or
843
+ // the state is formally complete), finish the run on it instead of stranding
844
+ // it in the artifacts dir behind a bare "max run limit" non-completion. This
845
+ // mirrors next-step's terminalStep so both loops present a completed audit the
846
+ // same way, even when finalization churned (runtime_validation <-> synthesis
847
+ // ping-pong, or filesystem-retry revision churn) up to the backstop. With no
848
+ // report yet, the run limit is a genuine non-terminal stop.
849
+ const reportRendered = state.status === "complete" || Boolean(bundle.audit_report);
850
+ if (reportRendered) {
851
+ await clearDispatchFiles(artifactsDir);
852
+ }
853
+ const terminalState = reportRendered && state.status !== "complete"
854
+ ? { ...state, status: "complete" }
855
+ : state;
856
+ await emitEnvelope({
857
+ root,
858
+ artifactsDir,
859
+ bundle,
860
+ audit_state: terminalState,
861
+ selected_obligation: lastResult?.obligation_id ?? decision.selected_obligation,
862
+ selected_executor: lastResult?.selected_executor ?? decision.selected_executor,
863
+ progress_made: anyProgress,
864
+ artifacts_written: Array.from(artifactsWritten),
865
+ progress_summary: reportRendered && state.status !== "complete"
866
+ ? `Audit report already rendered; completing the run after reaching the max run limit (${maxRuns}) during finalization.`
867
+ : `Reached max run limit (${maxRuns}) before terminal state.`,
868
+ next_likely_step: reportRendered ? null : decision.selected_obligation,
869
+ providerName,
870
+ });
871
+ if (reportRendered) {
872
+ await promoteFinalAuditReport({ artifactsDir });
573
873
  }
574
- return {
575
- done: false,
576
- lastResult,
577
- anyProgress,
578
- artifactsWritten,
579
- pendingBatchAuditResults,
580
- pendingAuditResultsPath,
581
- pendingRuntimeUpdatesPath,
582
- pendingExternalAnalyzerPath,
583
- };
584
874
  }
585
875
  export async function cmdRunToCompletion(argv) {
586
876
  const root = getRootDir(argv);
@@ -678,287 +968,62 @@ export async function cmdRunToCompletion(argv) {
678
968
  obligationId = "runtime_validation_current";
679
969
  runtimeUpdatesPath = pendingRuntimeUpdatesPath;
680
970
  }
681
- if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
682
- const blocker = buildManualReviewBlocker(provider.name);
683
- const blockedState = buildBlockedAuditState({
684
- state: decision.state,
685
- obligationId,
686
- executor: preferredExecutor,
687
- blocker,
688
- });
689
- await writeCoreArtifacts(artifactsDir, {
690
- ...bundle,
691
- audit_state: blockedState,
692
- });
693
- const blockRunId = buildRunId(obligationId, runCount + 1);
694
- const blockPaths = getRunPaths(artifactsDir, blockRunId);
695
- const blockPendingTasks = await addFileLineCountHints(root, buildPendingAuditTasks(bundle));
696
- const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
697
- const blockAuditResultsPath = join(blockPaths.runDir, "run-results.json");
698
- const blockReadPaths = new Set();
699
- for (const pt of blockPendingTasks) {
700
- for (const fp of pt.file_paths)
701
- blockReadPaths.add(fp);
702
- }
703
- const blockTask = {
704
- contract_version: "audit-code-worker/v1alpha1",
705
- run_id: blockRunId,
706
- repo_root: root,
707
- artifacts_dir: artifactsDir,
708
- obligation_id: obligationId,
709
- preferred_executor: preferredExecutor,
710
- result_path: blockPaths.resultPath,
711
- worker_command: [
712
- process.execPath,
713
- selfCliPath,
714
- "worker-run",
715
- "--task",
716
- blockPaths.taskPath,
717
- ],
718
- audit_results_path: blockAuditResultsPath,
719
- pending_audit_tasks_path: blockPendingTasksPath,
720
- timeout_ms: timeoutMs,
721
- max_retries: 0,
722
- access: {
723
- read_paths: [...blockReadPaths],
724
- write_paths: [blockAuditResultsPath, blockPaths.resultPath],
725
- },
726
- };
727
- const blockPrompt = renderWorkerPrompt(blockTask);
728
- await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
729
- await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
730
- const reviewRun = {
731
- run_id: blockRunId,
732
- task_path: blockPaths.taskPath,
733
- prompt_path: blockPaths.promptPath,
734
- pending_audit_tasks_path: blockPendingTasksPath,
735
- audit_results_path: blockAuditResultsPath,
736
- worker_command: blockTask.worker_command,
737
- };
738
- // Render the actionable dispatch / single-task step here instead of
739
- // leaving the host to issue next-step as a second command. Capability is
740
- // resolved from flags/config/env with a sane default, so nothing is
741
- // required from the host to make progress. If rendering fails we still
742
- // emit the hand-off below — run-to-completion is never worse than before,
743
- // and next-step will re-render and surface the error loudly.
744
- try {
745
- await renderSemanticReviewStep({
746
- root,
747
- artifactsDir,
748
- activeReviewRun: reviewRun,
749
- hostCanDispatch: resolveHostDispatchCapability({
750
- explicit: getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents"),
751
- sessionConfig,
752
- }),
753
- hostMaxActiveSubagents: getHostMaxActiveSubagents(argv),
754
- hostCanRestrictSubagentTools: getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ?? false,
755
- hostCanSelectSubagentModel: getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false,
756
- });
757
- }
758
- catch (stepError) {
759
- process.stderr.write(`[audit-code] Could not pre-render the review step; the operator hand-off points to next-step instead. ${stepError instanceof Error ? stepError.message : String(stepError)}\n`);
760
- }
761
- await emitEnvelope({
762
- root,
763
- artifactsDir,
764
- bundle: {
765
- ...bundle,
766
- audit_state: blockedState,
767
- },
768
- audit_state: blockedState,
769
- selected_obligation: obligationId,
770
- selected_executor: preferredExecutor,
771
- progress_made: anyProgress,
772
- artifacts_written: Array.from(new Set([...artifactsWritten, "audit_state.json"])),
773
- progress_summary: blocker,
774
- next_likely_step: null,
775
- providerName: provider.name,
776
- activeReviewRun: reviewRun,
777
- });
971
+ if (await handleLocalSubprocessBlock({
972
+ root,
973
+ artifactsDir,
974
+ selfCliPath,
975
+ bundle,
976
+ argv,
977
+ obligationId,
978
+ preferredExecutor,
979
+ provider,
980
+ sessionConfig,
981
+ timeoutMs,
982
+ runCount,
983
+ anyProgress,
984
+ artifactsWritten,
985
+ }))
778
986
  return;
779
- }
780
987
  if (!preferredExecutor) {
781
- const state = decision.state;
782
- await clearDispatchFiles(artifactsDir);
783
- await emitEnvelope({
988
+ await handleNoExecutor({
784
989
  root,
785
990
  artifactsDir,
786
991
  bundle,
787
- audit_state: state,
788
- selected_obligation: anyProgress
789
- ? (lastResult?.obligation_id ?? null)
790
- : null,
791
- selected_executor: anyProgress
792
- ? (lastResult?.selected_executor ?? null)
793
- : null,
794
- progress_made: anyProgress,
795
- artifacts_written: Array.from(artifactsWritten),
796
- progress_summary: anyProgress && state.status === "complete"
797
- ? `Completed audit in ${runCount} fresh worker runs.`
798
- : decision.reason,
799
- next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
992
+ decision,
993
+ lastResult,
994
+ anyProgress,
995
+ artifactsWritten,
996
+ runCount,
800
997
  providerName: provider.name,
801
998
  });
802
- if (state.status === "complete") {
803
- await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
804
- }
805
999
  return;
806
1000
  }
807
1001
  if (preferredExecutor === "agent" && parallelWorkers > 1) {
808
- const quotaState = await readQuotaState();
809
- const providerModelKey = buildProviderModelKey(provider.name, hostModel);
810
- const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
811
- const allCandidateTasks = buildPendingAuditTasks(bundle);
812
- const candidateGroups = chunkArray(allCandidateTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
813
- const candidateSizeIndex = sizeIndexFromManifest(bundle.repo_manifest);
814
- const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g, candidateSizeIndex));
815
- const providerLimits = await provider.queryLimits?.(hostModel)
816
- .then((r) => r ? { ...r, source: "provider_query" } : null)
817
- .catch(() => null)
818
- ?? null;
819
- const cachedLimits = await lookupDiscoveredLimits(providerModelKey).catch(() => null);
820
- const discoveredLimits = mergeDiscoveredLimits(providerLimits, cachedLimits);
821
- const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
822
- const quotaSource = buildQuotaSource({ halfLifeHours });
823
- const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(providerModelKey).catch(() => null);
824
- const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
825
- sessionConfig,
826
- });
827
- const waveSchedule = scheduleWave({
828
- providerName: resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig),
829
- sessionConfig,
830
- hostModel,
831
- requestedConcurrency: parallelWorkers,
832
- estimatedSlotTokens: slotTokenEstimates,
833
- quotaStateEntry,
834
- hostConcurrencyLimit,
835
- quotaSourceSnapshot,
836
- discoveredLimits,
837
- });
838
- const waveSize = waveSchedule.wave_size;
839
- if (waveSchedule.cooldown_until) {
840
- const waitMs = new Date(waveSchedule.cooldown_until).getTime() - Date.now();
841
- if (waitMs > 0) {
842
- const cappedWait = Math.min(waitMs, 120_000);
843
- process.stderr.write(`[quota] Cooldown active — waiting ${Math.ceil(cappedWait / 1000)}s before next wave.\n`);
844
- await new Promise((r) => setTimeout(r, cappedWait));
845
- }
846
- }
847
- const taskGroups = candidateGroups.slice(0, waveSize);
848
- const { slots: workerSlots, runCountAfter } = await buildParallelWaveSlots({
1002
+ const waveResult = await runParallelWaveStep({
849
1003
  root,
850
1004
  artifactsDir,
851
1005
  selfCliPath,
852
- taskGroups,
1006
+ bundle,
1007
+ argv,
1008
+ sessionConfig,
1009
+ provider,
1010
+ hostModel,
853
1011
  obligationId,
854
- runCountStart: runCount,
1012
+ parallelWorkers,
1013
+ agentBatchSize,
1014
+ runCount,
855
1015
  timeoutMs,
856
- });
857
- runCount = runCountAfter;
858
- const parallelStartedAt = new Date().toISOString();
859
- await writeWaveManifest(artifactsDir, {
860
- obligation_id: obligationId ?? "unknown",
861
- started_at: parallelStartedAt,
862
- pid: process.pid,
863
- slots: workerSlots.map(buildWaveSlotEntry),
864
- });
865
- const { results: launchResults } = await runSlidingWindow(workerSlots.map((slot) => () => provider.launch({
866
- repoRoot: root,
867
- runId: slot.runId,
868
- obligationId,
869
- promptPath: slot.paths.promptPath,
870
- taskPath: slot.paths.taskPath,
871
- resultPath: slot.paths.resultPath,
872
- stdoutPath: slot.paths.stdoutPath,
873
- stderrPath: slot.paths.stderrPath,
874
1016
  uiMode,
875
- timeoutMs,
876
- })), waveSize);
877
- const launchErrorsByRunId = new Map();
878
- for (let index = 0; index < launchResults.length; index++) {
879
- const outcome = launchResults[index];
880
- if (outcome?.status === "rejected") {
881
- launchErrorsByRunId.set(workerSlots[index].runId, outcome.reason instanceof Error
882
- ? outcome.reason.message
883
- : String(outcome.reason));
884
- }
885
- else if (outcome?.status === "fulfilled") {
886
- const launchExitSummary = summarizeLaunchExit(outcome.value);
887
- if (launchExitSummary) {
888
- launchErrorsByRunId.set(workerSlots[index].runId, launchExitSummary);
889
- }
890
- }
891
- }
892
- const ingestion = await ingestParallelWaveResults({
893
- root,
894
- artifactsDir,
895
- workerSlots,
896
- launchErrorsByRunId,
897
- obligationId,
898
- parallelStartedAt,
899
- providerName: provider.name,
900
1017
  anyProgress,
901
1018
  artifactsWritten,
902
1019
  });
903
- const batchProgress = ingestion.batchProgress;
904
- const batchErrors = ingestion.batchErrors;
905
- anyProgress = ingestion.anyProgress;
906
- await recordWaveQuota({
907
- providerModelKey,
908
- providerName: provider.name,
909
- workerSlots,
910
- slotTokenEstimates,
911
- batchErrors,
912
- halfLifeHours: sessionConfig.quota?.empirical_half_life_hours ?? 24,
913
- });
914
- await removeWaveManifest(artifactsDir);
915
- if (batchErrors.length > 0) {
916
- const bundleAfter = await loadArtifactBundle(artifactsDir);
917
- const blockedState = buildBlockedAuditState({
918
- state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
919
- obligationId,
920
- executor: "agent",
921
- blocker: `Parallel worker batch failed for ${batchErrors.length} run(s). ` +
922
- batchErrors.slice(0, 3).join(" | "),
923
- });
924
- await writeCoreArtifacts(artifactsDir, {
925
- ...bundleAfter,
926
- audit_state: blockedState,
927
- });
928
- await emitEnvelope({
929
- root,
930
- artifactsDir,
931
- bundle: { ...bundleAfter, audit_state: blockedState },
932
- audit_state: blockedState,
933
- selected_obligation: obligationId,
934
- selected_executor: "agent",
935
- progress_made: anyProgress,
936
- artifacts_written: Array.from(new Set([...artifactsWritten, "audit_state.json"])),
937
- progress_summary: `Parallel worker batch failed for ${batchErrors.length} run(s).\n` +
938
- batchErrors.join("\n"),
939
- next_likely_step: null,
940
- providerName: provider.name,
941
- });
942
- return;
943
- }
944
- if (!batchProgress) {
945
- const bundleAfter = await loadArtifactBundle(artifactsDir);
946
- const state = bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
947
- await emitEnvelope({
948
- root,
949
- artifactsDir,
950
- bundle: bundleAfter,
951
- audit_state: state,
952
- selected_obligation: obligationId,
953
- selected_executor: "agent",
954
- progress_made: anyProgress,
955
- artifacts_written: Array.from(artifactsWritten),
956
- progress_summary: "Parallel worker batch made no progress.",
957
- next_likely_step: obligationId,
958
- providerName: provider.name,
959
- });
1020
+ runCount = waveResult.runCount;
1021
+ anyProgress = waveResult.anyProgress;
1022
+ artifactsWritten.clear();
1023
+ for (const a of waveResult.artifactsWritten)
1024
+ artifactsWritten.add(a);
1025
+ if (waveResult.done)
960
1026
  return;
961
- }
962
1027
  continue;
963
1028
  }
964
1029
  runCount += 1;
@@ -1028,39 +1093,13 @@ export async function cmdRunToCompletion(argv) {
1028
1093
  if (single.done)
1029
1094
  return;
1030
1095
  }
1031
- const bundle = await loadArtifactBundle(artifactsDir);
1032
- const decision = decideNextStep(bundle);
1033
- const state = decision.state;
1034
- // A rendered report is the deliverable: if synthesis already produced one (or
1035
- // the state is formally complete), finish the run on it instead of stranding
1036
- // it in the artifacts dir behind a bare "max run limit" non-completion. This
1037
- // mirrors next-step's terminalStep so both loops present a completed audit the
1038
- // same way, even when finalization churned (runtime_validation <-> synthesis
1039
- // ping-pong, or filesystem-retry revision churn) up to the backstop. With no
1040
- // report yet, the run limit is a genuine non-terminal stop.
1041
- const reportRendered = state.status === "complete" || Boolean(bundle.audit_report);
1042
- if (reportRendered) {
1043
- await clearDispatchFiles(artifactsDir);
1044
- }
1045
- const terminalState = reportRendered && state.status !== "complete"
1046
- ? { ...state, status: "complete" }
1047
- : state;
1048
- await emitEnvelope({
1096
+ await handleMaxRunsReached({
1049
1097
  root,
1050
1098
  artifactsDir,
1051
- bundle,
1052
- audit_state: terminalState,
1053
- selected_obligation: lastResult?.obligation_id ?? decision.selected_obligation,
1054
- selected_executor: lastResult?.selected_executor ?? decision.selected_executor,
1055
- progress_made: anyProgress,
1056
- artifacts_written: Array.from(artifactsWritten),
1057
- progress_summary: reportRendered && state.status !== "complete"
1058
- ? `Audit report already rendered; completing the run after reaching the max run limit (${maxRuns}) during finalization.`
1059
- : `Reached max run limit (${maxRuns}) before terminal state.`,
1060
- next_likely_step: reportRendered ? null : decision.selected_obligation,
1099
+ lastResult,
1100
+ anyProgress,
1101
+ artifactsWritten,
1102
+ maxRuns,
1061
1103
  providerName: provider.name,
1062
1104
  });
1063
- if (reportRendered) {
1064
- await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
1065
- }
1066
1105
  }