<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dylan Merigaud</title>
    <description>The latest articles on DEV Community by Dylan Merigaud (@dylanmerigaud).</description>
    <link>https://dev.to/dylanmerigaud</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1187075%2F724be4e5-1241-485b-859d-9472a2f0e89c.jpeg</url>
      <title>DEV Community: Dylan Merigaud</title>
      <link>https://dev.to/dylanmerigaud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dylanmerigaud"/>
    <language>en</language>
    <item>
      <title>A rulebook of how money code breaks, and an agent that refutes its own findings</title>
      <dc:creator>Dylan Merigaud</dc:creator>
      <pubDate>Sun, 12 Jul 2026 20:10:39 +0000</pubDate>
      <link>https://dev.to/dylanmerigaud/a-rulebook-of-how-money-code-breaks-and-an-agent-that-refutes-its-own-findings-c1l</link>
      <guid>https://dev.to/dylanmerigaud/a-rulebook-of-how-money-code-breaks-and-an-agent-that-refutes-its-own-findings-c1l</guid>
      <description>&lt;h2&gt;
  
  
  Money bugs pass review
&lt;/h2&gt;

&lt;p&gt;Money bugs are rare per repository and expensive when they happen. Rounding that loses a&lt;br&gt;
cent per invoice, a webhook that captures a payment twice on retry, a cached balance that&lt;br&gt;
quietly drifts from the ledger. These do not look like bugs in review. The code compiles,&lt;br&gt;
the tests pass, and the tests pass precisely because they use round numbers and one&lt;br&gt;
currency, which is itself one of the failure modes.&lt;/p&gt;

&lt;p&gt;Most "AI code review" aimed at this problem is a single prompt that pattern-matches on&lt;br&gt;
&lt;code&gt;float&lt;/code&gt; and calls it a day. That catches the easy cases and misses the part that actually&lt;br&gt;
costs money, which is semantic and spread across files: an allocation that does not sum to&lt;br&gt;
its total, a counter updated without a lock, a tax total rounded at the wrong level.&lt;/p&gt;
&lt;h2&gt;
  
  
  Put the value in a rulebook, not a prompt
&lt;/h2&gt;

&lt;p&gt;fintech-roast is built the other way around. The product is a rulebook of 41 rules across&lt;br&gt;
10 domains: storage and types, rounding and allocation, idempotency and concurrency, ledger&lt;br&gt;
design, FX, time and dates, aggregation, taxes, API serialization, and testing. Each rule&lt;br&gt;
has per-language detection and fixes for TypeScript, Python, and Java, at least two sources&lt;br&gt;
you can check (language specs, ISO standards, tax-authority manuals, engineering write-ups&lt;br&gt;
from practitioners), and its own false-positive notes documenting where it cries wolf.&lt;/p&gt;

&lt;p&gt;A rule is a claim you can check, not a regex. So a human can read the reasoning and decide&lt;br&gt;
instead of trusting the tool. The same rule keeps working when the executor is a cheaper&lt;br&gt;
model next year. And when a rule is wrong, you can argue with the specific claim.&lt;/p&gt;

&lt;p&gt;An example, abbreviated. Rule ROU-2, pro-rata allocation that loses or creates cents:&lt;br&gt;
detect a total split by looping over shares and rounding each independently with no&lt;br&gt;
reconciliation of the residual. Why it breaks: the rounded parts do not sum back to the&lt;br&gt;
total, so a 100.00 split three ways becomes 33.33 + 33.33 + 33.33 = 99.99 and a cent&lt;br&gt;
vanishes. The fix: allocate with an explicit remainder pass (largest-remainder, or push the&lt;br&gt;
residual to the last bucket). False positives: a high-precision internal allocation that&lt;br&gt;
carries the residual forward and only rounds once at the end is fine, do not flag it.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the agent applies it
&lt;/h2&gt;

&lt;p&gt;The agent is a Claude Code plugin, read-only, running on your own session. It scans the repo&lt;br&gt;
for where money lives, then fans out one auditor subagent per domain, each pointed at that&lt;br&gt;
domain's rule file and the candidate code. Then a second agent runs whose only job is to&lt;br&gt;
refute each finding: is this display-only, a rate rather than an amount, dead code, already&lt;br&gt;
guarded a layer up, a misreading? Refuted findings are dropped before you ever see them. The&lt;br&gt;
survivors come back with a severity, a confidence tier from the verifier, the offending&lt;br&gt;
code, a fix direction, and the rule citation.&lt;/p&gt;
&lt;h2&gt;
  
  
  What happened on real code
&lt;/h2&gt;

&lt;p&gt;The eval fixtures in the repo are bugs I planted, which is useful for measuring recall but&lt;br&gt;
proves nothing about false positives on code that is mostly correct. So I ran it on real&lt;br&gt;
codebases.&lt;/p&gt;

&lt;p&gt;On Medusa, the open-source commerce platform, I pointed it at the money-core files across&lt;br&gt;
six domains, at a pinned commit. The auditors emitted 16 findings; the verifier refuted 10,&lt;br&gt;
downgraded 2 to narrower claims, and confirmed 4. The confirmed 4 are one concurrency&lt;br&gt;
cluster. Simplified, the payment capture path does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// read how much has already been captured&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;capturedAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* sum */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// guard: refuse to capture more than authorized minus already-captured&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newCaptureAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;authorizedAmount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;capturedAmount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// insert the capture&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;captureService_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The read, the guard, and the write are not serialized. Under the default READ COMMITTED&lt;br&gt;
isolation, two concurrent captures of a 100 authorization both read captured = 0, both see&lt;br&gt;
100 remaining, both pass the guard, and both insert. Result: 200 captured against a 100&lt;br&gt;
authorization. There is no row lock, no version check, and no unique constraint to catch it.&lt;br&gt;
The same shape appears in refunds and in two promotion-budget counters. The gateway does not&lt;br&gt;
save you: the bundled Stripe provider swallows the already-captured error, so both capture&lt;br&gt;
rows persist, and each refund row carries its own idempotency key, so stripe.refunds.create&lt;br&gt;
moves real money twice. I filed the cluster upstream with a failing test that drops straight&lt;br&gt;
into Medusa's own concurrency test block; it is filed, not yet triaged, so judge it by the&lt;br&gt;
test: &lt;a href="https://github.com/medusajs/medusa/issues/16012" rel="noopener noreferrer"&gt;https://github.com/medusajs/medusa/issues/16012&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The refutations are the interesting part
&lt;/h2&gt;

&lt;p&gt;The verifier killed 10 of the 16 Medusa findings, and that is the number I would judge the&lt;br&gt;
tool on. A tool that dumps raw findings costs you an afternoon per false positive and loses&lt;br&gt;
your trust on the first wrong one.&lt;/p&gt;

&lt;p&gt;One example. An auditor flagged that Medusa stores tax rates in a single-precision &lt;code&gt;REAL&lt;/code&gt;&lt;br&gt;
column, and by the letter of the rule that is a hit. The verifier worked the whole chain&lt;br&gt;
mechanically: a float32 stored rate, read back through PostgreSQL 12+'s&lt;br&gt;
shortest-round-trip text output, parsed by node-postgres, then converted to the exact&lt;br&gt;
decimal. Every rate tested (0.21, 8.25, 9.975, 8.0625, 13.9125) round-trips exactly;&lt;br&gt;
corruption would need a rate with seven or more significant digits. The finding died on the&lt;br&gt;
rule's own test: does an inexact binary value ever actually reach the money math? The&lt;br&gt;
answer was no. That refutation used a false-positive note I had added to the rulebook the&lt;br&gt;
same day, which is the feedback loop working.&lt;/p&gt;

&lt;p&gt;All ten kills are published with their mechanisms in the field report, so a wrong kill is&lt;br&gt;
itself checkable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;86% recall on the cold scan is a best case, not proof: I planted the bugs and wrote the
answer key, the fixture only holds bug classes I already knew to write rules for, and
the scan still missed 5 of the 35. The misses are in the repo.&lt;/li&gt;
&lt;li&gt;On that fixture the verifier refuted nothing (0 of 53): every file is dense with planted
bugs, so the fixture says nothing about false-positive suppression. The kill rates on
real code are the evidence: 10 of 16 on Medusa, 14 of 36 on the private repo.&lt;/li&gt;
&lt;li&gt;The verifier is adversarial, not human. "Confirmed" means the finding survived an attack
by a second model, not that a person proved it.&lt;/li&gt;
&lt;li&gt;One of my two field writeups is on a private, anonymized codebase you cannot reproduce.
The Medusa one you can, and its issue is filed upstream, not yet accepted.&lt;/li&gt;
&lt;li&gt;No Go or Ruby yet.&lt;/li&gt;
&lt;li&gt;A whole-repo run costs real tokens on your own account (the two runs in field report 1
cost roughly 2.2M subagent tokens total). Diff mode is the cheap day-to-day run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, or argue with a rule
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add DylanMerigaud/fintech-roast
/plugin install fintech-roast@fintech-roast
/fintech-roast:roast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules are claims about how money code breaks. If one is wrong, overstated, or missing a&lt;br&gt;
jurisdiction nuance, that is the most valuable contribution you can make. The rulebook, the&lt;br&gt;
evals with their misses, and both field reports are all in the repo:&lt;br&gt;
&lt;a href="https://github.com/DylanMerigaud/fintech-roast" rel="noopener noreferrer"&gt;https://github.com/DylanMerigaud/fintech-roast&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fintech</category>
      <category>codereview</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
