<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Hands On FullStack Development]]></title><description><![CDATA[Hands On FullStack Development with "Infrastructure Management" Product implementation]]></description><link>https://fullstackinfra.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!Y3IK!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3505e4c-4513-4325-9c7c-20715b7d8910_1079x1079.png</url><title>Hands On FullStack Development</title><link>https://fullstackinfra.substack.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 27 Jul 2026 16:56:20 GMT</lastBuildDate><atom:link href="https://fullstackinfra.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[System Design Roadmap]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[fullstackinfra@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[fullstackinfra@substack.com]]></itunes:email><itunes:name><![CDATA[System Design Roadmap]]></itunes:name></itunes:owner><itunes:author><![CDATA[System Design Roadmap]]></itunes:author><googleplay:owner><![CDATA[fullstackinfra@substack.com]]></googleplay:owner><googleplay:email><![CDATA[fullstackinfra@substack.com]]></googleplay:email><googleplay:author><![CDATA[System Design Roadmap]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Day 138: Performance Testing]]></title><description><![CDATA[Load &#183; Stress &#183; Endurance &#183; Spike &#183; Capacity]]></description><link>https://fullstackinfra.substack.com/p/day-138-performance-testing</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-138-performance-testing</guid><dc:creator><![CDATA[systemdr19]]></dc:creator><pubDate>Mon, 27 Jul 2026 06:30:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JCbm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Load &#183; Stress &#183; Endurance &#183; Spike &#183; Capacity</h2><div><hr></div><h2>What We&#8217;re Building Today</h2><p>By the end of this lesson you will have a working, production-grade performance testing platform &#8212; not a toy, not a demo with mock numbers. Here is what gets built:</p><ul><li><p>A Locust-powered test suite with five distinct test profiles, each answering a different question about your system</p></li><li><p>A FastAPI backend that orchestrates test runs, polls live metrics every two seconds, computes latency percentiles, evaluates SLO thresholds, and streams everything over WebSocket</p></li><li><p>A React dashboard &#8212; styled after tools like Grafana and k6 Cloud &#8212; that shows live RPS charts, P50/P95/P99 latency curves, error rates, and a real-time SLO pass/fail verdict</p></li><li><p>A SQLite store for run history so you can compare test results across deployments</p></li></ul><p>The full project runs with a single command, with or without Docker.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JCbm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JCbm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 424w, https://substackcdn.com/image/fetch/$s_!JCbm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 848w, https://substackcdn.com/image/fetch/$s_!JCbm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 1272w, https://substackcdn.com/image/fetch/$s_!JCbm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JCbm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png" width="1456" height="900" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:900,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JCbm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 424w, https://substackcdn.com/image/fetch/$s_!JCbm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 848w, https://substackcdn.com/image/fetch/$s_!JCbm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 1272w, https://substackcdn.com/image/fetch/$s_!JCbm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F57baad34-375a-4502-91bb-5940bee94d09_5939x3671.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"></div></div></a></figure></div><p></p>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-138-performance-testing">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 137 - Security Testing — Automate Before the Attacker Does]]></title><description><![CDATA[What we will build today]]></description><link>https://fullstackinfra.substack.com/p/day-137-security-testing-automate</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-137-security-testing-automate</guid><dc:creator><![CDATA[ctoi]]></dc:creator><pubDate>Sat, 25 Jul 2026 06:31:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-tfX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><span>What we will build today</span></h2><p><span>- A Flask API protected by JWT, role checks, rate limiting, and security headers</span></p><p><span>- One scan pipeline combining SAST, DAST, dependency, and compliance checks</span></p><p><span>- A React dashboard that refreshes from every real scan</span></p><p><span>- Repeatable local and Docker workflows with automated verification</span></p><p><span>This closes Week 20&#8217;s testing loop: earlier tests asked whether features work; today we ask whether an attacker can make them work </span><strong><span>**against us**</span></strong><span>.</span></p><h2><span> The core idea: evidence, not a green badge</span></h2><p><span>A security scanner is an evidence pipeline. Source code, dependencies, and a running API are different evidence sources, so one tool cannot honestly cover all three. Bandit inspects Python syntax, `pip-audit` checks published advisories, and DAST sends hostile HTTP requests to the live service.</span></p><p><span>The non-obvious lesson is that &#8220;zero findings&#8221; is not automatically good. It may mean the scanner could not reach its target, parsed no output, or ran with the wrong configuration. Production systems record scan type, timestamp, tool, severity, evidence, and failures so operators can distinguish &#8220;clean&#8221; from &#8220;not measured.&#8221;</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-tfX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-tfX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 424w, https://substackcdn.com/image/fetch/$s_!-tfX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 848w, https://substackcdn.com/image/fetch/$s_!-tfX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 1272w, https://substackcdn.com/image/fetch/$s_!-tfX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-tfX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png" width="604" height="362.5659340659341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:874,&quot;width&quot;:1456,&quot;resizeWidth&quot;:604,&quot;bytes&quot;:628579,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/207510847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-tfX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 424w, https://substackcdn.com/image/fetch/$s_!-tfX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 848w, https://substackcdn.com/image/fetch/$s_!-tfX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 1272w, https://substackcdn.com/image/fetch/$s_!-tfX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf021e4a-77bb-4f73-b0c9-650becc62861_4800x2880.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-137-security-testing-automate">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 136 — Quality Metrics: Building the Nervous System of Your Codebase]]></title><description><![CDATA[What You Will Build Today]]></description><link>https://fullstackinfra.substack.com/p/day-136-quality-metrics-building</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-136-quality-metrics-building</guid><dc:creator><![CDATA[systemdr19]]></dc:creator><pubDate>Thu, 23 Jul 2026 06:30:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!lOBc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What You Will Build Today</h2><ul><li><p><strong>Quality Gate engine</strong> &#8212; enforces pass/fail thresholds before deployments</p></li><li><p><strong>Code quality metrics</strong> &#8212; static analysis for complexity, duplication, and coverage</p></li><li><p><strong>Technical debt tracker</strong> &#8212; converts violations to engineer-hours and trends over time</p></li><li><p><strong>Performance benchmark harness</strong> &#8212; catches latency regressions automatically</p></li><li><p><strong>Quality Dashboard</strong> &#8212; a SonarQube-inspired React UI showing all metrics in real-time</p></li></ul><div><hr></div><h2>Where This Fits in the Overall System</h2><pre><code><code>[Source Code] &#9472;&#9472;&gt; [Test Automation Pipeline (Day 135)]
                               |
                               v
                  [Quality Gate Engine]  &lt;&#9472;&#9472; TODAY
                               |
           +&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;+&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;+
           v               v              v
   [Code Metrics]  [Debt Tracker]  [Perf Benchmarks]
           +&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;+&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;+
                               |
                               v
                     [Quality Dashboard UI]
                               |
                               v
                  [Deployment Decision: PASS / FAIL]
</code></code></pre><p>This module sits between CI and deployment &#8212; it is the enforcer. Tomorrow (Day 137) we add security testing on top of this gate.</p><div><hr></div><h2>Component Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lOBc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lOBc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 424w, https://substackcdn.com/image/fetch/$s_!lOBc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 848w, https://substackcdn.com/image/fetch/$s_!lOBc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 1272w, https://substackcdn.com/image/fetch/$s_!lOBc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lOBc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png" width="1456" height="1113" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1113,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lOBc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 424w, https://substackcdn.com/image/fetch/$s_!lOBc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 848w, https://substackcdn.com/image/fetch/$s_!lOBc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 1272w, https://substackcdn.com/image/fetch/$s_!lOBc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9e904d1-572d-4657-a7c4-81e65ae445c1_3671x2807.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-136-quality-metrics-building">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[ Day 135 — Test Automation Pipeline]]></title><description><![CDATA[What You Will Build Today]]></description><link>https://fullstackinfra.substack.com/p/day-135-test-automation-pipeline</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-135-test-automation-pipeline</guid><dc:creator><![CDATA[systemdr19]]></dc:creator><pubDate>Tue, 21 Jul 2026 06:30:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7j32!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What You Will Build Today</h2><ul><li><p>A CI/CD-ready test automation pipeline &#8212; one orchestrator that drives every test layer in the right order</p></li><li><p>A parallel test execution engine &#8212; three suites running simultaneously, cutting wall-clock time by 60&#8211;70%</p></li><li><p>A centralised reporting dashboard &#8212; real-time pass/fail/skip counters, duration trends, and per-test failure detail</p></li><li><p>A test data management layer &#8212; deterministic fixture factories, SQLite snapshots, isolated teardown</p></li><li><p>A continuous testing loop &#8212; a file-watcher that re-runs only the tests affected by your last save</p></li></ul><div><hr></div><h2>Why Any of This Matters</h2><p>Netflix runs roughly 10,000 automated tests before a single commit reaches production. Google&#8217;s CI infrastructure runs more than 150 million test executions every day across hermetic sandboxes. The gap between a 12-minute test suite and a 2-hour one is almost never better hardware. It is pipeline architecture &#8212; what runs in parallel, what is cached, and what never runs at all when nothing relevant changed.</p><p>When a pipeline is built correctly, it becomes a living contract between the codebase and its expected behaviour. Break that contract and the entire team knows within minutes, not at 3 AM after a bad deployment.</p><div><hr></div><h2>Component Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7j32!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7j32!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 424w, https://substackcdn.com/image/fetch/$s_!7j32!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 848w, https://substackcdn.com/image/fetch/$s_!7j32!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 1272w, https://substackcdn.com/image/fetch/$s_!7j32!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7j32!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png" width="1456" height="901" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:901,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7j32!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 424w, https://substackcdn.com/image/fetch/$s_!7j32!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 848w, https://substackcdn.com/image/fetch/$s_!7j32!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 1272w, https://substackcdn.com/image/fetch/$s_!7j32!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b4c51d5-e7ee-4143-9a7f-b0558a93e4b3_5669x3509.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The pipeline is five cooperating layers:</p><p>Layer File What it does Orchestrator <code>pipeline.py</code> Reads config, resolves run order, fans work out to the thread pool Executor Pool <code>executor.py</code> One subprocess per suite; each gets its own SQLite schema Reporter <code>reporter.py</code> Parses JUnit XML, computes flake rates, writes <code>pipeline_results.json</code> Data Manager <code>data_manager.py</code> Seeded fixture factory, snapshot files, clean teardown per worker Watcher <code>watcher.py</code> <code>watchdog</code> FS events, 500 ms debounce, import-graph-based selection</p><p>The React frontend connects to a FastAPI server and receives live events over Server-Sent Events (SSE). There is no polling, no WebSocket handshake &#8212; just a plain HTTP stream that the browser reconnects to automatically if it drops.</p><div><hr></div><h2>Control Flow and Data Flow</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iJv0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iJv0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 424w, https://substackcdn.com/image/fetch/$s_!iJv0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 848w, https://substackcdn.com/image/fetch/$s_!iJv0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 1272w, https://substackcdn.com/image/fetch/$s_!iJv0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iJv0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png" width="1024" height="921" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:921,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:588919,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/201580185?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iJv0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 424w, https://substackcdn.com/image/fetch/$s_!iJv0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 848w, https://substackcdn.com/image/fetch/$s_!iJv0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 1272w, https://substackcdn.com/image/fetch/$s_!iJv0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1334a2a2-0506-4a18-a5dc-f3aac48d7ef6_1024x921.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p></p><p>Here is the full path from a source file change to a result on screen:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;003879f1-d8f8-4834-a540-f5f2dbf9a41e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Source Change
     |
     v
[ Watcher ]  -- debounce 500 ms --&gt;  [ Change Detector ]
                                              |
                                   [ Affected Test Map ]
                                    (reverse dep lookup)
                                              |
                                       [ Orchestrator ]
                                        (pipeline.py)
                                    /         |         \
                               Unit       Integration    E2E
                             Worker 0      Worker 1    Worker 2
                                    \         |         /
                                   [ Result Aggregator ]
                                              |
                                  [ JUnit XML  +  JSON ]
                                              |
                                   [ FastAPI SSE stream ]
                                              |
                                    [ React Dashboard ]
</code></pre></div><p>The orchestrator submits all three suites to a <code>ThreadPoolExecutor</code> simultaneously, then iterates <code>as_completed()</code> &#8212; it processes results in whichever order the workers finish, never blocking on the slowest one.</p><div><hr></div><h2>Parallel Execution &#8212; The Right Mental Model</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SyX-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SyX-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 424w, https://substackcdn.com/image/fetch/$s_!SyX-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 848w, https://substackcdn.com/image/fetch/$s_!SyX-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 1272w, https://substackcdn.com/image/fetch/$s_!SyX-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SyX-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png" width="1024" height="666" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2194187d-9c02-4256-907a-03bc86b82316_1024x666.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:666,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:426412,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/201580185?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SyX-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 424w, https://substackcdn.com/image/fetch/$s_!SyX-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 848w, https://substackcdn.com/image/fetch/$s_!SyX-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 1272w, https://substackcdn.com/image/fetch/$s_!SyX-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2194187d-9c02-4256-907a-03bc86b82316_1024x666.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Most engineers run <code>pytest -n auto</code> and consider the problem solved. The real issue is test isolation at the data layer. If two parallel workers both seed a <code>users</code> table with fixed IDs into the same database, they corrupt each other&#8217;s data midway through and produce failures that only occur when tests run simultaneously &#8212; the worst kind to debug.</p><p>The approach here gives each worker its own SQLite file, named by worker ID. The file is created at setup and deleted at teardown. Nothing leaks between runs.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;be321272-f008-4e80-97ea-0f3d5ecdcf56&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># pseudo-code &#8212; full implementation is in data_manager.py
class WorkerDataManager:
    def __init__(self, worker_id: int):
        self.db_path = reports_dir / f"worker_{worker_id}.db"

    def setup(self):
        # Start from a clean file every time
        if self.db_path.exists():
            self.db_path.unlink()
        self._run_migrations()
        self._seed_fixtures()

    def teardown(self):
        self._conn.close()
        self.db_path.unlink()   # no files left behind
</code></pre></div><p>This eliminates an entire class of timing-sensitive failures. Worker count scales with CPU cores. On CI you pin it to <code>nproc</code> or whatever the machine&#8217;s core count is.</p><div><hr></div><h2>Continuous Testing &#8212; Smart Selection</h2><p>Running all 500 tests on every keystroke is expensive and slow. The watcher uses import graph analysis &#8212; when <code>services/billing.py</code> changes, only tests whose import chain touches <code>billing</code> get re-run. This is how Jest&#8217;s <code>--watch</code> mode and the <code>pytest-testmon</code> plugin work internally.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;16b673b6-854c-4885-be97-dee643eba80c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"># pseudo-code
changed_modules = detect_changed_files(event)
affected_tests  = import_graph.reverse_deps(changed_modules)
runner.execute(affected_tests, mode="fast_feedback")
</code></pre></div><p>The full suite still runs on every <code>git push</code> through the CI gate. The watcher is your local feedback loop &#8212; under three seconds for a change in a leaf module, which keeps the flow state intact while you are working.</p><div><hr></div><h2>Test Reporting &#8212; What Good Looks Like</h2><p>Raw pass/fail counts are nearly useless. Production-grade reporting surfaces four things:</p><p><strong>Flake rate per test</strong> &#8212; the ratio of failures to total runs over the last N executions. Anything above 2% gets auto-quarantined. A test that fails one time in twenty is not a passing test.</p><p><strong>Duration trend</strong> &#8212; P50 and P95 duration over the last seven days. A test that was 200 ms last week and is now 1.8 s has a regression somewhere, and it will cause timeouts in CI before anyone notices.</p><p><strong>First-failure lines</strong> &#8212; pulled directly from the JUnit XML stacktrace. Nobody should have to click into a separate log viewer to see why a test failed.</p><p><strong>Coverage delta</strong> &#8212; did this run improve or regress line coverage? A green pipeline that quietly drops coverage from 84% to 61% is not a green pipeline.</p><p>The dashboard here produces exactly what CircleCI&#8217;s Test Insights tab and Buildkite&#8217;s test analytics show for their enterprise customers.</p><div><hr></div><h2>Test Data Management &#8212; Snapshots and Factories</h2><p>Test data breaks in two opposite directions. Tests that are too tightly coupled to specific row IDs fail whenever the seed data changes. Tests that use fully random data cannot reproduce the same failure twice.</p><p>The approach that splits the difference is a factory function seeded with a deterministic PRNG. The seed is derived from the test&#8217;s node ID &#8212; which means the same test always generates exactly the same fake data, and two different tests never collide.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1032ed3c-e3d1-46f5-b98a-1e5dcd85b5db&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># pseudo-code
class TestDataFactory:
    def user(self, **overrides):
        faker = Faker()
        faker.seed_instance(hash(self.test_node_id) % 2**32)
        base = {"name": faker.name(), "email": faker.email()}
        return {**base, **overrides}
</code></pre></div><p>Snapshot testing extends this one step further. After the first passing run, the factory serialises the generated dataset to <code>fixtures/snapshots/&lt;test_id&gt;.json</code>. On subsequent runs, if that file exists, it is replayed exactly &#8212; no randomness, maximum reproducibility, and no brittle hardcoded fixtures in your repository.</p><div><hr></div><h2>Project Structure</h2><h2>Github Link:</h2><p><a href="https://github.com/sysdr/infrawatch-fullstack-p/tree/main/day135/infra-dashboard">https://github.com/sysdr/infrawatch-fullstack-p/tree/main/day135/infra-dashboard</a></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;1359fb2b-74b7-49e3-9b6e-8bbf0f2c20a8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">infra-dashboard/
&#9500;&#9472;&#9472; backend/
&#9474;   &#9500;&#9472;&#9472; test_pipeline/
&#9474;   &#9474;   &#9500;&#9472;&#9472; pipeline.py          # orchestrator &#8212; fans out to worker pool
&#9474;   &#9474;   &#9500;&#9472;&#9472; executor.py          # subprocess runner per suite
&#9474;   &#9474;   &#9500;&#9472;&#9472; reporter.py          # JUnit XML &#8594; JSON aggregator + flake calc
&#9474;   &#9474;   &#9500;&#9472;&#9472; data_manager.py      # fixture factory + SQLite isolation
&#9474;   &#9474;   &#9492;&#9472;&#9472; watcher.py           # FS watcher &#8594; debounce &#8594; selective run
&#9474;   &#9500;&#9472;&#9472; api/
&#9474;   &#9474;   &#9492;&#9472;&#9472; pipeline_routes.py   # SSE /run, /results, /history endpoints
&#9474;   &#9500;&#9472;&#9472; tests/
&#9474;   &#9474;   &#9500;&#9472;&#9472; unit/
&#9474;   &#9474;   &#9500;&#9472;&#9472; integration/
&#9474;   &#9474;   &#9492;&#9472;&#9472; e2e/
&#9474;   &#9500;&#9472;&#9472; main.py
&#9474;   &#9492;&#9472;&#9472; conftest.py
&#9500;&#9472;&#9472; frontend/
&#9474;   &#9492;&#9472;&#9472; src/
&#9474;       &#9500;&#9472;&#9472; pages/TestDashboard.jsx
&#9474;       &#9492;&#9472;&#9472; api/pipelineApi.js
&#9500;&#9472;&#9472; start.sh
&#9500;&#9472;&#9472; stop.sh
&#9492;&#9472;&#9472; docker-compose.yml
</code></pre></div><div><hr></div><h2>Prerequisites</h2><p>Before running anything, confirm these are installed:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;e2c2f725-8ddc-48d2-a5be-4c9a106c9445&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">python3 --version    # Python 3.11 or 3.12
node --version       # v20.x
npm --version        # 10.x
docker --version     # optional, for containerised run
</code></pre></div><div><hr></div><h2>Part 1 &#8212; Build and Run (Without Docker)</h2><h3>Step 1: Run the build script</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;d8501e21-bbd3-4b21-b809-b4b40ec7604f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">chmod +x start.sh
./start.sh
</code></pre></div><p>This creates the entire project from scratch, installs all Python dependencies, runs all three test suites, executes the pipeline smoke-test, and starts both backend and frontend.</p><p><strong>What you should see:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;23244498-6cf5-4f39-9407-36971ae58de7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[BUILD] Creating project structure...
[BUILD] Writing data_manager.py...
[BUILD] Writing reporter.py...
[BUILD] Writing executor.py...
[BUILD] Writing pipeline.py...
[BUILD] Writing watcher.py...
[BUILD] Writing pipeline_routes.py...
[BUILD] Writing main.py...
[BUILD] Writing tests...
[BUILD] Writing frontend files...
[BUILD] Verifying file structure...
  &#10003; infra-dashboard/backend/test_pipeline/pipeline.py
  &#10003; infra-dashboard/backend/test_pipeline/executor.py
  &#10003; infra-dashboard/backend/test_pipeline/reporter.py
  &#10003; infra-dashboard/backend/test_pipeline/data_manager.py
  &#10003; infra-dashboard/backend/test_pipeline/watcher.py
  &#10003; infra-dashboard/backend/api/pipeline_routes.py
  &#10003; infra-dashboard/backend/main.py
  ... 21 files total &#10003;
[BUILD] Python dependencies installed &#10003;
</code></pre></div><h3>Step 2: Inspect what was created</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;4e5f9ca4-a8cf-48cb-9267-f687cef4322e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">find infra-dashboard -type f -name "*.py" | sort
</code></pre></div><p>You should see thirteen Python files across <code>test_pipeline/</code>, <code>api/</code>, <code>tests/</code>, and the root.</p><div><hr></div><h2>Part 2 &#8212; Manual Backend Setup (Alternative)</h2><p>If you want to set up without the build script, or if you are integrating this into an existing project:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;8d2a60d0-36c8-47ab-8183-d8f53e020702&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">cd infra-dashboard/backend
python3 -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate
pip install -r requirements.txt
</code></pre></div><div><hr></div><h2>Part 3 &#8212; Running the Tests</h2><p>Run each suite individually first to make sure every layer is working independently before you fire the parallel pipeline.</p><h3>Unit tests</h3><p>These test the <code>WorkerDataManager</code> class directly &#8212; schema creation, fixture seeding, factory determinism, and clean teardown.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;2e157477-466b-4695-bff3-860f0e3097b5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">cd infra-dashboard/backend
PYTHONPATH=$(pwd) python -m pytest tests/unit/ -v --tb=short
</code></pre></div><p><strong>Expected output:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c4c3d9cf-a06e-4efb-aafe-d72e04246004&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">tests/unit/test_data_manager.py::test_setup_creates_tables          PASSED
tests/unit/test_data_manager.py::test_fixture_seeds_users           PASSED
tests/unit/test_data_manager.py::test_fixture_seeds_tasks           PASSED
tests/unit/test_data_manager.py::test_factory_user_deterministic    PASSED
tests/unit/test_data_manager.py::test_factory_user_override         PASSED
tests/unit/test_data_manager.py::test_factory_task_deterministic    PASSED
tests/unit/test_data_manager.py::test_query_returns_list            PASSED
tests/unit/test_data_manager.py::test_teardown_removes_db           PASSED
tests/unit/test_executor_pipeline.py::test_suite_result_passed_when_zero   PASSED
tests/unit/test_executor_pipeline.py::test_suite_result_failed_when_nonzero PASSED
tests/unit/test_executor_pipeline.py::test_suite_result_stores_attributes   PASSED
tests/unit/test_executor_pipeline.py::test_suite_result_xml_path_reference  PASSED

12 passed in 0.3s
</code></pre></div><h3>Integration tests</h3><p>These test the <code>Reporter</code> class &#8212; XML parsing, report building, flake rate calculation, quarantine flag logic, and history persistence.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;1abfe733-1e4d-4ef6-a0d3-7b6e49595fd1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">PYTHONPATH=$(pwd) python -m pytest tests/integration/ -v --tb=short
</code></pre></div><p><strong>Expected output:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;f070b058-ae30-4a71-b039-1c351fb206e3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">tests/integration/test_reporter.py::test_parse_junit_xml_counts                    PASSED
tests/integration/test_reporter.py::test_parse_junit_xml_statuses                  PASSED
tests/integration/test_reporter.py::test_reporter_builds_report                    PASSED
tests/integration/test_reporter.py::test_reporter_flake_rate_zero_for_new_test     PASSED
tests/integration/test_reporter.py::test_reporter_report_has_timestamp             PASSED
tests/integration/test_reporter.py::test_reporter_quarantine_flag                  PASSED
tests/integration/test_reporter.py::test_reporter_history_persisted                PASSED
tests/integration/test_reporter.py::test_parse_missing_xml_returns_empty           PASSED

8 passed in 0.6s
</code></pre></div><h3>E2E tests</h3><p>These test the FastAPI layer via <code>TestClient</code> &#8212; all three API endpoints plus CORS header behaviour.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;0302bdd3-655e-442f-b9c9-705870b6652b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">PYTHONPATH=$(pwd) python -m pytest tests/e2e/ -v --tb=short
</code></pre></div><p><strong>Expected output:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;54be4093-77aa-47d3-bcd2-401e3220f1a3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">tests/e2e/test_api_pipeline.py::test_health_endpoint        PASSED
tests/e2e/test_api_pipeline.py::test_results_404_before_run PASSED
tests/e2e/test_api_pipeline.py::test_history_returns_list   PASSED
tests/e2e/test_api_pipeline.py::test_history_run_structure  PASSED
tests/e2e/test_api_pipeline.py::test_cors_headers           PASSED

5 passed in 1.2s
</code></pre></div><h3>All suites at once</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;5f809629-e58f-4fdf-b017-32780dbaac94&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">PYTHONPATH=$(pwd) python -m pytest tests/ -v --tb=short
</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;a535cbee-f28b-49e3-9310-a76a5bb48043&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">25 passed in 0.96s
</code></pre></div><div><hr></div><h2>Part 4 &#8212; Pipeline Smoke Test</h2><p>This is the real proof of parallel execution. Run it and watch all three suites start before any of them finish.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c75aad61-c094-4fe8-a910-df7d0aab742d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">cd infra-dashboard/backend
PYTHONPATH=$(pwd) python -c "
import sys; sys.path.insert(0, '.')
from test_pipeline import run_pipeline

def cb(e): print(f'  [{e[\"event\"]}] {e.get(\"suite\",\"pipeline\")}')
report = run_pipeline(progress_cb=cb)
s = report['summary']
print(f'passed={s[\"passed\"]} failed={s[\"failed\"]} total={s[\"total\"]}')
print(f'pipeline_duration={report[\"pipeline_duration\"]}s')
"
</code></pre></div><p><strong>Expected output:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;6efb9425-96ea-4c45-8b96-7a4bbca2176f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">  [started] unit
  [started] integration
  [started] e2e
  [suite_done] integration
  [suite_done] unit
  [suite_done] e2e
  [pipeline_done] pipeline
passed=21 failed=0 total=21
pipeline_duration=2.26s
</code></pre></div><p>Notice that <code>integration</code> finishes before <code>unit</code> even though <code>unit</code> started first &#8212; the suites are genuinely running simultaneously. The pipeline wall time (~2.3s) is the duration of the single slowest suite, not the sum of all three (~5.4s sequential).</p><div><hr></div><h2>Part 5 &#8212; Starting the API Server</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;5663fb55-3dd3-4d1d-b681-ff6c6685ef45&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">cd infra-dashboard/backend
PYTHONPATH=$(pwd) uvicorn main:app --host 0.0.0.0 --port 8000 --reload
</code></pre></div><p><strong>Expected:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;5e02b7e3-35d8-4088-a21a-21e2beca220c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000
</code></pre></div><h3>Verify all three API endpoints</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;5686dca9-7ae4-45b0-b49f-0430faf142ea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"># Health check
curl http://localhost:8000/health
# {"status":"ok","service":"test-pipeline"}

# Latest pipeline results (available after first SSE run)
curl http://localhost:8000/api/pipeline/results

# Run history from SQLite
curl http://localhost:8000/api/pipeline/history
# {"runs": [...]}

# Interactive API docs
open http://localhost:8000/docs
</code></pre></div><div><hr></div><h2>Part 6 &#8212; Starting the Frontend</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;726935ee-a7a9-4fb8-8d7b-b1cd9e1fb2d2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">cd infra-dashboard/frontend
npm install --legacy-peer-deps
npm start
</code></pre></div><p><strong>Expected:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;2c10ba33-0d61-4c62-bad2-867ac8c23587&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Compiled successfully!
Local: http://localhost:3000
</code></pre></div><div><hr></div><h2>Part 7 &#8212; Live Dashboard Walkthrough</h2><p>Open </p><p>http://localhost:3000</p><p>. The dashboard has four tabs across the top.</p><p><strong>Overview tab</strong> &#8212; this is what you land on. Four metric cards across the top show Passed, Failed, Skipped, and Total. Below them is a bar chart breaking results down per suite. Below the chart is the live event log.</p><p><strong>Running the pipeline:</strong></p><ol><li><p>Click the green <strong>Run Pipeline</strong> button in the header</p></li><li><p>The live log immediately starts showing events as they stream in from the SSE endpoint: </p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;91bd279c-a3e6-4476-a36a-064e9c1d72dc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[10:32:14] started: unit[10:32:14] started: integration[10:32:14] started: e2e[10:32:16] suite_done: integration[10:32:17] suite_done: unit[10:32:18] suite_done: e2e[10:32:18] pipeline_done:
</code></pre></div><ol><li><p>The metric cards update when <code>pipeline_done</code> arrives</p></li><li><p>The bar chart redraws with real numbers</p></li></ol><p><strong>Suites tab</strong> &#8212; three cards, one per suite. Each shows a PASS or FAIL badge, the test count, and the execution time. While the pipeline is running, the card says RUNNING in green.</p><p><strong>Tests tab</strong> &#8212; a full table of every test with its suite, status badge, duration in seconds, flake rate percentage, and a warning icon if the test is quarantined (flake rate above 5%). Quarantined rows have an amber background.</p><p><strong>History tab</strong> &#8212; every test result from every previous run, pulled from <code>run_history.db</code>. This is where you would eventually build duration trend charts.</p><div><hr></div><h2>Part 8 &#8212; Docker Run</h2><p>If you prefer containers, the same build script handles everything with a flag:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;845137d6-0030-498e-a990-1c0fb8ecc750&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">./start.sh --docker
</code></pre></div><p>Or step by step:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c352bf43-e27c-4e7b-b35a-6e1f2e412ecf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">docker-compose build --no-cache
docker-compose up -d
</code></pre></div><p><strong>Expected:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;e752a03f-c055-4b61-a97c-f0ab0fba1a31&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[+] Building backend  ... done
[+] Building frontend ... done
[+] Running 2/2
 &#10004; Container backend   Started
 &#10004; Container frontend  Started
</code></pre></div><p><strong>Verify the containers are healthy:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;6ab16d24-bab2-4980-b1a7-3a69cca9570e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">docker-compose ps
# NAME       STATUS    PORTS
# backend    Up        0.0.0.0:8000-&gt;8000/tcp
# frontend   Up        0.0.0.0:3000-&gt;80/tcp

curl http://localhost:8000/health
# {"status":"ok","service":"test-pipeline"}
</code></pre></div><p><strong>Stream backend logs</strong> (includes live test output from each pipeline run):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;405afda4-bf18-478c-9955-a2d5b0e7d3a8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">docker-compose logs -f backend
</code></pre></div><p><strong>Stop everything:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;bea95311-499b-4a24-afbb-25edc7501640&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">./stop.sh
# or
docker-compose down
</code></pre></div><div><hr></div><h2>Part 9 &#8212; Testing the Watcher</h2><p>Open a second terminal and start the watcher:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;7adca131-723e-4dba-8634-6202f6639a60&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">cd infra-dashboard/backend
source ../../venv/bin/activate
python -c "
import sys; sys.path.insert(0, '.')
import time
from test_pipeline.watcher import ContinuousTestRunner

runner = ContinuousTestRunner()
runner.start()
print('Watcher running &#8212; edit any .py file in tests/ to trigger re-run')
try:
    while True: time.sleep(1)
except KeyboardInterrupt:
    runner.stop()
"
</code></pre></div><p>In your original terminal, touch any test file:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;36a4d39e-1083-4308-b0f4-b9662044f661&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">touch infra-dashboard/backend/tests/unit/test_data_manager.py
</code></pre></div><p><strong>Expected watcher output (within 500 ms):</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;624ceb65-95fb-4673-a224-046789b3149e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[WATCHER] Watching .../backend/tests
[WATCHER] Change detected: .../tests/unit/test_data_manager.py &#8212; triggering selective run
[WATCHER] Running pipeline...
[WATCHER] Done &#8212; 21 passed, 0 failed
</code></pre></div><p>The 500 ms debounce means rapid successive saves (like auto-format on save) only trigger one run, not ten.</p><div><hr></div><h2>Part 10 &#8212; Verifying the Reports</h2><p>After at least one pipeline run, check the output files:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;2dfcafd3-69ae-433f-b518-4d8115c88a52&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">ls infra-dashboard/backend/reports/
</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;6f561e83-3357-4d8b-9f74-a37b8b48935c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">junit_unit.xml
junit_integration.xml
junit_e2e.xml
pipeline_results.json
run_history.db
</code></pre></div><p>Inspect the JSON report directly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;d8f72d0f-2bc7-4c85-b8ee-f4bc1c22122c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">cat infra-dashboard/backend/reports/pipeline_results.json | python3 -m json.tool | head -40
</code></pre></div><p><strong>Expected structure:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;2a2eb480-97b2-4769-9d5a-fd9443c444e2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">{
  "timestamp": "2025-...",
  "summary": {
    "passed": 21,
    "failed": 0,
    "skipped": 0,
    "error": 0,
    "total": 21
  },
  "suites": ["unit", "integration", "e2e"],
  "tests": [
    {
      "test_id": "unit::tests.unit.test_data_manager.test_setup_creates_tables",
      "suite": "unit",
      "status": "passed",
      "duration": 0.03,
      "flake_rate": 0.0,
      "quarantined": false
    }
  ],
  "pipeline_duration": 2.26
}
</code></pre></div><div><hr></div><h2>Success Criteria</h2><p>Work through this checklist before moving on to Day 136. Every item should be a confirmed yes.</p><p>What to check How to check it 12 unit tests pass <code>pytest tests/unit/ -v</code> shows 12 passed 8 integration tests pass <code>pytest tests/integration/ -v</code> shows 8 passed 5 E2E tests pass <code>pytest tests/e2e/ -v</code> shows 5 passed All 25 tests pass at once <code>pytest tests/ -v</code> shows 25 passed Pipeline runs in parallel Smoke-test wall time &lt; sum of individual suite times Schema isolation holds No <code>worker_*.db</code> files remain after pipeline completes Fixtures are deterministic Run <code>WorkerDataManager</code> twice with same node ID &#8594; same data SSE stream works Dashboard live log shows started + suite_done + pipeline_done Reports are written <code>reports/pipeline_results.json</code> and <code>run_history.db</code> both exist Watcher triggers correctly <code>touch</code> a test file &#8594; pipeline re-runs within 1 second Docker build succeeds <code>docker-compose build</code> exits with code 0</p><div><hr></div><h2>Troubleshooting</h2><p><code>ModuleNotFoundError: No module named 'test_pipeline'</code></p><p>The backend root is not on the Python path. Fix it with:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;67e52f36-933a-45f7-98fd-63df7edd06d2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">export PYTHONPATH=$(pwd)/infra-dashboard/backend:$PYTHONPATH
</code></pre></div><p>The build script sets this automatically. If you are running commands manually, you need it every session.</p><p><strong>SSE stream closes immediately without events</strong></p><p>The backend is not running when you click Run Pipeline. Check:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;3adb08f3-519c-43d3-851f-e804fddfa915&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">curl http://localhost:8000/health</code></pre></div><p>If that fails, start the backend first (Part 5 above).</p><p><strong>Frontend shows a blank page</strong></p><p>Open the browser console. If you see a CORS error, <code>REACT_APP_API_URL</code> is pointing somewhere unexpected. Unset it or set it explicitly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;0f080f6f-2774-4fb6-afa9-0bdffd0c8b7e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">REACT_APP_API_URL=http://localhost:8000 npm start
</code></pre></div><p><strong>Port already in use</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;e60718e6-0d18-47c7-a3dd-546cb745c3bf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">./stop.sh
lsof -ti:8000 | xargs kill -9
lsof -ti:3000 | xargs kill -9
</code></pre></div><div><hr></div><h2>Key Insights for When You Design Systems</h2><p><strong>Pipeline as code.</strong> <code>pipeline.py</code> is not a dev tool. It is a production artefact. Treat it the same way you treat application code &#8212; version it, code-review it, test the pipeline itself.</p><p><strong>Schema isolation is cheaper than database isolation.</strong> Spinning up a separate database container per worker costs time and memory. Creating a separate SQLite file per worker takes microseconds and zero extra memory. Use the cheapest isolation that is still actually isolated.</p><p><strong>Quarantine flaky tests automatically.</strong> A test that fails intermittently is not a passing test. It is a test that is lying to you about half the time. Flag it, exclude it from the gate, and fix it in a separate branch.</p><p><strong>SSE over WebSockets for one-way dashboards.</strong> If the server is the only one pushing data, WebSockets adds unnecessary handshake overhead. SSE is a plain HTTP response with a persistent connection, has native browser reconnection logic, and passes through every proxy and load balancer that HTTP passes through.</p><div><hr></div><h2>Assignment</h2><p><strong>Task &#8212; Extend the flake detection logic with quarantine events.</strong></p><p>This extends everything you built today. You are adding three things: persistent flake history, automatic quarantine detection, and a visual signal in the dashboard.</p><p><strong>Steps:</strong></p><ol><li><p>Open <code>infra-dashboard/backend/test_pipeline/reporter.py</code>. In the <code>ingest()</code> method, confirm the <code>run_timestamp</code> field is being written to <code>test_runs</code>. It already is &#8212; take a moment to read the insert statement and understand how the history builds up.</p></li><li><p>Add a method <code>get_quarantined_tests(threshold=5.0)</code> to the <code>Reporter</code> class. It should query <code>run_history.db</code> for tests where the failure ratio over all recorded runs exceeds the threshold:</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;dockerfile&quot;,&quot;nodeId&quot;:&quot;9931f2a1-0598-4422-b4f2-30b622d1c629&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-dockerfile">SELECT test_id,
       COUNT(*) as total,
       SUM(CASE WHEN status='failed' THEN 1 ELSE 0 END) as fails
FROM test_runs
GROUP BY test_id
HAVING (fails * 1.0 / total) &gt; 0.05
</code></pre></div><ol start="3"><li><p>In <code>api/pipeline_routes.py</code>, after the <code>pipeline_done</code> event is yielded in the SSE generator, call <code>get_quarantined_tests()</code> and yield a second event:</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;66c67bb9-38ba-4b49-b0f7-c24b5b653328&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">{"event": "quarantine", "tests": [list of quarantined test_ids]}
</code></pre></div><ol start="4"><li><p>In <code>frontend/src/pages/TestDashboard.jsx</code>, add a listener for <code>quarantine</code> events in the <code>subscribePipeline</code> callback. Store the quarantined list in a new state variable.</p></li><li><p>On the Overview tab, render a new amber-coloured card below the metric cards. It should only appear when the quarantined list is non-empty, and should list each quarantined test ID.</p></li></ol><p><strong>Hint for the dashboard card:</strong></p><p>Use a background of <code>#fffbeb</code> with a <code>#d97706</code> border and heading &#8212; matching the amber colour already used on the quarantine warning icons in the Tests table. Consistency matters in a dashboard.</p><div><hr></div><h2>Test File Reference</h2><p>File Tests What it covers <code>tests/unit/test_data_manager.py</code> 8 <code>WorkerDataManager</code> &#8212; schema creation, fixture seeding, deterministic factory, teardown cleanup <code>tests/unit/test_executor_pipeline.py</code> 4 <code>SuiteResult</code> struct &#8212; returncode mapping, attribute storage, XML path handling <code>tests/integration/test_reporter.py</code> 8 <code>Reporter</code> &#8212; JUnit XML parsing, report building, flake rates, quarantine flag, history persistence <code>tests/e2e/test_api_pipeline.py</code> 5 FastAPI endpoints &#8212; <code>/health</code>, <code>/results</code>, <code>/history</code>, CORS headers <strong>Total</strong> <strong>25</strong> Full stack: data layer &#8594; executor &#8594; reporter &#8594; API</p><div><hr></div><h2>Working Code Demo:</h2><div id="youtube2-3KqdDET0hmI" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;3KqdDET0hmI&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/3KqdDET0hmI?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Day 134: Test Strategy Enhancement]]></title><description><![CDATA[What We Build Today]]></description><link>https://fullstackinfra.substack.com/p/day-134-test-strategy-enhancement</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-134-test-strategy-enhancement</guid><dc:creator><![CDATA[systemdr19]]></dc:creator><pubDate>Sun, 19 Jul 2026 06:30:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fkt3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We Build Today</h2><p># Layer Tools 1 Unit test suite pytest &#183; React Testing Library 2 Integration test suite FastAPI TestClient &#183; real PostgreSQL 3 End-to-end test suite Playwright &#183; Chromium 4 Visual regression Playwright screenshots &#183; pixel diff 5 Accessibility audit axe-core injected into every E2E run</p><div><hr></div><h2>Why a Testing Pyramid &#8212; and Why It Matters at Scale</h2><p>Google, Netflix, and Shopify all operate on the same principle: <strong>the cost of a bug multiplies with how late you catch it.</strong> A unit test that runs in 5 ms catches logic errors in milliseconds. A production incident takes hours of engineering time and burns user trust that is very hard to earn back.</p><p>The classic <strong>Testing Pyramid</strong> &#8212; many unit tests, fewer integration tests, fewest E2E tests &#8212; is not academic doctrine; it is a throughput constraint. If 80% of your coverage lives in fast unit tests, your CI pipeline finishes in 3 minutes. Invert the pyramid and every pull request blocks for 45 minutes.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;19cfcaa3-e422-43ae-8036-3fd7560bf10b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">              /\
             /E2\           few &#183; slow &#183; high-confidence
            /----\
           / Int. \         moderate &#183; sits at the API boundary
          /--------\
         /  Unit   \        many &#183; fast &#183; pure logic
        /------------\
</code></pre></div><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fkt3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fkt3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 424w, https://substackcdn.com/image/fetch/$s_!fkt3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 848w, https://substackcdn.com/image/fetch/$s_!fkt3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 1272w, https://substackcdn.com/image/fetch/$s_!fkt3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fkt3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png" width="1456" height="946" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:946,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fkt3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 424w, https://substackcdn.com/image/fetch/$s_!fkt3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 848w, https://substackcdn.com/image/fetch/$s_!fkt3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 1272w, https://substackcdn.com/image/fetch/$s_!fkt3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad2561d-25b8-4e05-b9ea-0e90e66779b9_4319x2807.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-134-test-strategy-enhancement">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 133: Mobile Integration: Testing the Complete Mobile Experience]]></title><description><![CDATA[You built the mobile-first UI with gestures, forms, and accessible dashboards on Day 132.]]></description><link>https://fullstackinfra.substack.com/p/day-133-mobile-integration-testing</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-133-mobile-integration-testing</guid><dc:creator><![CDATA[systemdr19]]></dc:creator><pubDate>Fri, 17 Jul 2026 06:31:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BbJV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><blockquote><p>You built the mobile-first UI with gestures, forms, and accessible dashboards on Day 132. Today you close the loop &#8212; validate that every moving part of your PWA actually works end-to-end on a real device or emulated environment. This is the day your mobile product becomes provably shippable.</p></blockquote><div><hr></div><h2>What We Build Today</h2><p>By the end of this lesson you will have:</p><ul><li><p>A <strong>PWA Audit Pipeline</strong> &#8212; Lighthouse-driven automated scoring for installability, performance, and offline readiness</p></li><li><p>An <strong>Offline Cache Validation</strong> layer &#8212; Service Worker cache integrity tests that prove your app works with zero network</p></li><li><p>A <strong>Push Notification Test Harness</strong> &#8212; backend-triggered push events verified through a Python test runner</p></li><li><p>A <strong>Mobile Performance Benchmark Dashboard</strong> &#8212; real-time FCP, TTI, CLS metrics collected and rendered in React</p></li><li><p>A complete <strong>End-to-End Mobile Test Suite</strong> &#8212; Playwright automation that simulates a Pixel 7 and iPhone 14 browser session</p></li></ul><div><hr></div><h2>Where This Fits</h2><p>In Week 18 you built the backend API and data layers. Week 19 wraps that work with a tested, installable, offline-capable mobile experience. Week 20 (Day 134 onwards) expands this test harness into a full QA strategy &#8212; the foundation you lay today becomes the integration test baseline for every feature you build after this.</p><div><hr></div><h2>Component Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BbJV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BbJV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 424w, https://substackcdn.com/image/fetch/$s_!BbJV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 848w, https://substackcdn.com/image/fetch/$s_!BbJV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 1272w, https://substackcdn.com/image/fetch/$s_!BbJV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BbJV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png" width="1024" height="637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6587639-9c91-4200-903e-ceccef2d5389_1024x637.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:637,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:558270,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/201286701?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BbJV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 424w, https://substackcdn.com/image/fetch/$s_!BbJV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 848w, https://substackcdn.com/image/fetch/$s_!BbJV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 1272w, https://substackcdn.com/image/fetch/$s_!BbJV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6587639-9c91-4200-903e-ceccef2d5389_1024x637.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Your test infrastructure wraps around the running application like a quality gate. The Python backend exposes a <code>/api/mobile-health</code> endpoint that aggregates five signal types: PWA score, cache status, push delivery, Core Web Vitals, and E2E pass rate. The React dashboard polls this endpoint and renders live status tiles &#8212; exactly the kind of internal QA dashboard you would find at teams running large-scale PWAs like Twitter Lite, Flipkart, or Uber Eats Web.</p><p></p>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-133-mobile-integration-testing">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 132: Mobile UI/UX — Building Touch-First Experiences That Scale]]></title><description><![CDATA[What We Build Today]]></description><link>https://fullstackinfra.substack.com/p/day-132-mobile-uiux-building-touch</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-132-mobile-uiux-building-touch</guid><dc:creator><![CDATA[ctoi]]></dc:creator><pubDate>Wed, 15 Jul 2026 10:15:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JNzZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>What We Build Today</h3><p>By the end of today you will have a production-grade mobile UI layer bolted onto your Infrastructure Management dashboard &#8212; the same kind of interface that tools like Datadog Mobile, PagerDuty, and Grafana Mobile expose to on-call engineers. Specifically:</p><ul><li><p><strong>Mobile-first layout system</strong> using CSS Grid and Flexbox with responsive breakpoints</p></li><li><p><strong>Gesture engine</strong> &#8212; swipe navigation, pull-to-refresh, long-press context menus</p></li><li><p><strong>Mobile-optimized forms</strong> with inline validation, autofill hints, and numeric keyboards</p></li><li><p><strong>Touch-friendly dashboard cards</strong> showing CPU, memory, and incident KPIs</p></li><li><p><strong>WCAG 2.1 AA accessibility layer</strong> &#8212; focus rings, ARIA roles, screen-reader labels</p></li></ul><blockquote><p><strong>This week&#8217;s arc:</strong> Day 131 got the app fast (lazy loading, image optimization, caching). Today we make it <em>feel</em> native. Day 133 ties everything together in integration testing.</p></blockquote><div><hr></div><h3>Where This Fits in the Overall System</h3><pre><code><code>[ React PWA Shell ]
      &#9474;
      &#9500;&#9472;&#9472; Mobile UI Layer  &#9668;&#9472;&#9472; YOU ARE HERE
      &#9474;      &#9500;&#9472;&#9472; Gesture Engine (touch events)
      &#9474;      &#9500;&#9472;&#9472; Responsive Layout Engine
      &#9474;      &#9500;&#9472;&#9472; Accessible Form System
      &#9474;      &#9492;&#9472;&#9472; Mobile Dashboard Cards
      &#9474;
      &#9500;&#9472;&#9472; Service Worker (Day 131)
      &#9500;&#9472;&#9472; Push Notification Handler (Day 133)
      &#9492;&#9472;&#9472; FastAPI Backend (Week 14&#8211;18)
</code></code></pre><p>The mobile UI layer sits between the PWA shell and every user-facing component. It intercepts raw browser touch events and normalises them into semantic gestures (swipe, pinch, long-press) that any child component can subscribe to without knowing touch internals.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JNzZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JNzZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 424w, https://substackcdn.com/image/fetch/$s_!JNzZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 848w, https://substackcdn.com/image/fetch/$s_!JNzZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 1272w, https://substackcdn.com/image/fetch/$s_!JNzZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JNzZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png" width="1456" height="873" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:873,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JNzZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 424w, https://substackcdn.com/image/fetch/$s_!JNzZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 848w, https://substackcdn.com/image/fetch/$s_!JNzZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 1272w, https://substackcdn.com/image/fetch/$s_!JNzZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ad0274a-8d55-4cbb-9927-6bb301b348d8_5399x3239.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3></h3>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-132-mobile-uiux-building-touch">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 131: Mobile Performance — Build Fast or Get Scrolled Past]]></title><description><![CDATA[What We&#8217;re Building Today]]></description><link>https://fullstackinfra.substack.com/p/day-131-mobile-performance-build</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-131-mobile-performance-build</guid><dc:creator><![CDATA[systemdr19]]></dc:creator><pubDate>Mon, 13 Jul 2026 10:16:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8p2l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We&#8217;re Building Today</h2><p>Here&#8217;s the agenda &#8212; sharp and focused:</p><ol><li><p><strong>Lazy Loading Engine</strong> &#8212; defer off-screen images and components until the viewport demands them</p></li><li><p><strong>Image Optimization Pipeline</strong> &#8212; serve WebP/AVIF with responsive breakpoints via a Python FastAPI microservice</p></li><li><p><strong>Mobile Cache Architecture</strong> &#8212; a multi-layer cache strategy (Memory &#8594; IndexedDB &#8594; Network) using Service Workers</p></li><li><p><strong>Performance Monitoring Dashboard</strong> &#8212; real-time Core Web Vitals collection and visualization</p></li></ol><p>By end of day, you&#8217;ll have a React PWA frontend talking to a Python backend with measurable, observable mobile performance &#8212; the same pattern Flipkart used when they rebuilt their mobile web as a PWA and saw 70% longer sessions.</p><div><hr></div><h2>Why Mobile Performance Is a Systems Problem, Not a Frontend Problem</h2><blockquote><p>Every 100ms of latency costs ~1% in conversions (Amazon, 2023 internal data). On a 3G connection &#8212; still 40% of Indian mobile traffic &#8212; that&#8217;s not hypothetical, it&#8217;s revenue bleeding per render.</p></blockquote><p>Mobile performance breaks down across three layers that most tutorials conflate into one:</p><p><strong>Network layer</strong> &#8212; bandwidth is scarce, latency spikes on cell handoff. You optimize here with compression, image format negotiation, and HTTP/2 push. <strong>Parse &amp; execute layer</strong> &#8212; mobile CPUs are 4&#8211;6x slower than desktop. JavaScript bundle size and render-blocking resources kill TTI (Time to Interactive). <strong>Perceived performance layer</strong> &#8212; what users feel. Skeleton screens, optimistic UI, and progressive image reveals are the engineering levers here.</p><p>Our system targets all three.</p><div><hr></div><h2>Component Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8p2l!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8p2l!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 424w, https://substackcdn.com/image/fetch/$s_!8p2l!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 848w, https://substackcdn.com/image/fetch/$s_!8p2l!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 1272w, https://substackcdn.com/image/fetch/$s_!8p2l!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8p2l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png" width="1456" height="923" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:923,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!8p2l!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 424w, https://substackcdn.com/image/fetch/$s_!8p2l!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 848w, https://substackcdn.com/image/fetch/$s_!8p2l!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 1272w, https://substackcdn.com/image/fetch/$s_!8p2l!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27d1ee8e-cb26-42dd-ba12-2f9e8c398ed1_4427x2807.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The architecture has three runtime zones:</p><p><strong>Client Zone (React PWA):</strong> The <code>PerformanceMonitor</code> component wraps the entire app tree. It registers a <code>PerformanceObserver</code> watching <code>largest-contentful-paint</code>, <code>first-input</code>, and <code>layout-shift</code> entries &#8212; these are the Core Web Vitals. The <code>LazyLoader</code> HOC (Higher Order Component) uses <code>IntersectionObserver</code> to trigger render only when a component&#8217;s root element enters the viewport with a configurable root margin. The <code>ImageOptimizer</code> component negotiates format with the backend by reading <code>navigator.connection.effectiveType</code> and passing it as a query param.</p><p><strong>Service Worker Zone:</strong> Sits between the network and the React app. Implements a Cache-First strategy for static assets, a Network-First strategy for API calls, and a Stale-While-Revalidate strategy for images. This means users on a flaky connection still get content &#8212; it&#8217;s served from cache while the SW silently refreshes in the background.</p><p><strong>Backend Zone (FastAPI):</strong> The <code>/api/images/optimize</code> endpoint receives an image URL, target width, and connection hint. It uses <code>Pillow</code> to resize and convert to WebP/AVIF, caches the result in Redis with a content-hash key, and returns the optimized binary with proper <code>Cache-Control: max-age=31536000, immutable</code> headers.</p><p></p>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-131-mobile-performance-build">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Week 6 : Alert Management Control Plane]]></title><description><![CDATA[What We Are Building]]></description><link>https://fullstackinfra.substack.com/p/week-6-alert-management-control-plane</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/week-6-alert-management-control-plane</guid><dc:creator><![CDATA[ctoi]]></dc:creator><pubDate>Sat, 11 Jul 2026 08:30:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zSeq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9aca9a48-14f0-453c-97f0-f83bcaa34b0c_4250x2000.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>What We Are Building</strong></h2><p>InfraWatch <strong>Alerts</strong> &#8212; a production alert management console modeled after PagerDuty and Datadog:</p><ol><li><p><strong>Rule engine</strong> &#8212; Threshold, anomaly, and expression-based rules with templates</p></li><li><p><strong>Evaluation pipeline</strong> &#8212; Sustained breach detection, deduplication, suppression windows</p></li><li><p><strong>Lifecycle processing</strong> &#8212; Severity scoring, escalation policies, auto-resolution</p></li><li><p><strong>Query plane</strong> &#8212; Search, statistics, bulk operations, CSV/JSON export</p></li><li><p><strong>Operator console</strong> &#8212; Dark-theme dashboard with live WebSocket updates</p></li><li><p><strong>Notification routing</strong> &#8212; Channel delivery with circuit-breaker protection</p></li></ol><div><hr></div><h2><strong>Core Concept: Signal vs Noise</strong></h2><p>Every monitoring platform ingests thousands of metric points per minute. The hard problem is not detection &#8212; it is <strong>deciding which signals deserve human attention</strong>. Datadog and PagerDuty solve this with pending timers (avoid flapping), fingerprint deduplication (one alert per root cause), and suppression windows (maintenance silence).</p><p>The non-obvious insight: <strong>alert state is a first-class entity</strong>, not a side effect of rule evaluation. A rule fires; an alert instance is born with its own lifecycle. Operators acknowledge, escalate, and resolve <em>alerts</em> &#8212; not rules.</p><div><hr></div><h2><strong>Where This Fits</strong></h2><pre><code><code>Metrics Collection          raw telemetry
        &#9474;
Background Processing       scheduled evaluation jobs
        &#9474;
Alert Management            &#9664; this lesson
        &#9474;
Notifications               delivers outcomes to humans
</code></code></pre><p>Alerts sit between completed metric pipelines and human response. They assume metrics arrive reliably and workers can evaluate rules on schedule.</p><div><hr></div><h2><strong>Component Architecture</strong></h2><p><strong>Ingest</strong> &#8212; Agents POST metric samples; each sample triggers rule evaluation.<br><strong>Evaluator</strong> &#8212; Compares values against thresholds or Z-score anomaly models.<br><strong>Processor</strong> &#8212; Manages pending&#8594;firing transitions, severity scoring, history audit.<br><strong>Query API</strong> &#8212; Powers search, statistics, and export for the operator console.<br><strong>WebSocket</strong> &#8212; Pushes state changes to the dashboard without polling.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vGb3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vGb3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 424w, https://substackcdn.com/image/fetch/$s_!vGb3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 848w, https://substackcdn.com/image/fetch/$s_!vGb3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!vGb3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vGb3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png" width="1456" height="920" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:920,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:672316,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/203792431?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vGb3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 424w, https://substackcdn.com/image/fetch/$s_!vGb3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 848w, https://substackcdn.com/image/fetch/$s_!vGb3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!vGb3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb053e6b7-2b49-4e80-b347-53a1d3946ee9_4750x3000.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/week-6-alert-management-control-plane">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 130: Push Notifications]]></title><description><![CDATA[What We Are Building Today]]></description><link>https://fullstackinfra.substack.com/p/day-130-push-notifications</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-130-push-notifications</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Thu, 09 Jul 2026 06:30:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!87ST!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We Are Building Today</h2><p>By the end of this lesson, InfraWatch proactively alerts engineers about system events &#8212; even when the browser tab is closed:</p><ul><li><p><strong>Web Push subscription pipeline</strong> &#8212; VAPID-authenticated, browser-native delivery</p></li><li><p><strong>Notification scheduler</strong> &#8212; APScheduler with per-user timezone and quiet-hour awareness</p></li><li><p><strong>Notification management UI</strong> &#8212; category-level enable/disable controls per user role</p></li><li><p><strong>Personalization engine</strong> &#8212; role-based defaults, quiet-hour filtering, and snooze support</p></li><li><p><strong>Analytics dashboard</strong> &#8212; delivery rate, CTR, dismiss rate, and subscription health</p></li></ul><div><hr></div><h2>Why Push Notifications Are an Architectural Decision, Not a Feature</h2><blockquote><p>Most developers treat push notifications as a UI checkbox. Engineering teams at PagerDuty and Datadog treat them as a <strong>critical reliability contract</strong> between the system and the on-call engineer. A notification arriving 4 minutes late during an incident cascade is functionally useless.</p><p>The Web Push Protocol (RFC 8030) makes browser notifications independent of your backend&#8217;s uptime. Your server doesn&#8217;t hold a persistent socket to the browser &#8212; it posts an encrypted payload to a <strong>vendor-operated push relay</strong> (Google&#8217;s FCM for Chrome, Mozilla&#8217;s autopush for Firefox). That relay maintains the connection and delivers the message when the browser next connects. This is why notifications survive tab closure and network drops.</p></blockquote><h4> Component Architecture</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!87ST!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!87ST!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 424w, https://substackcdn.com/image/fetch/$s_!87ST!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 848w, https://substackcdn.com/image/fetch/$s_!87ST!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 1272w, https://substackcdn.com/image/fetch/$s_!87ST!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!87ST!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png" width="1237" height="880" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:880,&quot;width&quot;:1237,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:354769,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/194054020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!87ST!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 424w, https://substackcdn.com/image/fetch/$s_!87ST!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 848w, https://substackcdn.com/image/fetch/$s_!87ST!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 1272w, https://substackcdn.com/image/fetch/$s_!87ST!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ecac534-0967-417a-8d3a-6d396ebf3378_1237x880.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Core Concept: VAPID Authentication</h2><p>VAPID (Voluntary Application Server Identification for Web Push) proves that a notification came from your server, not a spoofed source.</p><p>You generate an <strong>EC P-256 key pair</strong> once. The public key is embedded in your frontend subscription call; the private key signs a short-lived JWT on every push request. The push relay validates that JWT before forwarding the message.</p><pre><code><code>VAPID Private Key  &#8594;  signs JWT  &#8594;  Authorization header  &#8594;  Push Relay validates  &#8594;  delivers
VAPID Public Key   &#8594;  embedded in PushSubscription  &#8594;  tied to your server identity
</code></code></pre><p>This is the same elliptic-curve cryptography used in TLS 1.3. The short-lived JWT (24-hour TTL max) means a leaked token has a bounded blast radius.</p><p>One rule that trips everyone up: <strong>never rotate VAPID keys without first unsubscribing all existing subscribers.</strong> Old subscriptions bind to the original public key &#8212; they start returning 403 the moment your keys change, silently breaking delivery for every user. Treat VAPID key rotation exactly like rotating a TLS certificate: plan it, notify, and migrate.</p><div><hr></div><h2>Subscription Lifecycle</h2><p>When a user clicks &#8220;Enable Notifications&#8221; in InfraWatch, five things happen in sequence:</p><ol><li><p>Browser calls <code>Notification.requestPermission()</code> &#8212; this requires an explicit user gesture; you cannot trigger it programmatically without a click</p></li><li><p>Service Worker calls <code>pushManager.subscribe({ userVisibleOnly: true, applicationServerKey })</code></p></li><li><p>Browser contacts the vendor push service and receives a unique <strong>push endpoint URL</strong> plus two encryption keys: <code>p256dh</code> and <code>auth</code></p></li><li><p>Frontend POSTs the <code>PushSubscription</code> object to <code>POST /api/subscriptions</code></p></li><li><p>Backend stores the endpoint and keys in PostgreSQL, tagged to the user ID and role</p></li></ol><p>The subscription object is the user&#8217;s <strong>mailing address in push-space</strong>. It does not belong to your database &#8212; it belongs to the browser and the vendor relay. When the relay returns <strong>HTTP 410 Gone</strong>, that address has been invalidated. Delete it immediately. Stale subscriptions silently inflate your &#8220;sent&#8221; counter while actual delivery stays at zero.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q1cm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q1cm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 424w, https://substackcdn.com/image/fetch/$s_!Q1cm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 848w, https://substackcdn.com/image/fetch/$s_!Q1cm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 1272w, https://substackcdn.com/image/fetch/$s_!Q1cm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q1cm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png" width="1182" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1182,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:145887,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/194054020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Q1cm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 424w, https://substackcdn.com/image/fetch/$s_!Q1cm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 848w, https://substackcdn.com/image/fetch/$s_!Q1cm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 1272w, https://substackcdn.com/image/fetch/$s_!Q1cm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8a84c75-f52c-4266-ac40-349c2aec6a02_1182x770.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Notification Scheduling Architecture</h2><p>InfraWatch uses <strong>APScheduler</strong> (<code>AsyncIOScheduler</code>) inside FastAPI. Three jobs run on independent schedules:</p><p>Job Trigger Purpose <code>heartbeat_check</code> every 60 s Alert digest for triggered monitors <code>daily_summary</code> cron 08:00 user-local Daily system health brief <code>subscription_gc</code> cron 03:00 UTC Remove 410-Gone stale subscriptions</p><p><strong>Quiet hours</strong> are enforced at the scheduler level, not in JavaScript. If it is 02:00 in the user&#8217;s configured timezone, the job skips that user entirely. Client-side suppression is unreliable &#8212; the tab might not even be open.</p><p>The scheduler <strong>must</strong> use <code>AsyncIOScheduler</code>, not <code>BackgroundScheduler</code>. The latter runs in a separate thread and cannot await async SQLAlchemy session methods. Mixing them causes event loop conflicts that are difficult to reproduce and nearly impossible to debug under load.</p><div><hr></div><h2>The Service Worker&#8217;s Role</h2><p>The Service Worker is a background JavaScript thread that persists independently of your React app. It registers a <code>push</code> event listener that fires whenever the relay delivers a message.</p><pre><code><code>self.addEventListener('push', event =&gt; {
    const data = event.data.json()
    event.waitUntil(
        self.registration.showNotification(data.title, {
            body: data.body,
            icon: data.icon,
            data: { url: data.url, notificationId: data.id }
        })
    )
})
</code></code></pre><p><code>event.waitUntil()</code> is not optional &#8212; without it, the browser terminates the Service Worker before <code>showNotification</code> completes and the notification never appears. The <code>notificationclick</code> handler uses <code>clients.openWindow()</code> to navigate the user to the relevant dashboard panel, using the URL embedded in the notification payload.</p><p>The Service Worker file must live at <code>public/sw.js</code> so Vite serves it at the root scope <code>/sw.js</code>. A worker registered at <code>/src/sw.js</code> controls only the <code>/src/</code> scope and will miss every push event.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tKQU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tKQU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 424w, https://substackcdn.com/image/fetch/$s_!tKQU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 848w, https://substackcdn.com/image/fetch/$s_!tKQU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 1272w, https://substackcdn.com/image/fetch/$s_!tKQU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tKQU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png" width="1072" height="687" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:687,&quot;width&quot;:1072,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:201928,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/194054020?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tKQU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 424w, https://substackcdn.com/image/fetch/$s_!tKQU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 848w, https://substackcdn.com/image/fetch/$s_!tKQU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 1272w, https://substackcdn.com/image/fetch/$s_!tKQU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fa9e7a1-ba83-4ffc-a0d0-2cd515dcc7fa_1072x687.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Personalization: More Than &#8220;Turn On/Off&#8221;</h2><p>InfraWatch segments preferences by category: <strong>Alerts</strong>, <strong>Team Updates</strong>, <strong>Daily Digest</strong>, <strong>Security Events</strong>. Each has independent enable/disable and threshold settings in the <code>notification_preferences</code> table.</p><p>Role-based defaults matter: a DevOps engineer defaults to all alert categories on; a product manager defaults to daily digest only. This mirrors PagerDuty&#8217;s urgency-tier routing &#8212; segmented per on-call rotation, not binary on/off.</p><p>Overnight quiet-hour windows (e.g., 22:00&#8211;07:00) require a specific branch in the scheduler. The naive check <code>22 &lt;= hour &lt; 7</code> is always False because 22 is never less than 7. You need:</p><pre><code><code>if start &gt; end:   # overnight window &#8212; e.g., 22:00 to 07:00
    if hour &gt;= start or hour &lt; end:
        continue  # inside quiet window, skip this user
else:
    if start &lt;= hour &lt; end:
        continue
</code></code></pre><div><hr></div><h2>Analytics: What Actually Matters</h2><p>Four metrics define push system health:</p><ul><li><p><strong>Delivery rate</strong> = delivered / sent <em>(target &gt; 95%; lower values indicate stale subscriptions)</em></p></li><li><p><strong>Click-through rate</strong> = clicked / delivered <em>(benchmark: 10&#8211;20% for actionable infrastructure alerts)</em></p></li><li><p><strong>Dismiss rate</strong> = dismissed / delivered <em>(rising dismiss rate signals low signal-to-noise)</em></p></li><li><p><strong>Subscription churn</strong> = unsubscribed this week / active <em>(above 5% means notification fatigue)</em></p></li></ul><p>The Service Worker reports <code>click</code> and <code>dismiss</code> events to <code>POST /api/notifications/event</code> with the <code>notificationId</code>. The backend timestamps these and links them to the original notification record.</p><p><code>notification_events</code> is a <strong>separate table</strong>, not columns on <code>notifications</code>. This lets you record multiple events per notification &#8212; delivered at T+0, clicked at T+45s &#8212; with full timestamps. This is the same pattern PagerDuty uses to calculate acknowledgement latency on incident alerts.</p><div><hr></div><h2>How This Fits InfraWatch</h2><p>This module connects Week 11&#8217;s User &amp; Team Management theme to the alerting system from earlier weeks. The notification subscription lives alongside user profile data. The scheduler reads from the same alert tables built in Weeks 5&#8211;7. Analytics feed the same dashboard infrastructure from Week 9.</p><p>Architecturally, Day 130 closes the loop: InfraWatch can now <strong>reach the engineer</strong> rather than waiting for the engineer to open a tab.</p><div><hr></div><div><hr></div><h1>Implementation Guide</h1><h3><strong>GitHub Link:-</strong></h3><p><a href="https://github.com/sysdr/infrawatch/tree/main/day130/infrawatch-day130">https://github.com/sysdr/infrawatch/tree/main/day130/infrawatch-day130</a></p><h2>Project Structure</h2><p>Running <code>./setup.sh</code> creates the full project at <code>./infrawatch-day130/</code> and writes every source file as heredoc. No file needs to be created manually.</p><pre><code><code>infrawatch-day130/
&#9500;&#9472;&#9472; backend/
&#9474;   &#9500;&#9472;&#9472; app/
&#9474;   &#9474;   &#9500;&#9472;&#9472; api/         notifications.py  subscriptions.py  users.py  vapid.py
&#9474;   &#9474;   &#9500;&#9472;&#9472; models/      models.py
&#9474;   &#9474;   &#9500;&#9472;&#9472; schemas/     schemas.py
&#9474;   &#9474;   &#9492;&#9472;&#9472; services/    push_service.py   scheduler_service.py
&#9474;   &#9500;&#9472;&#9472; tests/           conftest.py   test_push_notifications.py
&#9474;   &#9500;&#9472;&#9472; generate_vapid.py
&#9474;   &#9500;&#9472;&#9472; main.py
&#9474;   &#9492;&#9472;&#9472; requirements.txt
&#9500;&#9472;&#9472; frontend/
&#9474;   &#9500;&#9472;&#9472; public/          sw.js   manifest.json   icons/
&#9474;   &#9492;&#9472;&#9472; src/
&#9474;       &#9500;&#9472;&#9472; hooks/       usePushNotifications.js
&#9474;       &#9500;&#9472;&#9472; pages/       Dashboard.jsx
&#9474;       &#9492;&#9472;&#9472; services/    api.js
&#9492;&#9472;&#9472; docker-compose.yml
</code></code></pre><div><hr></div><h2>Step 1 &#8212; VAPID Key Generation</h2><p>VAPID keys are EC P-256 asymmetric keypairs. Generate them once. In production, store the private key in a secrets manager (Vault, AWS Secrets Manager). The setup script calls <code>generate_vapid.py</code> automatically on first run &#8212; but you can run it independently:</p><pre><code><code>cd infrawatch-day130/backend
python generate_vapid.py
# Writes VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY to .env
</code></code></pre><p>The generator uses Python&#8217;s <code>cryptography</code> library. The private key is PEM-encoded (required by pywebpush); the public key is URL-safe base64 (required by the browser&#8217;s <code>subscribe()</code> call). Never commit the private key to source control.</p><p>Verify the key was written:</p><pre><code><code>curl http://localhost:8130/api/vapid/public-key
</code></code></pre><pre><code><code>{ "public_key": "BOM4eVR2T1gNlBMv8UXa2n..." }
</code></code></pre><p>If this returns an empty string, the <code>.env</code> file was not populated. Re-run <code>generate_vapid.py</code>.</p><div><hr></div><h2>Step 2 &#8212; Database Schema</h2><p>Four tables, each with a distinct responsibility:</p><p>Table Key Fields Purpose <code>push_subscriptions</code> endpoint, p256dh, auth, user_id, is_active One row per browser session per user <code>notification_preferences</code> category flags, quiet_hours_start/end, snoozed_until Per-user delivery rules <code>notifications</code> title, body, category, status, sent_at Every sent notification record <code>notification_events</code> notification_id, event_type, occurred_at Click / dismiss / delivered timestamps</p><p>The <code>endpoint</code> column on <code>push_subscriptions</code> carries a unique constraint. Subscription creation always uses an <strong>upsert on endpoint</strong> &#8212; the browser will call <code>subscribe()</code> again after a service worker re-registration, and a blind insert would throw a unique constraint violation every time.</p><div><hr></div><h2>Step 3 &#8212; Subscription API</h2><pre><code><code># Pseudo-code: POST /api/subscriptions
existing = query(PushSubscription).filter_by(endpoint=endpoint).first()
if existing:
    existing.is_active = True
    existing.p256dh    = keys.p256dh
    existing.auth      = keys.auth
else:
    db.add(PushSubscription(user_id=user_id, endpoint=endpoint, ...))
# Then upsert role-based default preferences for this user
</code></code></pre><p>Create a user and register a subscription:</p><pre><code><code># Create a user
UID=$(curl -s -X POST http://localhost:8130/api/users \
  -H 'Content-Type: application/json' \
  -d '{"username":"alice","email":"alice@infrawatch.dev","role":"devops","timezone":"UTC"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "User: $UID"

# Register a subscription for that user
curl -X POST http://localhost:8130/api/subscriptions \
  -H 'Content-Type: application/json' \
  -d "{
    \"user_id\":  \"$UID\",
    \"endpoint\": \"https://fcm.googleapis.com/fcm/send/...\",
    \"keys\": { \"p256dh\": \"BNcR...\", \"auth\": \"tBHI...\" }
  }"
</code></code></pre><pre><code><code>{ "id": "775dbc46...", "user_id": "...", "is_active": true }
</code></code></pre><div><hr></div><h2>Step 4 &#8212; Sending with pywebpush</h2><pre><code><code>from pywebpush import webpush, WebPushException

webpush(
    subscription_info={
        "endpoint": sub.endpoint,
        "keys": { "p256dh": sub.p256dh, "auth": sub.auth }
    },
    data=json.dumps({ "title": title, "body": body, "url": url, "id": notif_id }),
    vapid_private_key=settings.VAPID_PRIVATE_KEY,
    vapid_claims={
        "sub": settings.VAPID_CLAIMS_EMAIL,
        "aud": extract_audience(sub.endpoint),
    },
)
</code></code></pre><p>Three response codes to handle explicitly:</p><p>HTTP Status Meaning Action Required 201 Created Accepted by relay Log as <code>delivered</code> 410 Gone Subscription invalidated Delete from DB immediately 429 Too Many Requests Rate limited Log warning, retry with exponential backoff</p><p>Send a notification:</p><pre><code><code>NID=$(curl -s -X POST http://localhost:8130/api/notifications/send \
  -H 'Content-Type: application/json' \
  -d "{
    \"user_id\":  \"$UID\",
    \"category\": \"alerts\",
    \"title\":    \"CPU Spike\",
    \"body\":     \"prod-web-01 CPU at 94%\",
    \"url\":      \"/dashboard/alerts\"
  }" | python3 -c "import sys,json; print(json.load(sys.stdin)['notification_id'])")
echo "Notification ID: $NID"
</code></code></pre><p>In development without a live browser subscription, the push delivery will report <code>no_subscription_or_vapid_not_configured</code>. The notification record and analytics write correctly regardless &#8212; only the actual relay dispatch requires a live browser.</p><div><hr></div><h2>Step 5 &#8212; APScheduler Setup</h2><pre><code><code>from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval  import IntervalTrigger
from apscheduler.triggers.cron      import CronTrigger

scheduler = AsyncIOScheduler()
scheduler.add_job(heartbeat_check, IntervalTrigger(seconds=60),     id="heartbeat")
scheduler.add_job(daily_summary,   CronTrigger(hour=8, minute=0),   id="daily_summary")
scheduler.add_job(subscription_gc, CronTrigger(hour=3, minute=0),   id="subscription_gc")

@asynccontextmanager
async def lifespan(app):
    await init_db()
    scheduler.start()
    yield
    scheduler.shutdown()
</code></code></pre><p>Each scheduler job must create its own <code>AsyncSession</code> via <code>async_sessionmaker</code>. Never pass the request-scoped session to a background job &#8212; it will be closed by the time the job runs, throwing <code>DetachedInstanceError</code> in production under concurrent load.</p><div><hr></div><h2>Step 6 &#8212; Service Worker Push Handler</h2><pre><code><code>// frontend/public/sw.js

self.addEventListener('push', event =&gt; {
    const data = event.data.json();
    event.waitUntil(
        self.registration.showNotification(data.title, {
            body:    data.body,
            icon:    '/icons/icon-192x192.png',
            data:    { url: data.url, notificationId: data.id },
            actions: [
                { action: 'view',    title: 'View'    },
                { action: 'dismiss', title: 'Dismiss' }
            ]
        }).then(() =&gt; reportEvent(data.id, 'delivered'))
    );
});

self.addEventListener('notificationclick', event =&gt; {
    event.notification.close();
    event.waitUntil(clients.openWindow(event.notification.data.url));
    reportEvent(event.notification.data.notificationId, 'clicked');
});
</code></code></pre><p><code>reportEvent</code> is a plain <code>fetch()</code> to <code>POST /api/notifications/event</code>. It fires inside <code>event.waitUntil()</code> so the browser keeps the service worker alive long enough for the network request to complete.</p><div><hr></div><h2>Step 7 &#8212; Frontend Subscription Hook</h2><pre><code><code>// Pseudo-code: usePushNotifications.js

const vapidKey   = await api.vapid.getPublicKey();
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;

const sw  = await navigator.serviceWorker.ready;
const sub = await sw.pushManager.subscribe({
    userVisibleOnly:      true,
    applicationServerKey: urlBase64ToUint8Array(vapidKey)  // must be Uint8Array, not a string
});

await api.subscriptions.create({ user_id: userId, ...sub.toJSON() });
</code></code></pre><p><code>urlBase64ToUint8Array</code> converts the URL-safe base64 VAPID public key into raw bytes. Passing the base64 string directly throws <code>DOMException: Failed to execute 'subscribe' on 'PushManager'</code>.</p><div><hr></div><h2>Step 8 &#8212; Preferences and Quiet Hours</h2><pre><code><code># View preferences (auto-created on first subscription with role-based defaults)
curl http://localhost:8130/api/notifications/preferences/$UID

# Enable quiet hours 22:00 to 07:00
curl -X PATCH http://localhost:8130/api/notifications/preferences/$UID \
  -H 'Content-Type: application/json' \
  -d '{"quiet_hours_enabled": true, "quiet_hours_start": 22, "quiet_hours_end": 7}'

# Snooze all notifications for 60 minutes
curl -X POST http://localhost:8130/api/notifications/snooze \
  -H 'Content-Type: application/json' \
  -d "{\"user_id\": \"$UID\", \"duration_minutes\": 60}"

# Clear snooze early
curl -X DELETE http://localhost:8130/api/notifications/snooze/$UID
</code></code></pre><div><hr></div><h2>Step 9 &#8212; Record Events and Query Analytics</h2><pre><code><code># Record a click event
curl -X POST http://localhost:8130/api/notifications/event \
  -H 'Content-Type: application/json' \
  -d "{\"notification_id\": \"$NID\", \"event_type\": \"clicked\"}"

# Pull the full analytics summary
curl http://localhost:8130/api/notifications/analytics | python3 -m json.tool
</code></code></pre><pre><code><code>{
  "totals":    { "sent": 4, "delivered": 4, "clicked": 2, "dismissed": 2, "failed": 0 },
  "rates":     { "delivery_rate": 100.0, "ctr": 50.0, "dismiss_rate": 50.0 },
  "subscriptions": { "active": 2, "inactive": 0 },
  "by_category": [
    { "category": "alerts",       "count": 2 },
    { "category": "daily_digest", "count": 2 }
  ]
}
</code></code></pre><div><hr></div><div><hr></div><h1>Build, Test and Demo</h1><h2>Running the Project</h2><p><strong>Without Docker</strong></p><pre><code><code>chmod +x setup.sh
./setup.sh
</code></code></pre><p>The script creates the full project, generates VAPID keys, installs all dependencies, runs 10 tests, starts the backend on <code>:8130</code> and the frontend on <code>:3130</code>, then runs a live functional demo against the API. Every source file is written from heredoc &#8212; nothing needs to be downloaded separately.</p><p><strong>With Docker</strong></p><pre><code><code>USE_DOCKER=true ./setup.sh
</code></code></pre><p>Starts two containers: <code>infrawatch-backend-130</code> (uvicorn) and <code>infrawatch-frontend-130</code> (nginx). Same ports apply.</p><p>Expected terminal output from either path:</p><pre><code><code>[OK]  backend/requirements.txt
[OK]  backend/.env
[OK]  backend/generate_vapid.py
[OK]  backend/main.py
[OK]  backend/app/config.py
[OK]  backend/app/database.py
[OK]  backend/app/models/models.py
[OK]  backend/app/schemas/schemas.py
[OK]  backend/app/services/push_service.py
[OK]  backend/app/services/scheduler_service.py
[OK]  backend/app/api/subscriptions.py
[OK]  backend/app/api/notifications.py
[OK]  backend/app/api/users.py
[OK]  backend/app/api/vapid.py
[OK]  backend/tests/conftest.py
[OK]  backend/tests/test_push_notifications.py
[OK]  frontend/package.json
[OK]  frontend/vite.config.js
[OK]  frontend/index.html
[OK]  frontend/public/sw.js
[OK]  frontend/public/manifest.json
[OK]  frontend/src/main.jsx
[OK]  frontend/src/App.jsx
[OK]  frontend/src/styles.css
[OK]  frontend/src/services/api.js
[OK]  frontend/src/hooks/usePushNotifications.js
[OK]  frontend/src/pages/Dashboard.jsx
[OK]  All tests passed
[OK]  Backend running on :8130
[OK]  Frontend running on :3130
</code></code></pre><div><hr></div><h2>Tests</h2><pre><code><code>cd infrawatch-day130/backend
.venv/bin/python -m pytest tests/ -v
</code></code></pre><pre><code><code>tests/test_push_notifications.py::test_create_user                  PASSED
tests/test_push_notifications.py::test_list_users                   PASSED
tests/test_push_notifications.py::test_create_subscription          PASSED
tests/test_push_notifications.py::test_get_user_subscriptions       PASSED
tests/test_push_notifications.py::test_get_and_update_preferences   PASSED
tests/test_push_notifications.py::test_analytics_endpoint           PASSED
tests/test_push_notifications.py::test_record_notification_event    PASSED
tests/test_push_notifications.py::test_snooze_and_clear             PASSED
tests/test_push_notifications.py::test_vapid_public_key             PASSED
tests/test_push_notifications.py::test_health                       PASSED

======================== 10 passed in ~2.7s ========================
</code></code></pre><p>All 10 tests use <code>httpx.AsyncClient</code> with <code>ASGITransport</code> &#8212; no background server is needed and no real push calls are made. Tests cover the complete request-response cycle including DB writes, event recording, and analytics aggregation.</p><div><hr></div><h2>Functional Verification</h2><p>Work through these steps in order after <code>./setup.sh</code> completes.</p><p><strong>Health check</strong></p><pre><code><code>curl http://localhost:8130/health
</code></code></pre><pre><code><code>{ "status": "ok", "service": "infrawatch-push-notifications", "day": 130 }
</code></code></pre><p><strong>VAPID public key</strong></p><pre><code><code>curl http://localhost:8130/api/vapid/public-key
</code></code></pre><pre><code><code>{ "public_key": "BFbDKwKfbskLDD42..." }
</code></code></pre><p><strong>Create a user and subscription, send a notification, record a click</strong></p><pre><code><code>UID=$(curl -s -X POST http://localhost:8130/api/users \
  -H 'Content-Type: application/json' \
  -d '{"username":"alice","email":"alice@infrawatch.dev","role":"devops","timezone":"UTC"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

curl -s -X POST http://localhost:8130/api/subscriptions \
  -H 'Content-Type: application/json' \
  -d "{\"user_id\":\"$UID\",\"endpoint\":\"https://test.fcm.invalid/alice\",
       \"keys\":{\"p256dh\":\"BNcRd...\",\"auth\":\"tBHI...\"}}" &gt; /dev/null

NID=$(curl -s -X POST http://localhost:8130/api/notifications/send \
  -H 'Content-Type: application/json' \
  -d "{\"user_id\":\"$UID\",\"category\":\"alerts\",
       \"title\":\"CPU Spike\",\"body\":\"prod-web-01 94%\",\"url\":\"/alerts\"}" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['notification_id'])")

curl -s -X POST http://localhost:8130/api/notifications/event \
  -H 'Content-Type: application/json' \
  -d "{\"notification_id\":\"$NID\",\"event_type\":\"clicked\"}"

curl http://localhost:8130/api/notifications/analytics | python3 -m json.tool
</code></code></pre><p>Confirm <code>delivery_rate</code> is 100.0 and <code>ctr</code> reflects your click event.</p><p><strong>Swagger UI</strong> &#8212; open <code>http://localhost:8130/docs</code> to browse all 14 endpoints and fire test requests from the browser.</p><div><hr></div><h2>Dashboard Verification</h2><p>Open </p><p>http://localhost:3130</p><p>. The dashboard has five tabs:</p><p>Tab What to confirm <strong>Overview</strong> Stat cards (Total Sent, Delivery Rate, CTR, Active Subscriptions), delivery funnel bar chart, category pie chart, recent notifications table <strong>Subscribe</strong> Browser push capability check, user selector, Subscribe / Unsubscribe button, live subscription status <strong>Preferences</strong> Four category toggles, quiet hours time pickers, snooze duration controls with active snooze display <strong>Send Test</strong> User selector, category dropdown, title/body/URL fields, four quick-fill templates <strong>Analytics</strong> CTR and delivery rate bar charts, category breakdown, metric interpretation guide</p><p><strong>Testing real push delivery</strong> (Chrome or Firefox on localhost &#8212; no HTTPS needed):</p><ol><li><p>Open </p></li></ol><p>http://localhost:3130</p><ol><li><p> and go to the <strong>Subscribe</strong> tab</p></li><li><p>Select a user and click <strong>Subscribe</strong> &#8212; the browser permission dialog appears</p></li><li><p>Click <strong>Allow</strong> &#8212; the subscription is saved to the backend and the status badge updates</p></li><li><p>Go to <strong>Send Test</strong>, select the same user, choose <strong>Alerts</strong>, fill in a message</p></li><li><p>Minimise the browser tab and click <strong>Send</strong> &#8212; the OS notification appears within 2 seconds</p></li></ol><div><hr></div><h2>Stop All Services</h2><pre><code><code>./setup.sh stop

# Docker
USE_DOCKER=true ./setup.sh stop
</code></code></pre><div><hr></div><h2>Troubleshooting</h2><p>Symptom Likely Cause Fix <code>public_key</code> is empty <code>generate_vapid.py</code> was not run <code>cd backend &amp;&amp; python generate_vapid.py</code> Push service returns 403 Keys regenerated after subscriptions were saved Regenerate keys; users must re-subscribe Push service returns 410 Browser invalidated the endpoint <code>subscription_gc</code> handles this at 03:00 UTC; delete manually to test immediately Permission dialog does not appear Not triggered from a user gesture <code>requestPermission()</code> must be called inside a click handler <code>sw.js</code> returns 404 Service worker not at root scope File must be in <code>frontend/public/</code>, not <code>frontend/src/</code> <code>DetachedInstanceError</code> in scheduler Job sharing the request-scoped session Create a fresh <code>AsyncSession</code> inside every scheduler job <code>DOMException</code> on <code>subscribe()</code> VAPID key passed as string, not bytes Run <code>urlBase64ToUint8Array()</code> before passing to <code>applicationServerKey</code></p><h3><strong>YouTube Demo Link:-</strong></h3><div id="youtube2-END3cOOwjv4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;END3cOOwjv4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/END3cOOwjv4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div>]]></content:encoded></item><item><title><![CDATA[Day 129 — Offline Capabilities]]></title><description><![CDATA[What We Are Building Today]]></description><link>https://fullstackinfra.substack.com/p/day-129-offline-capabilities</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-129-offline-capabilities</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Tue, 07 Jul 2026 06:31:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kTje!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We Are Building Today</h2><p>Today you will wire up a production-grade offline-capable task management system. By the end of this lesson your application will:</p><ul><li><p>Store data locally in the browser using IndexedDB</p></li><li><p>Detect network state and queue writes when offline</p></li><li><p>Sync queued changes to the server the moment connectivity returns</p></li><li><p>Detect and resolve data conflicts when server and client diverge</p></li><li><p>Run background sync via a Service Worker &#8212; no user action required</p></li></ul><p>This slots directly into the Week 11 infrastructure around team collaboration. Users on unstable connections &#8212; field technicians, remote workers, mobile users &#8212; can keep working without hitting a 503. Their edits land on the server when the network recovers.</p><div><hr></div><h2>Before You Start</h2><p><strong>Prerequisites:</strong></p><ul><li><p>Python 3.11+, Node 20+, Docker (optional)</p></li><li><p>Day 128 backend available as reference</p></li><li><p>Browser: Chrome or Edge &#8212; they have full Background Sync API support. Firefox handles reconnect sync through the foreground <code>online</code> listener instead.</p></li></ul><p><strong>The offline pipeline has four stages, in order:</strong></p><pre><code><code>User Action
  &#8594; IndexedDB write    (immediate &#8212; user never waits)
  &#8594; Sync Queue append
  &#8594; pushSync() drain   (on reconnect / 30s interval / SW event)
  &#8594; FastAPI /api/sync/ (conflict check, merge, return server delta)
</code></code></pre><p>Every stage is decoupled. The user never waits for the network. Keep this mental model in your head as you build &#8212; every decision in this lesson traces back to it.</p><div><hr></div><h2>Why Offline-First Matters in Production</h2><p>Google Docs autosaves every keystroke locally before it ever reaches a Google server. Notion queues every block edit in IndexedDB. Figma stores the entire file graph client-side and syncs deltas. These are not edge-case features &#8212; they are table-stakes for any collaborative tool.</p><p>The discipline is called <strong>offline-first</strong>: writes happen locally first, sync happens second. This inverts the classic &#8220;optimistic update&#8221; pattern (assume the network works) into something more resilient &#8212; assume the network is gone, sync opportunistically.</p><div><hr></div><h2>The Core Architecture</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kTje!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kTje!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 424w, https://substackcdn.com/image/fetch/$s_!kTje!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 848w, https://substackcdn.com/image/fetch/$s_!kTje!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 1272w, https://substackcdn.com/image/fetch/$s_!kTje!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kTje!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png" width="1127" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1127,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:153724,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/193434303?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kTje!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 424w, https://substackcdn.com/image/fetch/$s_!kTje!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 848w, https://substackcdn.com/image/fetch/$s_!kTje!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 1272w, https://substackcdn.com/image/fetch/$s_!kTje!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e7d3cc-1398-4744-a9e0-2ab7123d9af1_1127x770.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-129-offline-capabilities">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Week 5: Background Processing Control Plane]]></title><description><![CDATA[What We Are Building]]></description><link>https://fullstackinfra.substack.com/p/week-5-background-processing-control</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/week-5-background-processing-control</guid><dc:creator><![CDATA[ctoi]]></dc:creator><pubDate>Sat, 04 Jul 2026 08:31:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uHCK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>What We Are Building</strong></h2><blockquote><p>InfraWatch <strong>AsyncOps</strong> &#8212; a production background-processing console that ships real work off the request path:</p></blockquote><ol><li><p><strong>Celery execution layer</strong> &#8212; Multi-queue routing, retries, worker scaling, Flower visibility</p></li><li><p><strong>Async task catalog</strong> &#8212; Metrics collection, batch processing, notifications, reports, maintenance</p></li><li><p><strong>Cron scheduler</strong> &#8212; User-defined jobs with priority and manual execution</p></li><li><p><strong>Workflow orchestration</strong> &#8212; DAG dependencies, conditional nodes, chord/chain patterns</p></li><li><p><strong>Monitoring plane</strong> &#8212; WebSocket streaming, Prometheus metrics, queue depth alerts</p></li><li><p><strong>Operator console</strong> &#8212; Datadog/Celery-style dark dashboard with live controls</p></li></ol><div><hr></div><h2><strong>Core Concept: Request Path vs Worker Path</strong></h2><blockquote><p>User-facing APIs must return in milliseconds. Anything that touches disks, networks, or aggregation belongs on a <strong>worker path</strong> &#8212; queued, retried, and observed separately. Stripe and GitHub run variations of this split: HTTP acknowledges intent; workers guarantee completion.</p><p>The non-obvious detail: <strong>queue naming is a capacity contract</strong>, not decoration. A <code>metrics.high</code> queue with dedicated workers prevents report generation from starving health checks &#8212; the same pattern AWS SQS priority queues and Sidekiq weighted queues use.</p></blockquote><div><hr></div><h2><strong>Where This Fits</strong></h2><pre><code><code>Metrics Collection          telemetry source
        &#9474;
Background Processing       &#9664; this lesson
        &#9474;
Alert Management            evaluates completed work &amp; thresholds
        &#9474;
Notifications               delivers outcomes
</code></code></pre><blockquote><p>AsyncOps is the <strong>execution backbone</strong> between raw metrics and human response. Alerts assume tasks complete reliably and emit measurable latency.</p></blockquote><div><hr></div><h2><strong>Component Architecture</strong></h2><blockquote><p><strong>API</strong> &#8212; FastAPI enqueues tasks, exposes stats, and streams WebSocket updates.<br><strong>Broker</strong> &#8212; Redis holds queues and result backends with isolated DB indexes.<br><strong>Workers</strong> &#8212; Celery consumers per queue family with autoretry and progress metadata.<br><strong>Scheduler</strong> &#8212; Croniter loop plus Celery Beat for platform housekeeping.<br><strong>Orchestrator</strong> &#8212; DAG engine resolves dependencies before dispatching child tasks.<br><strong>Console</strong> &#8212; React operator UI with queue pause/resume, retry/cancel, workflow triggers.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uHCK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uHCK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 424w, https://substackcdn.com/image/fetch/$s_!uHCK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 848w, https://substackcdn.com/image/fetch/$s_!uHCK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 1272w, https://substackcdn.com/image/fetch/$s_!uHCK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uHCK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png" width="566" height="334.3131868131868" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:860,&quot;width&quot;:1456,&quot;resizeWidth&quot;:566,&quot;bytes&quot;:526071,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/201729122?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uHCK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 424w, https://substackcdn.com/image/fetch/$s_!uHCK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 848w, https://substackcdn.com/image/fetch/$s_!uHCK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 1272w, https://substackcdn.com/image/fetch/$s_!uHCK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F966eb93d-1b66-43c1-8485-362a1e507b59_5500x3250.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/week-5-background-processing-control">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 128: Mobile Optimization — Building a Touch-First Infrastructure Dashboard]]></title><description><![CDATA[&#8594; Subscribe now to Access All paid lessons & private source code repository - 250 + coding lessons.]]></description><link>https://fullstackinfra.substack.com/p/day-128-mobile-optimization-building</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-128-mobile-optimization-building</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Thu, 02 Jul 2026 05:30:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Unl5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="callout-block" data-callout="true"><p>&#8594; <a href="https://fullstackinfra.substack.com/subscribe">Subscribe</a> now to Access All paid lessons &amp; private source code repository - 250 + coding lessons.</p><p>Are you preparing for <strong>Cracking FAANG interviews</strong> ? Here is full Preparation kit: <a href="https://systemdrd.com/ebooks/52-faang-questions-drillcards-cheatsheets/">52 FAANG System Design Interview Questions</a></p><p>&#8594;<a href="https://systemdrd.com/ebooks/sdcourse-distributed-systems-interview">Download the free Interview Pack</a></p></div><h2>What We&#8217;re Building Today</h2><p>By the end of this lesson you will have a fully mobile-optimized InfraWatch dashboard that feels and behaves like a native app. Here&#8217;s the agenda:</p><ul><li><p>A bottom navigation bar with badge counts &#8212; the pattern used by PagerDuty, Datadog mobile, and New Relic</p></li><li><p>Swipe-to-dismiss gesture on alert items &#8212; no buttons, just touch</p></li><li><p>Pull-to-refresh with a distance threshold and haptic feedback</p></li><li><p>Safe area insets so the UI doesn&#8217;t get swallowed by iPhone notches and Android chin bars</p></li><li><p>44px minimum touch targets throughout &#8212; the Apple HIG and Google Material Design minimum</p></li><li><p>Real system metrics from <code>psutil</code> served by FastAPI, updating every 30 seconds</p></li></ul><p>This builds directly on Day 127 (PWA), taking the installable shell and making it genuinely usable with one thumb.</p><div><hr></div><h2>Why Mobile Optimization Is a Serious Engineering Problem</h2><p>Every DevOps engineer has been there: a 3 a.m. page on your phone, you open the dashboard, and the table is a pixel-wide soup of compressed columns you can&#8217;t read, let alone tap. Grafana, Datadog, and PagerDuty all shipped dedicated mobile apps or heavily invested in mobile web precisely because an unoptimized monitoring dashboard under incident pressure is actively dangerous &#8212; you mis-read a number, you tap the wrong button.</p><p>Mobile optimization is not just &#8220;make it responsive.&#8221; It covers three separate problems:</p><ol><li><p><strong>Input model mismatch</strong> &#8212; Touch has no hover state, no right-click, 50ms tap delay (unless you remove it), and a 300ms ghost-click issue browsers introduced in 2014 that still bites developers who don&#8217;t set <code>touch-action: manipulation</code>.</p></li><li><p><strong>Layout model</strong> &#8212; <code>100vh</code> breaks on iOS Safari when the address bar shows/hides. The correct unit is <code>100dvh</code> (dynamic viewport height), available since Safari 15.4.</p></li><li><p><strong>Interaction affordance</strong> &#8212; A user cannot swipe something they don&#8217;t know is swipeable. Design for discoverability: a slight peek of the red dismiss background before the user even swipes tells them what will happen.</p></li></ol><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Unl5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Unl5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 424w, https://substackcdn.com/image/fetch/$s_!Unl5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 848w, https://substackcdn.com/image/fetch/$s_!Unl5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 1272w, https://substackcdn.com/image/fetch/$s_!Unl5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Unl5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png" width="1100" height="715" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:715,&quot;width&quot;:1100,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:238239,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/189636788?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Unl5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 424w, https://substackcdn.com/image/fetch/$s_!Unl5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 848w, https://substackcdn.com/image/fetch/$s_!Unl5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 1272w, https://substackcdn.com/image/fetch/$s_!Unl5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e99036c-78c8-4caa-8824-b36e0e980ce0_1100x715.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="callout-block" data-callout="true"><p>&#8594; <a href="https://fullstackinfra.substack.com/subscribe">Subscribe</a> now to Access All paid lessons &amp; private source code repository - 250 + coding lessons.</p><p>Are you preparing for <strong>Cracking FAANG interviews</strong> ? Here is full Preparation kit: <a href="https://systemdrd.com/ebooks/52-faang-questions-drillcards-cheatsheets/">52 FAANG System Design Interview Questions</a></p><p>&#8594;<a href="https://systemdrd.com/ebooks/sdcourse-distributed-systems-interview">Download the free Interview Pack</a></p></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-128-mobile-optimization-building">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 127: Progressive Web App]]></title><description><![CDATA[What We Are Building Today]]></description><link>https://fullstackinfra.substack.com/p/day-127-progressive-web-app</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-127-progressive-web-app</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Tue, 30 Jun 2026 06:30:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5iQr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We Are Building Today</h2><p>By the end of today, InfraWatch will install on a phone or desktop like a native app, work during a network outage using cached data, receive push alerts from our FastAPI backend, and silently queue failed API calls for retry when connectivity returns. Here is what we cover:</p><ul><li><p>PWA manifest and installability via <code>vite-plugin-pwa</code></p></li><li><p>A hand-written service worker with four caching strategies</p></li><li><p>VAPID key generation and push notification dispatch from FastAPI</p></li><li><p>Background sync queue for offline action replay</p></li><li><p>The App Shell architecture that makes InfraWatch open in under 200 ms</p></li></ul><div><hr></div><h2>Part One: Concepts</h2><div><hr></div><h3>The Three Pillars</h3><p>A Progressive Web App is any web app that ships three things together: a <strong>Web App Manifest</strong> (JSON file declaring the app identity), a <strong>Service Worker</strong> (a background JavaScript thread with network interception power), and a secure context (<strong>HTTPS</strong> or localhost). That is the complete definition. The rest &#8212; push notifications, background sync, install prompts &#8212; layers on top.</p><p>Twitter Lite, Pinterest, and Starbucks were not built by rewriting their apps. They added these three things and unlocked 30&#8211;70% improvements in engagement metrics.</p><div><hr></div><h3>Service Worker: A Programmable Network Proxy</h3><p>The most accurate mental model for a service worker is a <strong>programmable reverse proxy embedded inside the browser</strong>. Every <code>fetch()</code> call from your React app passes through it. Once active, the SW controls all network traffic from its scope, across all open tabs matching that scope.</p><p>The lifecycle has four states to internalize:</p><p>State What Happens <strong>Installing</strong> SW downloads; <code>install</code> event fires; you pre-cache the app shell <strong>Waiting</strong> Old SW still controls pages; new SW waits for all tabs to close <strong>Activating</strong> New SW takes over; <code>activate</code> fires; old caches are pruned <strong>Activated</strong> SW intercepts every <code>fetch</code>; caching logic runs here</p><p>The &#8220;waiting&#8221; state is where most PWA bugs appear. A new SW silently sits installed but inactive until all pages using the old SW close. Calling <code>self.skipWaiting()</code> in <code>install</code> bypasses this &#8212; useful in development, handled carefully in production.</p><div><hr></div><h3>Four Caching Strategies &#8212; Choosing a Policy</h3><p>Every caching strategy is a tradeoff between <strong>freshness</strong> and <strong>availability</strong>. Pick based on how often your data changes.</p><p><strong>Cache-First</strong> &#8212; For static assets (JS bundles, fonts, icons). Check cache first; if missing, fetch from network and cache it. The InfraWatch app shell uses this &#8212; the skeleton renders in under 100 ms even on a slow connection.</p><p><strong>Network-First</strong> &#8212; For API data. Always attempt a live request. On failure, serve the cached response. InfraWatch uses this for metric data &#8212; you always see the most recent numbers available.</p><p><strong>Stale-While-Revalidate</strong> &#8212; Serve cache immediately, update in background. Twitter&#8217;s timeline uses this &#8212; you see last-known content instantly while new data arrives silently.</p><p><strong>Cache-Only</strong> &#8212; Serve from cache only, never touch the network. Used for the offline fallback page.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5iQr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5iQr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 424w, https://substackcdn.com/image/fetch/$s_!5iQr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 848w, https://substackcdn.com/image/fetch/$s_!5iQr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 1272w, https://substackcdn.com/image/fetch/$s_!5iQr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5iQr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png" width="1237" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1237,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:160529,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/193326814?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5iQr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 424w, https://substackcdn.com/image/fetch/$s_!5iQr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 848w, https://substackcdn.com/image/fetch/$s_!5iQr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 1272w, https://substackcdn.com/image/fetch/$s_!5iQr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2292f8e1-d8f3-47b2-8a1c-d0e66f78903c_1237x770.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-127-progressive-web-app">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Week 4: Metrics Collection Platform]]></title><description><![CDATA[&#128452;&#65039; 52 FAANG Interview Question Vault &#8212; $299 one-time]]></description><link>https://fullstackinfra.substack.com/p/week-4-metrics-collection-platform</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/week-4-metrics-collection-platform</guid><dc:creator><![CDATA[ctoi]]></dc:creator><pubDate>Sat, 27 Jun 2026 02:20:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!WMJ3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="callout-block" data-callout="true"><p><span>&#128452;&#65039; </span><strong>52 FAANG Interview Question Vault</strong><span> &#8212; $299 one-time</span></p><p>All 52 walkthroughs + 52 drill cards + 6 framework cheatsheets + the Senior+ System Design Playbook. Everything, immediately. Lifetime access with all future updates included.</p><p><span>&#8594; [</span><a href="https://systemdrd.com/ebooks/52-faang-questions-drillcards-cheatsheets/">VAULT CHECKOUT LINK</a><span>]</span></p></div><h2><strong>What We Are Building</strong></h2><blockquote><p>This lesson delivers a production-grade <strong>metrics collection and observability console</strong> for InfraWatch:</p></blockquote><ol><li><p><strong>Time-series schema</strong> &#8212; Raw points, retention policies, metric catalog, and pre-aggregated rollups</p></li><li><p><strong>Collection pipeline</strong> &#8212; Agent protocol, validation, batch ingestion, and scheduled collection</p></li><li><p><strong>Storage layer</strong> &#8212; PostgreSQL persistence with Redis hot-path cache and pub/sub streaming</p></li><li><p><strong>Aggregation engine</strong> &#8212; Sliding-window statistics, historical rollups, and trend detection</p></li><li><p><strong>Query API</strong> &#8212; Time-range queries with caching, CSV/JSON export, and summary endpoints</p></li><li><p><strong>Observability console</strong> &#8212; Datadog-style dashboard with live charts and agent status</p></li></ol><div><hr></div><h2><strong>Core Concept: Write Path vs Read Path</strong></h2><blockquote><p>Production metrics systems split into two lanes. The <strong>write path</strong> accepts high-volume, fire-and-forget data points from agents&#8212;validate, index, persist, and push to a hot cache in milliseconds. The <strong>read path</strong> serves dashboards and alerts&#8212;query pre-aggregated buckets first, fall back to raw data only when needed, and cache results by time range.</p><p>The insight most tutorials skip: <strong>retention is a write-time decision, not a cleanup cron job.</strong> When a point lands, the system already knows its tier (<code>realtime</code>, <code>recent</code>, <code>daily</code>) and expiry date based on metric name patterns. Datadog and Grafana Cloud use this pattern&#8212;policy resolution at ingest keeps query performance predictable as volume grows.</p></blockquote><div><hr></div><h2><strong>Where This Fits in the Overall System</strong></h2><pre><code><code>Server Fleet Management        inventory spine &#8212; agents attach here
        &#9474;
Metrics Collection             &#9664; this lesson
        &#9474;
Alert Management               rules evaluate against stored series
        &#9474;
Notification System            delivers when thresholds breach
</code></code></pre><blockquote><p>You are building the <strong>telemetry pipeline</strong> that turns raw host signals into queryable time-series. Every alert rule and capacity plan downstream assumes this layer exists and returns data within SLA.</p></blockquote><div><hr></div><h2><strong>Component Architecture</strong></h2><blockquote><p><strong>Agents</strong> &#8212; Lightweight collectors (psutil-based) push validated batches over WebSocket every five seconds.</p><p><strong>Ingestion</strong> &#8212; Validator rejects out-of-range values; ingester dual-writes to PostgreSQL and Redis; catalog index updates on every point.</p><p><strong>Aggregation</strong> &#8212; Background collector plus sliding-window engine compute avg/min/max/p95; rollups compress raw data into 1m/5m/1h buckets.</p><p><strong>Query API</strong> &#8212; FastAPI serves cached queries, exports, summaries, and SSE streams for live dashboards.</p><p><strong>Console</strong> &#8212; Dark-theme React UI polls REST every five seconds and receives WebSocket push on new points.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WMJ3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WMJ3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 424w, https://substackcdn.com/image/fetch/$s_!WMJ3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 848w, https://substackcdn.com/image/fetch/$s_!WMJ3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 1272w, https://substackcdn.com/image/fetch/$s_!WMJ3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WMJ3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png" width="534" height="346.9532967032967" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:946,&quot;width&quot;:1456,&quot;resizeWidth&quot;:534,&quot;bytes&quot;:860127,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/201729111?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WMJ3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 424w, https://substackcdn.com/image/fetch/$s_!WMJ3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 848w, https://substackcdn.com/image/fetch/$s_!WMJ3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 1272w, https://substackcdn.com/image/fetch/$s_!WMJ3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5cd3d6-a7b4-490b-880c-df3328623a42_6000x3900.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/week-4-metrics-collection-platform">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 126 — API Integration: The Moment of Truth]]></title><description><![CDATA[What We&#8217;re Building Today]]></description><link>https://fullstackinfra.substack.com/p/day-126-api-integration-the-moment</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-126-api-integration-the-moment</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Thu, 25 Jun 2026 06:31:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4Zmc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We&#8217;re Building Today</h2><ul><li><p>A <strong>live FastAPI server</strong> with 18 documented endpoints &#8212; auth, users, teams, metrics, security</p></li><li><p>A <strong>Python SDK</strong> (<code>InfraStackClient</code>) with chainable resource namespaces, typed errors, and exponential backoff</p></li><li><p><strong>45 integration tests</strong> across 5 suites &#8212; all passing before we move on</p></li><li><p>A <strong>React dashboard</strong> styled after Postman&#8217;s collection runner &#8212; live test execution, latency charts, status distribution</p></li><li><p>A single self-contained <code>setup.sh</code> that builds, tests, and demos the whole thing in one command</p></li></ul><p><strong>Prerequisites:</strong> Python 3.11+, Node.js 20+, Docker (optional) <strong>Completed:</strong> Days 122&#8211;125 (API Security, Documentation, Testing, SDKs)</p><div><hr></div><h2>Project Structure</h2><p>Everything in this project is created by <code>setup.sh</code> &#8212; you don&#8217;t manually create anything.</p><pre><code><code>day126/
&#9500;&#9472;&#9472; setup.sh                   # one-command master script
&#9500;&#9472;&#9472; docker-compose.yml
&#9500;&#9472;&#9472; backend/
&#9474;   &#9500;&#9472;&#9472; main.py                # FastAPI app (18 endpoints)
&#9474;   &#9500;&#9472;&#9472; requirements.txt
&#9474;   &#9500;&#9472;&#9472; sdk/
&#9474;   &#9474;   &#9492;&#9472;&#9472; client.py          # Python SDK (InfraStackClient)
&#9474;   &#9492;&#9472;&#9472; tests/
&#9474;       &#9500;&#9472;&#9472; conftest.py
&#9474;       &#9492;&#9472;&#9472; test_integration.py  # 45 tests, 5 suites
&#9500;&#9472;&#9472; frontend/
&#9474;   &#9500;&#9472;&#9472; package.json
&#9474;   &#9492;&#9472;&#9472; src/App.jsx            # React dashboard
&#9492;&#9472;&#9472; diagrams/
</code></code></pre><div><hr></div><h2>Why Integration Testing Deserves Its Own Day</h2><blockquote><p>You can have 100% unit test coverage and still ship a broken API. Unit tests verify functions in isolation; integration tests verify the <strong>system as a whole</strong> &#8212; authentication gates holding, database writes surviving network roundtrips, SDK error handling matching actual server behavior.</p><p>Stripe&#8217;s payment API has a dedicated integration sandbox because even minor contract drift between client and server can cause millions in failed transactions. GitHub&#8217;s API runs end-to-end tests on every deployment. This isn&#8217;t paranoia &#8212; it&#8217;s engineering maturity.</p><p>Today we prove our Week 11 API stack actually works, together. Every piece we built across Days 120&#8211;125 gets validated end-to-end: auth, users, teams, metrics, security headers, SDK contracts, documentation coverage, and latency baselines.</p></blockquote><div><hr></div><h2>The Five Test Dimensions</h2><p>Every production API needs validation along five distinct axes. Miss any one and you ship blind. Our 45-test suite covers all five &#8212; a standard you&#8217;ll carry into every job you take.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4Zmc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4Zmc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 424w, https://substackcdn.com/image/fetch/$s_!4Zmc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 848w, https://substackcdn.com/image/fetch/$s_!4Zmc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 1272w, https://substackcdn.com/image/fetch/$s_!4Zmc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4Zmc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png" width="975" height="750" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:750,&quot;width&quot;:975,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:138079,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/192284033?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4Zmc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 424w, https://substackcdn.com/image/fetch/$s_!4Zmc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 848w, https://substackcdn.com/image/fetch/$s_!4Zmc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 1272w, https://substackcdn.com/image/fetch/$s_!4Zmc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd552c23-51ca-44fb-a30d-063fea766cc2_975x750.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-126-api-integration-the-moment">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Week 3 : Server Fleet Management]]></title><description><![CDATA[&#128452;&#65039; 52 FAANG Interview Question Vault &#8212; $299 one-time]]></description><link>https://fullstackinfra.substack.com/p/week-3-server-fleet-management</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/week-3-server-fleet-management</guid><dc:creator><![CDATA[ctoi]]></dc:creator><pubDate>Wed, 24 Jun 2026 00:35:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!K8yI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="callout-block" data-callout="true"><p>&#128452;&#65039; <strong>52 FAANG Interview Question Vault</strong> &#8212; $299 one-time </p><p>All 52 walkthroughs + 52 drill cards + 6 framework cheatsheets + the Senior+ System Design Playbook. Everything, immediately. Lifetime access with all future updates included.</p><p>&#8594; [<a href="https://systemdrd.com/ebooks/52-faang-questions-drillcards-cheatsheets/">VAULT CHECKOUT LINK</a>]</p></div><h2><strong>What We Are Building</strong></h2><blockquote><p>This lesson delivers a production-grade <strong>server fleet control plane</strong> for InfraWatch:</p></blockquote><ol><li><p><strong>Inventory domain</strong> &#8212; Rich server models with lifecycle state, health telemetry, tags, dependencies, and audit history</p></li><li><p><strong>Operations API</strong> &#8212; CRUD with pagination, soft-delete, advanced search, bulk actions, groups, and templates</p></li><li><p><strong>Validation layer</strong> &#8212; IP/hostname checks, TCP connectivity, HTTP health, SSL expiry, and network discovery</p></li><li><p><strong>Credential plane</strong> &#8212; SSH key generation, encrypted storage, rotation, and connection testing</p></li><li><p><strong>Live console</strong> &#8212; Datadog-style React dashboard with real-time WebSocket health updates</p></li></ol><div><hr></div><h2><strong>Core Concept: Inventory as the Source of Truth</strong></h2><blockquote><p>Every metrics pipeline, alert rule, and automation workflow in a platform like Datadog or PagerDuty starts with one question: <strong>which servers exist right now, and are they reachable?</strong> Server inventory is not a spreadsheet&#8212;it is the authoritative graph that downstream systems query before they act.</p><p>The insight most tutorials skip: <strong>lifecycle state and health state are different axes.</strong> A server can be <code>active</code> in provisioning terms but <code>degraded</code> in health terms. Mixing them into one <code>status</code> field breaks autoscaling logic (which cares about lifecycle) and paging logic (which cares about health). InfraWatch Fleet keeps both dimensions explicit.</p></blockquote><div><hr></div><h2><strong>Where This Fits in the Overall System</strong></h2><pre><code><code>Authentication Foundation     identity gate for all API calls
        &#9474;
Server Fleet Management       &#9664; this lesson
        &#9474;
Metrics Collection            time-series from known inventory
        &#9474;
Alerts &amp; Notifications        rules evaluated per server identity
</code></code></pre><blockquote><p>You are building the <strong>inventory spine</strong> that metrics agents attach to and alert engines evaluate against. Without it, alerts fire on hostnames nobody registered&#8212;or worse, on hosts that were decommissioned last week.</p></blockquote><div><hr></div><h2><strong>Component Architecture</strong></h2><blockquote><p><strong>Presentation</strong> &#8212; Dark-theme React console: fleet overview metrics, searchable inventory grid, validation tools, credential manager. Polls REST every 15 seconds; WebSocket pushes health deltas instantly.</p><p><strong>API</strong> &#8212; Flask blueprints under <code>/api/v1</code>. Thin routes delegate to services: <code>server_service</code>, <code>health_service</code>, <code>ssh_service</code>, <code>bulk_service</code>.</p><p><strong>State</strong> &#8212; PostgreSQL stores servers, tags, groups, templates, audit logs, SSH keys. Redis caches health snapshots and increments check counters.</p><p><strong>Runtime</strong> &#8212; Background health monitor runs every 30 seconds. Docker Compose orchestrates Postgres 16, Redis 7, API, and UI.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K8yI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K8yI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 424w, https://substackcdn.com/image/fetch/$s_!K8yI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 848w, https://substackcdn.com/image/fetch/$s_!K8yI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 1272w, https://substackcdn.com/image/fetch/$s_!K8yI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K8yI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png" width="1456" height="823" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:823,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:586236,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/201422451?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K8yI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 424w, https://substackcdn.com/image/fetch/$s_!K8yI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 848w, https://substackcdn.com/image/fetch/$s_!K8yI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 1272w, https://substackcdn.com/image/fetch/$s_!K8yI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17053970-de21-42cb-bde1-29744f8490f1_4600x2600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/week-3-server-fleet-management">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 125: API Client SDKs — The Layer That Makes Your API Trustworthy]]></title><description><![CDATA[Today&#8217;s Agenda]]></description><link>https://fullstackinfra.substack.com/p/day-125-api-client-sdks-the-layer</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-125-api-client-sdks-the-layer</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Tue, 23 Jun 2026 06:31:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!lw5C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Today&#8217;s Agenda</h2><p>By the end of this lesson you will understand:</p><ul><li><p>What an SDK actually is &#8212; and why it is architecturally distinct from a wrapper</p></li><li><p>The three-layer model that every production SDK follows (transport, resources, exceptions)</p></li><li><p>How token lifecycle management works and why it must live inside the SDK</p></li><li><p>The typed exception hierarchy pattern and why it beats raw HTTP status codes</p></li><li><p>How this SDK layer connects everything we built this week into a usable product surface</p></li></ul><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lw5C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lw5C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 424w, https://substackcdn.com/image/fetch/$s_!lw5C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 848w, https://substackcdn.com/image/fetch/$s_!lw5C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 1272w, https://substackcdn.com/image/fetch/$s_!lw5C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lw5C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png" width="1182" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/afcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1182,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147621,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/192184813?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lw5C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 424w, https://substackcdn.com/image/fetch/$s_!lw5C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 848w, https://substackcdn.com/image/fetch/$s_!lw5C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 1272w, https://substackcdn.com/image/fetch/$s_!lw5C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafcd5a9a-d65e-4738-b50b-5282c3520bf6_1182x770.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>What an SDK Actually Is</h2><blockquote><p>Most engineers describe an SDK as &#8220;a wrapper around an API.&#8221; That description is accurate but misleading. A wrapper hides HTTP. An SDK <em>owns the client-side failure surface</em>.</p><p>The distinction matters enormously at scale. When Stripe ships a Python SDK, they are not saving developers the ten minutes it takes to write <code>requests.post()</code>. They are ensuring that every authentication failure, rate limit event, network timeout, and validation error is caught, classified, and surfaced as a typed exception &#8212; consistently, across every company that integrates Stripe, forever.</p><p>Without an SDK, each consumer writes their own error handling. Some check <code>.status_code</code>. Others parse <code>.json()["error"]["message"]</code>. A few catch <code>requests.exceptions.Timeout</code>. The result is thirty different error-handling strategies in thirty codebases, each fragile in its own way. When you push a breaking change to your API response shape, all thirty break in different places.</p><p>With an SDK, there is one place where that contract lives. One patch fixes everyone.</p></blockquote><p>This is the calculation Twilio made in 2008, Stripe made in 2011, and GitHub made with Octokit in 2015. For InfraManager, Day 125 is that same decision point. The Week 11 API is solid. Now we make it trustworthy to integrate.</p><div><hr></div><h2>The Three-Layer SDK Model</h2><p>Every production SDK &#8212; regardless of language, company, or domain &#8212; follows the same three-layer architecture. Understanding these layers is more valuable than understanding any specific implementation.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K7gb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K7gb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 424w, https://substackcdn.com/image/fetch/$s_!K7gb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 848w, https://substackcdn.com/image/fetch/$s_!K7gb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 1272w, https://substackcdn.com/image/fetch/$s_!K7gb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K7gb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png" width="1045" height="852" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:852,&quot;width&quot;:1045,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:129760,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/192184813?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K7gb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 424w, https://substackcdn.com/image/fetch/$s_!K7gb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 848w, https://substackcdn.com/image/fetch/$s_!K7gb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 1272w, https://substackcdn.com/image/fetch/$s_!K7gb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f031626-e81d-44f3-aeb6-6ff8ea435dc3_1045x852.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Layer 1 &#8212; The Transport</h3><p>The transport layer is the engine room. Its job is to convert method calls into HTTP requests and HTTP responses into either data or exceptions. Consumers never interact with it directly.</p><p>The transport handles four responsibilities: building request headers (authentication, content type, API version), enforcing timeouts, managing the connection pool, and handing off error responses to the exception layer.</p><p>In an async SDK (which InfraManager uses), the transport client is async-native from the start. Wrapping a synchronous HTTP client in async code causes subtle deadlocks under load &#8212; a mistake that plagues SDKs built quickly and patched slowly. Our Python transport uses <code>httpx.AsyncClient</code> natively. The JavaScript transport uses <code>fetch</code> with <code>AbortController</code> for timeout enforcement, since the browser&#8217;s native fetch has no built-in timeout.</p><p>One detail worth internalising: <strong>the transport does not retry.</strong> That is a separate concern. The transport makes one clean attempt, captures the result or the exception, and surfaces it. Retry logic lives in the consumer&#8217;s workflow, where it belongs &#8212; because the consumer knows whether a given operation is idempotent.</p><h3>Layer 2 &#8212; The Resources</h3><p>Resources are the public surface of the SDK. They are intentionally thin. A <code>UsersResource</code> exposes <code>list</code>, <code>get</code>, <code>create</code>, <code>update</code>, and <code>delete</code>. Each method maps to exactly one REST verb and path. No business logic lives here.</p><p>The resource layer&#8217;s only responsibility is to accept typed parameters, construct the correct path and payload, call the transport, and deserialize the response into a typed object.</p><p>This thinness is architectural discipline, not laziness. When Stripe&#8217;s Python library adds a <code>stripe.customers.list()</code> method, there is almost no decision-making in that method. The complexity lives in the transport and the data models. Resources stay readable because they are designed to read like API documentation.</p><p>In InfraManager, the three resources &#8212; <code>AuthResource</code>, <code>UsersResource</code>, <code>TeamsResource</code> &#8212; follow this pattern exactly. Each hangs off the central <code>InfraClient</code> object, which is the only import a consumer needs.</p><h3>Layer 3 &#8212; The Exceptions</h3><p>This is where most SDKs are mediocre and a handful are excellent. The difference is almost entirely about whether exceptions carry typed, actionable information or generic HTTP noise.</p><p>Compare these two error-handling experiences:</p><p><strong>Mediocre SDK:</strong></p><pre><code><code>HTTPError: 409 Conflict
</code></code></pre><p><strong>Excellent SDK:</strong></p><pre><code><code>ConflictError: Email already registered
  .status_code = 409
  .message = "Email already registered"
  .request_id = "req_7f3a2c"
</code></code></pre><p>The mediocre SDK forces the consumer to parse error messages as strings. The excellent SDK gives the consumer a named class they can catch precisely, with structured fields they can log, display, or act on.</p><p>InfraManager&#8217;s exception hierarchy maps every meaningful HTTP status code to a named, catchable exception class. All inherit from a single <code>InfraSDKError</code> base, so consumers can catch broadly or narrowly depending on their context:</p><p>HTTP Status Exception Class Actionable Signal 401 <code>AuthenticationError</code> Re-authenticate 403 <code>PermissionError</code> Check RBAC role 404 <code>NotFoundError</code> Handle missing resource 409 <code>ConflictError</code> Duplicate resource 422 <code>ValidationError</code> Check <code>.errors</code> list 429 <code>RateLimitError</code> Wait <code>.retry_after</code> seconds 5xx <code>ServerError</code> Retry with backoff Network <code>NetworkError</code> Check connectivity</p><p>The <code>RateLimitError</code> exception is worth a moment. On Day 121 we built the server-side rate limiting system. Today, the SDK reads the <code>Retry-After</code> header from the 429 response and populates <code>exception.retry_after</code> &#8212; so the consumer has everything they need without parsing HTTP headers manually.</p><div><hr></div><h2>Token Lifecycle: Why It Lives Inside the SDK</h2><p>Token management is the most commonly misimplemented part of API integration. Most teams either pass tokens in every call (verbose), refresh them too aggressively (wasteful), or refresh them too lazily (broken sessions).</p><p>The correct pattern is a <code>TokenStore</code> inside the transport that handles this invisibly:</p><p>When <code>client.auth.login()</code> succeeds, the JWT is stored internally with a TTL set to the token&#8217;s expiry minus a 60-second buffer. Every subsequent request checks whether the stored token is still valid before injecting the <code>Authorization</code> header. If it has expired, the store returns nothing &#8212; and the transport omits the header, causing the server to return 401, which surfaces to the consumer as <code>AuthenticationError</code>. They then call <code>client.auth.login()</code> again and the cycle continues.</p><p>The consumer never touches a token string after the initial login. This is the experience GitHub&#8217;s Octokit delivers, and it is one of the primary reasons Octokit adoption overtook raw <code>fetch</code> calls within GitHub&#8217;s own developer community.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wqNN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wqNN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 424w, https://substackcdn.com/image/fetch/$s_!wqNN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 848w, https://substackcdn.com/image/fetch/$s_!wqNN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 1272w, https://substackcdn.com/image/fetch/$s_!wqNN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wqNN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png" width="1045" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1045,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:163745,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/192184813?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wqNN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 424w, https://substackcdn.com/image/fetch/$s_!wqNN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 848w, https://substackcdn.com/image/fetch/$s_!wqNN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 1272w, https://substackcdn.com/image/fetch/$s_!wqNN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3752c4c-b69e-4a18-96c4-7409bb745fb3_1045x770.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The auth state machine has five named states: <code>UNAUTHENTICATED</code>, <code>AUTHENTICATING</code>, <code>AUTHENTICATED</code>, <code>TOKEN_EXPIRED</code>, and <code>LOGGED_OUT</code>. The transitions between them are deterministic. When you call <code>client.auth.logout()</code>, the store is cleared and the client returns to <code>UNAUTHENTICATED</code>. When TTL elapses, the next request attempt discovers <code>TOKEN_EXPIRED</code> and surfaces an <code>AuthenticationError</code>. No silent failures. No stale tokens silently accepted.</p><div><hr></div><h2>Python vs JavaScript SDK: Same Contract, Different Runtimes</h2><p>InfraManager ships SDKs in both Python and JavaScript with intentionally identical public surface areas. A Python developer and a JavaScript developer looking at the SDK documentation see the same resources, the same exception names, the same method signatures.</p><p>The internal differences are runtime-specific:</p><p><strong>Python SDK</strong> &#8212; uses <code>httpx.AsyncClient</code> with Python&#8217;s native <code>asyncio</code>. The entire SDK is async-native with <code>async/await</code>. Consumers use it inside <code>async def</code> functions or <code>asyncio.run()</code>.</p><p><strong>JavaScript SDK</strong> &#8212; uses the <code>fetch</code> API with <code>AbortController</code> for timeout enforcement. Ships as ESM (ECMAScript Modules) so it works in Node 18+, modern browsers, and Cloudflare Workers without bundler configuration.</p><p>Both SDKs use <code>TokenStore</code> for credential management. Both define the same exception hierarchy by name. Both auto-inject the <code>Authorization</code> header. The runtime differences are fully encapsulated &#8212; the consumer experience is identical.</p><p>This is the same commitment GitHub makes with Octokit &#8212; <code>@octokit/rest</code> for JavaScript and <code>octokit.py</code> for Python share the same design language &#8212; and it is a significant adoption driver because teams that use both languages only need to learn the SDK once.</p><div><hr></div><h2>SDK Testing: Why ASGITransport Changes Everything</h2><p>SDK testing has a well-known trap: if you mock the HTTP layer, you are testing the mock, not the SDK. If you spin up a real server for tests, you add infrastructure dependencies and flakiness.</p><p>The solution for FastAPI-backed SDKs is <code>httpx.AsyncClient</code> with <code>ASGITransport</code>. This routes the SDK&#8217;s HTTP calls through the FastAPI app <em>in-process</em>, with no sockets, no ports, and no process management. The SDK executes identically to how it would against a live server &#8212; same middleware, same ORM, same exception propagation &#8212; but the tests are hermetic and fast.</p><p>This matters architecturally: the 36 tests on Day 125 found two real bugs that mocks would have missed:</p><ol><li><p>A duplicate route registration in the auth router where a placeholder <code>GET /me</code> handler returned <code>None</code>, shadowing the real authenticated handler. In production this would have caused every <code>/me</code> call to return a 500.</p></li><li><p>SQLAlchemy lazy-loading a relationship synchronously inside an async ASGI context. <code>len(team.members)</code> looks harmless in code review but triggers a synchronous DB call that raises <code>MissingGreenlet</code> under ASGI transport. The fix &#8212; an explicit <code>SELECT COUNT(*)</code> against the association table &#8212; is architecturally correct for async ORMs and would never have been caught by HTTP mocks.</p></li></ol><p>The test suite is layered into three categories that mirror production concerns:</p><p><strong>Unit tests</strong> validate the exception hierarchy in isolation &#8212; no database, no HTTP, no FastAPI. Nine tests verify that <code>raise_for_status(401, ...)</code> raises <code>AuthenticationError</code>, that <code>RateLimitError.retry_after</code> populates correctly, and that the inheritance chain is intact. These run in milliseconds.</p><p><strong>Integration tests</strong> validate every resource method against the live app &#8212; register, login, create user, update role, add team member, delete. Twenty-two tests cover the full happy path and all error paths.</p><p><strong>Contract tests</strong> validate response shape &#8212; that <code>User.id</code> is a string, <code>User.is_active</code> is a boolean, <code>UserList.total</code> is an integer. Five tests. These guard against silent schema drift that would break consumers without failing unit or integration tests.</p><div><hr></div><h2>The InfraManager SDK Playground Dashboard</h2><h3><strong>GitHub Link:-</strong></h3><p><a href="https://github.com/sysdr/infrawatch-fullstack-p/tree/main/day125/project">https://github.com/sysdr/infrawatch-fullstack-p/tree/main/day125/project</a></p><p>The React dashboard at port 3000 is not a demo &#8212; it is a functional internal tool built on top of the same JavaScript SDK that ships to consumers. It demonstrates four capabilities:</p><p><strong>Users tab</strong> &#8212; live paginated user list, role-filtered views, create form with immediate feedback, delete with optimistic UI. All mutations go through <code>client.users.create()</code> and <code>client.users.delete()</code> &#8212; the same SDK calls a consumer would write.</p><p><strong>Teams tab</strong> &#8212; team cards with live member counts, add-member dropdown that cross-references the user list, instant count updates after member operations.</p><p><strong>SDK Explorer</strong> &#8212; syntax-highlighted, copy-ready code examples in both Python and JavaScript for every common workflow: login, list users, create team, add member, error handling.</p><p><strong>API Docs tab</strong> &#8212; the full endpoint reference table generated from our Week 11 API surface, showing method, path, and description for all 15 endpoints.</p><p>The dashboard&#8217;s dark green aesthetic is intentional &#8212; it mirrors the design language of professional infrastructure tooling (Datadog, Grafana, AWS console) rather than generic admin UIs. Engineers who use professional tools respond better to dashboards that look like professional tools.</p><div><hr></div><h2>Key Design Decisions Worth Remembering</h2><p><strong>Single entry point.</strong> <code>InfraClient</code> is the only thing a consumer imports. All resources hang off it. This mirrors Stripe (<code>stripe.customers.create()</code>), GitHub (<code>octokit.rest.users.list()</code>), and Twilio (<code>client.messages.create()</code>). A single entry point means one object to configure, one context manager to use, one thing to mock in tests.</p><p><strong>Async context manager.</strong> <code>async with InfraClient("http://...") as client:</code> ensures the underlying connection pool is properly closed. This is not a nice-to-have &#8212; unclosed <code>httpx.AsyncClient</code> instances in production cause connection pool exhaustion over time.</p><p><strong>No retry logic in the SDK.</strong> The SDK classifies errors and surfaces them cleanly. Retry decisions belong to the consumer because they know the idempotency characteristics of their workflow. An SDK that retries opaquely creates silent duplicate operations in non-idempotent contexts.</p><p><strong>Typed dataclasses, not raw dicts.</strong> Every response is deserialized into a Python dataclass or JavaScript object with named properties. <code>user.email</code> is safer than <code>response["email"]</code> &#8212; it fails loudly at the point of deserialization rather than silently returning <code>undefined</code> or raising <code>KeyError</code> somewhere downstream.</p><div><hr></div><h2>Assignment</h2><p>Extend the Python SDK with a <code>SearchResource</code> that wraps a <code>GET /api/v1/users/search?q=&lt;query&gt;</code> endpoint.</p><p>The endpoint should filter users by <code>full_name</code> or <code>email</code> using a case-insensitive SQL <code>LIKE</code> query. The SDK resource should follow the same pattern as <code>UsersResource</code> &#8212; a single <code>search(query: str) -&gt; list[User]</code> method. Wire it into <code>InfraClient</code> as <code>client.search</code>.</p><p>Write three tests:</p><ol><li><p>A search that matches at least one user returns a non-empty list of typed <code>User</code> objects</p></li><li><p>A search with no matches returns an empty list without raising an exception</p></li><li><p>An unauthenticated search raises <code>AuthenticationError</code></p></li></ol><p><strong>Hints:</strong></p><ul><li><p>Backend filter: <code>User.full_name.ilike(f"%{q}%") | User.email.ilike(f"%{q}%")</code></p></li><li><p>Return the same <code>{"items": [...], "total": N}</code> envelope as the users list endpoint</p></li><li><p>The <code>SearchResource</code> is six lines of code if you pattern-match against <code>UsersResource.list()</code></p></li></ul><div><hr></div><h2>Success Criteria</h2><p>You have completed Day 125 when:</p><ul><li><p><code>pytest sdk/python/tests/test_sdk.py</code> reports <strong>36 passed, 0 failed</strong></p></li><li><p>The React dashboard loads at </p></li></ul><p>http://localhost:3000</p><ul><li><p>, you can log in and interact with Users and Teams</p></li><li><p>You can copy the Python SDK example from the SDK Explorer tab, paste it into a Python shell, run it against the live backend, and see real data returned as typed objects</p></li><li><p>You understand why <code>TokenStore</code> must live inside the transport and not be managed by the consumer</p></li><li><p>You can explain the difference between a transport error, a resource method call, and an exception class &#8212; and which layer owns each</p></li></ul><h3><strong>YouTube Demo Link:-</strong></h3><div id="youtube2-PshvgAp8Rpk" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;PshvgAp8Rpk&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/PshvgAp8Rpk?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div>]]></content:encoded></item><item><title><![CDATA[Day 124 — API Testing: Building a Production-Grade Test Suite]]></title><description><![CDATA[What We Are Building Today]]></description><link>https://fullstackinfra.substack.com/p/day-124-api-testing-building-a-production</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-124-api-testing-building-a-production</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Thu, 18 Jun 2026 06:39:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!L9jc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We Are Building Today</h2><blockquote><p>By the end of this lesson you will have a working, automated API test suite for the User &amp; Team Management backend we built across Week 11. The suite covers five distinct testing layers &#8212; unit, integration, contract, load, and security &#8212; all wired together under a single <code>setup.sh</code> script that outputs results to a live React dashboard. Nothing is mocked. Every test hits the real FastAPI server.</p></blockquote><p><strong>Today&#8217;s agenda:</strong></p><ul><li><p>Understand the API testing pyramid and why each layer exists</p></li><li><p>Write unit and integration tests with pytest and httpx</p></li><li><p>Implement JSON Schema contract testing</p></li><li><p>Run Locust load tests and capture p95 latency</p></li><li><p>Run bandit security scans and custom OWASP surface checks</p></li><li><p>Combine all results and stream them into a React test dashboard</p></li></ul><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L9jc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L9jc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 424w, https://substackcdn.com/image/fetch/$s_!L9jc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 848w, https://substackcdn.com/image/fetch/$s_!L9jc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 1272w, https://substackcdn.com/image/fetch/$s_!L9jc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L9jc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png" width="1125" height="775" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:775,&quot;width&quot;:1125,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:187358,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/193547885?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!L9jc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 424w, https://substackcdn.com/image/fetch/$s_!L9jc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 848w, https://substackcdn.com/image/fetch/$s_!L9jc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 1272w, https://substackcdn.com/image/fetch/$s_!L9jc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F58cb38d5-801f-434a-bbea-3b62ccc37f8c_1125x775.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="callout-block" data-callout="true"><p>Preparing for a distributed systems interview?</p><p>&#8594;<a href="https://systemdrd.com/ebooks/sdcourse-distributed-systems-interview">Download the free Interview Pack</a></p><p>&#8594; <a href="https://fullstackinfra.substack.com/subscribe">Subscribe</a> now to access source code repository - 200 + coding lessons</p></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-124-api-testing-building-a-production">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Day 123 — API Documentation: The Contract That Ships With Your Code ]]></title><description><![CDATA[What We Build Today]]></description><link>https://fullstackinfra.substack.com/p/day-123-api-documentation-the-contract</link><guid isPermaLink="false">https://fullstackinfra.substack.com/p/day-123-api-documentation-the-contract</guid><dc:creator><![CDATA[systemdrll165]]></dc:creator><pubDate>Tue, 16 Jun 2026 06:30:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5plm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What We Build Today</h2><blockquote><p>By the end of this lesson you will have a fully operational API documentation portal &#8212; the kind that makes engineers trust your API before they write a single line of code against it. Here is what gets built:</p></blockquote><ul><li><p>A <strong>versioned OpenAPI 3.1 specification</strong> generated programmatically for both v1 (deprecated) and v2 (current) API surfaces, complete with security schemes, contact info, and server blocks.</p></li><li><p>A <strong>code sample engine</strong> that generates Python, JavaScript, and cURL snippets for every endpoint automatically &#8212; no manual copy-paste.</p></li><li><p>A <strong>deprecation lifecycle system</strong> that brands the v1 spec with <code>deprecated: true</code>, <code>x-sunset-date</code>, <code>Deprecation</code> and <code>Sunset</code> response headers, and a <code>Link: successor-version</code> header on every response.</p></li><li><p>An <strong>InfraDocs React portal</strong> &#8212; a dark-mode API explorer that mirrors the interaction patterns of Stripe Docs and ReadMe.io, with a Try It panel, SDK snippet generator, and version comparison view.</p></li></ul><div><hr></div><p><strong>Prerequisites</strong></p><ul><li><p>Day 122 complete &#8212; API key management and auth patterns are in place</p></li><li><p>Python 3.11+, Node.js 20+, Docker (optional but recommended)</p></li><li><p>Comfortable with FastAPI routing and Pydantic models</p></li></ul><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5plm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5plm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 424w, https://substackcdn.com/image/fetch/$s_!5plm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 848w, https://substackcdn.com/image/fetch/$s_!5plm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 1272w, https://substackcdn.com/image/fetch/$s_!5plm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5plm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png" width="1182" height="770" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:770,&quot;width&quot;:1182,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:334250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://fullstackinfra.substack.com/i/191089263?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5plm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 424w, https://substackcdn.com/image/fetch/$s_!5plm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 848w, https://substackcdn.com/image/fetch/$s_!5plm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 1272w, https://substackcdn.com/image/fetch/$s_!5plm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F252380b5-da8e-438b-a2eb-6112c8beaed8_1182x770.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="callout-block" data-callout="true"><p>Preparing for a distributed systems interview?</p><p>&#8594;<a href="https://systemdrd.com/ebooks/sdcourse-distributed-systems-interview">Download the free Interview Pack</a></p><p>&#8594; <a href="https://fullstackinfra.substack.com/subscribe">Subscribe</a> now to access source code repository - 200 + coding lessons</p></div>
      <p>
          <a href="https://fullstackinfra.substack.com/p/day-123-api-documentation-the-contract">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>