<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>Ryan Zidago&#x27;s Blog</title>
	<subtitle>Product engineering and AI&#x2F;LLMs</subtitle>
	<link rel="self" type="application/atom+xml" href="https://ryanzidago.com/posts/feed.xml"/>
  <link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/"/>
  
	<updated>2026-08-13T00:00:00+03:00</updated>
	
	<id>https://ryanzidago.com/posts/feed.xml</id>
	<entry xml:lang="en">
		<title>AI code reviewers should provide executable evidence</title>
		<published>2026-08-13T00:00:00+03:00</published>
		<updated>2026-08-13T00:00:00+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/ai-code-reviewers-should-provide-executable-evidence/"/>
		<id>https://ryanzidago.com/posts/ai-code-reviewers-should-provide-executable-evidence/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/ai-code-reviewers-should-provide-executable-evidence/">&lt;p&gt;Code-review agents should express actionable findings as failing tests against the expected correct behaviour whenever possible.
A code review comment is a claim but a failing test is a claim &lt;strong&gt;and an executable evidence&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This make findings more reliable, easier to understand, and cheaper to communicate.&lt;&#x2F;p&gt;
&lt;p&gt;More reliable:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Running tests verifies the alleged defect exists and filters out false positives.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Easier to understand:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A test demonstrates the problematic behaviour and expected behaviour in executable form.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Cheaper to communicate:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A concise test can replace a lengthy prose explanation, reducing review effort and context window usage (for both humans and AI agents alike!!!)
I have concrete examples at work where Claude Opus 5 used 2,892 characters to explain something that Codex had expressed as a failing test in 20 lines of test code.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;example&quot;&gt;Example&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;avoid&quot;&gt;Avoid&lt;&#x2F;h2&gt;
&lt;p&gt;A long prose comment that asks the reviewer to reconstruct and verify the claim:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Order.total&#x2F;1&lt;&#x2F;code&gt; appears to allow discounts greater than the subtotal. This can produce a negative total when, for example, an order with a subtotal of 100 cents receives a discount of 150 cents. The total should probably never be lower than zero. Please consider clamping the result or validating the discount before subtracting it.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The finding may be correct, but the reviewer still has to determine whether the input is valid, reproduce the issue, and confirm the current behaviour.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;prefer&quot;&gt;Prefer&lt;&#x2F;h2&gt;
&lt;p&gt;Express the same finding as a failing test:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;@doc &amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;Applying a discount greater than the subtotal produces a negative order total.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;A negative total could result in an invalid refund or credit instead of a charge.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;the total cannot be lower than zero&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  order &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Order&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;subtotal&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; discount&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 150&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Order&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;total&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;order&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The test makes the input, disputed behaviour, and proposed expectation explicit. Running it proves whether the finding applies to the codebase: if the test fails because &lt;code&gt;Order.total&#x2F;1&lt;&#x2F;code&gt; returns &lt;code&gt;-50&lt;&#x2F;code&gt;, the reviewer has executable evidence of the defect.&lt;&#x2F;p&gt;
&lt;p&gt;And if you want to make it even easier for your agents to communicate code review findings, tell them to follow these rules for writing &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ryanzidago.com&#x2F;posts&#x2F;self-contained-tests-in-elixir&#x2F;&quot;&gt;self-contained tests in Elixir&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;two-important-qualifications&quot;&gt;Two important qualifications&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;test-the-expected-behaviour&quot;&gt;Test the expected behaviour&lt;&#x2F;h2&gt;
&lt;p&gt;The LLM needs to understand your intent unambiguously.&lt;&#x2F;p&gt;
&lt;p&gt;I had cases where it was writing the test that proved the error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;the total is lower than zero&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  org &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Org&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;subtotal&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; discout&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 150&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Order&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;total&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;order&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;50&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This isn&#x27;t as useful as declaring what the correct behaviour is and the test will be obsolete as soon as we fix the issue.
This is why I use the term &quot;failing test against the expected correct behaviour&quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;not-every-finding-needs-a-test&quot;&gt;Not every finding needs a test&lt;&#x2F;h2&gt;
&lt;p&gt;When a finding claims that a particular input or state produces incorrect observable behaviour, prefer a failing test. Findings about naming, readability, or architecture may still be better expressed in prose.&lt;&#x2F;p&gt;
&lt;p&gt;Some behavioural findings, such as concurrency bugs or unusual edge cases, are also difficult to reproduce reliably. Be cautious about insisting on a failing test in these cases: an AI agent may spend 50 minutes adding substantial test setup and complexity only to produce weak or inconclusive evidence. When the cost of a reliable reproduction is disproportionate, a clear explanation of the risk and the conditions under which it may occur is more useful.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Do we need referential integrity for prompts?</title>
		<published>2026-07-25T23:50:01+03:00</published>
		<updated>2026-07-25T23:50:01+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/do-we-need-referential-integrity-for-prompts/"/>
		<id>https://ryanzidago.com/posts/do-we-need-referential-integrity-for-prompts/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/do-we-need-referential-integrity-for-prompts/">&lt;h2 id=&quot;problem&quot;&gt;Problem&lt;&#x2F;h2&gt;
&lt;p&gt;I ran into a problem at work that I suspect will become increasingly common as products allow end users to write and save their own prompts.&lt;&#x2F;p&gt;
&lt;p&gt;These prompts rarely exist in isolation. They refer to things in the application: a location, a form field, a role, a workflow step, a report, a policy, or even a particular person.&lt;&#x2F;p&gt;
&lt;p&gt;Imagine a hotel group with several properties. Each hotel manager can create a prompt that runs every Monday:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Summarize last week’s guest satisfaction scores for the Rosebridge Hotel London and compare them with the previous week.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;At the time the prompt is written, “Rosebridge Hotel London” is the name of a real location in the system. Six months later, the property is renamed “The Rosebridge Kensington.” The hotel itself has not changed, and it may still have the same internal database ID, but the saved prompt continues to use its old name.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Eventually the prompt and the application drift apart.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The difficult part is that this does not necessarily produce a clean failure. The prompt is still valid text, so the model will try to interpret it. It may guess that the old and new names refer to the same hotel, fail to find any matching location, or confidently use data from the wrong one. Each outcome wastes tokens and time, but more importantly, it gives the hotel manager an unreliable experience without making the underlying problem obvious.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, someone can update the prompt. That is manageable when there is one prompt and everyone remembers it exists. It becomes much harder when customers have created hundreds of prompts across an organization. The application may not know which prompts depend on the entity that changed, so nobody learns about the breakage until users start seeing bad results.&lt;&#x2F;p&gt;
&lt;p&gt;Databases have a name for the property that prevents this class of problem: referential integrity. If one record points to another, the database can preserve that relationship or reject a change that would break it. A plain-text prompt has no such protection. It can continue referring to a field, role, or workflow that no longer exists.&lt;&#x2F;p&gt;
&lt;p&gt;That led me to this question:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;If prompts can refer to real entities in a product, do they need something like referential integrity?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I explored a few possible approaches, but never arrived at a solution that I found both reliable over time and capable of preserving the simplicity and flexibility of natural-language prompts.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Parallelising guardrails for faster feedback loops</title>
		<published>2026-07-24T09:30:00+03:00</published>
		<updated>2026-07-24T09:30:00+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/parallelising-guardrails-for-faster-feedback-loops/"/>
		<id>https://ryanzidago.com/posts/parallelising-guardrails-for-faster-feedback-loops/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/parallelising-guardrails-for-faster-feedback-loops/">&lt;p&gt;In &lt;a href=&quot;&#x2F;posts&#x2F;ai-coding-agents-need-software-engineering-guardrails&#x2F;&quot;&gt;AI coding agents need software engineering guardrails&lt;&#x2F;a&gt;, I made the case for why deterministic guardrails matter. In &lt;a href=&quot;&#x2F;posts&#x2F;bylaw-a-concrete-example-of-software-engineering-guardrails&#x2F;&quot;&gt;How to build software engineering guardrails: An example with Bylaw&lt;&#x2F;a&gt;, I showed how to think about a guardrail and add one to a project through a concrete example.&lt;&#x2F;p&gt;
&lt;p&gt;Once we know why we need guardrails and how to build them, the next challenge is keeping them out of the way. As the suite grows, the checks need to remain fast enough to run often and shape the work while it is still in progress.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;feedback-loops-are-one-of-software-engineering-s-advantages&quot;&gt;Feedback loops are one of software engineering&#x27;s advantages&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;strong&gt;Feedback loops are one of the most valuable things we have&lt;&#x2F;strong&gt; when producing work: make a change, observe the result, correct the change, and repeat. The sooner the result arrives, the sooner we can learn from it. Each pass through the loop improves the work until it reaches the quality bar we want.&lt;&#x2F;p&gt;
&lt;p&gt;Software engineers are fortunate to work with unusually short feedback loops, especially in backend development. A compiler, test suite, static analyser, or security scanner can respond in seconds and give us concrete information about what we just changed.&lt;&#x2F;p&gt;
&lt;p&gt;Compare that with a salesperson who may wait months to learn whether an enterprise deal will close, or a payroll administrator whose incorrect entry may surface only during review, reconciliation, or a later pay run. In both cases, the distance between the action and its consequences makes learning slower and attribution harder.&lt;&#x2F;p&gt;
&lt;p&gt;Software engineering does not escape this problem entirely. A compiler can tell us immediately that an abstraction is valid code; it may take six months and several feature requests to discover that it was the wrong abstraction. Our unusually fast feedback applies mainly to properties we can check mechanically.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;We should take every opportunity to preserve that advantage.&lt;&#x2F;strong&gt; Short feedback loops keep the change and its intent fresh in our minds. They make defects easier to investigate because there is less distance between introducing a problem and learning about it. Slow feedback forces us to restore context and begin a second debugging session.&lt;&#x2F;p&gt;
&lt;p&gt;Guardrails are part of this loop, but only if they are cheap enough to run frequently. A fast quality gate can run before a commit, before a push, in remote CI, and again before merge. The same checks can shape the code throughout the software development lifecycle instead of judging it only at the end.&lt;&#x2F;p&gt;
&lt;p&gt;The challenge is that every new guardrail also makes the feedback loop slower.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-parallelism-is-the-right-lever&quot;&gt;Why parallelism is the right lever&lt;&#x2F;h1&gt;
&lt;p&gt;As the number of guardrails grows, running them one after another makes the feedback loop progressively slower:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;format → compile → Credo → Sobelow → dependency audit → Dialyzer → tests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Most of those checks do not depend on the result of the check before them. They only need the same prepared project. That makes the sequential pipeline unnecessarily expensive.&lt;&#x2F;p&gt;
&lt;p&gt;I changed the quality gate in a small Elixir project to prepare the project once, then run its independent checks concurrently:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;deps.get → compile ─┬─ format&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ├─ Credo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ├─ Sobelow&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ├─ dependency audit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    ├─ Dialyzer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    └─ tests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The work has not disappeared. Dependency resolution and compilation still happen in order because the checks depend on their output. The independent verification checks still do exactly the same work as before. Concurrency changes the wall-clock cost: the parallel phase takes roughly as long as its slowest check rather than the sum of every check.&lt;&#x2F;p&gt;
&lt;p&gt;This is the right lever because it preserves the strength of the gate. Removing checks would also make the command faster, but at the cost of feedback. Parallelising independent checks keeps the same coverage while returning that feedback sooner.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-experiment&quot;&gt;The experiment&lt;&#x2F;h1&gt;
&lt;p&gt;I measured both the complete command and the verification phase by itself.&lt;&#x2F;p&gt;
&lt;p&gt;For the complete command, the sequential variant ran:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; deps.get&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; compile&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-warnings-as-errors&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; format&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-check-formatted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; credo&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-strict&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sobelow&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-exit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; deps.audit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dialyzer&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-no-compile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; test&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-no-compile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The parallel variant ran the real &lt;code&gt;mix qa&lt;&#x2F;code&gt; task described below. It performed the same dependency resolution and compilation, then launched the same six checks concurrently.&lt;&#x2F;p&gt;
&lt;p&gt;After one warm-up, I ran five measured trials of each variant. I alternated which variant ran first, used the same warm &lt;code&gt;deps&lt;&#x2F;code&gt;, &lt;code&gt;_build&lt;&#x2F;code&gt;, and Dialyzer PLT, discarded command output, and required every command to exit successfully.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Scope&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sequential median&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Parallel median&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Reduction&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Sequential range&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Parallel range&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Verification checks only&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.676s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.300s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;55.6%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.260–13.030s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4.208–4.920s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Complete quality gate&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;15.128s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;11.434s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;24.4%&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14.767–18.736s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.263–21.739s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Despite the project&#x27;s small size, parallelising the verification phase reduced its median duration from 9.676 seconds to 4.300 seconds: a 55.6% reduction. Larger projects will not necessarily see the same proportional improvement, especially when one check, often the test suite, dominates the runtime. Even then, running the remaining checks behind that bottleneck can shave seconds or minutes from every feedback loop.&lt;&#x2F;p&gt;
&lt;p&gt;The isolated verification phase shows the effect of parallelisation most clearly: its median was 2.25 times faster and its parallel results stayed within a 712ms range.&lt;&#x2F;p&gt;
&lt;p&gt;The complete gate includes shared preparation that remains sequential. It also showed more variance. Two parallel trials took 16.719 and 21.739 seconds, while the other three took between 10.263 and 11.434 seconds. &lt;code&gt;deps.get&lt;&#x2F;code&gt; performs dependency resolution before the checks begin, so slow preparation affects either implementation without changing the work that was parallelised.&lt;&#x2F;p&gt;
&lt;p&gt;The project had 352 ExUnit tests. I ran the benchmark on an Apple M4 Max with 14 logical CPUs, macOS 26.5.2, Erlang&#x2F;OTP 29, and Elixir 1.20.2. These are local development measurements from one machine. The speedup in another project will depend on the duration of its checks, their resource usage, and how much work must remain sequential.&lt;&#x2F;p&gt;
&lt;p&gt;Saving roughly four seconds on a command that runs dozens or hundreds of times per day compounds surprisingly quickly.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;separate-preparation-from-verification&quot;&gt;Separate preparation from verification&lt;&#x2F;h1&gt;
&lt;p&gt;The first decision is which tasks may safely run in parallel.&lt;&#x2F;p&gt;
&lt;p&gt;Dependency resolution and compilation prepare shared state in &lt;code&gt;deps&lt;&#x2F;code&gt; and &lt;code&gt;_build&lt;&#x2F;code&gt;. They run once, in order, before any checks begin:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;run_command!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;dependencies&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;deps.get&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;run_command!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;compile&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;compile&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--warnings-as-errors&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If either command fails, the task stops. There is little value in launching checks against a project that could not fetch its dependencies or compile successfully.&lt;&#x2F;p&gt;
&lt;p&gt;The remaining checks only inspect the prepared project:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;checks&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;format&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;format&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--check-formatted&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;credo&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;credo&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--strict&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sobelow&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;sobelow&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--exit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;dependency audit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;deps.audit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;dialyzer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;dialyzer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--no-compile&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;tests&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;test&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--no-compile&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;--no-compile&lt;&#x2F;code&gt; flags matter. The parent task has already compiled the project with warnings treated as errors. Asking Dialyzer and the test suite to compile again would duplicate work and allow several processes to compete over the same build artifacts.&lt;&#x2F;p&gt;
&lt;p&gt;This gives the quality gate two explicit phases:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Prepare shared state sequentially.&lt;&#x2F;li&gt;
&lt;li&gt;Verify that state concurrently.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That boundary is the main design constraint. Parallelising commands that mutate the same files or database can introduce races. Independent read-only checks are the safest place to begin.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;run-every-check-in-its-own-mix-process&quot;&gt;Run every check in its own Mix process&lt;&#x2F;h1&gt;
&lt;p&gt;Mix tasks are designed to run once within a Mix process. They may also change application state, start supervision trees, configure logging, or halt the VM. Running several of them as functions inside one process would couple checks that should be isolated.&lt;&#x2F;p&gt;
&lt;p&gt;Instead, the QA task launches a fresh &lt;code&gt;mix&lt;&#x2F;code&gt; command for every check:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;mix_runner_prefix&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;--erl&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;-elixir ansi_enabled true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;-S&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;defp&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; run_command&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;args&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  System&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cmd&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;elixir_executable&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; @&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;mix_runner_prefix&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ++&lt;&#x2F;span&gt;&lt;span&gt; args&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; File&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cwd!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    env&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;MIX_ENV&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;test&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    stderr_to_stdout&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each command receives the same working directory and &lt;code&gt;MIX_ENV&lt;&#x2F;code&gt;, while keeping its own BEAM VM and Mix task lifecycle. &lt;code&gt;System.cmd&#x2F;3&lt;&#x2F;code&gt; returns both the command&#x27;s output and exit code, which is enough to treat every tool uniformly.&lt;&#x2F;p&gt;
&lt;p&gt;The parent Mix task uses &lt;code&gt;Task.async_stream&#x2F;3&lt;&#x2F;code&gt; to run those commands concurrently:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;results &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  @&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;checks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Task&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;async_stream&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;run_check&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    max_concurrency&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Enum&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;count&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;checks&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    ordered&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    timeout&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;infinity&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Enum&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; result&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; result &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;max_concurrency&lt;&#x2F;code&gt; allows every configured check to start without an artificial queue. &lt;code&gt;ordered: false&lt;&#x2F;code&gt; lets the stream receive completed work immediately. &lt;code&gt;timeout: :infinity&lt;&#x2F;code&gt; leaves timeout policy with the underlying tools, which is useful for test suites and initial Dialyzer runs whose duration varies substantially between machines.&lt;&#x2F;p&gt;
&lt;p&gt;Starting every check at once is appropriate for this project and this number of tasks. A larger suite may need a lower concurrency limit to avoid CPU or memory contention. The useful target is the shortest reliable feedback loop, not the highest possible process count.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;buffer-output-per-check&quot;&gt;Buffer output per check&lt;&#x2F;h1&gt;
&lt;p&gt;Concurrent processes create a usability problem: if every command writes directly to the terminal, their output interleaves.&lt;&#x2F;p&gt;
&lt;p&gt;A test failure can appear between two Credo warnings. A Dialyzer message can be split by Sobelow output. The suite may finish sooner while taking longer to understand.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;System.cmd&#x2F;3&lt;&#x2F;code&gt; buffers each command&#x27;s output. The QA task prints that buffer only after the command has finished:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;defp&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; run_check&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; args&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span&gt;output&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; exit_code&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; run_command&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;args&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; output&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; exit_code&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;defp&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; print_result&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; output&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; exit_code&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  status &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; if&lt;&#x2F;span&gt;&lt;span&gt; exit_code &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; do&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; else&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;failed&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  Mix&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;shell&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;==&amp;gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;#{&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;label&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;#{&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;status&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; output &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;!=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    Mix&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;shell&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;trim_trailing&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;output&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The checks still execute concurrently, but each one appears as a coherent section:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;==&amp;gt; format (ok)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;==&amp;gt; credo (ok)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;==&amp;gt; tests (failed)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;complete test output&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This trades live, line-by-line progress for readable results. That is a reasonable default for finite local checks. A command that can produce very large output may need a temporary file or a bounded capture strategy instead of keeping its entire log in memory.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;report-all-failures&quot;&gt;Report all failures&lt;&#x2F;h1&gt;
&lt;p&gt;Failing fast is useful during preparation because later checks depend on that state. It is less useful once independent checks are already running.&lt;&#x2F;p&gt;
&lt;p&gt;If formatting, Credo, and tests all fail, I want one run to tell me about all three:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Enum&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;each&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;results&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;print_result&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;failures &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Enum&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;reject&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;results&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;match?&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; _&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Enum&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;any?&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;failures&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  failed_labels &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    Enum&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;map_join&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;failures&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;, &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; _&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; _&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; label &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  Mix&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;raise&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;QA checks failed: &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;#{&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;failed_labels&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The task waits for every check, prints every result, then exits unsuccessfully with a concise summary. An engineer or coding agent can fix the complete set of known violations before running the gate again.&lt;&#x2F;p&gt;
&lt;p&gt;This is another feedback-loop improvement. Parallel execution reduces the time spent waiting for one run. Aggregating failures reduces the number of runs needed to discover the current state of the branch.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;put-the-complete-gate-behind-one-command&quot;&gt;Put the complete gate behind one command&lt;&#x2F;h1&gt;
&lt;p&gt;The custom task is exposed through a Mix alias:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;defp&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; aliases&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    qa&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;qa.run&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The public command remains:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; qa&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The implementation task is named &lt;code&gt;qa.run&lt;&#x2F;code&gt; because defining &lt;code&gt;Mix.Tasks.Qa&lt;&#x2F;code&gt; while also aliasing &lt;code&gt;qa&lt;&#x2F;code&gt; can create an ambiguous entry point. The alias owns the stable interface; the task owns the orchestration.&lt;&#x2F;p&gt;
&lt;p&gt;One command matters because a guardrail only protects the codebase when it runs consistently. The same complete gate can be called by developers, coding agents, Git hooks, worktree tooling, and CI without each caller reconstructing the list of checks.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-complete-task&quot;&gt;The complete task&lt;&#x2F;h1&gt;
&lt;p&gt;The excerpts above cover the design decisions. The &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;ryanzidago&#x2F;3f4151196b20db7c1f0b4ca0d819d42a&quot;&gt;complete &lt;code&gt;Mix.Tasks.Qa.Run&lt;&#x2F;code&gt; implementation&lt;&#x2F;a&gt; is available as a GitHub Gist.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;faster-guardrails-get-used-more-often&quot;&gt;Faster guardrails get used more often&lt;&#x2F;h1&gt;
&lt;p&gt;Adding deterministic guardrails increases confidence, but each new check also adds latency. If that latency grows linearly, a comprehensive quality gate eventually becomes something people postpone until the end of a task.&lt;&#x2F;p&gt;
&lt;p&gt;Independent checks do not need to impose a linear cost. Prepare their shared inputs once, isolate each check in its own process, run them concurrently, keep their output readable, and report all violations together.&lt;&#x2F;p&gt;
&lt;p&gt;The goal is not merely to make CI finish sooner. It is to make the complete set of guardrails cheap enough to use throughout development. A formatting error, unsafe code path, type mismatch, or failing test can then be corrected while the implementation is still fresh in the engineer&#x27;s or coding agent&#x27;s context.&lt;&#x2F;p&gt;
&lt;p&gt;Guardrails are most useful when they participate in the edit loop: change the code, run the checks, act on the feedback, and repeat. Making that loop fast is part of designing the guardrails themselves.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Each test should own its setup</title>
		<published>2026-07-23T12:00:00+03:00</published>
		<updated>2026-07-23T12:00:00+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/self-contained-tests-in-elixir/"/>
		<id>https://ryanzidago.com/posts/self-contained-tests-in-elixir/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/self-contained-tests-in-elixir/">&lt;p&gt;A test should explain itself.&lt;&#x2F;p&gt;
&lt;p&gt;Every time I have to jump to a setup callback to understand why an assertion passes, the test has already failed at its second job: communication.&lt;&#x2F;p&gt;
&lt;p&gt;I understand why they are appealing. A &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ex-unit.hexdocs.pm&#x2F;ExUnit.Callbacks.html#setup&#x2F;1&quot;&gt;&lt;code&gt;setup&lt;&#x2F;code&gt; callback&lt;&#x2F;a&gt; removes repetition, makes each test shorter, and gives us one place to create the records that several tests need.&lt;&#x2F;p&gt;
&lt;p&gt;But shorter tests are not necessarily simpler tests.&lt;&#x2F;p&gt;
&lt;p&gt;Very often, shared setup makes a test harder to read, harder to review, harder to change, and harder to move. It reduces the number of lines in the test by moving essential information somewhere else.&lt;&#x2F;p&gt;
&lt;p&gt;I much prefer self-contained tests: each test should create and own the state it needs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;a-short-test-with-hidden-context&quot;&gt;A short test with hidden context&lt;&#x2F;h1&gt;
&lt;p&gt;Here is a fairly typical Elixir test:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;defmodule&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; MyApp&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;OrdersTest&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; MyApp&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;DataCase&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  setup &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    customer &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; price&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1_000&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;customer&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; product&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;uses the product price as the order total&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    customer&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    product&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; order&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Orders&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert order&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;total &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1_000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The test itself is short, but it is not self-explanatory.&lt;&#x2F;p&gt;
&lt;p&gt;Why should the total be &lt;code&gt;1_000&lt;&#x2F;code&gt;? The answer is not in the test. We need to leave it, find the relevant &lt;code&gt;setup&lt;&#x2F;code&gt;, notice the product&#x27;s price, and remember that detail when we return.&lt;&#x2F;p&gt;
&lt;p&gt;The setup is not incidental. It contains the condition that causes the behaviour we are testing.&lt;&#x2F;p&gt;
&lt;p&gt;I would write the test like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;uses the product price as the order total&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  customer &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; price&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1_000&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; order&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Orders&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert order&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;total &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1_000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the test tells the whole story:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the product costs &lt;code&gt;1_000&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;creating the order results in a total of &lt;code&gt;1_000&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are no important facts hidden elsewhere in the module.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;locality-matters-more-than-line-count&quot;&gt;Locality matters more than line count&lt;&#x2F;h1&gt;
&lt;p&gt;Shared setup is usually introduced to remove duplication. That is a useful goal in production code, but I do not think it should be applied mechanically to tests.&lt;&#x2F;p&gt;
&lt;p&gt;Production code and test code have different jobs. Production code primarily implements behaviour. A test is also an explanation and an example.&lt;&#x2F;p&gt;
&lt;p&gt;Some repetition helps that explanation.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;creates an order when the product is in stock&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  customer &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; stock&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; order&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Orders&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert order&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;product_id &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;rejects an order when the product is out of stock&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  customer &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; stock&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;out_of_stock&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;           Orders&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Those tests repeat several lines, but the repetition makes the difference between the two cases immediately visible.&lt;&#x2F;p&gt;
&lt;p&gt;If the customer and product were created in a setup callback, we would save a few lines while making the reader reconstruct each scenario from multiple places.&lt;&#x2F;p&gt;
&lt;p&gt;That is not a good trade.&lt;&#x2F;p&gt;
&lt;p&gt;The useful question is not:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;How do I remove every repeated line?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;It is:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;How much context does someone need to understand this test?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;For me, a few duplicated fixture calls are usually cheaper than scattered context.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;self-contained-tests-make-better-review-comments&quot;&gt;Self-contained tests make better review comments&lt;&#x2F;h1&gt;
&lt;p&gt;The cost of shared setup becomes particularly visible during code review.&lt;&#x2F;p&gt;
&lt;p&gt;A reviewer rarely reads a test file from top to bottom with every detail loaded in their head. They often encounter a test in a pull request diff, an inline comment, a suggested patch, or a general PR conversation.&lt;&#x2F;p&gt;
&lt;p&gt;Imagine that an AI agent flags a potential bug while reviewing a pull request. I ask it to draft a failing test that demonstrates the issue. I often find a concrete test case easier to understand than a description in plain English. I run the test locally, confirm that it fails for the expected reason, and paste it into a GitHub comment:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;self-contained-test-review-comment.svg&quot; alt=&quot;A GitHub review comment containing a self-contained failing test&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That comment is useful on its own. The author, another reviewer, or an AI agent can understand the scenario without access to anything else.&lt;&#x2F;p&gt;
&lt;p&gt;Now imagine that the same test relies on the module&#x27;s shared setup:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;shared-setup-test-review-comment.svg&quot; alt=&quot;A GitHub review comment containing a test that depends on hidden shared setup&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;What kind of product did the setup create? Was it available before the update? Does the setup create anything else that affects the result?&lt;&#x2F;p&gt;
&lt;p&gt;The test may make sense inside the file, after jumping to the setup callback and possibly through one or two helper functions. It does not make sense in isolation.&lt;&#x2F;p&gt;
&lt;p&gt;This creates unnecessary work for every reviewer. They must open the file, find the setup, understand which parts apply, and mentally combine that information with the test in the comment.&lt;&#x2F;p&gt;
&lt;p&gt;It is even worse for an AI agent. We either need to provide the entire file as context, trust the agent to locate every indirect dependency, or accept that it may reason about an incomplete scenario.&lt;&#x2F;p&gt;
&lt;p&gt;Self-contained tests reduce that context requirement. They make the evidence for a review comment portable.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tests-should-be-portable&quot;&gt;Tests should be portable&lt;&#x2F;h1&gt;
&lt;p&gt;I want to be able to copy a test from:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;my editor into a pull request comment&lt;&#x2F;li&gt;
&lt;li&gt;a pull request comment into my local repository&lt;&#x2F;li&gt;
&lt;li&gt;one test module into another&lt;&#x2F;li&gt;
&lt;li&gt;an issue into an AI coding agent conversation&lt;&#x2F;li&gt;
&lt;li&gt;an AI agent&#x27;s response into the codebase&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The test should carry the facts required to understand it.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, no test is literally independent of the application. It will still use factories, application modules, and common test infrastructure. Portability is not about eliminating every dependency. It is about making the dependencies that define the scenario explicit at the point of use.&lt;&#x2F;p&gt;
&lt;p&gt;A shared setup callback couples a test to its location in the file. Move the test and it may stop compiling, stop passing, or, more dangerously, continue passing under different assumptions.&lt;&#x2F;p&gt;
&lt;p&gt;A self-contained test can usually be moved without bringing along a web of setup callbacks. If it cannot, the missing dependency tends to be obvious.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;shared-setup-accumulates-stale-state&quot;&gt;Shared setup accumulates stale state&lt;&#x2F;h1&gt;
&lt;p&gt;Shared setup also has a tendency to grow.&lt;&#x2F;p&gt;
&lt;p&gt;A test needs an account, so we add an account. Another test needs an administrator, so we add an administrator. A third needs a subscription, so we add a subscription and perhaps a helper that constructs it.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually the setup creates a small world:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;setup &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  account &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;account&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  admin &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; role&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; account&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; account&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  employee &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;user&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; role&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;employee&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; account&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; account&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  subscription &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;subscription&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; account&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; account&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    account&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; account&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    admin&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; admin&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    employee&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; employee&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; subscription&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    product&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not every test needs that world, but every test pays for it.&lt;&#x2F;p&gt;
&lt;p&gt;Later, tests are removed or rewritten, while setup entries and helper functions remain. It becomes difficult to tell whether a fixture is still necessary because its consumers are distributed across the module. Unused values, stale helpers, and accidental dependencies survive much longer than they would if the setup were local.&lt;&#x2F;p&gt;
&lt;p&gt;Deep setup abstractions also make intermittent failures harder to investigate. When a test fails only occasionally, the first task is to reconstruct its exact starting state. If that state is spread across setup callbacks and helper functions, it becomes difficult to determine what data was created, which defaults mattered, and whether the test accidentally depended on state it did not explicitly request. Self-contained tests do not prevent every flaky test, but they reduce the amount of hidden context we must inspect when one fails.&lt;&#x2F;p&gt;
&lt;p&gt;Local setup makes dead code easier to see. If a test no longer uses &lt;code&gt;subscription&lt;&#x2F;code&gt;, we remove the line directly above it. There is no need to audit every other test before deciding whether it is safe.&lt;&#x2F;p&gt;
&lt;p&gt;It also makes changes easier to reason about. Changing a shared setup value may affect an entire module, including tests that do not visibly reference the changed detail. Changing local setup affects the test in front of us.&lt;&#x2F;p&gt;
&lt;p&gt;This is the same reason global state is difficult to reason about, only at a smaller scale: the distance between cause and effect makes the impact of a change less obvious.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;self-contained-does-not-mean-no-abstraction&quot;&gt;Self-contained does not mean no abstraction&lt;&#x2F;h1&gt;
&lt;p&gt;I am not arguing that every test should manually build every struct or repeat a dozen low-level database operations.&lt;&#x2F;p&gt;
&lt;p&gt;Helpers are useful when they express domain intent:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;test &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;a customer cannot order an unavailable product&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  customer &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; unavailable_product&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  assert &lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product_unavailable&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;           Orders&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;customer&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; product&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The test still owns its setup. It explicitly asks for the state it needs, and the helper gives that state a meaningful name.&lt;&#x2F;p&gt;
&lt;p&gt;The important distinction is control:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;a helper runs because the test calls it&lt;&#x2F;li&gt;
&lt;li&gt;a setup callback runs because the test happens to live in a module&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Explicit helpers can make tests clearer. Implicit setup makes their dependencies easier to miss.&lt;&#x2F;p&gt;
&lt;p&gt;I would still be careful with helpers that hide the exact value under test. If product availability is central to the behaviour, spelling it out may be clearer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;product &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; insert&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;product&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; available&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The goal is not to inline everything. The goal is to keep the facts that explain the expected result close to the assertion.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-economics-of-repetition-have-changed&quot;&gt;The economics of repetition have changed&lt;&#x2F;h1&gt;
&lt;p&gt;Shared setup was more appealing when every repeated fixture had to be written and maintained manually. Even then, I disliked the tradeoff: we saved a few keystrokes by moving important context away from the test.&lt;&#x2F;p&gt;
&lt;p&gt;AI coding agents have weakened that tradeoff even further. Asking an agent to generate a few explicit fixture calls costs almost nothing. The expensive part is no longer producing those lines. It is reading, reviewing, and reasoning about them later.&lt;&#x2F;p&gt;
&lt;p&gt;AI has made code cheaper to write, but it has not made hidden context cheaper to understand. If an abstraction saves typing while making every test harder to comprehend, I no longer see much value in it.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;when-i-still-use-setup&quot;&gt;When I still use &lt;code&gt;setup&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;Elixir&#x27;s &lt;code&gt;setup&lt;&#x2F;code&gt; is not inherently bad. Uniform infrastructure that is not part of the scenario being described is the strongest case for using it.&lt;&#x2F;p&gt;
&lt;p&gt;For example, a Phoenix controller test might apply the same request header to every connection:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;setup %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;conn&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; conn&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; conn&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; put_req_header&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;accept&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That configuration is different from silently deciding that every test has a customer, an active subscription, and an available product worth &lt;code&gt;1_000&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Even in cases like this, I generally prefer to avoid &lt;code&gt;setup&lt;&#x2F;code&gt;. I would rather keep the convention unambiguous: each test owns the state it needs.&lt;&#x2F;p&gt;
&lt;p&gt;Once a setup callback exists, it becomes the obvious place to put the next shared dependency. A human sees the existing pattern and extends it. An AI agent does the same. The callback that started with harmless infrastructure gradually accumulates domain state, and the boundary between the two becomes less clear.&lt;&#x2F;p&gt;
&lt;p&gt;Repeating a small amount of infrastructure is often a reasonable price for making the preferred pattern obvious to every contributor, human or agent.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;each-test-should-own-its-world&quot;&gt;Each test should own its world&lt;&#x2F;h1&gt;
&lt;p&gt;Self-contained tests are sometimes longer. I am happy to accept that.&lt;&#x2F;p&gt;
&lt;p&gt;What bothers me is not abstraction itself. A domain function or module can also hide implementation details, but it is part of the product and carries domain meaning. Calling it from a test still tells the reader something about the system.&lt;&#x2F;p&gt;
&lt;p&gt;A setup callback introduces a different kind of abstraction: an ad hoc API that exists only between the setup and its tests. That API is implicit, specific to the test module, and has no meaning outside it. I do not want readers to learn both the product&#x27;s API and a separate setup API just to understand one test.&lt;&#x2F;p&gt;
&lt;p&gt;Self-contained tests are easier to read because the relevant state is nearby. They are easier to review because a diff or comment contains the whole example. They are easier to change because their dependencies are explicit. They are easier to move because they carry their setup with them. They also give both humans and AI agents enough context to reason about the behaviour without reconstructing it from the rest of the file.&lt;&#x2F;p&gt;
&lt;p&gt;A test is not better because it has fewer lines. It is better when someone can understand what it proves, why it should pass, and what would make it fail.&lt;&#x2F;p&gt;
&lt;p&gt;Each test should own its world, and that world should be visible wherever the test appears.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>How to build software engineering guardrails: An example with Bylaw</title>
		<published>2026-07-22T21:27:01+03:00</published>
		<updated>2026-07-22T21:27:01+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/bylaw-a-concrete-example-of-software-engineering-guardrails/"/>
		<id>https://ryanzidago.com/posts/bylaw-a-concrete-example-of-software-engineering-guardrails/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/bylaw-a-concrete-example-of-software-engineering-guardrails/">&lt;h1 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h1&gt;
&lt;p&gt;In the &lt;a href=&quot;&#x2F;posts&#x2F;ai-coding-agents-need-software-engineering-guardrails&#x2F;&quot;&gt;previous article&lt;&#x2F;a&gt;, I argued that deterministic guardrails are a better place for repeatable engineering policies than an AI agent&#x27;s context window.
It avoids bloating the context window with information that could be encoded elsewhere.&lt;&#x2F;p&gt;
&lt;p&gt;But how do you turn a policy into a guardrail?&lt;&#x2F;p&gt;
&lt;p&gt;Start with something that can be programmatically inspected. That might be source code, an abstract syntax tree, a database schema, a query, generated HTML, or a dependency graph.&lt;&#x2F;p&gt;
&lt;p&gt;Then:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;inspect the artifact&lt;&#x2F;li&gt;
&lt;li&gt;recognize the pattern that matters&lt;&#x2F;li&gt;
&lt;li&gt;express your policy as an invariant&lt;&#x2F;li&gt;
&lt;li&gt;find a choke point through which every relevant artifact passes&lt;&#x2F;li&gt;
&lt;li&gt;reject violations with an actionable error&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;If a policy can be evaluated from data that tooling can inspect, an AI agent should not have to remember it.&lt;&#x2F;p&gt;
&lt;p&gt;A guardrail is only reliable if it runs consistently. A single choke point lets you validate the invariant once instead of relying on every caller to remember to invoke the check.&lt;&#x2F;p&gt;
&lt;p&gt;That is why I built &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&quot;&gt;Bylaw&lt;&#x2F;a&gt;. Bylaw is a collection of Elixir packages for encoding engineering policies as deterministic checks:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;tree&#x2F;main&#x2F;packages&#x2F;bylaw_credo&quot;&gt;bylaw_credo&lt;&#x2F;a&gt; inspects source code&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;tree&#x2F;main&#x2F;packages&#x2F;bylaw_postgres&quot;&gt;bylaw_postgres&lt;&#x2F;a&gt; inspects PostgreSQL schemas&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bylaw-ecto-query.hexdocs.pm&#x2F;&quot;&gt;bylaw_ecto_query&lt;&#x2F;a&gt; inspects composed &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Query.html&quot;&gt;&lt;code&gt;Ecto.Query&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; values&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;tree&#x2F;main&#x2F;packages&#x2F;bylaw_html&quot;&gt;bylaw_html&lt;&#x2F;a&gt; inspects HTML&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The following examples show how the same process applies to different kinds of software artifacts.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;inspecting-source-code&quot;&gt;Inspecting source code&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;no-repo-transaction&quot;&gt;No repo transaction&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;blob&#x2F;main&#x2F;packages&#x2F;bylaw_credo&#x2F;lib&#x2F;bylaw&#x2F;credo&#x2F;check&#x2F;ecto&#x2F;no_repo_transaction.ex&quot;&gt;&lt;code&gt;Bylaw.Credo.Check.Ecto.NoRepoTransaction&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; prevents using the deprecated &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Repo.html#c:transaction&#x2F;2&quot;&gt;&lt;code&gt;Repo.transaction&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and suggests using &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Repo.html#c:transact&#x2F;2&quot;&gt;&lt;code&gt;Repo.transact&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; instead.&lt;&#x2F;p&gt;
&lt;p&gt;The artifact is Elixir source code. The pattern is a call to &lt;code&gt;Repo.transaction&lt;&#x2F;code&gt;. The invariant is that application code must use the current &lt;code&gt;Repo.transact&lt;&#x2F;code&gt; API. AI agents often use outdated APIs because older examples are well represented in their training data.&lt;&#x2F;p&gt;
&lt;p&gt;For example, you ask an AI agent to make sure that creating an account and its audit event happen atomically. It writes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; register_account&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;attrs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;transaction&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    account &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Account&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Account&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;changeset&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;attrs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;insert!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;insert!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AuditEvent&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;account_created&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;account&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    account&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  end&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The implementation is otherwise correct, but the agent used &lt;code&gt;Repo.transaction&#x2F;1&lt;&#x2F;code&gt;, an API deprecated in Ecto 3.13. The code looks plausible because years of examples use it.&lt;&#x2F;p&gt;
&lt;p&gt;The Bylaw check rejects the call and points the agent towards the current API:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; register_account&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;attrs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;transact&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    account &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Account&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Account&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;changeset&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;attrs&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;      |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;insert!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;insert!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AuditEvent&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;account_created&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;account&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    account&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  end&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I could add “use &lt;code&gt;Repo.transact&lt;&#x2F;code&gt; instead of &lt;code&gt;Repo.transaction&lt;&#x2F;code&gt;” to the agent&#x27;s instructions, but the agent can still forget or overlook it. Once the policy is a Bylaw check, every violation produces the same result.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;inspecting-a-database-schema&quot;&gt;Inspecting a database schema&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;foreign-key-actions&quot;&gt;Foreign key actions&lt;&#x2F;h2&gt;
&lt;p&gt;Suppose your organization&#x27;s engineering standard is that foreign keys must never cascade deletes. Records should only be removed through explicit application flows, where retention requirements, audit events, and other side effects can be handled deliberately.&lt;&#x2F;p&gt;
&lt;p&gt;The artifact is the PostgreSQL schema. The pattern is any foreign key with an &lt;code&gt;ON DELETE&lt;&#x2F;code&gt; action. The invariant is that every foreign key must restrict deletion while dependent records still exist.&lt;&#x2F;p&gt;
&lt;p&gt;You encode that standard once with &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;blob&#x2F;main&#x2F;packages&#x2F;bylaw_postgres&#x2F;lib&#x2F;bylaw&#x2F;db&#x2F;adapters&#x2F;postgres&#x2F;checks&#x2F;foreign_key_actions.ex&quot;&gt;Bylaw.Db.Adapters.Postgres.Checks.ForeignKeyActions&lt;&#x2F;a&gt; and apply it to the entire schema:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Bylaw&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Db&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Adapters&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Postgres&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Checks&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ForeignKeyActions&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt; rules&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;on_delete&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;restrict&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The schema-validation test is the choke point. It inspects the current database schema and validates every foreign key in one pass. It does not matter which migration introduced a constraint or which part of the application owns the affected tables: if the schema violates the invariant, the test fails.&lt;&#x2F;p&gt;
&lt;p&gt;Later, an AI agent is asked to add a relationship between orders and accounts. Orders must be retained for accounting and customer support, even after an account is closed, but the agent generates this migration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alter &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;table&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;orders&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  add &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;account_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;      references&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;accounts&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; on_delete&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;delete_all&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      null&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The migration looks reasonable, but deleting one account will now silently delete all of its orders. The dangerous behavior is easy to overlook because it is hidden behind the &lt;code&gt;on_delete&lt;&#x2F;code&gt; option. It also violates the organization&#x27;s engineering standard.&lt;&#x2F;p&gt;
&lt;p&gt;The check inspects every foreign key in the PostgreSQL schema and rejects this cascade.&lt;&#x2F;p&gt;
&lt;p&gt;The agent then fixes the migration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alter &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;table&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;orders&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  add &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;account_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;      references&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;accounts&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; on_delete&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;restrict&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      null&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Postgres will now prevent an account from being deleted while orders still reference it. The application has to handle those business records explicitly instead of losing them as a side effect.&lt;&#x2F;p&gt;
&lt;p&gt;The same guardrail applies to every future foreign key. It protects the whole schema against drift, regardless of which tables are involved or whether the migration was written by a person or an AI agent.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;inspecting-composed-queries&quot;&gt;Inspecting composed queries&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;an-ecto-query-is-a-data-structure&quot;&gt;An Ecto query is a data structure&lt;&#x2F;h2&gt;
&lt;p&gt;At some point, I realized that an Ecto query is a data structure.&lt;&#x2F;p&gt;
&lt;p&gt;That changed how I thought about query validation. If a query is data, I can inspect it. If I can inspect it, I can understand what it is trying to do and define which structures are valid or invalid for my application.&lt;&#x2F;p&gt;
&lt;p&gt;Take an ordinary query:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;p &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Post&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; join&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;inner&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; a &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; assoc&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;p&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;author&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;author&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; where&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;status &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;published&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; where&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;author&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;active &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; true&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; order_by&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;published_at&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This reads like a sequence of database operations: start from posts, join their authors, add two filters, order the results, and execute the query.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Query.html#from&#x2F;2&quot;&gt;&lt;code&gt;from&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Query.html#join&#x2F;5&quot;&gt;&lt;code&gt;join&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Query.html#join&#x2F;5&quot;&gt;&lt;code&gt;assoc&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Query.html#where&#x2F;3&quot;&gt;&lt;code&gt;where&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, and &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Query.html#order_by&#x2F;3&quot;&gt;&lt;code&gt;order_by&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; expressions build the query one step at a time.&lt;&#x2F;p&gt;
&lt;p&gt;Before &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Repo.html#c:all&#x2F;2&quot;&gt;&lt;code&gt;Repo.all&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; executes it, those pipeline steps have built an &lt;code&gt;%Ecto.Query{}&lt;&#x2F;code&gt; value. Here is an abridged view of the relevant fields:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  from&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;FromExpr&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    source&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;posts&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Post&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  joins&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;JoinExpr&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      qual&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;inner&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      assoc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;author&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;author&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  wheres&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;BooleanExpr&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      op&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      expr&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           {&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Tagged&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;             value&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;published&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;             type&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         ]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;BooleanExpr&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      op&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      expr&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;==&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           {&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;active&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Tagged&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;             value&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; true&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;             type&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;active&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         ]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  order_bys&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    %&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ecto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ByExpr&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;      expr&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;        desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;published_at&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;        desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  aliases&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    author&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is Ecto&#x27;s actual representation, with incidental metadata such as file names, line numbers, parameters, and subquery lists omitted. &lt;code&gt;&amp;amp;0&lt;&#x2F;code&gt; refers to the root &lt;code&gt;Post&lt;&#x2F;code&gt; binding and &lt;code&gt;&amp;amp;1&lt;&#x2F;code&gt; refers to the joined author. The important part is that the query exposes its components:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;from&lt;&#x2F;code&gt; identifies the base table and schema&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;joins&lt;&#x2F;code&gt; describes the joined associations and join types&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;wheres&lt;&#x2F;code&gt; contains the predicates and how they are combined&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;order_bys&lt;&#x2F;code&gt; contains the fields and directions used for sorting&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;aliases&lt;&#x2F;code&gt; maps the named bindings &lt;code&gt;:post&lt;&#x2F;code&gt; and &lt;code&gt;:author&lt;&#x2F;code&gt; to their positions in the query&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-repository-is-the-choke-point&quot;&gt;The repository is the choke point&lt;&#x2F;h2&gt;
&lt;p&gt;Bylaw validates the completed query through &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;ecto&#x2F;Ecto.Repo.html#c:prepare_query&#x2F;3&quot;&gt;&lt;code&gt;Ecto.Repo.prepare_query&#x2F;3&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. Ecto invokes this callback for query-based repository operations, after the application has composed the query and before the repository executes it.&lt;&#x2F;p&gt;
&lt;p&gt;That location is important. A query can be built dynamically across several functions and files. A source-code linter like &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hexdocs.pm&#x2F;credo&#x2F;&quot;&gt;Credo&lt;&#x2F;a&gt; can inspect each expression independently, but it may never see the final query shape. &lt;code&gt;prepare_query&#x2F;3&lt;&#x2F;code&gt; receives the composed &lt;code&gt;%Ecto.Query{}&lt;&#x2F;code&gt; at the shared repository boundary, giving Bylaw one place to validate the relevant queries before they reach the database.&lt;&#x2F;p&gt;
&lt;p&gt;This means a program can ask questions about the query before it reaches the database:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Does it contain contradictory predicates?&lt;&#x2F;li&gt;
&lt;li&gt;Can its ordering produce non-deterministic results?&lt;&#x2F;li&gt;
&lt;li&gt;Does it join a table that this operation should not access?&lt;&#x2F;li&gt;
&lt;li&gt;Is a required tenant or authorization filter present?&lt;&#x2F;li&gt;
&lt;li&gt;Does an update or delete have an appropriately narrow filter?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Once I saw the query as data, guardrails around Ecto queries felt like a natural consequence. Bylaw can inspect the same structure that Ecto will execute, recognize unsafe or invalid patterns, and reject the query before it reaches the database.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conflicting-where-predicates&quot;&gt;Conflicting where predicates&lt;&#x2F;h2&gt;
&lt;p&gt;Suppose your application builds a query incrementally across multiple files.&lt;&#x2F;p&gt;
&lt;p&gt;The artifact is the composed Ecto query. The pattern is multiple equality predicates on the same field. The invariant is that those predicates cannot require mutually exclusive values.&lt;&#x2F;p&gt;
&lt;p&gt;The posts context starts from a scope that only returns published posts:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; lib&#x2F;my_app&#x2F;blog.ex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; list_public_posts&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;params&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  from&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;p &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Post&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; where&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;status &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;published&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; PostFilters&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;apply&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;params&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Later, an AI agent is asked to add a status filter to the API. It works in a separate filter module and writes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; lib&#x2F;my_app&#x2F;blog&#x2F;post_filters.ex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; apply&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;query&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;status&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;draft&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  where&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;query&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;status &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;draft&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; apply&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;query&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; _params&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; do:&lt;&#x2F;span&gt;&lt;span&gt; query&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each function looks reasonable when read on its own. But a request with &lt;code&gt;?status=draft&lt;&#x2F;code&gt; produces this query:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; status&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;published&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; status&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;draft&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The query does not crash. It silently returns no records. Because the predicates were added in different files, inspecting either function by itself will not reveal the bug.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;blob&#x2F;main&#x2F;packages&#x2F;bylaw_ecto_query&#x2F;lib&#x2F;bylaw&#x2F;ecto&#x2F;query&#x2F;checks&#x2F;conflicting_where_predicates.ex&quot;&gt;Bylaw.Ecto.Query.Checks.ConflictingWherePredicates&lt;&#x2F;a&gt; inspects the composed query and reports the conflicting predicates.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deterministic-order&quot;&gt;Deterministic order&lt;&#x2F;h2&gt;
&lt;p&gt;Another query invariant is deterministic ordering.&lt;&#x2F;p&gt;
&lt;p&gt;The artifact is again the composed Ecto query. The pattern is a query that returns records in a non-deterministic order. The invariant is that an ordered result set must have a stable order across executions.&lt;&#x2F;p&gt;
&lt;p&gt;An AI agent writes an API endpoint:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; index&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; _params&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  posts &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    from&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;p &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Post&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; order_by&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;inserted_at&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; posts&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two posts can have the same &lt;code&gt;inserted_at&lt;&#x2F;code&gt; value. When that happens, Postgres is free to return those posts in a different order between requests.&lt;&#x2F;p&gt;
&lt;p&gt;This is based on a real issue I encountered at work. The bug itself was small: an API endpoint returned a list without deterministic ordering. It passed through tests and code review, was deployed, and eventually affected a customer consuming the API.&lt;&#x2F;p&gt;
&lt;p&gt;Once a bug like this reaches a customer, fixing one missing tie-breaker can consume time across an entire organization:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The customer notices inconsistent results and investigates whether the problem is in their own integration.&lt;&#x2F;li&gt;
&lt;li&gt;The problem has to bother them enough that they spend more time writing a support ticket.&lt;&#x2F;li&gt;
&lt;li&gt;Support investigates whether the issue has already been reported, fixed, or planned.&lt;&#x2F;li&gt;
&lt;li&gt;If it has not, support creates an actionable item for product.&lt;&#x2F;li&gt;
&lt;li&gt;Product evaluates the issue and prioritizes it against other work.&lt;&#x2F;li&gt;
&lt;li&gt;The issue is assigned to a developer, who reproduces it, identifies the missing deterministic order, writes a regression test, and implements the fix.&lt;&#x2F;li&gt;
&lt;li&gt;The change goes through code review and deployment.&lt;&#x2F;li&gt;
&lt;li&gt;Engineering informs support that the fix has been released.&lt;&#x2F;li&gt;
&lt;li&gt;Support informs the customer, who then verifies the behavior in their integration.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Every step is reasonable, but together they consume significant time from the customer, support, product, and engineering. The organization pays that cost because a small invariant was not enforced before release.&lt;&#x2F;p&gt;
&lt;p&gt;Fixing this endpoint does not prevent the same mistake from happening again. A regression test can protect this particular endpoint, but another developer or AI agent can add a different query without deterministic ordering. That query can also pass through tests and code review, reach production, and start the same lifecycle again.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanzidago&#x2F;bylaw&#x2F;blob&#x2F;main&#x2F;packages&#x2F;bylaw_ecto_query&#x2F;lib&#x2F;bylaw&#x2F;ecto&#x2F;query&#x2F;checks&#x2F;deterministic_order.ex&quot;&gt;Bylaw.Ecto.Query.Checks.DeterministicOrder&lt;&#x2F;a&gt; requires the root schema&#x27;s primary key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; index&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; _params&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  posts &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    from&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;p &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Post&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; :&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; order_by&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;inserted_at&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; desc&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; p&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Repo&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  json&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span&gt; %&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; posts&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The primary key acts as a tie-breaker. Even when two posts have the same &lt;code&gt;inserted_at&lt;&#x2F;code&gt; value, their order is now stable between API requests.&lt;&#x2F;p&gt;
&lt;p&gt;Encoding this rule as a guardrail catches the missing primary key while the query is being developed. The agent or developer fixes it immediately, before the bug can begin that lifecycle. Because the check runs at the repository choke point, it protects future queries as well as the endpoint that originally exposed the problem. This is the practical value of guardrails: they preserve attention across the organization by preventing known classes of mistakes at the cheapest point to correct them.&lt;&#x2F;p&gt;
&lt;p&gt;Another field with a unique database constraint could also make the ordering deterministic. However, an Ecto schema does not expose arbitrary unique indexes, so this query check cannot prove that such a field is unique. It only accepts the root schema&#x27;s primary key, which it can verify through Ecto schema reflection. A database-aware check could inspect unique constraints and safely recognize additional tie-breakers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-deliberate-trade-off&quot;&gt;A deliberate trade-off&lt;&#x2F;h2&gt;
&lt;p&gt;Ecto exposes the &lt;code&gt;%Ecto.Query{}&lt;&#x2F;code&gt; struct, but the internal representation of its expressions is not a stable extension API. A future Ecto release could change that representation and require Bylaw&#x27;s query checks to be updated.&lt;&#x2F;p&gt;
&lt;p&gt;I decided that trade-off was acceptable for three reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Ecto&#x27;s query API has been fairly stable across releases&lt;&#x2F;li&gt;
&lt;li&gt;I run these checks in development and test, not in production&lt;&#x2F;li&gt;
&lt;li&gt;if an Ecto upgrade breaks a check, it fails in the development lifecycle instead of changing behavior for customers&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The checks may need maintenance after an Ecto upgrade, but that failure should be visible and relatively easy to fix before deployment.&lt;&#x2F;p&gt;
&lt;p&gt;The checks are also intentionally narrow. They do not attempt to prove every semantic property of a query. Each check recognizes a specific set of query shapes and may need to balance false positives against false negatives. Their purpose is to steer the application toward explicit, approved patterns and catch high-value mistakes consistently.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-makes-a-good-guardrail&quot;&gt;What makes a good guardrail?&lt;&#x2F;h1&gt;
&lt;p&gt;The examples above inspect different artifacts, but the guardrails share the same properties:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deterministic:&lt;&#x2F;strong&gt; the same artifact always produces the same result&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Focused:&lt;&#x2F;strong&gt; each check enforces one policy&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Fast:&lt;&#x2F;strong&gt; developers and agents receive feedback while the relevant change is still fresh&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Actionable:&lt;&#x2F;strong&gt; the error explains what failed and how to correct it&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hard to bypass accidentally:&lt;&#x2F;strong&gt; the checks run before a change is merged&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Guardrails are also educational tools for humans and AI agents. A developer may not know that a database does not guarantee row order without an &lt;code&gt;ORDER BY&lt;&#x2F;code&gt;, or that ordering by a non-unique field can still produce unstable results. When a guardrail rejects the query, it can identify the violated invariant, explain why the query is unsafe, and show what needs to change.&lt;&#x2F;p&gt;
&lt;p&gt;The timing makes that feedback especially effective. The human or agent receives the explanation while writing the relevant code, with the failing query directly in front of them. The lesson is attached to a concrete decision instead of presented as an abstract rule that they may need someday.&lt;&#x2F;p&gt;
&lt;p&gt;A company-wide &lt;code&gt;CLAUDE.md&lt;&#x2F;code&gt;, style guide, or prompt can document the same policy, but every contributor and agent still has to find, interpret, and remember it. An actionable guardrail delivers the relevant policy at the point of violation and prevents the code from moving forward until the issue is addressed. It enforces the invariant and teaches the reasoning behind it in the same feedback loop.&lt;&#x2F;p&gt;
&lt;p&gt;A noisy or ambiguous check will eventually be ignored. A useful guardrail identifies a real violation, explains why it matters, and gives the developer or agent a clear path forward.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;where-to-enforce-guardrails&quot;&gt;Where to enforce guardrails&lt;&#x2F;h1&gt;
&lt;p&gt;A guardrail is most effective when it runs throughout the development lifecycle:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;in the agent&#x27;s feedback loop after it changes code&lt;&#x2F;li&gt;
&lt;li&gt;before commits or pushes&lt;&#x2F;li&gt;
&lt;li&gt;in CI before merge&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Fast local feedback helps the agent correct its own output. CI provides the final guarantee that the policy applies to every contribution.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;Building a software engineering guardrail starts with an inspectable artifact. Recognize the pattern that carries risk, express the desired policy as an invariant, and enforce it at a choke point through which every relevant artifact passes.&lt;&#x2F;p&gt;
&lt;p&gt;Bylaw applies that process to Elixir source code, PostgreSQL schemas, Ecto queries, and HTML. The result is a set of policies that AI agents and developers do not need to remember because the tooling enforces them every time.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>AI coding agents need software engineering guardrails</title>
		<published>2026-07-22T21:26:59+03:00</published>
		<updated>2026-07-22T21:26:59+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/ai-coding-agents-need-software-engineering-guardrails/"/>
		<id>https://ryanzidago.com/posts/ai-coding-agents-need-software-engineering-guardrails/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/ai-coding-agents-need-software-engineering-guardrails/">&lt;h1 id=&quot;problem&quot;&gt;Problem&lt;&#x2F;h1&gt;
&lt;p&gt;LLMs cannot reliably remember every rule in your codebase:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;your coding style guide&lt;&#x2F;li&gt;
&lt;li&gt;how to write a database query returning multiple records in a deterministic order&lt;&#x2F;li&gt;
&lt;li&gt;add type specs to functions&lt;&#x2F;li&gt;
&lt;li&gt;soft deleting a record instead of hard deleting it&lt;&#x2F;li&gt;
&lt;li&gt;etc ...&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Every time you tell an LLM to remember a rule, you&#x27;re paying for that rule on every generation.
The model may forget it, misunderstand it, or decide another instruction is more important. It is very tempting to pack a huge list of instructions into the LLM&#x27;s prompt or even a review agent but:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;it is slow&lt;&#x2F;li&gt;
&lt;li&gt;it is expensive&lt;&#x2F;li&gt;
&lt;li&gt;and above all, it is non-deterministic.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Engineering rules are different. Once you&#x27;ve decided that all public functions need typespecs, or that queries should always have deterministic ordering, there&#x27;s little value in asking an AI agent to rediscover those rules every time it writes code.&lt;&#x2F;p&gt;
&lt;p&gt;Which is why, with LLMs, I always ask myself:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can this probabilistic output be made deterministic so producing the output is faster, cheaper, and more reliable?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;solution&quot;&gt;Solution&lt;&#x2F;h1&gt;
&lt;p&gt;Encode engineering policies as deterministic guardrails enforced by:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;static analysis and type checkers (&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;erlang&#x2F;otp&#x2F;tree&#x2F;master&#x2F;lib&#x2F;dialyzer&quot;&gt;Dialyzer&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;microsoft&#x2F;pyright&quot;&gt;Pyright&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;linters and structural code checks (&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rrrene&#x2F;credo&quot;&gt;Credo&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;eslint&#x2F;eslint&quot;&gt;ESLint&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ast-grep&#x2F;ast-grep&quot;&gt;ast-grep&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;formatters (&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;elixir-lang&#x2F;elixir&#x2F;blob&#x2F;main&#x2F;lib&#x2F;mix&#x2F;lib&#x2F;mix&#x2F;tasks&#x2F;format.ex&quot;&gt;mix format&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;prettier&#x2F;prettier&quot;&gt;Prettier&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;test frameworks (&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;elixir-lang&#x2F;elixir&#x2F;tree&#x2F;main&#x2F;lib&#x2F;ex_unit&quot;&gt;ExUnit&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vitest-dev&#x2F;vitest&quot;&gt;Vitest&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;security checks (&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nccgroup&#x2F;sobelow&quot;&gt;Sobelow&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;semgrep&#x2F;semgrep&quot;&gt;Semgrep&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;architectural checks (&lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sasa1977&#x2F;boundary&quot;&gt;Boundary&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Each tool encodes a different kind of rule. Formatters eliminate debates about syntax. Type checkers and static analyzers catch invalid assumptions. Linters and structural code checks can enforce project-specific conventions and code patterns. Tests protect behavioral invariants. Security checks detect known vulnerabilities and unsafe patterns. Architectural checks enforce dependencies and boundaries between components.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of asking AI to remember your policies, configure tools that enforce them consistently.&lt;&#x2F;p&gt;
&lt;p&gt;Ensure those guardrails run throughout the software development lifecycle:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;prevent commits and pushes when a guardrail fails&lt;&#x2F;li&gt;
&lt;li&gt;fail CI when a guardrail fails&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When a policy is violated, its error message should clearly explain what policy was violated, why it exists, and whether bypassing it is ever appropriate.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;If you find yourself repeatedly reminding an AI agent about the same engineering rule over again and again, stop and ask yourself if you could encode that rule as a deterministic guardrail.
In the next article, we will look at how to build them.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Reducing Elixir worktree setup time by 83%</title>
		<published>2026-07-17T19:30:00+03:00</published>
		<updated>2026-07-17T19:30:00+03:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/reducing-elixir-worktree-setup-time-by-83-percent/"/>
		<id>https://ryanzidago.com/posts/reducing-elixir-worktree-setup-time-by-83-percent/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/reducing-elixir-worktree-setup-time-by-83-percent/">&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Copying &lt;code&gt;deps&lt;&#x2F;code&gt; and &lt;code&gt;_build&lt;&#x2F;code&gt; is the obvious first optimization. How much it helps depends on the project&#x27;s build profile: dependency-heavy projects benefit most, while projects dominated by application compilation may still have substantial work remaining.&lt;&#x2F;li&gt;
&lt;li&gt;Mix may still recompile the application&#x27;s own modules. In Phoenix, that happened because the compiler manifest recorded the original checkout&#x27;s current working directory.&lt;&#x2F;li&gt;
&lt;li&gt;On Elixir 1.19.2 or newer, &lt;code&gt;elixirc_options: [check_cwd: false]&lt;&#x2F;code&gt; can remove this blanket invalidation, but only when the project build is otherwise relocatable.&lt;&#x2F;li&gt;
&lt;li&gt;Copy-on-write cloning keeps each worktree independently writable without immediately making a full second copy of the cached files.&lt;&#x2F;li&gt;
&lt;li&gt;Always run &lt;code&gt;mix deps.get&lt;&#x2F;code&gt; and &lt;code&gt;mix compile&lt;&#x2F;code&gt; after cloning to validate your changes.&lt;&#x2F;li&gt;
&lt;li&gt;In my Phoenix benchmark, copying the caches reduced setup from 12.01 seconds to 2.47 seconds. Making the project build relocatable and cloning copy-on-write reduced it further to 2.06 seconds.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;Creating a &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-worktree&quot;&gt;Git worktree&lt;&#x2F;a&gt; took milliseconds. Preparing it for Elixir development took much longer because the new checkout had no &lt;code&gt;deps&lt;&#x2F;code&gt; or &lt;code&gt;_build&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The obvious solution was to copy both directories from an existing checkout. It worked: Mix reused the compiled dependencies and setup became much faster. But &lt;strong&gt;Phoenix still recompiled all 74 of its own project files, even when the worktree started at the same commit&lt;&#x2F;strong&gt; and the copy preserved timestamps.
Why is that?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-solution&quot;&gt;The solution&lt;&#x2F;h2&gt;
&lt;p&gt;I benchmarked &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;phoenixframework&#x2F;phoenix&quot;&gt;Phoenix&lt;&#x2F;a&gt; at commit &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;phoenixframework&#x2F;phoenix&#x2F;commit&#x2F;0d9c79ebfc0f2065b41e268551638bd8b767eda9&quot;&gt;&lt;code&gt;0d9c79e&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; while answering that question.&lt;&#x2F;p&gt;
&lt;p&gt;Assume my Mix project is already compiled on &lt;code&gt;main&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I create a worktree from the same commit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;git&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; worktree&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; add&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; .worktrees&#x2F;add-export&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;b&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; add-export&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The worktree lives at a different absolute path:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;.worktrees&#x2F;add-export&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Git does not put ignored build artifacts in the new checkout. Instead of rebuilding them from scratch, I seed the worktree from &lt;code&gt;main&lt;&#x2F;code&gt; and let Mix validate them:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;deps&#x2F;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;.worktrees&#x2F;add-export&#x2F;deps&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;_build&#x2F;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;.worktrees&#x2F;add-export&#x2F;_build&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;.worktrees&#x2F;add-export&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; deps.get&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; compile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;deps&lt;&#x2F;code&gt; contains dependency source. &lt;code&gt;_build&lt;&#x2F;code&gt; contains compiled dependencies, project modules, and compiler manifests. Copying both provided the largest improvement because Mix could reuse the compiled dependency tree. However, Phoenix&#x27;s own modules still recompiled:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Compiling 74 files (.ex)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Generated phoenix app&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Even with that recompilation, setup fell from 12.01 seconds to 2.47 seconds. Copying the caches saved 9.54 seconds by preserving the compiled dependencies, but it did not yet preserve Phoenix&#x27;s own compiled modules.&lt;&#x2F;p&gt;
&lt;p&gt;Why does the project still recompile?&lt;&#x2F;p&gt;
&lt;p&gt;The explanation was inside &lt;code&gt;_build&lt;&#x2F;code&gt;. Mix writes compiler manifests describing the inputs and assumptions behind compiled modules, and the Elixir manifest stores the current working directory. Copying &lt;code&gt;_build&lt;&#x2F;code&gt; preserved the path from &lt;code&gt;main&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cwd = &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;manifest stores this cwd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              │ copy deps + _build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              ▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;worktree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cwd = &#x2F;Users&#x2F;ryanzidago&#x2F;Projects&#x2F;my_mix_project&#x2F;.worktrees&#x2F;add-export&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;manifest still stores the main cwd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              ▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stored cwd != current cwd → manifest stale → recompile project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With the default &lt;code&gt;check_cwd: true&lt;&#x2F;code&gt;, Mix deliberately treats that mismatch as stale. The behavior is visible in &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;elixir-lang&#x2F;elixir&#x2F;blob&#x2F;v1.20.2&#x2F;lib&#x2F;mix&#x2F;lib&#x2F;mix&#x2F;compilers&#x2F;elixir.ex&quot;&gt;&lt;code&gt;Mix.Compilers.Elixir&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;: it writes &lt;code&gt;File.cwd!()&lt;&#x2F;code&gt; into the manifest and compares it on the next compile.&lt;&#x2F;p&gt;
&lt;p&gt;That check is a correctness feature. Compiled modules retain absolute source-path metadata from the original checkout. Relocating them does not normally change runtime behavior, but developer tools that use this metadata may still point to the original source tree. Recompiling updates the metadata to the new checkout.&lt;&#x2F;p&gt;
&lt;p&gt;More importantly, application code can use the cwd during compilation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;defmodule&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; MyMixProject&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;BuildInfo&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  @&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;project_root&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; File&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cwd!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; project_root&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; do:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; @&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;project_root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;@project_root&lt;&#x2F;code&gt; becomes part of the compiled module. Copying that BEAM into a worktree without recompiling it leaves runtime code pointing to &lt;code&gt;main&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;There are therefore two kinds of path dependence:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Metadata-only:&lt;&#x2F;strong&gt; the BEAM points to source on &lt;code&gt;main&lt;&#x2F;code&gt;. Runtime behavior is unchanged, but tooling may open the wrong checkout.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Behavioral:&lt;&#x2F;strong&gt; compile-time code embeds a path or data read through it. Reusing the BEAM can be incorrect.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Most application modules do not behave differently based on their checkout path. The risky cases are compile-time macros and module attributes that read files relative to the project root, custom Mix compilers, code reloaders, absolute paths in compile-time configuration, and native builds with absolute include or runtime paths. &lt;code&gt;check_cwd: false&lt;&#x2F;code&gt; accepts the metadata tradeoff, so do not use it while behavioral path dependence remains implicit.&lt;&#x2F;p&gt;
&lt;p&gt;Elixir 1.19.2 introduced a configurable cwd check. If the remaining project recompilation matters, audit that path usage before adding this to &lt;code&gt;mix.exs&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; project&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    elixirc_options&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;check_cwd&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Changing the option changes the compiler cache key, so build &lt;code&gt;main&lt;&#x2F;code&gt; once after adding it. New worktrees can then reuse manifests created with the same compiler options.&lt;&#x2F;p&gt;
&lt;p&gt;Setting a literal &lt;code&gt;false&lt;&#x2F;code&gt; makes relocation a project-wide property. To opt in only for the worktree workflow, map the option to &lt;code&gt;MIX_CHECK_CWD&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; project&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  check_cwd? &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; System&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;get_env&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;MIX_CHECK_CWD&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;false&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;    #&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    elixirc_options&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;check_cwd&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; check_cwd?&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Worktree commands can then run with &lt;code&gt;MIX_CHECK_CWD=false&lt;&#x2F;code&gt;, while teammates and CI retain the default. Only the literal value &lt;code&gt;false&lt;&#x2F;code&gt; disables the check. The base cache and worktree must use the same value because &lt;code&gt;elixirc_options&lt;&#x2F;code&gt; participates in the compiler cache key. Warm the base cache once with &lt;code&gt;MIX_CHECK_CWD=false mix compile&lt;&#x2F;code&gt;, then export the same value in the setup hook.&lt;&#x2F;p&gt;
&lt;p&gt;Phoenix still compiled four files because they consult &lt;code&gt;Mix.Project&lt;&#x2F;code&gt; at compile time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;recompiled 4 of 74 project files:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lib&#x2F;mix&#x2F;tasks&#x2F;phx.ex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lib&#x2F;mix&#x2F;tasks&#x2F;phx.gen.embedded.ex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lib&#x2F;mix&#x2F;tasks&#x2F;phx.gen.schema.ex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  lib&#x2F;phoenix&#x2F;code_reloader&#x2F;server.ex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Setup fell again, from 2.47 seconds to 2.17 seconds, while project recompilation dropped from 74 files to four. The option removes blanket cwd invalidation; Mix continues applying its other stale checks.&lt;&#x2F;p&gt;
&lt;p&gt;The last optimization is to clone the caches copy-on-write. On &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;documentation&#x2F;foundation&#x2F;about-apple-file-system&quot;&gt;APFS&lt;&#x2F;a&gt;, &lt;code&gt;cp -a -c&lt;&#x2F;code&gt; creates file clones. On a reflink-capable Linux filesystem such as &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;btrfs.readthedocs.io&#x2F;en&#x2F;latest&#x2F;Reflink.html&quot;&gt;btrfs&lt;&#x2F;a&gt; or &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.kernel.org&#x2F;filesystems&#x2F;xfs&#x2F;index.html&quot;&gt;XFS&lt;&#x2F;a&gt;, GNU &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;coreutils&#x2F;manual&#x2F;html_node&#x2F;cp-invocation.html&quot;&gt;&lt;code&gt;cp --reflink&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; provides the equivalent behavior. Each worktree gets an independently writable directory tree, while unchanged data initially shares physical storage. Regular &lt;code&gt;cp -a&lt;&#x2F;code&gt; still provides the main compilation benefit; copy-on-write only reduces the time and storage needed to seed the cache. In the benchmark, it reduced setup from 2.17 seconds to 2.06 seconds.&lt;&#x2F;p&gt;
&lt;p&gt;I use &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;worktrunk.dev&#x2F;&quot;&gt;Worktrunk&lt;&#x2F;a&gt; to run the complete hook when it creates a worktree, but the script works with any Git worktree workflow. It includes the optional cwd optimization; remove the &lt;code&gt;MIX_CHECK_CWD&lt;&#x2F;code&gt; export if you only want the broadly applicable cache-copying benefit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#!&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;bin&#x2F;sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;set&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;eu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;base_worktree&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage&quot;&gt;export&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MIX_CHECK_CWD&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;clone_directory&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  source_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  destination_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -d&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;source_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; ]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; return&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  mkdir&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;p&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;destination_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;uname&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; Darwin &lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;c&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;source_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;destination_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  elif&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; !&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-reflink=always&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;source_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;destination_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;source_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;destination_path&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;clone_directory&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;base_worktree&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;deps&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; deps&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;clone_directory&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;base_worktree&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;_build&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; _build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; deps.get&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; compile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Always run &lt;code&gt;mix deps.get&lt;&#x2F;code&gt; and &lt;code&gt;mix compile&lt;&#x2F;code&gt; after cloning. The first command reconciles the copied dependency source with the worktree&#x27;s &lt;code&gt;mix.lock&lt;&#x2F;code&gt;; the second recompiles anything genuinely stale. If compilation fails, report the error instead of automatically deleting &lt;code&gt;_build&lt;&#x2F;code&gt;: the branch may contain a real source error, and a full rebuild would only repeat it more slowly.&lt;&#x2F;p&gt;
&lt;p&gt;On my machine, the final median consisted of 53ms to create the worktree, 215ms to clone the caches, 1.33s to reconcile dependencies, and 441ms to compile.&lt;&#x2F;p&gt;
&lt;p&gt;Here is the complete progression:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Setup&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Median&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Range&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Phoenix files compiled&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Saved from previous step&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Fresh worktree&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;12.01s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;10.53–13.98s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;74&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;N&#x2F;A&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Copy &lt;code&gt;deps&lt;&#x2F;code&gt; and &lt;code&gt;_build&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.47s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.29–2.73s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;74&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.54s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add &lt;code&gt;check_cwd: false&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.17s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.13–2.27s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.30s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Copy-on-write clone&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2.06s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.92–2.18s&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.11s&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Copying the caches delivered 96% of the final 9.95-second saving. Making the project build relocatable and cloning copy-on-write recovered the smaller remaining opportunity.&lt;&#x2F;p&gt;
&lt;p&gt;Every scenario includes &lt;code&gt;git worktree add&lt;&#x2F;code&gt;, &lt;code&gt;mix deps.get&lt;&#x2F;code&gt;, and &lt;code&gt;mix compile&lt;&#x2F;code&gt;. The benchmark used one discarded warmup and five measured trials on an Apple M4 Max, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;documentation&#x2F;foundation&#x2F;about-apple-file-system&quot;&gt;APFS&lt;&#x2F;a&gt;, Erlang&#x2F;OTP 29, and Elixir 1.20.2.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-learning&quot;&gt;The learning&lt;&#x2F;h2&gt;
&lt;p&gt;Build caches are not just collections of files. They encode assumptions about the environment in which they were created: paths, toolchain versions, operating systems, architectures, configuration, and dependency state.&lt;&#x2F;p&gt;
&lt;p&gt;Whenever you relocate a cache, the useful question is not only “Can I copy these files?” It is “Which assumptions were encoded when they were built, and are those assumptions still true?”&lt;&#x2F;p&gt;
&lt;p&gt;I repeated the experiment against &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;livebook-dev&#x2F;livebook&quot;&gt;Livebook&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;plausible&#x2F;analytics&quot;&gt;Plausible Analytics&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hexpm&#x2F;hexpm&quot;&gt;Hex.pm&lt;&#x2F;a&gt;, and &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;supabase&#x2F;realtime&quot;&gt;Supabase Realtime&lt;&#x2F;a&gt;. Copying the caches reduced median setup time by 79% to 91% even though every application file still recompiled. Those projects embed checkout-specific absolute paths in compile-time configuration, so disabling the manifest cwd check was not enough to make their builds relocatable. Phoenix was the controlled example because it isolated the cwd check cleanly.&lt;&#x2F;p&gt;
&lt;p&gt;This also means the benefit depends on the project. A dependency-heavy project with little application code can gain most of its speedup just by copying the caches. A project dominated by expensive application compilation may have much more work remaining.&lt;&#x2F;p&gt;
&lt;p&gt;At minimum, reuse caches only across compatible:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;commits and source trees&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;MIX_ENV&lt;&#x2F;code&gt; values and &lt;code&gt;mix.lock&lt;&#x2F;code&gt; files&lt;&#x2F;li&gt;
&lt;li&gt;compiler options&lt;&#x2F;li&gt;
&lt;li&gt;Elixir and Erlang&#x2F;OTP versions&lt;&#x2F;li&gt;
&lt;li&gt;operating systems and CPU architectures, especially with native dependencies&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Clone into empty cache directories, preserve timestamps, and keep every worktree independently writable. Do not symlink every worktree to one shared build directory: branches would become competing owners of the same modules and manifests.&lt;&#x2F;p&gt;
&lt;p&gt;The goal is not to bypass Mix. It is to give Mix a cheap, independent cache that it can validate. Once the cache&#x27;s assumptions become explicit, reuse stops being only a file-copying question and becomes a relocatability question, with Mix remaining the correctness gate.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;ryanzidago&#x2F;d943ca563a00a528874338337ceb91db&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;reproduction script&lt;&#x2F;a&gt; pins the Phoenix commit, prints every phase, reports its environment, and verifies the result with module loading and Phoenix router tests. The benchmark used a warm Hex cache and excluded network download time, so treat it as one-machine evidence rather than a promise of equivalent timings elsewhere.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>What I learned shipping a visual dashboard builder in 3 days</title>
		<published>2026-03-28T17:20:22+02:00</published>
		<updated>2026-03-28T17:20:22+02:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/what-i-learned-shipping-a-visual-dashboard-builder-in-3-days/"/>
		<id>https://ryanzidago.com/posts/what-i-learned-shipping-a-visual-dashboard-builder-in-3-days/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/what-i-learned-shipping-a-visual-dashboard-builder-in-3-days/">&lt;p&gt;A customer wanted a visual dashboard builder (where you click buttons and create your own dashboard).
They needed this since yesterday™, but I absolutely didn&#x27;t want to compromise on quality as I knew having this poorly implemented would bite us a lot in the future.&lt;&#x2F;p&gt;
&lt;p&gt;In 3 days we shipped a usable first version of a visual dashboard builder where users could:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;compose a query by selecting a source, dimensions, measures, filters, sorts, limit, and timezone&lt;&#x2F;li&gt;
&lt;li&gt;use the result of that query to configure a widget, including chart settings and previewing the output&lt;&#x2F;li&gt;
&lt;li&gt;place one or more widgets into a dashboard grid, then drag, drop, and resize them until the layout felt right&lt;&#x2F;li&gt;
&lt;li&gt;save, edit, and delete dashboards once they were happy with the result&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Under the hood, each widget stored structured query intent rather than relying on SQL as the editing format. That made the builder easier to reopen, validate, and evolve over time. The system could still compile that intent into executable ClickHouse SQL for preview and persistence, but the source of truth for editing stayed in a format the UI could understand directly.&lt;&#x2F;p&gt;
&lt;p&gt;I decided to organise the work in 4 phases:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;research planning&lt;&#x2F;li&gt;
&lt;li&gt;architecture planning&lt;&#x2F;li&gt;
&lt;li&gt;implementation planning&lt;&#x2F;li&gt;
&lt;li&gt;implementation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;During this work I learned that:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In my experience on this project, Codex was amazing at research and following a plan but weak at UI work&lt;&#x2F;li&gt;
&lt;li&gt;In my experience on this project, Claude Code did decent research, could follow a plan, and was much stronger at UI work&lt;&#x2F;li&gt;
&lt;li&gt;As such, from now on I shall remember:&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Switching large language model is faster than forcing it to do something it isn&#x27;t good at&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;It is hard to know in advance which model will be best at a given task, so I use several and keep the strongest output&lt;&#x2F;li&gt;
&lt;li&gt;User interfaces deserve their own spec (which can be a .html file)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here&#x27;s how I proceeded in more detail:&lt;&#x2F;p&gt;
&lt;h1 id=&quot;research-plan&quot;&gt;Research plan&lt;&#x2F;h1&gt;
&lt;p&gt;First, I asked an LLM to produce a research prompt:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Context&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Our tech stack&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; What the dashboard builder needs to do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; What I need from this research&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 1. Architecture &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; design patterns&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 2. ClickHouse-specific challenges&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 3. Phoenix LiveView-specific considerations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 4. Common bugs &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; failure modes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 5. Query safety &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; guardrails&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 6. Dashboard layout &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; composition&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 7. Lessons from existing tools&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; 8. Build vs. integrate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Output format&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Please structure your response with clear sections matching the numbered topics above. For each section:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Lead with the &lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;key insight or recommendation&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Follow with &lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;supporting evidence&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;span&gt; (how other tools do it, common patterns)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; End with &lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;specific pitfalls to avoid&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Where relevant, suggest &lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;concrete Elixir&#x2F;LiveView patterns&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-bold&quot;&gt;**&lt;&#x2F;span&gt;&lt;span&gt; or pseudocode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then I asked the following LLM platforms to produce their own research outputs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;claude code (cli)&lt;&#x2F;li&gt;
&lt;li&gt;codex (cli)&lt;&#x2F;li&gt;
&lt;li&gt;claude (web)&lt;&#x2F;li&gt;
&lt;li&gt;gemini (web)&lt;&#x2F;li&gt;
&lt;li&gt;perplexity (web)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;After that, I asked Claude Code which research output was the best and to my surprise it said Codex!
I then asked it to add to the Codex research output any insights from other research outputs.&lt;&#x2F;p&gt;
&lt;p&gt;Once we had compiled this information, we moved on to the next step -- architecting the solution.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;architecture-plan&quot;&gt;Architecture plan&lt;&#x2F;h1&gt;
&lt;p&gt;The purpose of this phase is to commit to a concrete architecture&#x2F;design and set of features based on the knowledge gathered from the previous phase.
I asked Claude Code to create an architecture plan and then asked it to review it while brainstorming with Codex (you can set up the Codex MCP in your &lt;code&gt;.mcp.json&lt;&#x2F;code&gt; so Claude can use Codex).
This created me a sensible design plan that looks roughly like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;Feature&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; architecture plan&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Goal&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Context &#x2F; problem&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Target architecture&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; User flow&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Storage model&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Safety constraints&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; metadata &#x2F; validation context&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; permissions &#x2F; limits &#x2F; performance guardrails &#x2F; failure handling&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Scope&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h1 id=&quot;implementation-plan&quot;&gt;Implementation plan&lt;&#x2F;h1&gt;
&lt;p&gt;The purpose of this phase is to commit to a specific implementation of the architecture we&#x27;ve specified above.
Here again, I used the same process as before and asked Claude Code to draft an implementation plan and then asked it to review it with Codex.
The implementation plan was defined as a series of steps but the plan was so huge that I ended up cutting it into multiple phases where each phase was its own plan:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;foundation with query compilation&lt;&#x2F;li&gt;
&lt;li&gt;routing seam&lt;&#x2F;li&gt;
&lt;li&gt;dashboard builder UI&lt;&#x2F;li&gt;
&lt;li&gt;save, edit and delete flow&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;Feature&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; implementation plan&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Goal&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Execution policy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; test-first &#x2F; commit cadence &#x2F; validation expectations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Safety guardrails&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; invariants that must never be broken&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Completion gate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; format &#x2F; compile &#x2F; lint &#x2F; tests &#x2F; QA required before done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; File plan&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; New files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Modified files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Test files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;##&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Tasks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Task 1: &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Purpose&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Required outcomes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Test outlines&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Manual spot checks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; Step-by-step checklist&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-markup z-heading&quot;&gt;###&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Task 2: &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;name&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-list z-begin z-markdown&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt; same shape&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h1 id=&quot;implementation&quot;&gt;Implementation&lt;&#x2F;h1&gt;
&lt;p&gt;For the implementation phase I told Codex to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;create a git worktree for the implementation phase&lt;&#x2F;li&gt;
&lt;li&gt;follow the plan&lt;&#x2F;li&gt;
&lt;li&gt;create a PR, assign it to me&lt;&#x2F;li&gt;
&lt;li&gt;move on to the next phase of the plan&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Implementation was not a straight line. The backend-heavy parts responded well to planning and agent execution, but the UI exposed a gap in the spec. The first versions were technically functional but hard to understand and visually inconsistent with the rest of the app. At that point I stopped trying to prompt my way out of an underspecified UI problem and wrote down a proper design direction instead: first a design-system HTML file based on the existing app, then low-fidelity wireframes, then a higher-fidelity wireframe, and only then another implementation pass.&lt;&#x2F;p&gt;
&lt;p&gt;So I changed the process:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;asked both Claude Code and Codex to produce a design system in a self-contained .html file out of what we already had in the application.
Claude Code was much better than Codex here so this is the design system I selected.&lt;&#x2F;li&gt;
&lt;li&gt;asked Claude via Claude.ai to implement a low fidelity wireframe (that we iterated over multiple times)&lt;&#x2F;li&gt;
&lt;li&gt;asked Claude via Claude.ai to implement a high fidelity wireframe using the previously created design system&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I used Claude via Claude.ai instead of via Claude Code as I didn&#x27;t want Claude to be distracted by existing code and practices, etc.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h1&gt;
&lt;p&gt;Three things I&#x27;d do again:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;use multiple models early for research and planning, then keep the best output instead of forcing one model to do everything&lt;&#x2F;li&gt;
&lt;li&gt;treat visual builders as two separate problems: query architecture and UI specification&lt;&#x2F;li&gt;
&lt;li&gt;store structured authoring state for visual features, and only compile to SQL at the execution boundary&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That combination let me move fast without turning the feature into a future maintenance problem. I would never have gotten the same result in the pre-agentic coding era.&lt;&#x2F;p&gt;
&lt;p&gt;The next thing I want to build is a more reactive development system around this workflow: when I move a PR from draft to ready for review, an LLM reviews it automatically; when CI goes red, an agent starts trying to make it green; when a design changes, downstream implementation work updates with it.&lt;&#x2F;p&gt;
&lt;p&gt;That kind of system creates more parallel work so I can spend more time on product, architecture, and judgment instead of manually pushing every step forward.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re building complex B2B interfaces with coding agents, I&#x27;d be interested to compare workflows—especially where the agents struggled with product or UI decisions. &lt;a href=&quot;mailto:ryan.zidago@protonmail.com&quot;&gt;Send me an email&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Incrementally enforcing code quality rules with coding agents</title>
		<published>2026-03-08T00:00:02+00:00</published>
		<updated>2026-03-08T00:00:02+00:00</updated>
		<link rel="alternate" type="text/html" href="https://ryanzidago.com/posts/incrementally-enforcing-code-quality-rules-with-coding-agents/"/>
		<id>https://ryanzidago.com/posts/incrementally-enforcing-code-quality-rules-with-coding-agents/</id>
    
		<content type="html" xml:base="https://ryanzidago.com/posts/incrementally-enforcing-code-quality-rules-with-coding-agents/">&lt;p&gt;AI agents are becoming part of the development workflow, and they&#x27;re only as good as the feedback your codebase gives them.&lt;&#x2F;p&gt;
&lt;p&gt;OpenAI calls this &lt;a rel=&quot;noopener nofollow noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;openai.com&#x2F;index&#x2F;harness-engineering&#x2F;&quot;&gt;harness engineering&lt;&#x2F;a&gt;: the idea that you should wrap your codebase in linters, structural tests, and automated checks that act as guardrails for both human developers and AI agents.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;In practice, we enforce these rules with custom linters and structural tests, plus a small set of &quot;taste invariants.&quot; [...] we statically enforce structured logging, naming conventions for schemas and types, file size limits, and platform-specific reliability requirements with custom lints.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Great. But what if your codebase doesn&#x27;t have any of this?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h2&gt;
&lt;p&gt;In a new project, it&#x27;s fairly easy to add new linter rules or CI checks as there are very few existing offenses against those rules.&lt;&#x2F;p&gt;
&lt;p&gt;In a large codebase that has seen many developers over the years, this is another story.&lt;&#x2F;p&gt;
&lt;p&gt;You may have never used types or lints. You add them; now you get hundreds and hundreds of errors, lints, warnings, refactoring opportunities etc.&lt;&#x2F;p&gt;
&lt;p&gt;How do you best proceed then?
Do you let the AI fix everything, everywhere, all at once?
You can end up with PRs with +10_000 lines of changes ... This is too much to review, so the probability that bugs slip through is higher. It&#x27;s also a merge conflict magnet against any parallel work, and if something does break, rolling back means losing all the fixes; not just the problematic one.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-strategy&quot;&gt;The strategy&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s what I suggest:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;add the tool&lt;&#x2F;li&gt;
&lt;li&gt;enable it in the CI&lt;&#x2F;li&gt;
&lt;li&gt;now locally, run the tool to see which config&#x2F;rules produce offenses&lt;&#x2F;li&gt;
&lt;li&gt;disable any rules that produce offenses and merge your PR into main&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This will prevent contributors from producing more offenses and set the best possible baseline.&lt;&#x2F;p&gt;
&lt;p&gt;Now that you have a best case, let&#x27;s look at how to bring the rest of the rules into play. Ask your favorite coding agent:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Here&#x27;s the list of rules that we want to enable in this project. Create stacked PRs where each PR enables (and eventually fix all offenses related to) one rule. Once all the tests and precommit checks and the CI passes, create the PR and move on to the next rule; rinse and repeat until all the following rules are enabled.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A word of caution: not all rules are equal. Fixing import ordering is mechanical; adding types to a legacy module with implicit contracts can surface deeper design issues. If a rule&#x27;s fixes start snowballing, it&#x27;s fine to skip it and come back later. The point is steady progress.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what the progression looks like:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Rule&lt;&#x2F;th&gt;&lt;th&gt;PR #0&lt;&#x2F;th&gt;&lt;th&gt;PR #1&lt;&#x2F;th&gt;&lt;th&gt;PR #2&lt;&#x2F;th&gt;&lt;th&gt;PR #3&lt;&#x2F;th&gt;&lt;th&gt;PR #4&lt;&#x2F;th&gt;&lt;th&gt;PR #5&lt;&#x2F;th&gt;&lt;th&gt;PR #6&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;eqeqeq&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;no-eval&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;import&#x2F;order&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;✓&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;no-console&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;✓&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;no-unused-vars&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;✓&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;naming-convention&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;✓&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;no-explicit-any&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;✓&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;✓&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;strict-null-checks&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;✕&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;✓&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;strong&gt;PR #0&lt;&#x2F;strong&gt;: Add tool to CI. Enable &lt;code&gt;eqeqeq&lt;&#x2F;code&gt; and &lt;code&gt;no-eval&lt;&#x2F;code&gt; (zero offenses). Disable the rest.
&lt;strong&gt;PR #1–#6&lt;&#x2F;strong&gt;: One rule per PR. Fix all offenses. Merge. Next.&lt;&#x2F;p&gt;
&lt;p&gt;There are various ways to slice this incremental work:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;per rule&lt;&#x2F;li&gt;
&lt;li&gt;per rule per area (backend, frontend)&lt;&#x2F;li&gt;
&lt;li&gt;per rule per domain (accounts, invoices, orders)&lt;&#x2F;li&gt;
&lt;li&gt;per file&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Find the granularity where you are comfortable reviewing and releasing changes.&lt;&#x2F;p&gt;
&lt;p&gt;The investment pays off twice: a cleaner codebase for your team and better guardrails for AI-assisted development.&lt;&#x2F;p&gt;
</content>
	</entry>
</feed>
