<?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: hope bilgic</title>
    <description>The latest articles on DEV Community by hope bilgic (@hope_bilgic_a2aa8388e830c).</description>
    <link>https://dev.to/hope_bilgic_a2aa8388e830c</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%2F4119797%2Fe294de05-aebe-48fb-8731-a176c6a30732.png</url>
      <title>DEV Community: hope bilgic</title>
      <link>https://dev.to/hope_bilgic_a2aa8388e830c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hope_bilgic_a2aa8388e830c"/>
    <language>en</language>
    <item>
      <title>A Green Schema Check Can Still Ship a Broken API Integration</title>
      <dc:creator>hope bilgic</dc:creator>
      <pubDate>Thu, 10 Sep 2026 19:59:51 +0000</pubDate>
      <link>https://dev.to/hope_bilgic_a2aa8388e830c/a-green-schema-check-can-still-ship-a-broken-api-integration-104g</link>
      <guid>https://dev.to/hope_bilgic_a2aa8388e830c/a-green-schema-check-can-still-ship-a-broken-api-integration-104g</guid>
      <description>&lt;p&gt;A lot of API failures are not caused by invalid JSON. They happen when both sides still produce syntactically valid data, but they no longer agree about what that data means.&lt;/p&gt;

&lt;p&gt;That is the uncomfortable gap between an expected contract and an observed contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five small drifts that can pass a green lint check
&lt;/h2&gt;

&lt;p&gt;Consider a client that expects this synthetic response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"item_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A schema or style check can remain green while the integration changes in several ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Field type drift: &lt;code&gt;quantity&lt;/code&gt; changes from the integer &lt;code&gt;2&lt;/code&gt; to the string &lt;code&gt;"2"&lt;/code&gt;. The JSON is valid and the field still exists, but arithmetic, validation, or serialization behavior may change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optionality drift: &lt;code&gt;items&lt;/code&gt; was optional for an account with no inventory, but the producer starts omitting another field that the consumer now treats as required. Conversely, a consumer may begin requiring &lt;code&gt;status&lt;/code&gt; even though older valid responses did not include it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enum drift: the producer adds &lt;code&gt;"paused"&lt;/code&gt; to a status enum while the consumer only handles &lt;code&gt;"active"&lt;/code&gt; and &lt;code&gt;"closed"&lt;/code&gt;. Both payloads conform to their local checks; the disagreement appears at the integration boundary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parameter drift: a request parameter map changes from &lt;code&gt;{"region": "eu"}&lt;/code&gt; to &lt;code&gt;{"geo": "eu"}&lt;/code&gt;, or a parameter remains present but changes from a string identifier to a nested object. The endpoint can still return a valid response while the selection logic queries a different scope.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Selection drift: a selector that used to mean “the newest successful item” is interpreted by the other side as “the first item returned.” The field names and types are unchanged, but the selected record is different.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downstream-receipt drift: a write returns HTTP 200 and a valid JSON receipt, but the receipt refers to a different operation identifier, omits a required correlation field, or reports a state that does not reconcile with the observed payload.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are semantic mismatches, not merely formatting mistakes. They can survive static syntax and style linting because the linter usually validates one artifact at a time. It may not compare the expected contract, the observed payload, the parameter map, the selection rule, and the optional downstream receipt as one relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deterministic read-only reconciliation pass
&lt;/h2&gt;

&lt;p&gt;A useful diagnostic does not need to mutate the system. A small read-only reconciliation pass can make the comparison explicit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Normalize the expected contract and the observed contract into a stable representation. Keep field paths, declared types, optionality, enum values, and parameter definitions separate instead of flattening everything into a string comparison.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compare field presence and types by path. Report additions, removals, and type changes independently. &lt;code&gt;items[0].quantity: integer -&amp;gt; string&lt;/code&gt; is more actionable than a generic “schema mismatch.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compare optionality and enum sets. A newly required field and a newly accepted enum value have different compatibility implications, so they should produce different findings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reconcile the parameter map and selection rule. Confirm that the same logical inputs select the same scope and record. When selection is nondeterministic, record that limitation instead of pretending the comparison is conclusive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a downstream receipt is available, compare its operation and correlation fields with the expected result and the observed response. Treat a missing receipt as “not checked,” not as proof of success.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Emit deterministic findings: stable ordering, explicit paths, before/after values where safe, and a clear distinction between mismatch, missing evidence, and unchanged fields.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pass should be read-only. It should not call a customer system, rewrite payloads, retry operations, or “repair” a contract. A safe diagnostic input can be a public example, a synthetic fixture, or genuinely sanitized data with no secrets, credentials, private production data, or personal data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this approach cannot prove
&lt;/h2&gt;

&lt;p&gt;Reconciliation is evidence, not omniscience. A comparison cannot prove that an undocumented field is safe, that every production variant was observed, or that a selected record is correct when the selection rule itself is ambiguous. It also cannot infer business intent from a payload. Different versions may be valid for different consumers, and a receipt can be delayed or incomplete.&lt;/p&gt;

&lt;p&gt;Those limitations are reasons to report uncertainty explicitly. They are not reasons to turn a read-only checker into an automatic migration or production repair tool. A useful result can say “type drift found,” “enum compatibility unknown,” or “receipt not supplied” without claiming more than the fixtures support.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small premarket prototype
&lt;/h2&gt;

&lt;p&gt;This investigation produced the PREMARKET PROTOTYPE Contract Drift Guard, a read-only checker focused on comparing an expected contract with an observed payload/contract, a parameter map, and an optional downstream receipt. The public validation asset is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nerimanozgun75-collab/contract-drift-guard-validation" rel="noopener noreferrer"&gt;https://github.com/nerimanozgun75-collab/contract-drift-guard-validation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can do one bounded free fixture check using only a public, synthetic, or genuinely sanitized non-secret fixture. Please do not provide secrets, credentials, private production data, or personal data.&lt;/p&gt;

&lt;p&gt;I would value technical critique: which drift category is hardest to diagnose in your stack, and what evidence would make a finding useful without making the checker noisy? If your team experiences this exact problem, would it pay for a reliable read-only guard to prevent it? This is a question about problem severity. I make no claims of customers, revenue, production readiness, guaranteed accuracy, or guaranteed savings.&lt;/p&gt;

&lt;p&gt;This article was written with AI assistance and reviewed for the technical scope and safety boundaries described here.&lt;/p&gt;

</description>
      <category>api</category>
      <category>testing</category>
      <category>backend</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
