<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ci</title>
<link>https://coderlegion.com/tag/ci</link>
<description>Powered by Question2Answer</description>
<item>
<title>My quality gate wasn't strict. It was dead.</title>
<link>https://coderlegion.com/32703/my-quality-gate-wasnt-strict-it-was-dead</link>
<description>&lt;p&gt;There is a job in my CI called &lt;code&gt;judge&lt;/code&gt;. It reads the monthly issue my content pipeline writes, scores it against a rubric with five criteria, and blocks the merge if anything comes back at 2 out of 5 or lower. An LLM grading an LLM, with a written policy about what counts as broken.&lt;/p&gt;
&lt;p&gt;It went red on a content PR this week. My first thought was that the piece was weak. My second thought, about four minutes later, was worse: this gate had been red on every content PR for a while, and I had been reading that as a high bar.&lt;/p&gt;
&lt;p&gt;It wasn't a high bar. The job never reached the rubric at all.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Error: anthropic messages -&amp;gt; 400: output_config.format.schema:
For 'integer' type, properties maximum, minimum are not supported
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The judge asks the model for structured output, and the schema it sends had this in it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;voto: { type: &quot;integer&quot;, minimum: 1, maximum: 5 },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is correct JSON Schema, and which the structured output layer rejects. The request never made it to the model. The job exited 1 before scoring a single word.&lt;/p&gt;
&lt;p&gt;A gate that cannot go green is not enforcing anything. It has the same effect on your pipeline as &lt;code&gt;continue-on-error&lt;/code&gt;, except it also makes you feel virtuous while it does nothing.&lt;/p&gt;
&lt;h3 id=&quot;the-error-message-named-half-the-problem&quot;&gt;The error message named half the problem&lt;/h3&gt;
&lt;p&gt;The obvious fix is to delete &lt;code&gt;minimum&lt;/code&gt; and &lt;code&gt;maximum&lt;/code&gt; and move on. I nearly did. What stopped me was noticing that the schema had a second kind of constraint in it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;motivo: { type: &quot;string&quot;, maxLength: 300 },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Length constraints are rejected too. &lt;code&gt;maxLength&lt;/code&gt;, &lt;code&gt;minLength&lt;/code&gt;, &lt;code&gt;pattern&lt;/code&gt;, &lt;code&gt;minItems&lt;/code&gt;, all of it. My schema had three of them, and the 400 mentioned none, because validation stops at the first failure. If I had fixed what the message named, I would have pushed, waited for CI, and collected the next 400. Then the one after that.&lt;/p&gt;
&lt;p&gt;This is a general property of error messages, not a quirk of one API: they report the first thing that broke, not the set of things that are broken. The fix that only addresses what the message says is a fix sized to the message, not to the defect.&lt;/p&gt;
&lt;p&gt;The other thing worth knowing here: the Python and TypeScript SDKs strip unsupported constraints for you before the request goes out. My pipeline is zero-dependency and calls the API with native &lt;code&gt;fetch&lt;/code&gt;, so nobody was stripping anything. If your SDK has been quietly saving you, you will find out the day you drop it.&lt;/p&gt;
&lt;h3 id=&quot;so-the-test-is-about-the-contract&quot;&gt;So the test is about the contract&lt;/h3&gt;
&lt;p&gt;I could have written a test asserting no &lt;code&gt;minimum&lt;/code&gt; on integers. It would have passed, and it would have been useless three weeks later when someone adds a &lt;code&gt;pattern&lt;/code&gt; to a string.&lt;/p&gt;
&lt;p&gt;Instead the accepted and rejected keywords are written down once, and every schema I send to the API gets checked against them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const RIFIUTATE = new Map([
  [&quot;minimum&quot;, &quot;numeric constraint&quot;],
  [&quot;maximum&quot;, &quot;numeric constraint&quot;],
  [&quot;maxLength&quot;, &quot;length constraint&quot;],
  [&quot;pattern&quot;, &quot;string constraint&quot;],
  // ...
]);

export function keywordRifiutate(schema, path = &quot;$&quot;) { /* walks the tree */ }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It walks nested schemas, and it knows that inside &lt;code&gt;properties&lt;/code&gt; the keys are my field names rather than schema keywords, so a field genuinely called &lt;code&gt;pattern&lt;/code&gt; doesn't trip it.&lt;/p&gt;
&lt;p&gt;On first run it found sixteen violations across the judge schema. Not two. My generation schema, checked at the same time, was already clean, which I would not have bet on.&lt;/p&gt;
&lt;p&gt;There is a second check next to it that lists keywords the contract doesn't mention. It fails rather than allowing them, which sounds aggressive for something the API might accept fine. The reason is that I want the moment where somebody adds an unfamiliar keyword to be the moment somebody reads the docs, instead of the moment CI goes red for a reason nobody connects to the schema.&lt;/p&gt;
&lt;h3 id=&quot;where-the-constraint-went&quot;&gt;Where the constraint went&lt;/h3&gt;
&lt;p&gt;The rubric still runs on a 1 to 5 scale. That range now lives in the two places the API can't reject it.&lt;/p&gt;
&lt;p&gt;The prompt describes it, with each score defined. And the code treats an out-of-range score the way it already treats a missing one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const fuoriScala = (v) =&amp;gt; !Number.isInteger(v) || v &amp;lt; 1 || v &amp;gt; 5;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fail closed. If the model returns a 7, the rubric is unreadable, and an unreadable rubric doesn't promote anything. That was already the policy for a criterion the model forgot to fill in. A score outside the scale is the same class of problem, so it gets the same answer.&lt;/p&gt;
&lt;p&gt;Moving a constraint out of a schema usually means giving it up. Here it just meant enforcing it somewhere with a worse reputation and better reach.&lt;/p&gt;
&lt;h3 id=&quot;what-i-d-check-on-your-own-pipeline&quot;&gt;What I'd check on your own pipeline&lt;/h3&gt;
&lt;p&gt;Grep your CI history for a job that has never been green. Not &quot;was red today&quot;, never green at all, or not since some commit that had nothing to do with it. That job is not protecting you, and it is currently costing you the specific kind of comfort that comes from thinking it is.&lt;/p&gt;
&lt;p&gt;Mine ran for weeks. It was the most reassuring line in the workflow file.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32703/my-quality-gate-wasnt-strict-it-was-dead</guid>
<pubDate>Tue, 27 Oct 2026 21:03:40 +0000</pubDate>
</item>
<item>
<title>What Is CI/CD? A Plain-English Guide for Teams Shipping Software Daily</title>
<link>https://coderlegion.com/32665/what-is-ci-cd-a-plain-english-guide-for-teams-shipping-software-daily</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;If you have spent any time around software teams, you have probably heard someone say, “It’s already in the pipeline.” That pipeline is usually a CI/CD pipeline, and it quietly powers much of the modern internet. So, what is CI/CD, and why do developers talk about it so often?&lt;/p&gt;
&lt;p&gt;In simple terms, CI/CD is a set of practices that helps teams build, test, and release software in small, safe steps instead of one risky leap. I have watched teams go from stressful monthly releases to calm daily deployments once they adopted it. This guide explains the idea in plain language, so even a newcomer can follow along.&lt;/p&gt;
&lt;h3 id=&quot;what-is-ci-cd-in-simple-words-&quot;&gt;What Is CI/CD in Simple Words?&lt;/h3&gt;
&lt;p&gt;Let’s answer the core question directly. &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.globallogic.com/glossary/continuous-integration-continuous-delivery-ci-cd/&quot;&gt;What is CI/CD?&lt;/a&gt; It stands for continuous integration and continuous delivery (or continuous deployment). Think of it as an assembly line for code. A developer writes a change, and the system automatically checks it, packages it, and prepares it for users.&lt;/p&gt;
&lt;p&gt;Before this approach became common, developers often worked alone for weeks. When they finally merged their work, everything clashed, and fixing it took days. CI/CD solves that by encouraging small, frequent updates that are verified automatically.&lt;/p&gt;
&lt;h3 id=&quot;understanding-continuous-integration&quot;&gt;Understanding Continuous Integration&lt;/h3&gt;
&lt;p&gt;Continuous integration, the “CI” part, is the habit of merging code into a shared repository often, sometimes several times a day. Each time someone pushes a change, an automated process builds the application and runs tests.&lt;/p&gt;
&lt;p&gt;If something breaks, the team knows within minutes rather than weeks. The developer who caused the problem still remembers what they changed, so the fix is quick. This is the first big reason people ask what is CI/CD and then decide to adopt it: it catches mistakes while they are still small and cheap to fix.&lt;/p&gt;
&lt;h3 id=&quot;understanding-continuous-delivery-and-deployment&quot;&gt;Understanding Continuous Delivery and Deployment&lt;/h3&gt;
&lt;p&gt;The “CD” has two common meanings, and they are easy to confuse.&lt;/p&gt;
&lt;p&gt;Continuous delivery means every change that passes testing is automatically prepared for release. A person still decides when to press the final button. Continuous deployment goes one step further, because passing code goes live without any manual approval.&lt;/p&gt;
&lt;p&gt;Which one is right depends on your team and your industry. A bank may prefer a human sign-off before each release, while a small content website might happily deploy automatically. Either way, the CI/CD idea is the same: remove slow, error-prone manual steps.&lt;/p&gt;
&lt;h3 id=&quot;how-a-ci-cd-pipeline-actually-works&quot;&gt;How a CI/CD Pipeline Actually Works&lt;/h3&gt;
&lt;p&gt;A pipeline is simply the sequence of automated steps that code travels through. While every company sets theirs up a little differently, the journey usually looks like this in practice.&lt;/p&gt;
&lt;p&gt;A developer commits code to a repository such as GitHub or GitLab. That commit triggers the pipeline, which first builds the application. Next, automated tests run, covering everything from tiny unit checks to broader integration tests. If the tests pass, the pipeline packages the software and sends it to a staging environment, where it can be reviewed in conditions close to the real thing. Finally, the release moves to production, either automatically or after approval.&lt;/p&gt;
&lt;p&gt;When someone asks me what is CI/CD in a practical sense, I describe it as a safety net. It does not stop you from making mistakes, but it makes sure mistakes are noticed early and do not reach customers.&lt;/p&gt;
&lt;h3 id=&quot;why-teams-rely-on-ci-cd&quot;&gt;Why Teams Rely on CI/CD&lt;/h3&gt;
&lt;p&gt;The benefits show up quickly once a pipeline is running smoothly. Releases become faster because machines handle repetitive work. Quality improves because every change faces the same tests, every time. Teams also gain confidence, since a deployment stops feeling like a high-stakes event.&lt;/p&gt;
&lt;p&gt;There is a human benefit too. Developers spend less time on tedious manual checks and more time on creative problem solving. Industry research, including the widely cited DORA reports from &lt;a rel=&quot;nofollow&quot; href=&quot;https://cloud.google.com/?utm_source=google&amp;amp;amp;utm_medium=cpc&amp;amp;amp;utm_campaign=Cloud-SS-DR-GCP-1713664-GCP-DR-APAC-IN-en-Google-BKWS-BRO-Infrastructure&amp;amp;amp;utm_content=c-Hybrid%20%7C%20BKWS%20-%20BRO%20%7C%20Txt%20-%20Infrastructure-Storage-Filestore-IN_en-&amp;amp;amp;utm_term=&amp;amp;amp;gclsrc=aw.ds&amp;amp;amp;gad_source=1&amp;amp;amp;gad_campaignid=8068803054&amp;amp;amp;gclid=EAIaIQobChMImLnd5oHNlwMV9xJECB0wTi-mEAAYASAAEgLNY_D_BwE&quot;&gt;Google Cloud&lt;/a&gt;, has consistently linked strong delivery practices with better team performance. That is a good reason to take what is CI/CD seriously rather than treating it as a passing trend.&lt;/p&gt;
&lt;h3 id=&quot;popular-tools-you-will-hear-about&quot;&gt;Popular Tools You Will Hear About&lt;/h3&gt;
&lt;p&gt;You do not need to master every tool, but a few names appear constantly. Jenkins is a long-standing open-source automation server. GitHub Actions and GitLab CI are built into popular code hosting platforms and are friendly for beginners. CircleCI and Azure DevOps are also widely used, and cloud providers offer their own pipeline services.&lt;/p&gt;
&lt;p&gt;The best choice is usually the one that fits where your code already lives. Start simple, and avoid overengineering your first setup.&lt;/p&gt;
&lt;h3 id=&quot;common-mistakes-beginners-make&quot;&gt;Common Mistakes Beginners Make&lt;/h3&gt;
&lt;p&gt;Many teams stumble in predictable ways. Some write too few tests, so the pipeline passes even when the software is broken. Others build pipelines so slow that developers stop waiting for them. A third common problem is ignoring failed builds, which defeats the entire purpose.&lt;/p&gt;
&lt;p&gt;A healthy pipeline should be fast, trustworthy, and treated as a shared responsibility. If a build fails, fixing it becomes the team’s top priority.&lt;/p&gt;
&lt;h3 id=&quot;how-to-get-started&quot;&gt;How to Get Started&lt;/h3&gt;
&lt;p&gt;If you are still wondering what is CI/CD and how to try it yourself, begin small. Pick a project, connect it to a free tool such as GitHub Actions, and automate just one thing, perhaps running your tests on every push. Once that works reliably, add packaging, then a staging deployment, and grow from there.&lt;/p&gt;
&lt;p&gt;Security matters as well. Keep passwords and API keys out of your code, store them in your tool’s secret manager, and review who can approve releases.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;So, what is CI/CD? It is a practical way of working that turns software delivery from a nerve-racking event into a routine, repeatable process. It rewards small changes, constant feedback, and automation of anything boring.&lt;/p&gt;
&lt;p&gt;You do not need a large team or an expensive setup to benefit. Start with a single automated test, learn from each failure, and improve step by step. Over time, you will wonder how you ever shipped software any other way.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32665/what-is-ci-cd-a-plain-english-guide-for-teams-shipping-software-daily</guid>
<pubDate>Tue, 27 Oct 2026 11:30:01 +0000</pubDate>
</item>
<item>
<title>The Night My Pipeline Rejected 43.8% of Everything — and Was Right</title>
<link>https://coderlegion.com/31505/the-night-my-pipeline-rejected-43-8-of-everything-and-was-right</link>
<description>&lt;p&gt;Last month, my automated software-ingestion pipeline did something terrifying: it rejected &lt;strong&gt;43.8% of all updates&lt;/strong&gt; in a single run. Forty-three point eight percent. My first instinct was that the pipeline was broken. It wasn't. It was the only thing in the room doing its job.&lt;/p&gt;
&lt;p&gt;I run an open software catalog. Every few days, an automated pipeline pulls new releases from vendors, GitHub, and F-Droid, verifies them, and publishes them. That night it pulled the fire alarm on nearly half the catalog &amp;mdash; two SHA-256 hash mismatches (Google Chrome and CCleaner, of all things) and twelve signature mismatches across apps like Obsidian, Tor, and Ventoy.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I learned fixing it, distilled into the checklist I now run before any binary goes live.&lt;/p&gt;
&lt;h2&gt;1. START WITH THE HASH &amp;mdash; BUT KNOW WHAT IT CAN&amp;rsquo;T TELL YOU&lt;/h2&gt;
&lt;p&gt;Every release I publish carries a SHA-256 checksum, recomputed at download time. It&amp;rsquo;s the cheapest integrity check in existence:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sha256sum installer.exe
# compare against the publisher's published value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the hash doesn&amp;rsquo;t match, stop. Don&amp;rsquo;t install it, don&amp;rsquo;t &amp;ldquo;try it anyway.&amp;rdquo; A mismatched hash means the file changed in transit &amp;mdash; or it isn&amp;rsquo;t the file the publisher shipped. That night, Chrome and CCleaner&amp;rsquo;s hashes didn&amp;rsquo;t match their expected values. The pipeline refused them, and rightly so: a hash mismatch is never a rounding error.&lt;/p&gt;
&lt;p&gt;But a hash answers only one question: &lt;em&gt;did this file change?&lt;/em&gt; It says nothing about &lt;em&gt;who made it&lt;/em&gt;. That&amp;rsquo;s what signatures are for.&lt;/p&gt;
&lt;h2&gt;2. VERIFY THE SIGNATURE, NOT JUST THE CHECKSUM&lt;/h2&gt;
&lt;p&gt;A checksum tells you the file is intact. A signature tells you it came from the right key. For Android packages, the tooling is free:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apksigner verify --print-certs app.apk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The critical step most people skip: check that the certificate &lt;strong&gt;fingerprint&lt;/strong&gt; matches the publisher&amp;rsquo;s known key. The first time you see a new fingerprint on a familiar app, treat it as suspicious until proven otherwise. For desktop software, GPG serves the same role:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --verify installer.exe.sig installer.exe&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern is identical everywhere: fetch the artifact, fetch the signature, verify against the publisher&amp;rsquo;s public key. And if a project ships binaries with no signatures at all? That&amp;rsquo;s a data point about the project &amp;mdash; not a reason to skip verification.&lt;/p&gt;
&lt;h2&gt;3. ENROLLMENT IS THE HALF OF VERIFICATION EVERYONE FORGETS&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the mistake that cost me a week. My pipeline verified every APK signature &lt;em&gt;correctly&lt;/em&gt; &amp;mdash; and still rejected twelve apps. The parser wasn&amp;rsquo;t broken. The cryptography was fine. The problem: &lt;strong&gt;I had never enrolled those signers in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Signature verification has two halves, and I was only doing one:&lt;/p&gt;
&lt;p&gt;1. Is this signature cryptographically valid? &lt;em&gt;(the tool answers this)&lt;/em&gt;&lt;br&gt;
2. Do I recognize this signer? &lt;em&gt;(only my allowlist answers this)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Twelve apps were signed by keys my system had simply never recorded &amp;mdash; new maintainers, rotated keys, corporate renames. My favorite: a publisher whose legal name changed by a single dot (&amp;ldquo;Dynalist Inc.&amp;rdquo; vs &amp;ldquo;Dynalist Inc&amp;rdquo;), which my strict string match treated as a stranger. The pipeline was doing its job; my trust store was incomplete.&lt;/p&gt;
&lt;p&gt;The fix is boring and essential. Keep an explicit registry of known-good signers per package, keyed by certificate fingerprint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# trust_store.json &amp;mdash; enrolled signers, one entry per package
{
  &quot;com.example.app&quot;: {
    &quot;fingerprints&quot;: [&quot;AB:CD:EF:12:34...&quot;],
    &quot;enrolled_on&quot;: &quot;2026-10-13&quot;,
    &quot;note&quot;: &quot;Key rotation announced in v4.2 changelog&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a new fingerprint appears, that&amp;rsquo;s an &lt;strong&gt;event&lt;/strong&gt;, not an error &amp;mdash; investigate it once, enroll it deliberately, and move on. Auto-trusting unknown signers defeats the entire purpose.&lt;/p&gt;
&lt;h2&gt;4. REJECT LOUDLY &amp;mdash; WITH A THRESHOLD YOU CHOSE IN ADVANCE&lt;/h2&gt;
&lt;p&gt;A verification system that silently accepts everything is decoration. Mine fails the entire ingestion run closed if more than 15% of updates get rejected. That number is deliberate: a sudden spike means either the upstream world changed (keys rotated, publishers restructured) or my pipeline broke. Either way, I want a human looking at it before anything ships.&lt;/p&gt;
&lt;p&gt;The exact threshold matters less than the principle: decide in advance what &amp;ldquo;too many failures&amp;rdquo; looks like, and make the pipeline &lt;strong&gt;stop&lt;/strong&gt; instead of shrugging. Quiet failures are how bad binaries reach users. My 43.8% night looked like a disaster and was actually the system working exactly as designed &amp;mdash; it just needed a human to enroll the new signers and re-run.&lt;/p&gt;
&lt;h2&gt;5. DECIDE IN ADVANCE WHAT TO DO WITH THE UNVERIFIABLE&lt;/h2&gt;
&lt;p&gt;Not everything is signable. Some legitimate tools ship unsigned binaries &amp;mdash; portable builds, indie utilities, projects too small for signing infrastructure. You have three honest options: reject them, allowlist them explicitly, or verify them another way (reproducible builds, publisher-published hashes fetched over HTTPS).&lt;/p&gt;
&lt;p&gt;I allowlist them &amp;mdash; explicitly, per binary, with a written reason. What I never do is let an unsigned binary through on a technicality. &amp;ldquo;The check didn&amp;rsquo;t apply&amp;rdquo; is not the same as &amp;ldquo;the check passed,&amp;rdquo; and conflating the two is how exceptions quietly become the rule. Ventoy&amp;rsquo;s unsigned binary, for example, sits in my allowlist with a note explaining why &amp;mdash; visible, reviewable, deliberate.&lt;/p&gt;
&lt;h2&gt;THE CHECKLIST&lt;/h2&gt;
&lt;p&gt;Before any binary goes live:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SHA-256 matches the publisher&amp;rsquo;s published value (recomputed at download, not trusted from a mirror)&lt;/li&gt;
&lt;li&gt;Signature verifies against the publisher&amp;rsquo;s known key&lt;/li&gt;
&lt;li&gt;Signer fingerprint is in the enrolled registry &amp;mdash; new fingerprints get investigated, never auto-trusted&lt;/li&gt;
&lt;li&gt;Rejection rate for the run is under threshold, or the whole run stops&lt;/li&gt;
&lt;li&gt;Unsigned artifacts are explicitly allowlisted with a written reason &amp;mdash; never silently passed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is exotic. It&amp;rsquo;s plumbing. But it&amp;rsquo;s the plumbing between a stranger&amp;rsquo;s server and your users&amp;rsquo; machines, and every piece of it earns its place the first time it catches something. Mine caught something on a random Tuesday night, rejected nearly half the catalog, and was right.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31505/the-night-my-pipeline-rejected-43-8-of-everything-and-was-right</guid>
<pubDate>Sat, 17 Oct 2026 09:46:03 +0000</pubDate>
</item>
<item>
<title>Learning `just`: A Practical Task Runner</title>
<link>https://coderlegion.com/30937/learning-just-a-practical-task-runner</link>
<description>&lt;p&gt;I've been learning &lt;code&gt;just&lt;/code&gt; recently.&lt;/p&gt;
&lt;p&gt;I came across it while setting up a Python project and looking for a simple way to collect all the commands I use during development.&lt;/p&gt;
&lt;p&gt;This started with something I got used to in Elixir.&lt;/p&gt;
&lt;p&gt;One of the things I really like about Elixir projects is that you can put the common development tasks behind simple commands/aliases:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mix format
mix test
mix ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don't have to remember every command that runs underneath. The project gives you a small set of commands and you use those.&lt;/p&gt;
&lt;p&gt;I wanted something similar for a Python project.&lt;/p&gt;
&lt;p&gt;For example, instead of remembering all the different checks I need to run, I wanted to be able to do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;just ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and let the project take care of the rest.&lt;/p&gt;
&lt;h3 id=&quot;my-first-thought-was-make&quot;&gt;My first thought was Make&lt;/h3&gt;
&lt;p&gt;Make was the obvious choice.&lt;/p&gt;
&lt;p&gt;I've used Make a lot, and it works. It's installed almost everywhere, most developers have seen a Makefile before, and there is a huge amount of existing knowledge around it.&lt;/p&gt;
&lt;p&gt;So I started there.&lt;/p&gt;
&lt;p&gt;The setup was pretty small:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;format:
	uv run ruff format --check .

lint:
	uv run ruff check .

typecheck:
	uv run mypy src/

test:
	uv run pytest --cov=src --cov-report=term-missing

ci: format lint typecheck test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gave me exactly what I wanted.&lt;/p&gt;
&lt;p&gt;I could run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;make format
make lint
make typecheck
make test
make ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing complicated.&lt;/p&gt;
&lt;p&gt;But while doing this, I kept seeing &lt;code&gt;just&lt;/code&gt; mentioned in newer projects.&lt;/p&gt;
&lt;p&gt;That made me curious.&lt;/p&gt;
&lt;p&gt;Not because I thought Make was bad. I still use it a lot, and I have no problem reaching for it.&lt;/p&gt;
&lt;p&gt;I just wondered what &lt;code&gt;just&lt;/code&gt; was doing differently.&lt;/p&gt;
&lt;p&gt;Was it basically Make with different syntax? Was it easier to use? Did it solve some of the small annoyances that come with Make?&lt;/p&gt;
&lt;p&gt;So I decided to rebuild the same thing in &lt;code&gt;just&lt;/code&gt; and see.&lt;/p&gt;
&lt;h3 id=&quot;the-just-version&quot;&gt;The &lt;code&gt;just&lt;/code&gt; version&lt;/h3&gt;
&lt;p&gt;The nice thing was that I didn't have to change any of the actual commands.&lt;/p&gt;
&lt;p&gt;I just changed the wrapper around them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-just&quot;&gt;format:
    uv run ruff format --check .

lint:
    uv run ruff check .

typecheck:
    uv run mypy src/

test:
    uv run pytest --cov=src --cov-report=term-missing

ci: format lint typecheck test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's pretty much the whole file.&lt;/p&gt;
&lt;p&gt;Now I can run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;just format
just lint
just typecheck
just test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or everything with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;just ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ci&lt;/code&gt; recipe depends on the other four recipes, so &lt;code&gt;just ci&lt;/code&gt; runs them in order and stops when one fails.&lt;/p&gt;
&lt;p&gt;For what I was trying to do, it felt very familiar.&lt;/p&gt;
&lt;p&gt;And that was probably the first thing I noticed. I didn't really have to learn a new way of thinking about the problem.&lt;/p&gt;
&lt;h3 id=&quot;a-few-things-i-liked-about-just&quot;&gt;A few things I liked about &lt;code&gt;just&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The basic syntax is very similar to Make.&lt;/p&gt;
&lt;p&gt;You have a recipe, optional dependencies, and then the commands to run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-just&quot;&gt;recipe_name: dependency1 dependency2
    command to run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But there are a few differences that make sense when you're using it mainly as a task runner.&lt;/p&gt;
&lt;h4 id=&quot;no-phony&quot;&gt;No &lt;code&gt;.PHONY&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;With Make, you often have something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-make&quot;&gt;.PHONY: format lint typecheck test ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's because Make is also a build system and cares about files and timestamps.&lt;/p&gt;
&lt;p&gt;For these development commands, I don't really need that behaviour.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;just&lt;/code&gt; doesn't use file timestamps to decide whether a recipe needs to run, so there is no &lt;code&gt;.PHONY&lt;/code&gt; declaration to maintain.&lt;/p&gt;
&lt;h4 id=&quot;no-tab-problem&quot;&gt;No tab problem&lt;/h4&gt;
&lt;p&gt;Then there is the classic Make issue where a command needs to be indented with a tab.&lt;/p&gt;
&lt;p&gt;I've definitely lost time to that one.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;just&lt;/code&gt;, you don't have that same tab requirement. You use normal indentation.&lt;/p&gt;
&lt;p&gt;It might seem like a small thing, but I like small improvements in tools I use.&lt;/p&gt;
&lt;h4 id=&quot;just---list&quot;&gt;&lt;code&gt;just --list&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This was another nice surprise.&lt;/p&gt;
&lt;p&gt;You can run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;just --list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and get a list of the recipes available in the project.&lt;/p&gt;
&lt;p&gt;You can add comments above recipes too:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-just&quot;&gt;# Format check
format:
    uv run ruff format --check .

# Run tests
test:
    uv run pytest --cov=src --cov-report=term-missing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those comments can then show up when listing the recipes.&lt;/p&gt;
&lt;p&gt;You can also make &lt;code&gt;just&lt;/code&gt; itself show the list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-just&quot;&gt;default:
    @just --list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So a developer can clone the project and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;just
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to see what is available.&lt;/p&gt;
&lt;p&gt;I like that because the task runner becomes a small piece of documentation.&lt;/p&gt;
&lt;h3 id=&quot;but-is-just-actually-better-than-make-&quot;&gt;But is &lt;code&gt;just&lt;/code&gt; actually better than Make?&lt;/h3&gt;
&lt;p&gt;I'm not sure that's the right question.&lt;/p&gt;
&lt;p&gt;Make is still a great choice in plenty of situations.&lt;/p&gt;
&lt;p&gt;If I'm working on a project that already has a Makefile, I'm not going to suggest replacing it with &lt;code&gt;just&lt;/code&gt; just because &lt;code&gt;just&lt;/code&gt; is newer.&lt;/p&gt;
&lt;p&gt;And if I'm writing something that needs to work in an environment where Make is already installed, that is a pretty good reason to use Make.&lt;/p&gt;
&lt;p&gt;The thing I found interesting about &lt;code&gt;just&lt;/code&gt; is that it feels more focused on the thing I was actually trying to do.&lt;/p&gt;
&lt;p&gt;I wasn't building anything based on file timestamps.&lt;/p&gt;
&lt;p&gt;I just wanted names for common commands and a way to combine them.&lt;/p&gt;
&lt;p&gt;For that use case, &lt;code&gt;just&lt;/code&gt; is pretty nice.&lt;/p&gt;
&lt;p&gt;But it also comes with a trade-off. Unlike Make, you probably have to install it first.&lt;/p&gt;
&lt;p&gt;So the comparison for me looks something like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Make         &lt;/th&gt;&lt;th&gt;just&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Already installed &lt;/td&gt;&lt;td&gt;Often        &lt;/td&gt;&lt;td&gt;Usually not&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Task runner       &lt;/td&gt;&lt;td&gt;Yes          &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Build system      &lt;/td&gt;&lt;td&gt;Yes          &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.PHONY&lt;/code&gt;          &lt;/td&gt;&lt;td&gt;Often needed &lt;/td&gt;&lt;td&gt;Not needed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tab requirement   &lt;/td&gt;&lt;td&gt;Yes          &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Task listing      &lt;/td&gt;&lt;td&gt;DIY          &lt;/td&gt;&lt;td&gt;Built in&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cross-platform    &lt;/td&gt;&lt;td&gt;Possible     &lt;/td&gt;&lt;td&gt;Designed for it&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Neither one wins across the board.&lt;/p&gt;
&lt;p&gt;Make has ubiquity.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;just&lt;/code&gt; has a nice developer experience.&lt;/p&gt;
&lt;p&gt;Which one makes more sense depends on the project.&lt;/p&gt;
&lt;h3 id=&quot;there-are-other-options-too&quot;&gt;There are other options too&lt;/h3&gt;
&lt;p&gt;Once I started looking into this, I found quite a few other tools doing roughly the same thing.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Task&lt;/code&gt; (go-task) was one I came across. It uses a YAML &lt;code&gt;Taskfile.yml&lt;/code&gt; and has some useful features around variables and templating.&lt;/p&gt;
&lt;p&gt;For Python, &lt;code&gt;poethepoet&lt;/code&gt; was another interesting option. It lets you put tasks in &lt;code&gt;pyproject.toml&lt;/code&gt;, which can be nice if you don't want another configuration file.&lt;/p&gt;
&lt;p&gt;And if you're working in Node, npm scripts already give you a similar setup through &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;They all solve a similar problem.&lt;/p&gt;
&lt;p&gt;You have a bunch of commands that developers need to run, and you give those commands names so people don't have to remember all the details.&lt;/p&gt;
&lt;p&gt;The differences are mostly around syntax, ecosystem, and how much functionality you want.&lt;/p&gt;
&lt;h3 id=&quot;so-am-i-switching-to-just-&quot;&gt;So, am I switching to &lt;code&gt;just&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;For everything? No.&lt;/p&gt;
&lt;p&gt;I'm still going to use Make.&lt;/p&gt;
&lt;p&gt;If I open a project and there is a Makefile, I'll use it. If I'm working on infrastructure where Make is already expected, I'll probably use it there too.&lt;/p&gt;
&lt;p&gt;But for projects I start myself, I can see myself reaching for &lt;code&gt;just&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The thing I liked most was how little there was to learn.&lt;/p&gt;
&lt;p&gt;My existing commands stayed exactly the same. I just put a small, readable interface around them.&lt;/p&gt;
&lt;p&gt;That brought me back to why I started looking at this in the first place.&lt;/p&gt;
&lt;p&gt;In Elixir, I'm used to having commands like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mix ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;that give the project a simple interface for common development tasks.&lt;/p&gt;
&lt;p&gt;With Python, I found myself wanting the same thing.&lt;/p&gt;
&lt;p&gt;Make was one way to do it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;just&lt;/code&gt; is another.&lt;/p&gt;
&lt;p&gt;I'm glad I spent the time learning it. Not because it replaced Make, but because now I know another tool that fits this particular problem really well.&lt;/p&gt;
&lt;p&gt;And sometimes that's enough of a reason to try something new.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30937/learning-just-a-practical-task-runner</guid>
<pubDate>Tue, 13 Oct 2026 12:45:10 +0000</pubDate>
</item>
<item>
<title>🧩 **Engineering a Machine-Validatable Contributor Evidence Workflow**</title>
<link>https://coderlegion.com/30305/engineering-a-machine-validatable-contributor-evidence-workflow</link>
<description>&lt;p&gt;🧩 &lt;strong&gt;Engineering a Machine-Validatable Contributor Evidence Workflow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I just merged a new infrastructure milestone into MyZubster:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PR #1572 — standardized contributor interoperability checkpoints&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The problem I wanted to solve was not just “how do we document a successful contributor test?”&lt;/p&gt;
&lt;p&gt;The real problem was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do we make technical evidence portable across contributors, technologies and verification paths without losing provenance or inflating claims?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Until now, MyZubster already had reproducible contributor checkpoints, but the structure was mostly expressed through Markdown, PR discussions and manually reviewed evidence.&lt;/p&gt;
&lt;p&gt;That works at small scale.&lt;/p&gt;
&lt;p&gt;It does not scale well when multiple contributors are producing different kinds of evidence.&lt;/p&gt;
&lt;p&gt;So I introduced a common machine-readable format:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;myzubster.interoperability-checkpoint.v1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Each checkpoint now describes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;contributor identity&lt;/li&gt;
&lt;li&gt;repository / PR&lt;/li&gt;
&lt;li&gt;immutable commit reference&lt;/li&gt;
&lt;li&gt;technical bridge type&lt;/li&gt;
&lt;li&gt;exact scope&lt;/li&gt;
&lt;li&gt;test environment&lt;/li&gt;
&lt;li&gt;reproduction procedure&lt;/li&gt;
&lt;li&gt;observed output&lt;/li&gt;
&lt;li&gt;evidence state&lt;/li&gt;
&lt;li&gt;limitations&lt;/li&gt;
&lt;li&gt;canonical provenance&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-the-limitations-field-matters&quot;&gt;Why the limitations field matters&lt;/h4&gt;
&lt;p&gt;One of the most important design decisions was making limitations mandatory.&lt;/p&gt;
&lt;p&gt;A technical checkpoint should not silently become a broader claim.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;A successful broker-to-agent test does not automatically mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;full decentralization&lt;/li&gt;
&lt;li&gt;direct P2P networking&lt;/li&gt;
&lt;li&gt;security certification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A research Knowledge Card marked &lt;code&gt;SUPPORTED&lt;/code&gt; should not become “scientifically validated.”&lt;/p&gt;
&lt;p&gt;A signed webhook regression should not become “payment settlement verified.”&lt;/p&gt;
&lt;p&gt;The schema is intentionally designed to preserve those boundaries.&lt;/p&gt;
&lt;h4 id=&quot;evidence-state-is-constrained-by-observation&quot;&gt;Evidence state is constrained by observation&lt;/h4&gt;
&lt;p&gt;The validator enforces an important rule:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TESTED&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;requires:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;observation.result = PASS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That sounds obvious, but encoding it into the schema means it is no longer just a documentation convention.&lt;/p&gt;
&lt;p&gt;It becomes something CI can reject.&lt;/p&gt;
&lt;h4 id=&quot;ajv-ci-integration&quot;&gt;AJV + CI integration&lt;/h4&gt;
&lt;p&gt;I added an AJV-based validator and connected it to the existing &lt;strong&gt;Continuous Evidence Gate&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So the pipeline now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Contributor work
        ↓
Scoped reproducible test
        ↓
Machine-readable checkpoint
        ↓
JSON Schema validation
        ↓
Continuous Evidence Gate
        ↓
Human review
        ↓
Canonical merge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important distinction:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;automation validates evidence structure; it does not replace engineering judgment.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;testing-the-format-across-different-contribution-types&quot;&gt;Testing the format across different contribution types&lt;/h4&gt;
&lt;p&gt;I did not want the first schema version to be designed around one specific contributor.&lt;/p&gt;
&lt;p&gt;So the initial dataset already includes multiple technical paths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N4K48 / Nicola&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Runtime interoperability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MyZubster VPS
→ authenticated HTTPS broker
→ contributor-controlled agent
→ local catalog
→ result returned to Bridge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;wasim-builds&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Security regression:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;unconfigured auth → rejected
missing credential → rejected
wrong credential → rejected
correct credential → accepted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;khongten124 / Open Period Care&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Semantic evidence bridge:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;public research evidence
→ structured ingestion
→ contributor-scoped retrieval
→ Knowledge Cards returned
→ original SUPPORTED state preserved
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Aming9303&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Signed webhook regression:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HMAC signing
→ stable delivery identity
→ unsigned request rejection
→ replay protection
→ stale-event rejection
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are very different systems.&lt;/p&gt;
&lt;p&gt;That is exactly why using the same evidence structure is useful.&lt;/p&gt;
&lt;h4 id=&quot;current-maturity&quot;&gt;Current maturity&lt;/h4&gt;
&lt;p&gt;I would describe the workflow as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;repeatable
→ auditable
→ multi-contributor
→ machine-validatable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next layer will be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;automated ingestion
→ Contributor Passport linkage
→ Knowledge Graph integration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while preserving the original evidence state and provenance.&lt;/p&gt;
&lt;h4 id=&quot;merge-result&quot;&gt;Merge result&lt;/h4&gt;
&lt;p&gt;PR #1572 passed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI – Test &amp;amp; Lint&lt;/li&gt;
&lt;li&gt;Security Audit&lt;/li&gt;
&lt;li&gt;Continuous Evidence Gate&lt;/li&gt;
&lt;li&gt;policy checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Canonical merge:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2be06b7e34f22744ecf81a9daef12f7ba11864be&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;What I like about this milestone is that it is not visually impressive.&lt;/p&gt;
&lt;p&gt;There is no shiny UI involved.&lt;/p&gt;
&lt;p&gt;But it improves something much more fundamental:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the trust model between contributors and the platform.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“This was tested.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the system can increasingly answer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Who tested it?&lt;br&gt;
Against which commit?&lt;br&gt;
In which environment?&lt;br&gt;
Using which procedure?&lt;br&gt;
What was observed?&lt;br&gt;
What does the evidence not prove?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the direction I want to keep pushing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evidence should be inspectable data, not just prose.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;coderlegion-softwareengineering-jsonschema-ajv-ci-devops-systemdesign-opensource-reproducibility-testing-developertools&quot;&gt;CoderLegion #SoftwareEngineering #JSONSchema #AJV #CI #DevOps #SystemDesign #OpenSource #Reproducibility #Testing #DeveloperTools&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30305/engineering-a-machine-validatable-contributor-evidence-workflow</guid>
<pubDate>Thu, 08 Oct 2026 13:50:59 +0000</pubDate>
</item>
<item>
<title>Internal Engineering Standard</title>
<link>https://coderlegion.com/11451/internal-engineering-standard</link>
<description>&lt;h2 id=&quot;dns-first-connectivity-for-distributed-systems&quot;&gt;DNS-First Connectivity for Distributed Systems&lt;/h2&gt;
&lt;h3 id=&quot;1-mandatory-rule&quot;&gt;1. Mandatory Rule&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;All services must be addressed by DNS/service name.
localhost, 127.0.0.1, or static IPs are forbidden outside single-process unit tests.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-scope&quot;&gt;2. Scope&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;Applies to:

Applications

ETL / MDM jobs

Kafka producers &amp;amp; consumers

Kafka Connect

CI/CD pipelines

Batch jobs, CronJobs, sidecars
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-environment-specific-naming-policy&quot;&gt;3. Environment-Specific Naming Policy&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;Local (Docker / Compose)

kafka:9092
zookeeper:2181
elasticsearch:9200
connect:8083
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;Kubernetes

kafka.&amp;lt;namespace&amp;gt;.svc.cluster.local:9092
elasticsearch.&amp;lt;namespace&amp;gt;.svc.cluster.local:9200
connect.&amp;lt;namespace&amp;gt;.svc.cluster.local:8083
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;rule-&quot;&gt;Rule:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;Service name stays constant.
Only the DNS suffix changes per environment.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-prohibited-patterns&quot;&gt;4. Prohibited Patterns&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;The following are architecture violations:

localhost:*

127.0.0.1:*

Hard-coded pod IPs

Node IPs

Hostnames tied to machines
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-configuration-requirements&quot;&gt;5. Configuration Requirements&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;All endpoints must be externalized (env vars, config files, Helm values)

No environment-specific logic in code

No string replacement during deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;6-ci-cd-enforcement&quot;&gt;6. CI/CD Enforcement&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;Pipelines must fail if:

localhost appears in configs

IP literals are detected

Environment-specific rewrites are required

Static scans are mandatory.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;7-kafka-specific-rules&quot;&gt;7. Kafka-Specific Rules&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;bootstrap.servers must use DNS

Kafka Connect REST must be addressed via service DNS

No broker IPs in configs

No advertised.listeners pointing to localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;8-kubernetes-requirements&quot;&gt;8. Kubernetes Requirements&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;All services exposed via Service objects

Stateful systems use headless services when required

Pods may never reference other pods directly by IP
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;9-reliability-amp-audit-rationale&quot;&gt;9. Reliability &amp;amp; Audit Rationale&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;This standard ensures:

Zero config rewrites across environments

Predictable scaling

Restart tolerance

CI/CD portability

MDM lineage stability

Audit-safe deployments
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;10-non-compliance-impact&quot;&gt;10. Non-Compliance Impact&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;Violations will cause:

Deployment rejection

CI/CD failure

Architecture review

Mandatory refactor before promotion
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;final-principle&quot;&gt;Final Principle&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-code&quot;&gt;DNS is not networking detail — it is system architecture.
If a component is not reachable by DNS, it is not production-ready.
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/11451/internal-engineering-standard</guid>
<pubDate>Wed, 11 Feb 2026 12:27:38 +0000</pubDate>
</item>
</channel>
</rss>