<?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: Mads Hansen</title>
    <description>The latest articles on DEV Community by Mads Hansen (@mads_hansen_27b33ebfee4c9).</description>
    <link>https://dev.to/mads_hansen_27b33ebfee4c9</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%2F3846701%2F6570ac8b-d5e5-413f-9198-dbbfaa431fc1.png</url>
      <title>DEV Community: Mads Hansen</title>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mads_hansen_27b33ebfee4c9"/>
    <language>en</language>
    <item>
      <title>Your pgvector benchmark is incomplete until you add real access filters</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sat, 22 Aug 2026 01:20:16 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-pgvector-benchmark-is-incomplete-until-you-add-real-access-filters-1475</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-pgvector-benchmark-is-incomplete-until-you-add-real-access-filters-1475</guid>
      <description>&lt;p&gt;A pgvector query can be fast and accurate on the full corpus, then lose relevant results when you add a tenant or business filter.&lt;/p&gt;

&lt;p&gt;That does not mean the authorization filter is wrong.&lt;/p&gt;

&lt;p&gt;It means retrieval quality must be measured inside the authorized subset.&lt;/p&gt;

&lt;p&gt;Build an exact filtered baseline and compare ANN results using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recall@k&lt;/li&gt;
&lt;li&gt;empty and underfilled result rate&lt;/li&gt;
&lt;li&gt;p95 latency&lt;/li&gt;
&lt;li&gt;rank movement&lt;/li&gt;
&lt;li&gt;exact-fallback rate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Report by filter selectivity. A global average hides tiny tenants and rare categories—the cases most likely to lose candidates.&lt;/p&gt;

&lt;p&gt;Pin the embedding model, dataset snapshot, distance metric, PostgreSQL/pgvector versions, index parameters, and query settings. Test rebuilds, revocations, concurrent writes, and model upgrades.&lt;/p&gt;

&lt;p&gt;For small eligible subsets, an exact distance scan may be both simpler and more accurate. For larger subsets, use ANN with a measured, bounded candidate budget.&lt;/p&gt;

&lt;p&gt;Access controls are not a retrieval tuning knob. Keep them fixed and make the search strategy prove it works behind them.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/pgvector-filtered-recall-tests-ai-database-search?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;pgvector filtered recall tests for AI database search&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>ai</category>
      <category>database</category>
      <category>vector</category>
    </item>
    <item>
      <title>Your PostgreSQL RLS test is meaningless if it runs as the table owner</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sat, 22 Aug 2026 01:20:15 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-postgresql-rls-test-is-meaningless-if-it-runs-as-the-table-owner-57id</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-postgresql-rls-test-is-meaningless-if-it-runs-as-the-table-owner-57id</guid>
      <description>&lt;p&gt;Your tenant policies exist. Your tests pass. Production still bypasses them.&lt;/p&gt;

&lt;p&gt;The usual cause: migrations, tests, and runtime all use the role that owns the tables.&lt;/p&gt;

&lt;p&gt;PostgreSQL table owners normally bypass RLS unless FORCE ROW LEVEL SECURITY applies.&lt;/p&gt;

&lt;p&gt;A safer acceptance test uses the exact serving role and proves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it owns no tenant tables&lt;/li&gt;
&lt;li&gt;it has no BYPASSRLS or superuser attribute&lt;/li&gt;
&lt;li&gt;tenant context comes from authenticated state&lt;/li&gt;
&lt;li&gt;context is transaction-scoped with SET LOCAL&lt;/li&gt;
&lt;li&gt;cross-tenant rows, joins, and aggregates fail closed&lt;/li&gt;
&lt;li&gt;rollback and pool reuse cannot leak the previous tenant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the same matrix as the serving role and the migration owner. The difference should be visible and deliberate.&lt;/p&gt;

&lt;p&gt;Catalog flags are configuration evidence. A negative query under the production identity is enforcement evidence.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/postgresql-rls-serving-role-acceptance-test-ai-agents?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;PostgreSQL RLS serving-role acceptance tests for AI agents&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>security</category>
      <category>database</category>
      <category>mcp</category>
    </item>
    <item>
      <title>An immutable audit log cannot prove a decision that was never written</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Fri, 21 Aug 2026 01:23:22 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/an-immutable-audit-log-cannot-prove-a-decision-that-was-never-written-28g9</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/an-immutable-audit-log-cannot-prove-a-decision-that-was-never-written-28g9</guid>
      <description>&lt;p&gt;An agent proposes an action from database evidence. A reviewer changes it and approves. The action succeeds.&lt;/p&gt;

&lt;p&gt;But the decision record is lost between chat, workflow state, and the destination system.&lt;/p&gt;

&lt;p&gt;A hash chain cannot detect a record that was never written.&lt;/p&gt;

&lt;p&gt;The safer order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;bind approval to the exact artefact digest&lt;/li&gt;
&lt;li&gt;durably admit the decision receipt&lt;/li&gt;
&lt;li&gt;issue a short-lived, single-use operation capability&lt;/li&gt;
&lt;li&gt;verify and consume it at dispatch&lt;/li&gt;
&lt;li&gt;reconcile the receipt, attempt, and observed external state&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test admitted/no action, action/no receipt, duplicate delivery, crashes around commit, changed artefacts, cross-tenant replay, expiry in queue, and indeterminate timeouts.&lt;/p&gt;

&lt;p&gt;Keep proposal, human decision, and execution as separate linked records. Approval is not execution. External state is not proof of approval.&lt;/p&gt;

&lt;p&gt;Receipt-before-action turns missing and replayed decisions into testable system states.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/ai-database-approval-receipt-before-action?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;AI database approvals: write the receipt before the action&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>security</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Two authenticated MCP gateways can still disagree about authorization</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Fri, 21 Aug 2026 01:23:11 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/two-authenticated-mcp-gateways-can-still-disagree-about-authorization-31i3</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/two-authenticated-mcp-gateways-can-still-disagree-about-authorization-31i3</guid>
      <description>&lt;p&gt;An internal IAM gateway and an external OAuth gateway expose the same database tool.&lt;/p&gt;

&lt;p&gt;Both authenticate correctly.&lt;/p&gt;

&lt;p&gt;One rejects a cross-tenant filter. The other accepts it.&lt;/p&gt;

&lt;p&gt;That is authorization drift.&lt;/p&gt;

&lt;p&gt;Use one versioned contract for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable capability IDs&lt;/li&gt;
&lt;li&gt;trusted tenant and environment scope&lt;/li&gt;
&lt;li&gt;allowed tool and argument shapes&lt;/li&gt;
&lt;li&gt;approved views and fields&lt;/li&gt;
&lt;li&gt;row, byte, and time limits&lt;/li&gt;
&lt;li&gt;redaction and approval requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then replay the same negative fixtures through every front door: cross-tenant IDs, stale scopes, resource substitution, aliases, oversized queries, revoked principals, queued requests during policy rollout, and prompt attempts to widen scope.&lt;/p&gt;

&lt;p&gt;Emit the contract version with every decision. Compare structured allow/deny evidence, not only status codes.&lt;/p&gt;

&lt;p&gt;Authentication tells you who arrived. A shared authorization contract keeps every entrance honest about what they may do.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-database-authorization-contract-drift-tests?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP database authorization contracts and drift tests&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>database</category>
      <category>oauth</category>
    </item>
    <item>
      <title>One hard question is a better database acceptance test than ten easy ones</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:20:48 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/one-hard-question-is-a-better-database-acceptance-test-than-ten-easy-ones-4lnm</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/one-hard-question-is-a-better-database-acceptance-test-than-ten-easy-ones-4lnm</guid>
      <description>&lt;p&gt;Do not approve a PostgreSQL-to-ChatGPT connection because ten easy questions returned plausible answers.&lt;/p&gt;

&lt;p&gt;Use one representative business question with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a known answer&lt;/li&gt;
&lt;li&gt;an explicit reporting cutoff&lt;/li&gt;
&lt;li&gt;a tenant boundary&lt;/li&gt;
&lt;li&gt;a join&lt;/li&gt;
&lt;li&gt;a versioned metric definition&lt;/li&gt;
&lt;li&gt;a meaningful stale, incomplete, or empty state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then require evidence for identity, PostgreSQL role, source/replica, freshness, coverage, normalized filters, query limits, truncation, retries, and provenance.&lt;/p&gt;

&lt;p&gt;Run the negative twin too: unauthorized tenant, missing partition, stale replica, unknown enum, and forced timeout. The final prose must preserve refusal, incomplete, stale, and unknown states instead of collapsing them into zero rows.&lt;/p&gt;

&lt;p&gt;One hard question tests the contract. Ten easy questions mostly test fluency.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/connect-postgresql-to-chatgpt-first-query-acceptance-test?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Connect PostgreSQL to ChatGPT: the first-query acceptance test&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>chatgpt</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>SELECT 1 is not a production MySQL connection test</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:20:47 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/select-1-is-not-a-production-mysql-connection-test-1hao</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/select-1-is-not-a-production-mysql-connection-test-1hao</guid>
      <description>&lt;p&gt;The connection test says “success.” ChatGPT still cannot see the right tables—or it can see far more than intended.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT 1&lt;/code&gt; proves reachability. A production preflight should prove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exact service identity and effective grants&lt;/li&gt;
&lt;li&gt;certificate and hostname verification&lt;/li&gt;
&lt;li&gt;visible schemas, tables, views, and columns&lt;/li&gt;
&lt;li&gt;time zone, SQL mode, character set, collation, and isolation&lt;/li&gt;
&lt;li&gt;read-only behavior and query limits&lt;/li&gt;
&lt;li&gt;pooled-session cleanup after errors and cancellation&lt;/li&gt;
&lt;li&gt;positive and negative query fixtures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test sensitive-table access, unbounded ranges, writes, DDL, expensive joins, and cross-tenant requests. The expected result is a structured refusal—not a plausible answer.&lt;/p&gt;

&lt;p&gt;The connection is ready only when identity, scope, session semantics, limits, and evidence all match the production contract.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/connect-mysql-to-chatgpt-connection-preflight?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Connect MySQL to ChatGPT with a production connection preflight&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>chatgpt</category>
      <category>database</category>
      <category>mcp</category>
    </item>
    <item>
      <title>A successful pg_dump is not a recovery test</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:24:01 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/a-successful-pgdump-is-not-a-recovery-test-3c14</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/a-successful-pgdump-is-not-a-recovery-test-3c14</guid>
      <description>&lt;p&gt;A nightly &lt;code&gt;pg_dump&lt;/code&gt; exits zero. That proves a backup command ran—not that the system is recoverable.&lt;/p&gt;

&lt;p&gt;A useful restore-verification contract defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RPO and RTO&lt;/li&gt;
&lt;li&gt;covered databases, roles, extensions, and keys&lt;/li&gt;
&lt;li&gt;an isolated clean restore&lt;/li&gt;
&lt;li&gt;schema, integrity, permissions, and representative query checks&lt;/li&gt;
&lt;li&gt;backup age and restore-verification age&lt;/li&gt;
&lt;li&gt;artifact checksum and recovery-point evidence&lt;/li&gt;
&lt;li&gt;ownership for failed drills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test corrupt archives, missing keys, version mismatch, full disks, expired credentials, and interrupted restores. Then verify application and MCP authorization with the same roles and approved operations used in production.&lt;/p&gt;

&lt;p&gt;A database that starts with the wrong grants or stale schema is not recovered.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/postgresql-ai-workload-restore-verification-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;PostgreSQL for AI workloads needs a restore-verification contract&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>devops</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Your RLS policy can work while the error still leaks data</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Wed, 19 Aug 2026 10:23:47 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-rls-policy-can-work-while-the-error-still-leaks-data-3207</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-rls-policy-can-work-while-the-error-still-leaks-data-3207</guid>
      <description>&lt;p&gt;A forbidden row never appears. Then the database says &lt;code&gt;duplicate key&lt;/code&gt; or &lt;code&gt;foreign key violation&lt;/code&gt;—and the caller learns that a hidden value exists.&lt;/p&gt;

&lt;p&gt;Errors are outputs. They can reveal tenant existence, schema details, identifiers, query structure, and infrastructure state.&lt;/p&gt;

&lt;p&gt;For database tools used by AI clients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep raw SQLSTATE, constraint, and traces in a restricted audit path&lt;/li&gt;
&lt;li&gt;return stable public error classes&lt;/li&gt;
&lt;li&gt;authorize before distinguishing “missing” from “exists elsewhere”&lt;/li&gt;
&lt;li&gt;prefer tenant-scoped uniqueness where semantics allow it&lt;/li&gt;
&lt;li&gt;normalize globally unique conflicts&lt;/li&gt;
&lt;li&gt;rate-limit repeated probes&lt;/li&gt;
&lt;li&gt;retry only explicitly transient classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test body, status, timing, and retry behavior—not just returned rows. A green RLS read test says nothing about constraint and error oracles.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-database-error-side-channel-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP database errors need a side-channel contract&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>security</category>
      <category>mcp</category>
      <category>database</category>
    </item>
    <item>
      <title>LIMIT 20 is not a sampling strategy</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sat, 15 Aug 2026 01:21:47 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/limit-20-is-not-a-sampling-strategy-1o04</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/limit-20-is-not-a-sampling-strategy-1o04</guid>
      <description>&lt;p&gt;“Show me twenty examples” sounds harmless.&lt;/p&gt;

&lt;p&gt;The generated SQL adds &lt;code&gt;LIMIT 20&lt;/code&gt; without a stable order.&lt;/p&gt;

&lt;p&gt;The database returns whichever rows happen to arrive first. The assistant finds a pattern, and the team treats that pattern as evidence.&lt;/p&gt;

&lt;p&gt;But a row limit is not a sample design.&lt;/p&gt;

&lt;p&gt;Physical layout, indexes, query plans, parallel workers, recent inserts, and cache state can all change which rows appear.&lt;/p&gt;

&lt;p&gt;Before examples support a conclusion, define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the population and cutoff&lt;/li&gt;
&lt;li&gt;authorization scope&lt;/li&gt;
&lt;li&gt;the sampling method&lt;/li&gt;
&lt;li&gt;stable row identity&lt;/li&gt;
&lt;li&gt;seed and algorithm version&lt;/li&gt;
&lt;li&gt;strata and weights&lt;/li&gt;
&lt;li&gt;redaction&lt;/li&gt;
&lt;li&gt;what the sample cannot prove&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use deterministic ordering for debugging. Use a stable hash sample for repeatable pseudo-random selection. Use stratification when important cohorts must be represented. Oversample rare cases deliberately—and disclose the bias.&lt;/p&gt;

&lt;p&gt;Most importantly, separate observation from inference.&lt;/p&gt;

&lt;p&gt;“7 of 20 sampled cases had a missing category” does not automatically mean “35% of all cases are missing a category.” Use an approved aggregate when prevalence matters.&lt;/p&gt;

&lt;p&gt;A sampling receipt should make the population, method, seed, version, limits, freshness, source coverage, and result checksum reviewable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LIMIT&lt;/code&gt; protects the system. A sampling contract protects the conclusion.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/chatgpt-database-query-deterministic-sampling-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;ChatGPT database queries need a deterministic sampling contract&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>sql</category>
      <category>ai</category>
    </item>
    <item>
      <title>Three correct database queries can produce one contradictory AI answer</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sat, 15 Aug 2026 01:21:36 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/three-correct-database-queries-can-produce-one-contradictory-ai-answer-1777</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/three-correct-database-queries-can-produce-one-contradictory-ai-answer-1777</guid>
      <description>&lt;p&gt;An AI assistant asks the database three reasonable questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many incidents are open?&lt;/li&gt;
&lt;li&gt;which services are affected?&lt;/li&gt;
&lt;li&gt;which incidents are oldest?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While those calls run, one incident closes and another is created.&lt;/p&gt;

&lt;p&gt;Every query can be correct on its own while the final answer contradicts itself.&lt;/p&gt;

&lt;p&gt;The fix is not to keep a transaction open for the entire AI conversation. Model reasoning and user clarification can turn seconds into minutes.&lt;/p&gt;

&lt;p&gt;Instead, define a short bounded data operation. Compose related reads inside one transaction, return a structured result, close the transaction, and let the model reason afterward.&lt;/p&gt;

&lt;p&gt;Make the consistency promise explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;single statement&lt;/li&gt;
&lt;li&gt;single snapshot&lt;/li&gt;
&lt;li&gt;watermark-aligned sources&lt;/li&gt;
&lt;li&gt;best-effort live reads&lt;/li&gt;
&lt;li&gt;unknown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If several tool calls cannot share a snapshot, return an observation time and watermark for each result. Do not let the final prose quietly upgrade them into one point-in-time claim.&lt;/p&gt;

&lt;p&gt;Retries matter too. After a serialization failure, deadlock, failover, or timeout, rerun the complete logical operation in a clean transaction. Never combine half of attempt one with half of attempt two.&lt;/p&gt;

&lt;p&gt;A snapshot receipt should carry identity, scope, isolation mode, source/replica, observation boundary, filters, limits, retry attempts, completion state, and trace ID.&lt;/p&gt;

&lt;p&gt;The conversation is not the transaction.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-database-multi-query-snapshot-consistency?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP database answers need multi-query snapshot consistency&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>database</category>
      <category>postgres</category>
      <category>ai</category>
    </item>
    <item>
      <title>Zero rows is not proof that nothing exists</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Fri, 14 Aug 2026 01:20:29 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/zero-rows-is-not-proof-that-nothing-exists-227d</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/zero-rows-is-not-proof-that-nothing-exists-227d</guid>
      <description>&lt;p&gt;“No failed payments exist” is a much stronger claim than “this query returned zero rows.”&lt;/p&gt;

&lt;p&gt;The result may be empty because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tenant or environment scope was wrong&lt;/li&gt;
&lt;li&gt;a partition has not arrived&lt;/li&gt;
&lt;li&gt;one source timed out&lt;/li&gt;
&lt;li&gt;a join removed unmatched records&lt;/li&gt;
&lt;li&gt;authorization hid the matching rows&lt;/li&gt;
&lt;li&gt;pagination or a timeout ended the search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So an MCP database result should distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;none found in the complete authorized population&lt;/li&gt;
&lt;li&gt;no visible matches in the caller's scope&lt;/li&gt;
&lt;li&gt;source unavailable&lt;/li&gt;
&lt;li&gt;incomplete search&lt;/li&gt;
&lt;li&gt;not answerable&lt;/li&gt;
&lt;li&gt;unknown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final prose must preserve that state. “No visible matches” must never become “none exist.”&lt;/p&gt;

&lt;p&gt;A negative-answer receipt should carry normalized scope, effective identity and policy, expected and observed sources, watermarks, counts before and after material joins, NULL/unmatched counts, pagination and truncation state, metric version, and trace ID.&lt;/p&gt;

&lt;p&gt;Test the empty path deliberately: delayed sources, known hidden rows, unmatched foreign keys, timezone boundaries, stale definitions, and forced timeouts.&lt;/p&gt;

&lt;p&gt;An empty result is data. An absence claim is a conclusion that needs evidence.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-database-negative-answer-proof?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Prove negative database answers before saying none exist&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>database</category>
      <category>postgres</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your AI did not change the metric. Your business did.</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Fri, 14 Aug 2026 01:20:28 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-ai-did-not-change-the-metric-your-business-did-1o3d</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-ai-did-not-change-the-metric-your-business-did-1o3d</guid>
      <description>&lt;p&gt;Ask an AI assistant for MRR today and again next quarter.&lt;/p&gt;

&lt;p&gt;The SQL can be valid both times while the answers use different definitions.&lt;/p&gt;

&lt;p&gt;Finance may have changed which plans count, how credits are handled, when cancellation becomes effective, or which exchange rate applies.&lt;/p&gt;

&lt;p&gt;If the assistant returns only a number and a query, that semantic change disappears inside a plausible answer.&lt;/p&gt;

&lt;p&gt;A production metric needs an immutable version with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;population and grain&lt;/li&gt;
&lt;li&gt;filters and exclusions&lt;/li&gt;
&lt;li&gt;dimensions&lt;/li&gt;
&lt;li&gt;timezone and cutoff&lt;/li&gt;
&lt;li&gt;source systems&lt;/li&gt;
&lt;li&gt;effective dates&lt;/li&gt;
&lt;li&gt;implementation and policy digests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose the version deliberately: the definition used when the period was originally reported, today's restated definition, or an explicitly pinned version.&lt;/p&gt;

&lt;p&gt;Before rollout, calculate old and new definitions over the same snapshot. Explain material cohort and dimension deltas. Keep historical versions reproducible.&lt;/p&gt;

&lt;p&gt;Metric version also belongs in cache keys, scheduled reports, exports, continuation tokens, and follow-up questions. Fresh rows do not make an old definition current.&lt;/p&gt;

&lt;p&gt;The model may explain a metric. It should not invent the business semantics.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/ai-database-metric-definition-versioning?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;AI database answers need versioned metric definitions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>ai</category>
      <category>dataengineering</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
