audit-tools 0.30.51 → 0.30.54
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.
- package/dist/audit/cli/prompts.d.ts.map +1 -1
- package/dist/audit/cli/prompts.js +3 -1
- package/dist/audit/cli/prompts.js.map +1 -1
- package/dist/audit/orchestrator/rollingDispatch.d.ts +2 -2
- package/dist/audit/orchestrator/rollingDispatch.d.ts.map +1 -1
- package/dist/audit/quota/index.d.ts +120 -18
- package/dist/audit/quota/index.d.ts.map +1 -1
- package/dist/audit/types/auditScope.d.ts +2 -2
- package/dist/remediate/steps/dispatch.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch.js +28 -1
- package/dist/remediate/steps/dispatch.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +9 -1
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +137 -4
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +1 -1
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js.map +1 -1
- package/dist/shared/dispatch/rollingDispatch.d.ts +22 -1
- package/dist/shared/dispatch/rollingDispatch.d.ts.map +1 -1
- package/dist/shared/dispatch/rollingDispatch.js +147 -7
- package/dist/shared/dispatch/rollingDispatch.js.map +1 -1
- package/dist/shared/index.d.ts +5 -4
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +4 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/quota/capacity.d.ts +87 -8
- package/dist/shared/quota/capacity.d.ts.map +1 -1
- package/dist/shared/quota/capacity.js +27 -2
- package/dist/shared/quota/capacity.js.map +1 -1
- package/dist/shared/quota/claudeOAuthQuotaSource.d.ts.map +1 -1
- package/dist/shared/quota/claudeOAuthQuotaSource.js +56 -0
- package/dist/shared/quota/claudeOAuthQuotaSource.js.map +1 -1
- package/dist/shared/quota/quotaSource.d.ts +62 -2
- package/dist/shared/quota/quotaSource.d.ts.map +1 -1
- package/dist/shared/quota/quotaSource.js +24 -0
- package/dist/shared/quota/quotaSource.js.map +1 -1
- package/dist/shared/quota/scheduler.d.ts +19 -2
- package/dist/shared/quota/scheduler.d.ts.map +1 -1
- package/dist/shared/quota/scheduler.js +162 -86
- package/dist/shared/quota/scheduler.js.map +1 -1
- package/dist/shared/quota/state.d.ts +22 -0
- package/dist/shared/quota/state.d.ts.map +1 -1
- package/dist/shared/quota/state.js +59 -0
- package/dist/shared/quota/state.js.map +1 -1
- package/dist/shared/quota/tokenBudgetView.d.ts +17 -0
- package/dist/shared/quota/tokenBudgetView.d.ts.map +1 -0
- package/dist/shared/quota/tokenBudgetView.js +77 -0
- package/dist/shared/quota/tokenBudgetView.js.map +1 -0
- package/dist/shared/quota/types.d.ts +19 -1
- package/dist/shared/quota/types.d.ts.map +1 -1
- package/dist/shared/quota/types.js +1 -2
- package/dist/shared/quota/types.js.map +1 -1
- package/dist/shared/repair/brokeredDispatch.d.ts.map +1 -1
- package/dist/shared/repair/brokeredDispatch.js +5 -3
- package/dist/shared/repair/brokeredDispatch.js.map +1 -1
- package/dist/shared/types/sessionConfig.d.ts +0 -3
- package/dist/shared/types/sessionConfig.d.ts.map +1 -1
- package/dist/shared/types/sessionConfig.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, statSync } 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";
|
|
@@ -246,6 +246,26 @@ function resolveInputPaths(root, input) {
|
|
|
246
246
|
* in the tool, never by asking the loader to remember to drop the flag (a needed
|
|
247
247
|
* manual flag is a bug signal).
|
|
248
248
|
*/
|
|
249
|
+
/**
|
|
250
|
+
* True when `candidatePath` (the best default-discovered input, e.g.
|
|
251
|
+
* `.audit-tools/audit-findings.json`) was modified more recently than
|
|
252
|
+
* `reportPath` (a leftover `remediation-report.md`). A freshly-regenerated
|
|
253
|
+
* audit doc postdating the last remediation report is a NEW remediation
|
|
254
|
+
* source, not evidence the old run is still "the" answer — used to stop
|
|
255
|
+
* `complete_redelivery` from silently re-presenting a stale report over it.
|
|
256
|
+
* Missing/unreadable files compare as "not fresher" (fail toward redelivering,
|
|
257
|
+
* the pre-existing behaviour) rather than throwing.
|
|
258
|
+
*/
|
|
259
|
+
function isDefaultCandidateFresherThanReport(candidatePath, reportPath) {
|
|
260
|
+
if (!candidatePath)
|
|
261
|
+
return false;
|
|
262
|
+
try {
|
|
263
|
+
return statSync(candidatePath).mtimeMs > statSync(reportPath).mtimeMs;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
249
269
|
function suppliedInputMatchesRun(inputResolution, manifest) {
|
|
250
270
|
if (!inputResolution.supplied)
|
|
251
271
|
return false;
|
|
@@ -517,11 +537,42 @@ export async function driveRollingDispatch(levels, options) {
|
|
|
517
537
|
});
|
|
518
538
|
dispatcher.enqueue(packets);
|
|
519
539
|
levelResults.push(...(await dispatcher.run()));
|
|
540
|
+
// Piece D: surface the wave's partial-completion terminal (quota_paused /
|
|
541
|
+
// empty_pool). Aggregate across waves keeping the EARLIEST-reset quota_paused
|
|
542
|
+
// terminal (a paused wave is retryable and should resume soonest); union the
|
|
543
|
+
// stranded ids so a later step re-dispatches all of them. An empty_pool
|
|
544
|
+
// terminal only stands when no quota_paused terminal was seen.
|
|
545
|
+
out.terminal = mergePartialTerminals(out.terminal, dispatcher.getTerminal());
|
|
520
546
|
}
|
|
521
547
|
out.levels.push({ blockIds: level.map((b) => b.block_id), results: levelResults });
|
|
522
548
|
}
|
|
523
549
|
return out;
|
|
524
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Fold a newly-observed partial-completion terminal into the run's aggregate
|
|
553
|
+
* (piece D). Prefers `quota_paused` (retryable) over `empty_pool`; among
|
|
554
|
+
* quota_paused terminals keeps the EARLIEST `earliest_reset_at`; always unions the
|
|
555
|
+
* stranded ids so no stranded node is lost across waves.
|
|
556
|
+
*/
|
|
557
|
+
function mergePartialTerminals(prior, next) {
|
|
558
|
+
if (!next)
|
|
559
|
+
return prior;
|
|
560
|
+
if (!prior)
|
|
561
|
+
return next;
|
|
562
|
+
const strandedIds = [...new Set([...prior.stranded_ids, ...next.stranded_ids])];
|
|
563
|
+
const priorPaused = prior.reason === "quota_paused";
|
|
564
|
+
const nextPaused = next.reason === "quota_paused";
|
|
565
|
+
if (priorPaused || nextPaused) {
|
|
566
|
+
const resets = [prior.earliest_reset_at, next.earliest_reset_at].filter((r) => typeof r === "string");
|
|
567
|
+
const earliest = resets.length > 0 ? resets.sort()[0] : undefined;
|
|
568
|
+
return {
|
|
569
|
+
reason: "quota_paused",
|
|
570
|
+
stranded_ids: strandedIds,
|
|
571
|
+
...(earliest ? { earliest_reset_at: earliest } : {}),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return { reason: prior.reason, stranded_ids: strandedIds };
|
|
575
|
+
}
|
|
525
576
|
/**
|
|
526
577
|
* Backends the orchestrator can drive IN-PROCESS as the per-node implement worker
|
|
527
578
|
* via `driveRollingImplementDispatch` (it resolves + launches the provider with each
|
|
@@ -734,7 +785,7 @@ export async function driveRollingImplementDispatch(options) {
|
|
|
734
785
|
// the merge-time write-scope gate reads — for file-ownership-disjoint admission
|
|
735
786
|
// (INV-SOO). A block with no plan scope falls back to its touched_files.
|
|
736
787
|
const writePathsByBlock = new Map(allBlockScopes.map((s) => [s.block_id, s.write_paths]));
|
|
737
|
-
await driveRollingDispatch(levels, {
|
|
788
|
+
const driven = await driveRollingDispatch(levels, {
|
|
738
789
|
confirmedPools,
|
|
739
790
|
sessionConfig: options.sessionConfig ?? {},
|
|
740
791
|
dispatchNode: dispatchNodeWithWorktree,
|
|
@@ -744,6 +795,19 @@ export async function driveRollingImplementDispatch(options) {
|
|
|
744
795
|
hostSession,
|
|
745
796
|
scopeForBlock: (block) => writePathsByBlock.get(block.block_id) ?? block.touched_files ?? [],
|
|
746
797
|
});
|
|
798
|
+
// Piece D — persist the rolling engine's partial-completion terminal onto state
|
|
799
|
+
// BEFORE the merge, so the merge can SKIP-block the quota_paused stranded nodes
|
|
800
|
+
// (their worker rate-limited → no result file, but they must stay PENDING for a
|
|
801
|
+
// later step to redispatch clean — the worktrees were PRESERVED by
|
|
802
|
+
// acceptNodeWorktree). An `empty_pool` terminal does not affect the merge (its
|
|
803
|
+
// nodes are genuine failures); the `partial_terminal` obligation blocks them.
|
|
804
|
+
if (driven.terminal) {
|
|
805
|
+
const pre = await new StateStore(artifactsDir).loadState();
|
|
806
|
+
if (pre) {
|
|
807
|
+
pre.partial_completion_terminal = driven.terminal;
|
|
808
|
+
await new StateStore(artifactsDir).saveState(pre);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
747
811
|
// Deterministic merge: same path the wave flow uses (tolerant remap, write-scope
|
|
748
812
|
// gate against each verified branch, lost-update detection). Worktrees are
|
|
749
813
|
// already removed; the merge reads each node's result file + branch diff.
|
|
@@ -1306,6 +1370,8 @@ Then run:
|
|
|
1306
1370
|
|
|
1307
1371
|
${renderQuotaCoverageNudge(rolling.quotaPath, artifactsDir)}
|
|
1308
1372
|
|
|
1373
|
+
${renderTokenBudgetView(rolling.quotaPath)}
|
|
1374
|
+
|
|
1309
1375
|
${DISPATCH_PROMPT_HANDOFF_NOTE}
|
|
1310
1376
|
`,
|
|
1311
1377
|
allowedCommands: [acceptCmd, rollMerge, rollNext],
|
|
@@ -1402,6 +1468,8 @@ ${SHARED_REBUILD_BETWEEN_LEVELS_NOTE}
|
|
|
1402
1468
|
|
|
1403
1469
|
${renderQuotaCoverageNudge(implQuotaPath, artifactsDir)}
|
|
1404
1470
|
|
|
1471
|
+
${renderTokenBudgetView(implQuotaPath)}
|
|
1472
|
+
|
|
1405
1473
|
${DISPATCH_PROMPT_HANDOFF_NOTE}
|
|
1406
1474
|
|
|
1407
1475
|
After all results exist:
|
|
@@ -1417,6 +1485,41 @@ Then run:
|
|
|
1417
1485
|
artifactPaths: { dispatch_plan: planPath, dispatch_quota: implQuotaPath },
|
|
1418
1486
|
}) };
|
|
1419
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Piece D — the quota-paused resumable step. The rolling engine stranded one or
|
|
1490
|
+
* more nodes because every eligible provider pool hit a host session limit and is
|
|
1491
|
+
* paused until its stated reset. The stranded nodes stay PENDING (their worktrees
|
|
1492
|
+
* were preserved), so re-running next-step at/after `resetAt` redispatches them
|
|
1493
|
+
* clean. Emitted (not blocked) so the run is resumable, never a failure.
|
|
1494
|
+
*/
|
|
1495
|
+
async function buildQuotaPausedStep(params) {
|
|
1496
|
+
const { root, artifactsDir, runId, strandedIds, resetAt } = params;
|
|
1497
|
+
const nextCommand = loaderCommand("next-step");
|
|
1498
|
+
const resetLine = resetAt
|
|
1499
|
+
? `The earliest provider reset is \`${resetAt}\`. Wait until then, then run:`
|
|
1500
|
+
: `Wait for the provider session limit to reset, then run:`;
|
|
1501
|
+
return writeCurrentStep({
|
|
1502
|
+
stepKind: "quota_paused",
|
|
1503
|
+
status: "ready",
|
|
1504
|
+
runId,
|
|
1505
|
+
repoRoot: root,
|
|
1506
|
+
artifactsDir,
|
|
1507
|
+
prompt: `
|
|
1508
|
+
# Remediation paused — provider session limit
|
|
1509
|
+
|
|
1510
|
+
Every eligible provider pool hit a host session limit and is paused until its
|
|
1511
|
+
stated reset. ${strandedIds.length} node(s) are stranded and remain PENDING —
|
|
1512
|
+
their worktrees were preserved, so they will redispatch clean on resume. Nothing
|
|
1513
|
+
was blocked or failed; this is a resumable pause.
|
|
1514
|
+
|
|
1515
|
+
${resetLine}
|
|
1516
|
+
|
|
1517
|
+
\`${nextCommand}\`
|
|
1518
|
+
`,
|
|
1519
|
+
allowedCommands: [nextCommand],
|
|
1520
|
+
stopCondition: "Stop and wait for the provider reset. Re-running next-step resumes the stranded nodes.",
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1420
1523
|
// --- Per-state handlers -----------------------------------------------------
|
|
1421
1524
|
// Each handler owns one branch of the original decideNextStepInner dispatch.
|
|
1422
1525
|
// Handlers that emit a step return RemediationStep directly; handlers that need
|
|
@@ -3156,9 +3259,17 @@ function buildPreIntakeObligations(ctx, snapshot) {
|
|
|
3156
3259
|
// built) — an active run, not a finished one. A fully-green close deletes
|
|
3157
3260
|
// the whole artifact dir (close.ts), so the summary + checkpoint can only
|
|
3158
3261
|
// co-exist for a live run; never re-deliver the leftover root report over it.
|
|
3262
|
+
//
|
|
3263
|
+
// A freshly-regenerated default-discovered audit doc (audit-findings.json /
|
|
3264
|
+
// audit-report.md newer than the leftover report) is the same "don't
|
|
3265
|
+
// redeliver" signal — a fresh audit run just landed and a bare next-step
|
|
3266
|
+
// must fall through to pending_intake (which re-presents the discovered
|
|
3267
|
+
// file for confirmation via confirm_auto_discovered_input, mtime + type +
|
|
3268
|
+
// finding count included) rather than silently re-showing the stale report.
|
|
3159
3269
|
const freshIntent = existsSync(ip.conversationStart) ||
|
|
3160
3270
|
existsSync(ip.extractedPlan) ||
|
|
3161
|
-
(existsSync(ip.summary) && existingCheckpoint?.confirmed_by === "host")
|
|
3271
|
+
(existsSync(ip.summary) && existingCheckpoint?.confirmed_by === "host") ||
|
|
3272
|
+
isDefaultCandidateFresherThanReport(inputResolution.existing[0], reportPath);
|
|
3162
3273
|
return freshIntent ? "satisfied" : "missing";
|
|
3163
3274
|
},
|
|
3164
3275
|
execute: async (state, c) => ({
|
|
@@ -3288,6 +3399,28 @@ function buildMainObligations(ctx) {
|
|
|
3288
3399
|
const terminal = s.partial_completion_terminal;
|
|
3289
3400
|
if (!terminal)
|
|
3290
3401
|
return { kind: "transition", state: s };
|
|
3402
|
+
// Piece D — quota_paused is a RETRYABLE pause, NOT a failure. The stranded
|
|
3403
|
+
// nodes stay PENDING (their worktrees were preserved), nothing is blocked,
|
|
3404
|
+
// and the close transition is NOT forced. EMIT a paused step (terminating
|
|
3405
|
+
// this advance loop) that tells the host to re-run next-step at/after the
|
|
3406
|
+
// stated reset, when the pool's session limit has cleared and the stranded
|
|
3407
|
+
// nodes redispatch clean. Clearing the terminal here (before the emit) so
|
|
3408
|
+
// the resuming step starts fresh; the pending nodes are the durable signal.
|
|
3409
|
+
if (terminal.reason === "quota_paused") {
|
|
3410
|
+
const resetAt = terminal.earliest_reset_at ?? null;
|
|
3411
|
+
delete s.partial_completion_terminal;
|
|
3412
|
+
await store.saveState(s);
|
|
3413
|
+
return {
|
|
3414
|
+
kind: "emit",
|
|
3415
|
+
step: await buildQuotaPausedStep({
|
|
3416
|
+
root,
|
|
3417
|
+
artifactsDir,
|
|
3418
|
+
runId: stateRunId(s),
|
|
3419
|
+
strandedIds: terminal.stranded_ids ?? [],
|
|
3420
|
+
resetAt,
|
|
3421
|
+
}),
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3291
3424
|
const strandedSet = new Set(terminal.stranded_ids ?? []);
|
|
3292
3425
|
for (const it of Object.values(s.items ?? {})) {
|
|
3293
3426
|
if (isTerminalStatus(it.status))
|