<?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>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>
    <item>
      <title>Two safe database aggregates can reveal one person's value</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Thu, 13 Aug 2026 01:21:27 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/two-safe-database-aggregates-can-reveal-one-persons-value-3j3c</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/two-safe-database-aggregates-can-reveal-one-persons-value-3j3c</guid>
      <description>&lt;p&gt;An aggregate can hide every row and still reveal one person's value.&lt;/p&gt;

&lt;p&gt;Imagine an AI assistant returns payroll total for a six-person team.&lt;/p&gt;

&lt;p&gt;The user asks again with one employee excluded.&lt;/p&gt;

&lt;p&gt;Subtract the two answers and you have that employee's salary.&lt;/p&gt;

&lt;p&gt;Each query is read-only. Each answer is an aggregate. Each cohort may even pass a minimum-size rule. The disclosure exists only across the sequence.&lt;/p&gt;

&lt;p&gt;That means aggregate privacy cannot be enforced one SQL statement at a time.&lt;/p&gt;

&lt;p&gt;The policy layer needs to evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;minimum cohort size&lt;/li&gt;
&lt;li&gt;dimensional granularity&lt;/li&gt;
&lt;li&gt;overlap with recent cohorts&lt;/li&gt;
&lt;li&gt;dominance by one or two records&lt;/li&gt;
&lt;li&gt;rare categories&lt;/li&gt;
&lt;li&gt;query history across sessions and clients&lt;/li&gt;
&lt;li&gt;cumulative disclosure budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Approved aggregate operations should limit dimensions and drill-down paths. Cached answers and conversation memory must participate in the same disclosure history.&lt;/p&gt;

&lt;p&gt;And when a result is suppressed, return a typed privacy reason—not zero. Zero is a factual claim, and the model must not infer the hidden value from adjacent cells.&lt;/p&gt;

&lt;p&gt;Test query &lt;strong&gt;sequences&lt;/strong&gt;, not isolated prompts.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/chatgpt-database-aggregate-differencing-protection?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Protect ChatGPT database aggregates from differencing attacks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>chatgpt</category>
      <category>security</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Read-only database access does not control where the result goes</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Thu, 13 Aug 2026 01:21:26 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/read-only-database-access-does-not-control-where-the-result-goes-fp6</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/read-only-database-access-does-not-control-where-the-result-goes-fp6</guid>
      <description>&lt;p&gt;A database connection can be read-only and still move sensitive data into places nobody approved.&lt;/p&gt;

&lt;p&gt;The result may enter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an AI client's context&lt;/li&gt;
&lt;li&gt;a hosted model&lt;/li&gt;
&lt;li&gt;application traces&lt;/li&gt;
&lt;li&gt;an observability vendor&lt;/li&gt;
&lt;li&gt;a cache&lt;/li&gt;
&lt;li&gt;an export&lt;/li&gt;
&lt;li&gt;a support screenshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database credential controls where data can be read. It does not define where the result may go next.&lt;/p&gt;

&lt;p&gt;That requires a data egress contract.&lt;/p&gt;

&lt;p&gt;For every result class, define the allowed destination, purpose, region, retention, redaction, caching, export, and deletion behavior. Enforce those facts before the payload crosses the connector boundary.&lt;/p&gt;

&lt;p&gt;Auditability does not require logging the raw result. Keep identity, tenant, operation and policy versions, normalized inputs, source watermark, row and byte counts, redaction decisions, destination class, checksum, and trace ID. Raw payload capture should be exceptional and short-lived.&lt;/p&gt;

&lt;p&gt;Then test the uncomfortable paths: unapproved regions, aliases for sensitive fields, partial streaming, policy changes mid-session, log leakage, and deletion from caches and backups.&lt;/p&gt;

&lt;p&gt;Scoped database access answers &lt;strong&gt;what may be read&lt;/strong&gt;. An egress contract answers &lt;strong&gt;where that data may go next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-database-data-egress-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP database access needs a data egress contract&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>database</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>A silent row limit turns a sample into a lie</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Wed, 12 Aug 2026 01:21:03 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/a-silent-row-limit-turns-a-sample-into-a-lie-3h62</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/a-silent-row-limit-turns-a-sample-into-a-lie-3h62</guid>
      <description>&lt;p&gt;Every production MCP server for PostgreSQL needs result limits.&lt;/p&gt;

&lt;p&gt;The dangerous part is not the limit.&lt;/p&gt;

&lt;p&gt;It is returning the first 1,000 rows without making truncation impossible to miss. The query succeeded, the model received data, and a bounded sample becomes a confident claim about the full population.&lt;/p&gt;

&lt;p&gt;Use independent budgets for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows&lt;/li&gt;
&lt;li&gt;bytes&lt;/li&gt;
&lt;li&gt;statement and end-to-end time&lt;/li&gt;
&lt;li&gt;query cost&lt;/li&gt;
&lt;li&gt;concurrency&lt;/li&gt;
&lt;li&gt;total pages per logical request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then make truncation a typed result state: completeness, reason, observed rows and bytes, stable ordering, and whether continuation is available.&lt;/p&gt;

&lt;p&gt;Continuation tokens are also authorization artifacts. Bind them to the principal, tenant, approved operation, query digest, policy version, stable-order keys, snapshot, and expiry.&lt;/p&gt;

&lt;p&gt;And choose semantics before adding &lt;code&gt;LIMIT&lt;/code&gt;. A request for “top customers” cannot be answered from the first 1,000 invoice rows. It needs an approved aggregate operation over the complete scoped population.&lt;/p&gt;

&lt;p&gt;The rule is simple: if the operation is incomplete, the model must not infer totals, rankings, maxima, or absence as if every row was examined.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-server-postgresql-result-limits-truncation-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;PostgreSQL MCP result limits and truncation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mcp</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>A correct database answer can still be incomplete</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Wed, 12 Aug 2026 01:21:02 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/a-correct-database-answer-can-still-be-incomplete-lmj</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/a-correct-database-answer-can-still-be-incomplete-lmj</guid>
      <description>&lt;p&gt;A database answer can be fresh, numerically correct, and still be incomplete.&lt;/p&gt;

&lt;p&gt;One regional source may be unavailable. An inner join may silently drop unmatched records. A connector may stop after the first page while the model describes the result as the whole population.&lt;/p&gt;

&lt;p&gt;Production AI database access needs a completeness contract.&lt;/p&gt;

&lt;p&gt;Start by defining the expected population: sources, entities, regions, transaction states, reporting interval, and metric version.&lt;/p&gt;

&lt;p&gt;Then return evidence with the result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expected versus observed sources&lt;/li&gt;
&lt;li&gt;source and partition watermarks&lt;/li&gt;
&lt;li&gt;row counts before and after filters and joins&lt;/li&gt;
&lt;li&gt;unknown, null, rejected, and duplicate counts&lt;/li&gt;
&lt;li&gt;missing or timed-out sources&lt;/li&gt;
&lt;li&gt;pagination and truncation state&lt;/li&gt;
&lt;li&gt;reconciliation status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical distinction is between &lt;strong&gt;zero&lt;/strong&gt;, &lt;strong&gt;unknown&lt;/strong&gt;, &lt;strong&gt;not received&lt;/strong&gt;, &lt;strong&gt;redacted&lt;/strong&gt;, and &lt;strong&gt;not applicable&lt;/strong&gt;. Turning all five into zero creates a clean answer that cannot be audited.&lt;/p&gt;

&lt;p&gt;Partial-result policy also belongs in the workflow contract. An exploratory trend may tolerate named gaps. A regulatory total may require every source and completed reconciliation.&lt;/p&gt;

&lt;p&gt;The model can explain why a result is partial. It should not decide whether the missing data is acceptable, and it should never upgrade a partial result to a complete one.&lt;/p&gt;

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

</description>
      <category>database</category>
      <category>ai</category>
      <category>mcp</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Your MCP cache key is part of your authorization model</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Tue, 11 Aug 2026 01:25:00 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-mcp-cache-key-is-part-of-your-authorization-model-1c4j</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-mcp-cache-key-is-part-of-your-authorization-model-1c4j</guid>
      <description>&lt;p&gt;Caching makes a PostgreSQL MCP server faster.&lt;/p&gt;

&lt;p&gt;A weak cache key can also erase the authorization boundary.&lt;/p&gt;

&lt;p&gt;Two users may call the same tool with identical arguments while having different tenants, roles, approved views, policies, or environments.&lt;/p&gt;

&lt;p&gt;If the key contains only the tool name and JSON arguments, discovery metadata or results can cross scopes.&lt;/p&gt;

&lt;p&gt;Start by separating cache classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tool discovery&lt;/li&gt;
&lt;li&gt;schema metadata&lt;/li&gt;
&lt;li&gt;policy decisions&lt;/li&gt;
&lt;li&gt;prepared operation shapes&lt;/li&gt;
&lt;li&gt;database results&lt;/li&gt;
&lt;li&gt;resumable state handles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then include every attribute that changes what the caller may discover or receive: principal, tenant, execution role, environment, policy version, tool-catalog version, schema version, normalized arguments, and source watermark.&lt;/p&gt;

&lt;p&gt;Three details matter in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool discovery itself can reveal sensitive structure.&lt;/li&gt;
&lt;li&gt;Single-flight request coalescing must use the same authorization-aware key.&lt;/li&gt;
&lt;li&gt;A cache hit must preserve freshness, truncation, redaction, and trace evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The negative test is simple: warm the cache, then repeat identical arguments across another tenant, role, environment, revoked approval, and changed schema.&lt;/p&gt;

&lt;p&gt;Measure the absence of cross-scope hits—not only the aggregate hit rate.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-server-postgresql-authorization-aware-cache?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Build an authorization-aware cache for a PostgreSQL MCP server&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>postgres</category>
      <category>database</category>
      <category>security</category>
    </item>
    <item>
      <title>Your enterprise AI answer needs more than a generated-at timestamp</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Tue, 11 Aug 2026 01:24:59 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-enterprise-ai-answer-needs-more-than-a-generated-at-timestamp-402a</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-enterprise-ai-answer-needs-more-than-a-generated-at-timestamp-402a</guid>
      <description>&lt;p&gt;An enterprise AI answer can be correct and already too old.&lt;/p&gt;

&lt;p&gt;The query ran seconds ago. The warehouse snapshot is six hours old. A late event changes the total after the answer is delivered.&lt;/p&gt;

&lt;p&gt;A “generated at” timestamp does not expose any of that.&lt;/p&gt;

&lt;p&gt;For database-connected AI, define freshness as a chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;event time&lt;/li&gt;
&lt;li&gt;ingestion time&lt;/li&gt;
&lt;li&gt;source snapshot or replication watermark&lt;/li&gt;
&lt;li&gt;query time&lt;/li&gt;
&lt;li&gt;answer time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then set a workflow-specific contract: source, maximum age, allowed cache age, timezone, late-arrival policy, and what happens when the threshold is exceeded.&lt;/p&gt;

&lt;p&gt;The result should carry trusted evidence from the connector—not timestamps invented by the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approved source and operation&lt;/li&gt;
&lt;li&gt;tenant and environment scope&lt;/li&gt;
&lt;li&gt;source watermark&lt;/li&gt;
&lt;li&gt;metric and schema version&lt;/li&gt;
&lt;li&gt;query time&lt;/li&gt;
&lt;li&gt;metadata and result-cache age&lt;/li&gt;
&lt;li&gt;filters, truncation, and late-arrival cutoff&lt;/li&gt;
&lt;li&gt;trace ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also separate schema, authorization, and result caches. They have different keys and invalidation rules. A result cached for one tenant must never become reusable by another because the prompt looks the same.&lt;/p&gt;

&lt;p&gt;Fluent prose is not a freshness guarantee.&lt;/p&gt;

&lt;p&gt;Full implementation guide: &lt;a href="https://conexor.io/blog/chatgpt-enterprise-database-answer-freshness-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Define an answer freshness contract for a ChatGPT enterprise database connection&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop retrying a PostgreSQL MCP connection you have not classified</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Mon, 10 Aug 2026 01:22:45 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/stop-retrying-a-postgresql-mcp-connection-you-have-not-classified-el5</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/stop-retrying-a-postgresql-mcp-connection-you-have-not-classified-el5</guid>
      <description>&lt;p&gt;Retrying a PostgreSQL MCP connection without classifying the failure can destroy the best evidence.&lt;/p&gt;

&lt;p&gt;The next attempt may resolve to another IP, use another certificate path, receive another pool connection, or replace the original driver error with a generic timeout.&lt;/p&gt;

&lt;p&gt;Capture a sanitized evidence bundle first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;environment, region, runtime, and connector build&lt;/li&gt;
&lt;li&gt;target fingerprint, database name, and credential version — never the secret&lt;/li&gt;
&lt;li&gt;DNS answers and timing&lt;/li&gt;
&lt;li&gt;TCP outcome and duration&lt;/li&gt;
&lt;li&gt;TLS certificate fingerprint and verification result&lt;/li&gt;
&lt;li&gt;PostgreSQL SQLSTATE and sanitized message&lt;/li&gt;
&lt;li&gt;effective role, schema version, and catalog digest&lt;/li&gt;
&lt;li&gt;pool state, timeout settings, attempt number, and trace ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then classify the layer: configuration, DNS, TCP, TLS, authentication, authorization, schema, pool, or statement execution.&lt;/p&gt;

&lt;p&gt;Retry bounded transient failures with backoff and jitter. Do not retry invalid credentials, certificate identity failures, authorization denials, wrong environments, missing approved objects, or policy rejections.&lt;/p&gt;

&lt;p&gt;“Connection failed” is not a useful error contract. A stable failure code, layer, retryability decision, safe message, and trace ID is.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-server-postgres-connection-troubleshooting-evidence-bundle?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP server Postgres connection troubleshooting with an evidence bundle&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mcp</category>
      <category>database</category>
      <category>security</category>
    </item>
    <item>
      <title>The credential that discovers your database should not query it</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Mon, 10 Aug 2026 01:22:44 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/the-credential-that-discovers-your-database-should-not-query-it-10jf</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/the-credential-that-discovers-your-database-should-not-query-it-10jf</guid>
      <description>&lt;p&gt;The credential that helps an AI discover a database should not automatically be able to query production data.&lt;/p&gt;

&lt;p&gt;Combining discovery and execution in one service account feels convenient. It also means a metadata refresh, indexing job, or compromised discovery component inherits live data access.&lt;/p&gt;

&lt;p&gt;Treat them as two trust boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery identity&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approved schemas, views, columns, types, and safe descriptions&lt;/li&gt;
&lt;li&gt;no table reads or arbitrary function execution&lt;/li&gt;
&lt;li&gt;no unrelated schemas or sensitive comments&lt;/li&gt;
&lt;li&gt;produces a versioned catalog snapshot and digest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Execution identity&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approved read operations only&lt;/li&gt;
&lt;li&gt;authenticated user, tenant, purpose, and environment&lt;/li&gt;
&lt;li&gt;database-enforced scope, statement limits, and result limits&lt;/li&gt;
&lt;li&gt;evidence tied to the promoted catalog digest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Promote metadata from discovery to execution. Never promote the credential.&lt;/p&gt;

&lt;p&gt;Keep development, staging, and production identities separate. Rotate and revoke discovery and execution independently. Then test that each identity cannot do the other one's job.&lt;/p&gt;

&lt;p&gt;A successful connection test proves that a credential works. Separation tests prove that its authority stops where it should.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/chatgpt-enterprise-database-discovery-execution-credentials?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Separate discovery and execution credentials for a ChatGPT enterprise database connection&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>security</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
