audit-tools 0.30.51 → 0.30.53

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 (60) hide show
  1. package/dist/audit/cli/prompts.d.ts.map +1 -1
  2. package/dist/audit/cli/prompts.js +3 -1
  3. package/dist/audit/cli/prompts.js.map +1 -1
  4. package/dist/audit/orchestrator/rollingDispatch.d.ts +2 -2
  5. package/dist/audit/orchestrator/rollingDispatch.d.ts.map +1 -1
  6. package/dist/audit/quota/index.d.ts +120 -18
  7. package/dist/audit/quota/index.d.ts.map +1 -1
  8. package/dist/audit/types/auditScope.d.ts +2 -2
  9. package/dist/remediate/steps/dispatch.d.ts.map +1 -1
  10. package/dist/remediate/steps/dispatch.js +28 -1
  11. package/dist/remediate/steps/dispatch.js.map +1 -1
  12. package/dist/remediate/steps/nextStep.d.ts +9 -1
  13. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  14. package/dist/remediate/steps/nextStep.js +107 -2
  15. package/dist/remediate/steps/nextStep.js.map +1 -1
  16. package/dist/remediate/steps/types.d.ts +1 -1
  17. package/dist/remediate/steps/types.d.ts.map +1 -1
  18. package/dist/remediate/steps/types.js.map +1 -1
  19. package/dist/shared/dispatch/rollingDispatch.d.ts +22 -1
  20. package/dist/shared/dispatch/rollingDispatch.d.ts.map +1 -1
  21. package/dist/shared/dispatch/rollingDispatch.js +147 -7
  22. package/dist/shared/dispatch/rollingDispatch.js.map +1 -1
  23. package/dist/shared/index.d.ts +5 -4
  24. package/dist/shared/index.d.ts.map +1 -1
  25. package/dist/shared/index.js +4 -3
  26. package/dist/shared/index.js.map +1 -1
  27. package/dist/shared/quota/capacity.d.ts +87 -8
  28. package/dist/shared/quota/capacity.d.ts.map +1 -1
  29. package/dist/shared/quota/capacity.js +27 -2
  30. package/dist/shared/quota/capacity.js.map +1 -1
  31. package/dist/shared/quota/claudeOAuthQuotaSource.d.ts.map +1 -1
  32. package/dist/shared/quota/claudeOAuthQuotaSource.js +56 -0
  33. package/dist/shared/quota/claudeOAuthQuotaSource.js.map +1 -1
  34. package/dist/shared/quota/quotaSource.d.ts +62 -2
  35. package/dist/shared/quota/quotaSource.d.ts.map +1 -1
  36. package/dist/shared/quota/quotaSource.js +24 -0
  37. package/dist/shared/quota/quotaSource.js.map +1 -1
  38. package/dist/shared/quota/scheduler.d.ts +19 -2
  39. package/dist/shared/quota/scheduler.d.ts.map +1 -1
  40. package/dist/shared/quota/scheduler.js +162 -86
  41. package/dist/shared/quota/scheduler.js.map +1 -1
  42. package/dist/shared/quota/state.d.ts +22 -0
  43. package/dist/shared/quota/state.d.ts.map +1 -1
  44. package/dist/shared/quota/state.js +59 -0
  45. package/dist/shared/quota/state.js.map +1 -1
  46. package/dist/shared/quota/tokenBudgetView.d.ts +17 -0
  47. package/dist/shared/quota/tokenBudgetView.d.ts.map +1 -0
  48. package/dist/shared/quota/tokenBudgetView.js +77 -0
  49. package/dist/shared/quota/tokenBudgetView.js.map +1 -0
  50. package/dist/shared/quota/types.d.ts +19 -1
  51. package/dist/shared/quota/types.d.ts.map +1 -1
  52. package/dist/shared/quota/types.js +1 -2
  53. package/dist/shared/quota/types.js.map +1 -1
  54. package/dist/shared/repair/brokeredDispatch.d.ts.map +1 -1
  55. package/dist/shared/repair/brokeredDispatch.js +5 -3
  56. package/dist/shared/repair/brokeredDispatch.js.map +1 -1
  57. package/dist/shared/types/sessionConfig.d.ts +0 -3
  58. package/dist/shared/types/sessionConfig.d.ts.map +1 -1
  59. package/dist/shared/types/sessionConfig.js.map +1 -1
  60. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, rename } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { StateStore } from "../state/store.js";
5
- import { readOptionalJsonFile, writeJsonFile, writeTextFile, buildAuditDeliverablePair, formatValidationIssues, isRecord, withFsRetry, RunLogger, DISPATCH_PROMPT_HANDOFF_NOTE, renderQuotaCoverageNudge, coerceJsonObjectArg, createRollingDispatcher, setQuotaStateDir, interpretFreeFormIntent, advance, decideFrictionTriage, buildFrictionTriageBlock, planHybridDispatch, readSettledPools, addSettledPool, sourceByPoolId, classifyProvider, selectDispatchDriver, renderDispatchDriverInstruction, HostSessionQuotaSource, buildProviderModelKey, captureStepBoundaryFriction, LENSES, SEVERITIES } from "audit-tools/shared";
5
+ import { readOptionalJsonFile, writeJsonFile, writeTextFile, buildAuditDeliverablePair, formatValidationIssues, isRecord, withFsRetry, RunLogger, DISPATCH_PROMPT_HANDOFF_NOTE, renderQuotaCoverageNudge, renderTokenBudgetView, coerceJsonObjectArg, createRollingDispatcher, setQuotaStateDir, interpretFreeFormIntent, advance, decideFrictionTriage, buildFrictionTriageBlock, planHybridDispatch, readSettledPools, addSettledPool, sourceByPoolId, classifyProvider, selectDispatchDriver, renderDispatchDriverInstruction, HostSessionQuotaSource, buildProviderModelKey, captureStepBoundaryFriction, LENSES, SEVERITIES } from "audit-tools/shared";
6
6
  import { applyPlanPipeline, buildCoverageLedger } from "../phases/plan.js";
7
7
  import { groundExtractedFindings } from "../phases/grounding.js";
8
8
  import { runTriagePhase } from "../phases/triage.js";
@@ -517,11 +517,42 @@ export async function driveRollingDispatch(levels, options) {
517
517
  });
518
518
  dispatcher.enqueue(packets);
519
519
  levelResults.push(...(await dispatcher.run()));
520
+ // Piece D: surface the wave's partial-completion terminal (quota_paused /
521
+ // empty_pool). Aggregate across waves keeping the EARLIEST-reset quota_paused
522
+ // terminal (a paused wave is retryable and should resume soonest); union the
523
+ // stranded ids so a later step re-dispatches all of them. An empty_pool
524
+ // terminal only stands when no quota_paused terminal was seen.
525
+ out.terminal = mergePartialTerminals(out.terminal, dispatcher.getTerminal());
520
526
  }
521
527
  out.levels.push({ blockIds: level.map((b) => b.block_id), results: levelResults });
522
528
  }
523
529
  return out;
524
530
  }
531
+ /**
532
+ * Fold a newly-observed partial-completion terminal into the run's aggregate
533
+ * (piece D). Prefers `quota_paused` (retryable) over `empty_pool`; among
534
+ * quota_paused terminals keeps the EARLIEST `earliest_reset_at`; always unions the
535
+ * stranded ids so no stranded node is lost across waves.
536
+ */
537
+ function mergePartialTerminals(prior, next) {
538
+ if (!next)
539
+ return prior;
540
+ if (!prior)
541
+ return next;
542
+ const strandedIds = [...new Set([...prior.stranded_ids, ...next.stranded_ids])];
543
+ const priorPaused = prior.reason === "quota_paused";
544
+ const nextPaused = next.reason === "quota_paused";
545
+ if (priorPaused || nextPaused) {
546
+ const resets = [prior.earliest_reset_at, next.earliest_reset_at].filter((r) => typeof r === "string");
547
+ const earliest = resets.length > 0 ? resets.sort()[0] : undefined;
548
+ return {
549
+ reason: "quota_paused",
550
+ stranded_ids: strandedIds,
551
+ ...(earliest ? { earliest_reset_at: earliest } : {}),
552
+ };
553
+ }
554
+ return { reason: prior.reason, stranded_ids: strandedIds };
555
+ }
525
556
  /**
526
557
  * Backends the orchestrator can drive IN-PROCESS as the per-node implement worker
527
558
  * via `driveRollingImplementDispatch` (it resolves + launches the provider with each
@@ -734,7 +765,7 @@ export async function driveRollingImplementDispatch(options) {
734
765
  // the merge-time write-scope gate reads — for file-ownership-disjoint admission
735
766
  // (INV-SOO). A block with no plan scope falls back to its touched_files.
736
767
  const writePathsByBlock = new Map(allBlockScopes.map((s) => [s.block_id, s.write_paths]));
737
- await driveRollingDispatch(levels, {
768
+ const driven = await driveRollingDispatch(levels, {
738
769
  confirmedPools,
739
770
  sessionConfig: options.sessionConfig ?? {},
740
771
  dispatchNode: dispatchNodeWithWorktree,
@@ -744,6 +775,19 @@ export async function driveRollingImplementDispatch(options) {
744
775
  hostSession,
745
776
  scopeForBlock: (block) => writePathsByBlock.get(block.block_id) ?? block.touched_files ?? [],
746
777
  });
778
+ // Piece D — persist the rolling engine's partial-completion terminal onto state
779
+ // BEFORE the merge, so the merge can SKIP-block the quota_paused stranded nodes
780
+ // (their worker rate-limited → no result file, but they must stay PENDING for a
781
+ // later step to redispatch clean — the worktrees were PRESERVED by
782
+ // acceptNodeWorktree). An `empty_pool` terminal does not affect the merge (its
783
+ // nodes are genuine failures); the `partial_terminal` obligation blocks them.
784
+ if (driven.terminal) {
785
+ const pre = await new StateStore(artifactsDir).loadState();
786
+ if (pre) {
787
+ pre.partial_completion_terminal = driven.terminal;
788
+ await new StateStore(artifactsDir).saveState(pre);
789
+ }
790
+ }
747
791
  // Deterministic merge: same path the wave flow uses (tolerant remap, write-scope
748
792
  // gate against each verified branch, lost-update detection). Worktrees are
749
793
  // already removed; the merge reads each node's result file + branch diff.
@@ -1306,6 +1350,8 @@ Then run:
1306
1350
 
1307
1351
  ${renderQuotaCoverageNudge(rolling.quotaPath, artifactsDir)}
1308
1352
 
1353
+ ${renderTokenBudgetView(rolling.quotaPath)}
1354
+
1309
1355
  ${DISPATCH_PROMPT_HANDOFF_NOTE}
1310
1356
  `,
1311
1357
  allowedCommands: [acceptCmd, rollMerge, rollNext],
@@ -1402,6 +1448,8 @@ ${SHARED_REBUILD_BETWEEN_LEVELS_NOTE}
1402
1448
 
1403
1449
  ${renderQuotaCoverageNudge(implQuotaPath, artifactsDir)}
1404
1450
 
1451
+ ${renderTokenBudgetView(implQuotaPath)}
1452
+
1405
1453
  ${DISPATCH_PROMPT_HANDOFF_NOTE}
1406
1454
 
1407
1455
  After all results exist:
@@ -1417,6 +1465,41 @@ Then run:
1417
1465
  artifactPaths: { dispatch_plan: planPath, dispatch_quota: implQuotaPath },
1418
1466
  }) };
1419
1467
  }
1468
+ /**
1469
+ * Piece D — the quota-paused resumable step. The rolling engine stranded one or
1470
+ * more nodes because every eligible provider pool hit a host session limit and is
1471
+ * paused until its stated reset. The stranded nodes stay PENDING (their worktrees
1472
+ * were preserved), so re-running next-step at/after `resetAt` redispatches them
1473
+ * clean. Emitted (not blocked) so the run is resumable, never a failure.
1474
+ */
1475
+ async function buildQuotaPausedStep(params) {
1476
+ const { root, artifactsDir, runId, strandedIds, resetAt } = params;
1477
+ const nextCommand = loaderCommand("next-step");
1478
+ const resetLine = resetAt
1479
+ ? `The earliest provider reset is \`${resetAt}\`. Wait until then, then run:`
1480
+ : `Wait for the provider session limit to reset, then run:`;
1481
+ return writeCurrentStep({
1482
+ stepKind: "quota_paused",
1483
+ status: "ready",
1484
+ runId,
1485
+ repoRoot: root,
1486
+ artifactsDir,
1487
+ prompt: `
1488
+ # Remediation paused — provider session limit
1489
+
1490
+ Every eligible provider pool hit a host session limit and is paused until its
1491
+ stated reset. ${strandedIds.length} node(s) are stranded and remain PENDING —
1492
+ their worktrees were preserved, so they will redispatch clean on resume. Nothing
1493
+ was blocked or failed; this is a resumable pause.
1494
+
1495
+ ${resetLine}
1496
+
1497
+ \`${nextCommand}\`
1498
+ `,
1499
+ allowedCommands: [nextCommand],
1500
+ stopCondition: "Stop and wait for the provider reset. Re-running next-step resumes the stranded nodes.",
1501
+ });
1502
+ }
1420
1503
  // --- Per-state handlers -----------------------------------------------------
1421
1504
  // Each handler owns one branch of the original decideNextStepInner dispatch.
1422
1505
  // Handlers that emit a step return RemediationStep directly; handlers that need
@@ -3288,6 +3371,28 @@ function buildMainObligations(ctx) {
3288
3371
  const terminal = s.partial_completion_terminal;
3289
3372
  if (!terminal)
3290
3373
  return { kind: "transition", state: s };
3374
+ // Piece D — quota_paused is a RETRYABLE pause, NOT a failure. The stranded
3375
+ // nodes stay PENDING (their worktrees were preserved), nothing is blocked,
3376
+ // and the close transition is NOT forced. EMIT a paused step (terminating
3377
+ // this advance loop) that tells the host to re-run next-step at/after the
3378
+ // stated reset, when the pool's session limit has cleared and the stranded
3379
+ // nodes redispatch clean. Clearing the terminal here (before the emit) so
3380
+ // the resuming step starts fresh; the pending nodes are the durable signal.
3381
+ if (terminal.reason === "quota_paused") {
3382
+ const resetAt = terminal.earliest_reset_at ?? null;
3383
+ delete s.partial_completion_terminal;
3384
+ await store.saveState(s);
3385
+ return {
3386
+ kind: "emit",
3387
+ step: await buildQuotaPausedStep({
3388
+ root,
3389
+ artifactsDir,
3390
+ runId: stateRunId(s),
3391
+ strandedIds: terminal.stranded_ids ?? [],
3392
+ resetAt,
3393
+ }),
3394
+ };
3395
+ }
3291
3396
  const strandedSet = new Set(terminal.stranded_ids ?? []);
3292
3397
  for (const it of Object.values(s.items ?? {})) {
3293
3398
  if (isTerminalStatus(it.status))