#165

Record truncated revalidations as incomplete runs

Opengespositowants to merge intofrom

What changed

revalidate now completes as phase: "error" when a quota abort left batches unstarted, and persists four coverage stats (findingsRequested, findingsUnresolved, batchesTotal, batchesCompleted) alongside the existing verdict counts.

Why

A truncated run was recorded as phase: "done" with a success-shaped stats block. The truncation reached emitProgress and the return value, but nothing on disk — so once the process was detached from its terminal the run record was indistinguishable from a full sweep.

We hit this on a 532-finding revalidation: it recorded phase: done, findingsRevalidated: 326, while on disk 0 of 2 CRITICAL and 4 of 17 HIGH findings had verdicts. deepsec status reported a completed run, and there was no way to learn otherwise.

The abandoned work was invisible in the return value too. totalRequested accumulates per batch, so the denominator truncates with the run — two planned batches with an abort after the first reports requested: 1, not 2. And batches never started are not appended to totalUnresolved either (only the batch that threw is), so that work appears in no output at all.

Phase flips only on abandoned batches, not on failures: a run that attempted its whole scope but had a batch fail stays "done" and reports the loss via findingsUnresolved. Keying on batch coverage rather than on quotaExhausted also covers the params.signal abort path, which truncates identically.

Reusing "error" avoids widening RunMeta["phase"]; lock reclaim treats exactly "done" | "error" as finished, so a third value would strand the run's files for STALE_LOCK_MS.

Verification

  • pnpm test passes — 2411 passed, 1 skipped, 63 files
  • pnpm lint passes
  • pnpm knip passes
  • pnpm -r build passes
  • pnpm test:bundle passes (RunMeta is on the deepsec/config surface)
  • pnpm typecheck:deepsec passes
  • N/A — no matcher added

Notes for reviewer

The new test asserts the persisted run meta. The existing coverage at process-revalidate.test.ts:929 asserts result.quotaExhausted — the in-memory return — which is why this went unnoticed.

process() has the identical shape (abort at :781, bail at :803/:811, unconditional completeRun(…, "done", …) at :833) and wants the same treatment. Left out to keep this single-purpose; happy to follow up or fold it in, whichever you prefer.

The four stats fields are optional and additive, so existing run records parse unchanged.

Activity

2
vercel[bot]2mo ago

@gesposito is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.