<?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: hyuga</title>
    <description>The latest articles on DEV Community by hyuga (@hyuga611).</description>
    <link>https://dev.to/hyuga611</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%2F4069679%2F58392e9f-7cbb-4566-8fed-b1de5f5d09f3.png</url>
      <title>DEV Community: hyuga</title>
      <link>https://dev.to/hyuga611</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hyuga611"/>
    <language>en</language>
    <item>
      <title>The card said one column. The apply wrote two.</title>
      <dc:creator>hyuga</dc:creator>
      <pubDate>Mon, 10 Aug 2026 09:56:46 +0000</pubDate>
      <link>https://dev.to/hyuga611/the-card-said-one-column-the-apply-wrote-two-2pa0</link>
      <guid>https://dev.to/hyuga611/the-card-said-one-column-the-apply-wrote-two-2pa0</guid>
      <description>&lt;p&gt;I have been building a thing that lets a language model propose an &lt;code&gt;UPDATE&lt;/code&gt;, then&lt;br&gt;
executes it for real inside a transaction, measures the actual before and after&lt;br&gt;
values, and always rolls back. A human reads the measurement and decides. Only&lt;br&gt;
then does anything commit.&lt;/p&gt;

&lt;p&gt;The pitch is one sentence: &lt;strong&gt;what you approve is not the model's description of&lt;br&gt;
its SQL, it is what the database did when the SQL ran.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Last week I found that the thing showing you that measurement was showing you a&lt;br&gt;
subset of it, and had been since the first release.&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure
&lt;/h2&gt;

&lt;p&gt;Real output, from &lt;code&gt;@hyuga/llm-safe-sql@0.4.0&lt;/code&gt; installed from npm. One row:&lt;br&gt;
&lt;code&gt;name = 'Tanaka'&lt;/code&gt;, &lt;code&gt;postcode = '00100'&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  UPDATE customers SET name='Sato', postcode='00100' WHERE id=1

What this touches
  customers — Customer records. The postcode is used for billing address and delivery.
  1 row would change, across 1 column: name

Measured by running the statement and rolling it back
  id = 1
      name: 'Tanaka' -&amp;gt; 'Sato'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One row, one column. &lt;code&gt;postcode&lt;/code&gt; is not mentioned, and that is &lt;em&gt;correct&lt;/em&gt; — it is&lt;br&gt;
being assigned the value it already holds, so nothing about it changes. The card&lt;br&gt;
is describing the diff accurately.&lt;/p&gt;

&lt;p&gt;Approve it. Then, before it is applied, somebody else notices the postcode is&lt;br&gt;
wrong and fixes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;postcode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'90210'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now apply the approved plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Applied: UPDATE on customers, 1 row(s), at 2026-08-10T09:49:12.049Z.
DB now: [{"name":"Sato","postcode":"00100"}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is gone. Zero warnings. The word &lt;code&gt;postcode&lt;/code&gt; never appeared on the&lt;br&gt;
approval card, never appeared in the audit record, and never appeared in the&lt;br&gt;
comparison the tool makes before it commits.&lt;/p&gt;
&lt;h2&gt;
  
  
  One variable doing two jobs
&lt;/h2&gt;

&lt;p&gt;The diff was built like 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;same&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// drop what did not move&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;auto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// drop what the DB maintains itself&lt;/span&gt;
  &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a correct answer to "what should the card show". Showing &lt;code&gt;postcode:&lt;br&gt;
'00100' -&amp;gt; '00100'&lt;/code&gt; would be noise, and worse than noise — it would be the card&lt;br&gt;
claiming a change where there is none.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;code&gt;changed&lt;/code&gt; was &lt;em&gt;also&lt;/em&gt; the list the apply iterated when it&lt;br&gt;
re-checked that nothing had moved since approval:&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;same&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;live&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&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;ApplyRefused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROW_CHANGED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;postcode&lt;/code&gt; is not in &lt;code&gt;changed&lt;/code&gt;, so it is not checked. And the statement writes it&lt;br&gt;
on every execution. &lt;strong&gt;A column that is written and never verified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"The set of columns that change" and "the set of columns the statement writes"&lt;br&gt;
are different sets, and I had used one name for both. &lt;code&gt;SET x = &amp;lt;the value it&lt;br&gt;
already has&amp;gt;&lt;/code&gt; is not exotic SQL — it is zero-padded codes, defensive &lt;code&gt;status&lt;/code&gt;&lt;br&gt;
assignments, &lt;code&gt;SET updated_by = 'batch'&lt;/code&gt;. Every one of those is this shape.&lt;/p&gt;
&lt;h2&gt;
  
  
  MySQL was catching it by accident
&lt;/h2&gt;

&lt;p&gt;There is a second version of the same hole, one level up.&lt;/p&gt;

&lt;p&gt;When a &lt;code&gt;WHERE&lt;/code&gt; matches several rows and one of them already holds the target&lt;br&gt;
value, the card says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  1 row would change, across 1 column: status
  (1 more match the condition but are already correct.)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That row's &lt;code&gt;changed&lt;/code&gt; is empty, so the verification loop runs zero times — before&lt;br&gt;
the write and after it. Nothing about that row's contents was ever checked.&lt;/p&gt;

&lt;p&gt;MySQL was saved by an accident of its protocol. It reports "rows matched" and&lt;br&gt;
"rows changed" separately, so a plan that measured one changed row and an apply&lt;br&gt;
that changed two is a detectable disagreement. PostgreSQL and SQLite rewrite a&lt;br&gt;
row even when the new values equal the old ones, so those two numbers are the&lt;br&gt;
same and the comparison says nothing. The reconciliation code knew 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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;op&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowsChangedIsMeaningful&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowsChanged&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowsChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rowsChangedIsMeaningful&lt;/code&gt; is true on MySQL and false on the other two. &lt;strong&gt;A guard&lt;br&gt;
that worked on one of three supported engines was the only thing standing there.&lt;/strong&gt;&lt;br&gt;
Develop on MySQL, test on MySQL, and you never learn this.&lt;/p&gt;

&lt;p&gt;The fix was to stop conflating the two sets: &lt;code&gt;PlanRow&lt;/code&gt; now carries &lt;code&gt;covered&lt;/code&gt; —&lt;br&gt;
every column the statement assigns — snapshotted before and after even when the&lt;br&gt;
value does not move, included in the tamper digest, and checked at both ends of&lt;br&gt;
the apply. &lt;code&gt;changed&lt;/code&gt; still drives the display. Same sequence on 0.4.2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refused (ROW_CHANGED): Row id=1 no longer holds the value you approved:
`postcode` was '00100' when the plan was made and is '90210' now.
Nothing was applied — make a new plan against the current values.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The audit that did not open the file
&lt;/h2&gt;

&lt;p&gt;I found this because of how the previous audit failed.&lt;/p&gt;

&lt;p&gt;I had run an adversarial review over the codebase — several independent passes by&lt;br&gt;
dimension, each finding verified by separate sceptics whose job was to refute it.&lt;br&gt;
Twenty-one findings survived. Adapters, engine, parser, policy. It felt like a&lt;br&gt;
good day's work.&lt;/p&gt;

&lt;p&gt;Then I asked a different question: not "what did you find" but &lt;strong&gt;"what did this&lt;br&gt;
review structurally not look at?"&lt;/strong&gt; The first line of the answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;src/apply.ts&lt;/code&gt; (494 lines at the time) produced zero findings. That should be read as&lt;br&gt;
"nobody opened it", not as "it was clean".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;apply.ts&lt;/code&gt; is the only code in the library that writes to production. Dry runs&lt;br&gt;
always roll back. Approval only writes a record. Committing happens in exactly&lt;br&gt;
one place, and the review had not been there.&lt;/p&gt;

&lt;p&gt;Scoped to that one file, the same process returned &lt;strong&gt;twenty-three&lt;/strong&gt; findings —&lt;br&gt;
more than the first pass found in the whole rest of the codebase. Everything&lt;br&gt;
above came out of it.&lt;/p&gt;

&lt;p&gt;The number 21 had felt like progress. It was a record of where I had looked.&lt;/p&gt;
&lt;h2&gt;
  
  
  Then the examples found two more
&lt;/h2&gt;

&lt;p&gt;This week I wrote worked examples: the four database accounts the design assumes,&lt;br&gt;
with the exact grants, for MySQL and Postgres. I decided to run every line against&lt;br&gt;
a real server rather than write what I knew to be true.&lt;/p&gt;

&lt;p&gt;The server disagreed twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A database-wide grant cannot be narrowed.&lt;/strong&gt; I had written the obvious thing —&lt;br&gt;
grant DML on the whole schema, then take it back on the two tables that hold the&lt;br&gt;
approval records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 1147 (42000): There is no such grant defined for user 'llm_plan'
                    on host '%' on table 'llm_safe_sql_plans'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MySQL will not revoke a table-level subset of a database-level grant. So&lt;br&gt;
&lt;code&gt;GRANT ... ON shop.*&lt;/code&gt; hands the dry-run account write access to the table that&lt;br&gt;
records approvals, permanently. A dry run could forge its own approval. The&lt;br&gt;
examples name each table instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And &lt;code&gt;check&lt;/code&gt; did not check.&lt;/strong&gt; The tool has a command whose entire job is to say&lt;br&gt;
whether the environment will work. It verified four connections and never the two&lt;br&gt;
tables the whole approval record lives in. With them missing it reported every&lt;br&gt;
table as &lt;code&gt;ready&lt;/code&gt; and exited 0 — and the omission surfaced on the first &lt;code&gt;plan&lt;/code&gt;, as&lt;br&gt;
a driver error escaping as an unhandled rejection, after the dry run had already&lt;br&gt;
executed and rolled back.&lt;/p&gt;

&lt;p&gt;That is the likeliest mistake anyone makes on day one, and it was invisible to the&lt;br&gt;
command that exists to catch exactly that class of thing. Fixed in 0.4.2: it&lt;br&gt;
reports the missing table and exits non-zero, and a missing store table is a&lt;br&gt;
refusal on every path rather than a stack trace.&lt;/p&gt;

&lt;p&gt;There was a third, smaller one. &lt;code&gt;check&lt;/code&gt; now asks the catalogue whether the tables&lt;br&gt;
exist rather than issuing &lt;code&gt;SELECT 1 FROM audit WHERE 1 = 0&lt;/code&gt; — because the store&lt;br&gt;
account the examples recommend holds &lt;code&gt;INSERT&lt;/code&gt; on the audit table and nothing else,&lt;br&gt;
so the select probe reports the table missing &lt;em&gt;exactly when the credential is as&lt;br&gt;
narrow as it is supposed to be.&lt;/em&gt; Writing the recommended configuration is what&lt;br&gt;
made that visible.&lt;/p&gt;

&lt;p&gt;And then CI failed, because CI runs the README's own quick start verbatim, and my&lt;br&gt;
fix had made the documented order wrong. The instructions said &lt;code&gt;check&lt;/code&gt; then&lt;br&gt;
&lt;code&gt;migrate&lt;/code&gt;; &lt;code&gt;check&lt;/code&gt; now exits non-zero before &lt;code&gt;migrate&lt;/code&gt; has run. Something noticed&lt;br&gt;
before a reader did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually take from this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Count what you have not looked at.&lt;/strong&gt; A list of findings looks like evidence of&lt;br&gt;
thoroughness and is evidence of coverage. The file that should obviously have been&lt;br&gt;
at the top of a risk-ordered list did not appear on the list at all, and absence&lt;br&gt;
looked exactly like safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing the explanation is a test.&lt;/strong&gt; Twice now, the act of documenting this&lt;br&gt;
thing has found defects in it that reading the code did not. Not because prose is&lt;br&gt;
magic — because writing an example means running it, and running it means the&lt;br&gt;
server gets a vote. Two privilege lists that were obviously right were wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch for one name doing two jobs.&lt;/strong&gt; The bug here was not a missing check. It&lt;br&gt;
was a display set and a verification set sharing a variable, so narrowing the&lt;br&gt;
display for good reasons silently narrowed the check. If you have something that&lt;br&gt;
both shows a human what will happen and decides whether it may happen, those are&lt;br&gt;
two lists, and they should have two names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A guard that only works on one backend is not a guard.&lt;/strong&gt; It is a coincidence&lt;br&gt;
with good timing, and it will be silently absent on the day you switch.&lt;/p&gt;

&lt;p&gt;If you are building anything that measures before it asks a human to agree, the&lt;br&gt;
question I would now ask it is: &lt;em&gt;what is the interface not showing me, and how&lt;br&gt;
would I know?&lt;/em&gt; The answers worth having are specific — which columns does it&lt;br&gt;
compare, what does it do with a value too long to print, what does it do with two&lt;br&gt;
values that look the same and are not.&lt;/p&gt;

&lt;p&gt;None of the five layers people usually reach for would have caught this. Not a&lt;br&gt;
role without write privileges, not a proxy, not an approval dialog, not a stricter&lt;br&gt;
prompt. Every one of these was a legitimate, authorised &lt;code&gt;UPDATE&lt;/code&gt; on an allowlisted&lt;br&gt;
table by a credential entitled to run it. Nothing about &lt;em&gt;what was permitted&lt;/em&gt; was&lt;br&gt;
violated. What was wrong was what the human was told before they agreed.&lt;/p&gt;




&lt;p&gt;Code, and the full list of what changed in each version, at&lt;br&gt;
&lt;a href="https://github.com/hyuga611/llm-safe-sql" rel="noopener noreferrer"&gt;github.com/hyuga611/llm-safe-sql&lt;/a&gt;.&lt;br&gt;
The &lt;code&gt;examples/&lt;/code&gt; directory is the part I would read first — it is the only&lt;br&gt;
documentation I have written where every line was executed against a real server&lt;br&gt;
before it was committed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>security</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
