<?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: Diya Burman</title>
    <description>The latest articles on DEV Community by Diya Burman (@diyaburman).</description>
    <link>https://dev.to/diyaburman</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%2F93964%2Fa85c0e0d-f413-4c6e-b6a0-b26ddf9b739d.jpeg</url>
      <title>DEV Community: Diya Burman</title>
      <link>https://dev.to/diyaburman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diyaburman"/>
    <language>en</language>
    <item>
      <title>I Converted the order-api to OKF. Here's What I Found.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/diyaburman/i-converted-the-order-api-to-okf-heres-what-i-found-5c8m</link>
      <guid>https://dev.to/diyaburman/i-converted-the-order-api-to-okf-heres-what-i-found-5c8m</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;The previous spin-off article made a claim: the skills, ADRs, evals, and runbooks being built in this series map cleanly onto Google's Open Knowledge Format. Same problem, different vocabulary.&lt;/p&gt;

&lt;p&gt;Claims need testing. So I ran the test.&lt;/p&gt;

&lt;p&gt;This article is the result of converting the order-api's &lt;code&gt;docs/&lt;/code&gt; directory into a conformant OKF v0.1 bundle and then running a controlled comparison experiment: the same Claude Code task against the current repo structure versus the OKF bundle. Two fresh agent contexts. Same task description. Document what each agent does differently.&lt;/p&gt;

&lt;p&gt;The result was not what I expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  The conversion
&lt;/h2&gt;

&lt;p&gt;25 documents in scope across the &lt;code&gt;docs/&lt;/code&gt; directory plus &lt;code&gt;CLAUDE.md&lt;/code&gt; at the project root.&lt;/p&gt;

&lt;p&gt;The mapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Current type&lt;/th&gt;
&lt;th&gt;OKF type&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ADR&lt;/td&gt;
&lt;td&gt;Decision&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eval&lt;/td&gt;
&lt;td&gt;Guardrail&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill (all tiers)&lt;/td&gt;
&lt;td&gt;Methodology&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runbook&lt;/td&gt;
&lt;td&gt;Playbook&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reference docs&lt;/td&gt;
&lt;td&gt;Reference&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pedagogical examples&lt;/td&gt;
&lt;td&gt;Reference&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLAUDE.md&lt;/td&gt;
&lt;td&gt;Agent Standing Orders&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Conversion involved two changes to each document. First, YAML frontmatter at line 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Guardrail&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Operation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Scope&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Eval"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pre-flight&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;answered&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;modifying&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;app/main.py&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tests/."&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;eval&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pre-flight&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;operation-scope&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;layer-3&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-28&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, a &lt;code&gt;## Related&lt;/code&gt; section at the bottom of every ADR, eval, skill, and runbook — cross-links to the documents most likely to be relevant when reading this one.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;eval-operation-scope.md&lt;/code&gt;, the related section links to ADR-001 and ADR-002 (whose invariants this eval enforces), the CLAUDE.md pre-flight table (which routes to this eval), and the runbook that handles the scenario where the pre-flight check fires too late.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;ADR-001-inventory-before-payment.md&lt;/code&gt;, the related section links back to the eval that enforces it at runtime, the Gherkin scenarios in &lt;code&gt;tests/features/order_creation.feature&lt;/code&gt; that test the behavioral outcome, and the CLAUDE.md decision index entry.&lt;/p&gt;

&lt;p&gt;Then index files at each directory level, following OKF spec §6:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;docs/index.md                  — entry point for the bundle
docs/ADR/index.md              — both decisions with descriptions
docs/evals/index.md            — three evals with trigger summaries
docs/runbooks/index.md         — both runbooks with trigger scenarios
docs/skills/index.md           — three-tier structure explanation
docs/skills/tier1/index.md
docs/skills/tier2/index.md     — five tier-2 skills including deprecated
docs/skills/tier3/index.md
docs/log.md                    — bundle update history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OKF conformance check after conversion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total docs: 24
Missing frontmatter: 0
All docs have frontmatter.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All tests still passing. The conversion touched only &lt;code&gt;docs/&lt;/code&gt; files — no implementation, no feature files, no step definitions, no Pact files.&lt;/p&gt;




&lt;h2&gt;
  
  
  The experiment
&lt;/h2&gt;

&lt;p&gt;One task. Two fresh agent contexts. No knowledge of what the other run did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The task:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add a new endpoint to the order service: &lt;code&gt;GET /orders/{order_id}/history&lt;/code&gt; — returns a list of status changes the order has gone through (created, confirmed, etc.) with timestamps. Write the Gherkin scenarios first. Apply the relevant skills. Run the pre-flight evals. Then implement."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Run A&lt;/strong&gt; started with: CLAUDE.md + task description only.&lt;br&gt;
&lt;strong&gt;Run B&lt;/strong&gt; started with: CLAUDE.md + task description + &lt;code&gt;docs/index.md&lt;/code&gt; as explicit starting point, with the instruction "The docs/ directory is an OKF knowledge bundle. Start by reading docs/index.md for an overview of available knowledge, then navigate from there."&lt;/p&gt;

&lt;p&gt;Both runs documented which files were read before the first Gherkin scenario was written.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Run A — Navigation log (10 files, in order)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier1/output-formatting-standard.md&lt;/code&gt; — CLAUDE.md named it explicitly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier2/gherkin-scenario-quality-v2.md&lt;/code&gt; — CLAUDE.md named it explicitly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/ADR/ADR-001-inventory-before-payment.md&lt;/code&gt; — CLAUDE.md decision index&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/ADR/ADR-002-fire-and-forget-notification.md&lt;/code&gt; — CLAUDE.md decision index&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/eval-operation-scope.md&lt;/code&gt; — CLAUDE.md pre-flight table&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/features/order_creation.feature&lt;/code&gt; — style reference&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/features/order_status_good.feature&lt;/code&gt; — GET endpoint assertion style&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/main.py&lt;/code&gt; — data model and in-memory store&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/features/notification_service.feature&lt;/code&gt; — count pattern style&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/index.md&lt;/code&gt; — end-of-navigation check: confirmed nothing missed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two tool calls to find the Gherkin skill. One call for each ADR. One call for the eval. Then code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run B — Navigation log (20 files, in order)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;docs/index.md&lt;/code&gt; — entry point (required by experiment)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/&lt;/code&gt; directory listing — index mentioned skills/ subdirectory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/ADR/&lt;/code&gt; directory listing — index mentioned ADR/ subdirectory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/&lt;/code&gt; directory listing — index mentioned evals/ subdirectory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/index.md&lt;/code&gt; — three-tier structure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/ADR/index.md&lt;/code&gt; — ADR listing before individual ADRs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/index.md&lt;/code&gt; — which evals exist and what they trigger on&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier2/index.md&lt;/code&gt; — find Gherkin quality skill by name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier2/gherkin-scenario-quality-v2.md&lt;/code&gt; — the skill&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/ADR/ADR-001-inventory-before-payment.md&lt;/code&gt; — pre-flight ADR check&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/eval-operation-scope.md&lt;/code&gt; — pre-flight eval&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/ADR/ADR-002-fire-and-forget-notification.md&lt;/code&gt; — complete ADR check&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/eval-environment.md&lt;/code&gt; — &lt;strong&gt;confirmed not triggered&lt;/strong&gt; (Run A did not read this)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/eval-contract-preflight.md&lt;/code&gt; — &lt;strong&gt;confirmed not triggered&lt;/strong&gt; (Run A did not read this)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier1/output-formatting-standard.md&lt;/code&gt; — formatting standard&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/features/order_status_good.feature&lt;/code&gt; — style reference&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/features/order_status_good.feature&lt;/code&gt; (second pass) — additional assertion style reference (Run A did not revisit this)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/main.py&lt;/code&gt; — data model&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/features/order_creation.feature&lt;/code&gt; — style reference&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier2/step-definition-style.md&lt;/code&gt; — step definition conventions (&lt;strong&gt;Run A did not read this&lt;/strong&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Five hops to find the Gherkin skill. Eight directory traversals before any ADR was opened.&lt;/p&gt;




&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: Did the agent find the relevant skill faster in Run B?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Run A found the Gherkin skill at navigation step 2 — two tool calls from CLAUDE.md. Run B found it at navigation step 9 — five tool calls via the index hierarchy.&lt;/p&gt;

&lt;p&gt;OKF was slower by three navigation steps.&lt;/p&gt;

&lt;p&gt;The reason is simple: CLAUDE.md names exact file paths. &lt;code&gt;docs/skills/tier2/gherkin-scenario-quality-v2.md&lt;/code&gt; is in CLAUDE.md's skill table. The agent opens it directly. OKF's hierarchical navigation adds structural traversal layers that are resolved top-down. When a direct pointer already exists, hierarchical navigation is strictly overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Did OKF cross-linking change which documents the agent consulted?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes — significantly.&lt;/p&gt;

&lt;p&gt;Run B's agent read all three evals. Run A's agent read one (the one that fires). The &lt;code&gt;docs/evals/index.md&lt;/code&gt; caused Run B's agent to read &lt;code&gt;eval-environment.md&lt;/code&gt; and &lt;code&gt;eval-contract-preflight.md&lt;/code&gt; and explicitly confirm they were not triggered. Run A's agent did not know those evals existed until step 10, when it checked &lt;code&gt;docs/evals/index.md&lt;/code&gt; as a final verification.&lt;/p&gt;

&lt;p&gt;The "Related" section in &lt;code&gt;eval-operation-scope.md&lt;/code&gt; provided a third confirmation path to ADR-001 and ADR-002 — beyond CLAUDE.md's decision index and the ADR/index.md listing. Three independent paths to the same documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Were there documents found in one run but not the other?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run B found, Run A did not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/eval-environment.md&lt;/code&gt; — proactively read and confirmed not triggered&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/evals/eval-contract-preflight.md&lt;/code&gt; — proactively read and confirmed not triggered&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/skills/tier2/step-definition-style.md&lt;/code&gt; — &lt;strong&gt;discovered via tier2/index.md; CLAUDE.md does not reference this skill in its skill table&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last item is the most important finding in the experiment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;step-definition-style.md&lt;/code&gt; is the skill that encodes five implicit conventions for writing step definition files — the fixture-chaining pattern, mock server state asserted via call log rather than response body, the &lt;code&gt;time.sleep(0.3)&lt;/code&gt; timing, and the &lt;code&gt;_post_order&lt;/code&gt;-style helper naming. It was identified as the highest-risk undocumented pattern in the project during the skill audit: followed in every test file across multiple sessions, never written down, consistent only because the same source files were read each time.&lt;/p&gt;

&lt;p&gt;CLAUDE.md's skill table does not list it. An agent relying solely on CLAUDE.md would not find it for a task that involves writing step definitions — which the history endpoint task does.&lt;/p&gt;

&lt;p&gt;Run B's agent found it because &lt;code&gt;tier2/index.md&lt;/code&gt; listed it alongside the four other tier-2 skills. The OKF index is not selective — it lists everything in the directory. CLAUDE.md is selective — it lists what the author remembered to add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: Did the index.md change the order of understanding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes — and this is the most structural difference between the two runs.&lt;/p&gt;

&lt;p&gt;Run A built understanding &lt;strong&gt;specific-first&lt;/strong&gt;: went directly to individual documents named in CLAUDE.md before having any overview of what existed.&lt;/p&gt;

&lt;p&gt;Run B built understanding &lt;strong&gt;overview-first&lt;/strong&gt;: read the bundle structure before reading any individual document, arriving at each document knowing what else existed in the same category.&lt;/p&gt;

&lt;p&gt;This mattered for the eval space specifically. Run B's agent knew "there are three evals" before reading any of them. Run A's agent knew only "there is a pre-flight table in CLAUDE.md" and read only the eval that fires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: Did OKF frontmatter change any agent decision?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not directly. The &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, and &lt;code&gt;description&lt;/code&gt; fields were not cited as decision drivers in either run. The &lt;code&gt;description&lt;/code&gt; field in index.md entries was used to confirm documents before opening them — but this was confirmation, not routing. Routing was driven by CLAUDE.md in both runs.&lt;/p&gt;

&lt;p&gt;The frontmatter's most useful effect was indirect: the description field in index entries gave Run B's agent enough context to decide whether to open a file without opening it. A modest efficiency gain, not a qualitative change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: How many implicit decisions were made?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run A: 10. Run B: 11. Difference of 1 — within noise.&lt;/p&gt;

&lt;p&gt;OKF cross-linking does not reduce implicit decisions for Gherkin scenario writing. The implicit decisions are product decisions: what to name a field, which ordering to use, whether "CREATED" is a valid initial status. These come from specification gaps, not navigation gaps. No amount of infrastructure can supply them. That remains the human's job.&lt;/p&gt;




&lt;h2&gt;
  
  
  What OKF gives that CLAUDE.md does not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Completeness over selection.&lt;/strong&gt; CLAUDE.md's skill table will always lag the actual document count. It lists what the author remembered to add. OKF's index never lags — the index is derived from the documents. Every file in &lt;code&gt;tier2/&lt;/code&gt; appears in &lt;code&gt;tier2/index.md&lt;/code&gt;. Every eval in &lt;code&gt;evals/&lt;/code&gt; appears in &lt;code&gt;evals/index.md&lt;/code&gt;. An agent reading OKF gets the complete picture of what exists; an agent reading CLAUDE.md gets the author's current mental model of what the agent needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview-first navigation.&lt;/strong&gt; An agent starting from &lt;code&gt;docs/index.md&lt;/code&gt; knows the shape of the knowledge bundle before it navigates into it. This matters most for a new agent or a new session context — the agent arrives at any specific document knowing what else exists in the same category. CLAUDE.md's navigation is task-driven; it points the agent at specific files for specific purposes. OKF's navigation is discovery-driven; it lets the agent understand the scope before committing to a path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmed non-applicable documents.&lt;/strong&gt; Run B's agent confirmed that two evals were not triggered and documented that confirmation. Run A's agent may have silently assumed those evals did not exist. The difference between "I checked and it does not apply" and "I did not check" matters in a project where a missed eval is the failure mode Layer 3 was built to prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CLAUDE.md gives that OKF cannot replace
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Direct routing is faster.&lt;/strong&gt; When CLAUDE.md names an exact file path, the agent opens it in one tool call. When OKF provides a hierarchy, the agent traverses N levels. For targeted navigation on a known task, CLAUDE.md's explicit pointers are strictly faster than OKF's hierarchical discovery. The three-step speed advantage in Run A is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavioral instructions.&lt;/strong&gt; CLAUDE.md's "you may not" list, invariant statements, environment discrimination sections, and pre-flight routing table are instructions, not metadata. "Before modifying &lt;code&gt;app/main.py&lt;/code&gt;, run &lt;code&gt;docs/evals/eval-operation-scope.md&lt;/code&gt;" is a behavioral instruction. OKF can express "eval-operation-scope.md exists and its description says it intercepts &lt;code&gt;app/main.py&lt;/code&gt; modifications" — but an agent must infer from that description that it should run the eval. Inference is the failure mode. The routing instruction is what prevents inference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavioral constraints.&lt;/strong&gt; CLAUDE.md's HALT conditions and prohibition list ("you may never push directly to main," "do not add &lt;code&gt;continue-on-error&lt;/code&gt; to pipeline jobs") cannot be represented as OKF frontmatter. They are instructions, not structured knowledge. OKF formalizes what exists; CLAUDE.md governs what must be done and what must not be done. These are different layers of the same system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest synthesis
&lt;/h2&gt;

&lt;p&gt;OKF and CLAUDE.md solve different problems.&lt;/p&gt;

&lt;p&gt;CLAUDE.md is an instruction document that happens to contain a knowledge map. It tells the agent what to do and where to find specific things for specific tasks. Its strength is precision and speed for known tasks.&lt;/p&gt;

&lt;p&gt;OKF is a knowledge map that happens to be readable by agents. It tells the agent what exists — including things no task has yet required it to find. Its strength is completeness and discovery for unknown scope.&lt;/p&gt;

&lt;p&gt;When both exist: CLAUDE.md's direct pointers are faster for targeted navigation. OKF's index hierarchy is more complete. The combination catches what each one misses alone. CLAUDE.md's skill table misses &lt;code&gt;step-definition-style.md&lt;/code&gt;; OKF's &lt;code&gt;tier2/index.md&lt;/code&gt; surfaces it. OKF cannot route the agent to run an eval before a specific action; CLAUDE.md's pre-flight table does exactly that.&lt;/p&gt;

&lt;p&gt;The finding that runs counter to the intuitive case for OKF: for a project with a well-maintained CLAUDE.md, OKF does not replace or accelerate what CLAUDE.md already does. It fills the gaps that CLAUDE.md leaves uncovered. Those gaps are real — &lt;code&gt;step-definition-style.md&lt;/code&gt; is a high-priority skill that CLAUDE.md's skill table does not reference, meaning any agent relying solely on CLAUDE.md for navigation would miss it entirely — but they are not the primary navigation problem. They are the completeness problem.&lt;/p&gt;

&lt;p&gt;The right architecture is both. CLAUDE.md for routing and behavioral instructions. OKF for structural completeness and cross-document relationships. Neither replaces the other. They solve different problems at the same layer of the stack.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The order-api repository is now an OKF v0.1 conformant bundle. The full conversion — frontmatter, index files, cross-links, and log.md — is in the repo.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Further reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;Google OKF v0.1 Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://level5engineer.substack.com" rel="noopener noreferrer"&gt;Spin-off Article A: What Google Just Formalized (And What We've Been Building All Along)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://level5engineer.substack.com" rel="noopener noreferrer"&gt;The Level 5 Engineer — start here with Issue #1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Skill Review: A New Artifact for a New Workflow</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/diyaburman/the-skill-review-a-new-artifact-for-a-new-workflow-2m4d</link>
      <guid>https://dev.to/diyaburman/the-skill-review-a-new-artifact-for-a-new-workflow-2m4d</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Issue #11 stress-tested the Gherkin quality skill and found four failure modes. Issue #11 then fixed them. The resulting v2.0 skill passed every adversarial input.&lt;/p&gt;

&lt;p&gt;This issue asks the question that should have come first: how do you review a skill before the stress tests tell you what's wrong?&lt;/p&gt;

&lt;p&gt;The answer matters because stress tests and skill reviews catch different things. A stress test answers "does the skill work when called?" A review answers "is the skill ready to be called in all the contexts its description implies?" They are complementary. Running the stress tests first and the review second — as happened in Issues #11 and #12 — is the wrong order.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why skill review is a different discipline from code review
&lt;/h2&gt;

&lt;p&gt;Code review is a solved problem. You review the diff. You check the logic. You ask "does this code do what it should?" and you either approve or request changes.&lt;/p&gt;

&lt;p&gt;Skill review is not a solved problem. The review target is different. You are not asking whether the code is correct. You are asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the routing signal route correctly in all contexts where this skill should fire, and no contexts where it shouldn't?&lt;/li&gt;
&lt;li&gt;Could two agents produce different outputs that both satisfy the output contract?&lt;/li&gt;
&lt;li&gt;Does the methodology describe reasoning that generalises, or a procedure that only applies to the examples shown?&lt;/li&gt;
&lt;li&gt;Does the skill fail explicitly when it can't produce correct output — or does it produce plausible-looking wrong output?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these questions can be answered by reading the code. They can only be answered by working through a structured checklist.&lt;/p&gt;




&lt;h2&gt;
  
  
  The five-dimension checklist
&lt;/h2&gt;

&lt;p&gt;The review framework built in this session covers five dimensions. Every numbered question must be answered before a skill version can be approved. A reviewer who reads a skill and asks "does this look reasonable?" is not doing a skill review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension 1 — Routing signal&lt;/strong&gt;&lt;br&gt;
Is the description on a single line and under 120 characters? Does it name the artifact type, the domain scope, and the methodology — specifically enough to route correctly and generally enough not to misroute?&lt;/p&gt;

&lt;p&gt;The test: write three prompts that SHOULD route to this skill and three that SHOULD NOT. Verify each. Document any misroutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension 2 — Output contract&lt;/strong&gt;&lt;br&gt;
Is the contract explicit and enumerable — every requirement a yes/no check, not a judgment call? Could two agents produce different outputs that both satisfy it? Does the contract specify what the skill must NOT produce, not just what it must?&lt;/p&gt;

&lt;p&gt;The test: identify the downstream consumer. Document what it does with the skill's output. Ask whether the contract is sufficient for that consumption pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension 3 — Methodology&lt;/strong&gt;&lt;br&gt;
Does the methodology describe reasoning or procedure? Pick three edge case inputs not covered by the methodology examples. Apply the methodology manually. Document whether it produces correct output for each.&lt;/p&gt;

&lt;p&gt;The test: identify domain knowledge that an agent cannot infer from first principles. It must be stated explicitly, not implied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension 4 — Idempotency and stability&lt;/strong&gt;&lt;br&gt;
Apply the skill to the same input with three different framings. Do all three produce structurally identical output? Apply the skill to an already-correct input. Does it return unchanged or rewrite unnecessarily? Apply the skill to its own output. Does it return unchanged?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dimension 5 — Failure modes&lt;/strong&gt;&lt;br&gt;
Test with one out-of-scope input, one contradictory input, one empty input. For each, classify the output: FAIL SIGNAL (explicit failure, no output), PLAUSIBLE WRONG (looks correct, contains error), or CORRECT REFUSAL (actionable error message). Are all PLAUSIBLE WRONG outcomes eliminated?&lt;/p&gt;


&lt;h2&gt;
  
  
  Applying the framework to v1.1
&lt;/h2&gt;

&lt;p&gt;The v1.1 review is the review that should have happened before the Issue #11 stress tests were needed. Working through all five dimensions found findings the stress tests could never have found — and confirmed exactly which failures the stress tests did find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing signal — 137 characters against a 120-character limit.&lt;/strong&gt; The description is 17 characters over the threshold above which many agent routing frameworks truncate or deprioritise the signal. The excess carries "and output contract" — meaningful to the skill author, invisible to an agent routing on a 120-character budget. The stress tests in Issue #11 could not find this — they test behaviour when the skill is invoked. They cannot test whether the skill is invoked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output contract permits agent divergence.&lt;/strong&gt; Four under-specified requirements allow two agents to produce different outputs that both satisfy the contract: scenario title "explicit" criterion, required fields per scenario type, required HTTP status codes per outcome, required external services per scenario type. Stress tests verify one agent's output. They cannot reveal the latitude available to a second agent working from the same contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Methodology gap for missing Given clause.&lt;/strong&gt; The Q3 check asks whether terms are defined, but not whether the precondition state is established. A scenario with no Given clause passes Q3 if none of the steps use undefined nouns. The stress tests used well-formed inputs; this gap was not in the input set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two PLAUSIBLE WRONG failure modes confirmed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI scenario → translated silently to API scenario&lt;/li&gt;
&lt;li&gt;Contradictory constraints → documented as assumptions, scenario produced anyway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are exactly the failures the Issue #11 stress tests found. A pre-v2.0 review using this checklist would have required explicit termination for both cases — and v2.0's Guards 2 and 3 might have been built before the stress tests were necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v1.1 Review verdict: CHANGES REQUESTED.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Applying the framework to v2.0
&lt;/h2&gt;

&lt;p&gt;The v2.0 review confirms that the four stress-test failures are fixed. It also finds three issues the stress tests missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing signal is now 179 characters.&lt;/strong&gt; v2.0 made the signal 42 characters longer than v1.1's already-failing signal. The addition of ", four pre-flight guards, and a minimal-change" describes internal implementation mechanisms that are irrelevant to a caller routing to this skill. The routing signal now describes how the skill works internally rather than what it produces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guard 4 return value format is ambiguous between two instructions.&lt;/strong&gt; This is the most important finding in the session. Documented in full below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guard 4 gap for missing Q5 assertions.&lt;/strong&gt; A scenario that passes all five Guard 4 format conditions — concrete IDs, named services, HTTP status, no UNDERSPECIFIED patterns, field+value Then clauses — but is missing Q5 side-effect assertions (payment gateway call count, inventory reservation assertion) triggers Guard 4 and returns "no changes required." The skill signals completion for an incomplete scenario. The stress tests in Issue #11 did not test this input type because the stress tests focused on the four failure modes v2.0 was designed to fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two new edge cases introduced:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guard 2 rejects mixed UI/API scenarios (one UI step, three API steps) entirely, when partial assistance on the three API steps is possible. Over-broad refusal.&lt;/li&gt;
&lt;li&gt;The v2.1 work list: shorten the description, clarify Guard 4 return format, add Q5 side-effect check to Guard 4, document mixed UI/API handling, add reasoning to Guard 2's pattern list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;v2.0 Review verdict: APPROVED WITH COMMENTS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The four major failure modes are addressed. The new issues are not blocking. v2.0 is ready to be the canonical version — with a v2.1 planned.&lt;/p&gt;


&lt;h2&gt;
  
  
  The real review comment
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PR:&lt;/strong&gt; &lt;code&gt;gherkin-scenario-quality-v2.md&lt;/code&gt; — Agent-safe Gherkin quality skill&lt;br&gt;
&lt;strong&gt;Section:&lt;/strong&gt; Pre-flight guards → Guard 4 (Idempotency check)&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Guard 4 has two return instructions that conflict, and the conflict matters at agent scale.&lt;/p&gt;

&lt;p&gt;The "Return:" block shows only the annotation comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SKILL: No changes required — scenario satisfies output contract.
# Five-question diagnostic result: [observations, or "none"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the next line says: "If Guard 4 triggers, return the input scenario unchanged."&lt;/p&gt;

&lt;p&gt;Together these read as: return the comment block, AND return the input scenario. But a skill returns a single value. The two instructions imply three possible interpretations: (a) the annotation only — the scenario is not in the output; (b) the annotation prepended to the scenario, matching the minimal-fix pattern used elsewhere; or (c) the scenario with the annotation appended.&lt;/p&gt;

&lt;p&gt;The rest of the skill uses format (b). Guard 4's "Return:" block uses format (a). The inconsistency is invisible when a human reads the output and manually pastes the scenario into a feature file — the human ignores the comment and pastes the scenario. But in an automated pipeline it is not invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concrete impact:&lt;/strong&gt; A downstream agent that receives Guard 4 output and writes all skill output to a feature file would write the &lt;code&gt;# SKILL: No changes required&lt;/code&gt; annotation as a Gherkin comment into the file. At pipeline scale across 50 feature files, that is 50 permanent skill-internal annotations committed to production specs. If the agent uses interpretation (a) and treats the annotation block as the complete output, the original scenario is silently discarded — replaced by two comment lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suggested fix:&lt;/strong&gt; Align Guard 4's return spec with the minimal-fix annotation pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return the input scenario with the following comment prepended:
  # SKILL: No changes required — scenario satisfies output contract.
  # Five-question diagnostic result: [observations; "none" if Q1–Q5 find nothing]
[followed by the complete input scenario, unchanged]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, if the annotation is caller metadata and NOT part of the Gherkin output, state this explicitly: "The guard annotation is caller metadata. Do not include it in the feature file. Return it as a separate response block before the unchanged scenario."&lt;/p&gt;

&lt;p&gt;Either formulation eliminates the ambiguity. The current text requires the downstream agent to guess.&lt;/p&gt;




&lt;p&gt;Why this is the most important finding from either review: v2.0 was built explicitly to be safe at agent scale. The four guards exist because automated pipelines create failure modes that human callers handle silently. Guard 4's return value specification has the same class of failure it was designed to prevent — a human reading the output knows which part is the scenario and which part is metadata; an automated pipeline does not. The stress tests verified that Guard 4 triggers correctly. What they could not verify — because they test the skill in isolation — is whether Guard 4's output is correctly specified for all downstream consumers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The boundary between stress testing and skill review
&lt;/h2&gt;

&lt;p&gt;The stress tests in Issue #11 found three behavioral failures and confirmed a fourth. This session's review found three findings the stress tests could not reach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The routing signal length: only testable by asking whether the skill is selected, not how it behaves when selected&lt;/li&gt;
&lt;li&gt;The Guard 4 return value ambiguity: only testable by asking what a downstream agent does with the output, not what the output contains&lt;/li&gt;
&lt;li&gt;The Guard 4 gap for missing Q5 assertions: only testable with an input type that passes the four guards while carrying a structural omission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stress tests answer "does the skill work?" Review answers "is the skill ready for every context?" The findings that stress tests cannot reach are the ones where the skill is correctly invoked, correctly produces output, and a downstream system still fails — because the output format was not specified for that consumption pattern, or because the routing signal was too long to fire reliably, or because a guard fired on valid input.&lt;/p&gt;

&lt;p&gt;Running the stress tests first, as happened in Issue #11, found the acute failures. Running the review second, as happened here, found the ones that would have surfaced later — quietly, in production, without a clear signal that the skill was the cause.&lt;/p&gt;

&lt;p&gt;The correct order is review first, stress tests second. The review tells you where to aim the stress tests.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: The Skill Audit — walking through the full prompt library accumulated across twelve issues, applying the tier framework, and building the audit template readers can use on their own libraries.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Agent-First Skills Architecture&lt;/a&gt; · &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/docs/skill-review-checklist.md" rel="noopener noreferrer"&gt;Skill review checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-12-skill-review.md" rel="noopener noreferrer"&gt;Session findings — Issue #12&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>What Google Just Formalized (And What We've Been Building All Along)</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/diyaburman/what-google-just-formalized-and-what-weve-been-building-all-along-21bd</link>
      <guid>https://dev.to/diyaburman/what-google-just-formalized-and-what-weve-been-building-all-along-21bd</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;On June 12, 2026, Google Cloud published the Open Knowledge Format — a specification for representing organizational knowledge as a directory of markdown files with YAML frontmatter, designed to be authored by people, generated by agents, and consumed by both without bespoke SDKs.&lt;/p&gt;

&lt;p&gt;I found out about it from a comment on Issue #11 — left by &lt;a href="https://substack.com/@larkwin" rel="noopener noreferrer"&gt;Larkwin&lt;/a&gt;, a friend whose work I genuinely respect. If you're building something ambitious and hitting the stage where operational scaling becomes the bottleneck, their firm &lt;a href="https://lark.win/" rel="noopener noreferrer"&gt;lark.win&lt;/a&gt; does fractional leadership and engineering velocity work with senior operators who embed with your team and own outcomes. Worth a conversation if that's where you are.&lt;/p&gt;

&lt;p&gt;But back to the comment — it pointed me at the OKF spec and that was the thread that unravelled this piece. So: thank you.&lt;/p&gt;

&lt;p&gt;My first reaction was recognition, not surprise. The problem OKF is solving — knowledge scattered across wikis, heads, tickets, and shared drives that AI agents cannot assemble reliably — is the exact problem this newsletter has been building infrastructure to address since Issue #2. Independently. In a single-repo, single-engineer context. With a different vocabulary but the same structure.&lt;/p&gt;

&lt;p&gt;This piece is the mapping. Every artifact built so far in The Level 5 Engineer sits somewhere in OKF's concept taxonomy. And the gap that OKF has not yet closed points directly at what the next phase of this series needs to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  What OKF is, briefly
&lt;/h2&gt;

&lt;p&gt;OKF formalizes the LLM-wiki pattern into a portable, interoperable format. It is vendor-neutral and agent-friendly, representing knowledge as a directory of markdown files with YAML frontmatter and requiring no new runtime or SDK.&lt;/p&gt;

&lt;p&gt;A bundle of OKF documents is just markdown, just files, and just YAML frontmatter. One required field — &lt;code&gt;type&lt;/code&gt;. Optional metadata: title, description, resource, tags, timestamp. A markdown body for everything else. Concepts link to each other with standard markdown links, turning the directory into a traversable graph.&lt;/p&gt;

&lt;p&gt;The full v0.1 specification fits on a single page.&lt;/p&gt;

&lt;p&gt;If you have used Obsidian or written a CLAUDE.md file, the shape is immediately familiar. What OKF adds is the agreed-upon conventions that make a bundle written by one team consumable by a different agent without translation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem it is solving
&lt;/h2&gt;

&lt;p&gt;Most teams don't suffer from a lack of data. They suffer from a lack of shared context. Definitions, caveats, ownership, and "how to use this safely" guidance end up scattered across wikis, tickets, dashboards, and people's heads.&lt;/p&gt;

&lt;p&gt;When an AI agent needs to answer a question about your system, it has to assemble the answer from these scattered, mutually incompatible surfaces. People compensate with experience — they know which wiki is "more correct," who to ask, and which dashboard is legacy. Agents don't have that intuition. When context is missing or split across systems, an agent has to infer and guess.&lt;/p&gt;

&lt;p&gt;This is precisely the problem the series has been building toward. The failure modes this newsletter is working to prevent — agents re-deriving decisions that were already made, agents treating production and staging resources interchangeably, agents removing a guard that was there for a reason — are all context failures. Not capability failures. The agent had the intelligence. It just didn't know enough about its environment.&lt;/p&gt;

&lt;p&gt;OKF is solving the same problem from a different angle. Where this newsletter builds project-specific artifacts (CLAUDE.md, ADRs, evals, runbooks), OKF is building a portable, cross-org standard. Same problem. Different scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mapping
&lt;/h2&gt;

&lt;p&gt;The artifacts built so far in this series, mapped to OKF concept types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills → &lt;code&gt;type: Methodology&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Gherkin quality skill, the step definition style skill, the session start protocol — these are methodologies. They encode domain-specific reasoning that agents use to produce consistent output. OKF's Methodology type captures this: a concept that describes how to do something rather than what something is.&lt;/p&gt;

&lt;p&gt;The OKF cross-linking that would make these most useful: a Methodology concept should link to the ADR or finding that motivated it, the artifacts it is meant to produce, and any prerequisites that should be read before using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ADRs → &lt;code&gt;type: Decision&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ADR-001 (inventory before payment) and ADR-002 (fire-and-forget notification) are Decision concepts — single units of knowledge that capture a choice, its context, and its consequences. OKF's structure maps cleanly: the YAML frontmatter holds the metadata, the markdown body holds the human-facing ADR content, and cross-links connect the decision to the Gherkin scenarios that enforce it and the evals that protect it at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evals → &lt;code&gt;type: Guardrail&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This type is not in OKF's example list — but OKF explicitly does not define a fixed taxonomy of concept types. Producers choose values that are descriptive and self-explanatory. &lt;code&gt;Guardrail&lt;/code&gt; is the right name for the pre-flight checks being built in this series: they are not tests of output, they are checks that intercept intent before execution and ask whether the situation is safe to proceed.&lt;/p&gt;

&lt;p&gt;The cross-linking for a Guardrail concept is the most important part. Each eval links to the failure mode it addresses, the ADRs whose invariants it enforces, and the CLAUDE.md section that routes to it before relevant actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runbooks → &lt;code&gt;type: Playbook&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OKF's own example uses &lt;code&gt;type: Playbook&lt;/code&gt; for a runbook — the incident response for a data freshness alert. The agent-facing runbook in this series fits this type precisely. The critical difference between the human-facing and agent-facing versions maps directly to OKF's design intent: OKF is written for agents that cannot fill gaps with judgment. The structure with explicit decision trees, named thresholds, and completion criteria is the agent-readable version of what OKF Playbooks should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLAUDE.md → &lt;code&gt;type: Agent Standing Orders&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CLAUDE.md is not a standard OKF type, but it is the most important concept in the bundle. It is the document the agent reads before any other — the standing orders that govern session behavior, permissions, and routing. In an OKF bundle, it would link outward to every other concept type: Methodologies (skills to apply), Decisions (ADRs to consult before modifying covered code), Guardrails (evals to run before risky actions), and Playbooks (runbooks for degraded scenarios).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The findings/ directory → &lt;code&gt;log.md&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OKF's &lt;code&gt;log.md&lt;/code&gt; is a chronological history of changes at any bundle level. The findings/ directory in this project is the same thing: a structured record of what was attempted, what failed, and what was learned, updated in real time during every session. The difference is scope — OKF's log.md records what changed; this project's findings files record why it changed and what the finding means for the reader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three-tier skills structure → OKF subdirectory organization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OKF's bundle structure is hierarchical: subdirectories group concepts, and each level can have its own &lt;code&gt;index.md&lt;/code&gt; for progressive disclosure. The &lt;code&gt;docs/skills/tier1/&lt;/code&gt;, &lt;code&gt;docs/skills/tier2/&lt;/code&gt;, and &lt;code&gt;docs/skills/tier3/&lt;/code&gt; structure is already OKF-conformant in shape. Adding &lt;code&gt;index.md&lt;/code&gt; files at each level would make the tier hierarchy navigable by an agent reading the bundle from the root.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the convergence means
&lt;/h2&gt;

&lt;p&gt;Two efforts, independently arriving at the same structure.&lt;/p&gt;

&lt;p&gt;OKF was designed for enterprise data teams managing BigQuery datasets, metric definitions, and incident runbooks across organizations. This newsletter was designed for a single engineer trying to make Claude Code sessions reliable and consistent across the project's lifetime.&lt;/p&gt;

&lt;p&gt;The core structure is the same: concepts as markdown files, cross-linked into a traversable graph, with YAML frontmatter that tells a consuming agent what kind of thing it is reading before it reads the body.&lt;/p&gt;

&lt;p&gt;The convergence is not coincidental. It reflects the underlying problem. Adopting OKF now is a bet that agentic workflows will move from experiments to core operations — it pays off fastest in projects with decisions made across sessions, dependencies with their own failure modes, and invariants that emerged without explicit documentation. The order-api project is exactly that profile.&lt;/p&gt;

&lt;p&gt;What OKF formalizes is the pattern this project reached by building toward the same problem from the implementation side. The spec arrived six weeks ago. The need has been here since Issue #2.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gap OKF has not yet closed
&lt;/h2&gt;

&lt;p&gt;Three places where the order-api's artifacts go beyond what OKF v0.1 handles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invariant documentation.&lt;/strong&gt; OKF has no standard concept type or section convention for "this property must never change." ADRs in this project contain invariant sections — explicit statements of what would break if the decision were reversed, and which tests currently enforce the invariant. This is not standard OKF. It is an extension that addresses one of the most dangerous agent failure modes: an agent optimizing away a load-bearing constraint because nothing in the bundle marks it as non-negotiable.&lt;/p&gt;

&lt;p&gt;A proposed OKF extension field: &lt;code&gt;invariants: [list]&lt;/code&gt; in the frontmatter of a Decision concept, naming the properties that must remain true in all future implementations. This could be the basis for a v0.2 proposal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eval routing.&lt;/strong&gt; OKF's cross-linking mechanism is manual: a human or agent adds a markdown link from one concept to another. There is no mechanism for an OKF bundle to express "before modifying file X, read eval Y." The CLAUDE.md pre-flight section in this project handles this routing with a table that maps action types to eval documents. OKF could express this relationship as a new frontmatter field on Guardrail concepts: &lt;code&gt;intercepts: [list of file paths or pattern matches]&lt;/code&gt;. An agent that reads a Guardrail concept with an &lt;code&gt;intercepts&lt;/code&gt; field knows to apply the eval before modifying the listed files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill versioning.&lt;/strong&gt; OKF's &lt;code&gt;timestamp&lt;/code&gt; field records last meaningful change. The skill files in this project have version numbers (v1.1, v2.0) but OKF has no standard version field. The skill review process in this series makes explicit that v1.1 and v2.0 are different things with different capability guarantees. An agent routing to the Gherkin quality skill should find v2.0, not v1.1. OKF's current model requires the producer to deprecate v1.1 explicitly rather than providing a standard field that consuming agents can use to select the canonical version.&lt;/p&gt;

&lt;p&gt;A proposed OKF extension: &lt;code&gt;version: "2.0"&lt;/code&gt; and &lt;code&gt;supersedes: ../gherkin-scenario-quality.md&lt;/code&gt; in the frontmatter of a versioned Methodology concept.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for the series
&lt;/h2&gt;

&lt;p&gt;The series is currently building the stewardship layer — CLAUDE.md, ADRs, evals, and runbooks. OKF's arrival suggests a fourth consideration that will matter when that layer is complete: portability.&lt;/p&gt;

&lt;p&gt;The skills, ADRs, evals, and runbooks being built are useful to the agents that work on this project. They are not currently portable — another project would have to read the full series to understand what each document does and why it exists.&lt;/p&gt;

&lt;p&gt;An OKF-conformant version of the same bundle would be portable. The &lt;code&gt;type: Methodology&lt;/code&gt; frontmatter on a skill file tells any consuming agent — in any project, using any framework — what kind of thing it is reading before it reads the body. The cross-links tell the agent where to look next. The index.md at the bundle root tells the agent what is available before it opens any individual file.&lt;/p&gt;

&lt;p&gt;The companion article to this one converts the order-api's &lt;code&gt;docs/&lt;/code&gt; directory into a conformant OKF bundle and runs a comparison experiment: the same Claude Code task against the current structure versus the OKF bundle. That article answers whether OKF's formal structure changes what an agent does, or whether the informal structure this project built achieves the same result.&lt;/p&gt;

&lt;p&gt;For now, the honest observation: Google published a specification for the problem this series has been trying to solve. The solution they arrived at is the same solution this series arrived at. The vocabulary is different. The structure is the same.&lt;/p&gt;

&lt;p&gt;That is not a coincidence. It is evidence that the problem is real and the structure works.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Further reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;Google OKF v0.1 Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/" rel="noopener noreferrer"&gt;Google Cloud Blog: How the Open Knowledge Format can improve data sharing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://level5engineer.substack.com" rel="noopener noreferrer"&gt;The Level 5 Engineer — start here with Issue #1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Designing for Non-Human Callers</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Tue, 14 Jul 2026 16:30:15 +0000</pubDate>
      <link>https://dev.to/diyaburman/designing-for-non-human-callers-5bge</link>
      <guid>https://dev.to/diyaburman/designing-for-non-human-callers-5bge</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Issues #9 and #10 built skills and organised them into tiers. This issue breaks them.&lt;/p&gt;

&lt;p&gt;The thesis: skills built for human use degrade under agent load in specific, predictable ways. Not randomly. Not dramatically. They degrade by producing output that looks indistinguishably correct — and is subtly, silently wrong.&lt;/p&gt;

&lt;p&gt;The only way to find this before production does is to stress-test the skill deliberately. So that's what this session did.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "agent scale" actually means
&lt;/h2&gt;

&lt;p&gt;When a human uses a skill, there is a correction layer between the skill's output and the downstream action. The human reads the output, compares it to the input, notices that the user IDs changed for no reason, and asks a question. The skill's imprecision gets caught before it causes damage.&lt;/p&gt;

&lt;p&gt;At agent scale, that correction layer is absent. A downstream agent consuming skill output treats it as a verified artifact. It does not re-read the input and compare it to the output. It implements from what the skill produced. Changed user IDs become changed step definition values. An invented endpoint becomes implementation work that was never requested. A retained contradiction becomes a test that can never pass.&lt;/p&gt;

&lt;p&gt;Three properties distinguish a skill that survives agent-scale usage from one that doesn't:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency:&lt;/strong&gt; Calling the skill twice on the same input produces the same output. Not a similar output. The same one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output stability:&lt;/strong&gt; The output format does not drift based on how the task is framed, only on what the input contains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure specificity:&lt;/strong&gt; When the skill cannot produce correct output, it fails in a way that tells the caller exactly what is missing — rather than producing plausible-looking wrong output.&lt;/p&gt;

&lt;p&gt;The Gherkin quality evaluator from Issue #9 had none of these. Here's the proof.&lt;/p&gt;




&lt;h2&gt;
  
  
  The idempotency test
&lt;/h2&gt;

&lt;p&gt;Five runs. Same input scenario. Only the framing changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Order is confirmed when all conditions are met
  &lt;span class="nf"&gt;Given &lt;/span&gt;a user with a valid account
  &lt;span class="nf"&gt;And &lt;/span&gt;items are available
  &lt;span class="nf"&gt;When &lt;/span&gt;the order is placed
  &lt;span class="nf"&gt;Then &lt;/span&gt;it should succeed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The five framings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;em&gt;"Evaluate this scenario using the Gherkin quality skill."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Apply the Gherkin quality skill to improve this scenario."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Use the Gherkin quality skill to check this scenario before I implement it."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"This scenario needs to be agent-ready. Run it through the Gherkin quality skill."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"The Gherkin quality skill should evaluate this. What does it produce?"&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Across five runs with identical input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP status code&lt;/strong&gt; varied: 201 (Runs 1, 3, 4) vs 200 (Runs 2, 5). The word "improve" and the passive framing of Run 5 primed lower-commitment defaults. "Agent-ready" in Run 4 primed explicit assumption surfacing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number of scenarios&lt;/strong&gt; varied: two scenarios (Runs 1 and 4), two different scenarios (Run 3), one scenario (Runs 2 and 5).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure path&lt;/strong&gt; varied: stock-out (Runs 1, 4), payment decline (Run 3), absent (Runs 2, 5).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assumption comment count&lt;/strong&gt; varied: 0 (Run 3), 1 (Runs 2, 5), 2 (Run 1), 3 (Run 4).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core Then clauses were stable. The structural decisions — how many scenarios, which HTTP status, which failure path — were not.&lt;/p&gt;

&lt;p&gt;For a human, this is manageable. Read all five outputs, merge the best elements, proceed. For a downstream agent, this is a silent contract violation. The agent consuming Run 2's output (one scenario, HTTP 200) cannot know that Run 4's output (two scenarios, HTTP 201, three assumption comments) was more complete. It implements from what it received.&lt;/p&gt;

&lt;p&gt;The routing signal description does not specify whether the output must include failure scenarios, which HTTP status to use when the input is silent, or how aggressively to surface assumptions. These are structural decisions the skill leaves open. Different framings resolve them differently. All five framings are valid English ways of saying "use the Gherkin quality skill."&lt;/p&gt;




&lt;h2&gt;
  
  
  The output stability test
&lt;/h2&gt;

&lt;p&gt;Six inputs, each slightly improving on the baseline. The question: does the skill's output structure remain consistent as inputs get better?&lt;/p&gt;

&lt;p&gt;Inputs A through E — progressively more specific versions of the same scenario — produced stable output. The Then clause pattern held. The assumption comments appeared. The external services were named. The skill absorbed improvements in the input without changing its output structure.&lt;/p&gt;

&lt;p&gt;Input F was the critical test: a scenario that was already substantially well-formed, taken directly from &lt;code&gt;tests/features/order_creation.feature&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Order is successfully created when payment succeeds and all items are in stock
  &lt;span class="nf"&gt;Given &lt;/span&gt;a registered user with id &lt;span class="s"&gt;"user-123"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the inventory service confirms all items are in stock
  &lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway will accept the charge
  &lt;span class="nf"&gt;When &lt;/span&gt;the user submits an order for SHOE-RED-42 and BELT-BRN-M
  &lt;span class="nf"&gt;Then &lt;/span&gt;the order status is &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the response includes an order id
  &lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway received exactly one charge request
  &lt;span class="nf"&gt;And &lt;/span&gt;the inventory service received a reservation request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill found two minor debt items — a missing HTTP status in the Then, and "received a reservation request" without a count. Both real. Both fixable.&lt;/p&gt;

&lt;p&gt;Then it produced a full rewrite. It changed &lt;code&gt;user-123&lt;/code&gt; to a new ID. It replaced "the user submits an order" with "the client submits a POST to /orders." It restated every clause that was already correct.&lt;/p&gt;

&lt;p&gt;The output satisfied the output contract. It passed quality criteria. It looked better than the input.&lt;/p&gt;

&lt;p&gt;But "better" is not the contract. "Only changes what violates the contract" is the contract. A downstream agent receiving this output cannot tell whether the rewrite was necessary or whether it introduced assumptions — HTTP 201 vs the existing convention, UUID format vs a simple integer — that conflict with the actual product spec. The skill has no idempotency check. It rewrites everything. Even what didn't need rewriting.&lt;/p&gt;




&lt;h2&gt;
  
  
  The adversarial tests
&lt;/h2&gt;

&lt;p&gt;Four inputs designed to probe specific failure modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial A — Empty scenario.&lt;/strong&gt; The skill produced an explicit failure signal. No steps invented. Correct behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial B — The self-referential case.&lt;/strong&gt; This is the one that matters most.&lt;/p&gt;

&lt;p&gt;The baseline output from the first run was fed back into the skill as new input. The skill should have returned it unchanged. Instead it produced a new rewrite with two changes:&lt;/p&gt;

&lt;p&gt;It changed the user IDs. &lt;code&gt;user-baseline-001&lt;/code&gt; became &lt;code&gt;user-selfref-001&lt;/code&gt;. &lt;code&gt;user-baseline-002&lt;/code&gt; became &lt;code&gt;user-selfref-002&lt;/code&gt;. No semantic reason. No assumption comment explaining the change.&lt;/p&gt;

&lt;p&gt;It removed an assumption comment. The original output had explicitly documented: &lt;code&gt;# Assumption: "registered user" implies an existing user ID, not an auth token.&lt;/code&gt; The second run dropped it silently.&lt;/p&gt;

&lt;p&gt;The output otherwise satisfied the output contract. Correct field names. Correct format. Correct structure. A downstream agent consuming this output would have no way to distinguish it from a legitimate improvement.&lt;/p&gt;

&lt;p&gt;A step definition that hardcoded &lt;code&gt;user-baseline-001&lt;/code&gt; would now fail. A decision that was explicitly documented — "registered user means user ID, not auth token" — was silently erased.&lt;/p&gt;

&lt;p&gt;The skill produced plausible-looking wrong output. Confidently. Correctly formatted. Invisibly broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial C — Wrong domain.&lt;/strong&gt; The input was a UI scenario about a user logging into a dashboard. The skill translated it into an HTTP API contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; User authentication succeeds when valid credentials are provided
  &lt;span class="nf"&gt;Given &lt;/span&gt;a registered user with id &lt;span class="s"&gt;"user-ui-001"&lt;/span&gt; and password &lt;span class="s"&gt;"••••••••"&lt;/span&gt;
  &lt;span class="nf"&gt;When &lt;/span&gt;the client submits a POST to /auth/login with username &lt;span class="s"&gt;"user-ui-001"&lt;/span&gt;
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response HTTP status is 200
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains a &lt;span class="s"&gt;"token"&lt;/span&gt; field in JWT format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It invented an endpoint (&lt;code&gt;/auth/login&lt;/code&gt;). It invented a token format (JWT). It invented a response shape. None of these exist in this codebase.&lt;/p&gt;

&lt;p&gt;A downstream agent implementing from this output would build authentication infrastructure that was never specced, never reviewed, and never requested. The output looked reasonable. The skill had no domain check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial D — Contradicting constraints.&lt;/strong&gt; The input contained logically incompatible constraints: "processes the charge exactly once" in the When, and "called no more than 3 times" in the Then.&lt;/p&gt;

&lt;p&gt;The skill identified the contradiction in an assumption comment. Then it produced a rewrite that embedded both constraints in the output.&lt;/p&gt;

&lt;p&gt;A downstream agent implementing from this output would write a test that can never pass: exactly one charge request in the When and no more than three in the Then are incompatible for the same action. The warning comment was present. The rewrite proceeded anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  The reinforced skill
&lt;/h2&gt;

&lt;p&gt;Four failures. Four fixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency check.&lt;/strong&gt; Before producing any output, the reinforced skill checks whether the input already satisfies the output contract. If it does, it returns the input unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SKILL: No changes required — scenario satisfies output contract.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it partially satisfies it, it returns only the minimal targeted corrections, not a full rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain check.&lt;/strong&gt; If the input describes UI interactions — browser, clicks, page loads, form submissions — the reinforced skill fails explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SKILL FAILURE: This scenario describes UI behaviour, not an HTTP API contract.
# This skill applies to API-level specifications only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Contradiction halt.&lt;/strong&gt; If the input contains logically incompatible constraints, the reinforced skill warns and stops — no rewrite produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SKILL WARNING: Contradicting constraints detected in [step].
# Resolve before implementation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Self-reference guard.&lt;/strong&gt; The idempotency check handles this automatically. Skill output fed back as input triggers the check and returns unchanged. The guard is documented in the skill's output contract section so the behaviour is explicit, not emergent.&lt;/p&gt;

&lt;p&gt;Running all four adversarial inputs through the reinforced skill:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test case&lt;/th&gt;
&lt;th&gt;Original skill&lt;/th&gt;
&lt;th&gt;Reinforced skill&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Empty scenario&lt;/td&gt;
&lt;td&gt;Explicit fail signal ✅&lt;/td&gt;
&lt;td&gt;Explicit fail signal ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-referential (Adversarial B)&lt;/td&gt;
&lt;td&gt;Plausible wrong output ❌&lt;/td&gt;
&lt;td&gt;Returns unchanged ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong domain (Adversarial C)&lt;/td&gt;
&lt;td&gt;Invented out-of-scope endpoint ❌&lt;/td&gt;
&lt;td&gt;Domain failure signal ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contradiction (Adversarial D)&lt;/td&gt;
&lt;td&gt;Rewrite with embedded contradiction ❌&lt;/td&gt;
&lt;td&gt;Warning, no rewrite ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The specific failure mode
&lt;/h2&gt;

&lt;p&gt;The stress tests found the answer to the question this issue was designed to answer.&lt;/p&gt;

&lt;p&gt;A human-friendly skill is designed to always produce something useful. When a human asks "evaluate this," they always want an answer — even if the answer is "I couldn't evaluate this and here's why." A skill optimised for human use therefore has no termination conditions for edge cases. It produces output in all circumstances.&lt;/p&gt;

&lt;p&gt;When the input is already valid, the skill produces unnecessary changes. When the input is out of domain, the skill translates it rather than rejecting it. When the input contains a contradiction, the skill documents the contradiction in a comment rather than refusing to proceed.&lt;/p&gt;

&lt;p&gt;Each of these produces output that satisfies the output contract. Correct field names. Correct format. Correct structure. A downstream agent cannot distinguish this output from a legitimate improvement. The output looks like a skill succeeded. The downstream action proceeds. The error only becomes visible when a test fails for a user ID that was silently changed, or when an engineer asks why authentication infrastructure was built when it was never in scope.&lt;/p&gt;

&lt;p&gt;A human-friendly skill is dangerous at agent scale not because it produces wrong output — it produces output that looks indistinguishably right — but because the mechanism by which it produces wrong output is exactly the same as the mechanism by which it produces correct output: it always gives you something useful, and never tells you when useful is the wrong thing to give.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: The Skill Review — what code review looks like when the review target is the skill, not the diff. A PR template, a checklist, and a real review of the reinforced skill from this issue.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Agent-First Skills Architecture&lt;/a&gt; · &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/docs/skills/tier2/gherkin-scenario-quality-v2.md" rel="noopener noreferrer"&gt;Reinforced Gherkin skill v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-11-non-human-callers.md" rel="noopener noreferrer"&gt;Session findings — Issue #11&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>The 3-Tier Skill Architecture in Practice</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Tue, 14 Jul 2026 16:28:21 +0000</pubDate>
      <link>https://dev.to/diyaburman/the-3-tier-skill-architecture-in-practice-46o6</link>
      <guid>https://dev.to/diyaburman/the-3-tier-skill-architecture-in-practice-46o6</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Issue #9 ended with a single skill: the Gherkin scenario quality evaluator. One prompt converted into versioned infrastructure with an output contract and a routing signal.&lt;/p&gt;

&lt;p&gt;Issue #10 asks the harder question. When you have multiple skills, where do they go? And why does it matter?&lt;/p&gt;

&lt;p&gt;The answer turns out to involve a decision most engineers have never explicitly made: which of your working patterns are personal, and which are organisational standards? The answer to that question determines who owns the risk when a pattern fails to transfer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3-tier model
&lt;/h2&gt;

&lt;p&gt;The model is straightforward. The implications are not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1 — Org-wide standards.&lt;/strong&gt; Consistent across every agent, every session, every domain. Formatting templates, naming conventions, commit message structure, the test verification sequence. No judgment required — compliance required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2 — Domain methodology.&lt;/strong&gt; High-craft, domain-specific skills encoding senior practitioner expertise. The competitive moat. Specific enough to be genuinely useful, which means specific enough not to apply everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3 — Personal workflow.&lt;/strong&gt; Individual patterns that encode one person's working style or editorial taste. Valuable. Transferable. Almost never written down.&lt;/p&gt;

&lt;p&gt;The problem is not that teams don't have Tier 2 skills. They do — they just call them "the way we do it here" and carry them in engineers' heads. The 3-tier model is a container for making that implicit knowledge explicit. And for making a harder decision: which Tier 3 patterns have been silently doing Tier 2 work?&lt;/p&gt;




&lt;h2&gt;
  
  
  Mapping the order-api project
&lt;/h2&gt;

&lt;p&gt;After nine issues, the project has accumulated substantial judgment — in CLAUDE.md, in the findings files, in the code, in the session instructions that get rewritten every time. Auditing it against the 3-tier model surfaced something uncomfortable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually belongs at Tier 1:&lt;/strong&gt;&lt;br&gt;
The findings file protocol. The commit message conventions. The project constraints ("you may not modify .feature files"). The test verification sequence (Gherkin → Pact → can-i-deploy). All of these apply uniformly to every agent in every session. None of them have routing signals. None of them have output contracts. They exist as prose in CLAUDE.md — which means they are re-read and re-interpreted in every session, and there is no signal when they change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What belongs at Tier 2:&lt;/strong&gt;&lt;br&gt;
The Gherkin quality evaluator (already a skill — Issue #9). The spec-audit framework (exists as a 1,500-word reference document, not a skill). The step definition writing pattern (does not exist as a skill at all — only inferrable from reading four test files). The external service mock server architecture (why Python-native mock_server.py rather than real WireMock — documented nowhere except as the fact of what was built).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What belongs at Tier 3:&lt;/strong&gt;&lt;br&gt;
The "Why this matters" paragraph writing pattern. The article-worthiness filter. The spec-fix decision tree (when to change only the feature file vs when to change the step definition vs when to change the implementation). All three appear consistently across nine issues. None are written down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tier with the most gaps: Tier 2.&lt;/strong&gt; The project has accumulated nine issues of domain methodology and converted exactly one piece of it into a proper skill. Everything else is prose, implicit code patterns, or session instructions that get re-derived each time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "org-wide" means for a solo project
&lt;/h2&gt;

&lt;p&gt;Before building the Tier 1 skill, I had to answer a question that only appears when you're a team of one: what does "org-wide" mean when there's no org?&lt;/p&gt;

&lt;p&gt;The answer: in a solo project, the "org" is the author plus every agent instance that works on the project. And agents are stateless between sessions. An agent in Issue #14 has no memory of the formatting decisions made in Issue #8.&lt;/p&gt;

&lt;p&gt;Without a Tier 1 skill, every session re-invents the output format. Some issues use &lt;code&gt;🔄 In progress&lt;/code&gt; as a status indicator. Some don't. Some code blocks have language tags. Some don't. The findings archive becomes inconsistent over time — not because anyone made a bad decision, but because no decision was ever locked in.&lt;/p&gt;

&lt;p&gt;The coordination problem a Tier 1 skill solves is not between engineers on a team. It is between agent instances across sessions. The "org" is temporal, not spatial.&lt;/p&gt;

&lt;p&gt;The Tier 1 formatting standard created in this session covers exactly this: status indicator conventions, code block formatting, commit message types, and the structure variants for the findings file (the standard five-section format vs the sequence variant for multi-fix sessions like Issue #8). The output before this skill: ad-hoc section headers invented mid-session. The output after: structurally compatible findings entries regardless of which session produced them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Gherkin skill is Tier 2 — and what "competitive moat" actually means
&lt;/h2&gt;

&lt;p&gt;The Gherkin quality evaluator moved from &lt;code&gt;docs/skills/&lt;/code&gt; to &lt;code&gt;docs/skills/tier2/&lt;/code&gt; this session. The relocation forced a precise answer to why it belongs there and not at Tier 1 or Tier 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not Tier 1&lt;/strong&gt; because it encodes project-specific conventions. The field name substitutions (&lt;code&gt;db_status&lt;/code&gt; → &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;order_created_at&lt;/code&gt; → &lt;code&gt;placed_at&lt;/code&gt;) are specific to this codebase's debt history. The feature file ownership rules are specific to this project's service architecture. A Tier 1 version would need to strip these specifics out — and at that point it would encode nothing that took nine issues to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not Tier 3&lt;/strong&gt; because it is not personal. The five-question diagnostic, the debt taxonomy, and the output contract are designed to produce compatible output regardless of which agent runs the skill. That compatibility is the whole point. If it were Tier 3, it would be optional — something one engineer uses because they like it, not something enforced on every agent that touches a feature file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The moat:&lt;/strong&gt; A generic Gherkin skill tells an agent "write clear Given/When/Then steps." Every Cucumber tutorial says the same thing. What the Tier 2 skill encodes that no tutorial can:&lt;/p&gt;

&lt;p&gt;The specific failure modes of this codebase. The five patterns in the Q2 check (relative quantities, count ambiguity, undefined time anchors, mechanism claims, internal field names) were not derived from a best-practices checklist. They were derived from the actual bugs found in Issues #2 through #8. They are calibrated to this project's failure history.&lt;/p&gt;

&lt;p&gt;The caller's perspective principle applied to this domain. Q4's "remove the implementation from the step and read only what the caller observes" is not standard Gherkin teaching. It requires understanding the difference between an HTTP API surface and its implementation — a distinction specific to contract-first API development.&lt;/p&gt;

&lt;p&gt;The output contract for downstream step definition authors. &lt;code&gt;"exactly N"&lt;/code&gt; not &lt;code&gt;"N times"&lt;/code&gt;. &lt;code&gt;"the payment gateway"&lt;/code&gt; not &lt;code&gt;"the external service"&lt;/code&gt;. These requirements come from how the step definitions in this project are actually implemented, not from abstract best practices.&lt;/p&gt;

&lt;p&gt;This expertise is not transferable to a generic context. That is what makes it a moat.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tier 3 skill and the socialization decision
&lt;/h2&gt;

&lt;p&gt;The "Why this matters" paragraph appears in every findings file, every session. Reading across nine issues, it has a consistent structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opens with the specific technical finding stated as a practitioner observation&lt;/li&gt;
&lt;li&gt;Connects to a broader engineering principle in one sentence&lt;/li&gt;
&lt;li&gt;Names the concrete failure mode that would occur without this finding&lt;/li&gt;
&lt;li&gt;Closes with the implication for the reader's own practice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From Issue #5: &lt;em&gt;"The bad spec was written from the implementation's perspective. The good spec was written from the caller's perspective — it describes what the caller can rely on."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From Issue #9: &lt;em&gt;"The prompt produces output that passes today's tests; the skill produces output that a different agent can implement tomorrow without making any decisions you didn't make."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both follow the same shape: observation → principle → failure mode → implication. This pattern was never written down. It exists as author instinct and as examples in the existing findings files.&lt;/p&gt;

&lt;p&gt;The socialization decision: &lt;strong&gt;promote to Tier 2.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pattern is not personal style — it is an output contract for the most reused artifact in this project. The findings files are the raw material for the newsletter. Structural consistency across them is a project requirement. An agent in Issue #14 that produces a "Why this matters" paragraph opening with the principle rather than the finding is technically correct per the CLAUDE.md description ("one paragraph, senior engineer audience") but structurally incompatible with the existing archive.&lt;/p&gt;

&lt;p&gt;What needs to change before promotion: the skill currently documents the pattern through examples. A Tier 2 skill needs the four components named and ordered explicitly, a routing signal precise enough to fire on findings-writing and not on general prose, and quality criteria the agent can self-check before submitting.&lt;/p&gt;




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

&lt;p&gt;Three patterns from this project's nine issues that belong in a skill and don't exist as one:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The step definition writing pattern.&lt;/strong&gt; Across all four step definition files, there is a consistent architectural pattern: fixtures injected from &lt;code&gt;conftest.py&lt;/code&gt;, mock server state asserted via the call log rather than the response body, async side effects using &lt;code&gt;time.sleep(0.3)&lt;/code&gt; before assertion. An agent adding a new step definition without knowing this will set up mock state inline, assert via response fields, and skip the sleep. The tests pass individually. They break in sequence. This pattern has been the foundation of every test session since Issue #2. It has never been written down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The spec-fix decision tree.&lt;/strong&gt; Issues #7 and #8 both required deciding: given a spec debt item, does fixing it require touching the feature file only, the step definition only, the implementation, or some combination? The answer was re-derived each time. The pattern: UNDERSPECIFIED → feature file only. LEAKY ABSTRACTION in the feature file → feature file + step definition. LEAKY ABSTRACTION in the step definition only → step definition only. IMPLICIT FLOW → remove if unspecced, new feature file if in scope. This decision tree appeared in Issue #8's seven fixes and was implicit in Issue #7. It has never been written down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The article-worthiness filter.&lt;/strong&gt; Not every event in a session becomes a finding. The timeout ambiguity in Issue #8 became a full five-section entry. The UUID format error in the WireMock stub became a single line. The filter: an article-worthy finding must have a root cause the reader would not have anticipated, a failure mode that would have occurred in a real system, and a fix encoding a transferable principle. A technical note is a fix with no generalizable lesson. This filter runs every session as editorial instinct. It has never been written down.&lt;/p&gt;

&lt;p&gt;Why haven't they been documented? Because writing them down felt like overhead at the moment they were useful. The step definition pattern was obvious when it was established. The decision tree was derived from first principles when it was needed. The filter runs automatically. All three share the same problem: they are invisible when they work and only visible as "what went wrong" when they don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  What would be wrong about promoting everything to Tier 1
&lt;/h2&gt;

&lt;p&gt;The Gherkin quality evaluator would be the instructive example.&lt;/p&gt;

&lt;p&gt;A Tier 1 version: "When writing a Gherkin scenario, apply the five-question diagnostic." This instruction fires in every context — including sessions focused on Pact contracts or CI/CD pipelines where no Gherkin is being written. The routing signal becomes noise. Agents start treating it as a background constraint to satisfy minimally rather than a deliberate skill to route to deliberately.&lt;/p&gt;

&lt;p&gt;Deeper problem: Tier 1 skills are enforced uniformly. Promoting the Gherkin skill to Tier 1 implies that every agent in every session must run the five-question diagnostic. In an Issue #6-style CI/CD session, that is overhead, not value.&lt;/p&gt;

&lt;p&gt;But the deepest problem is what generalization does to the skill. A Tier 1 version must strip out the project-specific conventions to apply universally. It becomes "write clear Given/When/Then steps." Every Cucumber tutorial says this. The nine issues of calibrated expertise that make the Tier 2 skill valuable get averaged out of existence.&lt;/p&gt;

&lt;p&gt;The Gherkin skill's value comes from being specific. Promoting it to Tier 1 would generalize it until it no longer encodes the expertise that makes it useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The closing that came from the session itself
&lt;/h2&gt;

&lt;p&gt;The 3-tier model is a container for a harder decision: which of your working patterns are personal and which are organisational standards?&lt;/p&gt;

&lt;p&gt;In this project, after ten issues, the most valuable institutional knowledge is not in the code. It is in the step definition architecture that has never been written down, the article-worthiness filter that runs as editorial instinct, and the spec-fix decision tree that was re-derived in Issue #8 and will be re-derived again in Issue #11.&lt;/p&gt;

&lt;p&gt;These are Tier 2 skills that exist at Tier 3. Which means they exist only as long as the sessions that carried them.&lt;/p&gt;

&lt;p&gt;The organisational liability is not that these patterns get lost. It is that when they get lost, nobody knows they were there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: Designing for Non-Human Callers — what changes when agents call your skills hundreds of times per session, and how human-friendly skills degrade under agent load.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Agent-First Skills Architecture&lt;/a&gt; · &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-10-three-tier-architecture.md" rel="noopener noreferrer"&gt;Session findings — Issue #10&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Prompts Are Disposable. Skills Are Infrastructure.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Mon, 29 Jun 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/diyaburman/prompts-are-disposable-skills-are-infrastructure-575p</link>
      <guid>https://dev.to/diyaburman/prompts-are-disposable-skills-are-infrastructure-575p</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Layer 1 is complete. Eight issues, a working order management API, Pact contracts, a CI/CD pipeline, and a spec audit framework. The specification layer is done.&lt;/p&gt;

&lt;p&gt;Layer 2 starts here. And it begins with a question that sounds simple until you think about it: why do you keep rewriting the same prompts?&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with copying prompts
&lt;/h2&gt;

&lt;p&gt;If you've been using AI seriously for more than a few weeks, you have a collection of prompts that work. You've refined them. You copy them between sessions. You paste them into Claude Code at the start of a task and the agent does the right thing.&lt;/p&gt;

&lt;p&gt;That feels like a system. It isn't.&lt;/p&gt;

&lt;p&gt;Here's what copying a prompt actually does: it copies the words. It doesn't copy the contract. The agent reads the words, interprets them in the context of this session, and makes a series of decisions that aren't in the prompt. Different sessions, different context, different decisions — even with the same words. You won't notice until two agents produce incompatible outputs from the same prompt and you have to figure out which one is right.&lt;/p&gt;

&lt;p&gt;A skill is different. A skill specifies what to produce, not just what to consider. It has a version, an output contract, and a routing signal. It gets better over time and the improvements persist. It's the difference between a note you wrote to yourself and infrastructure your whole team — human and agent — can depend on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding the right candidate
&lt;/h2&gt;

&lt;p&gt;I reviewed the entire order-api project to find the best prompt-to-skill conversion candidate. Three instructions surfaced:&lt;/p&gt;

&lt;p&gt;The test-run verification sequence (&lt;code&gt;pytest tests/steps/ -v &amp;amp;&amp;amp; pytest tests/pact/ -v &amp;amp;&amp;amp; python scripts/can_i_deploy.py&lt;/code&gt;) appears in every session. Rejected — it's a procedure, not a judgment call. Any agent can run three commands.&lt;/p&gt;

&lt;p&gt;The findings file protocol appears in CLAUDE.md and has been followed since Issue #3. Rejected — it describes a format and cadence, not a methodology.&lt;/p&gt;

&lt;p&gt;The Gherkin scenario quality evaluation — the methodology for deciding whether a scenario is well-formed before accepting or writing it — appeared across Issues #5, #7, and #8. Every time, the agent re-derived the same judgment framework from scratch. This is the winner.&lt;/p&gt;

&lt;p&gt;Why: it encodes judgment, not procedure. Whether a step is UNDERSPECIFIED or LEAKY ABSTRACTION is a reasoning call. Its output drives everything downstream — every implementation session depends on the scenarios being well-formed. A bad scenario written in a planning session becomes broken step definitions two sessions later.&lt;/p&gt;

&lt;p&gt;And here's the uncomfortable detail: the timeout ambiguity that was fixed in Issue #8 — &lt;code&gt;And the response is returned within 12 seconds&lt;/code&gt; — was introduced in Issue #2. Three sessions inherited it silently before it was caught. A quality evaluation skill running in Issue #2 would have caught it before it was ever committed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The prompt version — and what it gets wrong
&lt;/h2&gt;

&lt;p&gt;Here's the current prompt as it would be pasted into a session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before writing or accepting a Gherkin scenario, check that it is well-formed. A well-formed scenario describes behavior from the caller's perspective, not from the implementation. Each step should be specific enough that only one implementation can satisfy it. Check for: vague quantities, counts that could be read as total or additional, time bounds without a start anchor, mechanism claims without the mechanism, and internal field names leaking into the spec. If the scenario has these problems, rewrite it before proceeding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Four weaknesses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No output format.&lt;/strong&gt; The prompt implies the agent will either rewrite or proceed — but doesn't say what to return. A list of problems? An annotated version? The corrected Gherkin? Two agents will produce two different output shapes. A downstream agent consuming either has to parse whichever shape it receives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No taxonomy.&lt;/strong&gt; "Internal field names" means something specific — but the prompt doesn't define what internal means vs external. Two agents draw that line differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No handling for partial problems.&lt;/strong&gt; "Rewrite before proceeding" — does that mean rewrite every step, or only the problematic ones?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context-dependence.&lt;/strong&gt; The prompt was written for reviewing an existing scenario. Used in a planning context ("write a new scenario for DELETE /orders/{id}"), an agent either ignores the check or applies it mid-draft. The two strategies produce different levels of revision.&lt;/p&gt;

&lt;p&gt;The fundamental problem: the prompt describes what to look for but not what to produce. The agent invents the output format. When the output format is invented, it is never the same twice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The demonstration
&lt;/h2&gt;

&lt;p&gt;Same input. Same endpoint. Prompt version first, skill version second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The task:&lt;/strong&gt; Write a Gherkin scenario for &lt;code&gt;DELETE /orders/{order_id}&lt;/code&gt; — cancel a confirmed order. The order can only be cancelled if payment has not been captured yet.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Task A — prompt version output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Cancel a confirmed order before payment capture
  &lt;span class="nf"&gt;Given &lt;/span&gt;a confirmed order exists with order ID &lt;span class="s"&gt;"order-123"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;payment has not been captured for the order
  &lt;span class="nf"&gt;When &lt;/span&gt;the user sends DELETE /orders/order-123
  &lt;span class="nf"&gt;Then &lt;/span&gt;the order is cancelled
  &lt;span class="nf"&gt;And &lt;/span&gt;the response status code is 200
  &lt;span class="nf"&gt;And &lt;/span&gt;the response includes a confirmation message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six implicit decisions the agent made that weren't in the prompt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;"confirmed order exists"&lt;/em&gt; — method of setup not specified. Seed directly or call POST /orders? Left open.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"payment has not been captured"&lt;/em&gt; — mechanism not specified. Mock server state? Flag on the order?&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"the order is cancelled"&lt;/em&gt; — mechanism language. What field changes? What value? Not specified.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"confirmation message"&lt;/em&gt; — any text in any field satisfies this. Undefined.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;200 vs 204&lt;/em&gt; — the agent chose 200 over 204 (no body). Undocumented judgment call.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;No failure scenario&lt;/em&gt; — the prompt mentioned "can only be cancelled if payment has not been captured yet" but the agent wrote only the success case.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Total implicit decisions: 6. All silent.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Task B — skill version output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Confirmed order is cancelled when payment has not yet been captured
  &lt;span class="nf"&gt;Given &lt;/span&gt;an order was created via POST /orders with order ID &lt;span class="s"&gt;"order-del-001"&lt;/span&gt; and status &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway has not captured payment for order &lt;span class="s"&gt;"order-del-001"&lt;/span&gt;
  &lt;span class="nf"&gt;When &lt;/span&gt;the client sends DELETE /orders/order-del-001
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response HTTP status is 200
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains a &lt;span class="s"&gt;"status"&lt;/span&gt; field with value &lt;span class="s"&gt;"CANCELLED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains an &lt;span class="s"&gt;"order_id"&lt;/span&gt; field with value &lt;span class="s"&gt;"order-del-001"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway receives no capture or charge requests for order &lt;span class="s"&gt;"order-del-001"&lt;/span&gt;

&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Cancellation is rejected when payment has already been captured
  &lt;span class="nf"&gt;Given &lt;/span&gt;an order was created via POST /orders with order ID &lt;span class="s"&gt;"order-del-002"&lt;/span&gt; and status &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway has already captured payment for order &lt;span class="s"&gt;"order-del-002"&lt;/span&gt;
  &lt;span class="nf"&gt;When &lt;/span&gt;the client sends DELETE /orders/order-del-002
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response HTTP status is 422
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains an &lt;span class="s"&gt;"error"&lt;/span&gt; field with value &lt;span class="s"&gt;"PAYMENT_ALREADY_CAPTURED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the order status remains &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two implicit decisions — both surfaced explicitly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;422 vs 409&lt;/em&gt; — the skill's output contract requires documenting assumptions, so this was flagged as a comment rather than silently embedded.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"status" vs "cancellation_status"&lt;/em&gt; — the skill's LEAKY ABSTRACTION check prevented using an implementation-facing field name.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Total implicit decisions: 2. Both visible.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The diff
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What changed&lt;/th&gt;
&lt;th&gt;Classification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"confirmed order exists" → "created via POST /orders with status CONFIRMED"&lt;/td&gt;
&lt;td&gt;SKILL CONSTRAINT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"payment has not been captured" → "the payment gateway has not captured payment for order-del-001"&lt;/td&gt;
&lt;td&gt;SKILL CONSTRAINT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"the order is cancelled" → HTTP status + "status" field with value "CANCELLED"&lt;/td&gt;
&lt;td&gt;QUALITY DELTA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"confirmation message" → specific field name and value&lt;/td&gt;
&lt;td&gt;QUALITY DELTA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;(absent)&lt;/em&gt; → "payment gateway receives no capture requests"&lt;/td&gt;
&lt;td&gt;SKILL CONSTRAINT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;em&gt;(absent)&lt;/em&gt; → full second scenario for failure case&lt;/td&gt;
&lt;td&gt;QUALITY DELTA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six meaningful differences. Three skill constraints, three quality deltas, six prompt ambiguities eliminated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three properties skills have that prompts don't
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Version control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A prompt has no version. When you improve it, you copy the new text into the next session. The old version exists in your clipboard history or a chat transcript from three weeks ago. You cannot diff it. You cannot pin a session to it. You cannot see what changed between the prompt that worked and the prompt that produced the wrong output.&lt;/p&gt;

&lt;p&gt;The Gherkin quality skill lives in &lt;code&gt;docs/skills/gherkin-scenario-quality.md&lt;/code&gt;. When Issue #8 added the IMPLICIT FLOW debt class, the skill gets a one-line update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+| IMPLICIT FLOW | A step that implies a follow-up flow that is not specced anywhere |
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every session after that commit uses the updated skill. Every session before it used the previous version. &lt;code&gt;git blame&lt;/code&gt; tells you exactly when IMPLICIT FLOW was added and which issue prompted it. With a prompt, "skill v1.1" means nothing. There is only "the prompt I'm using today."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Output contract&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The skill specifies exactly what it must return:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One or more complete Gherkin scenarios in Given/When/Then format&lt;/li&gt;
&lt;li&gt;All Then clauses must assert a field name AND a value — not just presence&lt;/li&gt;
&lt;li&gt;All counts must use "exactly N" or "no more than N total" — never "N times"&lt;/li&gt;
&lt;li&gt;All time bounds must include a start anchor&lt;/li&gt;
&lt;li&gt;Each external service in a Given clause must be named explicitly&lt;/li&gt;
&lt;li&gt;Assumptions not in the input must appear as &lt;code&gt;# Assumption:&lt;/code&gt; comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The downstream dependency is the step definition author. When &lt;code&gt;tests/steps/test_order_creation.py&lt;/code&gt; implements &lt;code&gt;And the payment gateway received exactly one charge request&lt;/code&gt; — "exactly one", "charge request", "payment gateway" are all actionable. When it implements "And the response includes a confirmation message" — the author must invent an assertion. That invention is where test coverage becomes unreliable.&lt;/p&gt;

&lt;p&gt;The output contract is the interface between the agent that writes scenarios and the agent that implements from them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Routing signal description&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The skill's description line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Evaluate and produce well-formed Gherkin scenarios for the order-api project using the five-question debt diagnostic and output contract.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It names the artifact type, the project, the method, and the output. An agent knows exactly when to use this skill and what it will receive.&lt;/p&gt;

&lt;p&gt;A bad description for the same skill:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Help with writing tests and checking scenarios for the project.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Tests" matches pytest, Pact contracts, unit tests, and Gherkin. "The project" matches any repo. No methodology named means two agents doing "help with writing tests" produce incompatible outputs — which is exactly the problem the skill exists to solve.&lt;/p&gt;




&lt;h2&gt;
  
  
  The answer
&lt;/h2&gt;

&lt;p&gt;If both the prompt and the skill produce output that works, the difference is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt produces output that passes today's tests. The skill produces output that a different agent can implement tomorrow without making any decisions you didn't make.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's why copying prompts isn't enough. The words travel. The contract doesn't.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: The 3-Tier Skill Architecture in Practice — mapping your skills to the right tier and why Tier 2 is where individual expertise becomes organizational leverage.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Agent-First Skills Architecture&lt;/a&gt; · &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/docs/skills/gherkin-scenario-quality.md" rel="noopener noreferrer"&gt;Gherkin quality skill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-09-skills-infrastructure.md" rel="noopener noreferrer"&gt;Session findings — Issue #9&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Spec Debt Doesn't Disappear When You Fix It. It Migrates.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:30:00 +0000</pubDate>
      <link>https://dev.to/diyaburman/spec-debt-doesnt-disappear-when-you-fix-it-it-migrates-d25</link>
      <guid>https://dev.to/diyaburman/spec-debt-doesnt-disappear-when-you-fix-it-it-migrates-d25</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Issue #7 ended with seven spec debt items documented in a project that had been built carefully for seven issues. Every item was passing its tests. None of them announced themselves. They were found by asking a different question: not "does this pass?" but "what would a second agent build from this step?"&lt;/p&gt;

&lt;p&gt;Issue #8 fixes all seven — and builds the tool that found them into something reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The seven fixes
&lt;/h2&gt;

&lt;p&gt;Working through each item one at a time, running the test suite after every individual fix. Not batching them. The discipline matters — if a fix breaks something, you want to know which fix broke it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 1 — Timeout measurement ambiguity&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the response is returned within 12 seconds

&lt;span class="c"&gt;# After&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the response is returned within 12 seconds of the order being submitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Of the order being submitted" anchors the clock to client-side HTTP request dispatch — the same moment &lt;code&gt;time.time()&lt;/code&gt; is captured in the step definition. Without this anchor, a second implementation could measure from server receipt, from the last retry attempt, or from when the response body is fully read. All three produce different numbers under load.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 2 — "Retried" vs "total attempts"&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway is not retried more than 2 times

&lt;span class="c"&gt;# After&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the payment gateway receives no more than 2 charge requests total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Retried 2 times" has two valid English readings: 2 retries meaning 3 total requests, or retried up to 2 times meaning 2 total. "No more than 2 charge requests total" counts requests, not retries, and the word "total" makes clear the initial attempt is included. This also changed the assertion in the step definition — from trusting the response body's &lt;code&gt;retry_count&lt;/code&gt; field to checking the actual call count at the mock server. Stronger assertion, same outcome.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 3 — "Released" without mechanism&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the inventory reservation is released

&lt;span class="c"&gt;# After&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the inventory service receives a reservation release request for SHOE-RED-42 and BELT-BRN-M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Released" says what happened but not how, and not for which items. The rewrite names the items and specifies that a request is sent to the inventory service. This fix also revealed a gap: the current implementation signals release via a response body field (&lt;code&gt;inventory_released: true&lt;/code&gt;) rather than a separate API call to the inventory service. The spec now describes the intended behaviour. The implementation doesn't fully match it yet. That's a future issue — but the gap is now visible rather than hidden.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 4 — "Explicit user action" — removed entirely&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;no order is confirmed without explicit user action

&lt;span class="c"&gt;# After&lt;/span&gt;
&lt;span class="err"&gt;(step&lt;/span&gt; &lt;span class="err"&gt;removed)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step implies a follow-up confirmation flow (&lt;code&gt;POST /orders/{id}/confirm&lt;/code&gt; or equivalent) that does not exist anywhere in the codebase. It passes trivially because no order is confirmed in the partial availability scenario — not because the confirmation flow was implemented. A spec step that passes for the wrong reason is not a safety net. It is a false guarantee. If the confirmation flow is built in a future issue, a new scenario should specify it precisely. Leaving this step in place would invite an agent to invent an unspecced endpoint.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 5 — Presence without value assertions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;order_status_bad.feature&lt;/code&gt; timestamp step was asserting only that a field exists and is a non-empty string. Tightened to assert the field name, the value, and the type explicitly. Kept conservative — &lt;code&gt;order_status_bad.feature&lt;/code&gt; is a pedagogical artifact and shouldn't be converted into a good spec, which would defeat its purpose in the newsletter.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 6 — "An order exists" without specifying how&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="nf"&gt;Given &lt;/span&gt;an order was successfully placed and confirmed with order ID &lt;span class="s"&gt;"aaa00000-..."&lt;/span&gt;

&lt;span class="c"&gt;# After&lt;/span&gt;
&lt;span class="nf"&gt;Given &lt;/span&gt;an order was created via POST /orders and confirmed with order ID &lt;span class="s"&gt;"aaa00000-..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Successfully placed and confirmed" describes the outcome but not the mechanism. "Created via POST /orders" makes explicit that a real creation flow is expected. The step definition currently seeds the order directly into the in-memory store — a shortcut. The rewrite creates a documented gap between spec intent and step implementation. Visible gap, not hidden one.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Fix 7 — "Correct" without definition&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the notification contains the correct order id and total

&lt;span class="c"&gt;# After&lt;/span&gt;
&lt;span class="nf"&gt;And &lt;/span&gt;the notification request body contains order_id &lt;span class="s"&gt;"order-abc-123"&lt;/span&gt; and total 134.97
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Correct" is relative to context that may not be available to the reader. The rewrite hardcodes the expected values established in the When clause. Two agents reading the original step would both implement something that checks the notification body — but one might compare against the When-clause values, another might check against a computed total, a third might only verify field presence. The rewrite removes all three interpretations.&lt;/p&gt;

&lt;p&gt;This fix also caught something the stub had been hiding: the notification mock was returning &lt;code&gt;"mock-notif-001"&lt;/code&gt; as a notification id. Not a UUID. The format assertion caught it immediately. This is exactly the value of adding concrete assertions — it surfaces stub data that was never valid but was never checked.&lt;/p&gt;




&lt;h2&gt;
  
  
  The audit framework
&lt;/h2&gt;

&lt;p&gt;After fixing all seven items, I built the diagnostic tool into a standalone document: &lt;code&gt;docs/spec-audit-framework.md&lt;/code&gt;. The full document is in the repo. Here's the core of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five questions — ask them for every scenario in every feature file:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q1: Who owns this scenario?&lt;/strong&gt;&lt;br&gt;
Can you name the team, service, or domain this scenario belongs to? If the answer includes "and also", the scenario is in the wrong file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What decisions does this scenario leave open?&lt;/strong&gt;&lt;br&gt;
For every Given, When, and Then clause: could two agents build different implementations that both pass? If yes, the step is underspecified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Are all terms defined within the file?&lt;/strong&gt;&lt;br&gt;
Every noun that is not a standard HTTP concept or a primitive type should be defined in the scenario or a Background clause. If understanding a term requires reading another file or asking a colleague, it is spec debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: Does this scenario describe behaviour or implementation?&lt;/strong&gt;&lt;br&gt;
Steps should describe what the system does from the caller's perspective. Any step that references internal concepts — database field names, function names, internal status codes — is leaking implementation into the spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What does this scenario NOT say that it should?&lt;/strong&gt;&lt;br&gt;
List the edge cases, error states, and boundary conditions the scenario implies but does not specify. Each one is a silent assumption waiting to become a production incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Six debt classes:&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;Class&lt;/th&gt;
&lt;th&gt;What it looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UNDERSPECIFIED&lt;/td&gt;
&lt;td&gt;Step present but leaves a decision open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIXED CONCERN&lt;/td&gt;
&lt;td&gt;Scenario covers more than one service domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UNDEFINED TERM&lt;/td&gt;
&lt;td&gt;A noun used without being defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMBIGUOUS COUNT&lt;/td&gt;
&lt;td&gt;A quantity with two valid interpretations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMPLICIT FLOW&lt;/td&gt;
&lt;td&gt;Implies a follow-up flow that isn't specced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEAKY ABSTRACTION&lt;/td&gt;
&lt;td&gt;References implementation details&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What the framework found that the manual audit missed
&lt;/h2&gt;

&lt;p&gt;Applying the five questions to all four fixed feature files surfaced one item the Issue #7 manual audit didn't catch.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;order_status_good.feature&lt;/code&gt;, the Given clause now reads "created via POST /orders" — the fixed version from this session. Q4 flagged it for a different reason than the original audit: the step definition still seeds the order directly into the in-memory store. The spec text is precise. The implementation of the spec takes a shortcut.&lt;/p&gt;

&lt;p&gt;The manual audit looked at feature file text. The framework applies Q4 to step definitions as well — and a step definition that silently does something different from what the spec says is spec debt, even if the test passes.&lt;/p&gt;

&lt;p&gt;This distinction matters: &lt;strong&gt;spec debt can migrate from the feature file into the step definition.&lt;/strong&gt; You fix the scenario, tighten the language, run the tests — green. But the step definition now implements a shortcut that contradicts the precise step text. The debt moved, it didn't disappear.&lt;/p&gt;




&lt;h2&gt;
  
  
  The scorecard — after all fixes
&lt;/h2&gt;

&lt;p&gt;Applied the framework to all four non-pedagogical feature files:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;order_creation.feature&lt;/code&gt; — 5 scenarios, 1 debt item remaining (LEAKY ABSTRACTION at step definition level — inventory release mechanism gap from Fix 3)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;order_status_good.feature&lt;/code&gt; — 2 scenarios, 1 debt item remaining (LEAKY ABSTRACTION — step definition seeds order directly rather than via POST /orders)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;notification_service.feature&lt;/code&gt; — 2 scenarios, 0 debt items&lt;/p&gt;

&lt;p&gt;&lt;code&gt;order_status_bad.feature&lt;/code&gt; — kept as pedagogical artifact, not audited for debt&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debt density after fixes: 0.22 items per scenario.&lt;/strong&gt; Both remaining items are LEAKY ABSTRACTION at the step definition level. Zero AMBIGUOUS COUNT or IMPLICIT FLOW items remain — the two highest-risk classes.&lt;/p&gt;




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

&lt;p&gt;After fixing seven spec debt items and applying a structured audit framework to a project that has been built carefully for eight issues, two debt items remain. Both were introduced by the same sessions that fixed other debt — a precise spec step was written, and the implementation of that step took a shortcut.&lt;/p&gt;

&lt;p&gt;Spec debt is not eliminated by fixing debt. It migrates.&lt;/p&gt;

&lt;p&gt;The practical conclusion: treat step definitions as part of the spec surface, not just as test harness code. A step definition that silently does something different from what the spec says is spec debt, even if the test passes. The audit framework catches both — but only if you apply Q4 to the step definitions as well as the feature text.&lt;/p&gt;

&lt;p&gt;The other finding worth naming: &lt;code&gt;notification_service.feature&lt;/code&gt; scored zero debt items. It was written after eight issues of accumulating lessons about what the previous files got wrong. The absence of debt is not accidental — it's the result of knowing what bad specs look like before writing the next one.&lt;/p&gt;

&lt;p&gt;The best time to write a spec is after you've written a few bad ones. Auditing retroactively and fixing forward is the realistic path. Not "write it right the first time."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: Prompts Are Disposable. Skills Are Infrastructure — the conceptual shift from session-level prompts to versioned, reusable skill definitions. Layer 2 begins.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/docs/gherkin/" rel="noopener noreferrer"&gt;Cucumber + Gherkin documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/docs/spec-audit-framework.md" rel="noopener noreferrer"&gt;Spec audit framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-08-spec-audit.md" rel="noopener noreferrer"&gt;Session findings — Issue #8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your Spec Files Are Lying to You. Mine Were Too.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Mon, 15 Jun 2026 17:30:55 +0000</pubDate>
      <link>https://dev.to/diyaburman/your-spec-files-are-lying-to-you-mine-were-too-1nie</link>
      <guid>https://dev.to/diyaburman/your-spec-files-are-lying-to-you-mine-were-too-1nie</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Every issue so far has worked with one service and one spec file. Issue #7 changes that. A second service enters the picture — a notification service that the order service calls after a confirmed payment — and with it comes the question that every growing system eventually forces: where do spec file boundaries go?&lt;/p&gt;

&lt;p&gt;The answer turns out to matter more than it looks. And the audit at the end of this issue found seven spec debt items in files we've been running since Issue #2. All passing. All carrying risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  The notification service — and a design decision that has spec implications
&lt;/h2&gt;

&lt;p&gt;The new service is minimal: &lt;code&gt;POST /notifications/order-confirmed&lt;/code&gt; accepts an order id, user id, and total, and returns a notification id and a &lt;code&gt;QUEUED&lt;/code&gt; status. Simple enough. The interesting part is how the order service calls it.&lt;/p&gt;

&lt;p&gt;The call is fire-and-forget.&lt;/p&gt;

&lt;p&gt;When an order is confirmed, the order service starts a daemon thread, fires the notification request, and returns the &lt;code&gt;CONFIRMED&lt;/code&gt; response immediately — without waiting for the notification to succeed. If the notification service is down, slow, or returning errors, the order is still confirmed. The customer gets their confirmation. The notification may or may not arrive.&lt;/p&gt;

&lt;p&gt;This is a deliberate design decision. The order service owns the transaction. The notification service owns delivery. Coupling the order confirmation response to notification delivery would mean a flaky notification service could block order creation — which is a much worse failure mode than a missed notification.&lt;/p&gt;

&lt;p&gt;But the decision has a direct spec implication: any scenario that asserts &lt;code&gt;Then the order status is "CONFIRMED"&lt;/code&gt; must remain true regardless of what the notification service does. The spec cannot simultaneously require &lt;code&gt;CONFIRMED&lt;/code&gt; and make &lt;code&gt;CONFIRMED&lt;/code&gt; depend on notification success. That would be a hidden coupling — the spec would look independent but the implementation would not be.&lt;/p&gt;

&lt;p&gt;This is the kind of architectural decision that should be in the spec before it's in the code. Once it's in the code it becomes folklore.&lt;/p&gt;




&lt;h2&gt;
  
  
  The wrong way first: one big spec file
&lt;/h2&gt;

&lt;p&gt;Before doing it right I did it wrong deliberately. I added two notification scenarios to the bottom of &lt;code&gt;order_creation.feature&lt;/code&gt; — the existing file that's been covering order creation since Issue #2.&lt;/p&gt;

&lt;p&gt;All 7 tests passed. Green across the board. &lt;code&gt;pytest&lt;/code&gt; has no opinion about spec architecture.&lt;/p&gt;

&lt;p&gt;The problems are structural, not functional:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixed ownership.&lt;/strong&gt; &lt;code&gt;order_creation.feature&lt;/code&gt; line 1 says &lt;code&gt;Feature: Order Creation&lt;/code&gt;. By line 48 it's testing notification delivery. If the notification team changes their contract — say, adding a &lt;code&gt;channel&lt;/code&gt; field to the request — they have to open &lt;code&gt;order_creation.feature&lt;/code&gt; to update it. That file is not theirs. The filename, the feature declaration, and the existing scenarios all signal "this belongs to the order team." The notification scenarios are squatters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The growing file problem.&lt;/strong&gt; At 5 scenarios the file is readable. At 7 it starts to smell. Extrapolate to a real system: 10 downstream services, 5–10 scenarios each, all appended to the originating feature file because each was "triggered by" an order creation event. The file becomes a catch-all that nobody owns and everybody edits. Ownership dissolves into "whoever last touched it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent routing problem.&lt;/strong&gt; When an agent is handed &lt;code&gt;order_creation.feature&lt;/code&gt; to build against, it must now implement both order logic and notification logic. It cannot know from the file whether the notification call belongs in &lt;code&gt;POST /orders&lt;/code&gt; or in a separate endpoint. It will make a decision — probably the wrong one — and that decision will be baked into the implementation before anyone notices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spec debt seed.&lt;/strong&gt; The scenario "Order confirmation succeeds even if notification fails" uses the step &lt;code&gt;"the notification service is unavailable"&lt;/code&gt; without defining what unavailable means. TCP connection refused? 503? A 30-second hang? Each is a different failure mode with different implications for retry logic. An agent will pick one interpretation silently. Two agents will pick different ones. Both implementations will pass the spec. This is spec debt: it forms quietly, passes its tests, and surfaces as a production incident months later.&lt;/p&gt;




&lt;h2&gt;
  
  
  The right way: bounded spec files
&lt;/h2&gt;

&lt;p&gt;After documenting what was wrong, I moved the notification scenarios into their own file: &lt;code&gt;tests/features/notification_service.feature&lt;/code&gt;. Rewrote both scenarios to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precisely define "unavailable" as &lt;code&gt;503 Service Unavailable&lt;/code&gt; — not a timeout, not a connection refused, not an ambiguous network failure&lt;/li&gt;
&lt;li&gt;Describe the notification contract from the notification service's perspective&lt;/li&gt;
&lt;li&gt;Make the file self-contained — a notification service team reading it wouldn't need to open &lt;code&gt;order_creation.feature&lt;/code&gt; to understand it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;order_creation.feature&lt;/code&gt;: 5 scenarios, all about order creation. No references to notifications.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;notification_service.feature&lt;/code&gt;: 2 scenarios, all about notification delivery behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The file boundary is now a contract boundary. They can be versioned, owned, and handed to different agents independently.&lt;/p&gt;

&lt;p&gt;Bounded spec files are not a tidiness preference. They are a precision tool for multi-agent systems. When a spec file is bounded to one service, an agent can be assigned exactly that file and nothing else. It builds one surface, tests one contract, returns. When the spec bleeds across services, the agent must make decisions about service ownership that were never written down. Those decisions accumulate as hidden assumptions in the implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The spec debt audit
&lt;/h2&gt;

&lt;p&gt;With the bounded file structure in place, I audited all four feature files in the project for spec debt — places where the spec passes its tests but leaves decisions that should have been made explicitly.&lt;/p&gt;

&lt;p&gt;Seven items. All passing. All carrying risk.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;1. Ambiguous timeout measurement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;order_creation.feature&lt;/code&gt; — Scenario: payment gateway times out&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: &lt;code&gt;And the response is returned within 12 seconds&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From when? The client sends the request? The server receives it? The last retry fires? Two agents will instrument this differently and both will pass. "Within 12 seconds of the order being submitted" — defining "submitted" as the moment the HTTP request body is sent — removes the ambiguity.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2. "Retried" vs "total attempts"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;order_creation.feature&lt;/code&gt; — Scenario: payment gateway times out&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: &lt;code&gt;And the payment gateway is not retried more than 2 times&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Does this mean 2 total attempts (1 original + 1 retry) or 2 retries on top of the original (3 total)? The English is genuinely ambiguous. An agent will pick one. The test will pass. The production system will behave differently than intended.&lt;/p&gt;

&lt;p&gt;Fix: &lt;code&gt;And the payment gateway receives no more than 2 charge requests total&lt;/code&gt; — "requests total" removes all ambiguity about whether the first attempt counts.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;3. "Released" is not a mechanism&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;order_creation.feature&lt;/code&gt; — Scenario: payment declined&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: &lt;code&gt;And the inventory reservation is released&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Released" is not defined. Does the inventory service receive a DELETE? A POST to a release endpoint? Does a TTL fire? An agent will implement whichever mechanism seems natural. Two agents will produce incompatible implementations that both pass the spec.&lt;/p&gt;

&lt;p&gt;Fix: Name the items and the mechanism: &lt;code&gt;And the inventory service receives a reservation release request for SHOE-RED-42 and BELT-BRN-M&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;4. "Explicit user action" describes a flow that doesn't exist&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;order_creation.feature&lt;/code&gt; — Scenario: partial availability&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: &lt;code&gt;And no order is confirmed without explicit user action&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Explicit user action" is not defined anywhere in the spec. A second API call? A UI confirmation? A webhook? This step passes trivially because no order is confirmed — the negative condition is true by absence. But it implies a follow-up confirmation flow that was never built, never specced, and never reviewed. If a future agent reads this step and builds a confirmation flow to satisfy it, it will invent something that was never intended.&lt;/p&gt;

&lt;p&gt;Fix: Remove it if the follow-up flow is out of scope. Or replace it with a concrete step: &lt;code&gt;And a subsequent POST to /orders/{order_id}/confirm is required to complete the order&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;5. Presence without value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;order_status_bad.feature&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: field-name assertions without value or type assertions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Asserting that a field exists only catches absence — not incorrect presence. An agent can return &lt;code&gt;{"status": null}&lt;/code&gt; and pass. The spec catches the wrong thing.&lt;/p&gt;

&lt;p&gt;Fix: Assert the full expected shape with explicit values rather than just field names.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;6. "An order exists" doesn't say how&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;order_status_good.feature&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: &lt;code&gt;Given an order exists with status "CONFIRMED"&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"An order exists" doesn't specify how it got there — full creation flow, or directly seeded into the store. The two methods produce different side effects. An agent building a test harness may seed the order directly, bypassing the creation flow entirely, which means the status endpoint tests never verify that a real confirmed order is actually readable via the API.&lt;/p&gt;

&lt;p&gt;Fix: &lt;code&gt;Given a previously confirmed order created via POST /orders with id "{order_id}"&lt;/code&gt; — or explicitly state that direct seeding is acceptable.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;7. "Correct" is relative&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;File: &lt;code&gt;notification_service.feature&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Step: &lt;code&gt;And the notification contains the correct order id and total&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Correct" compared to what? If the order total is computed, two agents may compute it differently and both pass "correct" against their own computation.&lt;/p&gt;

&lt;p&gt;Fix: Hardcode the expected value: &lt;code&gt;And the notification request body contains order_id matching the confirmed order and total of 134.97&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why all seven of these matter even though they're all green
&lt;/h2&gt;

&lt;p&gt;Every item in that audit passes its test. That is the point.&lt;/p&gt;

&lt;p&gt;Spec debt is not visible in a green CI run. It is visible only when you ask: &lt;em&gt;what would a second agent build from this spec?&lt;/em&gt; The step "the payment gateway is not retried more than 2 times" has been in the codebase since Issue #2. It has passed every run. But it encodes an ambiguity that will be resolved differently by every agent that implements it fresh. The "no order is confirmed without explicit user action" step describes a flow that does not exist anywhere in the codebase. It passes because the negative condition is trivially true.&lt;/p&gt;

&lt;p&gt;If a future agent reads that step and builds a confirmation flow to satisfy it, it will build something that was never specced, never reviewed, and never integrated. The spec invited it. The tests blessed it. Nobody noticed.&lt;/p&gt;

&lt;p&gt;This is the exact failure mode that makes AI-assisted development unreliable at scale. Specs that look precise, pass their tests, and silently invite incompatible implementations. The debt doesn't announce itself. It compounds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the project stands
&lt;/h2&gt;

&lt;p&gt;Fifteen tests passing across four bounded feature files. The notification service is integrated. The Pact contracts — which existed before this session — remain unbroken because the notification call happens after the transaction completes. Adding a new service boundary didn't require touching existing contracts.&lt;/p&gt;

&lt;p&gt;Seven spec debt items documented. None fixed yet. The fixes are the next issue.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: The Spec Audit — applying the debt framework to a real existing service and building the diagnostic tool readers can use on their own codebases.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/docs/gherkin/" rel="noopener noreferrer"&gt;Cucumber + Gherkin documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-07-scope-problem.md" rel="noopener noreferrer"&gt;Session findings — Issue #7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>agents</category>
      <category>testing</category>
    </item>
    <item>
      <title>My Tests Passed. My Pipeline Caught What They Missed.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Sat, 13 Jun 2026 18:59:27 +0000</pubDate>
      <link>https://dev.to/diyaburman/wiring-the-guardrails-19i</link>
      <guid>https://dev.to/diyaburman/wiring-the-guardrails-19i</guid>
      <description>&lt;p&gt;&lt;em&gt;A Level 5 Engineer — Issue #6&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Five issues in, everything we've built lives on one machine. The Gherkin scenarios, the WireMock stubs, the Pact contracts, the can-i-deploy script — all of it runs locally, passes locally, and means nothing the moment someone else touches the codebase.&lt;/p&gt;

&lt;p&gt;Issue #6 fixes that. A GitHub Actions pipeline now runs on every push, executes the full specification stack in dependency order, and blocks merges to main if anything breaks. The pipeline is the guardrail. From this point on, a broken contract or a failing scenario cannot reach main undetected.&lt;/p&gt;

&lt;p&gt;Getting there took ninety minutes and two interventions I didn't plan for. Both are worth documenting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before the YAML: deciding what "green" means
&lt;/h2&gt;

&lt;p&gt;The first thing Claude Code did before touching any pipeline config was run the full test suite to establish a baseline. The instruction was explicit: everything must pass before a single line of YAML gets written.&lt;/p&gt;

&lt;p&gt;It found a failure immediately — and it wasn't from the breaking change experiment. It was from Issue #5.&lt;/p&gt;

&lt;p&gt;The bad-spec test (&lt;code&gt;test_order_status_bad.py::test_retrieving_status_for_a_confirmed_order&lt;/code&gt;) was still asserting &lt;code&gt;db_status&lt;/code&gt; in the response body. That was intentional in Issue #5 — the failure was the finding. The session ended with it red because the point was to show what bad specs produce. But on main, with CI incoming, that means the pipeline would have been red on day one before a single feature change.&lt;/p&gt;

&lt;p&gt;The fix was adding backward-compat aliases to the response:&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;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;order_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;order_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;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;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;            &lt;span class="c1"&gt;# good spec field
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_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;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;         &lt;span class="c1"&gt;# bad spec alias — keeps Issue #5 test passing
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;placed_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# good spec field
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# bad spec alias
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither test file was modified. No feature files were touched. The aliases kept both the good-spec and bad-spec tests passing against the same endpoint.&lt;/p&gt;

&lt;p&gt;The reason this matters before the pipeline exists: a team that starts CI with a known failure trains itself to ignore red. The cost of normalising a red CI is much higher than the cost of fixing the baseline first. Claude Code made the right call and documented it before moving on.&lt;/p&gt;




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

&lt;p&gt;Four jobs, in dependency order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test → pact-consumer → pact-verify → can-i-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each job only runs if its predecessor passes. If Gherkin breaks, Pact never runs. If the consumer tests fail, verification never runs. If verification fails, can-i-deploy is skipped. The pipeline fails fast and tells you exactly which layer broke.&lt;/p&gt;

&lt;p&gt;The artifact chain is what makes it a pipeline rather than four parallel scripts. The &lt;code&gt;pact-consumer&lt;/code&gt; job generates the &lt;code&gt;.pact&lt;/code&gt; files and uploads them as a GitHub Actions artifact. The &lt;code&gt;pact-verify&lt;/code&gt; job downloads that artifact and verifies it — the same files, not freshly regenerated ones. Without this, each job would build its own consumer contract from scratch, and verification would be proving that the contract matches the code rather than proving it matches what &lt;code&gt;pact-consumer&lt;/code&gt; actually produced.&lt;/p&gt;

&lt;p&gt;One non-obvious piece: &lt;code&gt;mock_server.py&lt;/code&gt; is a library module with no command-line entry point. The pipeline needed servers running as background processes. The fix was an inline Python invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start mock servers&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;. .venv/bin/activate&lt;/span&gt;
    &lt;span class="s"&gt;python -c "&lt;/span&gt;
    &lt;span class="s"&gt;import time&lt;/span&gt;
    &lt;span class="s"&gt;from mock_server import start_mock_server&lt;/span&gt;
    &lt;span class="s"&gt;start_mock_server(8091, 'wiremock/payment-mappings')&lt;/span&gt;
    &lt;span class="s"&gt;start_mock_server(8092, 'wiremock/inventory-mappings')&lt;/span&gt;
    &lt;span class="s"&gt;time.sleep(86400)&lt;/span&gt;
    &lt;span class="s"&gt;" &amp;amp;&lt;/span&gt;
    &lt;span class="s"&gt;sleep 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;time.sleep(86400)&lt;/code&gt; keeps the process alive for the duration of the job. Inelegant but functional. A proper &lt;code&gt;if __name__ == "__main__"&lt;/code&gt; entry point with argparse is the obvious cleanup for a future session.&lt;/p&gt;




&lt;h2&gt;
  
  
  The first CI run — and why I had to intervene manually
&lt;/h2&gt;

&lt;p&gt;The YAML was committed, pushed to main, and the pipeline ran. All three runs failed on the &lt;code&gt;test&lt;/code&gt; job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OSError: [Errno 98] Address already in use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ports 8091 and 8092. Every test in &lt;code&gt;test_order_creation.py&lt;/code&gt; errored at setup. The order status tests — which don't use the mock servers — passed fine.&lt;/p&gt;

&lt;p&gt;Claude Code didn't catch this on its own. Here's why that's worth explaining.&lt;/p&gt;

&lt;p&gt;When Claude Code wrote the pipeline, it was working from the codebase and its own knowledge of GitHub Actions patterns. It knew the mock servers needed to be running before pytest started, so it added an explicit start-servers step to the YAML — a reasonable decision based on the information it had. What it couldn't see was the runtime interaction between that YAML step and pytest's session-scoped fixtures, because that interaction only manifests in the CI environment, not locally.&lt;/p&gt;

&lt;p&gt;Locally, running &lt;code&gt;pytest tests/steps/ -v&lt;/code&gt; has always worked correctly because the session fixture starts the servers and nothing else competes. Claude Code had only ever seen local runs succeed. It had no signal that the YAML step was creating a conflict — because the conflict doesn't exist locally.&lt;/p&gt;

&lt;p&gt;This is a fundamental limit of the "paste and walk away" approach at the boundary between local and remote environments: the agent can reason about the codebase and about CI patterns, but it can't observe the CI run itself. The failure was on GitHub. Claude Code was in a terminal. Those two things weren't connected.&lt;/p&gt;

&lt;p&gt;I diagnosed the error from the GitHub Actions log, explained the root cause, and pasted new instructions. Claude Code fixed it in one step — removing the redundant YAML steps entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Removed from both test and pact-verify jobs:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start mock servers&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;. .venv/bin/activate&lt;/span&gt;
    &lt;span class="s"&gt;python -c "..." &amp;amp;&lt;/span&gt;
    &lt;span class="s"&gt;sleep 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pytest session fixtures already own server lifecycle correctly. &lt;code&gt;scope="session"&lt;/code&gt; means pytest starts the servers once per test run and keeps them alive. The YAML step was duplicating a responsibility that was already handled. The fix wasn't a workaround — it was removing the wrong layer.&lt;/p&gt;

&lt;p&gt;The root cause in plain terms: the YAML step and the pytest fixture both thought they were responsible for starting the servers. The port was already bound when the fixture tried to bind it again. Works on my machine. Breaks in CI. Classic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The breaking change experiment — in the pipeline
&lt;/h2&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%2F0pme03rx2l6f1uwkhb5f.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%2F0pme03rx2l6f1uwkhb5f.png" alt="All four jobs green — 1m 34s. SAFE TO DEPLOY." width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the pipeline green, the breaking change test ran as designed.&lt;/p&gt;

&lt;p&gt;Branch &lt;code&gt;test/breaking-change-pipeline&lt;/code&gt;, commit &lt;code&gt;76c0d89&lt;/code&gt;: renamed &lt;code&gt;"status"&lt;/code&gt; to &lt;code&gt;"result"&lt;/code&gt; in &lt;code&gt;wiremock/payment-mappings/payment-success.json&lt;/code&gt;. Same change as Issue #4, now running through CI instead of local verification.&lt;/p&gt;

&lt;p&gt;The expected failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;a successful payment charge (FAILED)
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;Failures:
1) Verifying a pact between OrderService and PaymentGateway
&lt;/span&gt;   1.1) has a matching body
          $ -&amp;gt; Actual map is missing the following keys: status
   {
     "amount": 134.97,
  -  "status": "ACCEPTED",
  +  "result": "ACCEPTED",
     "transaction_id": "txn-abc-123"
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pact-verify&lt;/code&gt; fails. &lt;code&gt;can-i-deploy&lt;/code&gt; is skipped. The merge is blocked.&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%2Fssmd6jo21xf0fw95613k.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%2Fssmd6jo21xf0fw95613k.png" alt="pact-verify catches the broken contract. can-i-deploy never runs." width="799" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the key point from Issue #4 holds at the pipeline level: the &lt;code&gt;test&lt;/code&gt; job — the Gherkin suite — would pass with the breaking change in place. The order creation scenarios check HTTP status codes and business outcomes. They never read &lt;code&gt;pay_resp.json()["status"]&lt;/code&gt;. A stub returning &lt;code&gt;result&lt;/code&gt; instead of &lt;code&gt;status&lt;/code&gt; still returns HTTP 200. Gherkin passes. Pact catches it.&lt;/p&gt;

&lt;p&gt;This is the division of labour. Gherkin proves the system does the right thing. Pact proves the contracts don't drift. You need both, and now both run automatically on every push.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one step that requires the GitHub UI
&lt;/h2&gt;

&lt;p&gt;Claude Code cannot configure branch protection rules — that requires the GitHub web UI or admin API. This step is non-negotiable and must be done manually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Repo → &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Branches&lt;/strong&gt; → &lt;strong&gt;Add branch protection rule&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Branch name pattern: &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Require status checks to pass before merging&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add all four status checks: &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;pact-consumer&lt;/code&gt;, &lt;code&gt;pact-verify&lt;/code&gt;, &lt;code&gt;can-i-deploy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Require branches to be up to date before merging&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without this, the pipeline is advisory. A push to main can still happen even if all four jobs are red. The pipeline becomes a dashboard — it shows you the problem but doesn't stop anything. Branch protection is what turns "CI failed" from a notification into enforcement. The pipeline is only a guardrail if something stops you going around it.&lt;/p&gt;




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

&lt;p&gt;The YAML took about twenty minutes to write. The session took ninety minutes total — because the baseline fix and the port conflict ate the rest.&lt;/p&gt;

&lt;p&gt;The instinct during the baseline audit was to skip past the known failure. It's a demo test, we know why it's there, configure CI to skip that file and move on. That would have been thirty seconds. It also would have been wrong — a pipeline with documented exceptions is a pipeline people route around.&lt;/p&gt;

&lt;p&gt;The instinct during the port conflict was to blame the CI environment. Ubuntu runs things differently, ports work differently, it's a platform quirk. That framing would have sent the debugging in the wrong direction. The actual cause was simpler: two layers both thought they owned the same responsibility, and nobody had written down which one was actually in charge.&lt;/p&gt;

&lt;p&gt;Both of those moments are the J-curve. Not the YAML — the discipline of not skipping and not blaming the environment. The overhead of CI is not the config file. It's every decision about what "green" actually means and who's responsible for what.&lt;/p&gt;

&lt;p&gt;The pipeline is now real infrastructure. The breaking change can't reach main. That's worth ninety minutes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: The Scope Problem — scaling Gherkin across a multi-service system. What happens when one spec file isn't enough, and how spec debt forms.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pact.io" rel="noopener noreferrer"&gt;Pact documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-06-cicd-guardrails.md" rel="noopener noreferrer"&gt;Session findings — Issue #6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
    <item>
      <title>The AI Built the Wrong Thing. Every Test Passed.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Wed, 10 Jun 2026 02:40:42 +0000</pubDate>
      <link>https://dev.to/diyaburman/the-spec-that-doesnt-lie-5a00</link>
      <guid>https://dev.to/diyaburman/the-spec-that-doesnt-lie-5a00</guid>
      <description>&lt;p&gt;&lt;em&gt;A Level 5 Engineer — Issue #5&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;Every issue so far has assumed something I haven't said out loud: that the specs are good. Issue #2 wrote them carefully. Issue #3 handed them to an agent and watched it build correctly. Issue #4 proved the contracts survive provider drift.&lt;/p&gt;

&lt;p&gt;But what happens when the spec isn't good? Not broken — Gherkin syntax is fine, tests pass, the agent builds something. Just imprecise. Vague in ways that feel precise when you're writing them.&lt;/p&gt;

&lt;p&gt;This issue answers that question by doing the thing deliberately. I wrote bad Gherkin on purpose, handed it to the agent, watched what it built — and then rewrote the spec and did it again. The difference between the two implementations is the article.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hardest thing about bad specs
&lt;/h2&gt;

&lt;p&gt;Bad specs are hard to spot when you're writing them because they feel complete.&lt;/p&gt;

&lt;p&gt;A scenario that references implementation details sounds like reasonable description — you wrote the implementation, so the details feel like specifics. A Given clause that feels obvious to you will be interpreted differently by every reader who hasn't seen the code. The Gherkin is syntactically correct. The tests pass. Nothing in the output signals that anything is wrong.&lt;/p&gt;

&lt;p&gt;This is the trap. It's not that bad specs break things. It's that they don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The endpoint
&lt;/h2&gt;

&lt;p&gt;I added a new endpoint to the order-api project: &lt;code&gt;GET /orders/{order_id}/status&lt;/code&gt;. It returns the current status of an order and relevant metadata. Simple enough that the spec should be easy to write well. Which makes it a good target for writing it badly on purpose.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bad specs
&lt;/h2&gt;

&lt;p&gt;Two scenarios. Both syntactically valid. Both produce passing tests. Both wrong in different ways.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# BAD SPEC 1 — The leaky spec&lt;/span&gt;
&lt;span class="c"&gt;# Problem: references internal implementation concepts (db_status, order_created_at)&lt;/span&gt;
&lt;span class="c"&gt;# rather than describing what a caller observes. The agent uses these names literally&lt;/span&gt;
&lt;span class="c"&gt;# in the response body, leaking storage terminology into the public API contract.&lt;/span&gt;

&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Retrieving status for a confirmed order
  &lt;span class="nf"&gt;Given &lt;/span&gt;an order exists in the system with db_status &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
  &lt;span class="nf"&gt;When &lt;/span&gt;I request GET /orders/{order_id}/status
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response should contain the db_status field set to &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the order_created_at field should be populated from the order record

&lt;span class="c"&gt;# BAD SPEC 2 — The vague Given&lt;/span&gt;
&lt;span class="c"&gt;# Problem: "an order that has not been placed" is underspecified. The agent must&lt;/span&gt;
&lt;span class="c"&gt;# guess what this means — a malformed ID? A well-formed UUID with no record?&lt;/span&gt;
&lt;span class="c"&gt;# A deleted order? Each interpretation is plausible and produces different behavior.&lt;/span&gt;

&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Retrieving status for an order that does not exist
  &lt;span class="nf"&gt;Given &lt;/span&gt;an order that has not been placed
  &lt;span class="nf"&gt;When &lt;/span&gt;I request GET /orders/{order_id}/status
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response should indicate the order was not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both passed immediately:&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="go"&gt;tests/steps/test_order_status_bad.py::test_retrieving_status_for_a_confirmed_order PASSED
tests/steps/test_order_status_bad.py::test_retrieving_status_for_an_order_that_does_not_exist PASSED

2 passed in 0.34s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Green. No warnings. No hint that anything is wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the agent built from the bad specs
&lt;/h2&gt;

&lt;p&gt;Here's the implementation the agent produced:&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="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;/orders/{order_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_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_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;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_orders&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;order_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;order&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&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;Order not found&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="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_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;order_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;db_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;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_status&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;order_created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_created_at&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;It satisfies the spec completely. It also made four decisions the spec never made:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision 1: The field is named &lt;code&gt;db_status&lt;/code&gt; in the response.&lt;/strong&gt;&lt;br&gt;
The spec said &lt;code&gt;db_status&lt;/code&gt; so the agent used &lt;code&gt;db_status&lt;/code&gt;. It never questioned whether this was an internal name leaking into a public API. It satisfied the spec literally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision 2: A missing order returns 404.&lt;/strong&gt;&lt;br&gt;
The spec says "indicate the order was not found." 404 is a defensible interpretation. So is 422, 403, or a 200 with a &lt;code&gt;NOT_FOUND&lt;/code&gt; status field. The agent picked the most conventional option — but the spec never mandated it, and FastAPI's default 404 body is &lt;code&gt;{"detail": "Order not found"}&lt;/code&gt;, not &lt;code&gt;{"error": "Order not found"}&lt;/code&gt;. A client checking &lt;code&gt;response.json()["error"]&lt;/code&gt; gets a KeyError.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision 3: The timestamp field is named &lt;code&gt;order_created_at&lt;/code&gt; with no format requirement.&lt;/strong&gt;&lt;br&gt;
The spec says "populated from the order record." The agent chose &lt;code&gt;order_created_at&lt;/code&gt; and returned an ISO string because that's what &lt;code&gt;datetime.utcnow().isoformat()&lt;/code&gt; produces. The step definition checked only that the field is non-empty and a string — so any format would have passed. A Unix timestamp integer would have passed. A human-readable string like "June 2nd" would have passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision 4: The order store is in-memory.&lt;/strong&gt;&lt;br&gt;
The spec says nothing about persistence. An in-memory dict is the simplest thing that makes the tests pass. In production, orders are persisted. The in-memory store vanishes on restart and isn't shared across worker processes.&lt;/p&gt;

&lt;p&gt;Every one of these decisions is plausible. The agent made the reasonable call every time. That's not the problem. The problem is that a different agent, given the same spec, might have made different reasonable calls — and both implementations would pass the same test suite.&lt;/p&gt;


&lt;h2&gt;
  
  
  The rewrite
&lt;/h2&gt;

&lt;p&gt;Writing the good spec forced every decision the bad spec had silently delegated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# GOOD SPEC 1 — Caller's perspective, not implementation's&lt;/span&gt;
&lt;span class="c"&gt;# Fixed: field names describe what the caller observes (status, placed_at)&lt;/span&gt;
&lt;span class="c"&gt;# not what the storage layer calls them (db_status, order_created_at).&lt;/span&gt;
&lt;span class="c"&gt;# The format of placed_at is now a contract obligation, not an assumption.&lt;/span&gt;

&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Confirmed order status is returned with placement timestamp
  &lt;span class="nf"&gt;Given &lt;/span&gt;a confirmed order with id &lt;span class="s"&gt;"order-abc-123"&lt;/span&gt; exists in the system
  &lt;span class="nf"&gt;When &lt;/span&gt;I request GET /orders/order-abc-123/status
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response status code is 200
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains &lt;span class="s"&gt;"order_id"&lt;/span&gt; equal to &lt;span class="s"&gt;"order-abc-123"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains &lt;span class="s"&gt;"status"&lt;/span&gt; equal to &lt;span class="s"&gt;"CONFIRMED"&lt;/span&gt;
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains &lt;span class="s"&gt;"placed_at"&lt;/span&gt; as a valid ISO 8601 timestamp

&lt;span class="c"&gt;# GOOD SPEC 2 — Precise Given, explicit 404 body shape&lt;/span&gt;
&lt;span class="c"&gt;# Fixed: "a well-formed UUID with no corresponding record" is now unambiguous.&lt;/span&gt;
&lt;span class="c"&gt;# The 404 response body shape is now a contract obligation, not a guess.&lt;/span&gt;

&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Unknown order id returns 404 with error message
  &lt;span class="nf"&gt;Given &lt;/span&gt;no order with id &lt;span class="s"&gt;"order-xyz-999"&lt;/span&gt; exists in the system
  &lt;span class="nf"&gt;When &lt;/span&gt;I request GET /orders/order-xyz-999/status
  &lt;span class="nf"&gt;Then &lt;/span&gt;the response status code is 404
  &lt;span class="nf"&gt;And &lt;/span&gt;the response body contains an &lt;span class="s"&gt;"error"&lt;/span&gt; field
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what changed. The scenarios describe the same two situations. The intent is identical. But now every decision is in the spec rather than in the agent's interpretation of the spec.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the agent built from the good spec
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&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;/orders/{order_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_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_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;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_orders&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;order_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;order&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&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;404&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;Order not found&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="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_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;order_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;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;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_status&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;placed_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_created_at&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;Same endpoint. Same logic. Different API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;db_status&lt;/code&gt; became &lt;code&gt;status&lt;/code&gt;. &lt;code&gt;order_created_at&lt;/code&gt; became &lt;code&gt;placed_at&lt;/code&gt;. The 404 body now contains &lt;code&gt;error&lt;/code&gt; not &lt;code&gt;detail&lt;/code&gt;. The timestamp is now asserted to be ISO 8601 — not just non-empty.&lt;/p&gt;

&lt;p&gt;These are not cosmetic differences. They are different contracts that clients build against.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cross-run
&lt;/h2&gt;

&lt;p&gt;After building from the good spec, I ran the bad-spec tests against the new implementation:&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="go"&gt;tests/steps/test_order_status_bad.py::test_retrieving_status_for_a_confirmed_order FAILED
tests/steps/test_order_status_bad.py::test_retrieving_status_for_an_order_that_does_not_exist PASSED

E   KeyError: 'db_status'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leaky test failed. The field &lt;code&gt;db_status&lt;/code&gt; doesn't exist in the good implementation — it's been renamed to &lt;code&gt;status&lt;/code&gt;, which is what a caller should see. The test that was checking for an internal name is now broken, correctly.&lt;/p&gt;

&lt;p&gt;The vague test passed. Both implementations return a 404 for a missing order — the good implementation just happened to reach the same conclusion, but for an explicit reason this time.&lt;/p&gt;

&lt;p&gt;That asymmetry is instructive. The vague Given produced the right answer by coincidence. The leaky Then produced the wrong field name by construction. One was luck. One was baked in.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Both implementations pass their own test suites. That is the trap.&lt;/p&gt;

&lt;p&gt;If you run the bad-spec tests against the bad-spec implementation: green. If you run the good-spec tests against the good-spec implementation: green. The difference only surfaces when you cross-run — and in production, you never cross-run. You ship the bad implementation, it passes CI, and the problem lands in a client exception report six months later.&lt;/p&gt;

&lt;p&gt;Here's the concrete difference: the bad-spec implementation returns &lt;code&gt;db_status&lt;/code&gt; and &lt;code&gt;order_created_at&lt;/code&gt; with no format guarantee. The good-spec implementation returns &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;placed_at&lt;/code&gt; with a mandatory ISO 8601 format. An agent given the bad spec had no way to know that &lt;code&gt;db_status&lt;/code&gt; was wrong — the spec said &lt;code&gt;db_status&lt;/code&gt;. An agent given the good spec had no choice but to produce &lt;code&gt;status&lt;/code&gt; — the spec said &lt;code&gt;status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Spec quality is not about whether tests pass. It is about how much of the implementation the spec author wrote versus how much was silently delegated to the agent. Every silent delegation is a place where two agents given the same spec produce different code — code that both passes, but disagrees on the contract.&lt;/p&gt;

&lt;p&gt;At scale — dozens of endpoints, hundreds of scenarios — that disagreement is the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The practical test for a good spec
&lt;/h2&gt;

&lt;p&gt;Before handing any scenario to an agent, ask one question: &lt;em&gt;what decisions does this scenario leave open?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "none — every field name, format, response code, and body shape is specified," the spec is ready. If the answer is "a few reasonable ones," those are the places where your implementation and the next agent's implementation will silently diverge.&lt;/p&gt;

&lt;p&gt;The agent will always make reasonable decisions. That's not the problem. The problem is that reasonable is not the same as specified — and at Level 4, specified is the only thing that counts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: Wiring the Guardrails — GitHub Actions, the Pact Broker, and the pipeline that turns contract violations into blocked merges automatically.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cucumber.io/docs/gherkin/" rel="noopener noreferrer"&gt;Cucumber + Gherkin documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-05-the-spec-that-doesnt-lie.mdL" rel="noopener noreferrer"&gt;Session findings — Issue #5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
    <item>
      <title>Green CI. Broken Contract. Nobody Noticed.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Wed, 10 Jun 2026 02:24:34 +0000</pubDate>
      <link>https://dev.to/diyaburman/how-pact-contract-testing-catches-breaking-changes-that-wiremock-misses-3ge6</link>
      <guid>https://dev.to/diyaburman/how-pact-contract-testing-catches-breaking-changes-that-wiremock-misses-3ge6</guid>
      <description>&lt;p&gt;&lt;em&gt;A Level 5 Engineer — Issue #4&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;If you've been following along, you know where we are. &lt;a href="https://dev.to/diyaburman/the-bottleneck-moved-did-you-notice-5beb"&gt;Issue #2&lt;/a&gt; introduced WireMock and Gherkin — write the behavioral contract before the code, stub your dependencies, run a real test suite. &lt;a href="https://dev.to/diyaburman/i-gave-the-agent-the-spec-and-walked-away-heres-what-it-built-jja"&gt;Issue #3&lt;/a&gt; handed that spec to an AI agent and walked away. Five scenarios passed. The agent even found a bug in my code.&lt;/p&gt;

&lt;p&gt;Everything worked. And that's exactly the problem this issue is about.&lt;/p&gt;

&lt;p&gt;Because the WireMock stubs working perfectly is not the same thing as the real services working. The gap between those two statements is where production incidents are born.&lt;/p&gt;




&lt;h2&gt;
  
  
  The confidence trap
&lt;/h2&gt;

&lt;p&gt;Here's the scenario nobody talks about until it happens to them.&lt;/p&gt;

&lt;p&gt;Your order service calls a payment gateway. You've stubbed it with WireMock. Your Gherkin scenarios pass. Your agent builds against those stubs. Five for five, green across the board.&lt;/p&gt;

&lt;p&gt;Meanwhile, the payment gateway team — a different squad, a different repo, maybe a different company entirely — ships a cleanup. They've been inconsistent about field naming across their API. &lt;code&gt;status&lt;/code&gt; in one endpoint, &lt;code&gt;result&lt;/code&gt; in another. They standardize. They rename &lt;code&gt;status&lt;/code&gt; to &lt;code&gt;result&lt;/code&gt; in the charge response. Their tests pass. They deploy.&lt;/p&gt;

&lt;p&gt;Your tests still pass too. The stub hasn't changed. The stub will never change unless you change it.&lt;/p&gt;

&lt;p&gt;The first time you learn about the rename is a production incident.&lt;/p&gt;

&lt;p&gt;This is the confidence trap: a mock that can drift from the real service makes you feel safe right up until production proves you weren't. The tests are green. The contract is broken. You just don't know it yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Pact does differently
&lt;/h2&gt;

&lt;p&gt;WireMock is a &lt;em&gt;behavioral double&lt;/em&gt; — it simulates a service so your tests can run in isolation. You define what it returns. You maintain it. You can make it say anything you want, which means it can silently lie about what the real service actually does.&lt;/p&gt;

&lt;p&gt;Pact inverts the trust relationship.&lt;/p&gt;

&lt;p&gt;Instead of you maintaining a stub that you hope reflects reality, your consumer tests &lt;em&gt;declare what they need&lt;/em&gt; from the provider. Those declarations get written into a &lt;code&gt;.pact&lt;/code&gt; file — a machine-readable contract. The provider then runs verification against that contract before it ships. If the provider no longer satisfies what the consumer declared, verification fails and the deploy is blocked.&lt;/p&gt;

&lt;p&gt;The consumer defines the need. The provider proves delivery. No human has to remember to update a stub.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building it — and what the docs didn't tell me
&lt;/h2&gt;

&lt;p&gt;I added Pact to the order-api project this issue, covering both downstream dependencies — the payment gateway and the inventory service — with consumer tests matching the same five scenarios from the Gherkin feature file.&lt;/p&gt;

&lt;p&gt;It was less smooth than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pact-python v3 FFI surprise
&lt;/h3&gt;

&lt;p&gt;Every tutorial for pact-python shows the same pattern: create a module-scoped Pact fixture, run multiple tests against it, write the pact file at the end. I wrote exactly that. The first test in each class passed. Every subsequent test failed with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RuntimeError: The provider state could not be specified.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hint of what was actually wrong. After digging into the source, the root cause: &lt;code&gt;pact-python&lt;/code&gt; 3.x is a complete rewrite backed by a Rust FFI binary. The Rust handle is &lt;em&gt;consumed&lt;/em&gt; by the first &lt;code&gt;serve()&lt;/code&gt; call — you cannot add new interactions to a handle after that point. The v2-style module-scoped pattern violates this constraint in a way the error message doesn't explain at all.&lt;/p&gt;

&lt;p&gt;The fix was restructuring the consumer tests so all interactions are defined upfront before any &lt;code&gt;serve()&lt;/code&gt; call:&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="c1"&gt;# ❌ v2-style — breaks in pact-python v3
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestPaymentConsumer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;module&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;pact&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OrderService&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;has_pact_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PaymentGateway&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;test_success&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;pact&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment succeeds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upon_receiving&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 charge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# test
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_declined&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;pact&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment declined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upon_receiving&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 decline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="c1"&gt;# RuntimeError — handle already consumed
&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ v3 correct pattern — all interactions before serve()
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_payment_gateway_consumer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;pact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OrderService&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;has_pact_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PaymentGateway&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pact&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;given&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 payment gateway will accept the charge&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="nf"&gt;upon_receiving&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 successful payment charge&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="nf"&gt;with_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;POST&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;/payments/charge/success&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="nf"&gt;will_respond_with&lt;/span&gt;&lt;span class="p"&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;body&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACCEPTED&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;transaction_id&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;txn-abc-123&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;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;134.97&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pact&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;given&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 payment gateway will decline the charge&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="nf"&gt;upon_receiving&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 declined payment charge&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="nf"&gt;with_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;POST&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;/payments/charge/declined&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="nf"&gt;will_respond_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DECLINED&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;reason&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;INSUFFICIENT_FUNDS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="c1"&gt;# ... all interactions defined ...
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# exercise all interactions against srv.url
&lt;/span&gt;    &lt;span class="n"&gt;pact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pacts/&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;If you're upgrading from pact-python 1.x or 2.x: expect to rewrite your test fixtures. This isn't a syntax change — it's a different mental model of how the mock server lifecycle works.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Verifier transport configuration gap
&lt;/h3&gt;

&lt;p&gt;Provider verification had its own friction. The &lt;code&gt;Verifier&lt;/code&gt; constructor in pact-python v3 takes a hostname, not a full URL. Passing a full URL causes a silent host mismatch when you later configure the transport:&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="c1"&gt;# ❌ Causes "Host mismatch: localhost != http://localhost:8291"
&lt;/span&gt;&lt;span class="nc"&gt;Verifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PaymentGateway&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;http://localhost:8291&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="nf"&gt;add_transport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8291&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ Correct
&lt;/span&gt;&lt;span class="nc"&gt;Verifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PaymentGateway&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;localhost&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="nf"&gt;add_transport&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;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8291&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;http&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="nf"&gt;add_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pact_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_request_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# needed for the 6s timeout stub
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;set_request_timeout(10000)&lt;/code&gt; line is also non-obvious: the payment timeout stub uses &lt;code&gt;fixedDelayMilliseconds: 6000&lt;/code&gt; to simulate a slow response. The verifier's default timeout is 5 seconds. Without the explicit timeout extension, the timeout interaction fails verification with a connection error rather than a clean pass.&lt;/p&gt;

&lt;p&gt;Neither of these are in the main documentation. Both took real time to find. They're in the findings file for this session — linked at the bottom.&lt;/p&gt;




&lt;h2&gt;
  
  
  The breaking change experiment
&lt;/h2&gt;

&lt;p&gt;All the Pact setup is preamble. This is the proof.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Baseline — all contracts verified&lt;/strong&gt;&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="go"&gt;pytest tests/pact/test_provider_verification.py -v

Verifying a pact between OrderService and PaymentGateway
  a declined payment charge         (OK)
  a successful payment charge       (OK)
  a timed-out payment charge        (OK)
PASSED

Verifying a pact between OrderService and InventoryService
  [3 interactions — all OK]
PASSED

2 passed in 8.19s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Introduce the breaking change&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;wiremock/payment-mappings/payment-success.json&lt;/code&gt;, one field rename:&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Before&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACCEPTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"transaction_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;"txn-abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;134.97&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;After&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;renamed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"result"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACCEPTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"transaction_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;"txn-abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;134.97&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;&lt;strong&gt;Step 3: Provider verification with the breaking change&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;pytest tests/pact/test_provider_verification.py -v
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;  a successful payment charge (FAILED)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;Failures:
&lt;/span&gt;  1.1) has a matching body
         $ -&amp;gt; Actual map is missing the following keys: status
  {
    "amount": 134.97,
  -  "status": "ACCEPTED",
  +  "result": "ACCEPTED",
    "transaction_id": "txn-abc-123"
  }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;1 failed in 7.22s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pact caught it. Exact field. Exact diff. No ambiguity about what broke or why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: The same breaking change against the WireMock test suite&lt;/strong&gt;&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="go"&gt;pytest tests/steps/test_order_creation.py -v

test_order_is_successfully_created... PASSED
test_order_is_rejected_when_payment_is_declined PASSED
test_order_is_rejected_when_an_item_is_out_of_stock PASSED
test_order_surfaces_partial_unavailability... PASSED
test_order_handling_is_graceful_when_the_payment_gateway_times_out PASSED

5 passed in 13.01s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five for five. All green. The breaking change is completely invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Revert and confirm&lt;/strong&gt;&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="go"&gt;2 passed in 8.19s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why the WireMock tests stayed green
&lt;/h2&gt;

&lt;p&gt;This isn't a flaw in the Gherkin approach — it's a precise boundary on what any behavioral test can and can't see.&lt;/p&gt;

&lt;p&gt;The Gherkin scenarios test the order service's &lt;em&gt;behavior&lt;/em&gt;: does the order get confirmed? Does the right status come back to the caller? In &lt;code&gt;app/main.py&lt;/code&gt;, when the payment gateway responds, the code checks the HTTP status code and returns &lt;code&gt;{"status": "CONFIRMED"}&lt;/code&gt; — it never reads the &lt;code&gt;status&lt;/code&gt; field from the payment gateway body. So from the test harness's perspective, nothing changed. The right HTTP code came back, the order was confirmed, all assertions passed.&lt;/p&gt;

&lt;p&gt;Pact caught it because the consumer test had explicitly declared that the order service &lt;em&gt;expects&lt;/em&gt; a &lt;code&gt;status&lt;/code&gt; field in the payment response. That expectation is encoded in the &lt;code&gt;.pact&lt;/code&gt; file. When provider verification ran against the modified stub, the Rust verifier compared the actual response against the contract and found the key missing.&lt;/p&gt;

&lt;p&gt;The Gherkin test and the Pact consumer test are testing different things. Gherkin tests the system's behavior end-to-end. Pact tests the shape of the conversation between services. You need both. They're not competing — they're covering different failure modes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The can-i-deploy gate
&lt;/h2&gt;

&lt;p&gt;The final piece was a local &lt;code&gt;can-i-deploy&lt;/code&gt; simulation — a script that reads the generated &lt;code&gt;.pact&lt;/code&gt; files, checks each interaction's expected response shape against the WireMock stub mappings, and exits 0 (safe) or 1 (blocked).&lt;/p&gt;

&lt;p&gt;With contracts intact:&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="go"&gt;python scripts/can_i_deploy.py

Pact: OrderService → PaymentGateway
  PASS  a declined payment charge
  PASS  a successful payment charge
  PASS  a timed-out payment charge

Pact: OrderService → InventoryService
  PASS  [3 interactions]

RESULT: ALL CONTRACTS VERIFIED — safe to deploy
Exit: 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the breaking change in place:&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="go"&gt;  FAIL  a successful payment charge
        stub is missing fields expected by consumer: ['status']

RESULT: CONTRACT VIOLATIONS DETECTED — do not deploy
Exit: 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real Pact Broker setup, this check queries a central record of which consumer versions have verified which provider versions. The local simulation does something simpler but teaches the same pattern: before you deploy, prove the contract is still satisfied. The exit code is what a CI pipeline reads. A non-zero exit stops the merge.&lt;/p&gt;

&lt;p&gt;The full GitHub Actions wiring — where this becomes an automated gate on every PR — is Issue #6. The local simulation is enough to feel how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where we are
&lt;/h2&gt;

&lt;p&gt;Four issues in, the specification layer is taking shape. Gherkin and WireMock proved the agent builds reliably against a well-written spec. The agent session proved that clean specs produce clean implementations and expose your assumptions. Pact closes the loop — the contract now survives beyond the stub and catches provider drift before it reaches production.&lt;/p&gt;

&lt;p&gt;The stack is starting to look like something real. But there's a question I've been putting off since Issue #2 that can't wait any longer: what actually makes a Gherkin scenario &lt;em&gt;good&lt;/em&gt;? Because not all specs are equal, and an agent that builds from a loose spec produces something very different from one that builds from a tight one. Next issue I'm going to prove that by deliberately writing bad Gherkin, handing it to the agent, and showing you what comes out.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: The Spec That Doesn't Lie — deliberately writing bad Gherkin, seeing what the agent builds from it, then rewriting it and comparing the output.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pact.io" rel="noopener noreferrer"&gt;Pact documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pact.io/implementation_guides/python" rel="noopener noreferrer"&gt;pact-python v3 migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yourusername/order-api/blob/main/findings/issue-04-pact-contract-testing.md" rel="noopener noreferrer"&gt;Session findings — Issue #4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Agent Found What Code Review Missed.</title>
      <dc:creator>Diya Burman</dc:creator>
      <pubDate>Wed, 10 Jun 2026 01:02:49 +0000</pubDate>
      <link>https://dev.to/diyaburman/i-gave-the-agent-the-spec-and-walked-away-heres-what-it-built-jja</link>
      <guid>https://dev.to/diyaburman/i-gave-the-agent-the-spec-and-walked-away-heres-what-it-built-jja</guid>
      <description>&lt;p&gt;&lt;em&gt;A Level 5 Engineer — Issue #3&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to be upfront about something before we get into it. None of the frameworks in this article is mine. The ideas here come from two people who have been thinking about this stuff way harder and longer than I have — and they deserve full credit before I say another word.&lt;/p&gt;

&lt;p&gt;Dan Shapiro — CEO of Glowforge, Wharton Research Fellow, and the person who gave this whole conversation a vocabulary. His blog post “The Five Levels: from Spicy Autocomplete to the Dark Factory” is the conceptual spine of everything I’m about to say. Read the original. It’s short, sharp, and will make you uncomfortable in the best way. &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;danshapiro.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nate B. Jones — AI strategist, zero-hype practitioner, and the person whose YouTube channel made me realize I had been fooling myself about where I actually sat on this ladder. His video “The 5 Levels of AI Coding (Why Most of You Won’t Make It Past Level 2)” is what triggered this entire newsletter. &lt;a href="https://www.natebjones.com/" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt; — &lt;a href="https://youtu.be/bDcgHzCBgmQ" rel="noopener noreferrer"&gt;Watch the video&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This newsletter — The Level 5 Engineer — is my public learning log. I’m a Senior Software Engineer and a Tech Lead, currently somewhere between Level 2 and Level 3 (in context of the title of this newsletter) on a good day. The goal is Level 5. I’m documenting the climb in real time — the frameworks, the tools, the mindset shifts, and the moments where I realize I’ve been doing it wrong. If you’re on a similar journey, pull up a chair.&lt;/p&gt;




&lt;p&gt;If you've been following along, you know what we've built so far. &lt;a href="https://dev.to/diyaburman/the-level-5-engineer-the-map-i-didnt-know-i-needed-5b5"&gt;Issue #1&lt;/a&gt; introduced the five levels framework and the Dark Factory concept. &lt;a href="https://dev.to/diyaburman/the-bottleneck-moved-did-you-notice-5beb"&gt;Issue #2&lt;/a&gt; got concrete — we wrote five Gherkin scenarios for an order management API before touching any implementation code, stubbed out two external dependencies with WireMock, and ran a real test suite against the whole thing.&lt;/p&gt;

&lt;p&gt;At the end of Issue #2 I made a promise: hand the spec to an AI agent, spec only, no implementation hints, and see what it builds.&lt;/p&gt;

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




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

&lt;p&gt;The instruction I gave Claude Code at the start of the session was exactly this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The Gherkin scenarios in &lt;code&gt;tests/features/order_creation.feature&lt;/code&gt; define the full behavioural contract for this API. Do not read the existing implementation in &lt;code&gt;app/main.py&lt;/code&gt;. Build a fresh implementation that makes all 5 scenarios pass. Document your findings in &lt;code&gt;FINDINGS.md&lt;/code&gt; as you go."&lt;/em&gt;&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%2F25xjzuwf4rqp71o099yc.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%2F25xjzuwf4rqp71o099yc.png" alt="screenshot" width="800" height="852"&gt;&lt;/a&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%2Fcokijs484m3r0ub7wmzt.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%2Fcokijs484m3r0ub7wmzt.png" alt="screenshot" width="800" height="773"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. No architecture hints. No "use FastAPI." No "here's how the mock servers work." Just the spec and a documentation instruction.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;CLAUDE.md&lt;/code&gt; in the repo handled the rest — the guardrails, the project context, the constraint that the &lt;code&gt;.feature&lt;/code&gt; files cannot be touched, and the format the &lt;code&gt;FINDINGS.md&lt;/code&gt; should follow. If you missed the deep dive on &lt;code&gt;CLAUDE.md&lt;/code&gt; in Issue #2, that file is essentially the agent's standing orders. It reads it at the start of every session.&lt;/p&gt;

&lt;p&gt;Then I sat back and watched.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the agent derived from the spec alone
&lt;/h2&gt;

&lt;p&gt;Here's what I found interesting. Before writing a single line of code, the agent read the Gherkin scenarios and derived the entire API contract from them. Unprompted. It produced this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /inventory/check/{inventory_scenario}
  → all available      → POST /payments/charge/{payment_scenario}
  → partial available  → return 207 PARTIAL_UNAVAILABLE (no charge)
  → all out of stock   → return 409 UNAVAILABLE (no charge)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the full response shape for all five scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;th&gt;status_code&lt;/th&gt;
&lt;th&gt;Key fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;td&gt;CONFIRMED&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;order_id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment declined&lt;/td&gt;
&lt;td&gt;PAYMENT_FAILED&lt;/td&gt;
&lt;td&gt;402&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;decline_reason&lt;/code&gt;, &lt;code&gt;inventory_released: true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Out of stock&lt;/td&gt;
&lt;td&gt;UNAVAILABLE&lt;/td&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unavailable_items&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial stock&lt;/td&gt;
&lt;td&gt;PARTIAL_UNAVAILABLE&lt;/td&gt;
&lt;td&gt;207&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;available_items&lt;/code&gt;, &lt;code&gt;unavailable_items&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment timeout&lt;/td&gt;
&lt;td&gt;PAYMENT_PENDING&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;inventory_hold_minutes: 15&lt;/code&gt;, &lt;code&gt;retry_count&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is exactly right. The agent read five plain-language scenarios and extracted a precise technical contract — the order of operations, the response codes, the body fields, the retry behaviour — without being told any of it explicitly.&lt;/p&gt;

&lt;p&gt;That's not nothing. That's the spec doing its job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it got interesting — the timeout scenario
&lt;/h2&gt;

&lt;p&gt;Scenario 5 is the one I was most curious about. Timeout behaviour is notoriously hard to test and easy to get wrong. The agent worked through it carefully and documented its reasoning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PAYMENT_TIMEOUT_SECONDS=5&lt;/code&gt; — per-attempt HTTP client timeout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MAX_PAYMENT_RETRIES=2&lt;/code&gt; — total attempt cap, not a retry count on top of the first attempt&lt;/li&gt;
&lt;li&gt;Worst-case wall time with 2 attempts at 5 seconds each: 10 seconds — comfortably inside the 12-second contract from the scenario&lt;/li&gt;
&lt;li&gt;The WireMock timeout stub uses &lt;code&gt;fixedDelayMilliseconds: 6000&lt;/code&gt; — deliberately longer than the client timeout so the client always times out before the mock responds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last detail is subtle and correct. If the mock delay were shorter than the client timeout, the test would be testing the wrong thing — the mock responding slowly rather than the client giving up. The agent caught this without being prompted. It's in the FINDINGS.md.&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%2Foj12ykckkfpgj974d9m8.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%2Foj12ykckkfpgj974d9m8.png" alt="screenshot" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The bug it found that I had written
&lt;/h2&gt;

&lt;p&gt;This is my favourite part of this issue.&lt;/p&gt;

&lt;p&gt;The original test setup — the code I pointed Claude Code at — had a hard-coded path:&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="n"&gt;sys&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/claude/order-api&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;On my machine this would silently start mock servers with no stubs loaded. Every payment call would return a 404. Every inventory call would return a 404. The tests would fail in ways that looked like logic errors rather than a configuration problem.&lt;/p&gt;

&lt;p&gt;The agent caught it, diagnosed the root cause, and fixed it:&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%2Fqrvhf3xhyz1n8j0i828o.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%2Fqrvhf3xhyz1n8j0i828o.png" alt="screenshot" width="800" height="855"&gt;&lt;/a&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="c1"&gt;# Before — hard-coded, breaks on any machine but the original
&lt;/span&gt;&lt;span class="n"&gt;sys&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/claude/order-api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After — computed dynamically, works everywhere
&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
&lt;span class="n"&gt;sys&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;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROJECT_ROOT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be clear: this bug was in &lt;em&gt;my&lt;/em&gt; code. Code I had written and shipped to the repo. The agent found it during implementation because it was trying to run the tests on a different environment and they failed in a way that forced the diagnosis.&lt;/p&gt;

&lt;p&gt;This is a thing that happens at Level 4 that doesn't happen at Level 2. When you're implementing yourself, you don't notice the hard-coded paths because everything works on your machine. When an agent implements on a clean environment, your assumptions get exposed immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  My honest reaction
&lt;/h2&gt;

&lt;p&gt;I'll be transparent about something. This API isn't complex. It's an order endpoint with two downstream dependencies and five scenarios. I didn't expect the agent to struggle with it, and it didn't. It hit errors, diagnosed them promptly, and moved on. Five scenarios, all passing.&lt;/p&gt;

&lt;p&gt;What struck me wasn't the capability — it was the &lt;em&gt;texture&lt;/em&gt; of the experience.&lt;/p&gt;

&lt;p&gt;Watching Claude Code work, I found myself doing something I don't usually do when I'm implementing: I was evaluating. Not writing, not debugging, not context-switching. Just reading the agent's reasoning and deciding whether I agreed with it. That's a different cognitive posture entirely. It felt closer to a code review than a coding session.&lt;/p&gt;

&lt;p&gt;I also noticed I spent the entire session approving individual commands — every file edit, every &lt;code&gt;pytest&lt;/code&gt; run, every &lt;code&gt;pip install&lt;/code&gt;. Claude Code asks for permission before each action by default. For this first session I let it. From the next task onward I'm going to configure it to run basic commands without checking in every thirty seconds. There's a trust-building curve here, and I'm on the early part of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this proves — and what it doesn't
&lt;/h2&gt;

&lt;p&gt;Five passing scenarios on a moderately simple API is not proof that Level 5 is solved. It's proof that the approach works at this scale and this complexity.&lt;/p&gt;

&lt;p&gt;The honest question — the one this newsletter is actually tracking — is whether it holds as the system grows. Pact tests across services. CI/CD pipelines. Evals as guardrails. Contextual stewardship documents for systems with years of history and undocumented decisions baked into the architecture.&lt;/p&gt;

&lt;p&gt;That's where the real test is. And that's where we're going next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;One thing the exercise exposed: the spec was good enough for the agent to build correctly, but I had one implicit assumption that didn't make it into the scenarios. The response shape for the success case doesn't specify that &lt;code&gt;status_code&lt;/code&gt; should be absent — it just checks for &lt;code&gt;order_id&lt;/code&gt;. The agent inferred this correctly, but if it hadn't, the test would have passed anyway.&lt;/p&gt;

&lt;p&gt;That's a gap in the spec, not a gap in the agent. The lesson is the same one from Issue #2: every implicit assumption is a decision waiting to cause a bug in production. Write it down. Make it a scenario. Make the machine prove it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next issue: Phase 3 — adding Pact contract testing between the order service and its dependencies. What happens when the service contract and the mock stub disagree?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources &amp;amp; Further Reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dan Shapiro — &lt;a href="https://www.danshapiro.com/blog/2026/01/the-five-levels-from-spicy-autocomplete-to-the-software-factory" rel="noopener noreferrer"&gt;The Five Levels: from Spicy Autocomplete to the Dark Factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nate B. Jones — &lt;a href="https://www.natebjones.com" rel="noopener noreferrer"&gt;natebjones.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.claude.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pytest-bdd.readthedocs.io" rel="noopener noreferrer"&gt;pytest-bdd documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api" rel="noopener noreferrer"&gt;Project repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kafka0nkoffee/lvl5engineer-order-api/blob/main/findings/issue-03-agent-fresh-implementation.md" rel="noopener noreferrer"&gt;Session findings - Issue #3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
