<?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: Ankita Patel</title>
    <description>The latest articles on DEV Community by Ankita Patel (@anp0429).</description>
    <link>https://dev.to/anp0429</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%2F4051738%2F292abc50-d751-4c7c-b2af-340311e564bb.png</url>
      <title>DEV Community: Ankita Patel</title>
      <link>https://dev.to/anp0429</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anp0429"/>
    <language>en</language>
    <item>
      <title>The schema defined 2 foreign key pairs. The API reported 4.</title>
      <dc:creator>Ankita Patel</dc:creator>
      <pubDate>Mon, 03 Aug 2026 19:38:26 +0000</pubDate>
      <link>https://dev.to/anp0429/the-schema-defined-2-foreign-key-pairs-the-api-reported-4-15c4</link>
      <guid>https://dev.to/anp0429/the-schema-defined-2-foreign-key-pairs-the-api-reported-4-15c4</guid>
      <description>&lt;p&gt;I hit this in supabase/mcp a few weeks back, it's merged now (&lt;a href="https://github.com/supabase/mcp/pull/317" rel="noopener noreferrer"&gt;PR #317&lt;/a&gt;), and the shape of the bug is worth writing down because it's exactly the kind of thing AI agents consume without blinking.&lt;/p&gt;

&lt;p&gt;The setup: the MCP server has a &lt;code&gt;list_tables&lt;/code&gt; tool. Verbose mode includes foreign key relationships, so an agent asking "how are these tables connected" reads that output as ground truth.&lt;/p&gt;

&lt;p&gt;For composite foreign keys it was wrong. The SQL joined source and target columns without pairing them positionally, so a 2-column FK came back as 4 pairings and a 3-column FK as 9. Cartesian product. N columns, N squared relationships, and all but N of them don't exist in the schema. Nothing errors. The output looks completely plausible. A human might squint at it. An agent just uses it, and now it's joining on a relationship that was never there.&lt;/p&gt;

&lt;p&gt;I filed it as a question, not a verdict, because I figured I might be missing context: is this intended behavior? The maintainer confirmed it was a bug and suggested going one better while we're in there: group each constraint's columns into ordered arrays, so the pairing is structural instead of implied by row adjacency.&lt;/p&gt;

&lt;p&gt;The part I actually want to talk about is proving the fix.&lt;/p&gt;

&lt;p&gt;The obvious fix is "order the columns." Ordered by what, though. Alphabetical order works on the happy path. Physical column order also works on the happy path. Both are wrong. The pairing has to follow the constraint's own declared column positions, so the fix uses &lt;code&gt;unnest ... WITH ORDINALITY&lt;/code&gt;, and I added a test where the FK's declared order differs from both the alphabetical and the physical order. That test is the invariant. Without it, the bug can come back wearing a green suite.&lt;/p&gt;

&lt;p&gt;Once the grouped shape was pushed, I wanted to sanity-check it beyond my own tests before it merged. So I ran an automated review over the branch: an LLM proposes edge-case tests from the intent and the diff, then a deterministic gate executes every one against the real code. No model anywhere in the pass or fail. It proposed 9 cases past what I'd written. Two independent composite FKs between the same table pair staying separate. Self-referential composite FKs. Cross-schema FKs visible from either side. Single-column FKs correctly emitted as one-element arrays. Repeated calls returning identical output. All 9 executed green, 0 gaps found. I posted the list &lt;a href="https://github.com/supabase/mcp/pull/317" rel="noopener noreferrer"&gt;on the PR&lt;/a&gt; and offered to commit them.&lt;/p&gt;

&lt;p&gt;The maintainer's reply: commit them all, and this merges. So they went in, plus a couple more scenarios that came up in review, and the final suite landed 110/110.&lt;/p&gt;

&lt;p&gt;Two things I took from this.&lt;/p&gt;

&lt;p&gt;One, the dangerous bugs in agent-facing APIs aren't crashes. They're confidently wrong data. A crash gets caught by the first retry loop. N squared foreign keys gets built on.&lt;/p&gt;

&lt;p&gt;Two, "my tests pass" and "the behavior is pinned" are different claims. The nine executed cases didn't find a gap, and that's fine, that's what they were for: turning "I think the fix is right" into "here is what was executed against it and held." What got merged was the evidence, not my confidence.&lt;/p&gt;

&lt;p&gt;The gate I used is something I've been building in the open. It's called edgeverdict, it's on PyPI, and there's a no-key demo that runs in about half a minute. If you point it at your own repo, tell me where it lies to you. That's genuinely the most useful thing you can do for it.&lt;/p&gt;

&lt;p&gt;The gate: &lt;a href="https://github.com/anp0429/edgeverdict" rel="noopener noreferrer"&gt;github.com/anp0429/edgeverdict&lt;/a&gt;, or &lt;code&gt;pip install edgeverdict&lt;/code&gt; for the no-key demo.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>postgres</category>
      <category>testing</category>
    </item>
    <item>
      <title>I built a lie detector for code. Then it caught itself lying.</title>
      <dc:creator>Ankita Patel</dc:creator>
      <pubDate>Wed, 29 Jul 2026 22:54:26 +0000</pubDate>
      <link>https://dev.to/anp0429/i-built-a-lie-detector-for-code-then-it-caught-itself-lying-1gn1</link>
      <guid>https://dev.to/anp0429/i-built-a-lie-detector-for-code-then-it-caught-itself-lying-1gn1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every AI code reviewer has the same tell: it states a wrong finding in the exact confident tone it uses for a right one. Nothing in the delivery tells you which is which. Confidence isn't evidence.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/anp0429/edgeverdict" rel="noopener noreferrer"&gt;edgeverdict&lt;/a&gt; around one rule: the model never gets to decide. It proposes a test, a deterministic gate runs it, and a finding only counts if the test &lt;em&gt;executes and fails&lt;/em&gt;. The model guesses. Execution rules.&lt;/p&gt;

&lt;p&gt;A rule like that is only worth anything if it survives being pointed at itself. So before merging a big refactor, I let the tool review its own diff. Here's what it posted, on my own PR:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9j4v3d53n5msjpfll06x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9j4v3d53n5msjpfll06x.png" alt="edgeverdict's automated PR comment: a confirmed failing test, flagged as advisory for human judgment" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The actual comment it posted on my own PR (the tool was named agentboard back then; it's edgeverdict now).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Read the last part closely. The tool found a real failing test &lt;strong&gt;and&lt;/strong&gt; flagged its own finding as probably not a real bug, in the same breath.&lt;/p&gt;

&lt;p&gt;That split is the whole point. The execution half said &lt;em&gt;"this test fails, here's the proof."&lt;/em&gt; The advisory half said &lt;em&gt;"but a human should decide if this is actually wrong."&lt;/em&gt; One is a fact. The other is a judgment. It never lets them blur into each other, because blurring them is exactly the confident-liar problem I built the thing to kill.&lt;/p&gt;

&lt;p&gt;But the sharpest catch came later, and it was worse, in the way that teaches you something.&lt;/p&gt;

&lt;p&gt;There's a function in the tool whose whole job is to decide &lt;em&gt;why&lt;/em&gt; a test failed, was this a real assertion failure, or just a crash? That call is load-bearing. Everything downstream trusts it. And here's how I'd written it: it checked whether the string &lt;code&gt;AssertionError&lt;/code&gt; appeared &lt;em&gt;anywhere&lt;/em&gt; in the failure report.&lt;/p&gt;

&lt;p&gt;Anywhere.&lt;/p&gt;

&lt;p&gt;So a test that crashed for some unrelated reason, whose output merely &lt;em&gt;mentioned&lt;/em&gt; that word somewhere in the noise, would get filed as a genuine bug in your code. A false finding, minted from a coincidence of text.&lt;/p&gt;

&lt;p&gt;Sit with where that lived. I'd built a tool whose entire thesis is &lt;em&gt;don't trust what merely looks true, make it prove itself&lt;/em&gt;, and buried in its verdict path, the one place the whole thesis rests on, was a line that trusted what merely looked true. My lie detector had a spot where it would believe a coincidence.&lt;/p&gt;

&lt;p&gt;I didn't catch it in review. I didn't catch it in testing. The tool caught it, by trying to break its own code. The fix classifies by the exception actually raised, not by grepping for a scary word, and the replacement now documents the bug it killed, right there in the source.&lt;/p&gt;

&lt;p&gt;Across a few rounds of reviewing itself, the tool found &lt;strong&gt;31 real defects in its own codebase&lt;/strong&gt;, each an executed, failing test, not an opinion. Seven were the serious kind that could produce a false verdict. I published all of them, with the run fingerprints, so nobody has to take my word for it.&lt;/p&gt;

&lt;p&gt;Which is the point. The tool exists so you don't have to trust a builder's confidence, only the execution. So I won't end by asking you to trust mine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;edgeverdict
edgeverdict demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About fifteen seconds, no API key. Then point it at your own code and tell me where it lies to you.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/anp0429/edgeverdict" rel="noopener noreferrer"&gt;github.com/anp0429/edgeverdict&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: edgeverdict is mine and I wrote this up myself, with an AI assistant helping tighten the draft. Every number, fingerprint, and test here is real and in the repo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>ai</category>
      <category>pr</category>
    </item>
  </channel>
</rss>
