<?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[Nik Malykhin]]></title><description><![CDATA[Reflections on platform engineering, developer experience, and the craft of modern software — plus the occasional analog side quest]]></description><link>https://www.nikmalykhin.com</link><image><url>https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png</url><title>Nik Malykhin</title><link>https://www.nikmalykhin.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 25 Aug 2026 14:10:15 GMT</lastBuildDate><atom:link href="https://www.nikmalykhin.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Nik Malykhin]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[nik1379616@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[nik1379616@substack.com]]></itunes:email><itunes:name><![CDATA[Nik]]></itunes:name></itunes:owner><itunes:author><![CDATA[Nik]]></itunes:author><googleplay:owner><![CDATA[nik1379616@substack.com]]></googleplay:owner><googleplay:email><![CDATA[nik1379616@substack.com]]></googleplay:email><googleplay:author><![CDATA[Nik]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Diagnosing Legacy Hardware]]></title><description><![CDATA[Building a Windows 98 SE Workstation]]></description><link>https://www.nikmalykhin.com/p/diagnosing-legacy-hardware</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/diagnosing-legacy-hardware</guid><pubDate>Tue, 18 Aug 2026 07:01:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!tLt6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Assembling a personal computer from the late 1990s provides a valuable retrospective look at hardware design and operating system architecture. Building the machine from an untested set of legacy components forced a return to basic system administration techniques, a stark contrast to modern automated environments. The primary goal was to create a functional installation of Windows 98 Second Edition, released by Microsoft in May 1999, to run programs like The Ultimate Doom directly from physical floppy disks.</p><h2>Hardware Discovery and BIOS Design</h2><p>The initial system initialization revealed an unusual artifact in the basic input/output system interface. A separate tab in the settings contained the message: <em>We design this board with pride</em>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tLt6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tLt6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 424w, https://substackcdn.com/image/fetch/$s_!tLt6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 848w, https://substackcdn.com/image/fetch/$s_!tLt6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 1272w, https://substackcdn.com/image/fetch/$s_!tLt6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tLt6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png" width="426" height="575" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:575,&quot;width&quot;:426,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:259043,&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://www.nikmalykhin.com/i/211163562?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.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_!tLt6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 424w, https://substackcdn.com/image/fetch/$s_!tLt6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 848w, https://substackcdn.com/image/fetch/$s_!tLt6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.png 1272w, https://substackcdn.com/image/fetch/$s_!tLt6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F367f3ab4-9407-4197-95ae-e1e307562db0_426x575.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>As a former hardware engineer, I believe this explicit acknowledgement of authorship underscores an era when board designers interacted directly with end users. Modern hardware development often hides these human touches behind standardized firmware interfaces and unified software packages. Seeing this statement made me reflect on how modern engineering workflows prioritize rapid iteration and abstraction over visible individual craftsmanship.</p><h2>Installation Pathways and OS Granularity</h2><p>During the Windows 98 Second Edition installation, the setup program offers a structured choice between a standard installation and an advanced user option. Modern operating systems prioritize simplified deployment models that hide configuration options from the user, relying on automatic hardware detection and strict default settings. When a modern OS encounters a non-standard case, changing the default behavior requires accessing hidden settings or running administrative commands in the command line.</p><p>In contrast, operating systems of the late 1990s recognized that automatic detection could fail with non-standard hardware configurations. User interfaces aimed at power users allowed for granular control over specific system components, configuration settings, and network protocols, requiring the installer to make explicit decisions rather than passively relying on automatic selection.</p><blockquote><p><em>Key Insight</em>: Legacy setup procedures operated under the assumption of user autonomy, providing direct access to low-level setup parameters rather than hiding hardware abstraction behind automated procedures.</p></blockquote><h2>Hardware Identification and Driver Resolution</h2><p>Integrating untested peripheral boards and motherboard components resulted in immediate driver failures. Generic drivers downloaded from modern online repositories failed to initialize devices due to version level mismatches and modified subsystem vendor codes. Resolving these driver conflicts required extracting precise hardware identifiers directly from bus registers.</p><p>To obtain these parameters without running display drivers or network stacks, the necessary diagnostic data was obtained using the Hardware Info utility via the MS-DOS command interface using the <code>hwinfo /ui</code> This utility retrieves a complete list of device attributes, including PCI vendor identifiers (VEN) and device identifiers (DEV). Matching these specific pairs of hexadecimal identifiers with the driver INF configuration files resolved device initialization errors.</p><p>When driver mismatches caused system instability or memory access violations, Safe Mode served as an important recovery mechanism. In Windows 98 SE, Safe Mode loads a minimal set of device drivers and system services, bypassing third-party kernel modules. This environment allowed manual editing of configuration files, such as SYSTEM.INI and the system registry, and removal of conflicting driver files before returning to normal operation.</p><blockquote><p><em>Key Insight</em>: Command-line diagnostic utilities, combined with minimal execution modes, remain the primary mechanism for recovering systems in the event of automatic device enumeration failure.</p></blockquote><h2>Execution and System Validation</h2><p>Achieving system stability required methodically resolving each driver assignment, adjusting interrupt requests, and checking the system&#8217;s storage channels. This process culminated in running <em>The Ultimate Doom</em>, originally released in 1995, loaded directly from physical floppy disks.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!06p6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!06p6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 424w, https://substackcdn.com/image/fetch/$s_!06p6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 848w, https://substackcdn.com/image/fetch/$s_!06p6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!06p6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!06p6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&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;: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="" srcset="https://substackcdn.com/image/fetch/$s_!06p6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 424w, https://substackcdn.com/image/fetch/$s_!06p6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 848w, https://substackcdn.com/image/fetch/$s_!06p6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!06p6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F878e0871-b85f-465a-90de-004352865694_2048x1152.jpeg 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>Running software from magnetic media required checking the disk controller&#8217;s synchronization and direct memory access channel allocation, confirming the proper configuration of both the physical layer and the operating system drivers. Reconstructing legacy hardware configurations reveals that behind the complexities of legacy setup procedures lies a clear logic of hardware control. The manual troubleshooting processes used in legacy systems provide a clearer understanding of the low-level interactions of components than modern plug-and-play abstractions.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Refactoring life, one Side Quest at a time.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Quantifying AI Adoption: From Initial Challenges to Doubling Speed]]></title><description><![CDATA[To quantify the impact of generative AI on engineer productivity, we need to move beyond sentiment analysis to verifiable task completion metrics.]]></description><link>https://www.nikmalykhin.com/p/quantifying-ai-adoption-from-initial</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/quantifying-ai-adoption-from-initial</guid><pubDate>Tue, 11 Aug 2026 07:01:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To quantify the impact of generative AI on engineer productivity, we need to move beyond sentiment analysis to verifiable task completion metrics. Over the course of eight months, as the AI &#8203;&#8203;champion for a team of three pairs of developers, I tracked our productivity at various stages of our workflow evolution. Before implementing AI in our software development cycle, the team established a productivity baseline of approximately 15 user stories per iteration. During the early stages of implementation and workflow experimentation, productivity dropped to 12 user stories per iteration as engineers mastered the cognitive load associated with learning new interaction models. After a simplified and optimized workflow was selected, the metric reached 27 user stories per iteration.</p><blockquote><p>Performance gains from AI integration are nonlinear; an initial performance hit is necessary to build the technical foundation required for sustained acceleration of adoption.</p></blockquote><h2>A Step-by-Step Strategy for Software Development Life Cycle Integration</h2><p>Transitioning the engineering team from manual development to stable production delivery using AI assistants required a gradual implementation, not a sudden mandate.</p><h3>Stage 1: Foundations and Trust in the Code</h3><p>We started with fundamental concepts, moving from high-level practical models to small, practical demonstrations. Instead of focusing on a specific vendor&#8217;s brand, we focused on practical application. The main obstacle at this initial stage was psychological. Engineers needed to transition from faith in AI tools to trust in compiled code. This required developing a solid technical understanding of the limitations of the context window, the construction of tooltips, and rigorous automated verification.</p><h3>Stage 2: Iterative experimentation in Scrum sprints</h3><p>We introduced weekly two-hour hands-on workshops with the support of engineering leadership. These regular sessions provided a controlled environment for estimating tasks, analyzing error modes, and reviewing created pull requests. Beyond these structured hours, engineers were free to explore AI tools within our standard two-week Scrum sprints without explicit daily guidance. The goal was to generate organic interest and identify areas where automated assistance was truly useful in our development cycle.</p><h3>Stage 3: Gradually move to default service provision</h3><p>The transition to production use was gradual, not immediate. Throughout the pilot period, the team integrated AI into an increasing proportion of sprint tasks. By the final stages of the initiative, the team had reached a consensus on adopting a <a href="https://github.com/nikmalykhin/lightweight-jira-story-workflow">lightweight workflow</a> as the primary method for completing tasks, channeling work on core features through the established process.</p><h2>Architectural evolution of context and managment</h2><p>Achieving sustainable development velocity required systematic refinement of our methodologies. Early on in the initiative, we evaluated structured query systems, such as spec-kit, to ensure <a href="https://www.nikmalykhin.com/p/designing-ai-driven-development-workflows">clear design constraints</a>. While this methodology established boundaries, the administrative overhead created obstacles along the way.</p><p>We subsequently tested <a href="https://www.nikmalykhin.com/p/assessing-file-stored-state-in-ai">storing execution state directly in local repository files</a> to enable auditing. However, practical implementation revealed that storing data in files serves only as a storage layer; it does not prevent infinite loops. Stopping uncontrolled execution requires explicit constraints specified directly in skill queries. Furthermore, expanding the model&#8217;s context windows resets the hallucination boundary rather than eliminating it. Multi-stage refinement in interactive chats allows the context buffer to strengthen over successive iterations, leading to higher-quality technical results.</p><p>We also noticed that allowing models to generate intermediate local copies of static documentation was causing hidden context changes. Replacing these intermediate local files with read-only links to a single source of truth eliminated model overwriting and preserved policy integrity. Ultimately, we abandoned local state storage in favor of a minimal workflow combining direct links to read-only documentation, strict skill tooltip definitions, and multi-stage chat refinement integrated with Jira.</p><blockquote><p>Effective workflow design strikes a balance between automated isolation and interactive refinement. Using fresh, contextual step-by-step instructions and read-only tools prevents context drift far better than storing data in local files.</p></blockquote><h2>Pragmatic problem solving in everyday engineering practice</h2><p>Instead of imposing formal execution algorithms, the choice of tasks remained an operational decision made dynamically by pairs of developers during the implementation process.</p><p>For low-complexity tasks, such as microfixes or individual user stories, developers opted for manual implementation if gathering context and generating hints created unnecessary overhead. Forgoing AI for simple fixes avoided administrative delays. Conversely, for complex user stories requiring design analysis, creating new features, or fixing bugs in production, teams relied on AI by default. Using model generation for complex tasks yielded significant results during the coding and test creation stages, maximizing speed where tool assistance provided a real advantage.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Price of Shared State: Database Links as an Organizational Bottleneck]]></title><description><![CDATA[The Anomaly of the Shared Production Database]]></description><link>https://www.nikmalykhin.com/p/the-price-of-shared-state-database</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-price-of-shared-state-database</guid><pubDate>Tue, 04 Aug 2026 07:00:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Anomaly of the Shared Production Database</h2><p>When scaling systems early on, operational efficiency often takes precedence over strict adherence to architectural constraints. In my experience managing core backend applications, PostgreSQL instances hosted on Google Cloud Platform served as the database engine. This database environment provided persistent storage for both low-latency transaction queries and the long-running analytical queries required by internal operations teams.</p><p>Initial development speed was high because engineers could write direct SQL queries against any combination of tables to retrieve metrics. Over time, background reporting processes began to directly compete with production applications. The structural flaw of this design became apparent when resource-intensive analytical queries caused row locks and resource contention, leading to daily connection pool exhaustion. The single PostgreSQL database went from being a high-performance storage solution to a major point of failure, leading to increased error rates for end users, while engineers spent time resetting database connections rather than writing software.</p><h2>The Misconception of Microservices Adoption</h2><p>The standard industry recommendation for resolving database access contention is to isolate the resource-intensive reporting module into a separate microservice. When evaluating this proposal, the short-term benefit of workload isolation was offset by significant operational and cost implications.</p><p>Creating a microservice for reporting without clearly defined business domain logic would have required duplicating significant portions of the core code for processing data input and object mapping. Running isolated network services on Google Cloud Platform would have increased ongoing infrastructure costs. Since our team operated without dedicated DevOps staff, the burden of maintenance&#8212;including continuous integration pipelines, infrastructure provisioning, and distributed monitoring&#8212;fell entirely on the shoulders of application developers. Implementing a distributed microservices architecture would have diverted limited developer resources from core product functionality to infrastructure management.</p><h2>Creation of a modular monolith</h2><p>Instead of complicating the distributed network, I decided to refactor the application into a modular monolith. The architectural goal was to ensure strict domain boundaries at the application level while maintaining a single deployable artifact and data store.</p><p>We reorganized the application code, dividing it into separate modules and establishing strict data access rules. Direct cross-table joins between non-isolated domain entities were explicitly prohibited in application queries. Inter-module communication was forced to use specific internal interfaces rather than direct database references. By refactoring the underlying data structures, we eliminated unnecessary query complexity and isolated areas with high memory utilization within the application.</p><h2>Intentional architecture compromises</h2><p>Choosing a modular monolith over a distributed network of services was a deliberate decision aimed at aligning the system&#8217;s complexity with the available engineering resources. We accepted the limitation of logic execution within a single process to maintain predictability and control cloud infrastructure costs.</p><p>Refactoring data access paths required a temporary reduction in the timeline for new feature implementations, as developers spent time restructuring database schemas and removing implicit cross-domain queries. This deliberate compromise ensured long-term stability. As a result, the codebase remained concise, easily understandable, and easy to maintain, without requiring increased engineering staff or large cloud budgets.</p><blockquote><p>A shared database rarely becomes an initial bottleneck for hardware performance. It functions primarily as an organizational constraint, constraining deployment schedules and engineer autonomy.</p></blockquote><p>Does this updated version align with the strategic perspective you want to give your blog, or would you like to add a section detailing the specific metrics you tracked to assess database load during the migration?</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[From Heavy Legacy Hardware to Modern Benchtop Tools]]></title><description><![CDATA[My introduction to electronics repair began with a heavy Soviet-made soldering iron.]]></description><link>https://www.nikmalykhin.com/p/from-heavy-legacy-hardware-to-modern</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/from-heavy-legacy-hardware-to-modern</guid><pubDate>Tue, 28 Jul 2026 07:00:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!K722!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My introduction to electronics repair began with a heavy Soviet-made soldering iron.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K722!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K722!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 424w, https://substackcdn.com/image/fetch/$s_!K722!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 848w, https://substackcdn.com/image/fetch/$s_!K722!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 1272w, https://substackcdn.com/image/fetch/$s_!K722!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K722!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png" width="397" height="399.78922716627636" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:430,&quot;width&quot;:427,&quot;resizeWidth&quot;:397,&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_!K722!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 424w, https://substackcdn.com/image/fetch/$s_!K722!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 848w, https://substackcdn.com/image/fetch/$s_!K722!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.png 1272w, https://substackcdn.com/image/fetch/$s_!K722!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5efafb09-5c62-4361-8f9d-08cf5da047e9_427x430.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><figcaption class="image-caption">USSR Soldering Iron</figcaption></figure></div><p>The device was reliable, but it lacked internal temperature control and the precision needed for working with delicate circuits. Years later, as a software engineer, I only needed soldering for simple assembly or repair of electronic devices. A simple 40-watt soldering iron with a flow-through tip proved sufficient for basic tasks, despite its fixed operating temperature and slow temperature recovery.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c8Vx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c8Vx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 424w, https://substackcdn.com/image/fetch/$s_!c8Vx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 848w, https://substackcdn.com/image/fetch/$s_!c8Vx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 1272w, https://substackcdn.com/image/fetch/$s_!c8Vx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c8Vx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png" width="400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:800,&quot;resizeWidth&quot;:400,&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;:false,&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_!c8Vx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 424w, https://substackcdn.com/image/fetch/$s_!c8Vx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 848w, https://substackcdn.com/image/fetch/$s_!c8Vx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 1272w, https://substackcdn.com/image/fetch/$s_!c8Vx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcaed621-265e-4b92-ba2a-e7e1e5d4ea70_800x800.png 1456w" sizes="100vw"></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><figcaption class="image-caption">Legacy 40W Soldering Iron</figcaption></figure></div><p>When I decided to return to electronics as a personal hobby after a ten-year hiatus, the range of available tools changed dramatically. The market now offers a wide selection of adjustable equipment at affordable prices. Setting up a new workstation required evaluating how modern heat dissipation systems and updated consumables would impact soldering at home.</p><h2>The problem of thermal inertia in component repair</h2><h3>JCD 80W Inline Iron Review</h3><p>Building a workbench from scratch, without using any old equipment, meant starting from the very basics. My first choice was a JCD 80W electric soldering iron with a built-in LCD display, purchased for $12.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ksVz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ksVz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 424w, https://substackcdn.com/image/fetch/$s_!ksVz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 848w, https://substackcdn.com/image/fetch/$s_!ksVz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 1272w, https://substackcdn.com/image/fetch/$s_!ksVz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ksVz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png" width="297" height="297" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb703594-c993-426c-8ab9-79671817b4da_300x300.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:300,&quot;resizeWidth&quot;:297,&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;:false,&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_!ksVz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 424w, https://substackcdn.com/image/fetch/$s_!ksVz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 848w, https://substackcdn.com/image/fetch/$s_!ksVz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 1272w, https://substackcdn.com/image/fetch/$s_!ksVz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb703594-c993-426c-8ab9-79671817b4da_300x300.png 1456w" sizes="100vw"></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><figcaption class="image-caption">JCD 80W Soldering Iron</figcaption></figure></div><p>This tool represented a significant functional improvement over older fixed-wattage soldering irons. A built-in microcontroller allowed for manual temperature control, eliminating the need to periodically unplug the device to prevent tip oxidation during periods of inactivity. For simple wire welding and light through-hole work, this soldering iron performed reliably.</p><p>During a specific maintenance task&#8212;replacing capacitors on an old ATX motherboard&#8212;the structural limitations of cheap integrated heating elements were revealed. Multilayer printed circuit boards (PCBs) utilize extensive internal copper ground planes. These copper layers act as large heat sinks, quickly conducting thermal energy away from soldered joints. When attempting to desolder high-temperature factory solder on such boards, an 80-watt JCD soldering iron was unable to maintain the set tip temperature. The motherboard&#8217;s thermal mass dissipated heat faster than the internal ceramic element could generate it, leaving the solder hardened and increasing the risk of damage to the board.</p><h3>Exploring hot air benchtop and precision soldering stations</h3><p>To solve the heat conduction problem, I used a YIHUA 8858-I portable drying station, which I purchased for $36.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!f79N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f79N!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 424w, https://substackcdn.com/image/fetch/$s_!f79N!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 848w, https://substackcdn.com/image/fetch/$s_!f79N!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 1272w, https://substackcdn.com/image/fetch/$s_!f79N!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f79N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png" width="220" height="220" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b585ebf0-2090-458e-9156-6122dbe5c406_220x220.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:220,&quot;width&quot;:220,&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;: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="" srcset="https://substackcdn.com/image/fetch/$s_!f79N!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 424w, https://substackcdn.com/image/fetch/$s_!f79N!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 848w, https://substackcdn.com/image/fetch/$s_!f79N!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 1272w, https://substackcdn.com/image/fetch/$s_!f79N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb585ebf0-2090-458e-9156-6122dbe5c406_220x220.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">YIHUA 8858-I Hot-Air Station</figcaption></figure></div><p>Hot-air stations are essential for surface-mount devices, but using hot air to desolder through-hole capacitors on high-density motherboards presented operational challenges. Ambient hot air distributed heat across a broader surface area than desired, creating potential thermal stress on adjacent components without concentrating sufficient energy onto the target pin.</p><p>The final technical solution was to switch to a YIHUA 982-V precision soldering station, purchased for $53.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UDxp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UDxp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 424w, https://substackcdn.com/image/fetch/$s_!UDxp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 848w, https://substackcdn.com/image/fetch/$s_!UDxp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 1272w, https://substackcdn.com/image/fetch/$s_!UDxp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UDxp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png" width="399" height="399" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1000,&quot;width&quot;:1000,&quot;resizeWidth&quot;:399,&quot;bytes&quot;:null,&quot;alt&quot;:null,&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="" srcset="https://substackcdn.com/image/fetch/$s_!UDxp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 424w, https://substackcdn.com/image/fetch/$s_!UDxp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 848w, https://substackcdn.com/image/fetch/$s_!UDxp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.png 1272w, https://substackcdn.com/image/fetch/$s_!UDxp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc48efd3e-4874-474d-835d-2cb7a564176f_1000x1000.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><figcaption class="image-caption">YIHUA 982-V Precision Station</figcaption></figure></div><p>Precision soldering stations use cartridge-type tips, where the heating element, temperature sensor, and tip form a single unit. This design minimizes thermal resistance between the heater and the work surface, ensuring rapid temperature recovery. When used on motherboard ground planes, the station increases heat transfer so much that it instantly melts old solder, allowing for clean component removal without overheating surrounding traces.</p><blockquote><p>Entry-level soldering irons often display accurate idle temperature readings, but they lack the dynamic temperature recovery necessary for heavy-duty grinding applications. A precision cartridge-based soldering station solves the problem of heat loss by placing the heating element directly within the working tip.</p></blockquote><h2>Modern consumables and working environment</h2><p>Equipment and components make up only half the soldering process. Traditional pine rosin and low-grade solder wire produce thick smoke and acrid, long-lasting fumes. In residential settings, airborne particles cause immediate discomfort and domestic conflict.</p><p>Updating the chemical consumables significantly improved performance without incurring excessive costs. Replacing the outdated alloy wire with MECHANIC DS6 solder wire, 0.8 mm in diameter, for $3 eliminated persistent odors in the room while maintaining consistent connection quality thanks to its low-smoke composition.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QLgp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QLgp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 424w, https://substackcdn.com/image/fetch/$s_!QLgp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 848w, https://substackcdn.com/image/fetch/$s_!QLgp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!QLgp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QLgp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png" width="399" height="399" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1200,&quot;width&quot;:1200,&quot;resizeWidth&quot;:399,&quot;bytes&quot;:null,&quot;alt&quot;:null,&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="" srcset="https://substackcdn.com/image/fetch/$s_!QLgp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 424w, https://substackcdn.com/image/fetch/$s_!QLgp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 848w, https://substackcdn.com/image/fetch/$s_!QLgp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.png 1272w, https://substackcdn.com/image/fetch/$s_!QLgp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ba1e974-7eee-4edd-88a1-5227cfb841e5_1200x1200.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><figcaption class="image-caption">MECHANIC DS6 0.8mm Solder Wire</figcaption></figure></div><p>The transition from solid rosin blocks to a soft gel flux, specifically MECHANIC UV559, priced at $7, has radically changed the solder flow dynamics. Applied with a syringe, this gel adheres directly to the solder pads, prevents surface oxidation during heating, and leaves a non-corrosive residue that requires no cleaning with aggressive solvents.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ot5g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ot5g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!Ot5g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!Ot5g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!Ot5g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ot5g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png" width="400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:640,&quot;resizeWidth&quot;:400,&quot;bytes&quot;:null,&quot;alt&quot;:null,&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="" srcset="https://substackcdn.com/image/fetch/$s_!Ot5g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!Ot5g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!Ot5g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!Ot5g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b4a891-b300-49d1-8c97-83e9313938cc_640x640.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><figcaption class="image-caption">MECHANIC UV559 Soft Rosin Flux</figcaption></figure></div><blockquote><p>Proper flux application reduces surface tension and accelerates heat transfer, making it as important to joint integrity as the heat output of the soldering station itself.</p></blockquote><h2>Lowering the barrier to entry into the world of amateur electronics</h2><p>The modern availability of equipment for hobbyists eliminates the historical challenges of home-based PCB fabrication and repair. While even the most basic entry-level soldering irons remain functional for basic wiring, ditching them in favor of a precision cartridge soldering station costing around $50 ensures superior temperature stability and protects sensitive PCBs from thermal stress. Combined with modern, no-clean consumables, the process becomes significantly cleaner, quieter, and more predictable.</p><blockquote><p>If a soldered joint is not transferring heat well, the primary corrective action is rarely increasing the force or raising the set temperature&#8212;it is applying fresh flux.</p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Refactoring life, one Side Quest at a time.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Assessing file-stored state in AI development workflows]]></title><description><![CDATA[In modern software development, using large language models to initiate tasks and refine requirements often reveals two pain points: unclear requirements and recursive, unhelpful follow-up questions.]]></description><link>https://www.nikmalykhin.com/p/assessing-file-stored-state-in-ai</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/assessing-file-stored-state-in-ai</guid><pubDate>Tue, 21 Jul 2026 07:01:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In modern software development, using large language models to initiate tasks and refine requirements often reveals two pain points: unclear requirements and recursive, unhelpful follow-up questions. In recent engineering experiments using Codex, powered by GPT-5.4-medium, in conjunction with Atlassian&#8217;s Rovo integration, I tested a lightweight, specification-driven workflow designed to evaluate whether file-based state can address these issues.</p><p>In my previous article on <a href="https://www.nikmalykhin.com/p/designing-ai-driven-development-workflows">AI-powered workflow development</a>, I examined the differences between having an agent pre-read a detailed specification file and conducting interactive launch sessions using chat. During this analysis, I noticed that processing static specification files often puts the model in a passive, rigid context mode, whereas interactive chat allows for dynamic context creation. Based on these findings, this experiment assessed whether storing state exclusively on local file systems and forcing the creation of new context windows for each run could further improve the launch phase.</p><h2>Architectural design of an isolated project launch workflow.</h2><p>To test this approach, I created a custom skill in Codex called ec-kick-off. The skill&#8217;s core contract was focused: it accepted a single Jira history key as an explicit source of truth, fetching the issue context directly from Jira and rejecting unstructured input.</p><p>At launch, the workflow ran in a subagent&#8217;s isolated environment rather than inheriting conversation context. It began by fetching the issue summary, description, and status directly through the Atlassian Rovo Jira plugin, storing the raw data in  <code>agents_utils/raw_tickets/&lt;KEY&gt;.json</code>. The workflow then normalized this JSON data into a clean Markdown document in <code>agents_utils/raw_tickets/&lt;KEY&gt;.md</code>, removing redundant API metadata and retaining only human-relevant content. In the final security step, the model read the normalized snapshot, merged existing draft context from previous passes, if any, and created an insert-ready technical ticket stored in <code>agents_utils/hardened_tickets/&lt;KEY&gt;.md</code>.</p><p>During the execution, I noticed an interesting implementation detail regarding how Codex handled updates. To process open issues and overwrite existing protected tickets, the agent generated temporary system scripts that created intermediate prompt files, such as <code>/tmp/&lt;KEY&gt;_kickoff_prompt_v2.txt</code>, before writing the output back to the target directory. This pattern of cloning and modifying demonstrates how file-based agent workflows leverage the generation of temporary local scripts to manage state transitions across isolated boundaries.</p><h2>System conclusions and engineering trade-offs</h2><h3>Managing Question Loops with Skill Rules</h3><p>The primary purpose of storing context in local files was to prevent the model from generating redundant clarifying questions when initiating application processing. Results showed that the storage medium does not affect the model&#8217;s behavior when providing prompts. Passing context through files, rather than through prompt strings, does not alter the model&#8217;s underlying decision-making logic.</p><blockquote><p>File storage serves solely as a persistent storage layer. Preventing infinite loops requires explicit constraints built into the skill request configuration, rather than changes to the way context is provided.</p></blockquote><p>To eliminate repeated feedback loops, constraints should be defined directly in the skill request configuration. If requirements are incomplete, the skill should instruct the model to apply minimal, reasonable assumptions, record any remaining uncertainties in a designated open questions section, and terminate without further prompting the user.</p><h3>The Fallacy of Stateless Context Isolation</h3><p>The second hypothesis was that creating a new context window for each iteration would eliminate hallucinations and noise in the output. Isolated execution was also expected to prevent contamination of the generation space by accumulated conversation history.</p><p>The experiment refuted this assumption. When the model reads state from a local file in a completely new context window, it must completely reconstruct its conceptual understanding of the problem domain. Since the model has no history of previous conversations, its baseline probability of generating hallucinations remains unchanged on each pass.</p><blockquote><p>The new context windows don&#8217;t eliminate the hallucination space; they reset it. Multi-stage refinement in chat allows the context buffer to strengthen over successive iterations, resulting in higher-quality technical characteristics.</p></blockquote><p>In practice, multi-step dialogue within a single context flow consistently yielded better results than isolated single-pass executions. Interactive chat allows engineers to gradually guide the model, validating assumptions and creating a shared context that strengthens over time.</p><h3>Changing context in local documentation snapshots</h3><p>To provide static context such as architectural rules and testing guidelines, I tested an approach where Codex downloaded a central Confluence page and created a local copy in Markdown format that served as a local rules file.</p><p>When inspecting the resulting local file, its contents were found to be identical to the source code. However, during subsequent implementation phases, the model generated end-to-end test cases containing extensive negative test cases. This conflicted with our team testing strategy, which explicitly limited end-to-end tests to core success scenarios, while negative testing was included in unit and integration tests.</p><p>While reviewing the local Markdown file generated by Codex, I discovered a minor change. The original Confluence code stated that end-to-end tests were limited to testing only the primary success scenarios. In the model-generated copy, this statement had been changed, and now, in addition to the primary success scenarios, critical business processes were included.</p><blockquote><p>Allowing LLM to generate intermediate local copies of static documentation results in hidden context modification. Retrieving rules directly from the source of truth using read-only tools eliminates the possibility of model rewriting and ensures policy integrity.</p></blockquote><p>This minor wording change expanded the scope of our testing and altered our engineering strategy. While this addition was overlooked during the initial review, it revealed a key risk: whenever a model generates local copies of reference documentation, it retains the ability to be modified. Reading documentation directly from the source with read-only tools eliminates the possibility of rewriting the model and ensures policy integrity.</p><h2>Strategic Conclusion</h2><p>The transition to specification-driven design requires a balance between automatic isolation and interactive refinement. While file persistence provides auditability and state history, relying on fresh contextual walkthroughs does not prevent hallucinations or looping questions. Successful implementation requires the use of direct read-only links for static documentation, guiding model behavior through strict skill hint definitions, and the use of multi-step chats to gradually tighten complex software specifications.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[De-Risking the Database Migration]]></title><description><![CDATA[How to Move from Oracle PL/SQL to AWS Without Freezing the Roadmap]]></description><link>https://www.nikmalykhin.com/p/de-risking-the-database-migration</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/de-risking-the-database-migration</guid><pubDate>Mon, 13 Jul 2026 19:10:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Monolithic Bottleneck: Stored Logic and On-Premises Lock-In</h2><p>Engineering leaders in scaling enterprises frequently encounter a critical conflict between maintaining infrastructure stability and sustaining product delivery velocity. Pausing a product roadmap to execute a foundational migration introduces severe business risk, yet operating on restrictive legacy infrastructure caps long-term scalability. I encountered this exact tension during an architecture modernization initiative that required a dual-axis migration: transitioning a production environment from on-premises servers to the cloud while simultaneously converting the database engine from Oracle to Amazon Aurora PostgreSQL.</p><p>The primary technical bottleneck was a legacy reporting engine processing gigabytes of transactional data daily. At the core of this system sat a monolithic Oracle PL/SQL function spanning approximately 1,000 lines of code, dense with procedural loops, cursor operations, and deeply embedded business logic. This function generated the primary daily transaction report for the business. Because the system was highly bound to native Oracle behavior, a direct automated schema conversion was impossible, and an immediate, comprehensive rewrite threatened to paralyze feature delivery across the entire engineering department.</p><blockquote><p>Modernization initiatives fail when treated as isolated, monumental events that require feature freezes. Scalable architectural evolution requires embedding structural changes directly into the operational fabric of the existing team.</p></blockquote><h2>The Reference Architecture Strategy: The Initial Component and the Operational Tax</h2><p>To reconcile the need for continuous feature delivery with the necessity of infrastructure modernization, I chose to avoid a broad, shallow migration approach. Instead, I selected a targeted strategy that prioritized isolating and resolving the single most complex, high-risk technical component first. This chosen approach centers on an initial reference component. By dedicating senior engineering resources to deconstruct the 1,000-line PL/SQL function, I could expose every fundamental compatibility friction point between Oracle and PostgreSQL under controlled conditions.</p><p>The deliberate trade-off in this approach involves a high upfront concentration of engineering effort on a single piece of code. However, the logical justification for this investment is the generation of reusable operational knowledge. Once the complex reporting function was successfully migrated and decoupled, I compiled the exact methodologies, automated scripts, and resolution steps into an internal, highly detailed technical tutorial.</p><h3>Scaling Migration Capabilities Across Internal Teams</h3><p>This tutorial functioned as a predictable operational tax on subsequent feature development. Rather than relying on specialized external infrastructure contractors, the client company used its existing engineering staff to migrate the remaining, less complex database modules. Because the engineering team possessed a definitive blueprint derived from the hardest technical problem, they could execute the remaining database conversions incrementally during standard sprint cycles. This strategy successfully preserved the main product roadmap while systematically hardening the underlying data tier.</p><h2>The Technical Execution: Deconstruction, Emulation, and Decoupling</h2><p>The execution phase required moving beyond the automated capabilities of basic schema migration tools. The initial phase focused on reverse-engineering the procedural logic without altering business outcomes. Because documentation for the legacy PL/SQL code was absent, I established an objective baseline by developing an automated load-testing framework. This suite generated extensive mock transactional datasets, passing them through the active Oracle environment to capture precise inputs and outputs. The client engineering team verified these test results, ensuring that the behavioral requirements of the report were completely preserved.</p><h3>Integrating the Target Cloud Architecture</h3><p>Once the data baseline was verified, the physical migration pipeline was established using a combination of managed cloud services and deliberate software decoupling. Data transfer from the on-premises database to AWS was managed via AWS Database Migration Service for schema baselines and ongoing change data capture replication. For the extraction, transformation, and loading phases of complex relational data subsets, I implemented AWS Glue jobs.</p><p>The primary architectural challenge lay in handling the specific structural capabilities of the legacy PL/SQL environment within an open-source database engine. I adopted a two-tiered resolution strategy to address this problem. For minor syntactic differences and native utility functions, I utilized the open-source orafce extension inside the Amazon Aurora PostgreSQL instance. This extension provides native compatibility layers for Oracle-specific components, including date utilities, conditional operations, and specific database packages, allowing mechanical translation of simpler logic strings.</p><h3>Moving Logic from the Storage Tier to Compute Workers</h3><p>For the complex procedural logic containing intensive loops and deep data mutations, I made a deliberate architectural decision to pull the code completely out of the database engine. Translating complex procedural routines directly into PostgreSQL PL/pgSQL often results in unscalable database CPU utilization. I extracted these core business calculation routines into standalone Scala jobs.</p><p>By migrating the processing logic to Scala, the database was restored to its optimal role as a high-performance transactional data store, rather than a heavy application processing server. The Scala application workers consumed the raw data from Aurora PostgreSQL, processed the transformations efficiently via structured memory management, and compiled the final daily reports.</p><h2>Quantifying the Architecture: Financial and Performance Outcomes</h2><p>The success of an infrastructure migration must ultimately be verified by objective operational metrics rather than structural elegance alone. By utilizing the initial reference component strategy and decoupling computation from storage, the architecture achieved clear, verifiable improvements in both system performance and operational expenditure.</p><blockquote><p>True architectural optimization aligns infrastructure cost reductions directly with performance enhancements, validating technical changes through concrete business metrics.</p></blockquote><h3>Analyzing Cost and Computational Velocity</h3><p>Transitioning away from the legacy environment immediately removed the substantial capital expense associated with commercial on-premises Oracle database licenses. By shifting the transactional and analytical workloads to a managed Amazon Aurora PostgreSQL environment combined with temporary compute workers for the Scala jobs, the client company reduced its total database infrastructure expenditures by approximately 10% to 15%.</p><p>Simultaneously, the performance of the core business report improved dramatically. The legacy PL/SQL function frequently caused read-write contention and locks on the primary database engine due to the sheer volume of daily transactions. The decoupled architecture, which isolated transactional writes in Aurora and shifted analytical transformations to the Scala runtime environment, executed the critical daily transaction report 40% faster. This optimization was definitively proven by running the initial verification load tests against the completed production cloud infrastructure, demonstrating that systematic, blueprint-driven hardening can occur without interrupting business velocity.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Designing AI-Driven Development Workflows]]></title><description><![CDATA[Why Out-of-the-Box Tools Require Customization]]></description><link>https://www.nikmalykhin.com/p/designing-ai-driven-development-workflows</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/designing-ai-driven-development-workflows</guid><pubDate>Tue, 30 Jun 2026 07:01:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>Evaluating the operational efficiency of engineering workflows is essential when integrating advanced language models into daily development cycles. To understand the practical boundaries of autonomous code generation, I conducted an implementation experiment using the GPT-5.3Codex model. The objective was to complete a medium-sized user story involving the integration of SendGrid template rendering and storage capabilities into an established email notification use case.</span></p><p><span>This assessment contrasts two distinct methodologies: an out-of-the-box framework known as spec-kit, and a lightweight alternative designated as the custom workflow. The custom workflow utilizes chat mode during the initial kick-off and planning stages to generate specific tasks, subsequently shifting to Codex for the explicit implementation of those tasks guided by an AGENT.md operational file and localized skills. The goal is to determine whether spec-kit provides immediate utility without modification or if its inherent structural characteristics necessitate explicit customization.</span></p><h2><span>Architectural Slicing and Pull Request Topography</span></h2><p><span>The structural composition of code updates significantly influences the sustainability of continuous integration pipelines. During the experiment, the custom workflow isolated changes into modular components that aligned directly with my default hexagonal architecture. This approach generated six discrete pull requests. The median size of these code updates remained between three and four files, with the most extensive single update containing five files. Every file generated by this workflow contained exclusively functional implementation code, eliminating secondary artifact noise.</span></p><p><span>In contrast, the spec-kit framework approached the user story through vertical slicing, attempting to package complete functional business capabilities into each cycle. This strategy yielded four pull requests, but the internal volume of these updates was substantially larger, averaging seven to eight files per pull request. The most expansive update within this set encompassed thirteen distinct files.</span></p><p><span>From an engineering operations perspective, managing large pull requests introduces definitive maintenance challenges. Reviewing a thirteen-file modification requires deep contextual immersion and can easily exhaust a multi-hour block of defensive engineering time. Conversely, integrating five to seven highly compact pull requests throughout a standard working day introduces negligible cognitive friction, provided the changes remain small and structurally isolated. Notably, both workflows initially introduced an identical rendering bug involving SendGrid template helpers, which required a targeted corrective commit. This suggests that the structural layout of the pull requests, rather than initial code accuracy, serves as the primary differentiator in developer friction.</span></p><h2><span>Discovery Mechanisms and Cognitive Loading</span></h2><p><span>The preparation phase exposes a stark contrast in the type of mental energy required by each workflow. The custom workflow relies heavily on an interactive discovery process during the chat-based kick-off. The model proactively initiated a clarification and planning dialogue to map out the implementation requirements before generating the discrete tasks for Codex.</span></p><p><span>This interactive session translated into a substantial textual footprint. The initial clarification phase required three distinct iterations of questioning and answering, totaling eight pages and 2,281 words. This was immediately followed by the planning phase, which required two subsequent iterations and produced an additional eight pages and 2,370 words. Cumulatively, this chat dialogue generated 16 pages of standard layout text, or 4,651 words. Assuming an average conversational rate of 150 words per minute, this preparatory phase equates to a thirty-minute collaborative pair-programming session.</span></p><p><span>The spec-kit framework approaches preparation through localized, static document synthesis rather than ongoing verbal dialogue. Before initiating code generation, the tool compiled eight distinct analytical documents within the specs directory.</span></p><p><span>An examination of the generated specs directory reveals how this text is distributed across individual documents. The requirements checklist contains 149 words, while the OpenAPI contract specifying the interface changes takes up 102 words. The data model specification consists of 211 words, and the implementation plan spans 409 words. Additionally, the quickstart document contains 140 words, the research summary covers 260 words, the comprehensive technical specification comprises 1,021 words, and the final task breakdown document details 1,540 words.</span></p><p><span>The documentation total matches the 16-page volume of the conversational workflow but contains 3,832 words of highly dense technical material. When applying an analytical reading standard of 75 words per minute for complex documentation, reviewing this output demands roughly 50 minutes of solitary, rigorous technical analysis. This calculation excludes the initial setup interactions required to seed the tool.</span></p><p><span>Insight: Engaging in a collaborative, bidirectional technical dialogue yields lower cognitive fatigue than parsing dense, machine-generated analytical documentation independently. The conversational format allows an engineer to guide the discovery path dynamically, whereas the document-heavy approach demands prolonged, solitary code-review stamina.</span></p><h2><span>Estimation Metrics and Delivery Impact</span></h2><p><span>To contextualize project velocity, I utilize a standard estimation scale where one point equates to a minor task, three points represent half of a development iteration, and five points correspond to a full iteration block. Historically, the targeted user story would receive an empirical estimate of three story points.</span></p><p><span>By utilizing either AI-driven development environment, the effective complexity of the implementation dropped significantly, allowing the story to be re-estimated at two points. This finding aligns with observations gathered over a multi-month period: the strategic application of generative models consistently removes approximately one story point from medium-sized requirements.</span></p><p><span>However, this efficiency gain exhibits a clear non-linear trend when applied to larger tasks. A single-point reduction on a highly complex, five-point user story does not alter the fundamental delivery architecture or allow the task to be decomposed more effectively. For larger software initiatives, the exact return on investment provided by these autonomous tools requires further empirical evaluation.</span></p><h2><span>Long-Term Repository Maintenance and Documentation Bloat</span></h2><p><span>A critical consideration when adopting spec-kit out of the box is the long-term structural health of the code repository. Generating eight non-service documentation files for a single medium-sized user story introduces a noticeable maintenance tail.</span></p><p><span>Consider a baseline engineering department consisting of three to four development pairs. If these pairs collectively deliver approximately three completed user stories per development iteration across 26 annual iterations, the repository configuration changes dramatically over time. Under the unmodified spec-kit framework, this delivery velocity results in the accumulation of roughly 600 non-service Markdown and YAML files every year. Managing the lifecycle, accuracy, and relevance of hundreds of static documentation files creates an administrative burden that can quickly devalue the initial velocity gains of automated generation.</span></p><h2><span>Chronological Integration Patterns</span></h2><h3><span>The Custom Workflow Evolution</span></h3><p><span>The custom workflow distributed code modifications across six isolated, single-purpose commits containing exclusively functional code. The sequence began with a five-file commit implementing active SendGrid template retrieval, which introduced the core repository interface, its SendGrid implementation, an exception for missing templates, a version value object, and a corresponding repository error test. Next, a four-file commit introduced the Handlebars email template renderer by modifying the build configuration and adding the renderer service, the rendered email domain model, and the renderer test suite.</span></p><p><span>The third step was a three-file commit handling the storage of the rendered template within the document management system, which impacted the primary use case, the consumer contract test, and the use case test. To address a rendering bug, a two-file corrective commit added explicit support for SendGrid Handlebars helpers within the core rendering logic. This was followed by a three-file commit introducing global exception mapping using an exception handler advice and its corresponding integration test. The evolution concluded with a three-file commit aggregating final integration and regression verifications across the controller and use-case boundaries.</span></p><h3><span>The Spec-Kit Framework Evolution</span></h3><p><span>The spec-kit framework grouped its operations into broader, multi-file updates that combined documentation and implementation boundaries. The process opened with an eight-file initial commit compiling the prerequisite requirements, OpenAPI specifications, data models, plans, quickstart guides, research notes, technical specifications, and task manifests within the specs directory.</span></p><p><span>This was followed by a thirteen-file monolithic commit deploying the dynamic template rendering architecture, which simultaneously modified the build configuration, the task checklist, the exception handler advice, the SendGrid repository implementation, the missing template exception, the document management system store request, the rendered email domain model, the core use case, and their associated tests. The third phase was a four-file verification commit introducing test coverage for document management system failure scenarios. The cycle concluded with a five-file verification commit ensuring proper handling of missing templates, which updated integration tests, contract verifications, and serialization payloads.</span></p><h2><span>Final Assessment: To Customize or Adopt As-Is</span></h2><p><span>Returning to the original operational query: can spec-kit be utilized effectively without modification? The data suggests that an out-of-the-box deployment introduces distinct operational trade-offs that make customization necessary for long-term health.</span></p><p><span>While spec-kit succeeds in lowering short-term delivery complexity, its vertical slicing strategy creates overly large pull requests that challenge standard daily review workflows. Furthermore, the generation of extensive static documentation introduces systemic repository bloat that scales poorly across multiple engineering teams.</span></p><p><span>The ideal path forward requires a hybrid architecture. By customizing spec-kit to inherit the structural instructions of the custom workflow, we can merge the systematic rigor of automated planning with the clean, highly isolated pull request structure required by hexagonal architectures. Future efforts will focus on implementing custom skills within the agent configuration to restrict the generation of non-service files while preserving shared context between the conversational interface and the underlying code generation engine.</span></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Non-Transactional Reality of PostgreSQL Sequences]]></title><description><![CDATA[The Expectation of Monotonicity in Order Systems]]></description><link>https://www.nikmalykhin.com/p/the-non-transactional-reality-of</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-non-transactional-reality-of</guid><pubDate>Tue, 23 Jun 2026 07:00:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><span>The Expectation of Monotonicity in Order Systems</span></h2><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">When building an order management pipeline, the primary objective is to capture, validate, and permanently store transactional records such as customer purchases, financial ledgers, or invoices. This system represents a comprehensive infrastructure architecture rather than a simple database configuration because it operates as a multi-layered distributed pipeline. In a typical production environment, this architecture encompasses web servers ingesting thousands of concurrent requests, connection pools regulating database lifecycles, and downstream services like fulfillment, inventory, and accounting that ingest this data via asynchronous message queues.</span></p><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">To ensure absolute tracking, auditing, and predictability across these decoupled architectural boundaries, a common engineering assumption is that these transaction records will possess sequentially ordered identifiers, moving uniformly from one integer to the next without omission. In my implementation, I utilized PostgreSQL with a primary key defined as a big integer generated by default as an identity. This approach is widely recognized for its enterprise stability and seamless integration within robust backend data ecosystems.</span></p><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">During routine disaster recovery drills, however, the monitoring logs revealed an unexpected pattern in the primary key sequence. Instead of a continuous, gapless progression, the identifiers exhibited distinct omissions, appearing as a broken sequence with missing elements. A manual audit verified that no records were lost; every transaction was accounted for, yet the identifiers contained significant gaps. This discovery prompted a detailed investigation into the core mechanics of sequence manipulation within the PostgreSQL engine.</span></p><h2><span>Simulating the Anomalies: Forward and Backward Jumps</span></h2><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">To isolate the root causes of these numerical omissions, I constructed a controlled replication environment using Kotlin and the Exposed framework to simulate various infrastructure failure states.</span></p><h3><span data-color="rgb(67, 67, 67)" style="color: rgb(67, 67, 67);">The Forward Jump and the Mechanics of the Write-Ahead Log</span></h3><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">The first scenario reproduces a sudden infrastructure termination. The configuration initializes a sequence and captures the initial increment within a standard database transaction block.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;2013d799-b5de-4d4e-acb8-6c7ed902f598&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">transaction {
    exec(&#8221;CREATE SEQUENCE seq;&#8221;)
    val firstVal = exec(&#8221;SELECT nextval(&#8217;seq&#8217;);&#8221;) { rs -&gt;
        rs.next()
        rs.getLong(1)
    }

    println(&#8221;Initial value: $firstVal&#8221;)
}</code></pre></div><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">Once the initial value is confirmed as one, the application executes an ungraceful process termination at the operating system level, targeted directly at the backend process identifier associated with the current database session.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;ae033a7e-598e-4de4-8156-63b96a749e2d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">fun triggerDatabaseProcessCrash() {
    transaction {
        val pid = exec(&#8221;SELECT pg_backend_pid();&#8221;) { rs -&gt;
            rs.next()
            rs.getInt(1)
        }

        Runtime.getRuntime().exec(&#8221;kill -9 $pid&#8221;)
    }
}</code></pre></div><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">Following this abrupt termination, the Kotlin application encounters a communication exception or a transient connection exception as the underlying connection pool loses its link to the server. Upon the re-establishment of a stable connection to the database instance, a subsequent call to the sequence reveals a substantial forward leap rather than the expected single increment.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;9382f250-a89c-41df-a4a7-1a718f470f71&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">transaction {
    val nextVal = exec(&#8221;SELECT nextval(&#8217;seq&#8217;);&#8221;) { rs -&gt;
        rs.next()
        rs.getLong(1)
    }
    println(&#8221;Value after crash recovery: $nextVal&#8221;)
}</code></pre></div><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">The resulting output yields a value of 34. This behavior is directly attributable to an internal optimization parameter within the PostgreSQL source code, governed by a pre-allocation macro.</span></p><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">To minimize persistent disk write frequency and maximize concurrent scalability, the engine pre-allocates a block of 32 sequence values by default and logs this allocation to the Write-Ahead Log. When an ungraceful shutdown occurs, the remaining unassigned values within that cached block are permanently lost, causing the sequence to resume from the boundary of the subsequent pre-allocated block during recovery.</span></p><h3><span data-color="rgb(67, 67, 67)" style="color: rgb(67, 67, 67);">The Backward Jump and Uncommitted States</span></h3><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">An even more perplexing anomaly occurs when a sequence appears to move backward following a critical system failure. This state can be demonstrated by advancing a sequence multiple times within a single transaction block without executing a formal commit statement, followed by an immediate hard process termination.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;c1da26b6-fbbf-43a3-90d4-9809e2c34e71&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.exec

fun demonstrateBackwardsJump() {
    transaction { exec(&#8221;CREATE SEQUENCE seq;&#8221;) }

    transaction {
        val v1 = exec(&#8221;SELECT nextval(&#8217;seq&#8217;);&#8221;) { it.next(); it.getLong(1) }
        val v2 = exec(&#8221;SELECT nextval(&#8217;seq&#8217;);&#8221;) { it.next(); it.getLong(1) }
        val v3 = exec(&#8221;SELECT nextval(&#8217;seq&#8217;);&#8221;) { it.next(); it.getLong(1) }
        println(&#8221;Sequence values in-transaction: $v1, $v2, $v3&#8221;)
        val pid = exec(&#8221;SELECT pg_backend_pid();&#8221;) { it.next(); it.getInt(1) }
        Runtime.getRuntime().exec(&#8221;kill -9 $pid&#8221;)
    }
}</code></pre></div><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">Upon reconnecting to the database and invoking the next value, the system returns a value of one. This behavior emphasizes that engine sequences operate entirely </span><em><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">outside</span></em><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);"> standard transactional boundaries. While individual sessions track these increments locally during an active transaction, the underlying values are discarded during a hard crash because they were never permanently etched into the Write-Ahead Log as a committed state.</span></p><h2><span>Evaluating Alternatives: The Flawed Custom Counter Workaround</span></h2><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">In an attempt to bypass the inherent gaps associated with standard database sequences, a developer might consider implementing a custom identity counter utilizing standard transactional tables and functions. The implementation typically involves creating an explicit sequence tracking table and an atomic update function.</span></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;f2eee821-bd05-4f8f-b131-c2654105f4ec&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">CREATE TABLE MY_SEQ (ID BIGINT NOT NULL);

INSERT INTO MY_SEQ (ID) VALUES (0);

CREATE FUNCTION NEXT_VAL() RETURNS BIGINT
    LANGUAGE SQL AS
&#8216;UPDATE MY_SEQ SET ID = ID + 1 RETURNING ID&#8217;;</code></pre></div><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">While this mechanism successfully eliminates numerical gaps by utilizing the standard transactional engine, it introduces a massive performance penalty that makes it unviable for high-throughput applications. When the custom function executes the update statement, PostgreSQL applies an exclusive row-level lock to that single row within the tracking table. Consequently, every concurrent transaction across the entire application ecosystem must wait in a strict, single-file queue to obtain a new identifier.</span></p><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">If a single transaction requires 100 milliseconds to process its internal business logic prior to committing, all other concurrent threads are completely blocked for that duration. In a high-concurrency production environment, this structural bottleneck rapidly triggers database connection timeouts, thread starvation, and severe application latency. The trade-off between absolute numerical continuity and system throughput represents a deliberate choice where performance must be prioritized.</span></p><h2><span>Architectural Best Practices for Kotlin Applications</span></h2><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">Managing non-transactional sequence behavior within Kotlin services requires a deliberate approach to application architecture and data flow design.</span></p><blockquote><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">Essential Insight: Database sequences must be treated as internal, transient optimization helpers rather than durable, externally accurate identifiers.</span></p></blockquote><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">When developing services that interface with PostgreSQL sequences, specific architectural practices must guide the implementation to prevent data degradation across the broader system. It is vital to never expose or distribute an identifier generated by a sequence to external systems, such as asynchronous message brokers or user-facing REST responses, until the enclosing database transaction has been successfully committed. If a system failure or network interruption occurs prior to the final commit, the sequence value is permanently discarded, leading to data inconsistencies or dangling references within external architectures.</span></p><p><span data-color="rgb(31, 31, 31)" style="color: rgb(31, 31, 31);">Furthermore, structuring database transaction boundaries to be as narrow and short-lived as possible limits the window of vulnerability for process crashes and mitigates the risk of unexpected numerical anomalies. If the core business domain dictates a strict requirement for guaranteed, immutable, and gapless identifiers that must survive catastrophic infrastructure failures, database-driven sequences must be abandoned entirely. In such scenarios, transitioning to high-resolution Universally Unique Identifiers or deploying a dedicated identity reservation ledger specifically engineered to preserve state integrity across volatile failure scenarios provides the necessary durability.</span></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Human Overwatch in AI Code Generation]]></title><description><![CDATA[Designing a Duplicate Protection Hashing Service]]></description><link>https://www.nikmalykhin.com/p/human-overwatch-in-ai-code-generation</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/human-overwatch-in-ai-code-generation</guid><pubDate>Tue, 09 Jun 2026 07:01:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Designing a Duplicate Protection Hashing Service</h2><p>In a recent architectural assignment, I was tasked with implementing a duplicate-protection data hashing service for a high-throughput Kotlin application. This service operates within an enterprise runtime environment utilizing Java 21 and is deployed on AWS ECS. The fundamental requirement was to convert incoming JSON payloads into a stable, unique string representation to prevent duplicate processing within the system pipeline.</p><p>Initially, I gravitated toward the MD5 algorithm. This selection was informed by a historical performance convention, assuming MD5 would minimize latency for an internal data validation check where cryptographic security was not a driving requirement. Because I routinely leverage generative artificial intelligence to streamline production workflows, I provided an AI assistant with a prompt detailing these exact constraints: a high-throughput Kotlin service tasked with producing an MD5 hash from an incoming JSON payload.</p><h3>The Baseline AI-Generated Implementation</h3><p>The generative AI model delivered exactly what was requested based on those initial assumptions. It produced a complete, functional component wrapper that mapped incoming data to an MD5 hash. Because the initial prompt did not ask the AI to evaluate potential platform regressions or optimize memory allocations, the model generated a literal translation of the requested functional requirements, complete with standard boilerplate utilities.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;80f85e5a-87d8-401b-b8dc-885f72ef866c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import org.springframework.stereotype.Component
import java.nio.charset.StandardCharsets
import java.security.MessageDigest

@Component
class HashGenerator(
   private val objectMapper: ObjectMapper,
) {
   fun generate(
       conversationId: String?,
       payload: JsonNode,
   ): String {
       val normalizedConversationId = conversationId.orEmpty()
       val canonicalPayloadBytes = objectMapper.writeValueAsBytes(canonicalize(payload))
       val conversationBytes = normalizedConversationId.toByteArray(StandardCharsets.UTF_8)

       val md5 = MessageDigest.getInstance(MD5_ALGORITHM)
       md5.update(conversationBytes)
       md5.update(canonicalPayloadBytes)
       return md5.digest().toHexString()
   }

   private fun ByteArray.toHexString(): String {
       val chars = CharArray(size * 2)
       forEachIndexed { index, byte -&gt;
           val value = byte.toInt() and 0xFF
           chars[index * 2] = HEX_CHARS[value ushr 4]
           chars[(index * 2) + 1] = HEX_CHARS[value and 0x0F]
       }
       return String(chars)
   }

   private fun canonicalize(node: JsonNode): JsonNode =
       when {
           node.isObject -&gt; {
               val objectNode = node as ObjectNode
               val sortedFields =
                   objectNode
                       .fields()
                       .asSequence()
                       .toList()
                       .sortedBy { it.key }
               val canonicalObject = objectMapper.nodeFactory.objectNode()
               sortedFields.forEach { (key, value) -&gt;
                   canonicalObject.set&lt;JsonNode&gt;(key, canonicalize(value))
               }
               canonicalObject
           }
           node.isArray -&gt; {
               val arrayNode = node as ArrayNode
               val canonicalArray = objectMapper.nodeFactory.arrayNode()
               arrayNode.forEach { item -&gt;
                   canonicalArray.add(canonicalize(item))
               }
               canonicalArray
           }
           else -&gt; node
       }

   private companion object {
       const val MD5_ALGORITHM = "MD5"
       val HEX_CHARS = "0123456789abcdef".toCharArray()
   }
}</code></pre></div><h2>The Conflict of Speed Versus Security</h2><p>While the code executed correctly in testing environment, it triggered a critical security flag during static code analysis in SonarQube. Our internal security champion mandated an immediate transition to SHA-256, citing systemic software vulnerabilities associated with MD5 collision risks. This requirement instigated a broader team discussion regarding the trade-offs between processing speed and cryptographic security within microservices.</p><p>To resolve this conflict, I conducted a deeper investigation into the execution paths of the hashing utility. The findings completely reframed the problem space. On a modern Java 21 runtime running on optimized cloud infrastructure, the execution variance between MD5 and SHA-256 is structurally negligible. The true computational bottlenecks were located within the data preprocessing layers rather than the mathematical operations of the message digest.</p><blockquote><p>Key Insight: Upgrading an algorithm to meet security compliance parameters rarely degrades system performance if the surrounding data manipulation logic remains unoptimized. The true latency hotspots frequently reside in object serialization and memory allocation patterns.</p></blockquote><h2>Identifying the True Microbenchmarking Hotspots</h2><p>The profiling data isolated three specific architectural execution risks within the original code structure:</p><ul><li><p>The canonicalization routine introduced deep recursion. Converting JSON fields into sequences, collecting them into lists, and sorting them generated an unsustainable volume of short-lived heap objects. This structure risks triggering frequent JVM Garbage Collection pauses under high throughput.</p></li><li><p>Jackson serialization via the writeValueAsBytes function consumed substantially more CPU cycles than any subsequent hashing operation. Transforming a newly instantiated object graph into a raw byte array is computationally expensive.</p></li><li><p>The manual byte-to-hex manipulation loop, while functional, missed the low-level optimizations provided by modern platform utilities.</p></li></ul><h2>Implementing Immediate Algorithmic and Structural Upgrades</h2><p>The first step involved addressing the security non-compliance while cleaning up the obvious inefficiencies. I replaced the manual hex encoding with the native HexFormat utility introduced in Java 17 and further optimized in Java 21. Concurrently, I transitioned the algorithm to SHA-256, which leverages hardware acceleration on contemporary processors.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;e57622a5-bed0-41a5-a8a7-79d551d727ad&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.stereotype.Component
import java.security.MessageDigest
import java.util.HexFormat

@Component
class HashGenerator(
    private val objectMapper: ObjectMapper,
) {
    private val hexFormatter = HexFormat.of()

    fun generate(
        conversationId: String?,
        payload: JsonNode,
    ): String {
        val normalizedConversationId = conversationId.orEmpty()
        val canonicalPayloadBytes = objectMapper.writeValueAsBytes(canonicalize(payload))
        val conversationBytes = normalizedConversationId.toByteArray(java.nio.charset.StandardCharsets.UTF_8)

        val sha256 = MessageDigest.getInstance(SHA256_ALGORITHM)
        sha256.update(conversationBytes)
        sha256.update(canonicalPayloadBytes)
        
        return hexFormatter.formatHex(sha256.digest())
    }
}</code></pre></div><h2>Optimizing the Canonicalization Routine</h2><p>Resolving the security alert was an essential compliance milestone, but achieving production-grade execution required a total refactoring of the canonicalize function. To eliminate high allocation rates and latency spikes, I rewrote the structural transformation logic to treat heap memory defensively.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;a52c3ba4-f2fe-4f86-bbc3-7f8ac22a102d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import java.util.TreeMap

private fun canonicalize(node: JsonNode): JsonNode =
    when {
        node.isObject -&gt; {
            val sortedMap = TreeMap&lt;String, JsonNode&gt;()
            val fieldsIterator = node.fields()
            while (fieldsIterator.hasNext()) {
                val entry = fieldsIterator.next()
                sortedMap[entry.key] = canonicalize(entry.value)
            }
            ObjectNode(objectMapper.nodeFactory, sortedMap)
        }
        node.isArray -&gt; {
            val canonicalArray = objectMapper.nodeFactory.arrayNode(node.size())
            for (item in node) {
                canonicalArray.add(canonicalize(item))
            }
            canonicalArray
        }
        else -&gt; node
    }</code></pre></div><h2>Strategic Improvements in Memory Management</h2><p>The architectural enhancements within the refactored canonicalization pipeline are governed by four distinct design choices across key subsections.</p><h3>Automatic Sorting via TreeMap</h3><p>The original logic explicitly pulled object fields into a Kotlin sequence, forced them into a temporary list, and executed a sorting lambda. The optimized approach streams fields directly into a java.util.TreeMap. Operating on a red-black tree architecture, the TreeMap inherently handles alphabetical key sorting upon element insertion, completely eliminating intermediate collection lifecycles.</p><h3>Elimination of Lambda Allocations</h3><p>Chains of functional methods like asSequence, toList, and sortedBy generate short-lived operational objects behind the scenes. In a high-throughput Spring Boot architecture, these objects increase the allocation rate and burden the garbage collector. Replacing functional abstractions with explicit while and for loops guarantees zero closure allocations inside the iteration logic.</p><h3>Pre-Sized Array Allocation</h3><p>The initial implementation initialized the array node wrapper using an empty factory declaration. By default, Jackson instantiates an underlying storage array with a conservative capacity constraint. When parsing highly populated arrays, the JVM is forced to repeatedly suspend execution to reallocate memory and migrate elements. Explicitly defining the initialization size via node.size prepares the exact memory requirements upfront.</p><h3>Direct Constructor Instantiation</h3><p>The default initialization sequence of a Jackson ObjectNode instantiates an internal LinkedHashMap before receiving data updates via the set method. The revised approach utilizes a public constructor that directly accepts the pre-populated TreeMap, reducing the required object instantiation operations by half.</p><h2>The Production-Ready Hash Service</h2><p>Combining these algorithmic upgrades and memory management adjustments results in a secure, performant, and enterprise-grade component.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;d0474add-3690-4d92-94b9-0533bab5ae8f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import org.springframework.stereotype.Component
import java.security.MessageDigest
import java.util.HexFormat
import java.util.TreeMap

@Component
class HashGenerator(
    private val objectMapper: ObjectMapper,
) {
    private val hexFormatter = HexFormat.of()

    fun generate(
        conversationId: String?,
        payload: JsonNode,
    ): String {
        val normalizedConversationId = conversationId.orEmpty()
        val canonicalPayloadBytes = objectMapper.writeValueAsBytes(canonicalize(payload))
        val conversationBytes = normalizedConversationId.toByteArray(java.nio.charset.StandardCharsets.UTF_8)

        val sha256 = MessageDigest.getInstance(SHA256_ALGORITHM)
        sha256.update(conversationBytes)
        sha256.update(canonicalPayloadBytes)
        
        return hexFormatter.formatHex(sha256.digest())
    }

    private fun canonicalize(node: JsonNode): JsonNode =
        when {
            node.isObject -&gt; {
                val sortedMap = TreeMap&lt;String, JsonNode&gt;()
                val fieldsIterator = node.fields()
                while (fieldsIterator.hasNext()) {
                    val entry = fieldsIterator.next()
                    sortedMap[entry.key] = canonicalize(entry.value)
                }
                ObjectNode(objectMapper.nodeFactory, sortedMap)
            }
            node.isArray -&gt; {
                val canonicalArray = objectMapper.nodeFactory.arrayNode(node.size())
                for (item in node) {
                    canonicalArray.add(canonicalize(item))
                }
                canonicalArray
            }
            else -&gt; node
        }

    private companion object {
        const val SHA256_ALGORITHM = "SHA-256"
    }
}</code></pre></div><h2>The Intersect of Generative Artificial Intelligence and Enterprise Engineering</h2><p>This optimization exercise highlights a critical reality regarding the application of generative artificial intelligence within enterprise software development. The initial AI-generated code was not technically broken. It accurately realized the precise constraints of the original prompt: it calculated an MD5 hash over an object payload. The <em>defect was rooted in my own outdated assumptions</em> about cryptographic overhead and the omission of strict platform analysis in the initial prompt requirements.</p><blockquote><p>Takeaway: Generative artificial intelligence operates as an exceptional execution mechanism, but it lacks the contextual capacity to independently enforce enterprise-grade performance boundaries without human engineering overwatch. The true value of the technology lies in its capacity to serve as an interactive learning accelerator, contracting traditional research cycles from hours down to a matter of minutes.</p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[When AI Breaks Database Parity]]></title><description><![CDATA[The Landscape of Database Selection and the Integration Testing Paradigm]]></description><link>https://www.nikmalykhin.com/p/when-ai-breaks-database-parity</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/when-ai-breaks-database-parity</guid><dc:creator><![CDATA[Nik]]></dc:creator><pubDate>Tue, 02 Jun 2026 07:03:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Landscape of Database Selection and the Integration Testing Paradigm</h2><p>According to global database engine rankings, relational models continue to dominate the software development landscape. The top positions are consistently occupied by Oracle, MySQL, Microsoft SQL Server, and PostgreSQL, with MongoDB following closely as the primary document-oriented alternative. In my own architectural designs, PostgreSQL serves as the primary relational database engine, complemented by Amazon Web Services S3 for object storage.</p><p>Previously, I explored the complexities of managing database migrations with Flyway. Today, I want to extend that conversation to address database integration testing and the critical requirement of environmental parity. For a considerable duration within Java and Kotlin development stacks, the H2 database engine served as the standard default for local execution and integration testing. As an in-memory, runtime-configured database, H2 provides seamless integration with the Spring Framework and requires zero external infrastructure installation. The engine also supports a dedicated PostgreSQL compatibility mode, which historically made it an appealing candidate for simulating a production environment during local development.</p><h2>The Illusion of Compatibility and <br>the Environmental Disparity Trap</h2><p>While H2 excels as a lightweight runtime database when interactions are mediated entirely by abstract object-relational mapping frameworks, its feature parity with PostgreSQL falls short of complete functional duplication. The compatibility boundary rarely covers advanced native database capabilities, leading to subtle and disruptive behavioral deviations between development and production environments.</p><p>For instance, H2 natively supports specific windowing functions like <em>ROWNUM</em>, which are completely absent in PostgreSQL. Conversely, writing advanced queries that exploit native PostgreSQL functions or triggers quickly exposes the limitations of the compatibility mode. The critical nature of this gap becomes evident during schema migration lifecycle events.</p><p>During a recent project iteration, our development workflow required introducing an MD5 hashing mechanism to process historical records during a data migration phase. The PostgreSQL syntax accepts a simple byte array input for its native <em>md5</em> function. When Flyway attempted to execute this migration script against the local H2 testing instance, the build failed immediately. The H2 engine does not recognize this function format, requiring an entirely different functional signature known as <em>HASH</em>, which demands an explicit algorithm string and expression parameters. This mismatch highlights the structural risk of relying on a simulated environment.</p><blockquote><p>True environmental parity cannot be achieved by translating syntax at runtime; it requires validating software against the exact engine configuration slated for production deployment.</p></blockquote><h2>The Architectural Evolution of Local Infrastructure</h2><p>The necessity of accepting the behavioral compromises of an in-memory database has been thoroughly eliminated by advancements in containerization and build-tool integration. The introduction of Docker fundamentally modified local engineering environments, a transformation subsequently extended to automated testing via the Testcontainers framework.</p><p>With the release of Spring Boot 3.1.0 in the spring of 2023, the framework introduced built-in, first-class configuration mechanisms for Testcontainers. This development eliminated the primary architectural justification for maintaining a split database architecture between testing and production. Even for projects maintaining simple data models, the modern tooling ecosystem removes the necessity of managing an alternate database dialect for local verification.</p><h2>The Token Regression: <br>Generative AI and Legacy Patterns</h2><p>The availability of modern containerized alternatives raises a pertinent question as to why environmental disparity remains a topic of discussion in 2026. The emergence of generative artificial intelligence as a ubiquitous development tool provides the explanation. During a concurrent development phase involving the bootstrapping of four distinct microservices, my engineering team utilized GitHub Copilot to accelerate the generation of service skeletons and initial configuration manifests.</p><p>Because generative models predict output tokens based on historical training data, their recommendations are heavily weighted toward long-standing industry conventions. Due to the decade-long prominence of H2 in historical Spring tutorials and code repositories, the assistant recommended an in-memory H2 configuration for local development. The engineers initializing the services accepted this recommendation as a functional baseline, thereby reintroducing legacy environmental friction back into a modern development stack.</p><blockquote><p>Generative code assistants operate on statistical probability derived from historical data, which can inadvertently cause architectural regressions by propagating legacy best practices into modern codebases.</p></blockquote><h2>Implementing Local Parity through Automation</h2><p>To resolve the structural friction caused by mismatched database engines, we replaced the in-memory simulation with a containerized PostgreSQL instance dedicated to local execution. To ensure this change did not introduce manual overhead to the developer workflow, we integrated the container lifecycles directly into our build orchestration layer.</p><h3>Declarative Local Infrastructure with Docker Compose</h3><p>The local database environment is declared using a concise seventeen-line Docker Compose configuration. This manifest utilizes a lightweight Alpine Linux distribution of PostgreSQL 17.9 and includes an explicit readiness health check to ensure dependent tasks block until the database engine is fully initialized.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;yaml&quot;,&quot;nodeId&quot;:&quot;22401e62-da02-42a4-88a3-f31e4be83d77&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-yaml">name: one_service

services:
 postgres:
   image: postgres:17.9-alpine
   container_name: one-service-postgres
   environment:
     POSTGRES_DB: one_service
     POSTGRES_USER: admin
     POSTGRES_PASSWORD: admin
   ports:
    - "5432:5432"
   healthcheck:
     test: ["CMD-SHELL", "pg_isready -U admin -d one_service"]
     interval: 10s
     timeout: 5s
     retries: 5</code></pre></div><p>This configuration allows developers to manage the entire infrastructure state directly from the terminal using standard compose lifecycle commands.</p><h3>Automating Container Lifecycles within the Gradle</h3><p>To eliminate manual intervention entirely, we registered custom execution tasks within the Kotlin DSL build configuration file (build.gradle.kts). These tasks manage the container lifecycle programmatically, guaranteeing that the database is active during specific phases such as schema generation or local application execution.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;9e88bf8e-4446-408e-b49a-c5df17270af1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">val composeUpPostgres by tasks.registering(Exec::class) {
   group = "documentation"
   description = "Starts local Postgres container and waits until it is healthy"
   commandLine("docker", "compose", "up", "-d", "--wait", "--wait-timeout", "120", "postgres")
}

val composeStopPostgres by tasks.registering(Exec::class) {
   group = "documentation"
   description = "Stops local Postgres container after OpenAPI generation"
   commandLine("docker", "compose", "stop", "postgres")
}</code></pre></div><p>By utilizing Gradle task graph dependencies, these infrastructure tasks are hooked automatically into the application build process. For example, generating OpenAPI documentation requires an active database to resolve the schema accurately. We map this dependency explicitly using the build task lifecycle.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;0cf1bbb9-c2b9-45ac-9803-63677e5a62cf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">tasks.named("generateOpenApiDocs") {
   dependsOn(composeUpPostgres)
   finalizedBy(composeStopPostgres)
   ...
}</code></pre></div><p>This structural configuration ensures that the container initializes prior to the generation task and terminates cleanly upon completion, removing manual environmental variance from the automated workflow.</p><p>Ultimately, the architectural tools available mean there are very few justifications for maintaining an in-memory database simulation in a modern ecosystem. When automated assistants suggest these legacy configurations, human engineers must remain the final arbiters of architectural validity, recognizing that <em>statistical probability</em> does not always equate to engineering excellence.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[A Systematic Approach to AI in Production]]></title><description><![CDATA[Implementing Triad Programming]]></description><link>https://www.nikmalykhin.com/p/a-systematic-approach-to-ai-in-production</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/a-systematic-approach-to-ai-in-production</guid><pubDate>Tue, 19 May 2026 07:01:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have utilized generative AI tools such as ChatGPT and GitHub Copilot for several years, but the central question that has consistently occupied my research is how to effectively apply these technologies within a production environment. Through dozens of experiments, I have moved beyond simple code generation to delivering production-ready stories with minimal manual intervention. My objective is to transition from viewing AI as a mere novelty to integrating it into a functional triad programming model.</p><h2>The Evolution Toward Triad Programming</h2><p>In my experience, modern enterprise software cannot be developed in isolation; it requires a collaborative team effort. For roughly six months, I have explored the transition from traditional pair programming to triad programming, where an AI teammate joins the human pair to facilitate development. This transition requires a cultural shift within the team to move from treating AI as a buzzword to utilizing it as a practical tool.</p><p>The support of technical leadership is an important prerequisite for this shift. Without such backing, changing established team initiatives and workflows is difficult. To support this cultural change, we organized internal sessions and weekly two-hour workshops dedicated to demystifying the technology. By exploring how to master context and refine instructions, the team can eliminate the <em>magical</em> perception often associated with artificial intelligence and treat it as a predictable component of the engineering process.</p><h2>Establishing the AI Environment through Context</h2><p>Defining the AI environment is an ongoing challenge, especially given the limitations inherent in production workflows. For my current purposes, I define the environment as the context provided to the model, which effectively makes the AI environment equal to its instructions. Whether these instructions are provided through a prompt, a specific configuration file, or an MCP server, they serve as the foundational constraints for the AI's output.</p><blockquote><p>I believe it is essential to manage the AI environment as closely as possible to the development process. This allows the team to remain agile and make necessary changes without creating disconnected silos of instruction.</p></blockquote><p>A significant advantage of this approach is the ability to leverage existing, plain-English documentation rather than creating specialized AI adaptations. For example, I use the team's standard Confluence page for quality assurance and testing strategies as a direct instruction set. This documentation outlines requirements such as ensuring every acceptance criterion is covered by a test and avoiding complex end-to-end suites in favor of integration coverage. Decoupling the testing strategy from AI-specific formatting ensures that if the team updates their standards, the AI's context is automatically updated, while the documentation remains readable for non-engineering stakeholders.</p><h2>Architectural Constraints and Testing Strategies</h2><p>To reduce cognitive load and provide clear boundaries for the AI, my team established a strict architectural agreement for our services. We utilize a hexagonal architecture, which is documented in Confluence to ensure consistency when engineers rotate between different services. This structure includes a defined hierarchy of adapters, controllers, and domain use cases.</p><p>The current structure organizes components into clear packages such as:</p><ul><li><p>com.todo.adapter.controller <em>for handling external requests and DTOs</em></p></li><li><p>com.todo.adapter.supplier <em>for repository adapters and external client configurations</em></p></li><li><p>com.todo.domain <em>for core exceptions, models, and use cases</em></p></li></ul><p>While this structure is optimized for organizational clarity rather than pure readability, it serves as a robust framework that prevents the AI from generating unexpected or hallucinated results. By grounding the AI in these established conventions, we save significant time that would otherwise be spent on custom instruction maintenance.</p><h2>The Practical Workflow: From Init Prompt to Autopilot</h2><p>The bridge between our documentation and the code is the initialization prompt. I have found that the most effective flow involves using ChatGPT, which has integrated connections to Jira, Confluence, and our GitHub repositories. This allows me to create a prompt that references specific Jira stories and Confluence guidance pages directly.</p><p>When provided with these links, ChatGPT analyzes the story details, the codebase structure, and the architectural standards to generate a grounded implementation plan. This plan maps to actual ports and adapter conventions rather than generic advice. This approach also facilitates a dialogue between human pair partners, as the chat becomes a shared space for reaching an agreement before the final prompt is passed to GitHub Copilot.</p><h2>Slicing and Iterative Implementation</h2><p>A critical aspect of using AI in production is task slicing. To prevent the AI from attempting to generate non-existent dependencies, it is vital to isolate fragments of the story. For a simple task involving a controller, a use case, and a client, I follow a isolated sequence:</p><ol><li><p>Implement a controller with a hard-coded response.</p></li><li><p>Implement the client that connects to the external service.</p></li><li><p>Develop the use case to bridge the domain model and the client.</p></li><li><p>Update the controller to utilize the new use case.</p></li></ol><p>Each slice follows a rigorous autopilot loop within GitHub Copilot. I provide a specific instruction set that mandates a test-driven development cycle:</p><ol><li><p>Analyze the task and the repository for alignment.</p></li><li><p>Create tests and mark them as skipped until the plan is approved.</p></li><li><p>Establish an implementation order for the tests.</p></li><li><p>Iterate through each test by removing the skip marker, implementing the code, and verifying the test passes.</p></li><li><p>Execute a full build, such as gradle clean build test, after each passing test to ensure overall system stability.</p></li></ol><h2>Human Oversight and Integration</h2><p>Despite the high level of AI involvement, human oversight remains a non-negotiable requirement for production code. I request that Copilot organize the resulting files into commit groups that are easy for a person to understand before opening a pull request.</p><blockquote><p>By keeping pull requests small and isolated, they remain manageable for human review, ensuring they meet specific client requirements and that the human engineers maintain a deep understanding of the codebase.</p></blockquote><p>This workflow demonstrates that by leveraging existing organizational processes and treating AI as an integrated teammate rather than an external tool, we can deliver high-quality software with greater efficiency and consistency.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Diagnosing Observability Gaps in Blocking Controller Methods]]></title><description><![CDATA[In a distributed system, the invisibility of an expected log entry often signals a deeper divergence between execution flow and infrastructure expectations.]]></description><link>https://www.nikmalykhin.com/p/diagnosing-observability-gaps-in</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/diagnosing-observability-gaps-in</guid><pubDate>Tue, 12 May 2026 07:02:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a distributed system, the invisibility of an expected log entry often signals a deeper divergence between execution flow and infrastructure expectations. During a recent implementation of a test email functionality within a Kotlin-based service, I encountered a scenario where logs in Datadog appeared for certain execution paths but remained absent for others. This inconsistency prompted an investigation into the interaction between the Kotlin <em>when</em> expression, blocking downstream calls, and the lifecycle of a request within the Datadog logging pipeline.</p><p>The target of this investigation was the <em>sendTestEmail</em> method located in the <em>TestEmailController</em>. The domain logic returns three distinct results: <em>Success</em>, <em>FeatureTurnedOff</em>, and <em>Error</em>. While the <em>FeatureTurnedOff</em> case consistently produced logs in the monitoring dashboard, the <em>Success</em> and <em>Error</em> outcomes frequently failed to emit the final confirmation log.</p><h2>Analyzing the Execution Flow</h2><p>The initial hypothesis centered on potential issues with the Kotlin <em>when</em> block or a misconfiguration of the Mapped Diagnostic Context (MDC). However, the technical finding revealed a more fundamental cause related to execution timing and the nature of the downstream service interaction.</p><p>The <em>FeatureTurnedOff</em> result is a short-circuit path. When the feature toggle is disabled, the use case returns a result immediately, allowing the controller to reach the final log statement and exit within a negligible timeframe. Conversely, both the <em>Success</em> and <em>Error</em> paths require a call to a downstream notification service. This call is implemented using a blocking mechanism via the <em>.block()</em> method on a reactive stream.</p><blockquote><p>The discrepancy in log visibility was not a failure of the logging library but a consequence of the controller thread waiting on a blocking call. If the downstream service experienced latency or if the client closed the connection before the call completed, the final log statement was never reached or recorded.</p></blockquote><p>This behavior was corroborated by Datadog errors indicating that the stream was closed by the client and that there were errors reading events. In environments utilizing the <em>ssm-agent-worker</em>, these interruptions can occur when the infrastructure or the initiating client terminates the request context before the application finishes its blocking operation.</p><h2>Implementing a Robust Logging Lifecycle</h2><p>To resolve the visibility gap, I restructured the logging strategy to separate request arrival from processing outcomes. By introducing a log statement immediately upon entry to the controller method, I ensured that a record exists regardless of how the downstream call performs.</p><p>The revised implementation follows a deliberate pattern of enrichment and cleanup. I utilized MDC to attach structured metadata to the log records, which facilitates precise filtering in Datadog. It is essential to avoid generic MDC keys such as <em>status</em>, as these often conflict with reserved fields or common conventions in log aggregators. Instead, I opted for specific identifiers like <em>testEmailOutcome</em> and <em>templateId</em>.</p><h3>Structured Implementation and MDC Hygiene</h3><p>The following structure ensures that the MDC is populated at the start of the request and, crucially, cleared in a finally block to prevent context leakage between threads.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;1e570632-6612-4578-afbb-aa1d5ab79ea8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">try {
    MDC.put("templateId", request.templateId)
    logger.info("Test email request received")

    val status =
        when (val result = sendTestEmailUseCase.execute(request.templateId)) {
            is SendTestEmailResult.Success -&gt; {
                MDC.put("testEmailOutcome", "test email was sent successfully")
                HttpStatus.CREATED
            }

            is SendTestEmailResult.FeatureTurnedOff -&gt; {
                MDC.put("testEmailOutcome", "feature toggle is off, test email was not sent")
                HttpStatus.ACCEPTED
            }

            is SendTestEmailResult.Error -&gt; {
                MDC.put("testEmailOutcome", "test email failed to send")
                MDC.put("testEmailErrorMessage", result.cause.message ?: "unknown error")
                HttpStatus.INTERNAL_SERVER_ERROR
            }
        }

    logger.info("Test email request processed")

    return ResponseEntity
        .status(status)
        .body(SendTestEmailResponse(templateId = request.templateId))
} finally {
    MDC.clear()
}</code></pre></div><p>This approach provides a clear narrative in the logs. The <em>Test email request received</em> log serves as a heartbeat, confirming the controller was reached. The final <em>Test email request processed</em> log confirms the blocking call completed and indicates which branch of the <em>when</em> logic was executed.</p><h2>Interpreting Downstream Service Signals</h2><p>Understanding the relationship between the application and the notification service is vital for interpreting the logs. For instance, an observed HTTP 400 Bad Request error from the notification service endpoint indicates that the feature toggle was active and the application successfully initiated the call. Because this is a terminal error from the downstream provider, the result maps to <em>SendTestEmailResult.Error</em>.</p><blockquote><p>Logging the specific error message from the result cause into a dedicated MDC field allows for immediate debugging of downstream rejections without requiring a manual trace of the network call.</p></blockquote><p>The introduction of the early log statement fixed the observability issue for all three execution paths. It provides a reliable controller-level record that the request was received before any slow or failing downstream behavior could interfere with the logging thread.</p><h2>Conclusion on Implementation Choices</h2><p>The decision to add a pre-call log and wrap the execution in a try-finally block was a logical response to the constraints of blocking I/O. While reactive, non-blocking patterns are often preferred, existing architectural constraints sometimes necessitate the use of <em>.block()</em>. In such cases, the primary responsibility of the developer is to ensure that the system remains observable even when execution is stalled.</p><p>By grounding the logging strategy in the lifecycle of the request rather than just the final outcome, I established a more resilient monitoring posture. The logs now clearly differentiate between request arrival, downstream processing, and final controller outcome, providing the necessary context to diagnose failures in a distributed environment.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Project 2002: Curating a Practical Build for the Windows 98 Era]]></title><description><![CDATA[The foundation of any retro-computing project is a clear definition of its historical boundaries.]]></description><link>https://www.nikmalykhin.com/p/project-2002-curating-a-practical</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/project-2002-curating-a-practical</guid><pubDate>Tue, 05 May 2026 07:01:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The foundation of any retro-computing project is a clear definition of its historical boundaries. My experience with personal computing began in late 1999 with a Pentium III 500MHz system. While that era provided significant exposure to the Windows 98 environment , much of my formative gaming history occurred during the Windows XP period. To capture the intersection of these two eras, I have focused my research on the late 2002 period.</p><p>The selection of Windows 98 SE as the primary operating system is a deliberate, pragmatic choice. While Windows XP offers stability, Windows 98 SE provides <em>native</em> MS-DOS support, which serves as a significant technical bonus for a gaming-centric build. This allows for a hardware configuration that can bridge the gap between late-nineties legacy software and the more demanding titles released at the turn of the millennium.</p><h2>The Core Architecture: Transitioning from Theory to Reality</h2><p>Identifying the ideal processor for 2002 requires a comparison between the AMD Athlon XP and the Intel Pentium 4. In early 2002, the AMD Athlon XP 1700+ was often viewed as the superior choice due to its performance-per-clock advantages over the Intel Pentium 4 Northwood. Furthermore, Windows 98 faces documented stability issues when running on processors exceeding 2.1GHz. This limitation makes the mid-range Athlon XP an <em>ideal</em> candidate for this specific operating system.</p><p>However, retro-computing often requires flexibility based on hardware availability. While my research initially favored a Socket A configuration, I acquired a ready-made system featuring an Intel Pentium 4 Northwood. This pivot highlights a core principle of the project: prioritizing functional, accessible hardware that matches the target era over an unattainable theoretical ideal.</p><h3>Motherboard and Maintenance</h3><p>The system is built around a QDI SuperB 4 motherboard. Positioned as a reliable middle-class component, it provides the necessary infrastructure for this era, though it presents a specific maintenance challenge common to hardware of this epoch.</p><blockquote><p>The longevity of early 2000s hardware is frequently compromised by failing capacitors. The QDI SuperB 4 requires a complete recapping to ensure future stability and prevent electrical failure.</p></blockquote><h2>System Stability and Memory Constraints</h2><p>While Windows 98 can be modified to address up to 1GB of memory, it is natively limited to 512MB. For a 2002 build, 512MB was considered a substantial amount and remains the most stable configuration for this operating system.</p><p>My selection for the memory module is a 512MB Kingston HyperX stick (KHX3200AK2/512). Although this specific module was released in July 2003, the HyperX line itself debuted in November 2002, making it a period-appropriate choice for a high-performance system of that time. I opted for a single 512MB module rather than a dual-channel configuration to ensure compatibility with the motherboard and to maintain a simpler, more stable signal path.</p><h2>Graphics and the Economics of Retro Hardware</h2><p>The video card is the most critical component for a gaming setup. While the Radeon 9700 Pro (August 2002) was the performance leader at the time, many users followed an upgrade path in subsequent years. In a modern context, the GeForce 6600 GT is often recommended as the fastest reliable solution for Windows 98 builds.</p><p>However, market dynamics dictate a different choice. The current price for a GeForce 6600 GT often reaches 100 euro, which is difficult to justify for a hobbyist project. By contrast, the ATI Radeon 9600 Pro (October 2003) can be acquired for approximately 10 euro. The 9600 Pro offers <em>excellent</em> driver support for Windows 98 and represents a logical "upper-mid" consumer upgrade that would have been common for a system originally purchased in late 2002.</p><h2>Storage, Audio, and Networking</h2><p>For storage, the system utilizes a 60GB Seagate Barracuda ATA IV. Released in late 2001, this drive is a period-correct selection that avoids the complexities and potential instability of using SATA-to-IDE adapters or industrial CompactFlash readers in a Windows 98 environment.</p><p>The audio configuration currently relies on a Creative Labs Sound Blaster PCI 128 (CT4750). While functional, the long-term goal is to source a Sound Blaster Audigy 1 or 2, which represented the pinnacle of consumer audio during the early 2000s.</p><p>The networking hardware is a standout artifact: the 3Com 3CSOHO100-TX.</p><ul><li><p>This card was released in September 1999.</p></li><li><p>It utilizes the Parallel Tasking II architecture.</p></li><li><p>It processes network traffic on its own silicon, reducing the load on the CPU.</p></li></ul><h2>Optical Drives and Media Artifacts</h2><p>The system includes two distinct optical drives that serve as markers of the epoch. The first is a Pioneer DVR-104 (April 2002), a reliable DVD-RW reader that requires the latest firmware for optimal performance. The second is the LG GDR-8161B. This drive is a unique historical artifact, as it is one of the few consumer drives capable of reading original GameCube and Wii discs.</p><p>To complement these, I have integrated a standard Samsung 3.5-inch floppy drive. The acquisition of new-old-stock floppy disks ensures that I can reliably write and load legacy DOS games using physical media.</p><h2>Conclusion</h2><p>Building a retro PC is a process where there is no single correct path, provided the researcher maintains a clear perspective on their goals. The transition from theoretical research to the physical assembly of hardware delivers a deep understanding of the technological transitions that defined the early 2000s. While maintenance tasks like recapping require time and patience, the result is a preserved piece of computing history that remains functional for modern exploration.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Refactoring life, one Side Quest at a time.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Preparation of the Machine]]></title><description><![CDATA[The Sim Racing Setup]]></description><link>https://www.nikmalykhin.com/p/the-preparation-of-the-machine</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-preparation-of-the-machine</guid><pubDate>Tue, 28 Apr 2026 07:01:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Sim Racing Setup</h2><p>I&#8217;ve spent some time in this industry to know that the promise of &#8220;plug-and-play&#8221; is usually a lie told to people who don&#8217;t have to maintain the results. We&#8217;ve grown accustomed to our IDEs functioning almost perfectly the moment we install them, which has created a bit of a lazy habit in our collective psyche. We expect our tools to meet us where we are without any effort on our part. But when I look at the current state of Generative AI, I&#8217;m reminded much more of high-performance sim racing or building a custom PC. You <em>can</em> just plug a wheel into a desk and start driving, but you won&#8217;t actually feel the road, and you certainly won&#8217;t win any races. To get professional results, you have to embrace the preparation. The setup isn&#8217;t an annoying preamble; it is the work itself.</p><h2>Hierarchies of Instruction</h2><p>In my recent experiments, I&#8217;ve moved away from treating ChatGPT as a blank slate. Instead, I&#8217;ve been refining a two-tier configuration that relies on <strong>Project Instructions</strong>, which are specific directives tailored to a particular codebase or business domain that work in tandem with my global settings. I found that by splitting instructions between a global level&#8212;who I am and how I want to be spoken to&#8212;and a project level, I could stop the AI from hallucinating a generic solution. This isn&#8217;t about giving the AI a long list of rules to follow blindly. It&#8217;s about creating a runtime environment that respects the reality of my actual repository.</p><h2>Slicing Against the Grain</h2><p>There is a fundamental tension in how we break down work for a machine versus how we break it down for a human. In the agile world, we are taught the value of a <em>Vertical Slice</em>, which is a functional piece of work that touches every layer of the system to deliver a complete feature. When I am working with AI, however, I&#8217;ve found that this approach often leads to a mess. I&#8217;ve started practicing a methodology where I break a complex story into isolated, technical layers&#8212;repository, use case, then controller&#8212;as separate steps. I didn&#8217;t set out to slice the &#8220;layers of a pie&#8221; instead of the &#8220;slices of a cake&#8221; because I thought it was a better way to design software; I did it because I <em>found</em> it simply works better for the AI&#8217;s current reasoning capabilities. It&#8217;s an empirical adjustment. By forcing the AI to focus on one technical layer at a time, I prevent the logic from becoming a tangled knot of half-finished abstractions.</p><h2>The Logic of Two Flows</h2><p>Within these project instructions, I&#8217;ve found success by defining two distinct paths of interaction. I call these Flow-Based Prompts, a system where the AI knows whether we are in an analysis phase or an execution phase.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;markdown&quot;,&quot;nodeId&quot;:&quot;4e21f924-a4a8-492a-8ff4-3b27d2e07960&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-markdown">Flow 1: Analysis &amp; Slicing
- Goal: Digest the Jira story and propose the technical slices.
- Output: A structured implementation plan.

Flow 2: Prompt Generation
- Goal: Create a specific instruction for GitHub Copilot.
- Output: A isolated prompt for a single technical layer.</code></pre></div><p>In the first flow, the AI acts as a sounding board, helping me decompose a story and identify the technical boundaries. In the second flow, it transitions into a generator, producing the exact context needed for GitHub Copilot to write the code. This prevents the &#8220;handoff&#8221; problem where context gets lost between the chat window and the code editor. It ensures that when I move to my IDE, the instructions are already tailored to the specific slice of the system I am currently building.</p><h2>The Evolutionary Tree</h2><p>Of course, I&#8217;ve been skeptical of &#8220;perfectly automated&#8221; prompts that try to handle every edge case from the start. I&#8217;ve discarded that idea for now because, at this stage of my understanding, those prompts usually just add unnecessary weight and noise. However, I don&#8217;t think we are stuck here. I suspect that as we get better at this, our instruction sets will evolve into something more like a tree. The system won&#8217;t just be a static list of rules; it will be an adaptive structure that detects the current context of the work and branches out to provide exactly the right level of detail.</p><blockquote><p>We are moving toward a future where the tool detects the type of instruction needed rather than requiring us to shout the same commands every morning.</p></blockquote><p>For now, the manual setup is where the value lives. It&#8217;s the difference between a tool that guesses and a tool that knows.</p><h2>Back to Reality</h2><p>In the end, I&#8217;m keeping the slicing methodology and the dual-flow instruction setup in my toolkit. I&#8217;ve set aside the hunt for a &#8220;magic&#8221; prompt that solves everything in one go. Reality is messy, and our tools need to be flexible enough to reflect that. We should be skeptical of any AI workflow that promises to do the thinking for us. The real value is in the preparation&#8212;the configuration of the environment&#8212;that allows us to do our best thinking with a bit less friction.</p><div><hr></div><p><em><strong>Further Reading / Related Reflections</strong></em></p><ul><li><p><em><a href="https://www.nikmalykhin.com/p/pragmatic-hexagon">The Pragmatic Hexagon: scaling decoupling without complexity</a> </em></p></li><li><p><a href="https://help.openai.com/en/articles/10169521-projects-in-chatgpt">Projects in ChatGPT</a> </p></li></ul><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Shared Reality of the Database Ledger]]></title><description><![CDATA[I spent a good portion of the early 2000s staring into the flickering glow of a CRT monitor, trying to master the precise sequence of an RTS build order.]]></description><link>https://www.nikmalykhin.com/p/the-shared-reality-of-the-database</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-shared-reality-of-the-database</guid><pubDate>Tue, 21 Apr 2026 07:01:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I spent a good portion of the early 2000s staring into the flickering glow of a CRT monitor, trying to master the precise sequence of an RTS build order. In games like <em>StarCraft</em>, you didn&#8217;t just build a Factory on a whim; you followed a rigid, physical sequence of Supply Depots and Barracks. The real problem wasn&#8217;t just losing a match&#8212;it was the <em>desync</em>, a fatal error where one player&#8217;s game state no longer matched the other&#8217;s. When that happened, the shared reality of the match simply evaporated.</p><p>I found that managing a database schema with Flyway feels remarkably similar. We often treat database evolution as a fluid, agile process, but the underlying reality is much more rigid. When we move from the isolated &#8220;practice map&#8221; of local development to the high-stakes environment of a production database, we are moving into a space where the history of what we built is just as important as the current state. In this space, a mismatch between your code&#8217;s expectations and the database&#8217;s actual schema is the ultimate game-breaker.</p><h2>The Migration Ledger</h2><p>Flyway manages this by utilizing a <strong>migration-based approach</strong>, which means every change to the database&#8212;whether adding a table or altering a column&#8212;is captured in a versioned SQL script. It maintains a dedicated table called <code>flyway_schema_history</code> to track exactly which scripts have been executed. To ensure consistency, the system calculates a <em>checksum</em>, which is a digital fingerprint of the file&#8217;s content.</p><p>If I ever change a script after it has already run on a server, Flyway detects that the fingerprint has changed. This results in a checksum mismatch, and the system will stop the application from starting. This <em>immutability</em> is not a hurdle; it is a safety feature designed to prevent the database from entering an unknown state where the code expects one schema but the database has another.</p><h2>Iteration in the Local Loop</h2><p>The friction often begins when we forget that our local environment is a sandbox, not a permanent monument. On macOS, I found that using Docker and Testcontainers is the most reliable way to ensure a local database actually <em>matches</em> production. We can spin up a local container with a single command to test our build order:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;256984f0-912b-417a-8a71-87a1db5337d0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">docker run --name my-db -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres</code></pre></div><p>This local container allows us to iterate quickly . In our <code>build.gradle.kts</code> configuration, we ensure that the <code>cleanDisabled</code> flag is set to false .</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;kotlin&quot;,&quot;nodeId&quot;:&quot;31c8acbf-13cb-4fc9-beaf-2d2a5a55d06f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-kotlin">flyway {
    url = "jdbc:postgresql://localhost:5432/mydb"
    user = "postgres"
    password = "pass"
    cleanDisabled = false
}</code></pre></div><p>This setup gives us a reset button . If I realize my first version of a script is flawed, I don&#8217;t create a second script to fix the first one locally. Instead, I edit the original script, run <code>./gradlew flywayClean</code>, and then <code>./gradlew flywayMigrate</code>. This ensures that my local state remains clean and my scripts remain concise before they are ever shared with the team.</p><h2>The Virtue of Squashing</h2><p>When working on a complex feature, I often end up with several different migration scripts as I refine the design. Merging all five into the main branch is a mistake because it clutters the history with a &#8220;diary&#8221; of my trial and error. Instead, I practice <em>squashing</em>, the act of consolidating all logic from multiple feature-branch scripts into one single, optimized file.</p><p>Squashing improves readability, making it easier for a peer to review one coherent table creation rather than a series of renames and drops. It also improves performance, as fewer scripts mean faster deployment and test execution. Before I merge a Pull Request, I ensure my local database is cleaned and migrated one last time to verify that the final, squashed script works perfectly.</p><h2>Constraints of the Persistent Environment</h2><p>The danger arises when we attempt to treat a <em>persistent environment</em>, like AWS Aurora, as if it were a local Docker container . Unlike our local sandbox, we cannot simply wipe a cloud database.</p><blockquote><p>Triggering a clean command in a persistent environment is the ultimate &#8220;Game Over,&#8221; as it will drop all application data and cause a full service interruption .</p></blockquote><p>Production database users usually lack the permissions to drop schemas anyway, which is a vital safety rail. However, errors still happen. Because PostgreSQL does not always roll back schema changes perfectly, a failed script can leave the database in a &#8220;half-built&#8221; state. When this happens, we must fix the script in the codebase and run <code>./gradlew flywayRepair</code> . This command updates the history table to match the new checksums without deleting any data, though sometimes manual SQL intervention is required to fix the table structure before the repair can succeed .</p><h2>Discipline Over Magic</h2><p>At the end of the day, database migrations are about the discipline you bring to the ledger rather than the tool itself. Flyway is a powerful engine, but it won&#8217;t save you from a messy build order or a lack of environmental parity. I&#8217;m keeping the practice of squashing and the strict use of containers in my toolkit, while setting aside any hope that these systems will ever be truly &#8220;set and forget&#8221;.</p><p>The reality is that database state is heavy and unforgiving. If you treat your migrations with the respect a shared reality demands, your deployments will become boring&#8212;which is exactly what we should strive for.</p><div><hr></div><p><em><strong>Further Reading / Related Reflections</strong></em></p><ul><li><p><em><a href="https://martinfowler.com/articles/evodb.html">Evolutionary Database Design by Martin Fowler</a> </em></p></li><li><p><a href="https://documentation.red-gate.com/fd/choosing-the-right-approach-with-flyway-246972498.html">Choosing the right approach with Flyway</a> </p></li></ul><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The ISO Wall and the CCD: Testing a €45 Film Alternative]]></title><description><![CDATA[The Parallel: Soul vs.]]></description><link>https://www.nikmalykhin.com/p/the-iso-wall-and-the-ccd-testing</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-iso-wall-and-the-ccd-testing</guid><pubDate>Mon, 20 Apr 2026 07:01:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!guio!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Parallel: Soul vs. Spec Sheets</h2><p>I recently moved the family to Spain, and in the process, I liquidated most of my camera gear. I told myself I&#8217;d be happy with the smartphone in my pocket, but after a few months of shooting the boys in the Mediterranean light, I realized the images felt sterile. They were too perfect, too computed. I found myself missing the unpredictability of film&#8212;the way a certain stock renders a sunset not as a collection of high-dynamic-range data points, but as a mood. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!guio!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!guio!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 424w, https://substackcdn.com/image/fetch/$s_!guio!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 848w, https://substackcdn.com/image/fetch/$s_!guio!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!guio!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!guio!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg" width="1358" height="2048" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2048,&quot;width&quot;:1358,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1200366,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nikmalykhin.com/i/194607577?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!guio!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 424w, https://substackcdn.com/image/fetch/$s_!guio!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 848w, https://substackcdn.com/image/fetch/$s_!guio!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!guio!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F70600991-6a8b-40fc-bec3-49d86940f7dc_1358x2048.jpeg 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 style="text-align: right;"><em>A 35mm film photograph</em></p><p>However, I didn&#8217;t want to deal with the rising cost of rolls or the lead times of lab processing while trying to settle into a new country. I started wondering if I could find a shortcut to that aesthetic by looking backward rather than forward.</p><h2>The Setup: The S45 System</h2><p>I decided to test a theory I&#8217;d seen floating around certain corners of the web: that early digital sensors possess a &#8220;soul&#8221; that modern ones have scrubbed away in the name of efficiency. I spent &#8364;45 on a Canon PowerShot S45, a brick-like device from 2002. It doesn&#8217;t have a modern CMOS sensor; instead, it uses a <em>CCD</em>, a type of light-gathering hardware that reads the entire sensor at once and, arguably, renders color with a more organic, film-like saturation.</p><p>Plaintext</p><pre><code><code>System: Canon PowerShot S45 (Circa 2002)
Sensor: 1/1.8" CCD (4.0 Megapixels)
Processor: DIGIC 1
Storage: CompactFlash (CF)
Interface: Tactical sliding lens cover, manual control dial
</code></code></pre><p>Holding the S45 feels like holding a piece of industrial equipment. It has weight, it makes mechanical noises, and it forces a specific cadence. You cannot &#8220;spray and pray&#8221; with this device. It demands that you wait for the buffer to clear.</p><h2>The Friction: The ISO Wall</h2><p>The experiment hit reality the moment the sun began to dip behind the hills. Modern sensors have spoiled us; we expect to shoot in near-darkness and let software sort out the mess. The DIGIC 1 processor inside the S45 has no such intelligence. I quickly discovered what I call the ISO Wall.</p><p>While the camera claims to go higher, anything above <em>ISO 100</em> introduces a level of electronic noise that doesn&#8217;t look like pleasant film grain&#8212;it looks like a broken television. The sensor &#8220;fatigues&#8221; almost immediately when the light isn&#8217;t optimal. This constraint changed my behavior. I stopped trying to capture everything and started looking for the light first, and the subject second. If the light wasn&#8217;t there, the camera stayed in my pocket. It is a fragile system that requires a high-light environment to maintain its integrity.</p><h2>The Signal and Load</h2><p>There is a significant difference in the cognitive load between shooting with an iPhone and the S45. With the phone, the signal is &#8220;everything is a photo.&#8221; The computational overhead is handled by the device, leaving me with a flat, predictable result. With the S45, the <strong>signal-to-noise ratio</strong> is much tighter. I have to think about the exposure compensation and the white balance because the early internal logic often gets it wrong.</p><blockquote><p>The friction of using old tech is actually a filter; it forces you to decide if a moment is actually worth the effort of capturing.</p></blockquote><p>Surprisingly, when I run the files through my PIXMA G650 printer at 13x18 size, the 4-megapixel files hold up beautifully. The &#8220;imperfections&#8221;&#8212;the slight softness and the specific way the CCD handles the blues and reds&#8212;provide a look that I would usually spend twenty minutes trying to emulate in post-processing software.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dooc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dooc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dooc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dooc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dooc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dooc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg" width="1456" height="1959" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1959,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:665441,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nikmalykhin.com/i/194607577?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dooc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dooc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dooc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dooc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e3a6a89-6022-415d-a968-5588e8486899_1522x2048.jpeg 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 style="text-align: right;">A CCD-sensor shot from the S45</p><h2>What Stood the Test</h2><p>The experiment confirmed that I don&#8217;t need to chase a $700 Fujifilm X100 to feel inspired. The &#8220;Third Way&#8221; of photography is now my ground truth. It&#8217;s a space that sits between the mindless convenience of a smartphone and the high-maintenance ritual of film.</p><p>The S45 proved that character matters more than cost. The hardware is slow, the screen is tiny, and the battery life is questionable, but the output has an aesthetic &#8220;thickness&#8221; that modern gear lacks. It isn&#8217;t a 1:1 replacement for 35mm film, but it satisfies the same creative itch for a fraction of the price.</p><h2>Final Reflections</h2><p>I am merging the &#8220;vintage digital&#8221; approach into my permanent toolkit. The S45 will stay in my bag for those bright, coastal afternoons where I want the world to look a bit more like a memory and less like a data set. I&#8217;m backlogging the idea of buying a high-end mirrorless body for now; the &#8220;Side Quest&#8221; taught me that I was bored with the sensor, not the hobby.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Refactoring life, one Side Quest at a time.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Cognitive Cost of AI Delegation]]></title><description><![CDATA[Reflections on the Attention Economy and AI Etiquette]]></description><link>https://www.nikmalykhin.com/p/the-cognitive-cost-of-ai-delegation</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-cognitive-cost-of-ai-delegation</guid><pubDate>Tue, 14 Apr 2026 07:02:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><strong>The Brake-Fade on the Downhill (The Hook)</strong></h3><p>When you&#8217;re descending a steep technical trail on a mountain bike, your most precious resource isn&#8217;t your speed&#8212;it&#8217;s your <strong>biological energy</strong> and grip strength. If you spend the entire descent white-knuckling the brakes because you&#8217;re afraid of the terrain, you hit &#8220;brake fade.&#8221; The system overheats, your hands cramp, and by the time you reach the truly dangerous rock garden at the bottom, you have zero &#8220;focus capital&#8221; left to navigate it. You crash not because the trail was too hard, but because you wasted your resources on the easy parts.</p><p>In the professional world, GenAI is being marketed as the ultimate &#8220;ebike&#8221; for our brains. The industry assumption is that more output equals more productivity. But if this &#8220;unlimited output&#8221; is the popular choice, why does it feel like I&#8217;m fighting the system? Why does receiving a perfectly formatted, AI-generated A4 page feel like a cognitive &#8220;crash&#8221; before I&#8217;ve even reached the conclusion?</p><h3><strong>The Architecture of the Proxy Mind (The Landscape)</strong></h3><p>The environment I&#8217;m navigating isn&#8217;t just a chat interface; it&#8217;s a <strong>Mind-to-Mind Pipeline</strong> where the AI acts as a middleware layer. We are dealing with a system defined by the following geometry:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;b625c3e7-feba-451b-a272-85eddc0b8732&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">[Input: Raw/Unorganized Chaos]

          &#8595;

[Processor: GenAI &#8220;Mind Extension&#8221;]

          &#8595;

[Output: Structured Narrative (High Volume)]

          &#8595;

[Buffer: Human Reviewer (The Fatigue Point)]

          &#8595;

[Destination: Recipient&#8217;s Attention Span]</code></pre></div><p>The constraints here are rigid. The LLM has no &#8220;physical&#8221; weight, but its output carries massive <strong>cognitive weight</strong>. The dependencies are tightly coupled: if I delegate the &#8220;thinking&#8221; to the tool without managing the &#8220;output volume,&#8221; the invisible boundary of the recipient&#8217;s attention is breached. Data moves through this space quickly, but <strong>meaning</strong> gets trapped in the friction of the preamble.</p><h3><strong>The A4 Saturation Point (The Stress Test)</strong></h3><p>I moved my observations from the &#8220;theoretical path&#8221; to the &#8220;actual terrain&#8221; where people have many unread messages.</p><p>&#10148; <strong>The Breaking Point:</strong> The methodology of &#8220;Ask and Forward&#8221; failed at the third iteration. When I pushed a full A4 page of structured AI text to a colleague, the system showed immediate fatigue.</p><p>&#10148; <strong>The Silent Failure:</strong> The recipient didn&#8217;t tell me the text was too long. Instead, they &#8220;swallowed&#8221; the error&#8212;skimming the preamble, missing the critical &#8220;result of work&#8221; buried in the middle, and asking a question that was already answered in the text.</p><p>&#10148; <strong>The Observation:</strong> The gap between the &#8220;Structured Answer&#8221; provided by the AI and the actual <strong>Information Transferred</strong> was a massive chasm. While I didn&#8217;t measure the exact percentage, the observation was clear: the system was technically functioning, but the mission failed. The recipient&#8217;s focus simply didn&#8217;t survive the &#8220;A4 size&#8221; barrier.</p><h3><strong>The Noise Floor of the Preamble (The Handoff)</strong></h3><p>This is a failure of delegation. When we use AI to structure &#8220;unstructured vision,&#8221; we often translate our goal into an action that generates <strong>clutter</strong> rather than <strong>clarity</strong>.</p><p>&#10148; <strong>Signal-to-Noise:</strong> GenAI tools are programmed to be &#8220;helpful,&#8221; which means adding long, polite preambles and exhaustive summaries. This is the <strong>&#8220;noise floor&#8221;</strong>.</p><p>&#10148; <strong>Cognitive Load:</strong> By sending unedited AI responses, you aren&#8217;t saving time; you are just shifting the <strong>processing debt</strong> onto the recipient. You spend 10 seconds generating the text, but you force the recipient to spend minutes mining it for value. This eventually leads to a &#8220;system blackout&#8221; where people ignore messages entirely.</p><h3><strong>The Hard Character Limit (The Verification)</strong></h3><p>After observing these failures, only one principle remained standing: <strong>The Short Style Constraint</strong>.</p><p>&#10148; <strong>Stability:</strong> The only communication that survived the &#8220;skimming&#8221; reflex was the <strong>&#8220;Elevator Pitch&#8221;</strong> format. When forced into a tight container, the AI is actually better at its job. It stops &#8220;hallucinating value&#8221; through word count and starts organizing logic.</p><p>&#10148; <strong>The New Baseline:</strong> The trusted approach is the <strong>Init Prompt Constraint</strong>. I tell the system: &#8220;Structure my thoughts, but do not exceed 280 characters&#8221; or &#8220;Provide the result first, no preamble&#8221;.</p><p>&#10148; <strong>The Evolution:</strong> I no longer view AI as a &#8220;writer&#8221;; I view it as a <strong>compressor</strong>. The strategy has shifted from using AI to say more to using it to say exactly enough.</p><h3><strong>The Navigator&#8217;s Log (Actionable Insights)</strong></h3><p>&#10148; <strong>Backlog:</strong></p><ul><li><p>The &#8220;A4-size&#8221; response&#8212;a legacy format that died with the printer.</p></li><li><p>&#8220;Respectful&#8221; AI preambles&#8212;they are actually disrespectful to the recipient&#8217;s time.</p></li><li><p>Trusting the human brain to catch errors in long AI texts after multiple iterations (brain laziness is a hardware feature, not a bug).</p></li></ul><p>&#10148; <strong>Merged:</strong></p><ul><li><p><strong>The &#8220;Short Style&#8221; Init Prompt:</strong> Force the AI into a constraint <em>before</em> it generates a single word.</p></li><li><p><strong>Energy Conservation:</strong> Spend mental energy on the <strong>constraint</strong>, not on editing massive, verbose text.</p></li><li><p><strong>The Win-Win Protocol:</strong> If the sender spends less energy reviewing and the recipient spends less energy reading, the system remains stable.</p></li></ul><p><strong>Final Wisdom:</strong> In a world of infinite AI-generated noise, the most &#8220;premium&#8221; technical skill is the discipline to <strong>limit</strong> content. Be respectful to the system, or the system will stop listening.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[𝗧𝗵𝗲 𝗣𝗿𝗮𝗴𝗺𝗮𝘁𝗶𝗰 𝗛𝗲𝘅𝗮𝗴𝗼𝗻: 𝗦𝗰𝗮𝗹𝗶𝗻𝗴 𝗗𝗲𝗰𝗼𝘂𝗽𝗹𝗶𝗻𝗴 𝘄𝗶𝘁𝗵𝗼𝘂𝘁 𝗖𝗼𝗺𝗽𝗹𝗲𝘅𝗶𝘁𝘆]]></title><description><![CDATA[&#120295;&#120309;&#120306; &#120295;&#120306;&#120315;&#120320;&#120310;&#120316;&#120315; &#120316;&#120315; &#120321;&#120309;&#120306; &#120295;&#120319;&#120302;&#120310;&#120313;]]></description><link>https://www.nikmalykhin.com/p/pragmatic-hexagon</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/pragmatic-hexagon</guid><pubDate>Tue, 24 Mar 2026 13:21:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3><strong>&#120295;&#120309;&#120306; &#120295;&#120306;&#120315;&#120320;&#120310;&#120316;&#120315; &#120316;&#120315; &#120321;&#120309;&#120306; &#120295;&#120319;&#120302;&#120310;&#120313;</strong></h3><p>In a professional kitchen, there is a concept called <em>mise en place</em>&#8212;everything in its place. You don&#8217;t start searing the scallops until every herb is chopped and every sauce is whisked. If you skip the prep to &#8220;save time,&#8221; you end up adjusting the recipe mid-saut&#233;, usually resulting in a frantic mess, ruined ingredients, and a dish that takes twice as long to serve.</p><p>Modern software development has a similar &#8220;popular choice&#8221;: start coding the logic immediately to show &#8220;progress.&#8221; But when we skip the architectural prep&#8212;the interfaces and boundaries&#8212;we aren&#8217;t moving fast; we are just building a kitchen we&#8217;ll have to tear down while the customers are waiting. I&#8217;ve watched engineers lose sight of the goal in the pursuit of a &#8220;perfect flow&#8221; that wasn&#8217;t grounded in discipline. If everyone says they want &#8220;clean code,&#8221; why does the system feel like it&#8217;s fighting us the moment we add a new story?</p><h3><strong>&#120294;&#120326;&#120320;&#120321;&#120306;&#120314; &#120282;&#120306;&#120316;&#120314;&#120306;&#120321;&#120319;&#120326;</strong></h3><p>The environment of this experiment is a standard <strong>Kotlin and Spring Boot</strong> stack. The landscape is defined by three distinct zones designed to minimize the &#8220;weight&#8221; of dependencies. To navigate this space, we use a rigid directory structure that acts as our map:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;78781d66-8266-4010-b6a8-432cfa8a8d42&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">app

&#9500;&#9472;&#9472; domain      &lt;-- THE HEART (POKOs only)
&#9474;   &#9500;&#9472;&#9472; model
&#9474;   &#9474;   &#9492;&#9472;&#9472; Data.kt     &lt;-- Pure Kotlin Data Class
&#9474;   &#9492;&#9472;&#9472; ports
&#9474;       &#9492;&#9472;&#9472; outgoing    &lt;-- Interfaces defining &#8220;What&#8221; we need
&#9474;           &#9500;&#9472;&#9472; DataPersistencePort.kt    &lt;- SQL db
&#9474;           &#9492;&#9472;&#9472; DataStoragePort.kt        &lt;- Object storage
&#9500;&#9472;&#9472; usecases    &lt;-- THE ORCHESTRATOR
&#9474;   &#9492;&#9472;&#9472; StoreDataUseCase.kt    &lt;-- Feature logic
&#9492;&#9472;&#9472; adapter     &lt;-- THE &#8220;HOW&#8221; (Infrastructure)
    &#9500;&#9472;&#9472; web         &lt;-- Inbound Adapter
    &#9474;   &#9500;&#9472;&#9472; DataController.kt
    &#9474;   &#9500;&#9472;&#9472; dto         &lt;-- Request/Response DTOs
    &#9474;       &#9492;&#9472;&#9472; WebMapper.kt    &lt;-- DTO &lt;-&gt; Domain mapping
    &#9500;&#9472;&#9472; sqldb       &lt;-- Outbound Adapter
    &#9474;   &#9500;&#9472;&#9472; entity
    &#9474;   &#9474;   &#9492;&#9472;&#9472; DataJpaEntity.kt    &lt;-- @Entity + JPA annotation
    &#9474;   &#9500;&#9472;&#9472; DataRepository.kt        &lt;-- Spring Data/CrudRepository
    &#9474;   &#9500;&#9472;&#9472; PersistenceMapper.kt     &lt;-- Entity &lt;-&gt; Domain mapping
    &#9474;   &#9492;&#9472;&#9472; PersistenceAdapter.kt    &lt;-- Impl DataPersistencePort
    &#9492;&#9472;&#9472; cloud       &lt;-- Outbound Adapter
        &#9492;&#9472;&#9472; ObjectStorageAdapter.kt</code></pre></div><p>&#10148; <strong>The Heart (Domain):</strong> Pure Kotlin Data Classes and business logic common to all usecases.</p><p>&#10148; <strong>The Orchestrator (Usecases):</strong> Where feature-specific logic lives and adapters are coordinated.</p><p>&#10148; <strong>The Infrastructure (Adapters):</strong> The &#8220;How&#8221; of the system&#8212;web controllers, JPA entities, and cloud storage clients.</p><p>The invisible boundary here is the <strong>Port</strong>. It&#8217;s an interface that defines &#8220;what&#8221; we need without caring &#8220;how&#8221; it&#8217;s done. In theory, this geometry should be light and flexible, yet many teams find it rigid because they misunderstand the direction of the signal.</p><h3><strong>&#120280;&#120314;&#120317;&#120310;&#120319;&#120310;&#120304;&#120302;&#120313; &#120280;&#120325;&#120317;&#120313;&#120316;&#120319;&#120302;&#120321;&#120310;&#120316;&#120315;</strong></h3><p>I moved from the &#8220;theoretical path&#8221; of perfect architecture to the &#8220;actual terrain&#8221; of daily PRs. The system showed its breaking point not in a crash, but in a silent failure of discipline: the <strong>Domain Import Leak</strong>.</p><p>&#10148; <strong>The Breaking Point:</strong> It usually starts when an engineer adds a domain service that directly imports an adapter: import app.adapter.NewAdapter.kt.</p><p>&#10148; <strong>The Silent Failure:</strong> The code still passes tests. It still &#8220;works&#8221;. But the &#8220;Pure Domain&#8221; has been poisoned by infrastructure concerns.</p><p>&#10148; <strong>The Result:</strong> When the time inevitably comes to move that service to a usecase, the system reacts with extreme fatigue. We end up with PRs requiring the renaming of tens of files, leading to typos, package mismatches, and a massive mental load on reviewers.</p><h3><strong>&#120288;&#120302;&#120315;&#120302;&#120308;&#120310;&#120315;&#120308; &#120321;&#120309;&#120306; &#120294;&#120310;&#120308;&#120315;&#120302;&#120313;</strong></h3><p>The handoff between layers is where the &#8220;spaghetti&#8221; starts or ends. In my exploration, I found that the clarity of intent is often lost because teams are afraid of the &#8220;complexity&#8221; of an extra interface.</p><p>&#10148; <strong>Cognitive Load:</strong> Trying to refactor architecture in the middle of a feature story creates a &#8220;refactoring nightmare&#8221;.</p><p>&#10148; <strong>Signal-to-Noise:</strong> If you are 100% sure a logic block belongs in the domain, put it there. If not, the &#8220;cleaner&#8221; signal is to start in a <strong>Usecase</strong> and extract downward only when the need is proven.</p><p>&#10148; <strong>Direct Translation:</strong> To keep the signal clear, I&#8217;ve found it&#8217;s even acceptable to call a Port directly from a controller for simple cases. This avoids 1:1 &#8220;pass-through&#8221; mapping while keeping the adapter decoupled through the interface.</p><h3><strong>&#120298;&#120309;&#120302;&#120321; &#120280;&#120302;&#120319;&#120315;&#120306;&#120305; &#120295;&#120319;&#120322;&#120320;&#120321;?</strong></h3><p>After the stress test of &#8220;no time to decouple,&#8221; one principle remained standing: <strong>Mandatory Ports from the Start</strong>.</p><p>&#10148; <strong>Stability:</strong> The &#8220;price&#8221; of an interface at the start is effectively zero. It provides an immediate boundary that prevents the &#8220;import leak&#8221; and allows the domain to remain pure. &#10148; <strong>The New Baseline:</strong> My trusted navigation strategy is now <strong>TDD-driven Hexagon</strong>.</p><p>&#8226; <strong>Step 1:</strong> Define the Domain Model.</p><p>&#8226; <strong>Step 2:</strong> Build the Adapter and verify it with <strong>Testcontainers</strong> (SQL or Object Storage).</p><p>&#8226; <strong>Step 3:</strong> Finally, orchestrate it all in the Usecase or Controller using the Port interface.</p><h3><strong>&#120276;&#120304;&#120321;&#120310;&#120316;&#120315;&#120302;&#120303;&#120313;&#120306; &#120284;&#120315;&#120320;&#120310;&#120308;&#120309;&#120321;&#120320;</strong></h3><p>&#10148; <strong>Backlog (Failed the Stress Test):</strong></p><p>&#8226; &#8220;Refactoring-in-the-middle&#8221;: Changing architecture while delivering a story leads to mess and typos.</p><p>&#8226; Direct Adapter Imports: Any import app.adapter inside app.domain is a bug, not a feature.</p><p>&#10148; <strong>Merged (Trusted Toolkit):</strong></p><p>&#8226; <strong>Ports First:</strong> Always create the interface for 3rd party services or repositories immediately.</p><p>&#8226; <strong>Adapter-First Testing:</strong> Use Testcontainers to prove your &#8220;How&#8221; works before you worry about the &#8220;What&#8221; in your orchestration.</p><p>&#8226; <strong>Minimum Layers:</strong> Only add a Usecase layer if there is actual orchestration; otherwise, call the Port from the Controller.</p><p><strong>Final Wisdom:</strong> Clean architecture isn&#8217;t about having the most layers; it&#8217;s about having the most resilient boundaries. The &#8220;price&#8221; of an interface is nothing compared to the cost of a messy PR that no one wants to review.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The 24-Inch Migration: Onboarding a 5-Year-Old to New Hardware]]></title><description><![CDATA[Learn how to apply software engineering principles&#8212;like look-ahead buffers and integration testing&#8212;to manage complex hardware migrations. Discover how to transition a junior rider to a new platform while protecting the Developer Experience (DX) and fostering long-term system ownership.]]></description><link>https://www.nikmalykhin.com/p/the-24-inch-migration-onboarding</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/the-24-inch-migration-onboarding</guid><pubDate>Tue, 17 Mar 2026 11:03:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!h0kn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of software, we often talk about &#8220;breaking changes.&#8221; You upgrade a core library, and suddenly the interfaces you relied on are deprecated, the latency spikes, and the system becomes unpredictable. Last week, I attempted a major version upgrade on my 5-year-old son&#8217;s primary transport layer: we moved from a 16-inch &#8220;legacy&#8221; bike to a <strong>Specialized Hotrock 24</strong>.</p><p>Physically, he was ready. He&#8217;s tall for his age, and the metrics suggested he could handle the 24-inch wheels. But as any Tech Lead knows, just because the hardware supports the requirements doesn&#8217;t mean the operator is ready to push to production.</p><h2>The System Architecture: Specialized Hotrock 24</h2><p>In this migration, the hardware selection was about finding the right <strong>Long Term Support (LTS)</strong> release. We skipped the 20-inch version entirely; in our roadmap, a 20-inch bike was a short-term patch that would only serve us for a year or two before hitting its end-of-life.</p><p>We went straight for the 24-inch platform as our LTS. To make this high-performance hardware compatible with a 5-year-old&#8217;s geometry, I chose the Hotrock for its low-slung frame&#8212;think of it as a <strong>compatibility layer</strong> or a &#8220;shim&#8221; that allows a smaller user to interface with a much larger system architecture.</p><h2>The Debugging Phase: Staging Environment (Weekend 1)</h2><p>We didn&#8217;t head straight for the trails. That would be like deploying a refactored monolith to 100% of users without a staging environment. We set up a 3x3 meter &#8220;Sandbox&#8221; in a parking lot to run our first integration tests.</p><h3>1. The Look-Ahead Buffer (The Square)</h3><p>The first bug we encountered was <strong>Visual Latency</strong>. He was looking at his front wheel&#8212;the equivalent of a system only processing the data packet currently in the buffer.</p><p><strong>The Fix:</strong> I implemented a new algorithm. <em>Start at Cone 1, look at Cone 2. When the front wheel enters the zone between 1 and 2, immediately point the sensors (eyes) toward Cone 3.</em> We were teaching him to process future state while executing current operations.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h0kn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h0kn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 424w, https://substackcdn.com/image/fetch/$s_!h0kn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 848w, https://substackcdn.com/image/fetch/$s_!h0kn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!h0kn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h0kn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg" width="1456" height="1096" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1096,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5694022,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nikmalykhin.com/i/191237160?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h0kn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 424w, https://substackcdn.com/image/fetch/$s_!h0kn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 848w, https://substackcdn.com/image/fetch/$s_!h0kn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!h0kn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97d9ca56-b3b9-4b46-8a6c-4a109979f92a_4080x3072.jpeg 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><h2>2. The I/O Interrupt v1.0 (Stop-on-Line)</h2><p>We tested the &#8220;Stop&#8221; command with a simple line. At this stage, we kept the requirements low: just execute a <code>HALT</code> command exactly on the line. He passed this test without issues&#8212;the braking interface was working, even if it was still a bit binary.</p><h2>Scaling the System (Weekend 2)</h2><p>Once the basic &#8220;Look-Ahead&#8221; logic was cached, we increased the complexity of our tests.</p><h3>2.1 The I/O Interrupt v1.1 (The &#8220;No-Touch&#8221; Constraint)</h3><p>We refactored the stop-and-go drill. Now, he had to stop on the line and then resume driving <em>without</em> touching the floor. This was about refining balance and power delivery&#8212;moving from a simple halt to a complex state transition.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hCuS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hCuS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hCuS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hCuS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hCuS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hCuS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg" width="1456" height="1934" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1934,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4512976,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nikmalykhin.com/i/191237160?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hCuS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hCuS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hCuS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hCuS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e744fb6-a17b-4f74-99d6-acd5107c51b1_3072x4080.jpeg 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>3. The Slalom (Logic Branching)</h3><p>Finally, we introduced the Slalom. This was a true logic-branching exercise: navigating a sequence of four cones. It required high-frequency adjustments to his trajectory based on the &#8220;Look-Ahead&#8221; data he was now successfully processing.</p><h2>The &#8220;Merged PR&#8221;: Managing the Developer Experience (DX)</h2><p>The first weekend wasn&#8217;t a &#8220;success&#8221; by pure performance metrics. He failed several drills, the &#8220;build&#8221; felt shaky, and the cones remained largely un-navigated.</p><p>But here is the most important log entry: <strong>He didn&#8217;t get frustrated.</strong> In my day job, when a Junior Developer (or an AI agent like Jules) struggles with a new stack, the worst thing a Tech Lead can do is demand they stay until midnight to &#8220;fix the build.&#8221; That is how you accrue <strong>Human Technical Debt</strong>&#8212;you might get the code merged today, but you&#8217;ve poisoned the developer&#8217;s relationship with the codebase for tomorrow.</p><p>By applying a &#8220;Freedom of Decision&#8221; protocol and capping sessions at 15 minutes, we prioritized the <strong>Developer Experience</strong>. Because I didn&#8217;t push, he didn&#8217;t associate the new hardware with stress. We maintained a high &#8220;morale-to-output&#8221; ratio, ensuring he was excited to &#8220;reboot&#8221; the training the following weekend.</p><p><strong>The Feature:</strong> By the end of the second weekend, something clicked. It wasn&#8217;t about completing the drills perfectly&#8212;it was about the <em>feel</em>. The &#8220;Look-Ahead&#8221; algorithm was finally running in the background, and he started to feel comfortable on the new hardware.</p><h2>The Post-Deployment Cleanup: Ownership</h2><p>The real sign that the migration was a success came after the training was over. Without being asked, he started cleaning the bike himself.</p><p>In engineering, we call this <strong>Full-Cycle Ownership</strong>. It&#8217;s the moment a developer stops just writing code and starts caring about the health of the system they operate. Seeing a 5-year-old wipe down his own &#8220;hardware&#8221; after a successful sprint in the sandbox is the ultimate proof of engagement. He wasn&#8217;t just using the tool; he was owning it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!940b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!940b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!940b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!940b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!940b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!940b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg" width="1456" height="1934" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1934,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3369257,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nikmalykhin.com/i/191237160?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!940b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 424w, https://substackcdn.com/image/fetch/$s_!940b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 848w, https://substackcdn.com/image/fetch/$s_!940b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!940b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F374b8953-b0fb-43b7-8456-6d931c84eb32_3072x4080.jpeg 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><h3>The Log:</h3><ul><li><p><strong>Hardware:</strong> Specialized Hotrock 24 (LTS Migration).</p></li><li><p><strong>Total Training Time:</strong> Two 15-minute sprints.</p></li><li><p><strong>Bugs Fixed:</strong> Visual Latency (Front-wheel staring).</p></li><li><p><strong>Post-Deployment:</strong> Automatic system maintenance (he cleaned the bike).</p></li><li><p><strong>Emotional ROI:</strong> High. The goal isn't to go fast on day one&#8212;it's to make sure that when we finally hit the trails, the pilot feels like the system belongs to him.</p></li></ul><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Refactoring life, one Side Quest at a time.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Lying Tests and the Silent Swallow: Hardening Legacy Java]]></title><description><![CDATA[Is your CI/CD pipeline telling you the truth, or is it just telling you what you want to hear?]]></description><link>https://www.nikmalykhin.com/p/lying-tests-and-the-silent-swallow</link><guid isPermaLink="false">https://www.nikmalykhin.com/p/lying-tests-and-the-silent-swallow</guid><pubDate>Tue, 17 Mar 2026 08:00:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-Ojx!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8d27381-c618-42b7-a15f-62e1d625e22d_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Is your CI/CD pipeline telling you the truth, or is it just telling you what you want to hear?</strong> </p><p>In many legacy projects, the build is &#8220;Green,&#8221; the tests pass, and the console shows no errors. Yet, the moment the application hits production, it fails. The culprit is often a &#8220;Lying Test&#8221;&#8212;a suite that passes not because the code works, but because the errors have been carefully hidden, logged to a void, or suppressed by a generic catch-all block.</p><p>How do you turn a &#8220;politely silent&#8221; codebase into one that fails loudly enough to be fixed?</p><h3>The &#8216;Before&#8217; State: Setting the Context</h3><p>In older Java applications (circa 2005), error handling was often synonymous with <code>e.printStackTrace()</code>. Developers used manual <code>main()</code> methods or early JUnit versions to &#8220;test&#8221; logic. When an exception occurred, the instinct was to keep the process running at all costs.</p><p>The &#8220;old way&#8221; of testing often looked like this:</p><ul><li><p><strong>The Silent Swallow:</strong> Generic <code>catch (Exception e)</code> blocks that log a message but do not rethrow or signal failure.</p></li><li><p><strong>Exit Code 0:</strong> Build scripts (Ant) that encounter a runtime error but still report a successful exit code, tricking the developer into thinking everything is fine.</p></li><li><p><strong>Manual Verification:</strong> Tests that require a human to read the console output to see if it &#8220;looks right,&#8221; rather than asserting a specific outcome.</p></li></ul><h3>Introducing the Core Concept: Honest Testing</h3><p><strong>Honest Testing</strong> is the process of stripping away the &#8220;safety blankets&#8221; of legacy error handling to force the application to <strong>Crash Loudly.</strong></p><p><strong>What is it?</strong> It is a &#8220;Hardening Phase&#8221; where you replace swallowed exceptions with meaningful failures and migrate manual checks to automated assertions.</p><p><strong>Why does it matter?</strong> You cannot refactor code you do not understand. If your tests are lying to you about the state of the system, any &#8220;improvement&#8221; you make is just a guess. Making the build <strong>RED</strong> is the first step toward making it truly <strong>GREEN.</strong></p><h3>Practical Applications &amp; Use Cases</h3><h4>Use Case A: Exposing the Silent Swallow</h4><p>The most common anti-pattern in legacy Java is the &#8220;Log and Forget&#8221; block. We must convert these into loud failures during the testing phase.</p><pre><code><code>// BEFORE: The Lying Code
public void storeData() {
    try {
        // critical logic
    } catch (Exception e) {
        System.out.println("Error happened, but let's keep going!");
    }
}

// AFTER: Honest Code for Testing
public void storeData() {
    try {
        // critical logic
    } catch (Exception e) {
        // Re-throwing as a RuntimeException forces the test to fail
        throw new RuntimeException("Hardened Failure: Data storage failed", e);
    }
}
</code></code></pre><p><em>Benefit: The test suite will now immediately catch failures that were previously invisible.</em></p><h4>Use Case B: From <code>main()</code> to JUnit 5</h4><p>Legacy projects often have &#8220;test&#8221; classes that are just <code>public static void main(String[] args)</code> methods. These don&#8217;t integrate with CI/CD.</p><pre><code><code>// Migrating to JUnit 5 Assertions
@Test
void testBackendConnection() {
    Backend b = new Backend("qbert.guba.com");
    // Instead of printing to console, we assert the state
    assertDoesNotThrow(() -&gt; b.connect(), "Connection should be stable");
    assertNotNull(b.getStatus(), "Status should be initialized");
}
</code></code></pre><p><em>Benefit: Provides a quantifiable &#8220;Safety Net&#8221; that build tools like Gradle can interpret as a Pass/Fail signal.</em></p><h3>Common Pitfalls &amp; Misconceptions</h3><p><strong>The &#8220;Fear of Red&#8221; Pitfall:</strong> Many teams are terrified of a broken build. They think that if the build turns red, they&#8217;ve failed.</p><p><strong>The Truth:</strong> In legacy refactoring, a <strong>Red Build</strong> is a victory. It means you&#8217;ve finally found the boundaries of the system. You&#8217;ve moved from &#8220;unknown-unknowns&#8221; to &#8220;known-knowns.&#8221; Don&#8217;t rush to fix the red; use it as a map to find where the code is truly broken.</p><h3>Core Trade-offs &amp; Nuances</h3><ul><li><p><strong>The &#8220;Crash&#8221; Period:</strong> When you start hardening tests, the project might not compile or pass for days. This requires stakeholder buy-in&#8212;you are breaking the &#8220;illusion of stability&#8221; to find the &#8220;reality of the debt.&#8221;</p></li><li><p><strong>Log Noise:</strong> Hardening exceptions often results in massive stack traces in your logs. This is necessary labor; you have to clean the noise to find the signals.</p></li></ul><h3>Forward-Looking Conclusion</h3><p>A &#8220;Green Build&#8221; is only valuable if it is earned. By removing the &#8220;Silent Swallows&#8221; from your legacy Java project, you are performing a diagnostic surgery. It is painful, and it reveals the rot, but it is the only way to heal the codebase.</p><p>Once your tests are honest, you can finally apply modern AI tools and refactoring patterns with confidence. You aren&#8217;t just &#8220;hacking&#8221; anymore; you are <strong>Engineering.</strong></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.nikmalykhin.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe to get more practical guides on using GenAI tools effectively in software development work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>