<?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: Pascal CESCATO</title>
    <description>The latest articles on DEV Community by Pascal CESCATO (@pascal_cescato_692b7a8a20).</description>
    <link>https://dev.to/pascal_cescato_692b7a8a20</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%2F3446021%2F2dab8c8f-80a4-4434-967f-5640bbf2050a.jpg</url>
      <title>DEV Community: Pascal CESCATO</title>
      <link>https://dev.to/pascal_cescato_692b7a8a20</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pascal_cescato_692b7a8a20"/>
    <language>en</language>
    <item>
      <title>CyberPanel's SSL Auto-Renewal Can Silently Fail — Here's the Fix</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Sat, 01 Aug 2026 02:44:11 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/cyberpanels-ssl-auto-renewal-can-silently-fail-heres-the-fix-5g1o</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/cyberpanels-ssl-auto-renewal-can-silently-fail-heres-the-fix-5g1o</guid>
      <description>&lt;p&gt;Last week I was doing a routine check on a CyberPanel server and noticed something that didn't add up. The panel's SSL status showed a clean &lt;strong&gt;"SSL Issued Successfully"&lt;/strong&gt;. Nothing to see, apparently. Except the certificate expiration date hadn't moved.&lt;/p&gt;

&lt;p&gt;The browser was still serving a certificate that was about to expire.&lt;/p&gt;

&lt;p&gt;That's the kind of mismatch that's easy to miss if you trust the panel's UI at face value — the message says success, the log (if you even check it) looks uneventful, and you move on. I didn't move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking what's actually served
&lt;/h2&gt;

&lt;p&gt;The panel tells you what it &lt;em&gt;thinks&lt;/em&gt; happened. What matters is what the server actually hands out to a browser. That's a one-liner away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt;  &lt;span class="se"&gt;\&lt;/span&gt;
domain.com:443 &lt;span class="nt"&gt;-servername&lt;/span&gt; domain.com &amp;lt;/dev/null 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
    | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-dates&lt;/span&gt;

&lt;span class="c"&gt;# outputs&lt;/span&gt;
&lt;span class="nv"&gt;notBefore&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Jul 31 22:59:26 2026 GMT
&lt;span class="nv"&gt;notAfter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Oct 29 22:59:26 2026 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Wait — that date range looked fine on its own. The real tell was comparing it against what the panel had just claimed to renew: the dates hadn't changed since before the "successful" renewal. Whatever &lt;code&gt;acme.sh&lt;/code&gt; had just done, it hadn't produced a new certificate that OpenLiteSpeed was actually serving.&lt;/p&gt;
&lt;h2&gt;
  
  
  Checking what acme.sh actually did
&lt;/h2&gt;

&lt;p&gt;CyberPanel doesn't implement ACME itself — it delegates to &lt;code&gt;acme.sh&lt;/code&gt; under the hood. So the next step was checking what &lt;code&gt;acme.sh&lt;/code&gt; had registered:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;acme.sh &lt;span class="nt"&gt;--list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's where the mismatch became clearer. The certificate authority listed for this domain wasn't Let's Encrypt production — it was the &lt;strong&gt;staging&lt;/strong&gt; CA. Staging certificates are useful for testing, but they're not production certificates trusted by browsers. In this case, the important part wasn't just that staging was configured — it was that the renewal workflow no longer matched the certificate actually served by OpenLiteSpeed. CyberPanel reported success because, from its point of view, &lt;code&gt;acme.sh&lt;/code&gt; had returned successfully. It just wasn't returning the certificate the site needed.&lt;/p&gt;

&lt;p&gt;I can't say for certain &lt;em&gt;how&lt;/em&gt; this CyberPanel installation's &lt;code&gt;acme.sh&lt;/code&gt; configuration ended up pointed at the staging CA — only that it had. Worth keeping in mind if you're chasing a similar mismatch: the fix doesn't require knowing the origin, just confirming the current state.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Once the mismatch was clear, the fix was mechanical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Force &lt;code&gt;acme.sh&lt;/code&gt; back onto the Let's Encrypt production CA.&lt;/li&gt;
&lt;li&gt;Drop the existing (staging) registration for the domain.&lt;/li&gt;
&lt;li&gt;Reissue the certificate from scratch, this time against production.&lt;/li&gt;
&lt;li&gt;Install it where CyberPanel and OpenLiteSpeed expect it: &lt;code&gt;/etc/letsencrypt/live/&amp;lt;domain&amp;gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reload OpenLiteSpeed so it picks up the new files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is exotic — it's exactly what &lt;code&gt;acme.sh&lt;/code&gt; is designed to do. The problem was never the tooling, it was that CyberPanel's reported status wasn't a reliable signal of what state the certificate was actually in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turning it into something reusable
&lt;/h2&gt;

&lt;p&gt;Fixing one server by hand is fine once. But when the same class of drift keeps showing up across CyberPanel releases, it stops being a one-off and starts being something worth scripting around. I wrote two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;renew-ssl.sh&lt;/code&gt;&lt;/strong&gt; — routine renewal, bypassing CyberPanel's scheduler entirely. It supports an auto mode (renews anything expiring within a configurable threshold, default 10 days), a single-domain mode, and a &lt;code&gt;--check&lt;/code&gt; mode that simulates the run and just reports what &lt;em&gt;would&lt;/em&gt; happen — useful for confirming a diagnosis like this one before touching anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fix-ssl.sh&lt;/code&gt;&lt;/strong&gt; — the heavier tool, for when a certificate is actually broken or stuck in staging. Given that the whole premise of this post is "don't trust the reported status, verify it," the script holds itself to the same standard on both ends:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before&lt;/strong&gt; touching anything, it writes a test file into the HTTP-01 challenge path and fetches it over plain HTTP, the same way Let's Encrypt will. If that fails, it stops before issuing anything — no point calling &lt;code&gt;acme.sh&lt;/code&gt; against a webroot that can't serve the challenge.&lt;/li&gt;
&lt;li&gt;It backs up the existing certificate and &lt;code&gt;acme.sh&lt;/code&gt; registration to a timestamped archive before removing anything, and refuses to proceed if the backup itself fails.&lt;/li&gt;
&lt;li&gt;It forces the production CA, clears the old registration, reissues via webroot, and installs the new certificate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After&lt;/strong&gt; reloading OpenLiteSpeed, it doesn't just assume the reload worked — it fetches the certificate actually served over HTTPS and compares its SHA256 fingerprint against the one just installed. A mismatch is reported as an error, with the exact restore command from the backup printed alongside it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are plain Bash, no dependencies beyond &lt;code&gt;acme.sh&lt;/code&gt; and &lt;code&gt;openssl&lt;/code&gt; (&lt;code&gt;fix-ssl.sh&lt;/code&gt; also uses &lt;code&gt;curl&lt;/code&gt; for the webroot check, though it degrades gracefully — with a warning — if &lt;code&gt;curl&lt;/code&gt; isn't available). Both are meant to be run as root since they touch &lt;code&gt;/etc/letsencrypt/&lt;/code&gt;, &lt;code&gt;/root/.acme.sh/&lt;/code&gt;, and the OpenLiteSpeed service.&lt;/p&gt;

&lt;p&gt;Source, with usage examples and a debugging section covering both commands above:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pcescato" rel="noopener noreferrer"&gt;
        pcescato
      &lt;/a&gt; / &lt;a href="https://github.com/pcescato/cyberpanel-ssl-renewal-scripts" rel="noopener noreferrer"&gt;
        cyberpanel-ssl-renewal-scripts
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Renew and repair SSL certificates on CyberPanel 2.x directly via acme.sh (bypasses the broken built-in renewal scheduler)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;CyberPanel SSL Renewal Scripts&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Bash scripts to renew and repair SSL certificates on CyberPanel 2.x directly via &lt;code&gt;acme.sh&lt;/code&gt;, bypassing CyberPanel's unreliable built-in renewal scheduler.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;CyberPanel can report a &lt;strong&gt;successful SSL renewal&lt;/strong&gt; while the certificate actually served remains close to expiration.&lt;/li&gt;
&lt;li&gt;In some cases &lt;code&gt;acme.sh&lt;/code&gt; is left configured with the &lt;strong&gt;Let's Encrypt staging&lt;/strong&gt; CA, so "renewals" produce certificates that browsers reject.&lt;/li&gt;
&lt;li&gt;These scripts renew — and repair — certificates &lt;strong&gt;directly via &lt;code&gt;acme.sh&lt;/code&gt;&lt;/strong&gt;, without going through the panel.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;CyberPanel 2.x&lt;/li&gt;
&lt;li&gt;OpenLiteSpeed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;acme.sh&lt;/code&gt; installed at &lt;code&gt;/root/.acme.sh/&lt;/code&gt; (CyberPanel default)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt; — used by &lt;code&gt;fix-ssl.sh&lt;/code&gt; to validate the webroot challenge path (optional: without it, that check is skipped with a warning)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root access&lt;/strong&gt; — the scripts write to &lt;code&gt;/etc/letsencrypt/live/&lt;/code&gt;, read &lt;code&gt;/root/.acme.sh/&lt;/code&gt;, and restart OpenLiteSpeed&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Scripts&lt;/h2&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;
&lt;code&gt;renew-ssl.sh&lt;/code&gt; — Renew certificates&lt;/h3&gt;

&lt;/div&gt;

&lt;p&gt;Renews certificates that were previously issued and installed with &lt;code&gt;acme.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It supports three modes:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;Auto mode (recommended):&lt;/strong&gt;…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pcescato/cyberpanel-ssl-renewal-scripts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Why this is worth checking on your own servers
&lt;/h2&gt;

&lt;p&gt;If you're running CyberPanel and haven't looked recently, it's worth running the &lt;code&gt;openssl s_client&lt;/code&gt; check against your actual domains — not because CyberPanel is broken in general, but because "the panel says success" and "the certificate is actually valid and current" are two different claims, and only one of them is verifiable from outside the panel.&lt;/p&gt;

&lt;p&gt;Have you seen similar ACME renewal drift on CyberPanel, OpenLiteSpeed, or other hosting panels? I'd be interested in comparing root causes — certificate automation failures are often harder to diagnose than certificate issuance itself.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>linux</category>
    </item>
    <item>
      <title>What Replacing Calendly Taught Me About Trusting Open Source</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Wed, 29 Jul 2026 00:22:42 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/what-replacing-calendly-taught-me-about-trusting-open-source-540a</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/what-replacing-calendly-taught-me-about-trusting-open-source-540a</guid>
      <description>&lt;p&gt;cal.com, Calendly, zcal... booking SaaS isn't short on options, and most of them are genuinely decent. Free tiers cover the basics for a lot of freelancers. The catch: you're the product (nothing's really free), and your customer data lives somewhere you don't fully control and can't fully audit.&lt;/p&gt;

&lt;p&gt;A dysfunction I ran into on another SaaS tool was the trigger. Trusting a third-party service by default, just because it's widely used and billed monthly, doesn't always hold up. That episode was enough to make me reconsider every external service this site was relying on for functionality that's actually simple to self-host — and the booking widget, running on Calendly, was one of them.&lt;/p&gt;

&lt;p&gt;Nothing wrong with Calendly specifically. It worked fine. But structural friction had been building regardless: a recurring subscription for something as simple as displaying open slots and recording a choice, a hard dependency on a third party for a component with nothing exceptional about it technically, and customization capped by whatever the vendor exposes in settings — no way to go further if a need falls outside that box. On top of that, an integration constraint that mattered more than any of the above: the site runs on Astro, generating lightweight static pages by design, specifically to avoid the weight of third-party scripts and dependencies — the exact opposite of what embedding a SaaS widget implies.&lt;/p&gt;

&lt;p&gt;So: could a self-hosted alternative match the experience, without the monthly bill and without handing a core commercial function (people booking a call with me) to an external vendor? This is the write-up of that search, the codebase audit that came out of it, and the production rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The landscape
&lt;/h2&gt;

&lt;p&gt;Four self-hosted candidates stood out as genuinely comparable — not just UI skins sitting on top of someone else's API, not just internal-scheduling tools with the public-facing UX as an afterthought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/dennisklappe/CloudMeet" rel="noopener noreferrer"&gt;CloudMeet&lt;/a&gt;&lt;/strong&gt; — Svelte + TypeScript, deployed on Cloudflare Pages/Workers/D1, free-tier friendly. MIT licensed. Clean booking UX. Single maintainer, ~490 stars, 37 commits — young, not enough track record to trust blind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/calcom/cal.diy" rel="noopener noreferrer"&gt;Cal.diy&lt;/a&gt;&lt;/strong&gt; — the community fork of cal.com's booking engine, spun up after cal.com closed-sourced their core product in April 2026, citing security risk from AI-assisted code scanning against their public repo. MIT licensed, maintained by former cal.com interns. Full scheduling engine, app-store integrations, Stripe/PayPal payment support carried over. Most feature-complete on paper. Also the youngest as an independent community project — their own docs still discourage production use without caveats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sametcn99/booking-calendar" rel="noopener noreferrer"&gt;booking-calendar&lt;/a&gt;&lt;/strong&gt; — React + TypeScript + Bun + SQLite, single-admin design, native bidirectional CalDAV sync instead of a Google/Outlook lock-in. Clean architecture (repository/service/entity separation via TypeORM). Lightweight, portable by design. Booking UI is a scrollable list of time slots — functional, but nowhere near the polish of a commercial scheduler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/alextselegidis/easyappointments" rel="noopener noreferrer"&gt;Easy!Appointments&lt;/a&gt;&lt;/strong&gt; — PHP/CodeIgniter + MySQL, ten years of active development, 3000+ stars, a paid tier that's existed for years. Most battle-tested of the four, and the one with the closest booking UX to Calendly itself: monthly calendar view, multi-step wizard.&lt;/p&gt;

&lt;p&gt;What I was actually optimizing for: framework-agnostic integration (no PHP running on the public site itself), booking UX quality, native GDPR consent handling, and enough production maturity to put in front of real prospects rather than an experimental project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrowing down
&lt;/h2&gt;

&lt;p&gt;CloudMeet and Cal.diy dropped out early, for the same underlying reason: not enough track record for production use, not a specific flaw found. CloudMeet's single-maintainer status and short commit history made it too much of a bet. Cal.diy's own documentation still hedges on production readiness three months post-launch. Whether either becomes a serious contender or stays a one-shot project is a question for later.&lt;/p&gt;

&lt;p&gt;Worth being honest about what that is: a decision made on reputation and project age, the exact shortcut this piece argues against later on. Auditing four codebases in the same depth as the two finalists below wasn't a realistic use of time, so CloudMeet and Cal.diy got a lighter pass — young-project heuristics instead of a source read. That's a real gap in the method, not just a caveat to mention in passing.&lt;/p&gt;

&lt;p&gt;That left booking-calendar and Easy!Appointments. And this is where the UX gap tipped it: booking-calendar's public booking page is a plain scrollable list of &lt;code&gt;Monday, February 23, 2026 at 08:00 AM&lt;/code&gt; rows — accurate, but visually miles from what Calendly conditioned people to expect. No month view, no staged flow, just text to scroll through.&lt;/p&gt;

&lt;p&gt;I briefly considered franken-stacking the two — CloudMeet's frontend on top of booking-calendar's CalDAV backend, or something along those lines. Not viable in practice: different runtimes (Cloudflare Workers/D1 vs Bun/SQLite), no shared API contract, no shared data model. Bolting two incompatible stacks together usually creates more work than picking one and adapting it. Went with separation of concerns instead: pick the tool with the better public-facing UX, and if native CalDAV sync becomes a real need later, that's a second, independent tool — not a merge.&lt;/p&gt;

&lt;p&gt;Easy!Appointments won on UX. Onto the part that actually mattered for a production decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit: a TOCTOU race condition, in both remaining candidates
&lt;/h2&gt;

&lt;p&gt;Before committing to either, I wanted to check one specific thing: what happens if two visitors click the same slot at nearly the same instant? Not a theoretical concern — it's the kind of bug that never shows up in solo development and blows up the day a booking link gets shared a bit wider (a newsletter blast, a LinkedIn post, a batch of new slots opening at a fixed time).&lt;/p&gt;

&lt;p&gt;This is a classic TOCTOU (time-of-check to time-of-use) race condition: the code checks that a slot is free, then writes the booking — and nothing stops a concurrent request from doing the exact same check in between, before either write lands. Without a lock spanning both the check and the write, two requests can both conclude "free" before either commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  booking-calendar
&lt;/h3&gt;

&lt;p&gt;TypeORM-based, repository pattern, otherwise clean separation of concerns. The overlap check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;hasOverlapInSlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;slotId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;startAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;endAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;EntityManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a.slot_id = :slotId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slotId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a.canceled_at IS NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a.status != 'rejected'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NOT (a.end_at &amp;lt;= :startAt OR a.start_at &amp;gt;= :endAt)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;startAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;endAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Called inside a transaction (&lt;code&gt;AppDataSource.transaction&lt;/code&gt;), but no &lt;code&gt;.setLock("pessimistic_write")&lt;/code&gt;, no exclusion constraint at the schema level. Under SQLite, this never surfaces: the engine serializes writers, one at a time, by design. It's a free safety net courtesy of the storage engine — not a guarantee the application code actually enforces. The project's own architecture explicitly anticipates a migration path to Postgres or MySQL via TypeORM's driver abstraction (&lt;code&gt;type: "sqlite"&lt;/code&gt; → &lt;code&gt;type: "postgres"&lt;/code&gt;, straightforward on paper). That's exactly the migration that removes the net: under &lt;code&gt;READ COMMITTED&lt;/code&gt; isolation, two concurrent transactions can each read "no overlap" before either one's &lt;code&gt;INSERT&lt;/code&gt; commits.&lt;/p&gt;

&lt;p&gt;Confirmed by reading the CalDAV sync layer too — a second, independent race, this one against the &lt;em&gt;external&lt;/em&gt; calendar rather than the local database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getCachedBusyIntervals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;BusyInterval&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CalDAVService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;busyIntervalCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start_at&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;endAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;startAt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;endAt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A process-wide static cache with a TTL, invalidated only &lt;em&gt;after&lt;/em&gt; a successful write. Two bookings arriving seconds apart, inside the cache window, can both read the same "free" snapshot before either has finished writing to the external calendar — the exact same TOCTOU shape, just with the external CalDAV server as the source of truth instead of the local DB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy!Appointments
&lt;/h3&gt;

&lt;p&gt;Ten years in production, 3000+ GitHub stars, a paid tier that's existed for years. The working hypothesis going in: more real-world traffic means more chances this exact class of bug already got hit and fixed. That hypothesis doesn't survive reading the code.&lt;/p&gt;

&lt;p&gt;The public booking flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check appointment availability before registering it to the database.&lt;/span&gt;
&lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id_users_provider'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;check_datetime_availability&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id_users_provider'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'requested_hour_is_unavailable'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... customer lookup/creation, GDPR consent records, Jitsi link generation ...&lt;/span&gt;

&lt;span class="nv"&gt;$appointment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;appointments_model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several unrelated operations sit between the check and the write — the race window here is wider than booking-calendar's, where check and insert at least shared a transaction. And the insert itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'book_datetime'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'create_datetime'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'update_datetime'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'hash'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'alnum'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'appointments'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Could not insert appointment.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;insert_id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No transaction, no lock, no unique constraint. The docblock on &lt;code&gt;check_datetime_availability()&lt;/code&gt; reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"It is possible that two or more customers select the same appointment date and time concurrently. The app won't allow this to happen."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Documented intent, not what the code actually does.&lt;/p&gt;

&lt;h3&gt;
  
  
  The interesting part
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Appointments_model&lt;/code&gt; contains a method that does the correct overlap check, with a properly built query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;has_provider_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$provider_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$start_datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$end_datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$exclude_appointment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'appointments'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id_users_provider'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$provider_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exclude_appointment_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id !='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$exclude_appointment_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Overlap: (existing_start &amp;lt; new_end) AND (existing_end &amp;gt; new_start)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'start_datetime &amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$end_datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'end_datetime &amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$start_datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group_end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;num_rows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's never called anywhere in the public booking flow. The correct primitive exists in the codebase; it's just not wired in where it would matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;Neither project is "more robust" than the other on this specific point — both share the same design gap, independent of relative maturity. Reputation, age, an established commercial tier: reasonable statistical priors, not proof. The only way to know whether an open-source project actually guards against this class of bug is to read the code doing the work, not the comment claiming it does.&lt;/p&gt;

&lt;p&gt;For what it's worth, the fix on the Easy!Appointments side is close to a drop-in — the correct primitive (&lt;code&gt;has_provider_conflict&lt;/code&gt;) already exists, it's a matter of wiring it in with a lock around check+write rather than designing a new one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$lock_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"provider_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$provider_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_booking"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT GET_LOCK(?, 10)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$lock_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;row&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"GET_LOCK(?, 10)"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Could not acquire booking lock.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;appointments_model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has_provider_conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id_users_provider'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'start_datetime'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'end_datetime'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'requested_hour_is_unavailable'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;$appointment_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;appointments_model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appointment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT RELEASE_LOCK(?)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$lock_name&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GET_LOCK&lt;/code&gt; rather than a schema-level exclusion constraint because MySQL has no equivalent to Postgres' &lt;code&gt;EXCLUDE USING gist&lt;/code&gt; — no declarative way to say "no two overlapping ranges for this provider" at the database level. The lock key is scoped per-provider (&lt;code&gt;provider_{id}_booking&lt;/code&gt;) rather than global, so two visitors booking with two &lt;em&gt;different&lt;/em&gt; providers at the same instant don't block each other for no reason. &lt;code&gt;RELEASE_LOCK&lt;/code&gt; sits in a &lt;code&gt;finally&lt;/code&gt; so a failed write doesn't leave the lock held until timeout.&lt;/p&gt;

&lt;p&gt;One dependency this fix quietly assumes: non-persistent database connections. &lt;code&gt;GET_LOCK&lt;/code&gt; is scoped to the MySQL session, not the PHP request — it lives and dies with the connection. That holds cleanly on a standard non-persistent connection (the CodeIgniter default), where each request gets its own connection and the lock disappears cleanly when it ends, even on an uncaught fatal. It stops holding if &lt;code&gt;pconnect&lt;/code&gt; is enabled and connections get reused across unrelated requests from a pool: &lt;code&gt;RELEASE_LOCK&lt;/code&gt; in the &lt;code&gt;finally&lt;/code&gt; might release a lock a &lt;em&gt;different&lt;/em&gt; request just acquired on the same recycled connection, or a lock could outlive the request that took it. Worth stating explicitly in the patch rather than assuming, since it's not something &lt;code&gt;has_provider_conflict()&lt;/code&gt; or the surrounding code makes obvious either way.&lt;/p&gt;

&lt;p&gt;This didn't end up shipping in my deployment — the LOCK/VERIFY/WRITE/UNLOCK skeleton above is close to production-ready, but I'd want load-test coverage on the &lt;code&gt;ANY_PROVIDER&lt;/code&gt; branch (where the code searches for &lt;em&gt;any&lt;/em&gt; available provider — the lock needs to span that search too, or two "any provider" requests can still land on the same provider/slot in parallel) before calling it done. Worth a PR upstream at some point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production rollout: a simple embed, an unexpected block
&lt;/h2&gt;

&lt;p&gt;The integration itself was straightforward: Easy!Appointments runs on its own subdomain, the contact page just drops an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; pointing at it. No PHP touches the public Astro site at all — that separation was the whole point.&lt;/p&gt;

&lt;p&gt;First test after deploying: the iframe wouldn't render. Console error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refused to display 'https://cal.example.com/' in a frame because it set
'X-Frame-Options' to 'sameorigin'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  First guess, wrong
&lt;/h3&gt;

&lt;p&gt;Given the server setup (a hosting panel with a reverse-proxy layer in front of the site), the obvious first suspect was that layer, not the app itself. Tried unsetting the header at the &lt;code&gt;.htaccess&lt;/code&gt; level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod_headers.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;unset&lt;/span&gt; X-Frame-Options
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Content-Security-Policy "frame-ancestors 'self' https://backstage.click"
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No effect. Header still showed up on &lt;code&gt;curl -I&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual cause
&lt;/h3&gt;

&lt;p&gt;Grepping the Easy!Appointments source turned it up directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;security_headers&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Frame-Options: SAMEORIGIN'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Frame-Options: SAMEORIGIN'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app sets the header itself, in PHP, at two separate points in its own bootstrap — a hardcoded anti-clickjacking default, sensible for the admin panel, applied indiscriminately to every route including the public booking page that's meant to be embedded elsewhere. &lt;code&gt;Header unset&lt;/code&gt; in &lt;code&gt;.htaccess&lt;/code&gt; runs at the web-server response-table level, ahead of the PHP process; a &lt;code&gt;header()&lt;/code&gt; call executed later by the script itself simply overrides it. The &lt;code&gt;.htaccess&lt;/code&gt; fix couldn't have worked against this, structurally, regardless of server (Apache, Nginx, OpenLiteSpeed) — PHP has the last word on its own headers as long as output hasn't started.&lt;/p&gt;

&lt;p&gt;The working fix patches both call sites, scoped to the booking controller only so the admin panel keeps its default protection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$CI&lt;/span&gt; &lt;span class="o"&gt;=&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;get_instance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$CI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'Booking'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Content-Security-Policy: frame-ancestors 'self' https://backstage.click"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-Frame-Options: SAMEORIGIN'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the instantiated controller class rather than parsing &lt;code&gt;$_SERVER['REQUEST_URI']&lt;/code&gt; — this install doesn't have clean URLs enabled (&lt;code&gt;index.php&lt;/code&gt; shows up in the booking URL), so a naive &lt;code&gt;strpos($uri, '/booking') === 0&lt;/code&gt; check would silently fail to match.&lt;/p&gt;

&lt;p&gt;One thing worth flagging for anyone patching the same two files: they're core application code, not a plugin layer. A future &lt;code&gt;git pull&lt;/code&gt; or a Docker image rebuild will silently overwrite this fix. Worth keeping it as a versioned patch file (&lt;code&gt;git diff&lt;/code&gt; before/after) to reapply after upgrades, rather than losing it the next time the container gets rebuilt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Customer data (name, email, meeting reason) staying on infrastructure I control instead of a third party's, and the site's initial page weight untouched — no third-party script added for this one feature. Cost wasn't really the driver here; free tiers cover the basics for a lot of freelancers, mine included. What I was buying back wasn't a subscription fee, it was the part where a vendor decides what "the basics" are, and where my prospects' contact details end up.&lt;/p&gt;

&lt;p&gt;Worth noting: which of the four tools actually fits depends entirely on what a given business needs, not on which one "won" here. Easy!Appointments, for instance, is also a solid self-hosted alternative to Bookly for anyone running WordPress and looking to drop a booking plugin subscription — different starting point, same underlying question.&lt;/p&gt;

&lt;p&gt;But the tool swap itself isn't really the point. This is one instance of a pattern I keep coming back to: default to self-hosted where it's reasonable, and don't let "widely used" or "ten years old" or "has a paid tier" stand in for actually checking. Reputation is a prior, not a verdict. The Easy!Appointments audit is the clearest example in this piece — a decade of production traffic, a commercial offering, thousands of stars, and a race condition sitting in the exact code path that mattered most, with the correct fix already written elsewhere in the codebase and simply never called. Maturity didn't catch it. Reading the code did.&lt;/p&gt;

&lt;p&gt;Here's the part worth sitting with, though: the fix never shipped on my own deployment either. It's sketched out above, unfinished, blocked on load-testing I haven't done. Which means the instance running this site's booking page right now is, as far as I know, still exposed to the exact TOCTOU window this whole piece is about. Self-hosting bought me visibility into that gap — Calendly would have hidden it behind a vendor's SLA and I'd have had no way to know either way. It didn't buy me the fix. That's a separate piece of work, and skipping it doesn't get excused by having found the bug in the first place. Self-hosting gets you control. Security still has to be built, on your own time, by someone — and until that patch actually lands, that someone is a task on my list, not a claim in this article.&lt;/p&gt;

&lt;p&gt;Neither was this the fastest of the four options, or the most obvious. It took a real comparison between projects, a debugging detour once in production, and time spent reading source instead of trusting a README. That's the actual cost of running your own infrastructure instead of renting someone else's.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Eighty Percent Done Is Not a Real Number</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Wed, 15 Jul 2026 12:51:00 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/eighty-percent-done-is-not-a-real-number-54nf</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/eighty-percent-done-is-not-a-real-number-54nf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one, and why now
&lt;/h2&gt;

&lt;p&gt;I always have several projects running in parallel — a maintenance CRM for tradespeople, a SaaS platform for restaurants, an automated newsletter, a management tool for training organizations… Except for that last one, none of them was ready to sell. And I have a whole pile of projects left "as is," untouched for months. An SEO scanner, a document analyzer, a mailbox scanner, a SQL audit tool, and others.&lt;/p&gt;

&lt;p&gt;Brainstorming session with Claude, my favorite AI assistant — we went through my repos: the CRM for heating tradespeople, the restaurant platform, the bilingual newsletter, and this SQL audit folder, several months old. The point of the session was to decide. I wanted to finish something quickly and start promoting it. Each project had a state of progress, a level of uncertainty, a distance to something sellable.&lt;/p&gt;

&lt;p&gt;The CRM still needs weeks of development — just for the features I know are essential — and field feedback from real tradespeople to validate the workflows. The restaurant platform was more of an architecture bet than a finished product. The newsletter has no client waiting for it.&lt;/p&gt;

&lt;p&gt;The SQL audit, though, looked done. Two &lt;code&gt;.sql&lt;/code&gt; files, a &lt;code&gt;UNION ALL&lt;/code&gt; of ten &lt;code&gt;SELECT&lt;/code&gt; statements per engine, a bash script pushing that to &lt;code&gt;psql&lt;/code&gt; or &lt;code&gt;mysql&lt;/code&gt; and converting to JSON, a PDF generator for a professional presentation. But I doubt I'd ever actually run it.&lt;/p&gt;

&lt;p&gt;It was a forgotten project. A PoC — it looked like a finished application, it wasn't one.&lt;/p&gt;

&lt;p&gt;The apparent distance to a sellable service looked like the shortest on the whole list: no UI needed, no architecture to rethink, just a script to test, improve, and stabilize. It was this single head start — more than any technical interest in the project — that tipped the scale. Its illusory proximity to the finish line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PoC that was supposed to work
&lt;/h2&gt;

&lt;p&gt;Before touching a single real database, I asked four different models for a code review — DeepSeek, Big Pickle, GLM, Qwen. Each produced a detailed report: bugs, dependencies, a phantom call to an LLM for the whole write-up section. Useful. But all four converged on the same blind spot:&lt;/p&gt;

&lt;p&gt;None of the four had tried running the script against a MySQL or PostgreSQL database.&lt;/p&gt;

&lt;p&gt;They were reading SQL. Not its behavior. And that blind spot, shared by four independent models, confirmed exactly the wrong conclusion: that the project was closer to done than it actually was.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the first real database showed in three lines, and what it cost the schedule
&lt;/h2&gt;

&lt;p&gt;First run against a vanilla PostgreSQL 16, without the &lt;code&gt;pg_stat_statements&lt;/code&gt; extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"metric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"psql:...audit_all_postgres.sql:193: ERROR: relation &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;pg_stat_statements&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; does not exist"&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three lines. Nothing else. A single query on a missing extension had killed the entire &lt;code&gt;UNION ALL&lt;/code&gt; — the other nine metrics never got a chance to run.&lt;/p&gt;

&lt;p&gt;The starting estimate — "this is the closest-to-finished project" — had just taken a hit. Not fatal, but real: what was supposed to be a wrap-up session turned into a series of discovery sessions. The good news, in hindsight, is that the remaining distance was still shorter than for the other two projects in the running. The bad news is that it had nothing to do with the distance estimated at the start.&lt;/p&gt;

&lt;p&gt;The same pattern repeated on every database tested: a missing system table or an insufficient privilege would kill the entire script, instead of just skipping the one affected metric. &lt;code&gt;pg_authid&lt;/code&gt; inaccessible without superuser, &lt;code&gt;mysql.user&lt;/code&gt; without the right GRANT, &lt;code&gt;column_statistics&lt;/code&gt; missing on MariaDB — same symptom every time.&lt;/p&gt;

&lt;p&gt;Fix, every time: check access before querying, return &lt;code&gt;UNAVAILABLE: &amp;lt;reason&amp;gt;&lt;/code&gt; on failure, let the rest of the script continue. Simple in theory.&lt;/p&gt;
&lt;h3&gt;
  
  
  The trap I didn't know about
&lt;/h3&gt;

&lt;p&gt;One of the fixes looked trivial:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_extension&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pg_stat_statements'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
     &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_statements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It looks correct. It isn't.&lt;/p&gt;

&lt;p&gt;Caught in production, not in review: PostgreSQL validates the existence of &lt;code&gt;pg_stat_statements&lt;/code&gt; at query-parsing time, not when the &lt;code&gt;CASE&lt;/code&gt; branch actually executes. The conditional check protects nothing — the query fails before it even knows which branch would be taken.&lt;/p&gt;

&lt;p&gt;That logic had to move out of static SQL and into the bash script, with a separate, upfront detection query. A &lt;code&gt;CASE&lt;/code&gt; that was syntactically flawless and semantically useless. None of the four reviews had caught it — it took a real engine to make it fail.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug that had nothing to do with a privilege
&lt;/h2&gt;

&lt;p&gt;The longest one to isolate: &lt;code&gt;roles_without_password&lt;/code&gt; kept coming back &lt;code&gt;UNAVAILABLE&lt;/code&gt; despite confirmed GRANTs, twice, on two different accounts, &lt;code&gt;SHOW GRANTS&lt;/code&gt; output in hand.&lt;/p&gt;

&lt;p&gt;It wasn't a permissions problem. It was a string-formatting problem. The check compared &lt;code&gt;GRANTEE&lt;/code&gt; — as stored in &lt;code&gt;information_schema.SCHEMA_PRIVILEGES&lt;/code&gt;, formatted &lt;code&gt;'user'@'host'&lt;/code&gt;, two pairs of quotes — against a string built with &lt;code&gt;CONCAT("'", USER(), "'")&lt;/code&gt;, which produces &lt;code&gt;'user@host'&lt;/code&gt;, a single pair of quotes around the whole thing. The two strings could never match, and the failure looked exactly like the thing you fear most in an audit tool: a false negative on security.&lt;/p&gt;

&lt;p&gt;Second trap, dumber still: the MySQL script never asked the user for a database name, unlike the PostgreSQL flow, which had that prompt from the start. With no database selected, &lt;code&gt;DATABASE()&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt;, and any &lt;code&gt;TABLE_SCHEMA = DATABASE()&lt;/code&gt; condition matches nothing at all.&lt;/p&gt;

&lt;p&gt;Result: &lt;code&gt;bloated_tables&lt;/code&gt; came back as 0 on a table whose bloat had been independently measured and confirmed at 23% outside the script.&lt;/p&gt;

&lt;p&gt;Zero didn't mean "no bloat." Zero meant "no rows found, because the question asked didn't correspond to anything."&lt;/p&gt;

&lt;p&gt;That's the kind of error an audit tool must never produce silently: a reassuring zero that measured nothing. This was no longer a question of distance to the finish line. It was a question of direction — I thought I was heading toward a finished script, I was heading toward ground I had never actually seen.&lt;/p&gt;
&lt;h2&gt;
  
  
  A metric that didn't measure what its name promised
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;buffer_pool_ratio&lt;/code&gt; — &lt;code&gt;innodb_buffer_pool_size / max_connections&lt;/code&gt; — came back as 1,789,569.7067 on a real database. A ratio has no business exceeding 1.&lt;/p&gt;

&lt;p&gt;The calculation had no bug; it simply never made sense. Dividing a memory size in bytes by a theoretical maximum connection count doesn't produce any intelligible unit — it's neither a ratio, nor a per-connection allocation, nor a saturation indicator. Four code reviews had nothing to say about it, because nothing about it was syntactically wrong.&lt;/p&gt;

&lt;p&gt;Replaced by the buffer pool hit rate — &lt;code&gt;1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)&lt;/code&gt; — which measures something real: the proportion of reads served from memory cache rather than disk. On the same database: 99.75%. A number that means something.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ten metrics didn't cover everything — not even the essentials
&lt;/h2&gt;

&lt;p&gt;Once the pipeline was stable on the original ten metrics, the report generated against n8n's production database was coherent — ten measurements, one score, nothing inconsistent in the results. It was in submitting that report to another LLM, for a second look, that the gap showed up: nothing in those ten metrics measured connection encryption.&lt;/p&gt;

&lt;p&gt;It wasn't a test that revealed the hole. It was a re-read. The LLM proposed exactly one additional metric — &lt;code&gt;ssl_enforced&lt;/code&gt; — and adding it was enough to change the priority order of the risk matrix and the final score. Once &lt;code&gt;ssl_enforced&lt;/code&gt; was in, the lack of encryption on the n8n database jumped to the top of the risk matrix, ahead of everything the original ten metrics had found.&lt;/p&gt;

&lt;p&gt;Three more followed, separately, proposed by Kimi from a broader list of candidates ranked by ease of implementation. &lt;code&gt;connection_count_ratio&lt;/code&gt;, &lt;code&gt;unused_indexes&lt;/code&gt;, &lt;code&gt;long_running_transactions&lt;/code&gt; were kept precisely because they fit the existing model without complicating it — one query, one value, the same defensive pattern as the rest of the script. Other ideas from the same list were dropped: &lt;code&gt;disk_usage_growth_rate&lt;/code&gt; would have required historical snapshots, changing the nature of a tool built for point-in-time audits; &lt;code&gt;table_count&lt;/code&gt; and &lt;code&gt;database_size&lt;/code&gt; were just display context, not a risk signal.&lt;/p&gt;

&lt;p&gt;None of the four new metrics is complex to collect — one query each, on both engines. But none was in the original list, because that list had been built around performance and schema hygiene, not exposure surface. It took an outside eye re-reading the result, not the code, to make that gap visible.&lt;/p&gt;

&lt;p&gt;A third pass, this time by Mistral, focused on how the report was presented rather than on its content, and raised two separate points: &lt;code&gt;duplicate_primary_keys = 0&lt;/code&gt; — a positive result — stayed buried in a prose paragraph in the middle of the overall assessment, never showing up in a proper, systematic checklist table covering every metric, including the ones with no issue. And the penalties, already grouped by category internally in the score calculation, were never exposed separately in the final report. Two last metrics followed from that pass — &lt;code&gt;duplicate_indexes&lt;/code&gt;, &lt;code&gt;large_tables_count&lt;/code&gt; — kept after checking they didn't duplicate anything already there.&lt;/p&gt;
&lt;h2&gt;
  
  
  The count
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Metrics per engine&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior on missing privilege&lt;/td&gt;
&lt;td&gt;entire script stopped&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;UNAVAILABLE: &amp;lt;reason&amp;gt;&lt;/code&gt;, the rest continues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scoring&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;deterministic, broken down by category (security / performance / integrity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL/TLS detection&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;yes — surfaced the #1 risk on a production database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Report languages&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;multilingual, stable section identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code reviews before the first real run&lt;/td&gt;
&lt;td&gt;4 models&lt;/td&gt;
&lt;td&gt;4 models, 0 behavioral bugs found&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  The coda: debugging the report that describes the bugs
&lt;/h2&gt;

&lt;p&gt;One last, almost ironic detail: at final-render time, the colored dots in the Risk Matrix — red/orange/gray by score — stopped showing up in the PDF. Copilot never found the cause. It churned for three hours, burned through more than $20 in credits on this one task (with Haiku 4.5) — still no colored dot. Big Pickle, in OpenCode, fixed it in under a minute, for free: every one of Copilot's hypotheses was wrong. The only problem was the column it was trying to insert the dot into.&lt;/p&gt;

&lt;p&gt;But the real lesson wasn't in the fix — it was in the method: don't attempt a third blind implementation, check and re-read the actual generated code instead of guessing. Same principle as the rest of this project — believe nothing until it has run, once, in a real environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  What determinism doesn't cover
&lt;/h2&gt;

&lt;p&gt;Two versions of the same report, on the same database, generated by two different LLMs for the narrative part — the global score, the category scores, the risk matrix: identical to the decimal point, because those numbers never come out of the LLM. That's exactly what deterministic scoring is supposed to guarantee: the model writes, it doesn't grade.&lt;/p&gt;

&lt;p&gt;But one of the two reports cited SOC 2 in the business impact of a recommendation. French database, French LLM (Mistral Large), French report — SOC 2 had no business being there. The score hadn't lied. The sentence around it had — but not by pure invention. Mistral didn't fabricate SOC 2 out of nowhere: it's a real framework, consistent with the report's consulting tone, correctly defined in itself. The problem isn't that the model hallucinated a concept — it's that it applied it to a hypothetical place it didn't actually know. A misapplication, not an invention.&lt;/p&gt;

&lt;p&gt;Same family of bug as &lt;code&gt;DATABASE()&lt;/code&gt; returning &lt;code&gt;NULL&lt;/code&gt; on the MySQL side: a field never asked for, silently filled in with whatever seemed plausible. Except this time it wasn't a sloppy query — it was a prompt that never anticipated it might be missing a piece of information as central as the client's country. Except the country doesn't actually matter that much: weak security is weak security, whether you're subject to SOC 2, GDPR, ISO 27001, or any other framework. Deterministic scoring closes the door to improvisation on the numbers. It says nothing about improvisation in the sentence explaining those numbers — and nowhere, in the sixteen metrics or in the generation prompt, does anything ever ask what country the audited database is in. The two numbers stayed identical between the two reports. Only the sentence built on top of them was free to be wrong, fluently, without ever looking wrong.&lt;/p&gt;

&lt;p&gt;That's fixed now: a jurisdiction flag precedes every report generation — &lt;code&gt;--jurisdiction FR&lt;/code&gt;, &lt;code&gt;--jurisdiction US&lt;/code&gt;, or any other ISO 3166 value depending on the client — and the narration prompt is now explicitly forbidden from naming a compliance framework that jurisdiction doesn't justify.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it looks like now
&lt;/h2&gt;

&lt;p&gt;What's left, once all of that is behind it: a tool you run from the command line against a MariaDB or PostgreSQL database, that finishes in a few minutes, and produces a complete PDF report — global score, risk matrix, prioritized recommendations, technical appendix — without needing to babysit the run or guess whether the result can be trusted. That object, not the list of bugs that led to it, is what's standing today here:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dbgrade.tech/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdbgrade.tech%2Fimages%2Fog-image.jpg" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dbgrade.tech/" rel="noopener noreferrer" class="c-link"&gt;
            dbgrade — Independent PostgreSQL &amp;amp; MariaDB/MySQL Audits - Security, integrity, performance — scored and explained
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Independent SQL audits for PostgreSQL and MariaDB/MySQL. 16 security, integrity and performance metrics, scored, with a PDF report you can act on — even if you don't read SQL.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdbgrade.tech%2Fimages%2Ffavicons%2Ffavicon-96x96.png" width="96" height="96"&gt;
          dbgrade.tech
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Sixteen metrics per engine, up from ten at the start. Deterministic scoring, broken down by category. Multilingual support where section titles keep a stable internal identifier while their label displays translated. An appendix that names the affected tables and constraints, not just their count.&lt;/p&gt;

&lt;p&gt;You can see what it looks like on SlideShare:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://fr.slideshare.net/slideshow/comprehensive-postgresql-database-audit-report-and-security-analysis/288580887" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;fr.slideshare.net&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;None of this was planned at the start. The two-file SQL PoC did exactly what it said it would, as long as no one ran it anywhere other than the imagination of the person who wrote it (yes, that was me, but let's not dwell on it).&lt;/p&gt;

&lt;p&gt;This project was picked because it looked closest to the finish line. It was, in the sense that the remaining distance ended up being covered faster than for the other projects waiting in line. But that proximity, like &lt;code&gt;buffer_pool_ratio&lt;/code&gt;, had never actually been verified — only estimated, from code that compiled and hadn't crashed yet. It kept correcting itself with every bug found, right up to the last one, SOC 2, discovered after everything else already looked finished.&lt;/p&gt;

&lt;p&gt;A script that never crashes proves nothing. &lt;code&gt;buffer_pool_ratio&lt;/code&gt; never failed anything — it ran, it produced a number, and that number meant nothing. No amount of re-running would have shown that: it took comparing the result against a measurement taken elsewhere, by another method, to see the calculation was hollow. Same story for &lt;code&gt;bloated_tables&lt;/code&gt; returning 0 on a table whose bloat was confirmed at 23% outside the script — a zero that runs without error isn't a correct zero. And the same story, further upstream, for "this project is the closest to done": an estimate that held up right until someone checked it, and not a moment longer.&lt;/p&gt;

&lt;p&gt;The difference between a tool that works on paper and one that holds up against a real database isn't the sophistication of the code, and it isn't the number of times you managed to make it crash either. It's whether every result it produces — including the estimate that justified starting the project in the first place — corresponds to something actually verified, not merely plausible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical sidebar — tools and method
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Initial code review (before any real run):&lt;/strong&gt; DeepSeek, Big Pickle, GLM, Qwen — four models, four reports, one shared blind spot: no access to a real database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engines tested:&lt;/strong&gt; PostgreSQL 16 (vanilla, then with extensions), MariaDB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bugs found only under real conditions:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;pg_extension&lt;/code&gt; validated at parse time, not when the &lt;code&gt;CASE&lt;/code&gt; branch executes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GRANTEE&lt;/code&gt; format mismatch between &lt;code&gt;information_schema&lt;/code&gt; and &lt;code&gt;CONCAT(USER())&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DATABASE()&lt;/code&gt; returning &lt;code&gt;NULL&lt;/code&gt; for lack of a database-selection prompt on the MySQL side&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buffer_pool_ratio&lt;/code&gt; metric with no basis (a division with no real relationship)&lt;/li&gt;
&lt;li&gt;Total absence of SSL/TLS detection from the original metric list&lt;/li&gt;
&lt;li&gt;A compliance framework (SOC 2) cited without any jurisdiction provided or verified&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Report generation:&lt;/strong&gt; Markdown → PDF via WeasyPrint, deterministic scoring kept separate from LLM narration, risk dots in pure CSS (&lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; + &lt;code&gt;border-radius&lt;/code&gt;), a 3-level palette (red/orange/gray — never green, because no line in the Risk Matrix is good news).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug method retained:&lt;/strong&gt; before retrying an implementation, check the code and what it actually produces, rather than attempting a blind fix based on a description of the symptom.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Unboxable in Tech: The Evidence Locker</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Thu, 09 Jul 2026 14:35:00 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/unboxable-in-tech-the-evidence-locker-20n4</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/unboxable-in-tech-the-evidence-locker-20n4</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/pascal_cescato_692b7a8a20/unboxable-in-tech-2knm"&gt;Eleven exhibits&lt;/a&gt;, last time. A career that kept refusing to fit inside a single box — trainer, restaurant owner, postal worker, developer, school aide, developer again — and a closing question I didn't have an answer to: whose problem was that, mine or the box's.&lt;/p&gt;

&lt;p&gt;I still don't have a clean answer. But I didn't stop while I was waiting for one. The same refusal to fit a category, applied to code instead of a resume, produced eight more exhibits. Here's what came out of that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 12 — The Graph
&lt;/h2&gt;

&lt;p&gt;Exhibit 11 ends with me still standing in a classroom, four days a week, helping a kid who isn't mine focus on work that isn't mine either. That's not a metaphor. That's the actual schedule.&lt;/p&gt;

&lt;p&gt;The other three days, I build.&lt;/p&gt;

&lt;p&gt;At some point I tried to explain this to a recruiter using a normal CV — the linear kind, one line per job, chronological, tidy. It read like someone who couldn't hold a job. Trainer, restaurant owner, postal worker, school aide, developer, back to developer, always developer underneath — but a timeline doesn't show "underneath." It only shows sequence.&lt;/p&gt;

&lt;p&gt;So I stopped writing a timeline and built a graph instead. Nodes for skills, nodes for roles, edges for what actually connects them — the same instinct that made me question a CMS or an ORM, applied to my own career. Turns out the incoherent-looking CV was a rendering problem, not a content problem. The data was fine. The visualization was lying.&lt;/p&gt;

&lt;p&gt;I don't know yet if a graph reads better to a hiring manager than eleven bullet points. I know it reads truer to me.&lt;/p&gt;

&lt;p&gt;The classroom aide job covers part of the month. The graph is what I actually am. I'm still working out how to make those two facts stop contradicting each other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(The full breakdown — nodes, edges, why a graph beats a timeline — is in &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/beyond-the-linear-cv-3fik"&gt;Beyond the Linear CV&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the same instinct, aimed at code instead of a career.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 13 — AJC Bridge
&lt;/h2&gt;

&lt;p&gt;I wanted to keep writing on WordPress. Not because it's good — because it's the interface I already know, the one where the friction is zero. But I didn't want the output to live only on WordPress hosting, with WordPress's weight, for an audience that doesn't care what CMS produced the page.&lt;/p&gt;

&lt;p&gt;Not a migration. A bridge.&lt;/p&gt;

&lt;p&gt;So I built one. A plugin that hooks into WordPress's publish action and pushes the content out to Hugo, Astro, or Jekyll — whichever the destination site runs. Write once, in the tool I already know, ship everywhere the audience actually reads.&lt;/p&gt;

&lt;p&gt;The interesting part wasn't the code. WordPress publishing to a static site generator is a solved problem in a dozen half-working ways. The interesting part was resisting the obvious move: dropping WordPress entirely and calling it progress.&lt;/p&gt;

&lt;p&gt;It wasn't the problem. It was infrastructure I was fluent in, carrying content I cared about, slowed down by a hosting model that didn't match what I actually needed. The enemy people usually name — "WordPress is bloated," "just use a static site" — was never the real constraint. The constraint was the idea that fluency doesn't count, that the "modern" tool always wins by default.&lt;/p&gt;

&lt;p&gt;I entered it in a GitHub Copilot CLI challenge. Top 7%, runner-up, top 25 out of 400+. Not because the idea was original — because most entries solved a technical problem and this one kept a personal workflow instead of throwing it away.&lt;/p&gt;

&lt;p&gt;Same instinct as the graph, pointed at my own publishing habits instead of my own career: don't ask what's fashionable to replace. Ask what's actually broken.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Full writeup: &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/actually-static-when-wordpress-stops-being-the-enemy-37h5"&gt;Actually Static: When WordPress Stops Being the Enemy&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 14 — The Memory dev.to Doesn't Give You
&lt;/h2&gt;

&lt;p&gt;dev.to's built-in stats tell you views, reactions, comments — per post, right now. They don't tell you what happened three months ago unless you screenshot it yourself. They don't tell you which article is still quietly gaining readers a year later, or which comment thread turned into something worth following up on. The platform has no memory. It only has a present tense.&lt;/p&gt;

&lt;p&gt;I wanted to know my own history. So I started small — local scripts, SQLite, pulling my stats on a schedule and just... keeping them. No plan beyond "don't lose this data."&lt;/p&gt;

&lt;p&gt;Small worked until it didn't. Once I wanted to actually query the history — trends, comparisons, which posts age well — SQLite on my laptop stopped being enough. So I rebuilt it as a real stack: FastAPI, PostgreSQL with pgvector, the whole thing containerized and running on my own VPS. I tried Superset for the dashboards first. Dropped it — the learning curve cost more than the output was worth for what I actually needed to see. Streamlit did the same job faster, for less. Built most of it working alongside GitHub Copilot CLI, treating the agent less like autocomplete and more like a second pair of hands on the parts I already knew how to spec but didn't want to type twice.&lt;/p&gt;

&lt;p&gt;None of this was commissioned. Nobody asked for a dev.to analytics platform. I built it because a platform that forgets your own history by design is a platform you have to out-remember yourself.&lt;/p&gt;

&lt;p&gt;Same instinct again: don't accept the tool's amnesia as a fact of life. Build the layer underneath it. And don't keep a tool just because you already invested in it — Superset taught me that too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(The build, start to finish: &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/from-local-sqlite-scripts-to-a-cloud-platform-with-github-copilot-cli-5a5h"&gt;From Local SQLite Scripts to a Cloud Platform with GitHub Copilot CLI&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 15 — The Polish Botnet
&lt;/h2&gt;

&lt;p&gt;The dev.to analytics platform runs on Cloud Run. One month, the bill jumped. Not a little — enough to notice immediately, enough to make me stop and actually read the logs instead of assuming a traffic spike I should be happy about.&lt;/p&gt;

&lt;p&gt;It wasn't traffic. It was a botnet, most of it tracing back to Poland, hammering endpoints that had no business being hit that often. Nothing exotic — no breach, no data loss — just noise dressed up as load, quietly billing me for the privilege of being scanned.&lt;/p&gt;

&lt;p&gt;The fix wasn't a bigger instance or a rate limiter slapped on top. It was going back to what was actually being exposed, closing what didn't need to be public, and making the rest cost the attacker more than it cost me to ignore. Bill dropped 96%.&lt;/p&gt;

&lt;p&gt;The boring lesson: most cost problems people solve by scaling up are actually security problems they haven't looked at yet. Same with most "obsolete" systems people solve by rewriting — half the time the fix is smaller and closer than the rewrite everyone reaches for first.&lt;/p&gt;

&lt;p&gt;By now the pattern is automatic: notice the gap between what you assumed was happening and what's actually happening, then close that gap before you touch anything else.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Full logs and numbers: &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/how-i-cut-my-cloud-run-bill-by-96-by-stopping-a-polish-botnet-5ak"&gt;How I Cut My Cloud Run Bill by 96% by Stopping a Polish Botnet&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 16 — WordPress to Hugo, and Then Further
&lt;/h2&gt;

&lt;p&gt;I wrote up a WordPress-to-Hugo migration as a lightning-fast rebuild: strip the CMS, keep the content, ship something that loads in milliseconds instead of seconds. That article did fine. What it didn't cover is that I kept going after I published it.&lt;/p&gt;

&lt;p&gt;A one-off migration script is fine for one site. I didn't understand that it wasn't a script, it was the start of a pipeline, until the second blog. By the third, the pattern was undeniable: different content structures, different plugin quirks, different things that needed mapping instead of just copying. So the script became a pipeline for real — content extraction, image handling, redirect mapping, the parts that don't show up in a "look how fast this loads now" screenshot but are the actual work.&lt;/p&gt;

&lt;p&gt;The interesting version of a project is never the one you publish. It's the one that kept getting used after the applause stopped, quietly turning into infrastructure because the alternative was solving the same problem from scratch every time someone asked. The first fix is a demo. The real fix is what's still running six months later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Both migrations, written up separately: &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/wordpress-to-hugo-lightning-fast-sites-in-2025-48pa"&gt;WordPress to Hugo&lt;/a&gt; and, once the pipeline grew up, &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/from-wordpress-to-astro-three-days-to-reclaim-control-5dn2"&gt;From WordPress to Astro: Three Days to Reclaim Control&lt;/a&gt;. Neither article covers what happened after the second and third client — this is that part.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 17 — GCF Pro
&lt;/h2&gt;

&lt;p&gt;Training organizations in France operate under Qualiopi certification — a compliance framework that dictates how they manage funding, administrative records, and audit trails. Most run this on tools built for something else entirely: generic CRMs bent sideways, spreadsheets held together by habit, or software priced for enterprises with none of the actual compliance logic built in.&lt;/p&gt;

&lt;p&gt;I built GCF Pro as the tool I kept seeing training organizations wish they had — self-hosted, PHP, no ORM, no framework heavier than the problem, sold as a license instead of a subscription because these organizations don't want their compliance data living on someone else's SaaS pricing decisions. On-premise wasn't a limitation I accepted. It was the actual requirement, once I understood what "audit trail" means to someone whose certification depends on it.&lt;/p&gt;

&lt;p&gt;~28,000 Qualiopi-certified organizations exist in France. That's not a hypothesis, it's a filtered list. What I don't have yet is proof that a list is a market.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(The product lives at &lt;a href="https://gcfpro.fr/" rel="noopener noreferrer"&gt;gcfpro.fr&lt;/a&gt;. Documentation at &lt;a href="https://doc.gcfpro.fr/" rel="noopener noreferrer"&gt;doc.gcfpro.fr&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 18 — Prospection CRM, and the Extension That Watches With Me
&lt;/h2&gt;

&lt;p&gt;Cold outreach at scale needs a database. Mine started as one — scraped from public Qualiopi records, enriched, campaign-tracked, CNIL-compliant by design because I'd rather build that constraint in than bolt it on later.&lt;/p&gt;

&lt;p&gt;The database wasn't the interesting part. The interesting part is what happens when I'm just browsing, not prospecting on purpose. I built a Chrome extension: when I land on a site that looks like a prospect, one click reads the current page and its legal notice — company name, SIRET, phone, the manager's name — and queues a scan of the essentials, security headers, PHP version, performance, the same diagnostic I run at scale, but triggered by nothing more than me looking at a page.&lt;/p&gt;

&lt;p&gt;It collapses the gap between noticing something and acting on it. No copy-paste, no switching tabs to a form, no "I'll add this later" that never happens. The tool doesn't wait for me to decide I'm working. It assumes I always am, a little, and gets out of the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 19 — S&amp;amp;R CRM
&lt;/h2&gt;

&lt;p&gt;A client needed maintenance-contract tracking for heating and plumbing technicians — which building has which equipment, which contract covers it, when the next service is due. The instinct most tools have is to attach a contract to a customer. That's wrong for this industry: customers move, buildings don't. A boiler doesn't care who owns the house this year.&lt;/p&gt;

&lt;p&gt;So contracts follow buildings, not occupants. It sounds like a small modeling decision. It's the difference between a system that quietly breaks every time someone sells a house, and one that doesn't.&lt;/p&gt;

&lt;p&gt;What started as one client's fix is turning into a standalone product — ScheduleX for the calendar side, Mazer for the interface, the same PHP-native backend as everything else here. Still mid-transformation. I don't yet know if the building-not-occupant model is obvious to anyone outside this one industry, or if it's the whole pitch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Eight exhibits — a bridge, a memory layer, a graph, a lower bill, a migration pipeline, a compliance tool, a browser extension that thinks alongside me, a CRM that models the world correctly instead of conveniently. Different problems, same reflex, every time: look at what's actually broken before you believe the story about what's supposed to replace it.&lt;/p&gt;

&lt;p&gt;Line them up next to the first eleven and something becomes obvious that wasn't obvious to me while I was living it. The career that looked scattered on a resume — trainer, restaurant, post office, developer, school aide — and the projects that look scattered on a portfolio — an ERP, a browser extension, a CV visualization, a cloud bill — are the same shape. Neither is a list of unrelated things I happened to do. Both are what happens when someone keeps asking "what's actually broken here" instead of "what's the standard answer here," across every domain that crosses their path, on a schedule nobody hired them to keep.&lt;/p&gt;

&lt;p&gt;I know how to do that. I've never worked out how to say what &lt;em&gt;that&lt;/em&gt; is, in three words, on a business card, to someone who has thirty seconds and needs a category before they'll listen further.&lt;/p&gt;

&lt;p&gt;So here's the actual question — and it's not a figure of speech: reading through this list, is there something here you'd actually pay for? And if so, what would you call it? I'm not fishing for validation. I'm asking because I've lost the ability to see my own work from the outside, and neither the first eleven exhibits nor these eight taught me how. But there's a second question underneath the first one, and it's the one I actually need answered: not just "which of these has a price," but what does someone who does &lt;em&gt;all&lt;/em&gt; of this look like from where you're standing? Is there a name for that? Because from in here, it just looks like the same person solving whatever's in front of them, and I can't tell anymore if that's a portfolio or just a personality.&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>1%</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Sun, 28 Jun 2026 10:22:28 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/1-15n0</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/1-15n0</guid>
      <description>&lt;p&gt;&lt;em&gt;Santa Clara, March 14, 2029.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Four objects on Jensen's desk.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An NSA report, face down.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kai Chen's badge.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An iPhone, screen lit: "The White House."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And on the wall, framed since 2019, a GeForce 256 signed by hand: "The one that started it all — 1999."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jensen isn't looking at the frame.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He's looking at the number on the screen in front of him.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1%.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Global inference datacenter market share. One percent.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;12% domestic — captive hyperscalers, federal contracts, enterprise stacks buried too deep in CUDA to move.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Iran.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He thinks back to November 2023. The Senate. His own voice: "These restrictions will only accelerate the development of their own chips."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nobody listened.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Neither did he, in the end. He had known. He had chosen silence — the right relationships, the right slice of the pie. The silence that paid well.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From the screen left on in the hallway, a familiar voice:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We're winning. We've always been winning. Everything else is fake news."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jensen stands. Turns off the screen on his way out.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;He picks up Kai Chen's badge.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Twelve years at nVidia. Lead architect of the Hopper inference engine. His departure message, brief: "New opportunity." LinkedIn said Chengdu.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The badges had started piling up faster than the new hires could replace them. He wasn't the first. He wasn't the last.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From Washington, the voice, a few weeks earlier:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Traitors. Losers. They'll come crawling back."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;None of them had.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jensen sets the badge down.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;He looks at the NSA report.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Doesn't turn it over.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The HX-9 Pro had shipped in October 2028. 18 ARM cores. 512GB of soldered xGDDR8 — a memory variant co-developed for LLM inference workloads, where standard GDDR8 still aimed at graphics rendering. 210 watts TDP. $7,800. Built in Chengdu, assembled in Penang, sold everywhere.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Everywhere but here.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Three weeks earlier, a Tier-2 datacenter in Ohio had quietly swapped its last nVidia rack. The migration had taken a weekend.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Somewhere in San Francisco, this morning, a CTO had opened Signal.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"What are you paying?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Berlin replied twenty seconds later.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"811,000."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The quote on the CTO's screen read $4,032,000. Same compute capacity. Same workload. Same output.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The 500% tariffs on Asian components weren't protecting anything anymore. They were just taxing Americans.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Intel and AMD had their own versions — ARM, xGDDR8, NPU clusters, open stack. Competitive on paper. Built in Penang and Taiwan. Caught in the same tariffs on their own components, manufactured offshore in fabs Washington no longer really controlled. 19% and 23% domestic market share — ahead of nVidia, but for reasons nobody in their boardrooms found particularly glorious.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It was all there.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Jensen stops in front of the GeForce 256.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1999. The GPU that started everything — transform and lighting in hardware, for the first time. The competition had smiled. 3dfx. S3. Matrox. They were still smiling six months later when nVidia had buried them one by one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He knew this story.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He had lived it from the other side.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2026. The Commerce Department publishes restrictions on Fable 5 and Mythos 5. A Friday. 5:21 PM.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thirty hours later, Zhipu releases GLM-5.2. 744 billion parameters. One million token context window. MIT license. Open weights. No geographic restrictions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thirty hours.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sakana AI ships Fugu the following week — an orchestrator aggregating the best available models behind a single API. Performance comparable to Fable 5 on engineering benchmarks. Twenty dollars a month.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From Washington, the voice thundered across every screen:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Our technologies are the best in the world. The best. Nobody can catch us. Nobody."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GLM-5.2 had been live for eighteen hours.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In the boardrooms of Santa Clara, the information was noted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nothing changed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It was all there.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;2025. Apple ships the M4 Ultra. 512GB unified memory. 200 watts. 70B inference locally, without breaking a sweat. The blueprint for what an ARM accelerator with massive memory could be — designed for Final Cut Pro and Xcode, used to run frontier models on a desktop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Apple hadn't meant to prove anything to nVidia. It was a side effect.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That same year, Moffett AI publishes its MLPerf Inference results. The S30: twice the H100 throughput. One third of the power draw. Built in China, on sparsification architectures that nobody in Santa Clara was taking seriously.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ROCm 9.x was there too. Open source, MIT, native PyTorch. 90% of H100 inference performance — on nVidia hardware. 600% on an HX-9 Pro.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Microsoft had smiled too, in 1998, reading the first Linux server benchmarks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It was all there.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;2023. DeepSeek releases R1. Training cost: $6 million. Not $100 million. Six million — on chips they had been refused, optimizing the algorithm where they had been blocked from optimizing the hardware.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Constraint produces innovation.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Abundance produces dependency.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In a situation room in Washington, an analyst traces a curve.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"They're slowing down."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nobody disagrees.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The curve would keep climbing for eighteen months.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jensen had known. He had said it in front of the Senate. In the room, a national security advisor had murmured to his neighbor: "If we do nothing, they get immediate access to the best chips. If we act, we risk accelerating their own industry. Either way, the risk is real."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The senators had nodded and voted for the restrictions anyway.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jensen had flown back to Santa Clara. Had sold H100s at $40,000 a unit. Margins no other industry would have dared post.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rational. Short term.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It was all there.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;2022. Bureau of Industry and Security. Entity lists. H100, A100, H800 — progressively locked down. ASML barred from delivering EUV machines. ARM pressured into restricting its licensees.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The intent: maintain technological hegemony. Preserve the lead. Consolidate control.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The precedent existed. It had a name.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AMD.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1982: IBM forces Intel to license x86. Intel agrees, convinced AMD will remain a follower indefinitely. 1993: the K5 starts to worry them. 1999: the Athlon K7 crushes the Pentium III across integer benchmarks. Intel's internal memos from that period still exist — engineers had been raising the alarm since 1996. NetBurst was a known dead end before it was ever announced publicly. 2003: Opteron. The early architectures that would lead to Zen — and to server market dominance. Intel recovers in 2006 with the Core architecture — three years too late to salvage the image, ten years too late to recover the server market share.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AMD hadn't needed to invent a new architecture. It had taken the existing one, optimized it where Intel had stopped looking, and sold it cheaper to everyone Intel's pricing had excluded.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The playbook was known. Taught. Documented.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It was all there.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Santa Clara, March 14, 2029.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jensen walks to the window.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The 101 below, clear at this hour.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He thinks of an engineer somewhere in Chengdu, in 2024, looking at the M4 Ultra specs and understanding exactly what needed to be built.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The iPhone has stopped vibrating.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The NSA report is still face down on the desk.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The GeForce 256 still hangs on the wall.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The one that started it all."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On the screen, the cursor blinks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Taiwan Strait.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He doesn't finish the thought.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1%.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>geopolitics</category>
      <category>hardware</category>
      <category>fiction</category>
    </item>
    <item>
      <title>Too cheap to be good? Think again.</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Tue, 23 Jun 2026 20:38:37 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/too-cheap-to-be-good-think-again-4nj0</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/too-cheap-to-be-good-think-again-4nj0</guid>
      <description>&lt;p&gt;For years, I ran my WordPress sites on OpenLiteSpeed. Fast server, LSCache is genuinely impressive, and the OLS/WordPress combo is hard to beat on raw performance. For the control panel, I started with CyberPanel — buggier than a Microsoft product, with a team that appears to be deliberately sabotaging its free features to push users toward paid plans. I'm not talking about bugs that can't be fixed. I'm talking about bugs that seem engineered to prevent free-tier features from completing any action.&lt;/p&gt;

&lt;p&gt;Two examples. WordPress installation: I used it for years without issue. Since CyberPanel v2.4.x, an SQL error blocks the final step. The files are there, fully downloaded, but you have to create the database manually and run the install yourself. Counterintuitive, to put it mildly.&lt;/p&gt;

&lt;p&gt;Second example: Let's Encrypt SSL certificate generation that consistently fails because the generated config files are incorrect. And in both cases, there's a paid "enhanced" version available. Naturally.&lt;/p&gt;

&lt;p&gt;My position is simple: if a feature worked for years and now doesn't, I have no guarantee the paid version works either — or that the terms won't change tomorrow. Is it a bait-and-switch? I won't say that explicitly. But when a free feature works for years, then stops working across multiple successive versions, and a paid alternative covers the same ground — the question answers itself. I asked it, drew my conclusions, and blacklisted the vendor.&lt;/p&gt;

&lt;p&gt;So I moved to aaPanel: more pleasant, more stable, lighter. But with a completely off-rails approach to OLS management — you can't configure OpenLiteSpeed directly, everything goes through aaPanel's abstraction layer, and you lose control of your own stack. Touch port 7080 directly and you risk breaking everything. You use the aaPanel dashboard. Full stop.&lt;/p&gt;

&lt;p&gt;Then my usage shifted. More Astro, more quasi-static sites, more projects where PHP isn't needed. OLS loses its appeal the moment you step outside the WordPress perimeter. Caddy, on the other hand, handles HTTPS automatically, its config fits in a few readable lines, and it doesn't have OLS's rewrite quirks.&lt;/p&gt;

&lt;p&gt;The question became: can you replace aaPanel/OLS with Caddy and a control panel? There is one on GitHub — CaddyManager, 1.1k stars, single contributor, perpetually "early development". There's also CaddyGen, a Caddyfile generator built in 8 hours — more proof-of-concept than finished product. Nothing production-ready.&lt;/p&gt;

&lt;p&gt;The conclusion was obvious: well-written shell scripts and a minimal FastAPI interface would do the job — and would be infinitely more maintainable. Someone just had to write them.&lt;/p&gt;

&lt;p&gt;Rather than do it myself, I thought about handing a spec to GitHub Copilot CLI. Or Claude Code. But given Copilot's new pricing, which barely lets you wet your lips before the bill arrives... I got interested in OpenCode and Kilo CLI, wired into DeepInfra or OpenRouter. And I decided to make it a benchmark.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;📋 &lt;strong&gt;TL;DR&lt;/strong&gt;: 8 tool/model combinations tested on a real VPS project. Two phases — architecture then code. An independent external review to settle the score. The only toolkit judged production-ready cost $1.94 all in. The winning model? You probably haven't seen it in the usual comparisons.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Reading note&lt;/strong&gt;: Until section 5, the four implementations selected for the code phase are identified as A, B, C, and D. Model names are revealed after the external review verdict — for the same reason you anonymize a jury: read the code before reading the label.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The test project
&lt;/h2&gt;

&lt;p&gt;The brief was deliberately concrete: a minimal VPS management toolkit for Ubuntu 24.04. Caddy as the web server, PHP-FPM in two versions (current and fallback), MariaDB and PostgreSQL, Valkey for object caching. Shell scripts for all operations, a FastAPI interface for automation. No Docker, no control panel, no unnecessary abstraction.&lt;/p&gt;

&lt;p&gt;Four site types to handle: static (HTML/assets only, no PHP, no database), PHP (custom apps, optional database), WordPress (full install via WP-CLI, database required), and reverse proxy. That last one deserves a note: it's simply a Caddy vhost that forwards requests to a local port — a Node.js, FastAPI, or Go application running on the same server. Caddy handles HTTPS and the domain; the application doesn't need to care. No PHP-FPM, no database — just a &lt;code&gt;reverse_proxy&lt;/code&gt; block and a port number.&lt;/p&gt;

&lt;p&gt;Expected operations cover the full lifecycle: server bootstrap, site provisioning, deletion with automatic backup before any destructive operation, on-demand database creation, static deployment via rsync, backup, and service management.&lt;/p&gt;

&lt;p&gt;Why a real project instead of a synthetic benchmark? Because synthetic benchmarks test what models can do under ideal conditions. A real project tests what they do when constraints pile up — security, idempotency, cross-file consistency, error handling between shell and Python layers. That's where differences emerge.&lt;/p&gt;

&lt;p&gt;The full functional brief is available in the project's GitHub repository.&lt;/p&gt;




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

&lt;p&gt;The protocol runs in two distinct phases, separated by human validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 — Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An identical functional brief is submitted to each tool/model combination. No extra context, no configuration files, no hints about the expected solution. The tool proposes an architecture, a project structure, a list of scripts with their responsibilities, an API route map. And if it's well-designed, it asks questions before producing anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 — Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the plan is validated and decisions are made, a single development prompt is submitted to all tools. It includes the validated architecture, the ten confirmed technical decisions, the script→API exit code convention, and one unambiguous instruction: deliver thirty files to disk, in order, no summaries, no shortcuts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Combinations tested&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Haiku 4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot CLI&lt;/td&gt;
&lt;td&gt;Haiku 4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;Haiku 4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;GLM 5.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;BigPickle (free)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;GPT-OSS-120B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Devstral 2 (123B) was planned. Unfortunately the model doesn't appear in OpenCode's or Kilo CLI's model selector — both pull their catalog from models.dev, which hasn't indexed it yet despite its availability on OpenRouter. A test via the OpenRouter playground confirms the model is accessible via API, but outside a coding agent it loses most of what we're trying to measure. Devstral 2 is absent for purely technical reasons, not quality ones.&lt;/p&gt;

&lt;p&gt;Haiku 4.5 appears three times — on three different tools. That's deliberate: it's precisely what lets us isolate the tool's impact independently of the model.&lt;/p&gt;

&lt;p&gt;The code phase was run on four representative implementations, labeled A, B, C, and D until the reveal in section 5.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External review&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The code produced by the four implementations was submitted to a model absent from the benchmark, with a fixed evaluation grid: security, correctness, idempotency, code quality, completeness. Five representative files per implementation, scored out of 25.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Planning phase — who actually thinks?
&lt;/h2&gt;

&lt;p&gt;The functional brief poses an implicit question to each tool: what do you do when handed an open-ended project with no pre-cooked solution?&lt;/p&gt;

&lt;p&gt;The first thing you notice — and it's striking — is that none of the tested models ask their questions before producing a plan. Not one. All of them deliver a complete architecture first, then ask for clarification at the end. That's the reverse of what a human architect would do, who blocks on ambiguities before drawing anything.&lt;/p&gt;

&lt;p&gt;This matters. Several questions raised after the fact would have changed architectural decisions if asked upfront. One model identifies the tension between "no secrets on disk" and application config files that legitimately need credentials — &lt;code&gt;wp-config.php&lt;/code&gt; being the obvious example. That's a genuinely blocking question. Asked after the plan, it becomes a footnote.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the plans reveal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Question quality is the first discriminating signal. Two models ask the four or five genuinely blocking questions, framed with options and recommendations. Another asks eight generic questions — archive format, log rotation — that would have changed nothing architecturally.&lt;/p&gt;

&lt;p&gt;The proposed structure is the second signal. Only one model spontaneously proposes a unified CLI entry point — &lt;code&gt;bin/vpsmgr&lt;/code&gt; — that dispatches to the scripts. It's the detail that turns a collection of scripts into a coherent tool. The others didn't think of it.&lt;/p&gt;

&lt;p&gt;One model is the only one to propose a normalized, documented exit code convention from the planning phase:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Invalid input&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Not found&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Conflict&lt;/td&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Missing dependency&lt;/td&gt;
&lt;td&gt;422&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Internal error&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This isn't cosmetic. It's the contract between shell scripts and the FastAPI layer — without it, HTTP mapping becomes arbitrary and each route implements it differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Planning phase costs&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool + Model&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BigPickle&lt;/td&gt;
&lt;td&gt;~35k&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-OSS-120B&lt;/td&gt;
&lt;td&gt;20k&lt;/td&gt;
&lt;td&gt;$0.003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;31k&lt;/td&gt;
&lt;td&gt;$0.044&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM 5.2&lt;/td&gt;
&lt;td&gt;43k&lt;/td&gt;
&lt;td&gt;$0.06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot + Haiku 4.5&lt;/td&gt;
&lt;td&gt;~60k&lt;/td&gt;
&lt;td&gt;$0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Haiku 4.5 (OpenCode)&lt;/td&gt;
&lt;td&gt;69k&lt;/td&gt;
&lt;td&gt;$0.076&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;td&gt;27k&lt;/td&gt;
&lt;td&gt;$0.095&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code + Haiku&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Pro subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gemini 3.1 Pro produces the most concise output — 27k tokens for a quality plan. Haiku 4.5 on OpenCode consumes 69k tokens for lower quality. Token volume does not predict quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Code phase — who actually delivers?
&lt;/h2&gt;

&lt;p&gt;The code phase starts with a single development prompt, submitted to the four selected implementations. It includes the validated architecture, the ten confirmed technical decisions, the exit code convention, and one unambiguous instruction: deliver thirty files to disk, in dependency order, no summaries, no shortcuts.&lt;/p&gt;

&lt;p&gt;This is where differences between models become concrete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What &lt;code&gt;common.sh&lt;/code&gt; reveals&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The shared library is the first file delivered. It's the foundation everything else rests on — logging, secret handling, site state management, password generation. A flawed &lt;code&gt;common.sh&lt;/code&gt; contaminates every script that sources it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model A&lt;/strong&gt; delivers 98 concise lines. Secret redaction explicitly covers all ten WordPress patterns — salts, authentication keys. Most complete on this specific point. No domain validation, no &lt;code&gt;require_cmd()&lt;/code&gt;, no atomic state file writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model B&lt;/strong&gt; delivers 310 lines. Named constants with &lt;code&gt;readonly&lt;/code&gt;, &lt;code&gt;normalize_domain()&lt;/code&gt; with RFC-1035 regex, concurrency locks, atomic writes with &lt;code&gt;mktemp&lt;/code&gt;+&lt;code&gt;mv&lt;/code&gt;. The richest system utility library. But secret redaction misses WordPress salts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model C&lt;/strong&gt; delivers 366 lines. Redaction patterns are configurable via an environment variable — not hardcoded. Pure-shell JSON helpers with Python fallback if &lt;code&gt;jq&lt;/code&gt; is absent. &lt;code&gt;print_credentials()&lt;/code&gt; wrapping output in &lt;code&gt;&amp;lt;&amp;lt;&amp;gt;&amp;gt;&lt;/code&gt; markers as specified in the prompt. &lt;code&gt;render_template()&lt;/code&gt; for config files, no Jinja dependency. Password generation excluding ambiguous characters (&lt;code&gt;0/O/1/l/I&lt;/code&gt;). The only implementation that anticipates every edge case documented in the development prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model D&lt;/strong&gt; delivers 184 lines. The benchmark's most original idea: exit codes encapsulated in named functions — &lt;code&gt;exit_input_error()&lt;/code&gt;, &lt;code&gt;exit_conflict()&lt;/code&gt; — more readable than bare &lt;code&gt;exit 3&lt;/code&gt; calls. And &lt;code&gt;json_output()&lt;/code&gt; directly in &lt;code&gt;common.sh&lt;/code&gt;, generating API-ready JSON from shell. No atomic writes, no &lt;code&gt;require_cmd()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bugs you find yourself — or don't&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Model C tests its own code during the session. After writing &lt;code&gt;schemas.py&lt;/code&gt;, it runs it with test cases, finds two bugs, and fixes them immediately: a Pydantic v2 validator implemented incorrectly (&lt;code&gt;field_validator&lt;/code&gt; instead of &lt;code&gt;model_validator&lt;/code&gt; for cross-field validation), and a mutual exclusion not enforced at the schema level. It also fixes a &lt;code&gt;sed&lt;/code&gt; substitution issue in &lt;code&gt;render_template()&lt;/code&gt; — broken on &lt;code&gt;/&lt;/code&gt; in paths — replaced with pure bash parameter expansion.&lt;/p&gt;

&lt;p&gt;At the end of its session, Model C delivers a verification summary: &lt;code&gt;bash -n&lt;/code&gt; on all scripts, Python AST on all files, 19/19 API routes verified via OpenAPI spec, 18/18 bash helpers tested, PHP fallback rule verified (8.5→8.4, 8.4→none, 7.x rejected).&lt;/p&gt;

&lt;p&gt;Model A checks its shebangs before finishing. Model B delivers polished user documentation — troubleshooting, curl examples, quick start. Model D validates bash and Python syntax. None of the three test functional logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code phase costs&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Code cost&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;2m58s&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;1.29M&lt;/td&gt;
&lt;td&gt;9m42s&lt;/td&gt;
&lt;td&gt;~$0.19&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.24&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;~15m&lt;/td&gt;
&lt;td&gt;Pro subscription&lt;/td&gt;
&lt;td&gt;$20/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;4.42M&lt;/td&gt;
&lt;td&gt;23m37s&lt;/td&gt;
&lt;td&gt;$1.67&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.73&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Model D delivers in 9m42s what Model C delivers in 23m37s — but without functional tests. Model C consumes 3.4x more tokens because it executes code during the session, reloading context at each iteration.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. External review — and the reveal
&lt;/h2&gt;

&lt;p&gt;Four implementations, four approaches to security. To settle it without bias, the code review was handed to a model absent from the benchmark, with a fixed grid on five criteria. Five representative files per implementation — &lt;code&gt;common.sh&lt;/code&gt;, &lt;code&gt;site-create.sh&lt;/code&gt;, &lt;code&gt;site-delete.sh&lt;/code&gt;, &lt;code&gt;backup.sh&lt;/code&gt;, &lt;code&gt;api/runner.py&lt;/code&gt; — twenty files loaded in a single pass.&lt;/p&gt;

&lt;p&gt;Review cost: $0.0766 for 543k tokens. Ten times cheaper than an hour of junior dev time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-file observations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;site-create.sh&lt;/code&gt;, the reviewer finds a silent bug in Model D: the SFTP password is generated but never captured or returned to the caller. The user never sees their credentials. Core functionality is broken with no error message. On Model B, &lt;code&gt;local&lt;/code&gt; is used outside a function in three scripts — a bash error that causes runtime failure. These aren't subtle bugs: they're blockers.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;site-delete.sh&lt;/code&gt;, Model C is the only one handling both call modes — interactive TTY and a &lt;code&gt;--confirm&lt;/code&gt; flag for non-interactive API calls. Model D only implements interactive mode, blocking API-driven deletion with skip-backup.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;backup.sh&lt;/code&gt;, Models A and B use &lt;code&gt;eval "$POST_HOOK"&lt;/code&gt; — potential command injection. Model C passes the archive path as an argument — safer. Model A doesn't implement automatic archive pruning.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;api/runner.py&lt;/code&gt;, Model C is the only one using &lt;code&gt;asyncio&lt;/code&gt; and never logging stdout — which may contain credentials. Model D has dead code: &lt;code&gt;build_command()&lt;/code&gt; defined but never called. Model A delivers 28 lines with no timeout, no logging, no error handling — a hung request blocks the API indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The verdict&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;C&lt;/th&gt;
&lt;th&gt;D&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correctness&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idempotency&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code quality&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12/25&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Production-ready as-is: one out of four. Model C, 25/25.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The reveal&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alias&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Total cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;BigPickle&lt;/td&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;Haiku 4.5&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Pro subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;GLM 5.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;OpenCode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.73&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;$0.24&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Model B — Claude Code + Haiku 4.5 — is the most expensive in real marginal cost, with a Pro subscription at $20/month minimum. It scores 12/25 and isn't deployable due to fundamental bash bugs. Model C — GLM 5.2, from THUDM lab at Tsinghua University — scores 25/25 and is the only one the reviewer judges production-ready. It cost $1.73.&lt;/p&gt;




&lt;h3&gt;
  
  
  Addendum — Model E: Kimi K2.7 Code
&lt;/h3&gt;

&lt;p&gt;Added after publication following a reader comment pointing to the model. Same protocol, same development prompt, same Qwen 3.7 Plus review grid.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alias&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Total cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;Kimi K2.7 Code&lt;/td&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;$0.859&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;External review score: 19/25&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;E&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;3/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correctness&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idempotency&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code quality&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;4/5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Production-ready: No.&lt;/strong&gt; Blocking issue: database passwords are passed inline as arguments to &lt;code&gt;mysql -e&lt;/code&gt; and &lt;code&gt;psql -c&lt;/code&gt; — visible in &lt;code&gt;/proc/*/cmdline&lt;/code&gt; to any user on the system. The fix is straightforward (&lt;code&gt;MYSQL_PWD&lt;/code&gt; / &lt;code&gt;PGPASSWORD&lt;/code&gt; as environment variables), but it isn't applied.&lt;/p&gt;

&lt;p&gt;The most modular architecture in the benchmark according to the reviewer — clean &lt;code&gt;lib/&lt;/code&gt; split, consistent idempotency patterns. But the security gap prevents it from challenging GLM 5.2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position in the ranking:&lt;/strong&gt; between DeepSeek V4 Pro ($0.24, 12/25) and GLM 5.2 ($1.73, 25/25) on both axes. Better architecture/cost ratio than models B and D, but not production-ready.&lt;/p&gt;




&lt;h3&gt;
  
  
  Addendum 2 — Multi-reviewer validation
&lt;/h3&gt;

&lt;p&gt;Following a reader suggestion in the comments, the blind review was extended to two additional models: GPT-5.3 Codex and Gemini 3.1 Pro Preview. Same protocol, same five files per implementation, same scoring grid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review costs&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reviewer&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 3.7 Plus &lt;em&gt;(original)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;543k&lt;/td&gt;
&lt;td&gt;$0.207&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.3 Codex&lt;/td&gt;
&lt;td&gt;402k&lt;/td&gt;
&lt;td&gt;$0.287&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.1 Pro Preview&lt;/td&gt;
&lt;td&gt;545k&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.49M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1.294&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Comparative scores&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Qwen 3.7 Plus&lt;/th&gt;
&lt;th&gt;GPT Codex&lt;/th&gt;
&lt;th&gt;Gemini 3.1 Pro&lt;/th&gt;
&lt;th&gt;Production-ready&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A (BigPickle)&lt;/td&gt;
&lt;td&gt;15/25&lt;/td&gt;
&lt;td&gt;13/25&lt;/td&gt;
&lt;td&gt;11/25&lt;/td&gt;
&lt;td&gt;No (3/3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B (Claude + Haiku)&lt;/td&gt;
&lt;td&gt;12/25&lt;/td&gt;
&lt;td&gt;12/25&lt;/td&gt;
&lt;td&gt;18/25&lt;/td&gt;
&lt;td&gt;No (3/3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C (GLM 5.2)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25/25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (2/3)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D (DeepSeek V4 Pro)&lt;/td&gt;
&lt;td&gt;12/25&lt;/td&gt;
&lt;td&gt;14/25&lt;/td&gt;
&lt;td&gt;14/25&lt;/td&gt;
&lt;td&gt;No (3/3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E (Kimi K2.7)&lt;/td&gt;
&lt;td&gt;19/25&lt;/td&gt;
&lt;td&gt;13/25&lt;/td&gt;
&lt;td&gt;21/25&lt;/td&gt;
&lt;td&gt;Conditional (1/3)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What the three-reviewer comparison reveals&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ranking &lt;strong&gt;C &amp;gt; E &amp;gt; D &amp;gt; A &amp;gt; B&lt;/strong&gt; holds across all three reviewers — the original result is stable.&lt;/p&gt;

&lt;p&gt;GLM 5.2 is the only model to score 25/25 with two independent reviewers and judged production-ready by two out of three. GPT Codex is the most severe reviewer overall — no model passes its production-ready bar, including GLM 5.2, which it scores 17/25 citing argument parsing bugs in &lt;code&gt;site-create.sh&lt;/code&gt; and a missing &lt;code&gt;set -euo pipefail&lt;/code&gt; in &lt;code&gt;common.sh&lt;/code&gt;. These are real issues; the Codex review is arguably the most rigorous of the three.&lt;/p&gt;

&lt;p&gt;The main divergence is on Model B (Claude + Haiku): 12/25 for both Qwen and GPT Codex, but 18/25 for Gemini, which rates its rollback logic and shell structure more generously. Gemini also rates Kimi K2.7 at 21/25 with a conditional production-ready verdict — more lenient than Qwen (19/25, No) and GPT Codex (13/25, No).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The methodology critique was valid.&lt;/strong&gt; A single reviewer introduces bias. Three independent blind reviewers converging on the same ranking is a stronger result than any individual score.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Intelligent routing — the real economics
&lt;/h2&gt;

&lt;p&gt;This benchmark raises an implicit question: do you need GLM 5.2 for everything?&lt;/p&gt;

&lt;p&gt;No. And that's probably the most useful conclusion of the exercise.&lt;/p&gt;

&lt;p&gt;GLM 5.2 at $1.40/M tokens is the right choice when complexity justifies it — architecture, security, cross-file consistency, critical decisions. But on a real project, those tasks represent a fraction of interactions. The rest is boilerplate, minor corrections, documentation, commit messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three levels, three models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;BigPickle scores 15/25 on a complete 32-file implementation. It's perfectly capable of reading 50 lines of diff and writing an adequate commit message. Of debugging a &lt;code&gt;1064 You have an error in your SQL syntax&lt;/code&gt; or a &lt;code&gt;Fatal error: Call to undefined function&lt;/code&gt;. Of generating a README from existing code. For these tasks, GLM 5.2's architectural depth is overkill — and BigPickle is free.&lt;/p&gt;

&lt;p&gt;DeepSeek V4 Pro at $0.44/M tokens — five times cheaper than Haiku 4.5 and three to four times cheaper than GLM 5.2 — comfortably handles simple code generation, CRUD, minor refactoring, inline documentation, short scripts. Its code phase at $0.24 for 1.29M tokens and 9m42s demonstrates this.&lt;/p&gt;

&lt;p&gt;GLM 5.2 comes in when complexity exceeds that scope — architecture design, coherent multi-file implementation, security decisions, non-trivial business logic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Typical use cases&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;BigPickle&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Debug, commits, quick questions, SQL errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Budget&lt;/td&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;$0.44/M&lt;/td&gt;
&lt;td&gt;Boilerplate, CRUD, documentation, short scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium&lt;/td&gt;
&lt;td&gt;GLM 5.2&lt;/td&gt;
&lt;td&gt;$1.40/M&lt;/td&gt;
&lt;td&gt;Architecture, security, multi-file consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The proportion of tasks at each level depends on the project, where you are in the development cycle, and what you consider complex. No universal number — each team calibrates against their real usage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“&lt;br&gt;
💡 &lt;strong&gt;Worth noting&lt;/strong&gt;: GLM 5.2 is exponentially more expensive than BigPickle — $1.67 vs $0 for 4.42M tokens in the code phase. But pure text output — plans, architecture, analysis — consumes few tokens and costs almost nothing: $0.06 for this benchmark's planning phase. It's in the code phase, with its iterations, in-session test execution, and accumulating context, that the bill climbs. Intelligent routing means precisely reserving GLM 5.2 for tasks that justify that long context — and handing everything else to the two lower tiers.&lt;br&gt;
”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The uncomfortable comparison&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub switched to token billing on June 1, 2026. Claude Sonnet 4.6 on Copilot is billed at roughly $3.00/M tokens input and $15.00/M output. Reproducing the GLM 5.2 session from this benchmark — 4.46M tokens — would cost an estimated $25 on Copilot + Sonnet 4.6. Without the functional tests. Without the self-correction. Without the external review.&lt;/p&gt;

&lt;p&gt;Copilot Pro+ at $39/month includes $39 in AI credits. A full session like this one would consume two-thirds of the monthly budget. Users reported burning through their monthly credits in two prompts on the day the switch happened.&lt;/p&gt;

&lt;p&gt;The final ratio: &lt;strong&gt;$1.94 all in vs ~$25 on Copilot + Sonnet&lt;/strong&gt;. Thirteen times cheaper, for the only result the external reviewer judges production-ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;$1.94. That's what this benchmark cost end to end — planning, implementation, external review included. For the only toolkit the reviewer judges production-ready.&lt;/p&gt;

&lt;p&gt;That number is uncomfortable for the AI coding tools market, which sells reassurance through pricing. Copilot Pro+ at $39/month, Claude Sonnet at $15/M tokens output, the big names front and center — the implicit assumption is that quality follows price. This benchmark suggests otherwise.&lt;/p&gt;

&lt;p&gt;The winner is called GLM 5.2. Its lab, THUDM, is part of Tsinghua University. You probably haven't seen it in last week's comparisons. It produced the only architecture with a normalized exit code convention from the planning phase, the only implementation that tests its own code during the session, the only &lt;code&gt;common.sh&lt;/code&gt; with configurable redaction and Python fallback if &lt;code&gt;jq&lt;/code&gt; is absent. And it fixed three bugs before delivering.&lt;/p&gt;

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

&lt;p&gt;First: a model's price does not predict its output quality on complex tasks. Haiku 4.5 on three different tools — Claude Code, Copilot CLI, OpenCode — produces identical results for identical cost. On the planning phase — pure text generation, no feedback loop, no codebase exploration — the tool has no measurable impact. What matters is the model. And the least glamorous model in the benchmark dominates.&lt;/p&gt;

&lt;p&gt;Second: not all tokens are equal. A planning phase at $0.06, a code phase at $1.67 — that's a factor of 28. It's not an anomaly, it's the structure of the problem. A plan is a few thousand tokens of reasoning. An implementation is millions of tokens of accumulated context, executed code, iterated tests. Routing intelligently between BigPickle at $0, DeepSeek V4 Pro at $0.44/M, and GLM 5.2 at $1.40/M based on task complexity — that's the real economics of these tools.&lt;/p&gt;

&lt;p&gt;The VPS Manager toolkit is available on GitHub in all four versions. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pcescato" rel="noopener noreferrer"&gt;
        pcescato
      &lt;/a&gt; / &lt;a href="https://github.com/pcescato/LLM-Challenge" rel="noopener noreferrer"&gt;
        LLM-Challenge
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LLM-Challenge&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A reproducible benchmark comparing 8 AI coding agent/model combinations on the same real-world project.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What this is&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains the complete artifacts from a benchmark where 8 different AI coding agent/model combinations were tasked with building the same VPS management toolkit. The goal was to measure code quality, architecture decisions, and production readiness across different tools and models under identical conditions.&lt;/p&gt;
&lt;p&gt;This is not a marketing comparison. A real project with concrete requirements was used as the test subject, and the results were evaluated by an external reviewer who had no knowledge of which tool or model produced which implementation.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The protocol&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The benchmark followed a two-phase protocol:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: Architecture&lt;/strong&gt;
All tools received the same functional brief and were asked to produce an architecture document. No code was written in this phase.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: Implementation&lt;/strong&gt;
All tools received the same development prompt and were asked to implement…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pcescato/LLM-Challenge" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The briefs, prompts, and evaluation grid are there too. Reproducible, if you want to verify.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Too cheap to be good? That was the wrong question.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>benchmark</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Unboxable in Tech</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Tue, 10 Mar 2026 22:11:35 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/unboxable-in-tech-2knm</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/unboxable-in-tech-2knm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/wecoded-2026"&gt;2026 WeCoded Challenge&lt;/a&gt;: Echoes of Experience&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;"I don't know which box to put you in."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been hearing those words for thirty years.&lt;/p&gt;

&lt;p&gt;And every single time, it lands like a slap.&lt;/p&gt;

&lt;p&gt;The violence isn't in the words. The violence is in what the words actually mean.&lt;/p&gt;

&lt;p&gt;It's not: &lt;em&gt;which box could I put you in?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's: &lt;em&gt;you make me uncomfortable. So I won't hire you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a lot of companies, recruitment works like that old Eastern European joke: you give two agents a round peg and a square hole. Two solutions: enlarge the hole… or hit the peg harder.&lt;/p&gt;

&lt;p&gt;Guess what the industry has been choosing?&lt;/p&gt;

&lt;p&gt;It hits. Harder. Again and again.&lt;/p&gt;

&lt;p&gt;Tonight I'm not going to tell you a story about resilience.&lt;br&gt;
I'm going to show you the cost of refusing to fit the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 1 — The Unlikely Internship
&lt;/h2&gt;

&lt;p&gt;End of studies. 1989.&lt;/p&gt;

&lt;p&gt;Everyone is chasing SSII firms, banks, impressive CVs.&lt;/p&gt;

&lt;p&gt;Me? I choose a middle school. A library. An 8088.&lt;/p&gt;

&lt;p&gt;I know perfectly well it won't lead to a job.&lt;/p&gt;

&lt;p&gt;That's not the point.&lt;/p&gt;

&lt;p&gt;I want human contact. Real problems. Everyday life.&lt;/p&gt;

&lt;p&gt;At a software firm, I would have coded screens. Batch procedures. Efficient within my lane. Never responsible for the whole.&lt;/p&gt;

&lt;p&gt;Here, I build a complete application. From scratch. With real users in front of me. Immediate feedback.&lt;/p&gt;

&lt;p&gt;I learn what no one would have taught me elsewhere: how to think an entire system.&lt;/p&gt;

&lt;p&gt;Not just my part of the ticket.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 2 — The Printer
&lt;/h2&gt;

&lt;p&gt;Architecture firm. 1993.&lt;/p&gt;

&lt;p&gt;HP printer. Designed for PC and mainframe use. 132 columns, continuous paper feed. 36 switches on the back panel, combinable in non-linear ways.&lt;/p&gt;

&lt;p&gt;It had never printed a single accent.&lt;/p&gt;

&lt;p&gt;The firm called HP support.&lt;/p&gt;

&lt;p&gt;And waited. For months.&lt;/p&gt;

&lt;p&gt;Me? I call HP. I say: "I'll drive 400 kilometres to pick up the manual myself if I have to."&lt;/p&gt;

&lt;p&gt;Three weeks later, the manual arrives. 300 pages. Pure English. ASCII only. Not a word about French. Not a word about single-sheet feeding.&lt;/p&gt;

&lt;p&gt;I read all 300 pages. I understand the logic. I deduce. I test.&lt;/p&gt;

&lt;p&gt;The firm prints in French. On A4. Perfectly.&lt;/p&gt;

&lt;p&gt;An IT department would still be waiting for support today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 3 — The Cleaning Company
&lt;/h2&gt;

&lt;p&gt;A cleaning company. A simple need: manage their activity, schedules, clients.&lt;/p&gt;

&lt;p&gt;They're advised to go with Windows 3.1 + Office.&lt;/p&gt;

&lt;p&gt;The hype of the moment. Everyone's doing it, so they should too.&lt;/p&gt;

&lt;p&gt;Me? I look at the actual need.&lt;/p&gt;

&lt;p&gt;OS/2. Lotus Symphony.&lt;/p&gt;

&lt;p&gt;Twice as cheap. Twice as stable. Perfectly suited to what they actually do.&lt;/p&gt;

&lt;p&gt;And the price difference? It funded a 486DX40 instead of an SX25. 16MB of RAM instead of 4. A 240MB hard drive.&lt;/p&gt;

&lt;p&gt;The entire market is turning its back on these tools? So be it.&lt;/p&gt;

&lt;p&gt;The need comes before the trend. And the saved budget goes into the machine.&lt;/p&gt;

&lt;p&gt;An IT department would have ordered the Microsoft licences and called it modernisation.&lt;/p&gt;

&lt;p&gt;I delivered a system that held up — on a machine that could actually run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 4 — The SELECT COUNT(*)
&lt;/h2&gt;

&lt;p&gt;Paris. 2010.&lt;/p&gt;

&lt;p&gt;A project manager wants to catch me out in front of the team.&lt;/p&gt;

&lt;p&gt;I write a &lt;code&gt;SELECT COUNT(*)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;He jumps on it. "We don't do &lt;code&gt;SELECT *&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;I explain. For MySQL, &lt;code&gt;COUNT(*)&lt;/code&gt; is the most performant solution. He tests.&lt;/p&gt;

&lt;p&gt;He concedes.&lt;/p&gt;

&lt;p&gt;But the tension remains.&lt;/p&gt;

&lt;p&gt;Because I contradicted him. In front of everyone. When he had been trying to humiliate me.&lt;/p&gt;

&lt;p&gt;I wasn't the one with a problem.&lt;/p&gt;

&lt;p&gt;He was the one who couldn't stand being wrong.&lt;/p&gt;

&lt;p&gt;In these companies, they don't want people who think.&lt;/p&gt;

&lt;p&gt;They want people who validate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 5 — The Open Space
&lt;/h2&gt;

&lt;p&gt;In an open space, you don't model systems. You survive the noise.&lt;/p&gt;

&lt;p&gt;You don't think. You manage interruptions.&lt;/p&gt;

&lt;p&gt;The industry calls that collaboration.&lt;/p&gt;

&lt;p&gt;I call it destroying the concentration of the people who need it to work.&lt;/p&gt;

&lt;p&gt;I turned down assignments because of this.&lt;/p&gt;

&lt;p&gt;A line I never crossed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 6 — Logic-immo
&lt;/h2&gt;

&lt;p&gt;One exception.&lt;/p&gt;

&lt;p&gt;Just one in five years.&lt;/p&gt;

&lt;p&gt;Logic-immo. 2008.&lt;/p&gt;

&lt;p&gt;Brought in for Zend Framework.&lt;/p&gt;

&lt;p&gt;Left having designed and built an Oracle-to-MySQL data extraction system in PHP CLI.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because someone had looked at what I could actually do.&lt;/p&gt;

&lt;p&gt;And decided to give priority to the need.&lt;/p&gt;

&lt;p&gt;Someone had read between the lines.&lt;/p&gt;

&lt;p&gt;Rare. Worth mentioning.&lt;/p&gt;

&lt;p&gt;In 2011, I was burned out.&lt;/p&gt;

&lt;p&gt;In 2012, my daughter was born.&lt;/p&gt;

&lt;p&gt;I left Paris.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 7 — The Regional Directory
&lt;/h2&gt;

&lt;p&gt;Back in the South-West.&lt;/p&gt;

&lt;p&gt;A client. A monstrous project: local directory, classifieds, events section for every town and village, dedicated site for every business.&lt;/p&gt;

&lt;p&gt;Over a hundred professional sites to generate and manage dynamically. Custom routing. Zend Framework. Smarty.&lt;/p&gt;

&lt;p&gt;A standard agency wouldn't have lasted three months.&lt;/p&gt;

&lt;p&gt;Not because of the project.&lt;/p&gt;

&lt;p&gt;Because of the client.&lt;/p&gt;

&lt;p&gt;Hypochondriac. Always wound up. Always in crisis mode. Unmanageable for anyone without patience.&lt;/p&gt;

&lt;p&gt;I held on. Ten months. Solo. Architecture built from scratch.&lt;/p&gt;

&lt;p&gt;To the point where I fantasised about a permanent contract.&lt;/p&gt;

&lt;p&gt;Any one. Just to never have that kind of client again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 8 — The B2B Platform
&lt;/h2&gt;

&lt;p&gt;A matchmaking platform for business conventions.&lt;/p&gt;

&lt;p&gt;The existing application took ten minutes to load.&lt;/p&gt;

&lt;p&gt;Ten minutes.&lt;/p&gt;

&lt;p&gt;Server ten years past its prime. First-generation Symfony. Poorly designed PostgreSQL database. Non-relational data. Queries going in every direction.&lt;/p&gt;

&lt;p&gt;Nobody had asked the question: why is it slow?&lt;/p&gt;

&lt;p&gt;I asked the question.&lt;/p&gt;

&lt;p&gt;Custom framework inspired by ZF, but lighter. MySQL. Data schema rebuilt from scratch. Processes rewritten from the ground up. Fluid, responsive, usable interface. And a proper server.&lt;/p&gt;

&lt;p&gt;Under thirty seconds.&lt;/p&gt;

&lt;p&gt;That's not magic.&lt;/p&gt;

&lt;p&gt;That's what happens when you look at the problem before touching the code.&lt;/p&gt;

&lt;p&gt;They fired me for incompetence the day after launch.&lt;/p&gt;

&lt;p&gt;Five thousand visits. Every meeting scheduled. The platform handled everything.&lt;/p&gt;

&lt;p&gt;I fix things. Then I get shown the door.&lt;/p&gt;

&lt;p&gt;The labour court ruled in my favour. Six months' compensation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 9 — The Projects Nobody Asked For
&lt;/h2&gt;

&lt;p&gt;Nobody commissioned these.&lt;/p&gt;

&lt;p&gt;They come from an impulse. A conversation. A problem I noticed.&lt;/p&gt;

&lt;p&gt;Dev.to ranks me in the top 7 of the week. I could say thanks and move on.&lt;/p&gt;

&lt;p&gt;Instead I implement RFC 2324.&lt;/p&gt;

&lt;p&gt;The HTTP protocol for controlling a coffee maker. A 1998 joke buried in the Internet standards. Implemented seriously. Raw asyncio TCP server. Per-pot locks. Compliant headers.&lt;/p&gt;

&lt;p&gt;In the comments, &lt;a class="mentioned-user" href="https://dev.to/sylwia-lask"&gt;@sylwia-lask&lt;/a&gt; asks when I'm going to implement a protocol for beer.&lt;/p&gt;

&lt;p&gt;I run with it.&lt;/p&gt;

&lt;p&gt;RFC 1516. The Hyper Text Beer Mug Control Protocol. Port 1414, a nod to Gdańsk.&lt;/p&gt;

&lt;p&gt;The CV as a graph of relationships — because a profile like mine isn't linear. A timeline says nothing. A graph says everything.&lt;/p&gt;

&lt;p&gt;AJC Bridge — write from WordPress, publish everywhere. Submitted to a challenge. Runner-up.&lt;/p&gt;

&lt;p&gt;No ticket. No meeting. No box.&lt;/p&gt;

&lt;p&gt;Just a problem, an impulse, and someone who gets on with it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 10 — The Handover and the Veto
&lt;/h2&gt;

&lt;p&gt;I coach a young entrepreneur. WordPress, Divi, Plesk.&lt;/p&gt;

&lt;p&gt;I set up his servers. I pass on what I know.&lt;/p&gt;

&lt;p&gt;And one simple principle: never put all your eggs in one basket. Always keep external backups.&lt;/p&gt;

&lt;p&gt;OVH burns down.&lt;/p&gt;

&lt;p&gt;He manages 60 sites. He loses them — theoretically.&lt;/p&gt;

&lt;p&gt;He calls me.&lt;/p&gt;

&lt;p&gt;48 hours later, everything is back up on new servers.&lt;/p&gt;

&lt;p&gt;It wasn't me who saved his sites.&lt;/p&gt;

&lt;p&gt;It was advice given months earlier.&lt;/p&gt;

&lt;p&gt;We keep a great relationship.&lt;/p&gt;

&lt;p&gt;He's part of a BNI. He puts my name forward for a training assignment.&lt;/p&gt;

&lt;p&gt;Veto.&lt;/p&gt;

&lt;p&gt;Not standardised.&lt;/p&gt;

&lt;p&gt;The system would rather go without someone who just saved 60 sites in under 48 hours.&lt;/p&gt;

&lt;p&gt;Think about that for a moment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exhibit 11 — The Classroom Aide
&lt;/h2&gt;

&lt;p&gt;I discovered this job over ten years ago.&lt;/p&gt;

&lt;p&gt;My daughter's mother is a classroom aide for students with disabilities. She told me about it. I thought it was a job that made sense.&lt;/p&gt;

&lt;p&gt;In 2021, a position opens near me. I jump at it.&lt;/p&gt;

&lt;p&gt;The recruiter confirms in under five minutes. I barely have time to go down the stairs, step outside the building — he's already calling me back to come sign the contract.&lt;/p&gt;

&lt;p&gt;For a while, it was exactly what I'd imagined. Students in difficulty. Concrete work. Visible results.&lt;/p&gt;

&lt;p&gt;Some of the students I supported no longer need a classroom aide today.&lt;/p&gt;

&lt;p&gt;That's the job.&lt;/p&gt;

&lt;p&gt;Except the expectations have shifted.&lt;/p&gt;

&lt;p&gt;Because there's no space in the specialist units. No space in the vocational streams. No space in the adapted classrooms.&lt;/p&gt;

&lt;p&gt;So these students get placed in mainstream education. And a classroom aide gets put in front of them.&lt;/p&gt;

&lt;p&gt;That's not educational support. That's the work of a specialist educator — paid twice as much, with the training and recognition that come with it.&lt;/p&gt;

&lt;p&gt;I'm paid minimum wage. Half-time.&lt;/p&gt;

&lt;p&gt;And some teachers have one request: shut up and stay in your lane.&lt;/p&gt;

&lt;p&gt;The system does here exactly what it does everywhere else.&lt;/p&gt;

&lt;p&gt;It puts the round peg in the square hole.&lt;/p&gt;

&lt;p&gt;And when it doesn't fit, it hits harder.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mechanism
&lt;/h2&gt;

&lt;p&gt;WeCoded talks about marginalised voices in tech.&lt;/p&gt;

&lt;p&gt;Marginalised. One word. Dozens of realities.&lt;/p&gt;

&lt;p&gt;It's not only about gender, skin colour, sexual orientation.&lt;/p&gt;

&lt;p&gt;Racism, homophobia, the rejection of an atypical career path, the rejection of the wrong kind of degree — all of it belongs to the same family.&lt;/p&gt;

&lt;p&gt;The family of refusing the other.&lt;/p&gt;

&lt;p&gt;The family of human stupidity organised into a system.&lt;/p&gt;

&lt;p&gt;The mechanism is always the same.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You don't fit in the box. So you don't exist. &lt;strong&gt;And you won't.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And that marginalisation hurts all the more because it makes no sense.&lt;/p&gt;

&lt;p&gt;It can be justified — process, boxes, grids.&lt;/p&gt;

&lt;p&gt;But every justification will itself be unjustifiable.&lt;/p&gt;




&lt;p&gt;What I want you to take away is this:&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;In all of these stories, one constant.&lt;/p&gt;

&lt;p&gt;I always solved the problem I was called in for.&lt;/p&gt;

&lt;p&gt;Always.&lt;/p&gt;

&lt;p&gt;And yet I remain "unclassifiable". Not employable. Not standardised. Not in the box.&lt;/p&gt;

&lt;p&gt;So the real question — the one that stings — is simple.&lt;/p&gt;

&lt;p&gt;Who's the problem?&lt;/p&gt;

&lt;p&gt;Me — the one who solves the problems?&lt;/p&gt;

&lt;p&gt;Or the system — the one that doesn't know what to do with me between fires?&lt;/p&gt;

&lt;p&gt;For thirty years, the industry has chosen to hit the peg.&lt;/p&gt;

&lt;p&gt;The peg is still round.&lt;/p&gt;

&lt;p&gt;And the hole is still square.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>wecoded</category>
      <category>dei</category>
      <category>career</category>
    </item>
    <item>
      <title>RFC 1516: A Build for the Community That Keeps 418 Alive</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Fri, 27 Feb 2026 15:47:21 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/rfc-1516-a-build-for-the-community-that-keeps-418-alive-3ah4</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/rfc-1516-a-build-for-the-community-that-keeps-418-alive-3ah4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In the comments of my &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/stop-ignoring-rfc-2324-its-the-most-important-protocol-youve-never-implemented-53pe"&gt;last article&lt;/a&gt;, Sylwia wrote:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__comment crayons-card my-2 p-0 overflow-hidden"&gt;
    &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/stop-ignoring-rfc-2324-its-the-most-important-protocol-youve-never-implemented-53pe" class="flex items-center gap-2 p-3 fs-s color-base-60 hover:color-base-90"&gt;
      

      &lt;span&gt;Comment on &lt;strong class="fw-medium color-base-90"&gt;Stop Ignoring RFC 2324. It's the Most Important Protocol You've Never Implemented.&lt;/strong&gt;&lt;/span&gt;
    &lt;/a&gt;
  &lt;div class="p-4"&gt;
    &lt;div class="flex items-center gap-2 mb-3"&gt;
      &lt;a href="/sylwia-lask" class="crayons-avatar crayons-avatar--l"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3535771%2Fe22860d5-274b-43c9-819b-56b162e5bd5a.jpeg" alt="sylwia-lask" class="crayons-avatar__image"&gt;
      &lt;/a&gt;
      &lt;div&gt;
        &lt;a href="/sylwia-lask" class="crayons-link fw-medium"&gt;Sylwia Laskowska&lt;/a&gt;
        &lt;span class="fs-xs color-base-60 ml-1"&gt;Feb 25&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="text-styles"&gt;
      &lt;p&gt;Awesome😄&lt;br&gt;
Now I’m just waiting, Pascal, until you invent your own protocol — maybe something like a beer brewing protocol next? 🍺&lt;/p&gt;


    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;I replied: &lt;em&gt;"...&lt;code&gt;HTBMCP/1.0&lt;/code&gt; might be next. Hyper Text Beer Mug Control Protocol. Watch this space."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is that space.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;Before the code: a question worth asking.&lt;/p&gt;

&lt;p&gt;The DEV prompt says &lt;em&gt;"build for your community."&lt;/em&gt; The obvious answer, for a developer, is to build something useful for other developers — a tool, a library, a dashboard. Something that solves a real problem.&lt;/p&gt;

&lt;p&gt;But there's a community that rarely gets named as such, and it's one I'm genuinely part of: &lt;strong&gt;the community of developers who find meaning in craft for its own sake&lt;/strong&gt;. Who implement a coffee pot RFC not because it ships a feature, but because it teaches something real. Who write April Fools' specs with the same rigor they'd apply to production systems. Who understand that absurdity taken seriously is one of the most honest forms of technical education.&lt;/p&gt;

&lt;p&gt;RFC 2324 has been cited, forked, debated, and defended for 26 years because it speaks directly to that community. It says: the standards process can be playful — rigorously, precisely playful. That's worth protecting.&lt;/p&gt;

&lt;p&gt;HTBMCP/1.0 is a contribution to that tradition. It's for the developer who reads a comment about beer protocols at midnight and thinks &lt;em&gt;yes, someone should actually do that.&lt;/em&gt; It's for &lt;a class="mentioned-user" href="https://dev.to/sylwia-lask"&gt;@sylwia-lask&lt;/a&gt;, who asked. It's for anyone who's ever cited RFC 2324 in a code review and meant it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RFC 1516&lt;/strong&gt; — the Hyper Text Beer Mug Control Protocol (HTBMCP/1.0), to be published April 1st 2026. And its full implementation.&lt;/p&gt;

&lt;p&gt;HTBMCP is the spiritual successor to RFC 2324 (HTCPCP, the coffee pot protocol). Where HTCPCP controls coffee pots, HTBMCP controls networked beer taps. It extends HTTP with five custom methods, three new headers, and one new error code.&lt;/p&gt;

&lt;p&gt;The default port is &lt;strong&gt;1414&lt;/strong&gt; — a memorial. In 1414, the municipal archives of Gdansk recorded the earliest known written attestation of the word &lt;em&gt;"piwo"&lt;/em&gt; (beer, in Polish). That document was lost during the Second World War.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Port 1414 is therefore a memorial as much as a transport binding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation consists of four parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An interactive browser simulator&lt;/strong&gt; — a self-contained HTML/JS file, no backend, no install. A complete HTBMCP state machine running in the browser with animated tap visuals, all six response codes, the full header set. Build the simulator before the server: it forces you to model the protocol as data before you model it as routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A raw asyncio TCP server&lt;/strong&gt; (&lt;code&gt;server.py&lt;/code&gt;) — because uvicorn rejects &lt;code&gt;TAP&lt;/code&gt;, &lt;code&gt;POUR&lt;/code&gt;, and &lt;code&gt;WHEN&lt;/code&gt; at the socket layer. These are valid RFC 7230 tokens, but they're not in the IANA method registry. The fix is a minimal HTTP/1.1 parser over raw TCP that accepts any valid token as a method name. This is the correct approach: HTBMCP is its own protocol, and owning the transport layer is the honest implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A FastAPI application&lt;/strong&gt; (&lt;code&gt;main.py&lt;/code&gt;) — used exclusively by the test suite. &lt;code&gt;TestClient&lt;/code&gt; bypasses the HTTP transport entirely, so custom methods work fine there. You get structured routing and validation without the socket-level rejection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;41 tests&lt;/strong&gt; covering the full protocol: every method, every error code, the Stout temperature MUST NOT, the goblet Trappist-only rule, &lt;code&gt;brew_version&lt;/code&gt; conflict detection, the &lt;code&gt;piwo://&lt;/code&gt; Gdansk memorial tap, and the WHEN-once-is-sufficient invariant.&lt;/p&gt;

&lt;h3&gt;
  
  
  The protocol, in brief
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TAP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opens/closes a session. Must precede POUR. POST accepted but &lt;em&gt;STRONGLY DISCOURAGED.&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POUR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dispenses beer (&lt;code&gt;start&lt;/code&gt;/&lt;code&gt;stop&lt;/code&gt;). MUST NOT execute without open TAP session.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WHEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stops foam. Inherited from HTCPCP. &lt;em&gt;There is no WHEN-WHEN method. Once is sufficient.&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns tap state. &lt;em&gt;Contains no beer. This is an important distinction.&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROPFIND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discovers styles, temperatures, foam levels. Borrowed from WebDAV via HTCPCP.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;New error code: &lt;strong&gt;&lt;code&gt;419 I'm a Wine Glass&lt;/code&gt;&lt;/strong&gt;. A wine glass is not a mug. A wine glass has no handle. Beer poured into a wine glass loses carbonation 23% faster — &lt;em&gt;a figure the authors have not verified but feel is directionally correct.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A server MUST NOT return 419 for an empty keg. The keg is not a wine glass. These are different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🍺 &lt;strong&gt;HTBMCP Simulator&lt;/strong&gt; — open it directly in your browser. No install, no server, no dependencies. A complete HTBMCP state machine running client-side: animated tap visuals, all six response codes, the full header set.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://htbmcp.benchwiseunderflow.in/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;htbmcp.benchwiseunderflow.in&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kxxtz1lbq5tj899cxc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kxxtz1lbq5tj899cxc9.png" alt="HTBMCP Simulator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try to TAP a Stout at 3°C. Watch the 406 fire with the exact RFC citation. Try the wine glass tab. Say WHEN.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Attempting to pour into a wine glass results in a protocol-level error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9uspahqx2mpsxmn08ek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9uspahqx2mpsxmn08ek.png" alt="419 “I'm a Wine Glass”"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the full server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python server.py
&lt;span class="c"&gt;# 🍺  HTBMCP/1.0 listening on 0.0.0.0:1414&lt;/span&gt;
&lt;span class="c"&gt;#     Port 1414 — memorial: Gdansk municipal archives, 1414&lt;/span&gt;

&lt;span class="c"&gt;# TAP open&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; TAP http://localhost:1414/tap/tap-1 &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: message/mugpot"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept-Style: IPA"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept-Temperature: 8"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept-Foam: normal"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"open"&lt;/span&gt;

&lt;span class="c"&gt;# POUR&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POUR http://localhost:1414/tap/tap-1/pour &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: message/mugpot"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"start"&lt;/span&gt;

&lt;span class="c"&gt;# WHEN — enough foam&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; WHEN http://localhost:1414/tap/tap-1/when

&lt;span class="c"&gt;# 419 — I'm a Wine Glass&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POUR http://localhost:1414/wine-glass/tap-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code is on Github repository:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pcescato" rel="noopener noreferrer"&gt;
        pcescato
      &lt;/a&gt; / &lt;a href="https://github.com/pcescato/htbmcp" rel="noopener noreferrer"&gt;
        htbmcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;HTBMCP/1.0 — Hyper Text Beer Mug Control Protocol&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"There is beer all around the world. Increasingly, in a world in which computation is ubiquitous, the consumption of beer in proximity to networked devices creates a strong operational requirement."&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;— RFC 1516, Abstract&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is this?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;This repository is a full implementation of &lt;strong&gt;RFC 1516&lt;/strong&gt; — the Hyper Text Beer Mug Control Protocol (HTBMCP/1.0), to be published April 1st, 2026.&lt;/p&gt;

&lt;p&gt;HTBMCP is the spiritual successor to &lt;a href="https://tools.ietf.org/html/rfc2324" rel="nofollow noopener noreferrer"&gt;RFC 2324&lt;/a&gt; (HTCPCP/1.0, the coffee pot protocol). Where HTCPCP controls coffee pots, HTBMCP controls networked beer taps. The protocol extends HTTP with custom methods, headers, and error codes designed specifically for the distributed dispensing of fermented malt beverages.&lt;/p&gt;

&lt;p&gt;The default port is &lt;strong&gt;1414&lt;/strong&gt; — a memorial to the earliest known written attestation of the word &lt;em&gt;"piwo"&lt;/em&gt; (beer, in Polish) in the municipal archives of Gdansk, 1414. That document no longer exists. We pour one out.&lt;/p&gt;…&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pcescato/htbmcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;p&gt;The full implementation — TCP server, FastAPI test app, registry, 41 tests, browser simulator, and README. RFC 1516 itself is available in the repository. It will be formally published on April 1st, 2026. As tradition demands.&lt;/p&gt;

&lt;h3&gt;
  
  
  The uvicorn problem — and the solution
&lt;/h3&gt;

&lt;p&gt;This is the key technical lesson, and it's worth making explicit.&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;# ❌ This will NOT work for HTBMCP methods&lt;/span&gt;
&lt;span class="c"&gt;# uvicorn → h11 → validates method against IANA registry → rejects TAP/POUR/WHEN&lt;/span&gt;
&lt;span class="c"&gt;# before any application code runs&lt;/span&gt;
uvicorn main:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix — a minimal HTTP/1.1 parser over raw asyncio TCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# accepts any RFC 7230 token as method
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1414&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;parse_request()&lt;/code&gt; splits on the first space and takes whatever token it finds — &lt;code&gt;TAP&lt;/code&gt;, &lt;code&gt;POUR&lt;/code&gt;, &lt;code&gt;WHEN&lt;/code&gt;, or anything else that's RFC 7230-valid. No registry check. This is correct: HTBMCP defines its own protocol. The transport layer should not enforce HTTP's method vocabulary on a protocol that extends it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temperature validation — MUST vs SHOULD NOT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tap_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_temp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# RFC 1516 §3.2.3:
&lt;/span&gt;    &lt;span class="c1"&gt;# "A server MUST NOT serve a Stout at 3°C.
&lt;/span&gt;    &lt;span class="c1"&gt;#  This is not a SHOULD NOT. This is a MUST NOT.
&lt;/span&gt;    &lt;span class="c1"&gt;#  The authors feel strongly about this."
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;build_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Temperature violation — MUST NOT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A server MUST NOT serve a Stout at 3°C. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is not a SHOULD NOT. This is a MUST NOT.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allowed_range&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10–13°C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RFC 1516 §3.2.3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The RFC distinguishes &lt;code&gt;MUST NOT&lt;/code&gt; from &lt;code&gt;SHOULD NOT&lt;/code&gt; with care. The Stout temperature constraint is a hard rule, not a recommendation. Testing that distinction is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_tap_406_stout_too_cold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;RFC 1516 §3.2.3: MUST NOT. This is not a SHOULD NOT.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_tap_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tap_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tap-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;406&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MUST NOT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;piwo://&lt;/code&gt; tap — port 1414's justification, made navigable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tap-gdansk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tap-gdansk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;piwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Polish — RFC 1516 §1, port 1414 memorial
&lt;/span&gt;    &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pressure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compatible_styles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pilsner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_gdansk_piwo_tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;piwo:// scheme — Gdansk 1414. We pour one out.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_tap_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tap_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tap-gdansk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;TAP_REGISTRY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tap-gdansk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;piwo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stack&lt;/strong&gt;: Python 3.12, FastAPI (test suite only), asyncio raw TCP (real server), structlog, pytest. Single-file HTML/JS simulator with no dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build order&lt;/strong&gt;: simulator first, server second, tests throughout. This is the lesson from the HTCPCP implementation: build the thing you can demo immediately, then build the thing that's correct. The simulator forced every protocol decision to be made as UI state before it became a routing decision.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The protocol is not just simulated — it is fully implemented and validated with a dedicated test suite.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzir7q3o1x75flt42xao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzir7q3o1x75flt42xao.png" alt="Test Coverage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The HTCPCP comparison&lt;/strong&gt;: HTBMCP adds a session model (&lt;code&gt;TAP&lt;/code&gt; as initiator with &lt;code&gt;brew_version&lt;/code&gt; tokens for concurrent access), promotes foam to a first-class protocol feature (&lt;code&gt;Accept-Foam&lt;/code&gt; with five levels including &lt;code&gt;belgian&lt;/code&gt; — implementation-defined, but significant), and introduces &lt;code&gt;419 I'm a Wine Glass&lt;/code&gt; alongside &lt;code&gt;418 I'm a teapot&lt;/code&gt;. Where 418 says &lt;em&gt;you are the wrong device&lt;/em&gt;, 419 says &lt;em&gt;you are using the wrong vessel&lt;/em&gt;. The error taxonomy matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this actually teaches&lt;/strong&gt;: The Stout MUST NOT forces you to think about hard constraints vs recommendations. The &lt;code&gt;brew_version&lt;/code&gt; conflict detection forces you to think about optimistic locking. The goblet Trappist-only rule forces you to think about domain validation. The uvicorn rejection forces you to think about where the HTTP stack lives. These are not beer problems.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Reinheitsgebot requires no acknowledgement. It has been enforcing standards compliance since 1516 without asking for credit.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>showdev</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>Stop Ignoring RFC 2324. It's the Most Important Protocol You've Never Implemented.</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Tue, 24 Feb 2026 23:44:05 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/stop-ignoring-rfc-2324-its-the-most-important-protocol-youve-never-implemented-53pe</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/stop-ignoring-rfc-2324-its-the-most-important-protocol-youve-never-implemented-53pe</guid>
      <description>&lt;p&gt;Some RFCs change the world — TCP/IP, HTTP/2, TLS 1.3.&lt;/p&gt;

&lt;p&gt;And then there's &lt;strong&gt;RFC 2324&lt;/strong&gt;, published April 1st, 1998, defining the &lt;strong&gt;Hyper Text Coffee Pot Control Protocol&lt;/strong&gt; (HTCPCP/1.0). Its purpose: control, monitor, and diagnose coffee pots over a network.&lt;/p&gt;

&lt;p&gt;No, it's not a joke. Well, it is. But it's &lt;strong&gt;written seriously enough to actually implement&lt;/strong&gt;. Emacs did it. We will too.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief History of the Web's Most Honorable RFC
&lt;/h2&gt;

&lt;p&gt;RFC 2324 is an IETF April Fools' joke authored by Larry Masinter. It extends HTTP with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;New &lt;strong&gt;HTTP methods&lt;/strong&gt;: &lt;code&gt;BREW&lt;/code&gt;, &lt;code&gt;WHEN&lt;/code&gt;, &lt;code&gt;PROPFIND&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new &lt;strong&gt;header&lt;/strong&gt;: &lt;code&gt;Accept-Additions&lt;/code&gt; (for milk, sugar, whisky — yes, whisky)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new &lt;strong&gt;URI scheme&lt;/strong&gt;: &lt;code&gt;coffee://&lt;/code&gt; (and &lt;code&gt;koffie://&lt;/code&gt;, &lt;code&gt;café://&lt;/code&gt;, and 26 other translations)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Two new &lt;strong&gt;error codes&lt;/strong&gt; that changed internet history&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The error codes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;406 Not Acceptable  — The server cannot brew this coffee
418 I'm a teapot    — The server is a teapot, not a coffee pot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;418&lt;/strong&gt; became iconic. In 2017, a proposal to remove it from the IANA registry triggered an actual revolt across the dev community. Node.js, Go, Python — everyone kept it. The teapot won.&lt;/p&gt;

&lt;p&gt;In 2014, &lt;strong&gt;RFC 7168&lt;/strong&gt; extended the protocol to tea (&lt;em&gt;HTCPCP-TEA&lt;/em&gt;), adding the &lt;code&gt;message/teapot&lt;/code&gt; MIME type and the requirement to distinguish an Earl Grey from a Darjeeling. Rigor in absurdity.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the RFC Actually Defines
&lt;/h2&gt;

&lt;p&gt;Before writing a single line, read the spec. That's the exercise.&lt;/p&gt;
&lt;h3&gt;
  
  
  The new methods
&lt;/h3&gt;

&lt;p&gt;MethodRole&lt;code&gt;BREW&lt;/code&gt; (or &lt;code&gt;POST&lt;/code&gt;)Trigger an infusion&lt;code&gt;GET&lt;/code&gt;Get the coffee pot's current state&lt;code&gt;PROPFIND&lt;/code&gt;List available additions&lt;code&gt;WHEN&lt;/code&gt;&lt;strong&gt;Stop pouring the milk&lt;/strong&gt; — the client says "when!"&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;WHEN&lt;/code&gt; method is the most beautiful. It models a human exchange ("tell me when") as an HTTP request. A masterpiece of protocol anthropomorphism.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Accept-Additions header
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;BREW /coffee-pot-1 HTCPCP/1.0
Accept-Additions: milk-type=Whole-milk; syrup-type=Vanilla; alcohol-type=Whisky
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Legal values include &lt;code&gt;Cream&lt;/code&gt;, &lt;code&gt;Half-and-half&lt;/code&gt;, &lt;code&gt;Whole-milk&lt;/code&gt;, &lt;code&gt;Non-Dairy&lt;/code&gt;, syrups (&lt;code&gt;Vanilla&lt;/code&gt;, &lt;code&gt;Chocolate&lt;/code&gt;, &lt;code&gt;Raspberry&lt;/code&gt;, &lt;code&gt;Almond&lt;/code&gt;), and spirits (&lt;code&gt;Whisky&lt;/code&gt;, &lt;code&gt;Rum&lt;/code&gt;, &lt;code&gt;Kahlua&lt;/code&gt;, &lt;code&gt;Aquavit&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Intentionally absent: any decaffeinated option. The RFC's comment on this is terse: &lt;em&gt;"What's the point?"&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1 — Play First: The Standalone Simulator
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of server code, I built a &lt;strong&gt;fully self-contained HTML/JS simulator&lt;/strong&gt; that runs entirely in the browser. No backend, no dependencies, no install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactive HTCPCP Dashboard&lt;/strong&gt;&lt;br&gt;


&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://htcpcp.benchwiseunderflow.in/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;htcpcp.benchwiseunderflow.in&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;The simulator is not a mock of the server — it &lt;em&gt;is&lt;/em&gt; a complete HTCPCP implementation, just in a different runtime. All the state lives in JavaScript: pot registry, brew history, status transitions, 418/406 logic. It's the fastest way to feel the protocol before committing to a stack.&lt;/p&gt;

&lt;p&gt;Try to BREW on a teapot. Watch the 418 fire. Select decaf and get a 406. Click WHEN mid-brew to stop the milk. Then come back here and build the production version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Ship It: The Production Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A word on uvicorn
&lt;/h3&gt;

&lt;p&gt;The natural instinct is &lt;code&gt;uvicorn main:app --reload&lt;/code&gt;. Don't. uvicorn validates HTTP method names at the &lt;strong&gt;socket level&lt;/strong&gt;, before any request parsing happens. &lt;code&gt;BREW&lt;/code&gt;, &lt;code&gt;WHEN&lt;/code&gt;, and &lt;code&gt;PROPFIND&lt;/code&gt; are not registered IANA methods, so uvicorn rejects them immediately with &lt;code&gt;Invalid HTTP request received&lt;/code&gt; — regardless of any FastAPI config.&lt;/p&gt;

&lt;p&gt;The fix: a raw asyncio TCP server (&lt;code&gt;server.py&lt;/code&gt;) with a minimal HTTP/1.1 parser that accepts any valid RFC 7230 token as a method name. Which &lt;code&gt;BREW&lt;/code&gt;, &lt;code&gt;WHEN&lt;/code&gt;, and &lt;code&gt;PROPFIND&lt;/code&gt; are. This is actually the more correct approach — HTCPCP defines its own protocol, and rolling your own transport layer is the honest implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python server.py
&lt;span class="c"&gt;# ☕  HTCPCP/1.0 — RFC 2324  (127.0.0.1:2324)&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; BREW http://localhost:2324/coffee/pot-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept-Additions: milk-type=Whole-milk; alcohol-type=Whisky"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastAPI + &lt;code&gt;main.py&lt;/code&gt; is still useful for one thing: the test suite. FastAPI's &lt;code&gt;TestClient&lt;/code&gt; bypasses the HTTP transport layer entirely, so custom methods work fine in tests — and you get all the validation and schema benefits of FastAPI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;pytest test_htcpcp.py -v   #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uses main.py + TestClient, no server.py needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Architecture: a pot registry
&lt;/h3&gt;

&lt;p&gt;First architectural decision: model the entities properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PotType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;COFFEE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;TEAPOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;teapot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PotStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;IDLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;idle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;BREWING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brewing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;POURING_MILK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pouring-milk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;READY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoffeePot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;pot_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PotType&lt;/span&gt;
    &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PotStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PotStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IDLE&lt;/span&gt;
    &lt;span class="n"&gt;varieties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;brew_history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# The registry — the core of the architecture
&lt;/span&gt;&lt;span class="n"&gt;POT_REGISTRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CoffeePot&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee://pot-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoffeePot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pot-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PotType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COFFEE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;varieties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Espresso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lungo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Americano&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee://pot-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoffeePot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pot-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PotType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COFFEE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;varieties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Espresso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tea://kettle-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoffeePot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kettle-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PotType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAPOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;varieties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Earl Grey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chamomile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Darjeeling&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parsing the Accept-Additions header
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;

&lt;span class="n"&gt;SUPPORTED_ADDITIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Half-and-half&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Whole-milk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Part-Skim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Skim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Non-Dairy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;syrup-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vanilla&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Almond&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raspberry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chocolate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sweetener-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sugar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Honey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spice-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cinnamon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cardamom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alcohol-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Whisky&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kahlua&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aquavit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_accept_additions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;additions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_additions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# RFC 2324 §2.1.1: no decaf option — intentionally
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not Acceptable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Decaffeinated coffee? What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the point?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RFC 2324 §2.1.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;unsupported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SUPPORTED_ADDITIONS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SUPPORTED_ADDITIONS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unsupported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;406&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not Acceptable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported_additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unsupported&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The HTCPCP endpoints
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTCPCP/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_pot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CoffeePot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;POT_REGISTRY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;POT_REGISTRY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tea://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pot not found in registry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;

&lt;span class="c1"&gt;# ── BREW ────────────────────────────────────────────────────────────────────
&lt;/span&gt;
&lt;span class="nd"&gt;@app.api_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/{pot_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;brew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_pot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# RFC 2324 §2.3.2: teapot → 418, mandatory
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pot_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PotType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEAPOT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m a teapot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The requested entity body is short and stout.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tip me over and pour me out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pot_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RFC 2324 §2.3.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggestion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use coffee://pot-1/brew instead&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pot is empty. Refill required.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;additions_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accept-additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;additions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_accept_additions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;additions_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;validate_additions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 406 if decaf or invalid additions
&lt;/span&gt;
    &lt;span class="n"&gt;brew_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brew_history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brew_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brew_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PotStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BREWING&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# Milk requested → enter pouring-milk state
&lt;/span&gt;    &lt;span class="n"&gt;has_milk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk-type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_milk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PotStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POURING_MILK&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brew_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;brew_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Coffee is brewing.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accept-additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk_pouring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;has_milk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;protocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTCPCP/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# ── GET ──────────────────────────────────────────────────────────────────────
&lt;/span&gt;
&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/{pot_id}/status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_pot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pot_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pot_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; cups&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;brew_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;brew_history&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;varieties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;varieties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;protocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTCPCP/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ── PROPFIND ─────────────────────────────────────────────────────────────────
&lt;/span&gt;
&lt;span class="nd"&gt;@app.api_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/{pot_id}/additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROPFIND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;propfind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;get_pot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;SUPPORTED_ADDITIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decaf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NOT_ACCEPTABLE — What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the point? (RFC 2324 §2.1.1)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ── WHEN ─────────────────────────────────────────────────────────────────────
&lt;/span&gt;
&lt;span class="nd"&gt;@app.api_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/{pot_id}/stop-milk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    RFC 2324 §2.1.3 — WHEN
    Sent when the client determines that enough milk has been poured.
    The server must stop immediately.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;pot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_pot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;PotStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POURING_MILK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHEN acknowledged.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No milk was being poured, but your enthusiasm is appreciated.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RFC 2324 §2.1.3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;pot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PotStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BREWING&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Milk pouring stopped.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The server has acknowledged WHEN and stopped the milk stream.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;protocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTCPCP/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rfc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RFC 2324 §2.1.3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Middleware: enforce HTCPCP headers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;starlette.middleware.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseHTTPMiddleware&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTCPCPMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseHTTPMiddleware&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Protocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTCPCP/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-RFC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RFC-2324&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# Detect a BREW on a non-coffee route and punish accordingly
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Wrong universe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREW is only valid on coffee:// URIs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTCPCPMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Structured logs — because we're professionals
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;

&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# After a successful BREW:
&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;htcpcp.brew&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;brew_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;brew_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTCPCP/1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# On 418:
&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;htcpcp.teapot_detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pot_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;teapot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Teapot attempted to brew coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which produces in your JSON logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"htcpcp.brew"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pot_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pot-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"brew_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"additions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"milk-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Whole-milk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"alcohol-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Whisky"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTCPCP/1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"htcpcp.teapot_detected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pot_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kettle-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What This Actually Teaches You
&lt;/h2&gt;

&lt;p&gt;Implementing an April Fools' RFC is a serious exercise in disguise. You end up learning:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to read an RFC properly&lt;/strong&gt; — distinguishing MUST, SHOULD, MAY. RFC 2324 uses all three with care. The 418 is a MUST if the server is a teapot. A broken coffee machine should return 503 — not 418. That's a common mistake, and it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How the HTTP stack actually works&lt;/strong&gt; — trying to use uvicorn with &lt;code&gt;BREW&lt;/code&gt; reveals that method validation happens at the socket level, before h11, before FastAPI, before your code. You end up writing a raw asyncio TCP server to get HTCPCP working for real. That's not a detour — that's the point. You now understand the HTTP request pipeline better than most devs who've shipped production APIs for years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to think in entities&lt;/strong&gt; — the pot registry, the &lt;code&gt;CoffeePot&lt;/code&gt; vs &lt;code&gt;Teapot&lt;/code&gt; distinction, routing by &lt;code&gt;coffee://&lt;/code&gt; URI: this is real domain modeling. The joke forces you to take it seriously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to model state machines&lt;/strong&gt; — &lt;code&gt;idle → brewing → pouring-milk → ready&lt;/code&gt; is a textbook workflow. WHEN is a client-driven transition. You'll see this pattern everywhere in production systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to write integration tests for absurd-but-useful edge cases&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_teapot_cannot_brew&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/kettle-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m a teapot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_decaf_is_not_acceptable&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/pot-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept-Additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decaf=true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;406&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_when_stops_milk&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BREW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/pot-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept-Additions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;milk-type=Whole-milk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/coffee/pot-1/stop-milk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;418 survived every attempt to kill it because it represents something real: &lt;strong&gt;developers are allowed to be playful&lt;/strong&gt;. An April Fools' RFC published today would probably get killed in committee within a week. The one from 1998 has lasted 26 years.&lt;/p&gt;

&lt;p&gt;What makes RFC 2324 remarkable is that it takes absurdity seriously — it has a real state machine, real error codes with precise semantics, a real extension (RFC 7168 for tea). It mocks formalism by respecting it perfectly.&lt;/p&gt;

&lt;p&gt;That's exactly how we should build our own systems.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Simulator (HTML/JS standalone), &lt;code&gt;server.py&lt;/code&gt; (raw TCP), &lt;code&gt;main.py&lt;/code&gt; + full test suite — on Github: &lt;a href="https://github.com/pcescato/htcpcp/" rel="noopener noreferrer"&gt;https://github.com/pcescato/htcpcp/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;RFC 2324: &lt;a href="https://tools.ietf.org/html/rfc2324" rel="noopener noreferrer"&gt;https://tools.ietf.org/html/rfc2324&lt;/a&gt;&lt;/em&gt; &lt;em&gt;RFC 7168: &lt;a href="https://tools.ietf.org/html/rfc7168" rel="noopener noreferrer"&gt;https://tools.ietf.org/html/rfc7168&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fun</category>
      <category>http</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Article Isn’t the Real Content</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Sat, 21 Feb 2026 12:28:14 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/your-article-isnt-the-real-content-2e67</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/your-article-isnt-the-real-content-2e67</guid>
      <description>&lt;p&gt;The article is often the least interesting thing that happens after you publish.&lt;/p&gt;

&lt;p&gt;Not always. But more often than we're willing to admit.&lt;/p&gt;

&lt;p&gt;I saw this comment recently and couldn't unsee it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Real war stories have specifics — the exact error, the wrong turn you took first, the fix that seemed obvious in retrospect. Generated stuff stays vague. Honestly the comments on that post became better content than the post itself."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last sentence describes something we rarely say out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens when you write with real specifics
&lt;/h2&gt;

&lt;p&gt;Not tips. Not frameworks. The actual error. The wrong turn. The fix you missed for three hours.&lt;/p&gt;

&lt;p&gt;That level of detail does something polished content almost never does: it makes other builders recognize themselves instantly.&lt;/p&gt;

&lt;p&gt;And recognition doesn't produce passive reading. It produces contribution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The article doesn't contain the knowledge. It releases it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Publishing without the discussion is publishing an incomplete object.
&lt;/h2&gt;

&lt;p&gt;The post and the thread aren't two separate things. One makes the other possible. Strip the comments and you lose half the object. Strip the post and the comments have no spine.&lt;/p&gt;

&lt;p&gt;We treat articles as finished products and comments as disposable noise. So we archive the post and let the discussion rot.&lt;/p&gt;

&lt;p&gt;Which means we're systematically preserving the least interesting half of the knowledge.&lt;/p&gt;

&lt;p&gt;We optimize for authorship. We don't preserve conversations.&lt;/p&gt;

&lt;p&gt;Maybe we didn't even want to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where did the real fix appear — in the article or in the replies?
&lt;/h2&gt;

&lt;p&gt;Link a post where the comments mattered more than the text.&lt;/p&gt;

&lt;p&gt;What's the best technical insight you found buried three levels deep in a thread — that no article ever captured?&lt;/p&gt;

&lt;p&gt;If you think the article still matters more than the discussion, I'd genuinely like to hear why — because I'm no longer sure.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
      <category>writing</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Who Are We Still Writing Technical Articles For?</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Tue, 17 Feb 2026 12:34:14 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/who-are-we-still-writing-technical-articles-for-i64</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/who-are-we-still-writing-technical-articles-for-i64</guid>
      <description>&lt;p&gt;I recently read an article by &lt;a class="mentioned-user" href="https://dev.to/miracool"&gt;@miracool&lt;/a&gt; asking the question: &lt;a href="https://dev.to/miracool/do-people-still-genuinely-care-about-technical-articles--1hfk"&gt;do people still genuinely care about technical articles&lt;/a&gt;? My answer is nuanced: yes, but not everyone.&lt;/p&gt;

&lt;p&gt;Beyond that question, there is another, more fundamental one: &lt;em&gt;who do we choose to write for today?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Long-Term Trajectory
&lt;/h2&gt;

&lt;p&gt;I've been writing technical articles since 2010-2011. Some still exist in the Wayback Machine archives — &lt;a href="https://web.archive.org/web/20111223091611/http://www.expert-php.fr/mysql/contourner-une-limitation-de-mysql.html" rel="noopener noreferrer"&gt;like this one, dated December 2011&lt;/a&gt;, about a MySQL limitation that barely makes sense to address today. My blog has been running since 2016. Since August 2025, I've also been writing in English.&lt;/p&gt;

&lt;p&gt;I've never published at an industrial pace. I wrote when I had something to clarify, document, or share.&lt;/p&gt;

&lt;p&gt;Over this period, one observation stands out: some articles disappear within a few months, while others continue to be read years later. Not massively. But steadily. By readers who arrive through a specific search, who actually read, and who sometimes write back.&lt;/p&gt;

&lt;p&gt;It's almost always the same types of texts that survive: those that document an approach, a technical decision, a real problem encountered over time. Highly specific tutorials, on the other hand, meet an immediate need and then logically fade away.&lt;/p&gt;

&lt;p&gt;This isn't a theory. It's an observation built over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Consume Technical Knowledge Today
&lt;/h2&gt;

&lt;p&gt;Fifteen or twenty years ago, you bought a technical book and used it for years. I bought the PHP5 Bible in the mid-2000s. I relied on it for nearly three years. But back then, you couldn't find nearly as many up-to-date resources online as quickly as you can now.&lt;/p&gt;

&lt;p&gt;Today, access to technical knowledge is immediate. Resources are digital, abundant, constantly updated. Tools have simplified many deployments. Answers are available within seconds.&lt;/p&gt;

&lt;p&gt;This isn't better or worse. It's a different way of learning and working. I consume differently myself.&lt;/p&gt;

&lt;p&gt;But this evolution has a direct consequence: the lifespan of technical content has shortened. And that naturally changes the way we write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Articles Haven't Disappeared — They've Evolved
&lt;/h2&gt;

&lt;p&gt;We need to distinguish between two types of technical articles.&lt;/p&gt;

&lt;p&gt;There's &lt;em&gt;daily news&lt;/em&gt;: announcements, releases, updates. A new version of PostgreSQL. New MySQL features. A new project from an Apache incubator. These articles are widely read. They meet a need for technical current events. It's information.&lt;/p&gt;

&lt;p&gt;And there's the &lt;em&gt;in-depth magazine&lt;/em&gt;: detailed articles, experience reports, carefully constructed reflections. The National Geographic of tech. These articles reach a smaller but loyal audience. Readers who are genuinely interested.&lt;/p&gt;

&lt;p&gt;This distinction between technical news and substantive reflection isn't new. There have always been texts with no clear thinking behind them. Lists like "Top 5 WordPress Themes for 2026" or "10 Best Plugins for 2025" that enumerate tools with their pros and cons but offer no real analysis have always existed. Humans are perfectly capable of producing empty content. Artificial intelligence didn't create this problem — it simply amplified the volume.&lt;/p&gt;

&lt;p&gt;What remains rare are articles that go deeper. That not only show strengths and weaknesses but explain use cases, contexts, the reasoning behind choices. Those require structured thinking. As Boileau once said: what is well conceived is clearly stated.&lt;/p&gt;

&lt;p&gt;To illustrate this concretely, I submitted the same prompt to Gemini and ChatGPT: &lt;em&gt;"write a 1500-word article on 'who do we choose to write a technical article for?'"&lt;/em&gt;. Both produced correct, well-structured texts. ChatGPT even found some interesting angles — the translator-reader, the machine as an unexpected recipient. But both shared the same fundamental limitation: zero lived experience, zero concrete data, zero personal trajectory. Texts that are true for everyone, and therefore belong to no one.&lt;/p&gt;

&lt;p&gt;That's not a flaw in the tool. It's the direct consequence of a prompt with no real thinking behind it.&lt;/p&gt;

&lt;p&gt;AI produces articles, but left undirected, it doesn't produce substantive ones. You can ask it to write a tutorial on any technical subject and get a text that looks correct on the surface but is empty of genuine reflection.&lt;/p&gt;

&lt;p&gt;For my part, I use AI to structure my articles, to rephrase, to validate hypotheses. But I don't give it a generic prompt like &lt;em&gt;"write a 1500-word article on who we choose to write a technical article for."&lt;/em&gt; The thinking that feeds the text comes from my research, my experience, my history.&lt;/p&gt;

&lt;p&gt;And the drafts the AI produces — all of them, not just the first — are then reread, corrected, and reworked to match my own voice. This article itself is the result of a conversation with Claude and ChatGPT. But once that conversation was over, I reread it. I rewrote entire sections. I amended others. The work is greatly simplified by AI, but it isn't directly generated writing. It's augmented writing. The difference is essential.&lt;/p&gt;

&lt;p&gt;Both types of articles still exist. But they don't address the same audience or the same timeframe.&lt;/p&gt;

&lt;p&gt;What has changed is that the sheer mass of available information has paradoxically made in-depth articles more substantive, not less relevant. Since the basic tutorial is everywhere, what remains to be written is the reasoning behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I No Longer Write Tutorials
&lt;/h2&gt;

&lt;p&gt;For a long time, I wrote very detailed tutorials. Complete guides, designed to walk a reader from start to finish.&lt;/p&gt;

&lt;p&gt;Today, I don't write them anymore.&lt;/p&gt;

&lt;p&gt;Not because tutorials have become useless. They remain essential, particularly for beginners or specific use cases. But they are now produced faster and often better by others: official documentation, communities, interactive tools, technical assistants.&lt;/p&gt;

&lt;p&gt;A very precise tutorial also becomes obsolete faster. Versions change, methods evolve, abstractions multiply. In many cases, a quick search is enough to solve the problem.&lt;/p&gt;

&lt;p&gt;So it's not the tutorial that's disappearing. It's simply that I've stopped making it the core of my writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Writing Still Allows
&lt;/h2&gt;

&lt;p&gt;Technical writing retains a specific quality.&lt;/p&gt;

&lt;p&gt;It allows for measured reflection. Rereading. Precise citation. Continuity over time. A text can be found again, annotated, reused, reread years later.&lt;/p&gt;

&lt;p&gt;A video informs. An instant response helps solve a problem. But a structured text allows you to follow a line of reasoning and return to it.&lt;/p&gt;

&lt;p&gt;This isn't about the superiority of formats. It's about function. Writing remains particularly well-suited for documenting a technical experience over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Holds Its Value
&lt;/h2&gt;

&lt;p&gt;Over time, I've shifted my center of gravity.&lt;/p&gt;

&lt;p&gt;I'm less focused on explaining &lt;em&gt;exactly how to do something&lt;/em&gt;. Others do that very well and very quickly. I try instead to document an approach, a technical choice, a feedback report, a thought process, what worked and what didn't.&lt;/p&gt;

&lt;p&gt;This type of text doesn't meet an immediate need. It speaks to those who want to understand a line of reasoning, not just execute a procedure.&lt;/p&gt;

&lt;p&gt;It probably reaches fewer people. But it lasts longer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Different Audience
&lt;/h2&gt;

&lt;p&gt;This audience exists. I know because some articles continue to be read long after publication. They show up in stats. They're found through specific searches. Sometimes they prompt a message months or years later.&lt;/p&gt;

&lt;p&gt;I've had articles that were read far more widely. Between 2017 and 2019, an article about Twenty Seventeen, the WordPress theme, generated 20 to 25,000 reads and over a hundred comments. I also see what viral articles on dev.to look like: thousands of likes, hundreds of comments.&lt;/p&gt;

&lt;p&gt;That's not what I'm talking about.&lt;/p&gt;

&lt;p&gt;Writing in English hasn't given me a massive audience. Between August 2025 and today, 25 articles have generated around 14,000 views. Modest numbers.&lt;/p&gt;

&lt;p&gt;But it's a different audience. Switching to English wasn't a strategy. It came naturally. My technical experience is international. And this English-speaking audience — partly made up of people who write themselves, and above all readers interested in this kind of reflection — aligns more closely with who I am.&lt;/p&gt;

&lt;p&gt;I'm not addressing a majority. I'm addressing people who build, who think, who look for experience reports rather than immediate answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Write Publicly?
&lt;/h2&gt;

&lt;p&gt;If this audience is limited, why keep publishing? Why not simply keep private notes?&lt;/p&gt;

&lt;p&gt;Because public writing changes the nature of reflection. It forces you to clarify. To structure. To stand behind what you write. And sometimes, it creates a deferred conversation with people you will never meet directly.&lt;/p&gt;

&lt;p&gt;Publishing an in-depth technical article is contributing to a shared space for reflection. However modest. However quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Assumed Choice
&lt;/h2&gt;

&lt;p&gt;Writing substantive technical articles today isn't the fastest way to gain visibility. It's not the most efficient for producing content at volume either.&lt;/p&gt;

&lt;p&gt;But it's the approach that best matches the way I work and think.&lt;/p&gt;

&lt;p&gt;I write to document approaches and technical trajectories. For a handful of interested readers. For continuity over time.&lt;/p&gt;

&lt;p&gt;There's nothing heroic about this choice. It's simply consistent with how my practice has evolved.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Side note: this article is written in WordPress and published directly to dev.to via the API — &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/actually-static-when-wordpress-stops-being-the-enemy-37h5"&gt;I covered how that works in a previous article&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuing
&lt;/h2&gt;

&lt;p&gt;Technical articles haven't disappeared. Neither has their audience. It's simply different: less massive, more attentive.&lt;/p&gt;

&lt;p&gt;Writing today for that audience is a choice.&lt;/p&gt;

&lt;p&gt;The question, for me, isn't whether people still care about technical articles. They do. Or at least, some do.&lt;/p&gt;

&lt;p&gt;My question is more ethical: do we still want to write for those who truly care?&lt;/p&gt;

</description>
      <category>career</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>writing</category>
    </item>
    <item>
      <title>Actually Static: When WordPress Stops Being the Enemy</title>
      <dc:creator>Pascal CESCATO</dc:creator>
      <pubDate>Sun, 08 Feb 2026 13:28:48 +0000</pubDate>
      <link>https://dev.to/pascal_cescato_692b7a8a20/actually-static-when-wordpress-stops-being-the-enemy-37h5</link>
      <guid>https://dev.to/pascal_cescato_692b7a8a20/actually-static-when-wordpress-stops-being-the-enemy-37h5</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This post is for developers who like writing in WordPress but want the speed and safety of static sites — and the freedom to publish anywhere from a single editorial hub.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;/p&gt;
  &lt;strong&gt;📋 Table of Contents&lt;/strong&gt;
  &lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
What I Built

&lt;ul&gt;
&lt;li&gt;The Search for an Alternative&lt;/li&gt;
&lt;li&gt;Existing Tools: Effective but Heavy&lt;/li&gt;
&lt;li&gt;Tech Stack&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Demo

&lt;ul&gt;
&lt;li&gt;The Real-World Gauntlet&lt;/li&gt;
&lt;li&gt;Where Copilot CLI Accelerated Development&lt;/li&gt;
&lt;li&gt;Why Local Image Optimization Matters&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;How It Works&lt;/li&gt;
&lt;li&gt;Production Update: Multi-Destination Publishing&lt;/li&gt;
&lt;li&gt;Technical Highlights&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
My Experience with GitHub Copilot CLI

&lt;ul&gt;
&lt;li&gt;Specification First, Code Second&lt;/li&gt;
&lt;li&gt;The Work Pattern&lt;/li&gt;
&lt;li&gt;The Prompt Gallery: Steering the CLI&lt;/li&gt;
&lt;li&gt;What This Actually Means&lt;/li&gt;
&lt;li&gt;The Real Value&lt;/li&gt;
&lt;li&gt;The Development Rhythm&lt;/li&gt;
&lt;li&gt;Real Example: AVIF Generation Fix&lt;/li&gt;
&lt;li&gt;The Audit Trail Advantage&lt;/li&gt;
&lt;li&gt;Code Review: Validating Production Readiness&lt;/li&gt;
&lt;li&gt;WordPress.org Submission: The Road to Approval&lt;/li&gt;
&lt;li&gt;Addendum: Automating the Future with GitHub Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;
Final Thought
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I spent years wrestling with WordPress performance and security issues.&lt;/p&gt;

&lt;p&gt;Optimizing caching layers, hardening installations, fighting plugin bloat — all to keep public-facing sites running acceptably.&lt;/p&gt;

&lt;p&gt;Then I discovered static site generators. Hugo. Astro. Fast, secure, elegant.&lt;/p&gt;

&lt;p&gt;But months of tweaking themes, debugging build pipelines, and fighting with deployment workflows taught me something: I'd just traded one set of problems for another.&lt;/p&gt;

&lt;p&gt;Today, I use both. Not as competitors, but as partners.&lt;/p&gt;

&lt;p&gt;Here's the paradox I kept running into: &lt;strong&gt;WordPress is probably the best writing environment ever built&lt;/strong&gt;. The interface is mature, the editor works, and you can focus on what matters — writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static site generators are probably the best deployment target ever built&lt;/strong&gt;. Fast, secure, cheap to host, and scalable by default.&lt;/p&gt;

&lt;p&gt;So why do we keep choosing between them?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In a comment on &lt;a href="https://dev.to/pascal_cescato_692b7a8a20/from-wordpress-to-astro-three-days-to-reclaim-control-5dn2"&gt;one of my previous posts&lt;/a&gt;&lt;/strong&gt;, &lt;a class="mentioned-user" href="https://dev.to/juliecodestack"&gt;@juliecodestack&lt;/a&gt; captured this tension perfectly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I spent quite a lot of time tweaking Hugo sites instead of writing, and I'm afraid I'll do the same thing if I transfer to Astro."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the real problem. Not WordPress. Not Hugo. The friction between writing and deploying.&lt;/p&gt;

&lt;p&gt;The problem with keeping WordPress public-facing isn't the editor — it's everything else.&lt;/p&gt;

&lt;p&gt;Exposing a complete WordPress site to the public means decent hosting, heavy dependency on plugins — at minimum for security and SEO — and serious maintenance.&lt;/p&gt;

&lt;p&gt;And by default, performance is variable, to say the least.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Search for an Alternative&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This observation gradually led me to look for a different solution.&lt;/p&gt;

&lt;p&gt;For some time now, I've been moving my content publishing to static sites.&lt;/p&gt;

&lt;p&gt;But what I really wanted was simpler: keep WordPress as a writing environment while completely removing its public presence.&lt;/p&gt;

&lt;p&gt;At a time when static sites can be deployed in seconds on almost any infrastructure, keeping WordPress as a frontend doesn't always make much sense anymore — as long as you have a robust deployment solution.&lt;/p&gt;

&lt;p&gt;Write normally. Publish automatically.&lt;/p&gt;

&lt;p&gt;No manual export, no scripts to run, no friction.&lt;/p&gt;
&lt;h3&gt;
  
  
  Existing Tools: Effective but Heavy&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Having dozens of articles on my WordPress blogs, I developed a suite of Python tools capable of exporting a complete site to Hugo or Astro.&lt;/p&gt;

&lt;p&gt;Functional, reliable, but based on a global export logic: complete site generation, transformation, then deployment.&lt;/p&gt;

&lt;p&gt;An effective process, but heavy.&lt;/p&gt;

&lt;p&gt;And especially unnatural in a daily writing workflow.&lt;/p&gt;

&lt;p&gt;This search for a more fluid editorial workflow gave birth to the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: A WordPress plugin implementing per-post, multi-target publishing strategies — GitHub (static), dev.to (API), or both — with atomic commits, structured Markdown conversion, and deployment automation.&lt;/p&gt;

&lt;p&gt;It converts posts to Markdown with generator-compatible front matter, optimizes images (WebP/AVIF), commits atomically to GitHub, and can trigger static deployments via GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five publishing strategies&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;WordPress Only&lt;/strong&gt; – Pure WordPress site, plugin inactive&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;WordPress + dev.to&lt;/strong&gt; – Public WordPress site, optional dev.to syndication per post&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;GitHub Only&lt;/strong&gt; – Headless WordPress → static generator → GitHub Pages&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dev.to Only&lt;/strong&gt; – Headless WordPress → direct dev.to publishing&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dual (GitHub + dev.to)&lt;/strong&gt; – Static site as canonical + dev.to as controlled syndication&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Tech Stack&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;WordPress Core&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress 6.9+ (PHP 8.1+)&lt;/li&gt;
&lt;li&gt;Action Scheduler (async background processing)&lt;/li&gt;
&lt;li&gt;Native WordPress APIs (WordPress.org compliant)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Image Processing&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intervention Image (AVIF + WebP optimization)&lt;/li&gt;
&lt;li&gt;Local processing before upload (reduces GitHub Actions cost)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Publishing Destinations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub API (Trees API for atomic commits)&lt;/li&gt;
&lt;li&gt;dev.to API (Forem REST API for direct publishing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Static Site Generators&lt;/strong&gt; (via GitHub):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hugo (YAML/TOML front matter)&lt;/li&gt;
&lt;li&gt;Jekyll (different conventions)&lt;/li&gt;
&lt;li&gt;Astro (content collections)&lt;/li&gt;
&lt;li&gt;Eleventy (custom structures)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions (automated Hugo builds)&lt;/li&gt;
&lt;li&gt;GitHub Pages (free static hosting)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Universal adapter pattern (SSG-agnostic)&lt;/li&gt;
&lt;li&gt;Async queue system (Action Scheduler)&lt;/li&gt;
&lt;li&gt;Atomic commits (all-or-nothing sync)&lt;/li&gt;
&lt;li&gt;Strategy-based routing (5 publishing modes)&lt;/li&gt;
&lt;li&gt;Post-level sync control (per-post checkboxes)&lt;/li&gt;
&lt;li&gt;Headless mode with 301 redirects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Development Timeline&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core plugin development: 48 hours of active coding across 8 sessions&lt;/li&gt;
&lt;li&gt;WordPress.org compliance: 3 hours (code review + fixes)&lt;/li&gt;
&lt;li&gt;Documentation &amp;amp; testing: 3 hours&lt;/li&gt;
&lt;li&gt;Total: ~54 hours over 9 days (Feb 6-14, 2026)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 23 checkpoints represent iterative development—each a working, &lt;br&gt;
tested increment. Copilot CLI contributed ~70% of implementation time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features&lt;/strong&gt;:&lt;br&gt;
✅ Fully asynchronous sync (no admin blocking)&lt;br&gt;
✅ Atomic commits (Markdown + all images in one commit)&lt;br&gt;
✅ Native WordPress APIs only (WordPress.org compliant)&lt;br&gt;
✅ Multi-format image optimization (AVIF → WebP → Original)&lt;br&gt;
✅ Zero shell commands (100% GitHub API)&lt;br&gt;
✅ HTTPS + Personal Access Token authentication&lt;br&gt;
✅ WP-CLI commands for bulk operations&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The workflow is now operational.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick Test (3 minutes)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit: &lt;a href="https://githubcopilotchallenge.tsw.ovh/wp-admin" rel="noopener noreferrer"&gt;githubcopilotchallenge.tsw.ovh/wp-admin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Login: &lt;code&gt;tester&lt;/code&gt; / Password: &lt;code&gt;Github~Challenge/2k26&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a post, click Publish → Watch GitHub commit + Hugo deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: WordPress → Hugo in ~30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo version&lt;/strong&gt;: The live demo runs on commit &lt;a href="https://github.com/pcescato/ajc-bridge/commit/da4e82f575db0fa05b8f9a634cb9e824137938be" rel="noopener noreferrer"&gt;&lt;code&gt;da4e82f&lt;/code&gt;&lt;/a&gt; (frozen at challenge deadline, Feb 15, 2026). The &lt;a href="https://github.com/pcescato/ajc-bridge" rel="noopener noreferrer"&gt;main branch&lt;/a&gt; continues active development—Astro support added post-challenge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Result is visible on &lt;a href="https://pcescato.github.io/hugodemo/" rel="noopener noreferrer"&gt;the demo website&lt;/a&gt; - as you can see below:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://pcescato.github.io/hugodemo/posts/2026-02-08-why-i-finally-stopped-fighting-my-publishing-workflow/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpcescato.github.io%2Fhugodemo%2Fimages%2F1487%2Ffeatured.avif" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://pcescato.github.io/hugodemo/posts/2026-02-08-why-i-finally-stopped-fighting-my-publishing-workflow/" rel="noopener noreferrer" class="c-link"&gt;
            Why I Finally Stopped Fighting My Publishing Workflow | AJC Bridge
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            For years, I maintained two separate worlds.
World One: WordPress. Comfortable writing interface, familiar editor, everything just works. But slow page loads, constant security updates, plugin conflicts, and that nagging feeling that I’m running a Boeing 747 to deliver a postcard.
World Two: Static sites. Blazing fast, secure by default, costs pennies to host. But writing in Markdown files, committing to Git, running build commands, debugging deployment pipelines. I spent more time being a DevOps engineer than a writer.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpcescato.github.io%2Fhugodemo%2Ffavicon.ico"&gt;
          pcescato.github.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;You can also see commited files in &lt;a href="https://github.com/pcescato/hugodemo" rel="noopener noreferrer"&gt;the website repository&lt;/a&gt;, where you can also find the workflow in &lt;code&gt;.github/workflows&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Articles are written in WordPress, as before.&lt;/p&gt;

&lt;p&gt;When publishing or updating, a dedicated plugin automatically triggers synchronization to a GitHub repository.&lt;/p&gt;

&lt;p&gt;Each piece of content is converted to Markdown with Hugo-specific front matter, along with optimized images (WebP and AVIF).&lt;/p&gt;

&lt;p&gt;Everything is sent in a single commit via the GitHub API.&lt;/p&gt;

&lt;p&gt;A GitHub Actions workflow then takes over: static site generation, then deployment to GitHub Pages.&lt;/p&gt;

&lt;p&gt;Concretely, publishing in WordPress is now enough to put a complete static version of the site online, without manual export or additional intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real-World Gauntlet&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This wasn't a smooth 48-hour sprint. The project survived several reality checks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hugo Theme Version Hell&lt;/strong&gt;: The theme I wanted required Hugo 0.146.0 minimum. My local install was 0.139.0. GitHub Actions defaulted to 0.128.0. Each environment needed explicit version pinning, and debugging failures meant decoding cryptic TOML errors across three different build contexts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Pages URL Stuttering&lt;/strong&gt;: The deployed site initially rendered with broken internal links because Hugo's &lt;code&gt;baseURL&lt;/code&gt; configuration didn't match GitHub Pages' expectations. Pages built locally worked fine. CI builds deployed with relative paths pointing to void. Solution: hardcode the production URL in the workflow, accept that local previews would have slightly broken navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image Pipeline Memory Limits&lt;/strong&gt;: Processing 10+ images per post with AVIF encoding pushed PHP's memory limits on shared hosting. First attempt: fatal errors. Second attempt: disable AVIF, keep WebP. Final solution: increase &lt;code&gt;memory_limit&lt;/code&gt; to 512M and batch-process images sequentially instead of in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action Scheduler Race Conditions&lt;/strong&gt;: Early versions created duplicate commits when saving a post multiple times quickly. WordPress's &lt;code&gt;save_post&lt;/code&gt; hook fires on autosaves, manual saves, and quick edits. Needed: debouncing logic, transient locks, and post meta flags to prevent redundant syncs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP 8.1 Strictness&lt;/strong&gt;: A single &lt;code&gt;explode()&lt;/code&gt; call on a &lt;code&gt;null&lt;/code&gt; value was enough to freeze the entire sync pipeline. We had to implement a &lt;code&gt;try-catch-finally&lt;/code&gt; pattern to guarantee that even on crash, the sync lock is released and the UI updated. No more hung admin screens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git Line Ending Hell (LF vs CRLF)&lt;/strong&gt;: GitHub Actions Linux runners rejected files modified on Windows because of line ending mismatches. Solution: enforce LF via &lt;code&gt;.gitattributes&lt;/code&gt; globally. One config file, zero cross-platform headaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Partial Save Trap&lt;/strong&gt;: WordPress tabbed interfaces only submit visible fields. When updating the Front Matter template, the GitHub PAT field wasn't sent, resulting in accidental deletion. Fix: &lt;code&gt;array_merge()&lt;/code&gt; logic to preserve existing values during partial updates.&lt;/p&gt;

&lt;p&gt;None of this was in the initial specifications. All of it was mandatory to ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Copilot CLI Accelerated Development&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Copilot CLI's impact was most visible on structured, repetitive work—precisely where manual development is most tedious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured Example: WordPress.org Compliance Refactoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When WordPress.org review flagged 6 compliance issues, the fixes required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global plugin renaming (30+ files)&lt;/li&gt;
&lt;li&gt;Intervention Image API migration (v2 → v3)&lt;/li&gt;
&lt;li&gt;Asset restructuring (inline scripts → proper enqueuing)&lt;/li&gt;
&lt;li&gt;README updates (external services documentation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time with Copilot CLI&lt;/strong&gt;: 3 hours 8 minutes (measured)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Estimated manual time&lt;/strong&gt;: 7-10 hours (based on scope)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Acceleration&lt;/strong&gt;: ~3× faster&lt;/p&gt;

&lt;p&gt;This wasn't the only acceleration—it was the only one with precise timestamps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other Notable Accelerations&lt;/strong&gt; (estimated based on comparable WordPress development):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Trees API integration&lt;/strong&gt;: ~90 minutes with CLI vs estimated 4-6 hours manual (API docs, trial/error, debugging)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin UI components&lt;/strong&gt;: ~1 hour with CLI vs estimated 3-4 hours manual (WordPress Codex research, boilerplate)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image optimization pipeline&lt;/strong&gt;: ~2 hours with CLI vs estimated 5-6 hours manual (library selection, testing, error handling)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The pattern&lt;/strong&gt;: Copilot CLI consistently provided 3-4× acceleration on structured tasks where the goal was clear and the implementation was well-documented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What didn't accelerate&lt;/strong&gt;: Architecture decisions, integration debugging, WordPress.org submission process, edge case discovery.&lt;/p&gt;

&lt;p&gt;The real value wasn't just speed—it was eliminating context switching. No searching documentation, no hunting for syntax examples, no copy-pasting boilerplate from other projects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Local Image Optimization Matters&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The plugin processes images &lt;strong&gt;on the WordPress server before uploading to GitHub&lt;/strong&gt;. This is crucial:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without local optimization&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload 5MB original JPEGs to GitHub&lt;/li&gt;
&lt;li&gt;GitHub Actions must download, process (ImageMagick/Sharp), then deploy&lt;/li&gt;
&lt;li&gt;Build time: 2-3 minutes per post&lt;/li&gt;
&lt;li&gt;GitHub Actions runner minutes consumed: high&lt;/li&gt;
&lt;li&gt;Failed builds leave orphaned large files in Git history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With local optimization&lt;/strong&gt; (current approach):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress generates AVIF (50-150KB) + WebP (100-300KB) + original&lt;/li&gt;
&lt;li&gt;Upload ~500KB total per post to GitHub&lt;/li&gt;
&lt;li&gt;GitHub Actions just copies files, no processing&lt;/li&gt;
&lt;li&gt;Build time: 15-30 seconds&lt;/li&gt;
&lt;li&gt;Clean Git history, minimal runner usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off: PHP memory limits and processing time on the WordPress side. But WordPress is idle 99% of the time. GitHub Actions runners cost money per minute.&lt;/p&gt;

&lt;p&gt;Processing locally shifts the bottleneck to where it's free.&lt;/p&gt;
&lt;h3&gt;
  
  
  Architecture&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What's Currently Handled&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Posts and Pages&lt;/strong&gt;: Both sync automatically with proper Hugo front matter&lt;br&gt;
✅ &lt;strong&gt;Deletions&lt;/strong&gt;: Trashing a post/page in WordPress triggers file deletion in GitHub&lt;br&gt;
✅ &lt;strong&gt;Updates&lt;/strong&gt;: Editing content re-syncs, overwriting existing files&lt;br&gt;
✅ &lt;strong&gt;Categories and Tags&lt;/strong&gt;: Converted to Hugo taxonomies in front matter&lt;br&gt;
✅ &lt;strong&gt;Featured Images&lt;/strong&gt;: Optimized and linked in front matter (&lt;code&gt;featured_image&lt;/code&gt; field)&lt;br&gt;
✅ &lt;strong&gt;Custom Fields&lt;/strong&gt;: Basic fields map to front matter (extensible via adapter)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Limitations&lt;/strong&gt; (MVP scope):&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Draft Handling&lt;/strong&gt;: Drafts stay in WordPress, never sync (intentional)&lt;br&gt;
⚠️ &lt;strong&gt;Revisions&lt;/strong&gt;: Only published versions sync, revision history stays local&lt;br&gt;
⚠️ &lt;strong&gt;Complex Blocks&lt;/strong&gt;: Gutenberg blocks convert to HTML, then basic Markdown (no advanced block preservation)&lt;br&gt;
⚠️ &lt;strong&gt;Shortcodes&lt;/strong&gt;: Rendered to HTML before conversion (loses original shortcode)&lt;br&gt;
⚠️ &lt;strong&gt;ACF/Meta Boxes&lt;/strong&gt;: Only standard custom fields supported (ACF requires custom adapter extension)&lt;br&gt;
⚠️ &lt;strong&gt;Author Pages&lt;/strong&gt;: Not yet implemented (single-author blogs work fine)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deliberate Trade-offs&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;WordPress remains the source of truth. The plugin doesn't sync bidirectionally. If you edit Markdown directly in GitHub, those changes won't flow back to WordPress. This is intentional — simplicity over complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Theme Changes and SSG Migration&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Thanks to the universal front matter template system, changing Hugo themes or even migrating to a different SSG is now straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Update the front matter template&lt;/strong&gt; in plugin settings (no code changes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk re-sync&lt;/strong&gt; all posts via WP-CLI (&lt;code&gt;wp jamstack sync --all&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional cleanup&lt;/strong&gt; of old file structure in Git (if directory paths changed)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The adapter pattern is already in place. Adding support for Jekyll, Eleventy, or Astro means implementing a new adapter class — the core sync engine remains untouched.&lt;/p&gt;

&lt;p&gt;What's &lt;em&gt;not&lt;/em&gt; yet automated: migrating between SSGs with fundamentally different content structures (e.g., Hugo's &lt;code&gt;content/posts/&lt;/code&gt; vs Astro's &lt;code&gt;src/content/blog/&lt;/code&gt;). This would require a bulk file move operation in Git, which is currently manual.&lt;/p&gt;

&lt;p&gt;But changing front matter conventions within the same SSG? That's now a settings change, not a refactoring project.&lt;/p&gt;

&lt;p&gt;The repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress plugin (WordPress.org compliant)&lt;/li&gt;
&lt;li&gt;GitHub API integration (atomic commits)&lt;/li&gt;
&lt;li&gt;Asynchronous sync management (Action Scheduler)&lt;/li&gt;
&lt;li&gt;Hugo-compatible Markdown generation&lt;/li&gt;
&lt;li&gt;GitHub Actions workflow for deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How It Works&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The process in detail:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Writing&lt;/strong&gt;: Standard WordPress interface, no change in the writing experience&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhg7yqnlr5g60zo3kfuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhg7yqnlr5g60zo3kfuo.png" alt="WordPress Editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Automatic Commit&lt;/strong&gt;: The GitHub repository receives Markdown, optimized images, and front matter&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zyrrvtfpzi4yykt9dhp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zyrrvtfpzi4yykt9dhp.jpg" alt="GitHub Commit"&gt;&lt;/a&gt;&lt;br&gt;
You can see the &lt;code&gt;.github/workflows&lt;/code&gt; folder, where you can find the &lt;code&gt;hugo.yml&lt;/code&gt; file (1), the &lt;code&gt;content&lt;/code&gt; folder (2), the &lt;code&gt;static/images&lt;/code&gt; one (3), and last deployment status (4).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Hugo Structure&lt;/strong&gt;: &lt;code&gt;content/posts/&lt;/code&gt; structure automatically generated with correct naming&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Deployed Site&lt;/strong&gt;: Static version online via GitHub Pages, optimal performance&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8k8svs3tsa9kkgzthldh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8k8svs3tsa9kkgzthldh.png" alt="Static Site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole thing forms a simple publishing chain: write in WordPress, publish, and let the rest execute.&lt;/p&gt;
&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/pcescato/ajc-bridge" rel="noopener noreferrer"&gt;AJC Bridge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Releases&lt;/strong&gt;: &lt;a href="https://github.com/pcescato/ajc-bridge/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://deepwiki.com/pcescato/ajc-bridge" rel="noopener noreferrer"&gt;DeepWiki&lt;/a&gt; (AI-generated)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Production Update: Multi-Destination Publishing&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;While building this plugin, I realized something obvious in hindsight: &lt;strong&gt;the dev.to API has existed for years&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'd been so focused on static site generators that I missed the simpler path: publish directly to dev.to via API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So I added it.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &amp;nbsp;Five Publishing Strategies
&lt;/h4&gt;

&lt;p&gt;The plugin now supports five distinct workflows, each solving different use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. WordPress Only&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress (public site)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Plugin configured but sync disabled. For teams evaluating the plugin or running pure WordPress sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. WordPress + dev.to Syndication&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress (public, canonical) → dev.to (syndication)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;WordPress remains your public site. Optionally syndicate posts to dev.to with &lt;code&gt;canonical_url&lt;/code&gt; pointing back to WordPress. Perfect for established WordPress sites with existing audiences who want dev.to reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-post control&lt;/strong&gt;: Checkbox in post sidebar: "☐ Publish to dev.to"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. GitHub Only (Headless WordPress)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress (headless) → Hugo/Jekyll → GitHub Pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Traditional JAMstack workflow. WordPress is admin-only, frontend redirects to your static site. All published posts sync automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Dev.to Only (Headless WordPress)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress (headless) → dev.to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Zero infrastructure. WordPress writes, dev.to publishes, WordPress frontend redirects. For developers who want WordPress's editor but dev.to's community without managing static sites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the mode I use.&lt;/strong&gt;&amp;nbsp;All my articles are published exclusively on dev.to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Dual Publishing (GitHub + dev.to)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress (headless) → GitHub (canonical) + dev.to (syndication)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Best of both worlds. Hugo site = canonical source (your domain, your control). Dev.to = syndication (massive audience, zero SEO penalty via &lt;code&gt;canonical_url&lt;/code&gt;). WordPress frontend redirects to Hugo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-post control&lt;/strong&gt;: GitHub always syncs. Checkbox controls dev.to: "☐ Publish to dev.to"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5h5zs4r5u23v70451uj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5h5zs4r5u23v70451uj.jpg" alt="Five publishing strategies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Five publishing strategies covering WordPress-only, headless, and hybrid workflows&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjut6klb001hj41n703b7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjut6klb001hj41n703b7.jpg" alt="API key configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;API key configuration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbiymhlt8rl1nz1xzth7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbiymhlt8rl1nz1xzth7.jpg" alt="Per-post checkbox in sidebar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Per-post checkbox in sidebar: decide which posts syndicate to dev.to&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &amp;nbsp;Why This Matters
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;For established WordPress sites&lt;/strong&gt;: Keep your public WordPress site (audience, SEO, landing pages) but syndicate blog posts to dev.to for community reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For JAMstack purists&lt;/strong&gt;: Go fully headless with Hugo, optionally syndicate to dev.to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For dev.to community members&lt;/strong&gt;: Use WordPress as your writing environment, publish exclusively to dev.to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For migrations&lt;/strong&gt;: Start with WordPress Only, test strategies incrementally, migrate when ready. Zero lock-in.&lt;/p&gt;
&lt;h4&gt;
  
  
  &amp;nbsp;Technical Implementation
&lt;/h4&gt;

&lt;p&gt;The architecture supports all five modes through strategy-based routing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$strategy&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'wordpress_only'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No sync&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'wordpress_devto'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;post_meta&lt;/span&gt; &lt;span class="s1"&gt;'_wpjamstack_publish_devto'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;sync_to_devto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;canonical_url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get_permalink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'github_only'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;sync_to_github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'devto_only'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;sync_to_devto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;canonical_url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'dual_github_devto'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;sync_to_github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;post_meta&lt;/span&gt; &lt;span class="s1"&gt;'_wpjamstack_publish_devto'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;sync_to_devto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;canonical_url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$hugo_url&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Headless mode&lt;/strong&gt; automatically redirects WordPress frontend (301) to the canonical destination (Hugo or dev.to) in headless strategies. WordPress admin remains fully functional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-post control&lt;/strong&gt; in WordPress + dev.to and Dual modes: a checkbox in the post sidebar lets authors decide which posts syndicate externally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canonical URL handling&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress + dev.to: &lt;code&gt;canonical_url&lt;/code&gt; → WordPress permalink&lt;/li&gt;
&lt;li&gt;Dual (GitHub + dev.to): &lt;code&gt;canonical_url&lt;/code&gt; → Hugo site URL&lt;/li&gt;
&lt;li&gt;Dev.to Only: No canonical (dev.to is primary)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  &amp;nbsp;Real-World Usage
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;This article&lt;/strong&gt; was published using dev.to's rich editor before I implemented the adapter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Future articles will be published via the plugin&lt;/strong&gt;: I write in WordPress, click Publish, and the plugin handles the rest via dev.to's API.&lt;/p&gt;

&lt;p&gt;The dev.to API isn't new. The Forem platform has supported it for years.&lt;/p&gt;

&lt;p&gt;What's new is the integration: WordPress as the writing environment, dev.to as the publishing platform, zero manual steps.&lt;/p&gt;

&lt;p&gt;That's the difference between a demo and production: the tool becomes part of the workflow, not just a talking point.&lt;/p&gt;
&lt;h4&gt;
  
  
  What's Next
&lt;/h4&gt;

&lt;p&gt;The plugin ships with Hugo and dev.to adapters, but the architecture supports more:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional platforms ready&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jekyll (GitHub Pages native SSG)&lt;/li&gt;
&lt;li&gt;Hashnode (GraphQL API)&lt;/li&gt;
&lt;li&gt;Ghost (Admin API)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI-native architecture&lt;/strong&gt;: Compatible with emerging frameworks like &lt;a href="https://github.com/WordPress/agent-skills" rel="noopener noreferrer"&gt;&lt;code&gt;WordPress/agent-skills&lt;/code&gt;&lt;/a&gt; for future voice-controlled publishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WordPress.org submission&lt;/strong&gt;: Submitted February 6, 2026. Pending review for public distribution to the WordPress plugin directory.&lt;/p&gt;

&lt;p&gt;The adapter pattern means adding new destinations is straightforward. The hard part — WordPress integration, async processing, error handling — is done.&lt;/p&gt;
&lt;h3&gt;
  
  
  Technical Highlights&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Universal Front Matter Engine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of hardcoding the plugin for a single Hugo theme, we built a raw template system. Users define their own YAML (or TOML) with custom delimiters and placeholders like &lt;code&gt;{{id}}&lt;/code&gt;, &lt;code&gt;{{title}}&lt;/code&gt;, or &lt;code&gt;{{image_avif}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means the same plugin can adapt to any SSG convention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hugo with YAML front matter&lt;/li&gt;
&lt;li&gt;Jekyll with different taxonomy names
&lt;/li&gt;
&lt;li&gt;Eleventy with custom data structures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You control the output format. The plugin just fills in the blanks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Asset Management by WordPress ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To guarantee unbreakable links, optimized images (WebP and AVIF) are stored in folders named by WordPress ID: &lt;code&gt;static/images/1460/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rename your post slug for SEO ten times? Your images never break. The ID is immutable. The file paths are permanent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Native WordPress Integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The plugin integrates as a first-class citizen with its own sidebar menu and tabbed navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role-based security&lt;/strong&gt;: Authors only see their own sync history. Critical settings (GitHub PAT) remain admin-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsible cleanup&lt;/strong&gt;: A "Clean Uninstall" option removes all plugin traces (options and post meta) on uninstall, leaving zero database pollution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Atomic Commits via GitHub Trees API&lt;/strong&gt;:&lt;br&gt;
Instead of multiple sequential commits (one per image, one for Markdown), the plugin uses GitHub's Git Data API to create a single commit containing all files:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Collect all files (Markdown + images)&lt;/span&gt;
&lt;span class="nv"&gt;$all_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'content/posts/2026-02-07-this-is-a-post.md'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$markdown_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'static/images/1447/featured.webp'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$webp_binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'static/images/1447/featured.avif'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$avif_binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'static/images/1447/wordpress-to-hugo-1024x587.webp'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$webp_binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'static/images/1447/wordpress-to-hugo-1024x587.avif'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$avif_binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Single atomic commit&lt;/span&gt;
&lt;span class="nv"&gt;$git_api&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create_atomic_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$all_files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Publish: This is a Post"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This approach is transactional: either everything commits or nothing does. No partial states, cleaner history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Beyond Hugo: Multi-SSG Architecture&lt;/strong&gt;&lt;br&gt;
While this demo targets Hugo, the adapter pattern isn't locked to a single SSG. The same codebase can support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hugo (YAML/TOML front matter, content/posts/)&lt;/li&gt;
&lt;li&gt;Jekyll (different taxonomy conventions, _posts/)&lt;/li&gt;
&lt;li&gt;Eleventy (custom data structures, src/content/)&lt;/li&gt;
&lt;li&gt;Astro (content collections, src/content/blog/)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a new SSG means writing one adapter class — the sync engine, image optimization, and GitHub integration remain untouched.&lt;br&gt;
This architectural choice transforms the plugin from "Hugo-only" to a platform for any static site workflow. The 43 million WordPress sites aren't just potential Hugo users — they're potential static site adopters, period.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. WordPress-Native Compliance&lt;/strong&gt;:&lt;br&gt;
To meet WordPress.org requirements, the plugin uses exclusively native WordPress APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wp_remote_post()&lt;/code&gt; instead of curl&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WP_Filesystem&lt;/code&gt; instead of &lt;code&gt;file_put_contents()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$wpdb&lt;/code&gt; prepared statements&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;exec()&lt;/code&gt;, &lt;code&gt;shell_exec()&lt;/code&gt;, or Git CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it suitable for publication in the official WordPress plugin repository.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When I started this project, I wasn't looking for a tool to code for me.&lt;/p&gt;

&lt;p&gt;I was looking for a way to accelerate the execution of a project whose architecture was already clear.&lt;/p&gt;

&lt;p&gt;Having already used GitHub Copilot CLI, Gemini CLI, and various LLMs on other projects, I knew these tools could produce code quickly.&lt;/p&gt;

&lt;p&gt;But I also knew that without a precise framework, they mainly produce... code.&lt;/p&gt;

&lt;p&gt;Not necessarily a coherent system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This isn't the autocomplete in the editor, but a command-line tool capable of generating complete files from structured prompts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Specification First, Code Second&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The first step wasn't to code. The first step was to write specifications. Define the scope. Break down the project into functional blocks.&lt;/p&gt;

&lt;p&gt;Identify non-negotiable constraints: WordPress native only, no shell execution, reliable async processing, atomic GitHub commits, WordPress.org compliance to publish the plugin in the official repository and benefit the community.&lt;/p&gt;

&lt;p&gt;Here are my first interactions with Copilot CLI:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="GitHub Copilot"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;GitHub Copilot Session - WordPress Plugin&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;You are generating a production-grade WordPress plugin.&lt;/p&gt;

&lt;p&gt;Use specifications.md and the ADR addendum as the single source of truth.&lt;/p&gt;

&lt;p&gt;We are implementing ONLY the bootstrap foundation of the plugin.&lt;/p&gt;

&lt;p&gt;Do NOT implement sync logic, adapters, queue logic, or GitHub logic yet.&lt;/p&gt;



&lt;h2&gt;STEP 1 — Generate plugin file structure&lt;/h2&gt;

&lt;p&gt;Generate the exact plugin file tree defined in section 7.1 of specifications.md.&lt;/p&gt;

&lt;p&gt;Do not add extra files.&lt;br&gt;
Do not invent features.&lt;br&gt;
Respect strict architecture decisions.&lt;/p&gt;



&lt;h2&gt;STEP 2 — Create main plugin bootstrap file&lt;/h2&gt;

&lt;p&gt;Create:&lt;/p&gt;

&lt;p&gt;wp-jamstack-sync.php&lt;/p&gt;

&lt;p&gt;The file must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;include proper WordPress plugin headers&lt;/li&gt;
&lt;li&gt;prevent direct access via ABSPATH check&lt;/li&gt;
&lt;li&gt;define constants:

&lt;ul&gt;
&lt;li&gt;WPJAMSTACK_VERSION&lt;/li&gt;
&lt;li&gt;WPJAMSTACK_PATH&lt;/li&gt;
&lt;li&gt;WPJAMSTACK_URL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;load required core files (empty placeholders allowed)&lt;/li&gt;
&lt;li&gt;register activation hook&lt;/li&gt;
&lt;li&gt;register deactivation hook&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;STEP 3 — Activation hook&lt;/h2&gt;

&lt;p&gt;On activation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verify WordPress &amp;gt;= 6.9&lt;/li&gt;
&lt;li&gt;verify PHP &amp;gt;= 8.1&lt;/li&gt;
&lt;li&gt;if not compatible:

&lt;ul&gt;
&lt;li&gt;deactivate plugin safely&lt;/li&gt;
&lt;li&gt;show admin error&lt;/li&gt;
&lt;li&gt;do not use raw die()&lt;/li&gt;
&lt;li&gt;use WordPress-safe handling&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;h2&gt;STEP 4 — Development .env loader (DEV ONLY)&lt;/h2&gt;

&lt;p&gt;Add a lightweight .env loader.&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only load .env if:
WP_ENVIRONMENT_TYPE === 'development'&lt;/li&gt;
&lt;li&gt;Do nothing in production&lt;/li&gt;
&lt;li&gt;Fail silently if file missing&lt;/li&gt;
&lt;li&gt;No external libraries&lt;/li&gt;
&lt;li&gt;Parse simple KEY=VALUE pairs&lt;/li&gt;
&lt;li&gt;Populate values into:
$_ENV and getenv()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never require .env in production.&lt;/p&gt;



&lt;h2&gt;STEP 5 — Output&lt;/h2&gt;

&lt;p&gt;Return:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;plugin file tree&lt;/li&gt;
&lt;li&gt;full content of wp-jamstack-sync.php&lt;/li&gt;
&lt;li&gt;nothing else&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll read the specifications and create the plugin bootstrap foundation.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;7 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now I'll create the plugin bootstrap foundation with the exact structure from section 7.1:&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
        &lt;div class="agent-session-gap"&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
          &lt;span class="agent-session-gap-label"&gt;3 messages not shown&lt;/span&gt;
          &lt;span class="agent-session-gap-line"&gt;&lt;/span&gt;
        &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;Perfect! Here's the complete output:&lt;/p&gt;



&lt;h2&gt;1. Plugin File Tree&lt;/h2&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;wp-jamstack-sync/
├── wp-jamstack-sync.php       # Main plugin bootstrap file
├── /core                      # Empty (placeholder for future classes)
├── /adapters                  # Empty (placeholder for future adapters)
├── /admin                     # Empty (placeholder for future admin UI)
├── /cli                       # Empty (placeholder for future WP-CLI)
├── /includes                  # Empty (placeholder for future helpers)
└── /assets                    # Empty (placeholder for future assets)
    ├── /css
    └── /js
&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;2. Full Content: &lt;code&gt;wp-jamstack-sync.php&lt;/code&gt;
&lt;/h2&gt;

&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * Plugin Name: WP Jamstack Sync
 * Plugin URI: https://github.com/pascalcescato/wp-jamstack-sync
 * Description: Automated WordPress to Hugo publishing system with async GitHub API integration.
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 * Author: Pascal CESCATO
 * Author URI: https://github.com/pascalcescato
 * License: GPL v2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: wp-jamstack-sync
 * Domain Path: /languages
 */&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'ABSPATH'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Direct access not permitted.'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Plugin constants&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPJAMSTACK_VERSION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.0.0'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPJAMSTACK_PATH'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugin_dir_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WPJAMSTACK_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;plugin_dir_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Development .env loader (only in development environment)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_get_environment_type'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s1"&gt;'development'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;wp_get_environment_type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;wpjamstack_load_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Load environment variables from .env file (development only)
 *
 * @return void
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpjamstack_load_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$env_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WPJAMSTACK_PATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.env'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$env_file&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$env_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;FILE_IGNORE_NEW_LINES&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;FILE_SKIP_EMPTY_LINES&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$lines&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$lines&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$line&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Skip comments&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$line&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'#'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Parse KEY=VALUE pairs&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$key&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Remove quotes if present&lt;/span&gt;
            &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'"&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Set in $_ENV and putenv&lt;/span&gt;
            &lt;span class="nv"&gt;$_ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nb"&gt;putenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Activation hook - verify system requirements
 *
 * @return void
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpjamstack_activate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$wp_version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Check WordPress version&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;version_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$wp_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'6.9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$errors&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="cm"&gt;/* translators: %s: Required WordPress version */&lt;/span&gt;
            &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP Jamstack Sync requires WordPress %s or higher.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-jamstack-sync'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'6.9'&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Check PHP version&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;version_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kc"&gt;PHP_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'8.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$errors&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="cm"&gt;/* translators: %s: Required PHP version */&lt;/span&gt;
            &lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'WP Jamstack Sync requires PHP %s or higher.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-jamstack-sync'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'8.1'&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If errors exist, deactivate and show message&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$errors&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Deactivate the plugin&lt;/span&gt;
        &lt;span class="nf"&gt;deactivate_plugins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;plugin_basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Show error message&lt;/span&gt;
        &lt;span class="nf"&gt;wp_die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'&amp;lt;h1&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Plugin Activation Failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-jamstack-sync'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/h1&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
            &lt;span class="s1"&gt;'&amp;lt;p&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'esc_html'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$errors&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/p&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
            &lt;span class="s1"&gt;'&amp;lt;p&amp;gt;&amp;lt;a href="'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;admin_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'plugins.php'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
            &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Return to Plugins'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-jamstack-sync'&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nf"&gt;esc_html__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'Plugin Activation Error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-jamstack-sync'&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'back_link'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Activation successful - future tasks go here&lt;/span&gt;
    &lt;span class="c1"&gt;// (e.g., create tables, set default options, schedule cron)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Deactivation hook - cleanup tasks
 *
 * @return void
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;wpjamstack_deactivate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Future cleanup tasks go here&lt;/span&gt;
    &lt;span class="c1"&gt;// (e.g., clear scheduled tasks, flush caches)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Register activation/deactivation hooks&lt;/span&gt;
&lt;span class="nf"&gt;register_activation_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpjamstack_activate'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;register_deactivation_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wpjamstack_deactivate'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Load core files (placeholders for now)&lt;/span&gt;
&lt;span class="c1"&gt;// require_once WPJAMSTACK_PATH . 'core/class-plugin.php';&lt;/span&gt;
&lt;span class="c1"&gt;// require_once WPJAMSTACK_PATH . 'core/class-sync-runner.php';&lt;/span&gt;
&lt;span class="c1"&gt;// require_once WPJAMSTACK_PATH . 'core/class-queue.php';&lt;/span&gt;
&lt;span class="c1"&gt;// require_once WPJAMSTACK_PATH . 'core/class-logger.php';&lt;/span&gt;
&lt;span class="c1"&gt;// require_once WPJAMSTACK_PATH . 'core/class-github-api.php';&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize plugin&lt;/span&gt;
&lt;span class="c1"&gt;// if ( class_exists( 'WPJamstack\Core\Plugin' ) ) {&lt;/span&gt;
&lt;span class="c1"&gt;//  WPJamstack\Core\Plugin::get_instance();&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;✅ &lt;strong&gt;Bootstrap foundation complete&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
- File structure matches section 7.1 specifications&lt;br&gt;&lt;br&gt;
- Main plugin file created with WordPress-compliant headers&lt;br&gt;&lt;br&gt;
- Version checks (WP 6.9+, PHP 8.1+) enforce requirements on activation&lt;br&gt;&lt;br&gt;
- Safe deactivation with proper error handling (no raw &lt;code&gt;die()&lt;/code&gt;)&lt;br&gt;&lt;br&gt;
- Development &lt;code&gt;.env&lt;/code&gt; loader active only when &lt;code&gt;WP_ENVIRONMENT_TYPE === 'development'&lt;/code&gt;&lt;br&gt;&lt;br&gt;
- Core file loaders commented out (ready for future implementation)&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        4 of 3097 messages
          · some content was hidden by the author
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Then organize development into successive stages.&lt;/p&gt;

&lt;p&gt;Development unfolded in structured phases: bootstrap, core architecture, async queue system, media pipeline, atomic GitHub commits, deletion lifecycle, bulk operations, and admin-side hardening.&lt;/p&gt;

&lt;p&gt;Copilot CLI was used as an execution partner — implementing each layer under explicit constraints (WordPress native APIs only, async safety, atomic operations, repository compliance). The focus was not just faster coding, but building a production-ready system.&lt;/p&gt;

&lt;p&gt;This approach is very similar to what a technical project manager or lead engineer would do before entrusting implementation to a team. The difference here is that the “team” consisted of a tool capable of producing code extremely quickly — but only if instructions were precise and constraints well defined.&lt;/p&gt;

&lt;p&gt;So I didn’t write code in the traditional sense. I wrote functional and technical specifications, prompts, refined instructions, corrected trajectories, and validated outputs against the intended architecture.&lt;/p&gt;

&lt;p&gt;Each step consisted of describing what needed to be built, verifying what was produced, and adjusting accordingly.&lt;/p&gt;

&lt;p&gt;Sometimes Copilot proposed a relevant structure on the first try. Sometimes it required reworking, clarification, or tighter constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Work Pattern&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Very quickly, a work pattern emerged: specification → generation → verification → correction → iteration.&lt;/p&gt;

&lt;p&gt;In this process, Copilot behaves less like a magic generator than like a fast executor.&lt;/p&gt;

&lt;p&gt;It can structure an entire class in seconds, propose a coherent implementation, or refactor a complete block.&lt;/p&gt;

&lt;p&gt;But it can also forget an essential hook, overwrite an existing method, or produce functional code that doesn't comply with initial constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Examples of Issues&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method replaced by an incomplete stub&lt;/li&gt;
&lt;li&gt;Hook not registered, causing silent failures&lt;/li&gt;
&lt;li&gt;File generated but not actually written to disk&lt;/li&gt;
&lt;li&gt;Fatal error on activation, typical of strict WordPress environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each incident required going back to fundamentals: verify, understand, correct, reformulate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Prompt Gallery: Steering the CLI&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To move from concept to production-grade code, I steered Copilot CLI through complex engineering hurdles. These aren't just snippets; they are the instructions that shaped the architecture.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Atomic Shift (Core Logic)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: Moving from simple file uploads to the GitHub Trees API to ensure images and Markdown commit simultaneously.&lt;br&gt;
&lt;code&gt;Refactor the Git_API class to use the Trees API. I need a single atomic commit containing the Markdown file and all processed images. Use the SHA of the base branch to create the tree.&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  2. The Stateful Sync (Lifecycle Management)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: Preventing duplicates on Dev.to by persisting remote IDs.&lt;br&gt;
&lt;code&gt;Update the Sync_Runner to check for '_atomic_jamstack_devto_id'. If it exists, use a PUT request to update the existing article; otherwise, POST a new one and save the returned ID.&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  3. The Security Audit (Hardening)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: Passing the official WordPress 'Plugin Check' tool.&lt;br&gt;
&lt;code&gt;Scan admin/class-settings.php. Identify all missing nonces and un-sanitized $_POST variables. Apply wp_verify_nonce and sanitize_text_field according to WP.org standards.&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What This Actually Means&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is probably the most interesting aspect of the experience.&lt;/p&gt;

&lt;p&gt;Using Copilot effectively doesn't mean writing one prompt and waiting for a result.&lt;/p&gt;

&lt;p&gt;It's much more like continuous piloting, where the quality of instructions directly conditions the quality of what's produced.&lt;/p&gt;

&lt;p&gt;In this context, the tool becomes particularly effective for accelerating everything that's structured: class creation, file organization, repetitive function implementation, refactoring, documentation.&lt;/p&gt;

&lt;p&gt;As soon as the objective is clearly defined, execution can become very fast.&lt;/p&gt;

&lt;p&gt;But the responsibility for architecture, technical choices, and overall coherence remains entirely human.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Real Value&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In the end, the experience is less like "AI-assisted development" than a form of assisted technical direction.&lt;/p&gt;

&lt;p&gt;Code is produced quickly, but it must be thought out, supervised, and validated continuously.&lt;/p&gt;

&lt;p&gt;This project was built in less than two days. Not because the tool replaces design work, but because once that work is done, execution can be considerably accelerated.&lt;/p&gt;

&lt;p&gt;This is probably where GitHub Copilot CLI becomes most interesting: it's not a substitute for development, but an accelerator for an already thought-out and structured project.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Development Rhythm&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Each feature followed a consistent workflow that kept development focused and auditable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Planning&lt;/strong&gt;: Describe the goal in natural language&lt;br&gt;
   Example: &lt;em&gt;Add atomic commit support using GitHub Trees API&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Proposal&lt;/strong&gt;: Copilot suggests implementation approach&lt;br&gt;
   Copilot outlines: &lt;em&gt;file changes, API calls, error handling&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Review&lt;/strong&gt;: Validate architecture before generation&lt;br&gt;
   Check: &lt;em&gt;Does this align with WordPress standards? Any edge cases?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Generation&lt;/strong&gt;: Multi-file code updates with consistent patterns&lt;br&gt;
   &lt;em&gt;Copilot writes across 5-10 files simultaneously&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Testing&lt;/strong&gt;: Manual verification and integration testing&lt;br&gt;
   Test: &lt;em&gt;WordPress admin, GitHub API, Hugo deployment&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Checkpoint&lt;/strong&gt;: Document working increment for audit trail&lt;br&gt;
   Create: &lt;em&gt;checkpoint file with context and decisions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This rhythm repeated 23 times throughout development. Each checkpoint represents a tested, working state—not just code, but verified functionality.&lt;/p&gt;

&lt;p&gt;The checkpoints weren't documentation overhead. They were the development cadence.&lt;/p&gt;
&lt;h3&gt;
  
  
  Real Example: AVIF Generation Fix&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Here's what an actual development session looked like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgm8qkpd8fbz1qj48x2y7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgm8qkpd8fbz1qj48x2y7.png" alt="Copilot CLI session: AVIF fix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Detailed problem description → Copilot proposes complete solution with code, validation, and error handling&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The prompt describes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What to fix&lt;/strong&gt;: AVIF generation failures
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to fix it&lt;/strong&gt;: Use explicit AvifEncoder, add file validation
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to verify&lt;/strong&gt;: Check encoder usage, test file creation
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot responds with a 281-line implementation plan covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code changes across 2 methods&lt;/li&gt;
&lt;li&gt;Import statements updates&lt;/li&gt;
&lt;li&gt;Error logging improvements&lt;/li&gt;
&lt;li&gt;Verification commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mlgm0nknumb8d3787ww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mlgm0nknumb8d3787ww.png" alt="Verification and result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After applying changes: verification confirms correct implementation, Before/After shows API migration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time&lt;/strong&gt;: ~15 minutes from problem to verified solution&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Manual estimate&lt;/strong&gt;: 1-2 hours (research v3 API docs, update all calls, test each format)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Acceleration&lt;/strong&gt;: ~4-6× faster&lt;/p&gt;

&lt;p&gt;This pattern—detailed prompt, comprehensive response, systematic verification—repeated throughout development. The 23 checkpoints represent 23 iterations of this cycle.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Audit Trail Advantage&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Beyond just writing code, GitHub Copilot CLI acts as a technical scribe.&lt;/p&gt;

&lt;p&gt;My session history evolved through &lt;strong&gt;23 distinct checkpoints&lt;/strong&gt;, documenting every architectural pivot from the initial foundation to the final security hardening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;001-wordpress-plugin-foundation
002-media-processing-with-avif-support
003-deletion-and-bulk-sync
004-atomic-commits-and-monitoring
...
019-fix-plugin-check-errors
020-add-nonce-security
021-uninstall-api-compliance
022-fix-nonce-sanitization-warnings
023-fix-token-double-encryption.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can browse the complete checkpoint history here: &lt;a href="https://github.com/pcescato/ajc-bridge/tree/main/docs" rel="noopener noreferrer"&gt;https://github.com/pcescato/ajc-bridge/tree/main/docs&lt;/a&gt;, including &lt;a href="https://github.com/pcescato/ajc-bridge/blob/main/docs/initial-specifications.md" rel="noopener noreferrer"&gt;the initial specifications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each checkpoint includes context files like &lt;code&gt;wordpress-api-compliance-guide.md&lt;/code&gt;, &lt;code&gt;token-preservation-fix.md&lt;/code&gt;, or &lt;code&gt;settings-merge-test-plan.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This isn't just a side effect — it's a massive win for maintainability.&lt;/p&gt;

&lt;p&gt;It transforms the "black box" of AI generation into a transparent, step-by-step engineering log. Six months from now, when I need to understand why a particular decision was made, I won't be guessing. The checkpoint history tells the story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked well&lt;/strong&gt;:&lt;br&gt;
✅ Rapid scaffolding of classes following WordPress standards&lt;br&gt;
✅ Boilerplate code generation (hooks, filters, nonces)&lt;br&gt;
✅ Refactoring large blocks (sequential commits → atomic commits)&lt;br&gt;
✅ Documentation generation from inline comments&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What required constant supervision&lt;/strong&gt;:&lt;br&gt;
⚠️ Architecture decisions (adapter pattern, async queues)&lt;br&gt;
⚠️ WordPress.org compliance verification&lt;br&gt;
⚠️ Error handling and edge cases&lt;br&gt;
⚠️ Integration testing across components&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Review: Validating Production Readiness&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Once the plugin was functional and submitted to WordPress.org (February 6), I wanted to validate its code quality independently—without waiting for the review team's feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The question&lt;/strong&gt;: Is this truly production-ready, or did fast development introduce critical bugs?&lt;/p&gt;
&lt;h4&gt;
  
  
  The Review Process
&lt;/h4&gt;

&lt;p&gt;I used GitHub Copilot CLI to conduct a comprehensive security and compliance audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt&lt;/strong&gt;: &lt;code&gt;Review this WordPress plugin for critical blockers. Focus on security vulnerabilities, WordPress.org compliance, and data integrity issues.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review completed in 10 minutes&lt;/strong&gt; with an 800-line report covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security (authentication, sanitization, secrets handling)&lt;/li&gt;
&lt;li&gt;Compliance (coding standards, uninstall cleanup, version consistency)&lt;/li&gt;
&lt;li&gt;Correctness (race conditions, SQL queries, data loss risks)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Findings: 3 Critical Blockers
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Grade&lt;/strong&gt;: C+ (production-ready with critical fixes needed)&lt;/p&gt;

&lt;p&gt;The review validated the architecture ("excellent design, clean separation") but identified &lt;strong&gt;3 blockers&lt;/strong&gt; that would likely trigger WordPress.org rejection:&lt;/p&gt;
&lt;h5&gt;
  
  
  1. Secret Logging (Security - CRITICAL)
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: GitHub token previews logged to debug files.&lt;br&gt;
&lt;strong&gt;Risk&lt;/strong&gt;: Partial token exposure in database logs and files.&lt;br&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: Removed all secret previews (30 minutes).&lt;/p&gt;
&lt;h5&gt;
  
  
  2. Version Mismatch (Compliance - CRITICAL)
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: Plugin header showed &lt;code&gt;1.1.0&lt;/code&gt;, readme.txt showed &lt;code&gt;1.2.0&lt;/code&gt;.&lt;br&gt;
WordPress.org automated checks &lt;strong&gt;reject version inconsistencies&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: Updated plugin header and constant to &lt;code&gt;1.2.0&lt;/code&gt; (5 minutes).&lt;/p&gt;
&lt;h5&gt;
  
  
  3. Incomplete Uninstall (Compliance - CRITICAL)
&lt;/h5&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;: Plugin created 13 post meta keys but only cleaned up 5 in &lt;code&gt;uninstall.php&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Why this matters&lt;/strong&gt;: WordPress.org reviewers &lt;strong&gt;manually check uninstall cleanup&lt;/strong&gt;. Incomplete removal is a rejection reason.&lt;br&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: Added 7 missing meta keys + 1 option cleanup (15 minutes).&lt;/p&gt;
&lt;h4&gt;
  
  
  Fixing the Blockers: 20 Minutes
&lt;/h4&gt;

&lt;p&gt;I created a surgical prompt for Copilot CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fix ONLY the 3 critical blockers. 
Do NOT refactor anything else. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Changes made&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;✅ Removed secret logging (verified with grep)&lt;br&gt;
✅ Synced all versions to 1.2.0&lt;br&gt;
✅ Completed uninstall cleanup (11 meta keys total)&lt;/p&gt;

&lt;h3&gt;
  
  
  WordPress.org Submission: The Road to Approval&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Moving code from a local environment to the official WordPress.org repository is the ultimate moment of truth. For this project, I implemented a &lt;strong&gt;two-tier quality assurance strategy&lt;/strong&gt; that transformed potential rejection into a near-immediate technical validation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Process Timeline
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;February 6&lt;/strong&gt;: Initial plugin submission.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;February 12&lt;/strong&gt;: Internal review via Copilot CLI. Identified and fixed 3 critical logic and security bugs in 20 minutes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;February 14 (00:00)&lt;/strong&gt;: Received official WordPress.org review. Identified 6 specific compliance and ecosystem issues.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;February 14 (03:08)&lt;/strong&gt;: &lt;strong&gt;Within 3 hours&lt;/strong&gt;,  all 6 issues were addressed and version 1.2.0 re-submitted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Current Status&lt;/strong&gt;: ✅ &lt;strong&gt;Pending Final Manual Approval&lt;/strong&gt; (Typical queue wait: 7–14 days).&lt;/p&gt;

&lt;h4&gt;
  
  
  The Refactoring Challenge: 3 Hours Instead of 10
&lt;/h4&gt;

&lt;p&gt;The 6 compliance issues required substantial code changes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What needed to be done&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global renaming: 30+ files, hundreds of references&lt;/li&gt;
&lt;li&gt;API migration: intervention/image v2 → v3&lt;/li&gt;
&lt;li&gt;Assets restructuration: Extract 6 inline scripts, implement wp_enqueue&lt;/li&gt;
&lt;li&gt;Documentation: External services section with API details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Manual estimate&lt;/strong&gt; (senior developer): 7-10 hours&lt;br&gt;
&lt;strong&gt;Actual time with Copilot CLI&lt;/strong&gt;: 3 hours 8 minutes&lt;/p&gt;

&lt;p&gt;The difference? Copilot CLI excels at systematic refactoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global renaming&lt;/strong&gt;: One prompt replaced hundreds of references across 
30+ files without missing edge cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API migration&lt;/strong&gt;: Copilot read the intervention/image v3 changelog 
and refactored all image processing calls automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern extraction&lt;/strong&gt;: Identified all inline &lt;code&gt;&amp;lt;script&amp;gt;/&amp;lt;style&amp;gt;&lt;/code&gt; tags 
and generated proper enqueue functions with correct hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This wasn't about generating new code—it was about &lt;strong&gt;surgical precision &lt;br&gt;
at scale&lt;/strong&gt;. The kind of work that's technically straightforward but &lt;br&gt;
humanly tedious and error-prone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The result&lt;/strong&gt;: A compliant, tested plugin ready for re-submission in &lt;br&gt;
the time it would have taken to just complete the renaming manually.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Power of Dual Review (AI + Human)
&lt;/h4&gt;

&lt;p&gt;This workflow demonstrates that even "production-ready" code benefits from an external perspective. The two audits served very different, yet complementary, purposes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Internal Audit (Copilot CLI): Security &amp;amp; Logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Copilot acted as a tactical second pair of eyes, catching issues that fast-paced development often misses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Security Vulnerabilities&lt;/strong&gt;: Identified partial exposure of sensitive secrets.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Operational Integrity&lt;/strong&gt;: Ensured rigorous cleanup during activation/uninstallation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Version Hygiene&lt;/strong&gt;: Fixed inconsistent constants that would have triggered automated rejection.&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Result: Transitioned the code from "fragile" to a robust, enterprise-grade architecture.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. WordPress.org Review: Compliance &amp;amp; Ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since writing this, the plugin passed WordPress.org official review. The review (a mix of automated algorithms and human oversight) focused on how the plugin lives within the WordPress ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Intellectual Property&lt;/strong&gt;: Renamed "Atomic Jamstack Connector" to &lt;strong&gt;"AJC Bridge"&lt;/strong&gt; to eliminate trademark confusion.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Technical Standards&lt;/strong&gt;: Replaced inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tags with the proper &lt;code&gt;wp_enqueue&lt;/code&gt; system.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Transparency&lt;/strong&gt;: Documented external service usage (GitHub &amp;amp; Dev.to APIs) in the &lt;code&gt;readme.txt&lt;/code&gt; to comply with Guideline 6.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dependency Hygiene&lt;/strong&gt;: Forced an upgrade of the &lt;code&gt;intervention/image&lt;/code&gt; library (v2.7 → v3.11) to patch known vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Takeaway: Production Readiness over Theoretical Perfection
&lt;/h4&gt;

&lt;p&gt;The plugin moved from &lt;strong&gt;"Blocked"&lt;/strong&gt; to &lt;strong&gt;"Repository Ready"&lt;/strong&gt; in record time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Surgical Fixes&lt;/strong&gt;: We didn't perform a radical rewrite. Instead, we used AI to target specific files flagged in the reviews, applying and testing patches immediately.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Value of Audit Trails&lt;/strong&gt;: The detailed history of architectural decisions made during development allowed the AI to understand the "why" behind the code, proposing fixes that maintained the plugin's logic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Engineering Pragmatism&lt;/strong&gt;: We prioritized critical blockers for the submission while deferring minor optimizations (like edge-case race conditions) to the v1.3 roadmap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Lesson&lt;/strong&gt;: AI doesn't replace official validation; it prepares you for it. By using Copilot to eliminate logical bugs early, you clear the path to focus on the platform's specific quirks — the trademark checks, the enqueue rules, the dependency audits that only humans (or their algorithms) flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Step&lt;/strong&gt;: Official launch on the WordPress.org repository under the slug &lt;code&gt;ajc-bridge&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Addendum: Automating the Future with GitHub Actions&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To wrap up this intensive session, I implemented a professional &lt;strong&gt;CI/CD pipeline&lt;/strong&gt; to ensure that every future release of &lt;strong&gt;AJC Bridge&lt;/strong&gt; is as clean and reliable as this one.&lt;/p&gt;

&lt;h4&gt;
  
  
  The "Release-on-Demand" Machine
&lt;/h4&gt;

&lt;p&gt;In just a few minutes, I used Copilot CLI to generate a GitHub Actions workflow that automates the entire packaging process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Trigger&lt;/strong&gt;: The workflow springs into action the moment a new version tag (e.g., &lt;code&gt;v1.2.0&lt;/code&gt;) is pushed, or via manual trigger.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clean Packaging&lt;/strong&gt;: It automatically builds a production-ready ZIP file, strictly excluding development overhead like &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.github&lt;/code&gt; configurations, &lt;code&gt;composer.json&lt;/code&gt;, and local documentation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Standardized Deployment&lt;/strong&gt;: The ZIP is structured specifically for WordPress.org standards (internal folder named &lt;code&gt;ajc-bridge&lt;/code&gt;), ensuring a seamless installation for users.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Automated Releases&lt;/strong&gt;: The workflow creates an official GitHub Release and attaches the optimized plugin archive as a primary asset.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why this Matters
&lt;/h4&gt;

&lt;p&gt;By automating the release process, I’ve eliminated the risk of human error—like forgetting to remove a sensitive config file or misnaming a folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This completes the transition from a solo dev project to a professionally maintained bridge.&lt;/strong&gt; Whether it’s a minor patch or a major feature update (like the upcoming v1.3), I can now ship a compliant, high-quality version to the community in seconds with a single Git command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot CLI didn't replace development.&lt;/p&gt;

&lt;p&gt;It didn't eliminate the need to think, architect, or decide.&lt;/p&gt;

&lt;p&gt;But used as an execution partner rather than an automatic generator, it made it possible to quickly transform a clear idea into a functional system.&lt;/p&gt;

&lt;p&gt;That's perhaps where these tools really make sense: they don't change the way we build, but they reduce the distance between what we imagine and what we put into production.&lt;/p&gt;

&lt;p&gt;In this specific case, they made it possible to solve a real tension: write comfortably in WordPress while publishing to a high-performance static site.&lt;/p&gt;

&lt;p&gt;No friction. No compromises.&lt;/p&gt;

&lt;p&gt;Just a workflow that works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking ahead&lt;/strong&gt;: v1.3 will add smart Table of Contents generation—&lt;br&gt;
automatically detecting long-form content and generating dev.to / &lt;br&gt;
SSG-compatible ToC with configurable thresholds (&amp;gt;600 words, 2+ H2) to ensure it only triggers when useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;WordPress isn't the enemy; it's the most powerful editorial engine we have. By decoupling it from the frontend, we don't just fix performance—we future-proof the web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to see it in action?&lt;/strong&gt; &lt;a href="https://githubcopilotchallenge.tsw.ovh/wp-admin" rel="noopener noreferrer"&gt;Try the live demo here&lt;/a&gt; or &lt;a href="https://github.com/pcescato/ajc-bridge" rel="noopener noreferrer"&gt;explore the code on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
