<?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: Shubham Anandani</title>
    <description>The latest articles on DEV Community by Shubham Anandani (@imshubh).</description>
    <link>https://dev.to/imshubh</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%2F4175872%2Ff7913371-472d-4e0b-8eb3-c24306cc6761.jpg</url>
      <title>DEV Community: Shubham Anandani</title>
      <link>https://dev.to/imshubh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imshubh"/>
    <language>en</language>
    <item>
      <title>Revocation Is the Hard Part</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Tue, 25 Aug 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/revocation-is-the-hard-part-1kad</link>
      <guid>https://dev.to/imshubh/revocation-is-the-hard-part-1kad</guid>
      <description>&lt;p&gt;Issuing verifiable credentials is straightforward: define a claim, sign it, return it to the holder. Revocation is harder: you need to detect when a credential should no longer be accepted, ideally without creating a surveillance channel that reveals who asked and when.&lt;/p&gt;

&lt;h3&gt;
  
  
  The failure mode
&lt;/h3&gt;

&lt;p&gt;A verifier calls a central revocation endpoint for every check. The endpoint sees who asked (IP or key), when, for which credential ID, and what outcome occurred. Even if the credential is privacy-preserving, the revocation lookup becomes a “phone home” channel that leaks context. Users and regulators won’t love that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understand the revocation design space
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Status list (offline)
&lt;/h3&gt;

&lt;p&gt;The issuer publishes a privacy-preserving list of credential status values (for example, a bitstring status list) that verifiers fetch periodically. Verification checks against the local list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Online status check
&lt;/h3&gt;

&lt;p&gt;The verifier queries a status endpoint per credential. Freshness improves; privacy worsens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Holder stapling
&lt;/h3&gt;

&lt;p&gt;The holder obtains a fresh status attestation and presents it along with the credential. The verifier checks the stapled proof without contacting the issuer per verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-lived credentials:
&lt;/h3&gt;

&lt;p&gt;Expire credentials quickly so revocation windows are short. This reduces reliance on revocation checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy-preserving lists: batch and compress
&lt;/h3&gt;

&lt;p&gt;A status list can represent many credentials without revealing individual identities. Verifiers download the list on a schedule (e.g., every policy-defined freshness window), cache it locally, and check bits/entries offline. Lists can be partitioned or hashed to limit list size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stapling shifts “phone home” to holder flow
&lt;/h3&gt;

&lt;p&gt;With stapling, the holder requests a status proof from the issuer (or a delegated service) at a cadence, then presents it to verifiers. This avoids per-verification lookups by verifiers but still requires the holder to contact the issuer on some schedule. The privacy difference is who calls whom and when.&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-lived credentials reduce time at risk
&lt;/h3&gt;

&lt;p&gt;If credentials expire quickly, revocation needs are less acute. This shifts load from revocation infrastructure to issuance (more frequent renewals). Combine with holder stapling to keep verification fast and private.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model cache freshness and failure modes
&lt;/h3&gt;

&lt;p&gt;Verifiers should track the freshness of status lists. If a list is too old (e.g., beyond your policy window), treat the result as “unverifiable” rather than “accepted,” and prompt re-fetch or stapling. Distinguish “cache stale” from “revoked.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid per-ID URLs in logs
&lt;/h3&gt;

&lt;p&gt;If you must hit an online status endpoint, avoid logging per-ID lookups in a way that identifies a particular holder or verifier. Aggregate metrics instead. Keep any necessary identifiers hashed or pseudonymous.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distinguish revocation from expiry and audience
&lt;/h3&gt;

&lt;p&gt;Revocation says “this specific credential is no longer valid.” Expiry says “credentials of this type are only valid until T.” Audience says “this verifier is permitted to consume the claim.” Don’t overload revocation with other policy failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Illustrative status-list freshness check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StatusSnapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fetched_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_fresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StatusSnapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_age_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetched_at&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_age_seconds&lt;/span&gt;

&lt;span class="c1"&gt;# Choose the freshness window from policy and operational requirements.
# Stale status should have an explicit outcome, such as "unverifiable".
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offline lists vs freshness:&lt;/strong&gt; Lists preserve privacy but can be stale. Address with frequent, batched updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stapling vs issuer load:&lt;/strong&gt; Stapling shifts calls to holders; the issuer still handles periodic status proofs. Load is manageable with caching and rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Short-lived credentials vs user friction:&lt;/strong&gt; Frequent renewals add UX overhead. Balance with clear prompts and automated refresh flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Phone home”:&lt;/strong&gt; Per-verification lookups leak context. Solution: offline lists or stapling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Stale cache”:&lt;/strong&gt; Verifiers use outdated lists. Solution: freshness checks and policy windows that treat stale as “unverifiable.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Revocation conflation”:&lt;/strong&gt; Treating expiry or audience failure as revocation. Solution: separate policy checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Implement an offline status list with a freshness window. Treat stale lists as “unverifiable.”&lt;/li&gt;
&lt;li&gt;Add holder stapling to your demo: the holder fetches a status proof and presents it alongside the credential.&lt;/li&gt;
&lt;li&gt;Track performance for offline list checks vs online status calls. Compare privacy and tail behavior.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/verified-id/how-to-issuer-revoke" rel="noreferrer noopener"&gt;Revoke a verifiable credential as an issuer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/vc-bitstring-status-list/" rel="noreferrer noopener"&gt;W3C Bitstring Status List v1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/verified-id/plan-verification-solution" rel="noreferrer noopener"&gt;Plan a Verified ID verification solution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>identity</category>
      <category>security</category>
      <category>privacy</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Designing for Partial Data: Distinguishing “We Don’t Know” From “False”</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Sat, 22 Aug 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/designing-for-partial-data-distinguishing-we-dont-know-from-false-56b2</link>
      <guid>https://dev.to/imshubh/designing-for-partial-data-distinguishing-we-dont-know-from-false-56b2</guid>
      <description>&lt;p&gt;Partial data is normal. Missing signals and unverifiable sources happen. If you collapse “unknown” into “false” or “zero,” you erode trust. As an engineering manager, I’ve learned to design services that treat “we don’t know” as a first-class outcome. In Java, sealed types and exhaustive pattern matching make this straightforward and enforceable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;Your aggregator treats null as “0.0,” sums four dimensions, and displays a neat number. A caller misreads that as “complete and low.” In reality, two dimensions were missing, and one was unverifiable. The number masked uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Model the states as a sealed type
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt; &lt;span class="n"&gt;permits&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Missing&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Unverifiable&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Score&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Missing&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Unverifiable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keep partial states in the domain layer, not just at the edges
&lt;/h3&gt;

&lt;p&gt;Don’t convert unknowns to defaults at the boundary. Let domain logic operate on sealed types. If you must produce a single number, force policy conversion in a dedicated function so that the decision is auditable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AggregationPolicy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;conservativeAverage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dims&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dims&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Missing&lt;/span&gt; &lt;span class="n"&gt;miss&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* exclude; track missing elsewhere */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Unverifiable&lt;/span&gt; &lt;span class="n"&gt;unv&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* exclude; track unverifiable */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NaN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// no numeric evidence&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;okToDisplay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dims&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;unknowns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dims&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unknowns&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or choose a threshold&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Propagate uncertainty deliberately
&lt;/h3&gt;

&lt;p&gt;Unknowns should travel with the data: into logs, decisions, and UI. For example, if two dimensions are unknown, surface that count alongside any numeric aggregation. The goal is to prevent silent conversions that distort confidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make unknown-handling policies explicit and testable
&lt;/h3&gt;

&lt;p&gt;Write tests that assert what happens when all dimensions are unknown, when some are missing, and when a single unverifiable dimension appears. Pattern matching keeps these tests aligned with the domain states. Don’t assume a “best effort” numeric default; decide and encode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resist “default values” in DTOs
&lt;/h3&gt;

&lt;p&gt;If you serialize to JSON, include a type field in each dimension and omit value when the state isn’t Score. A consumer reading &lt;code&gt;{"type":"Missing","reason":"timeout"}&lt;/code&gt; can act accordingly. Defaulting to 0.0 or “unknown=false” invites misuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Teach callers to branch by type, not numeric checks
&lt;/h3&gt;

&lt;p&gt;Documentation should show switch examples, not “if value is 0 then unknown.” This reduces coupling and preserves room for future states (e.g., “Contested,” “Deprecated”).&lt;/p&gt;

&lt;h3&gt;
  
  
  Audit conversion points with a line list
&lt;/h3&gt;

&lt;p&gt;List every place where ScoreOrUnknown becomes a single number or a boolean. Audit these conversions periodically. If you discover accidental flattening, refactor to preserve states or add proper policy functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accept that aggregation may return “no number”
&lt;/h3&gt;

&lt;p&gt;Returning NaN or omitting a field is reasonable when inputs are fully unknown. Treat this as normal, not exceptional. UI can show “insufficient evidence” with counts; APIs can document this outcome clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON serialization with a type field
&lt;/h3&gt;

&lt;p&gt;A simple serializer sketch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;toJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ScoreOrUnknown&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Score&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Score"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Missing&lt;/span&gt; &lt;span class="n"&gt;miss&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Missing"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"reason"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;miss&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;Unverifiable&lt;/span&gt; &lt;span class="n"&gt;unv&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Unverifiable"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"note"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;};&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transparency vs brevity:&lt;/strong&gt; Returning types and reasons increases payload size but improves clarity. Favor clarity when ambiguity matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strictness vs convenience:&lt;/strong&gt; Throwing on invalid scores and disallowing defaults catches bugs early but may frustrate callers used to flexible shapes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NaN vs explicit omission:&lt;/strong&gt; NaN signals “no numeric value” but can be mishandled by consumers. Omitting a field or providing a separate state field avoids confusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Silent flattening”:&lt;/strong&gt; A downstream transforms unknowns to zero, likely out of habit. Catch this in contract tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Enum drift”:&lt;/strong&gt; If reason codes evolve, consumers break. Document codes and provide a safe default handling path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Partial measures hiding in averages”:&lt;/strong&gt; An average of one numeric with three unknowns looks “complete” unless you also surface unknown counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Refactor one DTO to embed a sealed state rather than a nullable numeric field. Add a type field in JSON. Update consumers to switch on type.&lt;/li&gt;
&lt;li&gt;Write aggregation policies that return NaN or omit fields when inputs are fully unknown. Test behavior explicitly.&lt;/li&gt;
&lt;li&gt;Add metrics that count unknowns and unverifiable states separately from numeric distributions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/395" rel="noreferrer noopener"&gt;Records (JEP 395)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/409" rel="noreferrer noopener"&gt;Sealed Classes (JEP 409)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/441" rel="noreferrer noopener"&gt;Pattern Matching for switch (JEP 441)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>architecture</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Packaging Inference for Production: A Checklist for Shipping an Optimized-Model Service</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Thu, 23 Jul 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/packaging-inference-for-production-a-checklist-for-shipping-an-optimized-model-service-dpo</link>
      <guid>https://dev.to/imshubh/packaging-inference-for-production-a-checklist-for-shipping-an-optimized-model-service-dpo</guid>
      <description>&lt;p&gt;A model can be accurate and still be hard to operate if conversion is manual, artifacts are opaque, precision changes silently, or fallbacks have never been exercised. Production readiness is the process around the model as much as the model itself.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aceu-edge-infer&lt;/code&gt; is a modest inference demo, but I treat its packaging path as if another engineer will need to rebuild, inspect, and recover it. Its outputs remain illustrative, not evaluative of real people. This checklist is about making that kind of service repeatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make model conversion reproducible
&lt;/h2&gt;

&lt;p&gt;Keep conversion in a script or build step rather than a shell history. Record the source model hash, tool and runtime versions, input shapes, conversion options, and warnings. Use separate, reviewable configuration for FP32 and INT8 artifacts, and retain the conversion logs with the release build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openvino&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ov&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/source.onnx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build/models/model_fp32.xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example shows the conversion step only. Production automation should also validate model inputs, run parity checks, record the tool version, and fail when expected artifacts are missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version and identify the artifacts
&lt;/h2&gt;

&lt;p&gt;Give model artifacts versions tied to the code and conversion configuration that produced them. Store IR XML and weights together, record hashes, and keep them immutable after release. If calibration or quantization changes, publish a new model version rather than overwriting a file called &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A release manifest can record the information operators need without embedding the model itself:&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;"model_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;release-version&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ONNX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OpenVINO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"precision_artifacts"&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="s2"&gt;"FP32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INT8"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"device_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;source-model-hash&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conversion_config_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;config-hash&amp;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;Replace the placeholders during the build. These are identifiers to calculate and record, not measured values to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclose the execution path
&lt;/h2&gt;

&lt;p&gt;Include the precision and device used in logs or response metadata. Where it makes sense for the product, provide a controlled option to route sensitive work through FP32. That small bit of transparency can make rollout analysis and incident response much faster. Keep the API contract stable and avoid exposing internal details that callers do not need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test fallbacks before a release
&lt;/h2&gt;

&lt;p&gt;Simulate an unavailable optimized runtime and verify the intended behavior. A valid fallback should either return correct outputs on a supported slower path or fail clearly with a non-ready/unavailable state. It should not manufacture placeholder results or quietly claim the preferred precision.&lt;/p&gt;

&lt;p&gt;If custom operators exist, test the fallback with representative inputs and confirm that the selected path is visible in logs. Document what operators or hardware are required for each execution mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package the runtime deliberately
&lt;/h2&gt;

&lt;p&gt;Use a multi-stage image where it keeps build tools out of the runtime. Run as a non-root user, keep the base image and dependencies maintained, copy only the application and model artifacts required at runtime, and provide a bounded health check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Illustrative Python service image; adapt paths and dependencies to your app.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv /venv
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/venv/bin:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;--uid&lt;/span&gt; 10001 &lt;span class="nt"&gt;--create-home&lt;/span&gt; appuser
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /venv /venv&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=10001:10001 app/ ./app/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=10001:10001 models/ ./models/&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/venv/bin:$PATH" \&lt;/span&gt;
    PYTHONDONTWRITEBYTECODE=1
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 10001&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --retries=3 \&lt;/span&gt;
  CMD python -c 'import urllib.request; urllib.request.urlopen("http://localhost:8000/health", timeout=2)' || exit 1
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that the chosen base image contains everything the service actually needs. The Docker health check should stay lightweight; configure a separate platform readiness check if model loading or a sample inference must gate incoming traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build, validate, and scan in CI
&lt;/h2&gt;

&lt;p&gt;Use the same conversion scripts in local development and CI. Check that expected files exist, compare model outputs against a golden set, build the service image, and scan it before release. Make missing artifacts, failed parity checks, and scan failures visible release blockers. Keep exceptions owned and time-bounded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep operational notes close to the code
&lt;/h2&gt;

&lt;p&gt;Give the README short, findable sections for conversion, artifact versions, precision modes, fallback behavior, and hardware assumptions. Add an incident note describing what to inspect when latency or output parity changes, and point to the known-good artifact for rollback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Process rigor vs. prototype speed:&lt;/strong&gt; Scripts and manifests take effort once and save time on the next rebuild.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More API metadata vs. smaller contract:&lt;/strong&gt; Publish only what helps callers; keep additional operational detail in logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict readiness vs. faster first traffic:&lt;/strong&gt; Waiting for model load and checks reduces false-green deploys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Mystery IR” files:&lt;/strong&gt; Artifacts nobody can trace back to a source model or configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent FP32 fallbacks:&lt;/strong&gt; The service quietly runs a different precision than callers expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unreviewed base image changes:&lt;/strong&gt; A rebuild produces a different runtime than the one you tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Version the inputs and outputs, disclose the selected path, and exercise rollback before you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it before your next release
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Script FP32 and INT8 conversion and retain logs with immutable model artifacts.&lt;/li&gt;
&lt;li&gt;Record source and configuration hashes in a release manifest.&lt;/li&gt;
&lt;li&gt;Add actual precision/device metadata and a tested force-FP32 option if the product needs it.&lt;/li&gt;
&lt;li&gt;Test missing-runtime and unsupported-operator behavior before launch.&lt;/li&gt;
&lt;li&gt;Build, validate, and scan the same container image in CI that you intend to deploy.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Packaging inference for production is about repeatability, transparency, and safe recovery. When another engineer can recreate the model, identify the execution path, and trigger a tested fallback, the service is ready for more than a successful demo.&lt;/p&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.openvino.ai/2026/openvino-workflow/model-preparation/convert-model-to-ir.html" rel="noreferrer noopener"&gt;OpenVINO model conversion and saving IR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/build/building/best-practices/" rel="noreferrer noopener"&gt;Docker build best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/build/building/multi-stage/" rel="noreferrer noopener"&gt;Docker multi-stage builds&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mlops</category>
      <category>ai</category>
      <category>devops</category>
      <category>intel</category>
    </item>
    <item>
      <title>Designing for Other People’s Code: A Plugin API That Welcomes Contributions</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Mon, 20 Jul 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/designing-for-other-peoples-code-a-plugin-api-that-welcomes-contributions-4bf3</link>
      <guid>https://dev.to/imshubh/designing-for-other-peoples-code-a-plugin-api-that-welcomes-contributions-4bf3</guid>
      <description>&lt;p&gt;The best way to attract contributions is to make the path small, clear, and safe. Big, powerful extension APIs are intimidating and fragile. A plugin interface is easier to adopt when it has a small typed contract, a complete example, and clear isolation boundaries for contributed code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;You want contributions, so you design a rich, flexible plugin API. The first contributor struggles to understand how the parts fit together. Another contributor introduces side effects that break core behavior. You spend time triaging issues rather than merging useful additions. The fix is to narrow the contract and improve examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep the interface tiny
&lt;/h3&gt;

&lt;p&gt;Use a single method with a small, typed result. Avoid optional hooks and deep callback trees. If you need flexibility, let plugins implement it internally. Keep core expectations documented and enforced with tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Illustrative plugin contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Protocol&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unverifiable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EvidenceSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EvidenceResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Documentation-by-example beats long narratives
&lt;/h3&gt;

&lt;p&gt;Write a short “Adding a plugin” doc that starts with a complete code example and a test. Follow with two variations that show common patterns (e.g., reading a field, combining small signals). Keep the words sparse and the code complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sandboxing contributed code: practical guardrails
&lt;/h3&gt;

&lt;p&gt;Run plugins within constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a timeout per plugin evaluation.&lt;/li&gt;
&lt;li&gt;Limit memory-intensive operations or external calls in the demo environment.&lt;/li&gt;
&lt;li&gt;Validate types and restrict dangerous imports in plugin modules if possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A thread timeout is not a security sandbox: a host process cannot safely terminate arbitrary plugin code. For untrusted extensions, use a separate process or container with resource limits and restricted credentials, or load only trusted plugins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Versioning and compatibility checks (keep them light)
&lt;/h3&gt;

&lt;p&gt;Require plugins to declare supported interface versions and run contract tests. Keep the declaration simple, and make the CI failure message actionable (e.g., “Plugin supports 1.1.0; core is 1.2.0 — update SUPPORTED_VERSIONS”).&lt;/p&gt;

&lt;h3&gt;
  
  
  Golden paths and anti-patterns
&lt;/h3&gt;

&lt;p&gt;Show five “golden path” examples contributors should follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pure function:&lt;/strong&gt; Read inputs, return &lt;code&gt;EvidenceResult&lt;/code&gt;, no side effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight computation:&lt;/strong&gt; Avoid heavy loops or large allocations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic outputs:&lt;/strong&gt; No random behavior or time-dependent outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed result:&lt;/strong&gt; Always return one of the three states with a note.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No external calls:&lt;/strong&gt; For demo simplicity, avoid network calls.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you allow external calls, define strict limits and document them clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error messages designed for contributors
&lt;/h3&gt;

&lt;p&gt;When a plugin fails a contract test, the message should name the plugin, the failing expectation, and the fix. Prefer helpful failures over terse stack traces. Provide a “common issues” page with targeted guidance.&lt;/p&gt;

&lt;h3&gt;
  
  
  “Hello world” PR built into the docs
&lt;/h3&gt;

&lt;p&gt;Include a numbered guide that results in a merged PR:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the plugin file.&lt;/li&gt;
&lt;li&gt;Add a minimal test.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pytest -q&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open a PR with the template.&lt;/li&gt;
&lt;li&gt;Review the expected CI jobs and how to interpret them.&lt;/li&gt;
&lt;li&gt;Review the merge expectations.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small interface vs expressiveness:&lt;/strong&gt; Narrow contracts limit advanced features; they also reduce fragility and cognitive load. For small projects, this trade-off is worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sandboxing vs performance:&lt;/strong&gt; Timeouts and thread isolation add overhead. Keep limits modest and configurable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation-by-example vs thorough reference:&lt;/strong&gt; Examples speed onboarding; deep reference can come later if demand arises.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Stealth side effects”:&lt;/strong&gt; Plugins write to global state or files unexpectedly. Solution: sandbox and contract tests; make side-effect-free behavior a rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Doc drift”:&lt;/strong&gt; Examples fall behind reality. Solution: run example snippets in CI and fail when they don’t compile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Silent timeouts”:&lt;/strong&gt; Sandboxing hides errors without surfacing enough context. Solution: attach structured error notes to &lt;code&gt;EvidenceResult&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Shrink your plugin interface to a single method that returns a small typed object.&lt;/li&gt;
&lt;li&gt;Add a sandbox wrapper with timeouts and typed error handling. Run plugins through it in CI contract tests.&lt;/li&gt;
&lt;li&gt;Write a “hello world plugin” guide with a copy-paste example and a minimal test, then merge a sample PR to validate the path.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/tutorials/build-and-test-code" rel="noreferrer noopener"&gt;GitHub Actions: Building and testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semver.org/" rel="noreferrer noopener"&gt;Semantic Versioning 2.0.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://keepachangelog.com/en/1.1.0/" rel="noreferrer noopener"&gt;Keep a Changelog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>api</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Reproducible Java Builds: Pinning, Locking, and Clean-Room Verification</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Sun, 12 Jul 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/reproducible-java-builds-pinning-locking-and-clean-room-verification-5bl1</link>
      <guid>https://dev.to/imshubh/reproducible-java-builds-pinning-locking-and-clean-room-verification-5bl1</guid>
      <description>&lt;p&gt;I’ve seen Java builds succeed locally and differ in CI: transitive dependencies float, plugin versions advance, or caches hide changes. Reproducibility takes intention: pin versions, use lock files, and verify clean-room rebuilds. In a Java build, dependency locks and clean CI environments help reveal resolution drift before artifacts are released.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;You set versions in &lt;code&gt;pom.xml&lt;/code&gt; but leave transitive ranges unconstrained. Locally, a cached artifact hides a minor release; in CI, it resolves a new version. Behavior changes. The fix is not “rerun until green.” It’s making builds deterministic and verifying them under clean conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pin dependencies and plugins explicitly
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;dependencyManagement&lt;/code&gt; to pin versions of critical dependencies and plugins. Avoid wildcard ranges. If you depend on a BOM (bill of materials), understand what it pins and add explicit versions for anything critical that the BOM leaves flexible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencyManagement&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.junit.jupiter&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;junit-jupiter&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;5.10.2&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- pin more --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencyManagement&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use a lock mechanism to freeze transitive versions
&lt;/h3&gt;

&lt;p&gt;A lock file captures resolved versions so CI doesn’t “discover” new transitive artifacts. Tools exist for Maven and Gradle that generate and enforce locks. The specifics depend on your chosen plugin; the principle is the same: generate once, commit, and enforce.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate the lock file locally (or in CI).&lt;/li&gt;
&lt;li&gt;Commit it.&lt;/li&gt;
&lt;li&gt;Configure the build to fail if resolutions differ from the lock.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Clean-room rebuild in CI
&lt;/h3&gt;

&lt;p&gt;Build in an isolated environment with a cleared local repository. Verify that resolving dependencies succeeds strictly against your lock. Optionally run the build twice: once with the lock (expected success), and once with lock enforcement disabled to detect version drift. The second build should fail if drift occurred.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI steps (illustrative)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clean local repo&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rm -rf ~/.m2/repository&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify dependency locks&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn -q -B clean verify -P enforce-lock&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Clean&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;room&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rebuild"&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker run --rm -v "$PWD:/w" -w /w eclipse-temurin:21-jdk bash -lc "mvn -q -B clean verify -P enforce-lock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reproducible artifacts (timestamps and ordering)
&lt;/h3&gt;

&lt;p&gt;Ensure JARs are reproducible: strip timestamps, stabilize entry ordering, and avoid embedding environment-specific paths. Many build tools support “reproducible build” flags. Enable them and verify artifact hashes are stable across clean rebuilds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Digest references for external assets
&lt;/h3&gt;

&lt;p&gt;If your build fetches external tools or base images (for integration tests, for example), reference them by digest. Tags are mutable; digests fix identity. The trade-off is manual refresh when security updates appear, which is worth it for determinism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automate updates safely
&lt;/h3&gt;

&lt;p&gt;Use a dependency-update bot that proposes version bumps. Gate those PRs with tests and scanning. Accepting updates becomes explicit, and drift is no longer silent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document the “how to fix drift” playbook
&lt;/h3&gt;

&lt;p&gt;When lock enforcement fails, the fix should be documented: regenerate the lock with your plugin, review changes, run tests, and commit. Avoid ad-hoc local resolutions, and keep the workflow consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pins vs agility:&lt;/strong&gt; Strict pins and locks slow updates but eliminate surprises. Use bots to keep pace safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean rooms vs speed:&lt;/strong&gt; Clean rebuilds take longer. For CI, prioritize correctness over a few minutes of build time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digests vs tags:&lt;/strong&gt; Digests provide determinism; tags ease maintenance. For production-critical assets, default to digests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Partial pins”:&lt;/strong&gt; You pin direct deps but miss transitive ones. Locks solve this; without locks, drift leaks in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Plugin drift”:&lt;/strong&gt; Build plugins advance silently. Pin plugin versions in &lt;code&gt;pluginManagement&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Non-reproducible JARs”:&lt;/strong&gt; Differences in timestamps or ordering break hash equality. Turn on reproducible flags and verify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add dependency and plugin pins in &lt;code&gt;dependencyManagement&lt;/code&gt; and &lt;code&gt;pluginManagement&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Integrate a lock mechanism, commit the lock file, and add an enforcement profile.&lt;/li&gt;
&lt;li&gt;Run a clean-room rebuild in CI and compare the artifacts. Confirm artifact hashes match between runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html" rel="noreferrer noopener"&gt;Maven dependency management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gradle.org/current/userguide/dependency_locking.html" rel="noreferrer noopener"&gt;Gradle dependency locking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html" rel="noreferrer noopener"&gt;Reproducible Builds Maven plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>devops</category>
      <category>cicd</category>
      <category>buildsystems</category>
    </item>
    <item>
      <title>Keys Don’t Belong in the Image: Secret Management for Credential Signing</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Sun, 12 Jul 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/keys-dont-belong-in-the-image-secret-management-for-credential-signing-2b72</link>
      <guid>https://dev.to/imshubh/keys-dont-belong-in-the-image-secret-management-for-credential-signing-2b72</guid>
      <description>&lt;p&gt;Signing keys are crown jewels in identity services. If they leak, attackers can mint forged credentials that look real. The easiest way to leak a key is to bake it into a container image or configuration file. Load signing keys at runtime from a managed secret store; keep private key material out of source files, build arguments, and image layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;A contributor tests locally with a &lt;code&gt;.env&lt;/code&gt; file containing a signing key. A sloppy Dockerfile copies everything under the project directory, including &lt;code&gt;.env&lt;/code&gt;. CI builds the image; the key ends up in a layer. The image is pushed to a registry. Even if you rotate later, the leak is permanent in history.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Secrets never live in source or image layers
&lt;/h3&gt;

&lt;p&gt;Keep signing keys out of your repository and artifact layers. Use a managed secret store pattern to provide keys at runtime via secure mounts or platform-managed environment variables that never touch the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.dockerignore&lt;/code&gt; is a security control
&lt;/h3&gt;

&lt;p&gt;Treat &lt;code&gt;.dockerignore&lt;/code&gt; as part of your security posture. Explicitly ignore &lt;code&gt;.env&lt;/code&gt;, credential files, test fixtures containing secrets, and local caches. Keep the build context as small as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime retrieval, not build-time embedding
&lt;/h3&gt;

&lt;p&gt;Load keys at runtime from the secret store client library or a platform-provided mount. Fail readiness until the key loads. If the secret is missing, return clear errors and refuse to sign. Do not “fall back” to dummy keys in production. Reserve test keys for explicit non-prod builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rotation: short lifetimes and multiple active keys
&lt;/h3&gt;

&lt;p&gt;Signing keys should rotate regularly. The verifier should accept signatures from a small set of current keys, and the issuer should roll keys with overlap: publish the new key, start signing with it, and keep the old key valid for verification until the overlap window expires.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audit and scanning: prevent accidental bake-ins
&lt;/h3&gt;

&lt;p&gt;Add CI checks that scan images for secret patterns. Simple rules catch common mistakes (e.g., files named &lt;code&gt;.env&lt;/code&gt;, PEM key headers). Block the build if found. Complement this with code scanning that flags dangerous &lt;code&gt;COPY&lt;/code&gt; patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate test and prod key handling
&lt;/h3&gt;

&lt;p&gt;For demos, use explicitly marked test keys with sandboxed scopes. Never reuse test keys in environments accessible outside controlled demos. Label logs and status endpoints so it’s obvious which environment your verifier belongs to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimize key surface: hardware-backed or managed services
&lt;/h3&gt;

&lt;p&gt;Where possible, use hardware-backed key stores or provider-managed keys with restricted export. Keep application access tightly scoped to “sign” and “read public key.” The app should never be able to exfiltrate private key material.&lt;/p&gt;

&lt;h3&gt;
  
  
  Illustrative runtime secret loading
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_signing_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secret_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signing key unavailable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;

&lt;span class="c1"&gt;# Load keys from a managed store at runtime. Never copy key material
# into source, build arguments, container layers, or sample configuration.
# Keep readiness false until the signing dependency is ready.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env
*.pem
__pycache__/
*.pyc
.git/
node_modules/
tests/fixtures/secrets/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example Dockerfile pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.12-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pyproject.toml poetry.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; poetry &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; poetry &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-root&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=10001:10001 app ./app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 10001&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime dependency vs build independence:&lt;/strong&gt; Secret store clients add runtime requirements. The gain is eliminating baked-in risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotation complexity vs safety:&lt;/strong&gt; Overlapping keys complicate issuers and verifiers. The gain is smooth migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict readiness vs availability:&lt;/strong&gt; Refusing to start without keys prevents undefined behavior. Availability dips are better than silent mis-signing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“COPY .”:&lt;/strong&gt; Blindly copying the entire source tree into the image. Fix with &lt;code&gt;.dockerignore&lt;/code&gt; and explicit &lt;code&gt;COPY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Fallback key”:&lt;/strong&gt; Using a dummy key in production when secret load fails. Fix with hard failures and improved observability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Exportable private keys”:&lt;/strong&gt; Allowing the app to export private keys. Fix with hardware-backed or provider-managed keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;.dockerignore&lt;/code&gt; entries for secrets and verify your build context (run &lt;code&gt;docker build --no-cache&lt;/code&gt; and inspect the logs).&lt;/li&gt;
&lt;li&gt;Use strict readiness that fails until keys load from a managed store. Test rotation: accept signatures from two keys during an overlap window.&lt;/li&gt;
&lt;li&gt;Add CI scans that fail on secret-like files in images. Track and eliminate false positives carefully.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/verified-id/verifiable-credentials-configure-tenant" rel="noreferrer noopener"&gt;Configure Verified ID with Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/manage-secrets" rel="noreferrer noopener"&gt;Azure Container Apps secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/build/concepts/context/" rel="noreferrer noopener"&gt;Docker build context and .dockerignore&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>azure</category>
      <category>identity</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Deploying Identity Services Without a Server to Babysit</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Thu, 25 Jun 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/deploying-identity-services-without-a-server-to-babysit-3ba</link>
      <guid>https://dev.to/imshubh/deploying-identity-services-without-a-server-to-babysit-3ba</guid>
      <description>&lt;p&gt;Running identity-adjacent services on a scale-to-zero container platform can be delightful: no server to babysit, external HTTPS ingress, and idle billing that doesn’t punish quiet periods. With scale-to-zero containers, quiet periods can reduce idle usage. The trade-off is that the first request after an idle period may be slower, so measure that cold path against the latency needs of the service.&lt;/p&gt;

&lt;p&gt;Identity workloads, however, bring constraints: some callers expect predictable latency, and some flows require warm caches or keys ready in memory. The trick is deciding where scale-to-zero helps and where “always on” wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;You deploy a credential verifier that scales to zero. The first request after an idle period may be much slower than warm requests. A downstream system times out, retries, and amplifies load just as the platform is warming. Worse, your verifier loads keys and trust lists at startup, so the warming step is non-trivial. You saved on idle billing but created unpredictable tails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cold start budget: measure and design around it
&lt;/h3&gt;

&lt;p&gt;Measure cold starts: container pull, runtime initialization, key load, trust list load, and framework boot. If your end-to-end verification flow includes out-of-process checks (e.g., privacy-preserving revocation lookups), include those too. Decide whether your SLA tolerates the tails; if not, keep a minimum warm replica.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warm critical paths with lazy loading and caches
&lt;/h3&gt;

&lt;p&gt;Avoid monolithic startup. Load credential-signing keys via a managed secret store, but defer non-critical data until requested. Cache trust lists in memory and refresh asynchronously. Keep proof verification code hot by executing a tiny “self-check” during startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set min replicas for latency-sensitive endpoints
&lt;/h3&gt;

&lt;p&gt;If your platform supports minimum replicas, keep one verifier instance warm. Scale additional instances on demand. This hybrid approach preserves predictable latency while retaining cost controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Health, readiness, and the “thin ready” trap
&lt;/h3&gt;

&lt;p&gt;Identity verifiers shouldn’t mark “ready” until critical dependencies (keys, trust lists) are present and a test proof passes. A “thin ready” (bare process up) can route traffic to a half-initialized instance, increasing failure rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale rules: event triggers vs queue length
&lt;/h3&gt;

&lt;p&gt;If you use event-driven scale, ensure triggers reflect verifier load. Queue length or request rate are better signals than raw CPU in a verifier, since cryptographic checks are sporadic and short. Use conservative ramp-up to avoid stampedes during traffic bursts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Idle billing: cost vs safety
&lt;/h3&gt;

&lt;p&gt;Scale-to-zero saves money during periods of inactivity. In identity systems, weigh this against the cost of false timeouts and retries. For certain flows (e.g., login or checkout proofs), unpredictable tails are more expensive than idle replicas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline hygiene: small image, quick pull
&lt;/h3&gt;

&lt;p&gt;Use multi-stage builds, slim bases, and &lt;code&gt;.dockerignore&lt;/code&gt; to keep images small. The smaller the image, the faster the cold start. Avoid compiling dependencies at startup; freeze them in the image. Keep secrets out of layers entirely (next article).&lt;/p&gt;

&lt;h3&gt;
  
  
  Illustrative configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Generic container-app.yaml structure&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;identity-service&lt;/span&gt;
&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
&lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-registry/identity-service:tag&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.5Gi&lt;/span&gt;
  &lt;span class="na"&gt;probes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;liveness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/health&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8000&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;readiness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/ready&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8000&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# Keep one warm replica&lt;/span&gt;
    &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http-requests&lt;/span&gt;
        &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;concurrentRequests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost vs predictability:&lt;/strong&gt; Scale-to-zero cuts idle spend but risks unpredictable tails. Hybrid min replicas often balance both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict readiness vs fast green:&lt;/strong&gt; Stricter readiness delays routing but avoids half-initialized failures. Bias toward strict in identity contexts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy load vs startup completeness:&lt;/strong&gt; Deferring non-critical data speeds boot but adds first-request work. Choose wisely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Thin ready”:&lt;/strong&gt; Traffic routed to instances without loaded keys or trust lists. Fix readiness to validate critical paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Stampede”:&lt;/strong&gt; A traffic surge triggers too many replicas and cache thrash. Fix scale rules and backpressure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Oversized image”:&lt;/strong&gt; Cold starts dominated by image pulls. Fix Docker hygiene.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Measure the cold start path and identify high-cost steps. Record cold-start and warm-request latency as separate measurements.&lt;/li&gt;
&lt;li&gt;Add a minimum warm replica and track tails. Compare p95 across scale-to-zero vs hybrid.&lt;/li&gt;
&lt;li&gt;Implement readiness that runs a tiny proof self-check and confirms secret load before returning ready.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/scale-app" rel="noreferrer noopener"&gt;Scale applications in Azure Container Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/health-probes" rel="noreferrer noopener"&gt;Health probes in Azure Container Apps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>serverless</category>
      <category>identity</category>
      <category>containers</category>
    </item>
    <item>
      <title>Null Isn’t False: Building Fair Credential Verifiers</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Sat, 30 May 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/null-isnt-false-building-fair-credential-verifiers-2mp3</link>
      <guid>https://dev.to/imshubh/null-isnt-false-building-fair-credential-verifiers-2mp3</guid>
      <description>&lt;p&gt;One of the subtle but damaging patterns in identity systems is treating null as false. If a credential lacks a field, or a proof is absent, some code paths flip a boolean to false and move on. In verification, this collapses “we don’t know” into “disproven.” The difference matters: missing evidence isn’t proof of falsehood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;A verifier expects a &lt;code&gt;proofOfX&lt;/code&gt; field. The credential arrives without it. The code sets &lt;code&gt;proofOfX=false&lt;/code&gt;. A downstream gate denies access. Later, it turns out &lt;code&gt;proofOfX&lt;/code&gt; wasn’t required for this flow, or the holder didn’t have a way to request it yet. The deny was based on an absence, not a disproven claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Model states, not booleans
&lt;/h3&gt;

&lt;p&gt;Replace booleans with intent-rich types. For a given proof, consider states like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Present:&lt;/strong&gt; The evidence arrived and checks out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing:&lt;/strong&gt; The evidence was not provided.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expired:&lt;/strong&gt; The evidence was valid but is past its lifetime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revoked:&lt;/strong&gt; The issuer withdrew the credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unverifiable:&lt;/strong&gt; The evidence can’t be checked (e.g., failed binding).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalid:&lt;/strong&gt; The evidence is malformed or has a bad signature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This simple taxonomy avoids flattening all “not present” into “false.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Exhaustive decision logic at boundaries
&lt;/h3&gt;

&lt;p&gt;At the verifier boundary, use exhaustive switches over the state. Force downstream callers to handle each branch. If your language supports pattern matching, lean on it to eliminate default fall-through behaviors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat absence as an opportunity to request or fall back
&lt;/h3&gt;

&lt;p&gt;If a required field is missing, decide whether to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt the holder to supply it (if applicable).&lt;/li&gt;
&lt;li&gt;Proceed with reduced assurance and log the state.&lt;/li&gt;
&lt;li&gt;Deny the request explicitly as “unverifiable,” not “invalid.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The right action depends on your policy and context; the key is keeping “unverifiable” distinct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make “invalid” a narrow, technical category
&lt;/h3&gt;

&lt;p&gt;Invalid should mean “structured wrong,” “bad signature,” or “contradiction.” It shouldn’t include “not provided,” “not bound,” or “not present yet.” Narrow “invalid” ensures you don’t over-penalize users for the system’s constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Propagate state through APIs and UX
&lt;/h3&gt;

&lt;p&gt;Expose the state in API responses: include &lt;code&gt;status: "unverifiable"&lt;/code&gt; with a short note. Avoid returning &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt; as a sentinel for “missing.” In UX, explain that more evidence is needed or that a different path is available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beware implicit type coercions
&lt;/h3&gt;

&lt;p&gt;JSON, databases, and ORMs often coerce nulls to zero or false. Audit these boundaries. Ensure your serializers and persistence layers preserve state labels rather than defaulting values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test for fairness: counterfactuals
&lt;/h3&gt;

&lt;p&gt;Put fairness into tests. For example: if &lt;code&gt;proofOfX&lt;/code&gt; is missing, does the system deny or ask for more evidence? Are there alternative flows? Keep tests that assert the difference between “unverifiable” and “invalid.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicit verification outcomes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;

&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accepted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unverifiable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FieldCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;
    &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More state complexity vs simplicity:&lt;/strong&gt; Explicit states add a little code. The clarity and fairness gains are worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UX friction vs silent denial:&lt;/strong&gt; Asking for more evidence can add steps. Silent denials are worse. Balance friction with guidance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging granularity vs privacy:&lt;/strong&gt; Notes improve debugging; ensure they contain no sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Hidden coercion”:&lt;/strong&gt; Tooling collapses null to false. Solution: audit boundaries and enforce state labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Catch-all invalid”:&lt;/strong&gt; Development shortcuts treat everything negative as invalid. Solution: write exhaustive switches and linters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Unverifiable loop”:&lt;/strong&gt; Repeated prompts for evidence without alternative paths. Solution: define fallback policies clearly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Replace boolean checks for critical credential fields with stateful outcomes (“accepted,” “unverifiable,” “invalid”).&lt;/li&gt;
&lt;li&gt;Expose the state in your verifier’s API. Track how often “unverifiable” occurs and address UX with prompts or fallbacks.&lt;/li&gt;
&lt;li&gt;Add tests that assert denials only occur for “invalid,” not “unverifiable.”&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/vc-data-model-2.0/" rel="noreferrer noopener"&gt;W3C Verifiable Credentials Data Model v2.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/javase/21/language/pattern-matching-switch-expressions-and-statements.html" rel="noreferrer noopener"&gt;Java pattern matching for switch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>identity</category>
      <category>security</category>
      <category>microsoft</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Docker in CI: The Real Cost of Docker-in-Docker</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Sat, 23 May 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/docker-in-ci-the-real-cost-of-docker-in-docker-20fc</link>
      <guid>https://dev.to/imshubh/docker-in-ci-the-real-cost-of-docker-in-docker-20fc</guid>
      <description>&lt;p&gt;Docker-in-Docker (DinD) can make CI feel simple: start a job, build an image, run container-based tests. The convenience comes with a real isolation decision. Privileged DinD can weaken the boundary around the runner; mounting the host Docker socket can give a build step powerful control over the host daemon.&lt;/p&gt;

&lt;p&gt;Teams still choose these patterns because they support familiar workflows. The important thing is to make the choice explicit, restrict which code can use it, and avoid a “green” result when container tests never ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, know which daemon the job is using
&lt;/h2&gt;

&lt;p&gt;“Docker in CI” does not always mean DinD. Some runners expose a Docker daemon to the job; DinD starts a separate Docker daemon inside a service container. A mounted &lt;code&gt;/var/run/docker.sock&lt;/code&gt; is a third pattern: the job’s Docker CLI talks to the host daemon. These setups have different isolation properties. Document the runner architecture before relying on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the daemon before running tests
&lt;/h2&gt;

&lt;p&gt;Make readiness an explicit step. If the daemon is missing or unresponsive, stop the job before it reports container-dependent tests as successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run only on a runner configured to provide Docker access.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eu&lt;/span&gt;
docker version
docker info

&lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"aceu-trust-demo:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_SHA&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;local&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
docker build &lt;span class="nt"&gt;--tag&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; pytest &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an actual DinD service, the job also needs to wait for that service’s daemon to become available. A fixed sleep is brittle; use a bounded readiness loop and fail with a clear message if the daemon never responds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not silently skip container-dependent tests
&lt;/h2&gt;

&lt;p&gt;If the daemon is a prerequisite, the test should fail when Docker is unavailable. Avoid code that catches every exception and quietly returns a passing or skipped result in a required CI job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;docker_available&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeoutExpired&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_containerized_health_check_requires_docker&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;docker_available&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Docker daemon is required for this test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# Continue with the containerized test flow.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tag Docker-dependent tests so developers can distinguish them from pure unit tests, but keep them required in the CI path that claims to validate the container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the access pattern with eyes open
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privileged DinD:&lt;/strong&gt; Familiar and flexible, but grants elevated capabilities to the service container. Restrict it to trusted workflows and isolate the runner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host socket mount:&lt;/strong&gt; Avoids starting another daemon, but the mounted socket can let job code control the host Docker daemon. Treat access as highly privileged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daemonless or remote builders:&lt;/strong&gt; Can reduce direct daemon exposure in some environments, but require setup and may change the build workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In every case, pin build images, keep credentials scoped, avoid running untrusted pull-request code on privileged self-hosted runners, and clean up jobs so containers and volumes do not leak into later runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for isolation leaks and flaky runners
&lt;/h2&gt;

&lt;p&gt;Look for unexpected host mounts, files written outside the workspace, orphaned containers, and daemon failures. Runner fleets can have transient problems. A documented single retry can filter a brief infrastructure hiccup, but repeated failure should remain visible and investigated, not retried until a broken test turns green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs to discuss with the team
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed vs. isolation:&lt;/strong&gt; DinD is familiar; stricter alternatives can require more setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail closed vs. ergonomics:&lt;/strong&gt; Blocking on daemon failure catches false greens but can be frustrating when runner health is poor. Give contributors a clear error and retry path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socket mount vs. privileged mode:&lt;/strong&gt; Neither is a free isolation boundary. Limit trusted code and runner scope for either design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it in your CI
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write down whether jobs use host Docker, a mounted socket, or a separate DinD service.&lt;/li&gt;
&lt;li&gt;Add a daemon readiness check before any container-dependent test.&lt;/li&gt;
&lt;li&gt;Make those tests fail if Docker is unavailable; do not silently skip them.&lt;/li&gt;
&lt;li&gt;Evaluate a daemonless or remote builder against your runner threat model.&lt;/li&gt;
&lt;li&gt;Document when one retry is appropriate and when repeated failure needs investigation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;DinD can be pragmatic, but it should never be invisible. Verify the daemon, make tests fail closed, and choose an isolation pattern that matches what the CI job is trusted to do.&lt;/p&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/security/protect-access/" rel="noreferrer noopener"&gt;Protect access to the Docker daemon socket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/security/" rel="noreferrer noopener"&gt;Docker Engine security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/reference/security/secure-use" rel="noreferrer noopener"&gt;GitHub Actions secure-use guidance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>cicd</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Flaky Tests Are a Concurrency Bug: Fixing Shared State Under Parallel Execution</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Fri, 15 May 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/flaky-tests-are-a-concurrency-bug-fixing-shared-state-under-parallel-execution-2500</link>
      <guid>https://dev.to/imshubh/flaky-tests-are-a-concurrency-bug-fixing-shared-state-under-parallel-execution-2500</guid>
      <description>&lt;p&gt;I’ve watched teams chase “test runner bugs” while the real culprit was design. Flakiness is often a concurrency issue: shared mutable state under parallel execution. JUnit 5 can run tests concurrently, so fixtures that share mutable state can make failures appear intermittent. Creating isolated data per test makes the behavior easier to reproduce and diagnose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;You enable parallel test execution and see intermittent failures: “record not found” or “unexpected duplicate.” You re-run and it passes. Blaming the test runner is easy. The problem is shared state: two tests reading or writing the same fixture or relying on an implicit order.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Default to per-test fixtures
&lt;/h3&gt;

&lt;p&gt;Use per-test initialization to avoid shared mutable state. In JUnit 5, keep your test instance lifecycle at &lt;code&gt;PER_METHOD&lt;/code&gt; and recreate fixtures for each test. If the fixture is expensive, consider a template with parameterization that still isolates state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@TestInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestInstance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Lifecycle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PER_METHOD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EvidenceServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;InMemoryStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EvidenceService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@BeforeEach&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InMemoryStore&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// new store per test&lt;/span&gt;
        &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EvidenceService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;seedProfile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"alice"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syntheticProfileAlice&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;returnsMissingWhenTimeout&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;returnsUnverifiableOnExceptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Avoid static mutable fields, especially in helpers
&lt;/h3&gt;

&lt;p&gt;Static caches and helpers make tests faster locally but brittle in parallel. If you truly need a cache, scope it per test, or guard it with thread-safe constructs and clear it explicitly. Better: don’t cache at all in tests unless you benchmarked the need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make isolation visible with name-bound fixtures
&lt;/h3&gt;

&lt;p&gt;If a test seeds data, bind it to a unique key per test. Use random UUIDs or test names in keys to prevent accidental cross-test reuse. With Testcontainers, create per-test containers when state isolation is critical; share containers only for read-only tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel execution deserves explicit design
&lt;/h3&gt;

&lt;p&gt;JUnit 5 lets you run tests in parallel. That doesn’t mean every test should be parallel. Annotate classes that require single-thread execution, but treat that as a last resort. Prefer making tests parallel-safe by design.&lt;/p&gt;

&lt;h3&gt;
  
  
  JUnit configuration (illustrative)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;junit.jupiter.execution.parallel.enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;junit.jupiter.execution.parallel.mode.default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;concurrent&lt;/span&gt;
&lt;span class="py"&gt;junit.jupiter.execution.parallel.mode.classes.default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;concurrent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deterministic time and waiting
&lt;/h3&gt;

&lt;p&gt;Tests that sleep and “hope the system is ready” fail under parallel runs. Replace sleeps with explicit synchronization: await a condition, poll with a small backoff, or use latches. Keep timeouts small and deterministic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embrace “given-when-then” with isolated builders
&lt;/h3&gt;

&lt;p&gt;Build input data per test with dedicated builders. Don’t reuse a mutable “default profile” object across tests; copy or reconstruct it. Records help: immutable inputs prevent accidental mutation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnose with logging and seeds
&lt;/h3&gt;

&lt;p&gt;When a test fails intermittently, print seeds and keys that identify data. If you see the same key in two tests, you found shared state. If you see non-deterministic ordering in a collection, sort inputs or assert set equivalence rather than list order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-test container with unique data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StoreTests&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@BeforeEach&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"postgres:alpine"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withDatabaseName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test_"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-only"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@AfterEach&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation vs speed:&lt;/strong&gt; Per-test fixtures are slower than shared ones. Optimize later: start with correct behavior and only share read-only fixtures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallelism vs determinism:&lt;/strong&gt; Parallel runs surface concurrency bugs but may require more careful test design. The benefits outweigh the cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container lifecycle vs developer ergonomics:&lt;/strong&gt; Per-test containers are robust but slower. Consider class-level containers for read-only tests with clear resets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Hidden globals”:&lt;/strong&gt; A helper modifies a static map. Under parallel execution, tests interleave and fail intermittently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Order assumptions”:&lt;/strong&gt; Tests rely on iteration order of sets or maps; parallel runs expose different orders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Implicit sleeps”:&lt;/strong&gt; Tests use fixed sleeps; faster or slower environments cause out-of-order expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Switch to the &lt;code&gt;PER_METHOD&lt;/code&gt; lifecycle and remove static mutable fields in tests. Add unique keys per test to seed data. Enable parallel execution and watch for failures that indicate hidden shared state.&lt;/li&gt;
&lt;li&gt;Replace sleep-based readiness with explicit condition checks. Add timeouts and backoff policies.&lt;/li&gt;
&lt;li&gt;Document which tests are parallel-safe and why; make non-parallel tests the exception and note the reason.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution" rel="noreferrer noopener"&gt;JUnit 5 parallel execution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://java.testcontainers.org/" rel="noreferrer noopener"&gt;Testcontainers for Java&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>testing</category>
      <category>concurrency</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>CPU-Only, By Design: Building an Edge-AI Service Without Assuming Accelerators</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Fri, 15 May 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/cpu-only-by-design-building-an-edge-ai-service-without-assuming-accelerators-3bl4</link>
      <guid>https://dev.to/imshubh/cpu-only-by-design-building-an-edge-ai-service-without-assuming-accelerators-3bl4</guid>
      <description>&lt;p&gt;Not every deployment target has a dedicated accelerator. In many environments, the only safe assumption is a general-purpose CPU. Treating that as a deliberate design constraint makes the service easier to deploy, explain, and operate.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aceu-edge-infer&lt;/code&gt; is a small CPU-first inference demo with a documented OpenVINO runtime path. Its ACE-U outputs are illustrative, not a validated assessment of people. The design lesson is broader: state the hardware contract and make the service’s behavior when that contract is not met explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document the hardware contract
&lt;/h2&gt;

&lt;p&gt;Say which device is required and what is optional. If CPU-only execution is supported, describe the expected model format, available runtime, resource needs, and what performance behavior users should plan for. If an accelerator is optional, list supported devices and the path selected when one is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make device selection explicit
&lt;/h2&gt;

&lt;p&gt;For an OpenVINO model intended for CPU execution, specify the device in the compile call rather than relying on a default selection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openvino&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ov&lt;/span&gt;

&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ov&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/aceu_ir.xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;compiled_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the exact runtime package and operating system you plan to ship. If startup cannot load the model or compile it for CPU, do not report the service as ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distinguish a fallback from a failure
&lt;/h2&gt;

&lt;p&gt;A fallback is a real, tested alternative execution path. It may be a different model artifact, a portable runtime, or a slower implementation that produces valid outputs. If none exists, the honest response to a missing runtime is a clear startup failure or an unavailable response, not placeholder scores or a “fallback” label that hides the problem.&lt;/p&gt;

&lt;p&gt;Expose execution metadata that helps operators understand the path without leaking environment details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;execution_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;precision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INT8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback_used&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real service, derive these values from the compiled model and request path; do not hard-code them when the runtime can choose a different path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Degrade predictably on constrained hardware
&lt;/h2&gt;

&lt;p&gt;Assume memory and CPU shares may be limited. Measure the model’s memory footprint and end-to-end latency on the target class of device. Start with conservative concurrency; more threads do not automatically mean better p95 latency. If the service supports it, adjust batch size, request timeout, or concurrency by deployment profile, and document the effect.&lt;/p&gt;

&lt;p&gt;Do not silently lower quality when the machine is under pressure. If a request is rejected, deferred, or routed to a different precision, make the behavior observable and keep the response contract clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate startup, readiness, and liveness
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Startup:&lt;/strong&gt; Load the model and confirm the selected CPU runtime can compile it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness:&lt;/strong&gt; Confirm the service can complete the critical inference path before it receives traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liveness:&lt;/strong&gt; Use a cheap check for a process that is stuck, not an expensive inference on every probe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A stricter readiness check may delay the first “ready” signal. That is preferable to routing requests to a service that has not loaded its model. Keep checks bounded so probes do not become a source of load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep operational assumptions beside the code
&lt;/h2&gt;

&lt;p&gt;Add a “Hardware assumptions” section to the README and link it from deployment instructions. Include supported devices, runtime versions, precision choices, resource settings, fallback behavior, health endpoints, and a short way to verify the selected execution mode. The next engineer should not have to infer these from source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU portability vs. latency:&lt;/strong&gt; A CPU path broadens where the service can run, but may not meet a workload’s latency target. Measure before promising an SLA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional accelerators vs. maintenance:&lt;/strong&gt; Each extra path adds test and support cost; add only devices the product needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict readiness vs. quick deployment:&lt;/strong&gt; Waiting for a successful model load delays traffic, but avoids a false-green service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assumed hardware:&lt;/strong&gt; The service expects a device that is not present.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight retry loops:&lt;/strong&gt; Device selection is retried repeatedly instead of failing clearly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undocumented requirements:&lt;/strong&gt; Hardware needs live only in someone’s head or in the source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Provide a clear failure state and a controlled recovery path instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the CPU and optional accelerator requirements in your README.&lt;/li&gt;
&lt;li&gt;Expose the actual device and precision selected for each execution path.&lt;/li&gt;
&lt;li&gt;Simulate a missing optimized runtime and verify that the service fails clearly or uses a genuine tested fallback.&lt;/li&gt;
&lt;li&gt;Measure memory and latency under representative CPU limits before selecting concurrency defaults.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Designing for CPU-only execution is not a claim that CPU is always enough. It is a way to make assumptions visible, provide a credible fallback where one exists, and keep the service predictable on the hardware it supports.&lt;/p&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.openvino.ai/2026/openvino-workflow/running-inference/inference-devices-and-modes/cpu-device.html" rel="noreferrer noopener"&gt;OpenVINO CPU device documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openvino.ai/2026/openvino-workflow/running-inference/optimize-inference.html" rel="noreferrer noopener"&gt;OpenVINO performance hints and inference optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openvino.ai/2026/openvino-workflow/model-preparation/convert-model-to-ir.html" rel="noreferrer noopener"&gt;OpenVINO model conversion and runtime loading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>edgeai</category>
      <category>ai</category>
      <category>cpu</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Virtual Threads Changed My Mental Model: Practical Lessons From I/O Fan-Out</title>
      <dc:creator>Shubham Anandani</dc:creator>
      <pubDate>Mon, 13 Apr 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/imshubh/virtual-threads-changed-my-mental-model-practical-lessons-from-io-fan-out-5b8</link>
      <guid>https://dev.to/imshubh/virtual-threads-changed-my-mental-model-practical-lessons-from-io-fan-out-5b8</guid>
      <description>&lt;p&gt;Virtual threads changed how I design I/O fan-out in Java services. As an engineering manager who grew up with fixed thread pools, queue sizing, and saturation metrics, I had muscle memory: “pick a pool size, tune it, protect it.” With virtual threads, I can treat blocking I/O more like straightforward code again, without tying throughput to a small pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;A caller triggers N downstream calls per request (fan-out). With a fixed pool, saturation appears quickly under spikes; with virtual threads, spikes are absorbed but not eliminated. If you translate “we can spawn thousands of threads” into “we don’t need back pressure,” you’ll rediscover failures, just in different places.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Think “task-per-thread” for blocking I/O, not “pool-per-service”
&lt;/h3&gt;

&lt;p&gt;Virtual threads make “one virtual thread per blocking task” feasible. You can write linear code for a downstream call and not contort it into callbacks just to avoid blocking. That simplifies failure handling and resource cleanup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example fan-out
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Callable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Callable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="nl"&gt;source:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fetchEvidence&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads like blocking I/O because it is, but with virtual threads, the scheduler multiplexes them efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back pressure still matters
&lt;/h3&gt;

&lt;p&gt;If downstream services or data sources choke under spikes, virtual threads won’t magically fix it. The caller can spin up many virtual threads, but the destination still has limits. You need timeouts, rate limits, and circuit breakers. You also need to avoid accidental fan-in (e.g., mass aggregation that blocks on slowest responders indefinitely).&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU-bound work belongs elsewhere
&lt;/h3&gt;

&lt;p&gt;Virtual threads help with blocking I/O, not CPU-heavy tasks. If a task saturates CPU, the scheduler can’t help. You need to bound parallelism with a platform-appropriate mechanism (e.g., a limited executor or structured concurrency approach) to avoid oversubscription.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure semantics are simpler when code is linear
&lt;/h3&gt;

&lt;p&gt;Exception handling in “straight-line” code is clearer than in callback-based designs. You still need to decide whether partial evidence returns a partial result (with Missing/Unverifiable states) or you fail the whole request. Virtual threads don’t make that decision; they just make the code easier to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  “Thousands of threads” doesn’t mean “no limits”
&lt;/h3&gt;

&lt;p&gt;Virtual threads are cheap, but not free. Sockets, file descriptors, and memory buffers are finite. The OS still enforces resource ceilings. Guard your fan-out with sensible limits: per-request caps, per-destination timeouts, and global safety checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability shifts upstream
&lt;/h3&gt;

&lt;p&gt;With fixed pools, saturation appears as queue wait time. With virtual threads, saturation shifts to downstream latency and timeouts. Make sure your metrics capture per-destination latency percentiles and error rates. A spike in virtual-thread creation can be fine if destinations are healthy; it’s not fine if percentiles explode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test under synthetic spike and tail latency
&lt;/h3&gt;

&lt;p&gt;I haven’t yet measured performance between virtual threads and a fixed pool for the demo. What I do measure is behavior under artificial spikes: do timeouts fire? Does the aggregation return timely partial results with Missing/Unverifiable? Tests simulate slow destinations and verify we don’t hang.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeout per downstream call
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;source:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMillis&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MILLISECONDS&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TimeoutException&lt;/span&gt; &lt;span class="n"&gt;timeoutError&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EvidenceResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unavailable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"timeout"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs and failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity vs explicit limits:&lt;/strong&gt; Virtual threads reduce boilerplate, but you still must implement back pressure. Silent defaults can mask saturation until it’s too late.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Everything virtual” vs CPU realities:&lt;/strong&gt; Virtual threads help with I/O. For CPU-bound work, a bounded executor or workload partitioning is safer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability differences:&lt;/strong&gt; You lose fixed-pool queue metrics and gain per-destination latency metrics. Update dashboards and alerts accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Failure modes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Soft hangs”:&lt;/strong&gt; Aggregation waits on the slowest responder forever. Add timeouts and partial-return logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Resource leaks”:&lt;/strong&gt; Thousands of virtual threads keep sockets open longer than intended. Validate resource cleanup paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Oversubscription”:&lt;/strong&gt; CPU-heavy tasks in virtual threads degrade the whole process. Bound CPU parallelism explicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Replace a fixed thread pool fan-out with &lt;code&gt;newVirtualThreadPerTaskExecutor()&lt;/code&gt; and add per-destination timeouts. Observe behavior under synthetic spikes.&lt;/li&gt;
&lt;li&gt;Add aggregation logic that treats timeouts as Missing and downstream errors as Unverifiable. Verify exhaustive handling with pattern matching.&lt;/li&gt;
&lt;li&gt;Measure throughput and p95 latency in a controlled environment to inform whether virtual threads help your workload.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/444" rel="noreferrer noopener"&gt;Virtual Threads (JEP 444)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ExecutorService.html" rel="noreferrer noopener"&gt;Java 21 ExecutorService API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>concurrency</category>
      <category>performance</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
