Skip to content

fix(worker): make scan.summary reflect all persisted findings, not just the engine's own count - #307

Merged
ecryptoguru merged 1 commit into
mainfrom
fix/scan-summary-finding-count
Aug 14, 2026
Merged

fix(worker): make scan.summary reflect all persisted findings, not just the engine's own count#307
ecryptoguru merged 1 commit into
mainfrom
fix/scan-summary-finding-count

Conversation

@ecryptoguru

@ecryptoguru ecryptoguru commented Aug 14, 2026

Copy link
Copy Markdown
Owner

DRAFT — root-caused while doing the marketing screenshot work in #306. Not blocking that PR (already merged); this is the underlying product bug it surfaced.

The bug

A real Trust Runs screenshot showed:

Engine status: completed. 0 finding(s) reported.          [badge] 39 findings

Both numbers were individually correct for what they measured — which is exactly the problem: nothing in the copy explains why they disagree, so it just looks broken.

Root cause

Two independent pipelines compute "how many findings" for the same scan:

  • The frozen summary text (output-parser.ts:824, parseEngineOutput) is built from vulnerabilities.length — parsed only from the raw agentic engine's vulnerabilities.json artifact.
  • The badge (scan-service.ts:347, _count.findings) is a live count of Finding rows actually persisted, which come from orchestratorResult.allFindings — the combined set across the engine, SCA, secrets, agent-config, and URL scanners, after false-positive filtering and dedup (scanner-orchestrator.ts:56-67).

run-scan.job.ts:1269 persisted the engine-only text to scan.summary after the combined set was already persisted, even though the correct total (persistedFindings.length) was sitting in scope one line above the findings_persisted event that already uses it.

Why this is bigger than a UI badge

scan.summary is read verbatim in four places, not one:

  • Trust Runs list + scan detail page (apps/web)
  • The private assurance report and shared report view — an exec/client-facing report's "Summary" row would say "0 finding(s) reported" while listing 39 findings
  • Scan-completion notifications — the body embeds the wrong summary right next to a title that already correctly says "39 findings" (notifyScanCompleted's 4th arg is persistedFindings.length, already correct) — so the same message contradicts itself

The fix

Scoped entirely to run-scan.job.ts. output-parser.ts is untouched — parseEngineOutput is a correctly-named pure parser of the engine's own artifact, and changing its meaning would be a bigger, less obviously correct change.

Built scanSummary once, right after persistedFindings is known:

  • If persistedFindings.length === engineResult.output.findingCount (the common case — nothing else contributed, nothing filtered) — pass the engine's text through unchanged.
  • If they disagree — append the real count: "Engine status: completed. 0 finding(s) reported. 39 finding(s) retained after all scanner layers and deduplication."

All four downstream writes (prisma.scan.update, the write completeScanWithScore performs, notifyScanCompleted's body, and the job's return value) now use this one corrected value instead of four separate references to the engine-only string.

No scoring risk: completeScanWithScore's summary parameter is display-text only (score-service.ts:244) — it never touches computeScore, the breakdown, or the ScoreSnapshot itself, all of which query the live Finding table independently. This does not touch the public-scorecard allowlist or the score model.

Tests

Traced every existing assertion in run-scan.job.test.ts by hand: the file's default mocks always have persistFindings resolving to the same count as the mocked findingCount (usually both 0), so the corrective branch never fires for any existing test — zero existing expectations changed. Added two new tests:

  1. Reproduces the exact bug shape (engine says 0, three scanner-layer findings persist) and asserts the corrected text at all four call sites.
  2. Guardrails the passthrough: when the counts already agree, the summary must not grow a second sentence.

Verification

Sandbox has no node_modules and npm returns 403, so vitest/tsc/eslint could not run locally. I traced every call site of engineResult.output.summary, every consumer of scan.summary across the repo, and every existing test assertion in the affected file by hand instead. CI on this PR is the execution gate — will report back once it's green.

Summary by CodeRabbit

  • Bug Fixes
    • Scan completion summaries now reflect the total retained findings when they differ from the engine-reported count.
    • Updated summaries are consistently used for scan results, completion scoring, and notifications.
    • Existing summaries remain unchanged when finding counts match.

…st the engine's own count

Root-caused from a Trust Runs screenshot found during marketing work: a run
showed "Engine status: completed. 0 finding(s) reported." beside a badge
reading "39 findings" for the same run. Both numbers were individually
correct, and both are user-facing, which made the run look broken.

engineResult.output.summary (parseEngineOutput, output-parser.ts) is built
purely from the agentic engine's own vulnerabilities.json artifact. It never
accounts for the SCA, secrets, agent-config, or URL scanner findings the
orchestrator merges in afterward, nor the false-positive filtering and dedup
that follow. run-scan.job.ts persisted that engine-only text to scan.summary
even after persisting the full merged set — so on any run where the engine
layer alone found nothing, the frozen summary said "0" next to a persisted
count that could be dozens.

scan.summary is not just a dashboard badge. It is read verbatim by:
- The Trust Runs list and scan detail page (apps/web).
- The private assurance report and shared report view (report-generator.ts,
  report-service.ts) — the html "Summary" row would say "0 finding(s)
  reported" on a report attached to 39 findings.
- Scan-completion notifications (notifications.ts) — the body text embeds the
  wrong summary right next to a title that already correctly says "39
  findings" from persistedFindings.length, so the same message contradicts
  itself.

Fix, scoped to the write sites in run-scan.job.ts (not to parseEngineOutput,
which is a correctly-named pure parser of the engine's own artifact and stays
untouched): build scanSummary once, right after persistedFindings is known.
When persistedFindings.length already matches the engine-only findingCount
(the common case — no other scanner contributed, nothing was filtered), pass
the engine's text through unchanged. When they disagree, append the real
persisted count. Every downstream write (prisma.scan.update, the DB write
completeScanWithScore also performs, notifyScanCompleted's body, and the job's
own return value) now uses this single corrected value instead of four
separate references to the engine-only string.

completeScanWithScore's summary parameter only ever becomes display text on
scan.summary (packages/db/src/score-service.ts:244) — it does not feed
computeScore, the breakdown, or the ScoreSnapshot itself, all of which query
the live Finding table independently. So this fix carries no risk to the
public-scorecard allowlist or the score model.

Verified against every existing assertion in run-scan.job.test.ts: the default
mock fixtures in this file always have persistFindings resolving to the same
count as the mocked engine findingCount (usually both 0), so the corrective
branch never fires for them and no existing expectation changes. Added two
targeted tests: one reproducing the exact bug shape (engine says 0, three
scanner-layer findings persist, summary must state both), and one guardrail
confirming the passthrough is untouched when the counts already agree.

Sandbox note: no node_modules, npm returns 403, so vitest/tsc/eslint could not
run locally. Traced every call site, every consumer of scan.summary, and every
existing test assertion in this file by hand; CI is the execution gate.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 03fb8d95-433d-4940-a632-4bc3c028c3d7

📥 Commits

Reviewing files that changed from the base of the PR and between 7a271c1 and 1c03018.

📒 Files selected for processing (2)
  • apps/worker/src/jobs/run-scan.job.test.ts
  • apps/worker/src/jobs/run-scan.job.ts

📝 Walkthrough

Walkthrough

The scan job now reconciles engine summaries with retained finding counts. It persists, scores, notifies, and returns the consolidated summary. Tests cover differing counts and unchanged matching counts.

Changes

Scan summary reconciliation

Layer / File(s) Summary
Consolidate and propagate scan summaries
apps/worker/src/jobs/run-scan.job.ts
The job creates scanSummary when retained findings differ from the engine count. It uses this summary for persistence, completion scoring, notifications, and the returned result.
Validate summary reconciliation
apps/worker/src/jobs/run-scan.job.test.ts
Tests cover retained findings from SCA, secrets, and agent-configuration scanners. A guardrail test confirms unchanged summaries when counts match.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/scan-summary-finding-count

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ecryptoguru
ecryptoguru marked this pull request as ready for review August 14, 2026 20:16
@ecryptoguru
ecryptoguru merged commit 75c1ef9 into main Aug 14, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant