<?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>AI support triage does not need your entire inbox</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Wed, 29 Jul 2026 01:21:52 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/ai-support-triage-does-not-need-your-entire-inbox-o5o</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/ai-support-triage-does-not-need-your-entire-inbox-o5o</guid>
      <description>&lt;p&gt;The support lead asks: &lt;strong&gt;what needs attention right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dangerous implementation gives an AI assistant raw ticket tables.&lt;/p&gt;

&lt;p&gt;Those tables contain names, emails, internal notes, copied logs, attachments, security details, and sometimes secrets that should never have been pasted there.&lt;/p&gt;

&lt;p&gt;The useful workflow returns less.&lt;/p&gt;

&lt;p&gt;Start with a governed summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;overdue count&lt;/li&gt;
&lt;li&gt;unassigned count&lt;/li&gt;
&lt;li&gt;reopened count&lt;/li&gt;
&lt;li&gt;age bands&lt;/li&gt;
&lt;li&gt;approved urgency categories&lt;/li&gt;
&lt;li&gt;data freshness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then make drill-down progressive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;aggregate summary&lt;/li&gt;
&lt;li&gt;bounded worklist with ticket IDs and controlled fields&lt;/li&gt;
&lt;li&gt;separately authorized, redacted detail for one case&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each expansion is a new policy decision.&lt;/p&gt;

&lt;p&gt;Keep tenant and queue scope in authenticated context. Keep metric definitions in approved views or a semantic layer. Keep raw notes, contact details, attachments, and unrestricted free text outside the first result.&lt;/p&gt;

&lt;p&gt;And separate analysis from action.&lt;/p&gt;

&lt;p&gt;A tool that identifies five tickets needing owners should not automatically gain authority to assign them, send replies, or close cases. Those are separate operations with validation, idempotency, and approval where needed.&lt;/p&gt;

&lt;p&gt;The goal is not to expose the inbox.&lt;/p&gt;

&lt;p&gt;It is to expose the smallest decision signal that helps the team act.&lt;/p&gt;

&lt;p&gt;Full workflow: &lt;a href="https://conexor.io/blog/chatgpt-database-query-support-triage?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;ChatGPT database query for support triage&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>Your MCP pagination cursor is a security boundary</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Wed, 29 Jul 2026 01:21:51 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-mcp-pagination-cursor-is-a-security-boundary-2jnc</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-mcp-pagination-cursor-is-a-security-boundary-2jnc</guid>
      <description>&lt;p&gt;An AI agent rarely consumes a large PostgreSQL result in one call.&lt;/p&gt;

&lt;p&gt;It reads a page, reasons, calls another tool, and comes back.&lt;/p&gt;

&lt;p&gt;Meanwhile, rows change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LIMIT/OFFSET&lt;/code&gt; describes a position in a changing list. Inserts can duplicate rows across pages. Deletes can skip them.&lt;/p&gt;

&lt;p&gt;For MCP tools, use a deterministic keyset such as:&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then return an opaque cursor bound to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the last ordering values&lt;/li&gt;
&lt;li&gt;tenant and environment&lt;/li&gt;
&lt;li&gt;normalized filter hash&lt;/li&gt;
&lt;li&gt;sort and query version&lt;/li&gt;
&lt;li&gt;page-size ceiling&lt;/li&gt;
&lt;li&gt;snapshot rule&lt;/li&gt;
&lt;li&gt;expiry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server should reject a cursor when any of that scope changes.&lt;/p&gt;

&lt;p&gt;Also document consistency. Does the next page reflect live committed data, a captured high-water mark, or a materialized short-lived result?&lt;/p&gt;

&lt;p&gt;Keyset pagination prevents position drift. It does not magically create a snapshot across separate tool calls.&lt;/p&gt;

&lt;p&gt;Every page should carry &lt;code&gt;has_more&lt;/code&gt;, returned count, filters, freshness or snapshot time, truncation state, and a trace ID. Otherwise the model may present page one as the complete answer.&lt;/p&gt;

&lt;p&gt;Finally, retry the same cursor after a timeout. It should produce the same logical page under the contract—not silently advance or restart.&lt;/p&gt;

&lt;p&gt;Full implementation guide: &lt;a href="https://conexor.io/blog/mcp-server-postgres-keyset-pagination-contract?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP server for Postgres: make pagination a stable data contract&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>postgres</category>
      <category>database</category>
      <category>security</category>
    </item>
    <item>
      <title>A database failover can preserve uptime and break the answer</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Tue, 28 Jul 2026 01:21:53 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/a-database-failover-can-preserve-uptime-and-break-the-answer-5dae</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/a-database-failover-can-preserve-uptime-and-break-the-answer-5dae</guid>
      <description>&lt;p&gt;A PostgreSQL MCP server can remain available during failover and still return the wrong answer.&lt;/p&gt;

&lt;p&gt;The connection retries against a replica.&lt;/p&gt;

&lt;p&gt;The replica is behind.&lt;/p&gt;

&lt;p&gt;A conversation combines a pre-failover result with a post-failover follow-up.&lt;/p&gt;

&lt;p&gt;Every query succeeds, so the final answer looks complete.&lt;/p&gt;

&lt;p&gt;Before production, give each workflow a consistency contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;eventual with a disclosed lag budget&lt;/li&gt;
&lt;li&gt;monotonic within one conversation&lt;/li&gt;
&lt;li&gt;read-your-writes&lt;/li&gt;
&lt;li&gt;point-in-time across multiple queries&lt;/li&gt;
&lt;li&gt;primary-only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then make source identity, schema version, snapshot marker, observed time, and freshness part of material results.&lt;/p&gt;

&lt;p&gt;Test more than promotion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idle pooled connections&lt;/li&gt;
&lt;li&gt;active transactions&lt;/li&gt;
&lt;li&gt;prepared statements and DNS caches&lt;/li&gt;
&lt;li&gt;interrupted multi-query answers&lt;/li&gt;
&lt;li&gt;bounded retries&lt;/li&gt;
&lt;li&gt;conversation continuity&lt;/li&gt;
&lt;li&gt;schema and policy versions&lt;/li&gt;
&lt;li&gt;paused replay and catch-up&lt;/li&gt;
&lt;li&gt;failback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never concatenate partial rows from the old primary with rows from the new one. If the answer cannot prove one consistency boundary, discard the partial result and return a structured retryable failure.&lt;/p&gt;

&lt;p&gt;Availability is a transport property.&lt;/p&gt;

&lt;p&gt;Trustworthy answers need a data contract.&lt;/p&gt;

&lt;p&gt;Full test guide: &lt;a href="https://conexor.io/blog/mcp-server-postgres-failover-consistency-test?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP server for Postgres: test failover consistency, not only availability&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mcp</category>
      <category>database</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your AI database access review needs a revocation test</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Tue, 28 Jul 2026 01:21:52 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-ai-database-access-review-needs-a-revocation-test-1cg4</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-ai-database-access-review-needs-a-revocation-test-1cg4</guid>
      <description>&lt;p&gt;An access review that checks only group membership can approve a connection that no longer exists—and miss one that is still usable.&lt;/p&gt;

&lt;p&gt;A ChatGPT Enterprise database connection crosses several control planes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enterprise identity&lt;/li&gt;
&lt;li&gt;workspace roles&lt;/li&gt;
&lt;li&gt;connector policy&lt;/li&gt;
&lt;li&gt;MCP tool exposure&lt;/li&gt;
&lt;li&gt;database credentials and grants&lt;/li&gt;
&lt;li&gt;row-level policies&lt;/li&gt;
&lt;li&gt;caches and active sessions&lt;/li&gt;
&lt;li&gt;emergency access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review the effective path from a real user to a real result.&lt;/p&gt;

&lt;p&gt;Then test revocation.&lt;/p&gt;

&lt;p&gt;Create a controlled user, establish an active conversation, queue a request, and populate relevant caches. Disable the user and remove the role. Verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new and existing sessions&lt;/li&gt;
&lt;li&gt;queued calls and retries&lt;/li&gt;
&lt;li&gt;refresh tokens&lt;/li&gt;
&lt;li&gt;connector caches&lt;/li&gt;
&lt;li&gt;pooled database connections&lt;/li&gt;
&lt;li&gt;exports and scheduled workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A directory change is not complete revocation if an old session can still execute.&lt;/p&gt;

&lt;p&gt;Break-glass access deserves the same treatment. Approve one bounded query, let the permission expire, and prove that both the credential and active session are unusable.&lt;/p&gt;

&lt;p&gt;The review should change effective access, not just produce a signed spreadsheet.&lt;/p&gt;

&lt;p&gt;Full checklist: &lt;a href="https://conexor.io/blog/chatgpt-enterprise-database-connection-access-review?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;ChatGPT Enterprise database connection: run an access review that tests reality&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>security</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Your PostgreSQL MCP server is only as current as its schema context</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Mon, 27 Jul 2026 01:22:44 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-postgresql-mcp-server-is-only-as-current-as-its-schema-context-1jjo</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-postgresql-mcp-server-is-only-as-current-as-its-schema-context-1jjo</guid>
      <description>&lt;p&gt;A PostgreSQL MCP server can work perfectly on Friday and become confidently wrong after Monday's migration.&lt;/p&gt;

&lt;p&gt;The database is available.&lt;/p&gt;

&lt;p&gt;The tool connects.&lt;/p&gt;

&lt;p&gt;The SQL may even execute.&lt;/p&gt;

&lt;p&gt;But a migration can still change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a view's grain&lt;/li&gt;
&lt;li&gt;a status enum&lt;/li&gt;
&lt;li&gt;a timestamp's business meaning&lt;/li&gt;
&lt;li&gt;a join's cardinality&lt;/li&gt;
&lt;li&gt;exposed permissions&lt;/li&gt;
&lt;li&gt;the authoritative metric source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Schema-change readiness therefore needs more than rediscovery.&lt;/p&gt;

&lt;p&gt;Test renamed and removed objects, executable-but-semantic changes, permission drift, stale caches, multiple MCP replicas, representative query plans, in-flight conversations, and interrupted rollout states.&lt;/p&gt;

&lt;p&gt;Bind every test to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database migration version&lt;/li&gt;
&lt;li&gt;exposed object and policy versions&lt;/li&gt;
&lt;li&gt;semantic definition&lt;/li&gt;
&lt;li&gt;MCP tool schema&lt;/li&gt;
&lt;li&gt;server build&lt;/li&gt;
&lt;li&gt;discovery timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If context freshness cannot be proven, affected tools should fail with a structured stale-schema result instead of guessing.&lt;/p&gt;

&lt;p&gt;And use expand-and-contract for tool contracts too: add the new version, run old and new against deterministic fixtures, migrate clients deliberately, observe the compatibility window, then remove the old contract.&lt;/p&gt;

&lt;p&gt;“The new view exists” is not a production gate.&lt;/p&gt;

&lt;p&gt;“The new meaning is tested, versioned, authorized, observable, and recoverable” is.&lt;/p&gt;

&lt;p&gt;Full test guide: &lt;a href="https://conexor.io/blog/mcp-server-postgres-schema-change-readiness?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP server for Postgres: test schema-change readiness before production&lt;/a&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mcp</category>
      <category>database</category>
      <category>testing</category>
    </item>
    <item>
      <title>The missing column in every ChatGPT connector comparison: who owns the failure?</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Mon, 27 Jul 2026 01:22:33 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/the-missing-column-in-every-chatgpt-connector-comparison-who-owns-the-failure-5h8</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/the-missing-column-in-every-chatgpt-connector-comparison-who-owns-the-failure-5h8</guid>
      <description>&lt;p&gt;The connector demo is usually the easy part.&lt;/p&gt;

&lt;p&gt;A user asks a question, the tool finds a source, and ChatGPT returns a plausible answer.&lt;/p&gt;

&lt;p&gt;Production starts when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an employee leaves&lt;/li&gt;
&lt;li&gt;a reporting view changes&lt;/li&gt;
&lt;li&gt;a replica becomes stale&lt;/li&gt;
&lt;li&gt;a query overloads the source&lt;/li&gt;
&lt;li&gt;sensitive data appears in a trace&lt;/li&gt;
&lt;li&gt;the model or connector changes underneath the workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why connector comparisons need an ownership matrix, not only a feature matrix.&lt;/p&gt;

&lt;p&gt;For every responsibility, name:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;one accountable owner&lt;/li&gt;
&lt;li&gt;the system that enforces the control&lt;/li&gt;
&lt;li&gt;evidence that proves it ran&lt;/li&gt;
&lt;li&gt;the escalation path when it fails&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cover identity and offboarding, semantic definitions, tenant policy, query pressure, result redaction, retention, regression testing, incident response, support, and exit.&lt;/p&gt;

&lt;p&gt;“Shared responsibility” is not useful until the hand-off is explicit.&lt;/p&gt;

&lt;p&gt;A provider can own the connector gateway while the customer still owns database capacity. A data team can own the revenue definition while identity owns offboarding. Security can own retention while the connector owns redaction before model context.&lt;/p&gt;

&lt;p&gt;The point is not to move every responsibility to one vendor.&lt;/p&gt;

&lt;p&gt;It is to make sure every production failure has a named owner before the pilot becomes infrastructure.&lt;/p&gt;

&lt;p&gt;Full matrix: &lt;a href="https://conexor.io/blog/chatgpt-connector-alternatives-production-ownership-matrix?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;ChatGPT connector alternatives: compare production ownership before features&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>Natural-language SQL needs metric contract tests, not just valid SQL</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sun, 26 Jul 2026 01:21:59 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/natural-language-sql-needs-metric-contract-tests-not-just-valid-sql-1ppp</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/natural-language-sql-needs-metric-contract-tests-not-just-valid-sql-1ppp</guid>
      <description>&lt;p&gt;Valid SQL can still produce the wrong business answer.&lt;/p&gt;

&lt;p&gt;An AI SQL assistant may choose real tables, legal joins, and executable filters while misunderstanding what “active customer,” “revenue,” “churn,” or “open pipeline” means.&lt;/p&gt;

&lt;p&gt;So the production test should not stop at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;did the SQL parse?&lt;/li&gt;
&lt;li&gt;did the query run?&lt;/li&gt;
&lt;li&gt;did it stay read-only?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It should test a versioned metric contract.&lt;/p&gt;

&lt;p&gt;For every important metric, define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entity and grain&lt;/li&gt;
&lt;li&gt;eligible population and exclusions&lt;/li&gt;
&lt;li&gt;tenant and environment scope&lt;/li&gt;
&lt;li&gt;numerator, denominator, and state transitions&lt;/li&gt;
&lt;li&gt;time field, timezone, and late-arrival policy&lt;/li&gt;
&lt;li&gt;currency and precision&lt;/li&gt;
&lt;li&gt;approved sources and joins&lt;/li&gt;
&lt;li&gt;freshness budget&lt;/li&gt;
&lt;li&gt;expected behavior when the question is ambiguous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then build a controlled fixture with the awkward cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal and trial accounts&lt;/li&gt;
&lt;li&gt;cancellations and reactivations&lt;/li&gt;
&lt;li&gt;nulls, refunds, and duplicates&lt;/li&gt;
&lt;li&gt;one-to-many joins&lt;/li&gt;
&lt;li&gt;month-end and daylight-saving boundaries&lt;/li&gt;
&lt;li&gt;stale upstream data&lt;/li&gt;
&lt;li&gt;missing conversion rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assert more than exact totals.&lt;/p&gt;

&lt;p&gt;Useful invariants include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the part cannot exceed the eligible population&lt;/li&gt;
&lt;li&gt;exhaustive segments must reconcile to the total&lt;/li&gt;
&lt;li&gt;adding an excluded tenant must not change the metric&lt;/li&gt;
&lt;li&gt;reordering rows must not change the answer&lt;/li&gt;
&lt;li&gt;truncated input cannot produce an unqualified complete result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And test ambiguity on purpose.&lt;/p&gt;

&lt;p&gt;If “active customer” has two approved meanings, the correct output may be a clarification request—not a confident query.&lt;/p&gt;

&lt;p&gt;The key is to separate failure classes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;wrong intent&lt;/li&gt;
&lt;li&gt;guessed contract&lt;/li&gt;
&lt;li&gt;invalid or unsafe SQL&lt;/li&gt;
&lt;li&gt;valid SQL with wrong business meaning&lt;/li&gt;
&lt;li&gt;missing source evidence&lt;/li&gt;
&lt;li&gt;incorrect final summary&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One accuracy score hides which boundary failed.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/natural-language-sql-metric-contract-tests?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Natural language SQL metric contracts: test business meaning before deployment&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>testing</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your AI database audit log should not become a second database</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sun, 26 Jul 2026 01:21:58 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-ai-database-audit-log-should-not-become-a-second-database-4bnl</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-ai-database-audit-log-should-not-become-a-second-database-4bnl</guid>
      <description>&lt;p&gt;An audit log should explain what happened.&lt;/p&gt;

&lt;p&gt;It should not quietly become a second database containing every row the AI was allowed to see.&lt;/p&gt;

&lt;p&gt;That is the evidence problem behind many ChatGPT-to-database rollouts.&lt;/p&gt;

&lt;p&gt;Teams need enough information to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who requested the data?&lt;/li&gt;
&lt;li&gt;which identity and policy applied?&lt;/li&gt;
&lt;li&gt;what source and business definition were used?&lt;/li&gt;
&lt;li&gt;were row, field, and byte limits enforced?&lt;/li&gt;
&lt;li&gt;was the result fresh, complete, or truncated?&lt;/li&gt;
&lt;li&gt;what reached the database?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They usually do &lt;strong&gt;not&lt;/strong&gt; need every returned row copied into prompts, traces, tickets, and observability tools.&lt;/p&gt;

&lt;p&gt;A better receipt records identity, tool and policy versions, normalized filters, source, freshness, row and byte counts, redaction decisions, structured outcome, and a trace reference.&lt;/p&gt;

&lt;p&gt;Keep raw result capture separate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;require an explicit debugging purpose&lt;/li&gt;
&lt;li&gt;restrict who can capture and read it&lt;/li&gt;
&lt;li&gt;redact before persistence&lt;/li&gt;
&lt;li&gt;scope it to the minimum rows and fields&lt;/li&gt;
&lt;li&gt;give it an automatic expiry&lt;/li&gt;
&lt;li&gt;track every downstream copy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two details matter more than they first appear.&lt;/p&gt;

&lt;p&gt;First, remove sensitive literals from query fingerprints. Email addresses and account IDs do not become safe just because they are inside SQL text.&lt;/p&gt;

&lt;p&gt;Second, do not assume hashing solves the problem. Low-entropy values can be guessed, and stable hashes can become identifiers of their own.&lt;/p&gt;

&lt;p&gt;The useful split is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the AI-layer receipt explains intent, policy, and result shape&lt;/li&gt;
&lt;li&gt;database-native evidence proves what reached the data system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together they provide an audit trail without turning telemetry into another store of customer data.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/chatgpt-enterprise-database-connection-evidence-retention?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;ChatGPT Enterprise database connection: retain evidence without retaining the data&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>security</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Claude Code can propose the Postgres migration. It should not execute it.</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sat, 25 Jul 2026 01:21:11 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/claude-code-can-propose-the-postgres-migration-it-should-not-execute-it-36cc</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/claude-code-can-propose-the-postgres-migration-it-should-not-execute-it-36cc</guid>
      <description>&lt;p&gt;Claude Code can inspect application code, trace a failing query, and propose a PostgreSQL migration in one session.&lt;/p&gt;

&lt;p&gt;That does not mean the same session should execute the migration.&lt;/p&gt;

&lt;p&gt;A safer Claude Code + MCP + Postgres boundary:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;use a dedicated read-only investigation identity&lt;/li&gt;
&lt;li&gt;expose schema, plan, lock, sample, and migration-status tools&lt;/li&gt;
&lt;li&gt;keep mutation tools out of that server&lt;/li&gt;
&lt;li&gt;bind every call to an explicit environment&lt;/li&gt;
&lt;li&gt;return a structured migration plan with preconditions and a digest&lt;/li&gt;
&lt;li&gt;validate the plan against current schema and runtime state&lt;/li&gt;
&lt;li&gt;approve the exact digest, target, scope, window, and impact&lt;/li&gt;
&lt;li&gt;execute through a controlled runner with a short-lived identity&lt;/li&gt;
&lt;li&gt;revalidate before execution&lt;/li&gt;
&lt;li&gt;return a durable receipt and preserve recovery checkpoints&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key distinction is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code can investigate.&lt;/li&gt;
&lt;li&gt;Claude Code can propose.&lt;/li&gt;
&lt;li&gt;A reviewer can approve.&lt;/li&gt;
&lt;li&gt;A constrained runner can execute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining those authorities makes a smooth demo.&lt;/p&gt;

&lt;p&gt;Separating them makes a production boundary.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/claude-code-mcp-postgres-developer-access-boundary?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;Claude Code MCP Postgres: separate investigation from migration authority&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>postgres</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>A successful ChatGPT database query can still be wrong</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Sat, 25 Jul 2026 01:21:10 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/a-successful-chatgpt-database-query-can-still-be-wrong-9di</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/a-successful-chatgpt-database-query-can-still-be-wrong-9di</guid>
      <description>&lt;p&gt;A plausible number is not a verified number.&lt;/p&gt;

&lt;p&gt;A ChatGPT database query can run successfully and still answer the wrong business question.&lt;/p&gt;

&lt;p&gt;Common causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the wrong metric definition&lt;/li&gt;
&lt;li&gt;an inherited tenant or environment scope&lt;/li&gt;
&lt;li&gt;stale upstream data&lt;/li&gt;
&lt;li&gt;a month or timezone boundary&lt;/li&gt;
&lt;li&gt;duplicate or late-arriving records&lt;/li&gt;
&lt;li&gt;a truncated result&lt;/li&gt;
&lt;li&gt;a confident summary of a provisional number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before sharing the answer, reconcile it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write the metric, population, exclusions, time window, timezone, and currency&lt;/li&gt;
&lt;li&gt;bind it to an approved source and definition version&lt;/li&gt;
&lt;li&gt;record source freshness and upstream completion&lt;/li&gt;
&lt;li&gt;inspect typed results before the prose&lt;/li&gt;
&lt;li&gt;compare with an independent control&lt;/li&gt;
&lt;li&gt;explain material differences with a movement bridge&lt;/li&gt;
&lt;li&gt;preserve row count, truncation, filters, and trace evidence&lt;/li&gt;
&lt;li&gt;require a separate approval before publication&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The best outcome is not always “the number matches.”&lt;/p&gt;

&lt;p&gt;Sometimes it is: “The difference is explained, the source is late, and this answer is provisional.”&lt;/p&gt;

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

</description>
      <category>chatgpt</category>
      <category>database</category>
      <category>mcp</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Your MCP credential is not revoked when the dashboard says disabled</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Fri, 24 Jul 2026 01:21:36 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/your-mcp-credential-is-not-revoked-when-the-dashboard-says-disabled-2j4p</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/your-mcp-credential-is-not-revoked-when-the-dashboard-says-disabled-2j4p</guid>
      <description>&lt;p&gt;An MCP credential is not revoked when the control plane says “disabled.”&lt;/p&gt;

&lt;p&gt;It is revoked when every path that could still accept it stops accepting it.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new client connections&lt;/li&gt;
&lt;li&gt;existing MCP sessions&lt;/li&gt;
&lt;li&gt;gateway policy caches&lt;/li&gt;
&lt;li&gt;refresh and exchanged tokens&lt;/li&gt;
&lt;li&gt;queued jobs&lt;/li&gt;
&lt;li&gt;background workers&lt;/li&gt;
&lt;li&gt;database connection pools&lt;/li&gt;
&lt;li&gt;downstream APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Define a revocation SLO from the authoritative change to the final protected operation.&lt;/p&gt;

&lt;p&gt;Then run a drill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;issue a uniquely tagged, narrowly scoped credential&lt;/li&gt;
&lt;li&gt;open fresh and persistent sessions&lt;/li&gt;
&lt;li&gt;queue a bounded job&lt;/li&gt;
&lt;li&gt;record the revocation timestamp&lt;/li&gt;
&lt;li&gt;retry through every supported path&lt;/li&gt;
&lt;li&gt;terminate downstream sessions as designed&lt;/li&gt;
&lt;li&gt;search for successful calls after the deadline&lt;/li&gt;
&lt;li&gt;restore access with a new credential&lt;/li&gt;
&lt;li&gt;prove the old one remains invalid&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most important timestamp is not when the dashboard acknowledged the change.&lt;/p&gt;

&lt;p&gt;It is the last successful protected operation.&lt;/p&gt;

&lt;p&gt;Full guide: &lt;a href="https://conexor.io/blog/mcp-credentials-revocation-propagation-test?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;MCP credentials: test revocation propagation before production&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>devops</category>
      <category>database</category>
    </item>
    <item>
      <title>Do not connect ChatGPT to the MySQL writer first</title>
      <dc:creator>Mads Hansen</dc:creator>
      <pubDate>Fri, 24 Jul 2026 01:21:35 +0000</pubDate>
      <link>https://dev.to/mads_hansen_27b33ebfee4c9/do-not-connect-chatgpt-to-the-mysql-writer-first-2ae9</link>
      <guid>https://dev.to/mads_hansen_27b33ebfee4c9/do-not-connect-chatgpt-to-the-mysql-writer-first-2ae9</guid>
      <description>&lt;p&gt;The risky part of connecting MySQL to ChatGPT is not the first successful answer.&lt;/p&gt;

&lt;p&gt;It is proving that a new question workload can reach live data without competing with checkout, support, billing, or other production traffic.&lt;/p&gt;

&lt;p&gt;A better first production path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;choose one bounded question family&lt;/li&gt;
&lt;li&gt;create a dedicated read replica or isolated reporting path&lt;/li&gt;
&lt;li&gt;use a purpose-built MySQL identity&lt;/li&gt;
&lt;li&gt;expose typed business operations, not arbitrary SQL&lt;/li&gt;
&lt;li&gt;measure replica freshness explicitly&lt;/li&gt;
&lt;li&gt;run answer-parity and negative contract tests&lt;/li&gt;
&lt;li&gt;set connection, query, row, byte, and lag budgets&lt;/li&gt;
&lt;li&gt;shadow representative questions&lt;/li&gt;
&lt;li&gt;enable a small cohort with a kill switch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result contract should include the source, filters, observation time, freshness state, row count, truncation, and trace ID.&lt;/p&gt;

&lt;p&gt;If the replica is outside the workflow's freshness budget, return a structured stale-data result. Do not quietly turn old data into a confident answer.&lt;/p&gt;

&lt;p&gt;A read replica is not the whole security model. It is a separate failure domain that makes load testing, canarying, and rollback much easier.&lt;/p&gt;

&lt;p&gt;Full tutorial: &lt;a href="https://conexor.io/blog/connect-mysql-chatgpt-read-replica-cutover?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=content" rel="noopener noreferrer"&gt;How to connect MySQL to ChatGPT with a read-replica cutover&lt;/a&gt;&lt;/p&gt;

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