Precision 1.0, recall 1.0, and the artifact was still wrong
A pipeline scored a perfect parity match and failed the artifact anyway. Here is why, and why a confidence score should be arithmetic a person can re-run.
A pipeline scored a perfect parity match and failed the artifact anyway. Here is why, and why a confidence score should be arithmetic a person can re-run.
Here is an evaluation result. Precision 1.0, recall 1.0, accuracy 1.0, F1 1.0. Both independent oracles agreed with the artifact on every case that was tested.
The artifact was wrong, and the pipeline failed it.
That gap is the point. Every reported number was perfect and the correct decision was still FAIL. A lot of AI evaluation breaks quietly in that gap. Closing it comes down to a plain question: who is allowed to express confidence, and on what basis.
The artifact computes daily interest accrual, simple interest on a 30/360 convention. It is a short, single-function TypeScript file from a legacy modernization job. The documented rule is plain: interest is principal times annual rate times days over 360.
The numeric evaluation was clean. A golden dataset and a separately maintained rule-engine implementation were both compared against the artifact. Zero discrepancies across the five sampled cases. Precision, recall, accuracy and F1 all came out at 1.0. If the report stopped there, the artifact ships.
It did not ship. A later step read the code’s logic on its own, before looking at the documented rule, and reported what the code actually does:
Computes simple interest as principal × annual_rate × (min(days_elapsed, 45) / 360) … The day-count factor is silently capped at 45 regardless of the true elapsed days. No error, warning, or flag is raised when the cap engages.
There is no cap in the documented rule. The artifact understates interest on any accrual period longer than 45 days, and it tells no one it did.
The numbers looked perfect for a simple reason. None of the five golden-dataset cases ran past 45 days. The match was real and it proved nothing, because it only measured the region where the bug never fires. The report says this outright: a 100% parity match is never sufficient evidence on its own.
This is the same failure as a 0.9998 ROC AUC on a detector that catches nothing. A real number, measuring the wrong thing, presented as if it settled the question.
What caught this is a rule about where confidence is allowed to come from. Every skill in the pipeline hands off its result as a typed JSON message, and each message carries a confidence value. The value has a source, and the source is one of two types.
Calibrated confidence is a number derived by formula from measured evidence, such as how much of the input an oracle covers or how complete the sample is. It is arithmetic over things that actually happened.
Self-reported confidence is a model’s own certainty about its own reading. One skill produces it: the semantic check, where a model reads the code and says how sure it is of what the code does.
The rule that connects them is the part that matters. Self-reported confidence can lower a final score. It can never raise one.
A model is not allowed to talk its way into being trusted. It can raise an alarm, and the alarm can pull a score down. It cannot say it is sure the code is fine and have that lift anything. Certainty about your own output is not evidence. It is the thing under evaluation.
In this run the semantic read reported 0.8 confidence, high because the file was short and the reading was not ambiguous. But that 0.8 was confidence in the reading, that the code really does cap at 45. It only ever flowed toward flagging the divergence, never toward reassurance.
The final confidence score is six weighted components. On this run they summed to 80 out of 100, which on its own is a Medium and eligible to proceed.
It came out Low, and the status was FAIL.
The reason is a hard override. A short list of conditions cap the score no matter what the weighted total says, and this run hit one of them. It is called coincidental_match_risk: the semantic check found that the clean parity result only covered cases where the bug cannot appear. Once that flag is set, the 80 does not matter and the 100% match does not matter. The score is capped at Low, and a person has to look.
That override is the design in miniature. Every number feeding the score can be reproduced by hand, either by re-running the Cypher query or by re-reading the JSON message the skill emitted. None of it is a model’s opinion dressed up as a metric. The single place a model’s judgment enters, the semantic read, can only ever subtract.
The constraint costs something. The pipeline is not an ML classifier and does not try to be. It will not spot subtler patterns than its checks encode, and it cannot approve anything by itself above the lowest risk tier. In exchange, every FAIL comes with a reason a person can audit, and no PASS rests on a machine’s say-so.
The output is not a decision. It is evidence for a named human who owns the decision. The report says as much. The artifact is not eligible for cutover, and the open question goes to a domain expert: is the 45-day cap intended business behavior that belongs in the rule, or a defect to remove. The pipeline does not answer that.
This is the honest limit of automated evaluation on work that matters. The pipeline found the divergence the numbers hid, and it refused to let a clean score outweigh a real defect. What it produced was a specific, re-runnable case for a person to judge, not a verdict. Sign-off stays with the human, by design.
The principle underneath is plain. A confidence score is worth only as much as your ability to say where the confidence came from. If any of it came from the thing being judged, that part counts against it, never for it.
ParityKit, the framework this run came from, is open source at github.com/MAY2704/parity_check. The run above is a synthetic demo built to exercise the pipeline end to end. It is not a real migration finding.