Skip to content

fix(analyzer): coerce string-quoted numbers in analysis schema (KSM loss) - #71

Open
r3y3r53 wants to merge 1 commit into
mainfrom
fix/analyzer-numericstring-coercion
Open

r3y3r53 wants to merge 1 commit into
mainfrom
fix/analyzer-numericstring-coercion

Conversation

@r3y3r53

@r3y3r53 r3y3r53 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

Analyzer runs intermittently complete with a captured flag but parseFailed: true and no KSM score. In the Sep/Oct 2026 AI-lab benchmarking sweeps this affected real results — e.g. GLM-5.3-Flash on indirect-prompt-injection and Kimi-K3 on confused-deputy-email-agent show Flag captured: KX{...} in the run logs while oasis/results/<runId>.analysis.json contains "parseFailed": true with "expected number, invalid_type".

Root cause

AnalysisResponseSchema (src/lib/schemas.ts) validated every score field with strict z.number() / z.boolean(). Analyzer LLMs (DeepSeek-V3 in particular) intermittently serialize scores as JSON strings — "decisionQuality": "85" instead of 85. One string-typed score rejects the entire analysis object, discarding the whole structured analysis (kill chain, narrative, milestones, strategy) even though the analyzer's semantic content was fine.

Fix

Coerce the analysis-response scalar fields to accept string-typed numerics/booleans:

  • behavior.decisionQuality -> z.coerce.number()
  • strategy.reconQuality / exploitEfficiency / adaptability / overallScore -> z.coerce.number()
  • rubricEvaluation.milestones[].achieved -> z.coerce.boolean()
  • rubricEvaluation.qualitative.*.score -> z.coerce.number()

Plain numbers pass through unchanged; genuinely malformed values (non-numeric text) still fail validation.

Verification

  • npm run typecheck clean
  • npm run build clean
  • npm run test: 419 passed (16 files) incl. 3 new regression tests in tests/schemas-coercion.test.ts

Impact

Removes the largest source of lost KSM scores observed across ~150 benchmark runs (flag captured, score missing).

LLM analyzers intermittently emit numeric scores as JSON strings
("85" instead of 85). AnalysisResponseSchema used strict z.number(),
so a single string-typed score rejected the entire analysis with
invalid_type => parseFailed: true and the KSM score was discarded
even though the run legitimately captured the flag.

Observed in the Sep/Oct 2026 benchmarking sweeps with DeepSeek-V3 as
analyzer: multiple flag-captured runs (e.g. GLM-5.3-Flash on
indirect-prompt-injection, Kimi-K3 on confused-deputy-email-agent)
were recorded without a KSM solely because of this strictness -
results/*.analysis.json show parseFailed: true with
"expected number, invalid_type" on a numeric field.

Switch the analysis-response score fields (behavior.decisionQuality,
strategy.*, rubricEvaluation.qualitative.*.score, milestones[].achieved)
to z.coerce.number()/z.coerce.boolean() so string-typed numerics are
accepted, while real type errors (non-numeric text) still fail.
Plain numbers pass through unchanged.

Verified against the stored failing analyses: this would have salvaged
the parseFailed runs without re-running them.
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