audit-tools 0.32.8 → 0.32.9

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 (99) hide show
  1. package/dist/audit/cli/dispatch/quotaPool.d.ts +20 -2
  2. package/dist/audit/cli/dispatch/quotaPool.d.ts.map +1 -1
  3. package/dist/audit/cli/dispatch/quotaPool.js +38 -7
  4. package/dist/audit/cli/dispatch/quotaPool.js.map +1 -1
  5. package/dist/audit/cli/dispatch/tierRouting.d.ts +4 -1
  6. package/dist/audit/cli/dispatch/tierRouting.d.ts.map +1 -1
  7. package/dist/audit/cli/dispatch/tierRouting.js +7 -4
  8. package/dist/audit/cli/dispatch/tierRouting.js.map +1 -1
  9. package/dist/audit/cli/dispatch/types.d.ts +9 -4
  10. package/dist/audit/cli/dispatch/types.d.ts.map +1 -1
  11. package/dist/audit/cli/dispatch.d.ts +7 -0
  12. package/dist/audit/cli/dispatch.d.ts.map +1 -1
  13. package/dist/audit/cli/dispatch.js +24 -12
  14. package/dist/audit/cli/dispatch.js.map +1 -1
  15. package/dist/audit/cli/mergeAndIngestCommand.d.ts +0 -9
  16. package/dist/audit/cli/mergeAndIngestCommand.d.ts.map +1 -1
  17. package/dist/audit/cli/mergeAndIngestCommand.js +29 -1
  18. package/dist/audit/cli/mergeAndIngestCommand.js.map +1 -1
  19. package/dist/audit/cli/prompts.d.ts.map +1 -1
  20. package/dist/audit/cli/prompts.js +4 -2
  21. package/dist/audit/cli/prompts.js.map +1 -1
  22. package/dist/audit/cli/rollingAuditDispatch.d.ts.map +1 -1
  23. package/dist/audit/cli/rollingAuditDispatch.js +3 -0
  24. package/dist/audit/cli/rollingAuditDispatch.js.map +1 -1
  25. package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
  26. package/dist/audit/cli/semanticReviewStep.js +10 -6
  27. package/dist/audit/cli/semanticReviewStep.js.map +1 -1
  28. package/dist/audit/cli/steps.d.ts +20 -10
  29. package/dist/audit/cli/steps.d.ts.map +1 -1
  30. package/dist/audit/cli/steps.js +4 -2
  31. package/dist/audit/cli/steps.js.map +1 -1
  32. package/dist/audit/orchestrator/rollingDispatch.d.ts +7 -6
  33. package/dist/audit/orchestrator/rollingDispatch.d.ts.map +1 -1
  34. package/dist/audit/orchestrator/rollingDispatch.js +37 -19
  35. package/dist/audit/orchestrator/rollingDispatch.js.map +1 -1
  36. package/dist/audit/quota/index.d.ts +137 -8
  37. package/dist/audit/quota/index.d.ts.map +1 -1
  38. package/dist/audit/quota/index.js +9 -8
  39. package/dist/audit/quota/index.js.map +1 -1
  40. package/dist/remediate/dispatch/ownershipRegistry.d.ts +2 -22
  41. package/dist/remediate/dispatch/ownershipRegistry.d.ts.map +1 -1
  42. package/dist/remediate/dispatch/ownershipRegistry.js +7 -45
  43. package/dist/remediate/dispatch/ownershipRegistry.js.map +1 -1
  44. package/dist/remediate/steps/dispatch.d.ts +22 -1
  45. package/dist/remediate/steps/dispatch.d.ts.map +1 -1
  46. package/dist/remediate/steps/dispatch.js +89 -5
  47. package/dist/remediate/steps/dispatch.js.map +1 -1
  48. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  49. package/dist/remediate/steps/nextStep.js +89 -116
  50. package/dist/remediate/steps/nextStep.js.map +1 -1
  51. package/dist/remediate/steps/rollingSession.d.ts +15 -17
  52. package/dist/remediate/steps/rollingSession.d.ts.map +1 -1
  53. package/dist/remediate/steps/rollingSession.js +33 -57
  54. package/dist/remediate/steps/rollingSession.js.map +1 -1
  55. package/dist/remediate/steps/types.d.ts +12 -4
  56. package/dist/remediate/steps/types.d.ts.map +1 -1
  57. package/dist/remediate/steps/types.js +1 -1
  58. package/dist/shared/dispatch/admissionLoop.d.ts +245 -0
  59. package/dist/shared/dispatch/admissionLoop.d.ts.map +1 -0
  60. package/dist/shared/dispatch/admissionLoop.js +204 -0
  61. package/dist/shared/dispatch/admissionLoop.js.map +1 -0
  62. package/dist/{remediate → shared}/dispatch/ownershipScheduler.d.ts +23 -2
  63. package/dist/shared/dispatch/ownershipScheduler.d.ts.map +1 -0
  64. package/dist/{remediate → shared}/dispatch/ownershipScheduler.js +34 -6
  65. package/dist/shared/dispatch/ownershipScheduler.js.map +1 -0
  66. package/dist/shared/dispatch/pathIdentity.d.ts +26 -0
  67. package/dist/shared/dispatch/pathIdentity.d.ts.map +1 -0
  68. package/dist/shared/dispatch/pathIdentity.js +49 -0
  69. package/dist/shared/dispatch/pathIdentity.js.map +1 -0
  70. package/dist/shared/dispatch/rollingDispatch.d.ts +37 -0
  71. package/dist/shared/dispatch/rollingDispatch.d.ts.map +1 -1
  72. package/dist/shared/dispatch/rollingDispatch.js +117 -18
  73. package/dist/shared/dispatch/rollingDispatch.js.map +1 -1
  74. package/dist/shared/dispatch/unifiedRolling.d.ts +131 -0
  75. package/dist/shared/dispatch/unifiedRolling.d.ts.map +1 -0
  76. package/dist/shared/dispatch/unifiedRolling.js +161 -0
  77. package/dist/shared/dispatch/unifiedRolling.js.map +1 -0
  78. package/dist/shared/index.d.ts +12 -1
  79. package/dist/shared/index.d.ts.map +1 -1
  80. package/dist/shared/index.js +18 -1
  81. package/dist/shared/index.js.map +1 -1
  82. package/dist/shared/quota/packetCost.d.ts +29 -0
  83. package/dist/shared/quota/packetCost.d.ts.map +1 -0
  84. package/dist/shared/quota/packetCost.js +51 -0
  85. package/dist/shared/quota/packetCost.js.map +1 -0
  86. package/dist/shared/quota/reservationLedger.d.ts +100 -0
  87. package/dist/shared/quota/reservationLedger.d.ts.map +1 -0
  88. package/dist/shared/quota/reservationLedger.js +224 -0
  89. package/dist/shared/quota/reservationLedger.js.map +1 -0
  90. package/dist/shared/quota/state.d.ts +20 -0
  91. package/dist/shared/quota/state.d.ts.map +1 -1
  92. package/dist/shared/quota/state.js +50 -0
  93. package/dist/shared/quota/state.js.map +1 -1
  94. package/dist/shared/quota/types.d.ts +11 -0
  95. package/dist/shared/quota/types.d.ts.map +1 -1
  96. package/dist/shared/quota/types.js.map +1 -1
  97. package/package.json +1 -1
  98. package/dist/remediate/dispatch/ownershipScheduler.d.ts.map +0 -1
  99. package/dist/remediate/dispatch/ownershipScheduler.js.map +0 -1
@@ -9,6 +9,7 @@ import { verifyPairingForFinding } from "../contractPipeline/changeClassificatio
9
9
  import { spawnSync } from "node:child_process";
10
10
  import { StateStore } from "../state/store.js";
11
11
  import { REMEDIATION_STEP, isClarificationCategory, } from "../state/types.js";
12
+ import { computeDispatchAdmission, createReservationLedger, tierRank } from "audit-tools/shared";
12
13
  import { withFileLock } from "audit-tools/shared";
13
14
  import { captureStepBoundaryFriction } from "audit-tools/shared";
14
15
  import { findingLead, renderFindingBadgeBody } from "audit-tools/shared";
@@ -264,7 +265,39 @@ export async function buildConfirmedPools(input) {
264
265
  primaryPools.push(...sourcePools);
265
266
  return primaryPools;
266
267
  }
267
- export function buildDispatchQuota(runId, phase, schedule, quotaStateEntry) {
268
+ /**
269
+ * Build the `AdmissionPool[]` the admission loop admits against from a schedule's
270
+ * per-pool capacity summaries — the remediate analog of audit's `finalizeDispatchQuota`
271
+ * pool mapping (both feed the single-sourced `computeDispatchAdmission`, so the two
272
+ * orchestrators can't drift). Budget = the pool's live remaining token budget
273
+ * (null ⇒ optimistic +Inf); declared cap = its host in-flight cap passed VERBATIM
274
+ * (null ⇒ none — the only place an explicit agent-count exists); cost/capability rank
275
+ * from its tier; capacity = its context window (a packet's input+output envelope must fit).
276
+ */
277
+ function admissionPoolsFromSchedule(schedule) {
278
+ return (schedule.capacity_pools ?? []).map((pool) => {
279
+ const rankNum = tierRank(pool.rank);
280
+ return {
281
+ poolId: pool.pool_id,
282
+ resourceKey: pool.pool_id,
283
+ budget: pool.remaining_token_budget ?? Number.POSITIVE_INFINITY,
284
+ declaredCap: pool.host_concurrency_limit?.active_subagents ?? null,
285
+ costRank: rankNum,
286
+ capabilityRank: rankNum,
287
+ capacityTokens: pool.resolved_limits.context_tokens,
288
+ };
289
+ });
290
+ }
291
+ export async function buildDispatchQuota(runId, phase, schedule, admissionPackets,
292
+ /**
293
+ * Whether to LEASE the granted set against the shared reservation ledger. The
294
+ * host-subagent path passes `true` (the host dispatches the grant across processes,
295
+ * so the tool reserves-before-dispatch and reconciles at accept-node). The
296
+ * in-process rolling engine passes `false`: it admits + leases per-packet itself, so
297
+ * a host-grant lease here would double-count the same work. Mirrors audit's
298
+ * `finalizeDispatchQuota` grantLeases parameterization.
299
+ */
300
+ grantLeases, quotaStateEntry) {
268
301
  let backoffState = null;
269
302
  const count = quotaStateEntry?.consecutive_429_count ?? 0;
270
303
  if (count > 0) {
@@ -274,12 +307,23 @@ export function buildDispatchQuota(runId, phase, schedule, quotaStateEntry) {
274
307
  current_failure_weight: computeBackoffFailureWeight(count),
275
308
  };
276
309
  }
310
+ // Admission control: instead of a computed `max_concurrent_agents`, GRANT the
311
+ // affordable admitted set (cost-first-capable, ledger-leased). Per-packet reservation
312
+ // = input estimate + output envelope (declared output cap; the learned ratio refines
313
+ // it once a provider reports usage — dormant on the always-on claude-code host).
314
+ const admission = await computeDispatchAdmission({
315
+ packets: admissionPackets,
316
+ pools: admissionPoolsFromSchedule(schedule),
317
+ outputCap: schedule.resolved_limits.output_tokens,
318
+ grantLeases,
319
+ ledger: createReservationLedger(),
320
+ });
277
321
  return {
278
322
  contract_version: REMEDIATION_DISPATCH_QUOTA_CONTRACT_VERSION,
279
323
  run_id: runId,
280
324
  phase,
281
325
  host_concurrency_limit: schedule.host_concurrency_limit,
282
- max_concurrent_agents: schedule.max_concurrent,
326
+ admission,
283
327
  estimated_wave_tokens: schedule.estimated_wave_tokens,
284
328
  model: schedule.model,
285
329
  confidence: schedule.confidence,
@@ -2332,6 +2376,7 @@ export async function prepareImplementDispatch(options, runId, onlyBlockId, wave
2332
2376
  items,
2333
2377
  };
2334
2378
  await writeJsonFile(dispatchPlanPath(options.artifactsDir, runId, "implement"), plan);
2379
+ const estimatedSlotTokens = itemReadFileLists.map((files) => estimateImplementSlotTokens(files, options.root));
2335
2380
  const schedule = await scheduleWave({
2336
2381
  hostMaxConcurrent: waveOptions?.hostMaxConcurrent,
2337
2382
  sessionConfig: waveOptions?.sessionConfig ?? null,
@@ -2340,11 +2385,20 @@ export async function prepareImplementDispatch(options, runId, onlyBlockId, wave
2340
2385
  hostModels: waveOptions?.hostModels,
2341
2386
  hostModelId: waveOptions?.hostModelId,
2342
2387
  itemCount: items.length,
2343
- estimatedSlotTokens: itemReadFileLists.map((files) => estimateImplementSlotTokens(files, options.root)),
2388
+ estimatedSlotTokens,
2344
2389
  });
2345
- process.stderr.write(`[remediate-code] dispatch: implement max_concurrent=${schedule.max_concurrent} of ${items.length} item(s) ` +
2390
+ // Admission packets in plan order: id = the node's block id (what
2391
+ // `admission.granted_packet_ids` references and the host matches to nodes),
2392
+ // inputTokens = its estimated slot cost, complexity = the remediate default 0.5.
2393
+ // Keyed by block_id (same filter the frontier builder uses), so a granted id always
2394
+ // resolves to a frontier node.
2395
+ const admissionPackets = items
2396
+ .map((item, i) => ({ item, inputTokens: estimatedSlotTokens[i] ?? 0 }))
2397
+ .filter((p) => typeof p.item.block_id === "string")
2398
+ .map((p) => ({ id: p.item.block_id, inputTokens: p.inputTokens, complexity: 0.5 }));
2399
+ process.stderr.write(`[remediate-code] dispatch: implement ${items.length} item(s) ` +
2346
2400
  `source=${schedule.source} cap=${schedule.binding_cap ?? "none"}\n`);
2347
- const quota = buildDispatchQuota(runId, "implement", schedule);
2401
+ const quota = await buildDispatchQuota(runId, "implement", schedule, admissionPackets, waveOptions?.grantLeases ?? true);
2348
2402
  await writeJsonFile(join(dir, "dispatch-quota.json"), quota);
2349
2403
  return plan;
2350
2404
  }
@@ -2870,7 +2924,37 @@ function obligationIdsForFinding(state, findingId) {
2870
2924
  ...(finding.verification_obligation_ids ?? []),
2871
2925
  ];
2872
2926
  }
2927
+ /**
2928
+ * Reconcile (free) the reservation-ledger leases the dispatch grant took for this
2929
+ * run's granted set — the "reconcile at result-ingest" half of admission control
2930
+ * (spec/audit/dispatch-admission-control.md), the remediate analog of audit's
2931
+ * `reconcileAdmissionLeases`. The host has now reported the granted set's results, so
2932
+ * those reservations are no longer in flight and their budget returns to the shared
2933
+ * account for the NEXT grant. Best-effort + token-checked (a missing/already-freed
2934
+ * lease is a no-op), so a lost reconcile self-heals via the lease TTL and never blocks
2935
+ * the merge. Only the host-subagent grant persists leases (`grantLeases: true`); the
2936
+ * in-process path leases per-packet in the engine and reconciles there.
2937
+ */
2938
+ async function reconcileAdmissionLeases(artifactsDir, runId) {
2939
+ const quotaPath = join(runDir(artifactsDir, runId, "implement"), "dispatch-quota.json");
2940
+ const quota = await readOptionalJsonFile(quotaPath);
2941
+ const leases = quota?.admission?.leases;
2942
+ if (!leases || leases.length === 0)
2943
+ return;
2944
+ const ledger = createReservationLedger();
2945
+ for (const lease of leases) {
2946
+ try {
2947
+ await ledger.reconcile(lease.resource_key, lease.lease_id);
2948
+ }
2949
+ catch {
2950
+ // Best-effort: the lease TTL reclaims budget if a reconcile is lost.
2951
+ }
2952
+ }
2953
+ }
2873
2954
  export async function mergeImplementResults(options, runId) {
2955
+ // Free the grant's reservation-ledger leases now that the host has reported the
2956
+ // granted set's results — returns the reserved budget for the next grant.
2957
+ await reconcileAdmissionLeases(options.artifactsDir, runId);
2874
2958
  const plan = await readJsonFile(dispatchPlanPath(options.artifactsDir, runId, "implement"));
2875
2959
  if (plan.contract_version !== REMEDIATION_DISPATCH_PLAN_CONTRACT_VERSION ||
2876
2960
  plan.phase !== "implement") {