<?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: Doby Baxter</title>
    <description>The latest articles on DEV Community by Doby Baxter (@dobybaxter127).</description>
    <link>https://dev.to/dobybaxter127</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%2F4027698%2F76f09f21-843d-463d-bfc3-ee4aa035c479.png</url>
      <title>DEV Community: Doby Baxter</title>
      <link>https://dev.to/dobybaxter127</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dobybaxter127"/>
    <language>en</language>
    <item>
      <title>One Rule Per File: Building a Conformance Corpus for CellML 2.0</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Wed, 28 Oct 2026 22:50:25 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/one-rule-per-file-building-a-conformance-corpus-for-cellml-20-4f7m</link>
      <guid>https://dev.to/dobybaxter127/one-rule-per-file-building-a-conformance-corpus-for-cellml-20-4f7m</guid>
      <description>&lt;p&gt;CellML is an XML format for describing mathematical models of biology: the equations behind a beating heart cell, a nerve impulse, a metabolic pathway. Researchers write a model once, and many different tools can read it, check it and simulate it.&lt;/p&gt;

&lt;p&gt;That only works if the tools agree on what a valid model is. So I built a small project to find out where they do: the &lt;strong&gt;CellML 2.0 Conformance Corpus&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://gitlab.com/dobybaxter127/cellml2-conformance" rel="noopener noreferrer"&gt;https://gitlab.com/dobybaxter127/cellml2-conformance&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The latest report: &lt;a href="https://cellml2-conformance-7c34a8.gitlab.io" rel="noopener noreferrer"&gt;https://cellml2-conformance-7c34a8.gitlab.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem with testing against yourself
&lt;/h2&gt;

&lt;p&gt;Every CellML implementation has its own test suite, and every suite passes. But each one tests the tool against its own reading of the specification. Two readings can differ, and both suites still pass.&lt;/p&gt;

&lt;p&gt;Nobody notices until a model that loads in one tool is rejected by another, and a researcher is left wondering which one is right.&lt;/p&gt;

&lt;p&gt;A shared corpus fixes that. Every tool reads the same files, and every file's expected verdict comes from the specification text, never from what any one tool happens to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standing on someone else's shoulders
&lt;/h2&gt;

&lt;p&gt;This idea isn't new, and I didn't invent it. Michael Clerx built &lt;a href="https://github.com/MichaelClerx/cellml-validation" rel="noopener noreferrer"&gt;cellml-validation&lt;/a&gt;, begun at the University of Oxford and continued at the University of Nottingham. It covers CellML 1.0 and 1.1 in depth, names each test file after the rule it checks, and publishes a report for each tool.&lt;/p&gt;

&lt;p&gt;CellML 2.0 arrived in 2020, and an open issue on that project asked for 2.0 support with libCellML in the comparison. My corpus is a CellML 2.0 iteration of that work. It follows the same layout and naming, and the README credits it as the origin. Every file in it is written fresh.&lt;/p&gt;

&lt;p&gt;It is an independent project, not run by the CellML project or its editors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the first slice came from
&lt;/h2&gt;

&lt;p&gt;The libCellML maintainers had already listed a set of cases in an issue titled "Dealing with real numbers has some holes". Things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a leading &lt;code&gt;+&lt;/code&gt;, as in &lt;code&gt;+2&lt;/code&gt; (which rule 1.3.3.2 of the specification allows)&lt;/li&gt;
&lt;li&gt;strings with no digits at all, such as &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;-.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;an empty value&lt;/li&gt;
&lt;li&gt;a number too large for a computer's &lt;code&gt;double&lt;/code&gt;, such as &lt;code&gt;1e999&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CellML 2.0 lets you write a real number in five places: a unit's multiplier, a unit's exponent, a variable's initial value, a &lt;code&gt;cn&lt;/code&gt; element in the math, and a &lt;code&gt;cn&lt;/code&gt; inside a &lt;code&gt;degree&lt;/code&gt;. Seven cases in five places gives 35 files. That became the first slice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the corpus works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One rule per file.&lt;/strong&gt; Each file is named after the numbered rule it is about, for example &lt;code&gt;2.6.2.3.unit_exponent_leading_plus.cellml&lt;/code&gt;. Its header comments state the rule in plain words and explain the verdict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control files.&lt;/strong&gt; Each place has one ordinary, valid file (&lt;code&gt;_plain&lt;/code&gt;, holding the value &lt;code&gt;2&lt;/code&gt;). Every other file for that place differs from it in exactly one value. So if a tool accepts the control and reports an issue on a variant, it is responding to that one value and nothing else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three folders.&lt;/strong&gt; &lt;code&gt;valid&lt;/code&gt;, &lt;code&gt;invalid&lt;/code&gt;, and &lt;code&gt;unclear&lt;/code&gt;. A file goes in &lt;code&gt;unclear&lt;/code&gt; when the specification genuinely doesn't settle it. &lt;code&gt;1e999&lt;/code&gt; is the first: the syntax is allowed and the spec sets no range, but the number has no IEEE 754 double value. Those files are run and shown, but no tool is counted as right or wrong on them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A self-check.&lt;/strong&gt; The test suite proves the one-value difference for every file. It also works out each number's verdict again with a small grammar written straight from section 1.3 of the specification, independent of every implementation. I tested the self-check by making mistakes on purpose in a copy (an invalid file moved into &lt;code&gt;valid&lt;/code&gt;, a second value changed, a header naming the wrong rule) and confirmed it caught every one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A runner.&lt;/strong&gt; Each implementation reads each file in a process of its own, with a time limit. Every pairing gets one of three outcomes: &lt;strong&gt;accepted&lt;/strong&gt;, &lt;strong&gt;reported an issue&lt;/strong&gt;, or &lt;strong&gt;stopped unexpectedly&lt;/strong&gt;, along with the tool's own message. Nothing is skipped.&lt;/p&gt;

&lt;p&gt;That last part mattered more than I expected. Some files end a tool's process outright. Because each pairing runs on its own, the run carries on, records what happened, and moves to the next file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the first run shows
&lt;/h2&gt;

&lt;p&gt;I compared libCellML 0.7.2 and Myokit 1.39.2. Of the 35 files, 30 have a verdict the specification settles.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;libCellML 0.7.2&lt;/th&gt;
&lt;th&gt;Myokit 1.39.2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Matches the spec&lt;/td&gt;
&lt;td&gt;9 of 30&lt;/td&gt;
&lt;td&gt;30 of 30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Differs from the spec&lt;/td&gt;
&lt;td&gt;21 of 30&lt;/td&gt;
&lt;td&gt;0 of 30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Myokit's outcomes match the specification on all 30 settled files.&lt;/strong&gt; On &lt;code&gt;1e999&lt;/code&gt; in a unit multiplier or exponent, it stops with an &lt;code&gt;OverflowError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;libCellML's differences all sit in the areas its own maintainers listed&lt;/strong&gt; in the issue that started this slice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A leading &lt;code&gt;+&lt;/code&gt; is reported as an issue everywhere except inside &lt;code&gt;degree&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The strings &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;-.&lt;/code&gt; end the process with an uncaught C++ exception in unit attributes and in &lt;code&gt;cn&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In initial values and inside &lt;code&gt;degree&lt;/code&gt;, those strings and an empty value are accepted with no message.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;1e999&lt;/code&gt;, libCellML gives a clear explanation that the value is out of range of the &lt;code&gt;double&lt;/code&gt; type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's encouraging, not discouraging. It means the maintainers' list was accurate, and now each item on it has a small, named file that shows it, plus a way to watch it change from one release to the next.&lt;/p&gt;

&lt;p&gt;The corpus measures and reports. It doesn't rank tools. A difference is information for the people who maintain them, not a judgment of their work. Tools built for simulation often accept more than the specification allows, and sometimes on purpose.&lt;/p&gt;

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

&lt;p&gt;My business is built on a standard I call No Silent Failure: &lt;strong&gt;software should never fail a person in silence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look at the three ways a tool can respond to a bad value. Reporting an issue tells the researcher exactly what to fix. Stopping unexpectedly is loud, but leaves them guessing. Accepting it with no message is the quietest of all: the model loads, the simulation runs, and nobody learns that a value was never a number.&lt;/p&gt;

&lt;p&gt;A conformance corpus makes those differences visible, one rule at a time, so they can be talked about with evidence instead of impressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built to be checked
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The report is generated in a pinned Docker image, so anyone gets the same bytes.&lt;/li&gt;
&lt;li&gt;GitLab CI runs the self-check on every push, confirms the committed report is still current, and only then publishes the web page.&lt;/li&gt;
&lt;li&gt;The test files are under the BSD Zero Clause License, so any project can copy them straight into its own tests. The code is under Apache 2.0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next slice covers unit prefixes. After that, a coverage map showing which rules have files and which don't yet, and more implementations in the comparison: libOpenCOR, cellmlmanip and OpenCOR are the next ones to look at.&lt;/p&gt;

&lt;p&gt;If you work with CellML, I'd love your thoughts, and new cases are welcome as issues on the project. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://gitlab.com/dobybaxter127/cellml2-conformance" rel="noopener noreferrer"&gt;https://gitlab.com/dobybaxter127/cellml2-conformance&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Report:&lt;/strong&gt; &lt;a href="https://cellml2-conformance-7c34a8.gitlab.io" rel="noopener noreferrer"&gt;https://cellml2-conformance-7c34a8.gitlab.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>testing</category>
      <category>python</category>
      <category>science</category>
    </item>
    <item>
      <title>Access, Not Ownership: My Thoughts on Digital Currency</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Mon, 26 Oct 2026 02:29:46 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/access-not-ownership-my-thoughts-on-digital-currency-o22</link>
      <guid>https://dev.to/dobybaxter127/access-not-ownership-my-thoughts-on-digital-currency-o22</guid>
      <description>&lt;p&gt;These are my thoughts about digital currency, and about what gives money its value at all.&lt;/p&gt;

&lt;p&gt;Currencies like Bitcoin are often presented as a fix for what's wrong with money: no central bank, a fixed supply, rules that nobody can quietly change. I understand the appeal. But the more I thought about it, the less I believed the problem was where money is kept, or who controls the ledger.&lt;/p&gt;

&lt;p&gt;What I keep coming back to is an old problem in a new shape: &lt;strong&gt;owning something is not the same as being able to use it&lt;/strong&gt;, and a system can be perfectly correct and still fail the person it exists for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money only works between people
&lt;/h2&gt;

&lt;p&gt;Bitcoin has a fixed supply. There will never be more than about 21 million coins. That means they can concentrate in fewer and fewer hands.&lt;/p&gt;

&lt;p&gt;But I don't think that concentration pays off the way it seems to. Imagine I owned every bitcoin in the world. Who would I pay? There would be nobody left on the other side of the transaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Money gets its value from interaction with the system around it.&lt;/strong&gt; On its own, a balance is just a number. It means something only when another person will accept it in exchange for food, a home, a doctor's time.&lt;/p&gt;

&lt;p&gt;There's a useful distinction here between nominal wealth (the number on the screen) and real wealth (what that number can actually get you). Concentration doesn't switch the network off. The software would keep running. But it hollows out the very thing that made the number worth having.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ownership is not access
&lt;/h2&gt;

&lt;p&gt;And that isn't only true of digital currency.&lt;/p&gt;

&lt;p&gt;Money doesn't even guarantee survival now. In the United States, people can be shut out of healthcare and education. Some work a full-time job, or two, and still fall below what it takes to live. They have money, and it still isn't enough to reach the things they need.&lt;/p&gt;

&lt;p&gt;So whether a currency is digital or physical, &lt;strong&gt;the actual value comes from access to resources, not from ownership.&lt;/strong&gt; Money is a claim on the world. It is not the world itself.&lt;/p&gt;

&lt;p&gt;The economist Amartya Sen made a related point in his capability approach: what matters is what a person is actually able to do and to be, not just what they hold. A wage that can't buy medical care isn't freedom on paper. It's the absence of it, measured in numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  A system can be correct and still be cruel
&lt;/h2&gt;

&lt;p&gt;Physical money already fails people. In my view, it loses value through poor political decisions and a lack of ethical leadership, and when that happens the wider economy follows.&lt;/p&gt;

&lt;p&gt;Moving money into software doesn't remove that. &lt;strong&gt;It turns law into software and hardware.&lt;/strong&gt; And as anyone who builds software knows, a program can be perfectly valid and still fail, because the world it runs in is hostile to the people using it.&lt;/p&gt;

&lt;p&gt;Think of a hospital scheduling system with no bugs at all. Every appointment is calculated exactly as specified. But if the specification puts people with the most complex needs at the back of the queue, the system will do that flawlessly, every day. Its correctness doesn't make it kind. It makes it reliable at the wrong thing.&lt;/p&gt;

&lt;p&gt;Bitcoin's rules are enforced with remarkable consistency. That tells us the rules are being followed. It doesn't tell us whether following them leaves anyone better off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the familiar option wins
&lt;/h2&gt;

&lt;p&gt;There's another pattern I kept noticing. Mass adoption leads to popularity, just as it did for the biggest technology companies we all know.&lt;/p&gt;

&lt;p&gt;That isn't always because they are the best. There are brilliant open-source alternatives to almost everything. But bigger companies have more money for marketing, more distribution, more integrations, and a head start that makes switching feel costly. Being familiar, and endorsed by the institutions around you, counts for a lot. &lt;strong&gt;Popularity is not the same as quality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And most people spend their whole lives busy surviving. When you're exhausted, you follow what you already know, not necessarily what is better or more ethical. That isn't a personal failing. It's a reasonable response to having no spare energy.&lt;/p&gt;

&lt;p&gt;Seen that way, centralization makes sense in a society that only has the energy, time and resources to follow, not to learn and love and grow. So this is less an argument for a new financial tool and more an argument about &lt;strong&gt;people healing from the structures that have harmed them.&lt;/strong&gt; Enduring hardship for a long time isn't proof the hardship was ever necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harm is harm
&lt;/h2&gt;

&lt;p&gt;So what actually changes a system like this? I don't think it's a new technology. It's people seeing clearly, and unprecedented times can open eyes on a much larger scale.&lt;/p&gt;

&lt;p&gt;I'm transgender, and my community has been harmed. When the same structures start extending that harm to women, to men, to children, people begin to recognize it as the same thing. Harm is harm, whoever it lands on. That recognition can open eyes faster than any new technology could.&lt;/p&gt;

&lt;p&gt;I want to be careful with this, though. Wider recognition builds solidarity, and that matters. But nobody's dignity should have to wait until the harm reaches enough other people to be taken seriously. Technology can't decide what a society is willing to accept. People do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decentralized doesn't mean better
&lt;/h2&gt;

&lt;p&gt;If centralization grows out of exhaustion, it's tempting to think the opposite must be the cure. Digital currency is often presented as better simply because it's decentralized. I don't think that follows.&lt;/p&gt;

&lt;p&gt;Imagine several institutions that each independently process the funding for a patient's medical procedure. That sounds robust: no single point of control. But if every one of those institutions fails to give the patient access, it doesn't matter that the process was decentralized. &lt;strong&gt;It's the same failure, just through more steps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Engineers have words for this. Component reliability, process reliability and outcome reliability are three different things. Every institution in that chain can do its own part "correctly" while the patient goes untreated. Each piece looks fine on its own, and the whole thing still fails. And when responsibility is spread across many hands, it gets easier for each one to point at the next.&lt;/p&gt;

&lt;p&gt;A system built around the outcome looks different. It has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A clear outcome.&lt;/strong&gt; Everyone knows what success means for the person, not just for their own step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visible failure states.&lt;/strong&gt; When something goes wrong, it shows, and someone is told.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end accountability.&lt;/strong&gt; One place is answerable for the whole journey, not just a slice of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appeals and recovery routes.&lt;/strong&gt; A way back when a decision is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring of the outcome itself.&lt;/strong&gt; Measuring whether people actually got what they needed, not only whether each step completed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Decentralization is a property of an architecture. Access is an outcome. One doesn't guarantee the other. &lt;strong&gt;Neither centralization nor decentralization is the answer by itself. The outcome is.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I keep coming back to
&lt;/h2&gt;

&lt;p&gt;My work rests on one line: &lt;strong&gt;software should never fail a person in silence.&lt;/strong&gt; The more I thought about digital currency, the more I saw that the line reaches well beyond software.&lt;/p&gt;

&lt;p&gt;A ledger can be perfectly consistent. A process can be spread across many hands. A rule can be followed by every computer in the world. None of that tells you whether the person at the end got the care, the home or the chance they needed.&lt;/p&gt;

&lt;p&gt;So why do we have so many systems that are correct and still fail people at so many stages? I don't think it's because we haven't found the right technology yet. Every system carries the values of the people who shaped it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;People's values become rules and policies. Policies become software. And software becomes the daily interaction every person has with the institutions they need:&lt;/strong&gt; the bank, the clinic, the school, the benefits office.&lt;/p&gt;

&lt;p&gt;The answer isn't a new system. It's a kinder, more loving world. When care is part of the values at the start, it travels all the way down into the code. That is what makes a strong economy, healthy societies and stability that lasts.&lt;/p&gt;

&lt;p&gt;The real test isn't whether responsibility was shared out correctly. It's whether it was fulfilled.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>philosophy</category>
      <category>systemdesign</category>
      <category>reliability</category>
    </item>
    <item>
      <title>What Software Can Show Us About Neurodivergent Minds</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Mon, 19 Oct 2026 21:26:53 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/what-software-can-show-us-about-neurodivergent-minds-4b5o</link>
      <guid>https://dev.to/dobybaxter127/what-software-can-show-us-about-neurodivergent-minds-4b5o</guid>
      <description>&lt;p&gt;&lt;em&gt;People have compared minds to machines for as long as we've had machines: clockwork, telephone switchboards, and then computers. It's an old comparison, and it isn't perfect. But it's very good at one thing: making something invisible easier to see. As an autistic software engineer with ADHD, I think it shows something important about masking, and about why its cost is so easy to miss.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Every person runs their own build
&lt;/h2&gt;

&lt;p&gt;Start with the simplest version. The body is the hardware. Our internal wiring (the way our brain processes, connects and responds) is the software.&lt;/p&gt;

&lt;p&gt;The hardware is broadly similar from person to person. The software is not. Every human runs their own build, shaped by their own neurology, their own memories and their own history. That build is their internal landscape, and it's their reality.&lt;/p&gt;

&lt;p&gt;This is where I want to use a word carefully: &lt;strong&gt;ego&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't mean arrogance or self-importance. I mean the ordinary fact of being a separate person. You have your own body, your own neurological wiring and your own memories, so you have your own perception of the world. Nobody else can stand exactly where you stand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That isn't a flaw. It's how being human is supposed to work.&lt;/strong&gt; Individuality is not a bug in the system. It is the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software can't pretend
&lt;/h2&gt;

&lt;p&gt;When we build software, we build it around requirements. It's meant to do certain things, under certain conditions, in a certain way.&lt;/p&gt;

&lt;p&gt;And software can't pretend. If a function doesn't handle an input, writing "handles all inputs" in the documentation doesn't change that. If a service falls over under load, a green badge on the README doesn't stop it falling over. &lt;strong&gt;What the system does is what the system does&lt;/strong&gt;, whatever anyone says about it.&lt;/p&gt;

&lt;p&gt;The same is true for people. Pretending your wiring is something it isn't doesn't change the wiring. It only changes what other people can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's no spec sheet for a person
&lt;/h2&gt;

&lt;p&gt;Here's where software and people really differ.&lt;/p&gt;

&lt;p&gt;With software, we can look inside. We have source code, documentation, logs, tests and metrics. When something behaves unexpectedly, we can trace why.&lt;/p&gt;

&lt;p&gt;With a person, we have almost none of that. Nobody can open up someone's neural pathways and read them like a blueprint. Neuroscience can say a lot about brains in general, but it can't produce an accurate, individual map of one person's internal wiring that anyone else could check.&lt;/p&gt;

&lt;p&gt;So people fill the gap with assumptions. And the easiest assumption is to use yourself as the reference implementation: if I'd react that way, so would they. When someone behaves differently, it gets read as a defect instead of a different build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's often how bias forms: not from malice, but from missing information and an unspoken default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The autistic researcher Damian Milton described a version of this as the &lt;a href="https://doi.org/10.1080/09687599.2012.710008" rel="noopener noreferrer"&gt;double empathy problem&lt;/a&gt; (2012). When people with different ways of experiencing the world misunderstand each other, the gap runs in both directions. It isn't one side's fault for being "wrong".&lt;/p&gt;

&lt;h2&gt;
  
  
  Masking is a swallowed exception
&lt;/h2&gt;

&lt;p&gt;Masking means hiding your natural responses so you come across the way other people expect. Many autistic and ADHD people do it every day, often without fully choosing to.&lt;/p&gt;

&lt;p&gt;In software terms, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_through_the_day&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;situation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;respond_naturally&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;situation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SensoryOverload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;look_fine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;SocialMismatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;look_fine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Exhaustion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;look_fine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every call returns something that looks like success. From the outside, nothing went wrong. But the errors still happened. They were simply hidden, every single time.&lt;/p&gt;

&lt;p&gt;That's what masking feels like from the inside. You try to do something, and your system throws an error. Too much noise, too much light, a conversation that doesn't fit how you process things. You keep going, and you act as if it didn't happen. Then it happens again. And again.&lt;/p&gt;

&lt;p&gt;Anyone who has debugged a production system knows that swallowed errors rarely come alone. Something wasn't cleaned up. A resource wasn't released. The leak grows quietly, until the process runs out of memory and stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In a person, that stop is often called burnout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The research matches this picture. In &lt;a href="https://link.springer.com/article/10.1007/s10803-017-3166-5" rel="noopener noreferrer"&gt;&lt;em&gt;"Putting on My Best Normal"&lt;/em&gt;&lt;/a&gt; (Hull and colleagues, 2017), autistic adults described masking as exhausting and said it blurred their sense of who they were. A later study by the same group found that masking was &lt;a href="https://doi.org/10.1186/s13229-021-00421-1" rel="noopener noreferrer"&gt;associated with more anxiety and depression&lt;/a&gt; (Hull and colleagues, 2021), and the authors called it a risk factor for mental health problems.&lt;/p&gt;

&lt;p&gt;In a study that set out to define &lt;a href="https://doi.org/10.1089/aut.2019.0079" rel="noopener noreferrer"&gt;autistic burnout&lt;/a&gt; (Raymaker and colleagues, 2020), autistic adults described chronic exhaustion, loss of skills and serious effects on their health and quality of life. They also named doing things in an autistic way, including unmasking, among the things that helped them recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  If it were a machine, we'd see the damage
&lt;/h2&gt;

&lt;p&gt;Imagine a server that swallowed thousands of errors a day while reporting that everything was fine. Even then, we'd have ways to notice. Memory would creep up on a dashboard. Response times would drift. Eventually an alert would fire.&lt;/p&gt;

&lt;p&gt;A physical machine forced to run against its design shows its wear too. It gets hot. It makes noises. Parts wear out, and you can measure it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A person has no dashboard.&lt;/strong&gt; The only real signal is what they show and tell you. And masking switches that signal off. The very thing that causes the harm is also the thing that hides it.&lt;/p&gt;

&lt;p&gt;That's the trap. Looking fine is easy to mistake for being fine. By the time the damage becomes visible from the outside, it has usually been building for a long time on the inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the comparison stops
&lt;/h2&gt;

&lt;p&gt;Like every analogy, this one has limits, and the limits are worth naming.&lt;/p&gt;

&lt;p&gt;In a computer, hardware and software are separate. You can wipe a disk and install something new. In a brain, the software &lt;em&gt;is&lt;/em&gt; the hardware. The wiring is physical, built over a lifetime.&lt;/p&gt;

&lt;p&gt;A brain does learn and change. But learning is not the same as becoming a different kind of brain. Masking doesn't rewrite the wiring. It adds a layer on top that has to run all the time.&lt;/p&gt;

&lt;p&gt;If there's a closer software picture, it's a compatibility layer: running a program built for one system on another. It can work, but every operation costs extra, and the native system underneath never goes away. &lt;strong&gt;You can't patch your way into a different build.&lt;/strong&gt; You can only spend more and more to look like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design around the real system
&lt;/h2&gt;

&lt;p&gt;When a system keeps throwing errors, good engineers don't respond by hiding them. They read them. They ask what the system actually needs, and they change the inputs, the environment or the design to fit.&lt;/p&gt;

&lt;p&gt;People deserve at least that much care. That can look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Letting the errors show.&lt;/strong&gt; Making it safe for someone to say "this is too much" without it costing them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Believing the report.&lt;/strong&gt; If someone tells you how their system works, that is the most accurate data you will get. There's no other source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjusting the environment.&lt;/strong&gt; Quieter spaces, written instructions, more time, fewer surprises. Small changes often remove the error entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looking at outcomes, not appearances.&lt;/strong&gt; What someone delivers matters more than how "normal" they looked while delivering it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also want to be honest: not everyone can safely stop masking. For many people it is protection in places that don't feel safe. That isn't their failure, and the responsibility for changing it shouldn't sit with them alone.&lt;/p&gt;

&lt;p&gt;My whole approach to software rests on one line: &lt;strong&gt;software should never fail a person in silence.&lt;/strong&gt; I think the same is true of the world we build around people. Nobody should have to run in silent-failure mode just to be accepted.&lt;/p&gt;

&lt;p&gt;A system that hides its errors doesn't become healthy. It just becomes harder to help. That's as true for a mind as it is for a machine.&lt;/p&gt;

</description>
      <category>neurodiversity</category>
      <category>softwareengineering</category>
      <category>mentalhealth</category>
      <category>a11y</category>
    </item>
    <item>
      <title>A Status Is Not Proof: The Laptop I Never Received, and the Job I Was Handed Anyway</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Thu, 15 Oct 2026 18:27:44 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/a-status-is-not-proof-the-laptop-i-never-received-and-the-job-i-was-handed-anyway-4ha8</link>
      <guid>https://dev.to/dobybaxter127/a-status-is-not-proof-the-laptop-i-never-received-and-the-job-i-was-handed-anyway-4ha8</guid>
      <description>&lt;p&gt;In October, an online marketplace asked me to return a laptop I never received.&lt;/p&gt;

&lt;p&gt;A day later, after looking into it, they agreed I had never received it. Then they asked me to go and fetch it, and send it back myself.&lt;/p&gt;

&lt;p&gt;I said no.&lt;/p&gt;

&lt;p&gt;This is a small story compared with the ones I usually write about. But it shows, step by step, how one wrong record can travel through a system until a customer who did nothing wrong is first treated as the problem, and then asked to be the fix.&lt;/p&gt;

&lt;p&gt;I write about this kind of thing because it is my work. I review how software behaves when something goes wrong, and my standard is called No Silent Failure. In this story, every failure stayed silent until it reached me.&lt;/p&gt;

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

&lt;p&gt;In August, I ordered a refurbished laptop from a marketplace that sells devices from independent sellers. The order was set for delivery to my home.&lt;/p&gt;

&lt;p&gt;It never arrived. Later that month, the order was refunded. As far as I was concerned, that was the end of it.&lt;/p&gt;

&lt;p&gt;Two months later, in mid-October, I got an email from the marketplace's customer care team. It said the courier and the seller had both confirmed that the parcel was "successfully collected" from a service point, and that I therefore had both the laptop and the refund. It asked me to send the laptop back, and offered a 10% discount code once the seller confirmed it had arrived.&lt;/p&gt;

&lt;p&gt;The same conversation also held a prepaid return label and a checklist for shipping the laptop back, including photographing it from every angle.&lt;/p&gt;

&lt;p&gt;I had never been to a service point for this order. I had never asked anyone to collect it for me. I had never seen the laptop.&lt;/p&gt;

&lt;p&gt;I wrote back the same day. I explained that the parcel never reached me, that I hadn't collected it or authorized anyone else to, and that under UK consumer law, goods remain the seller's responsibility until they are in the buyer's hands. I asked them to get the courier's proof of collection: the date and time, the service point's address, and the name, signature or ID given at the counter.&lt;/p&gt;

&lt;p&gt;Later that day, a second agent replied. They apologized, opened an investigation, and promised an update within one business day.&lt;/p&gt;

&lt;p&gt;The next day, the update came. The parcel had never been collected. It was still sitting at the service point.&lt;/p&gt;

&lt;p&gt;And then: since the order had already been refunded, would I please go and collect it, and send it back to the seller with the prepaid label? As a goodwill gesture, here was a 10% discount code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it went wrong
&lt;/h2&gt;

&lt;p&gt;I have no view inside any of these systems, so I can only describe what they did from the outside. From the outside, I count six points where a check, a link or a handoff was missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The refund closed the money, not the parcel
&lt;/h3&gt;

&lt;p&gt;When the order was refunded in August, the money side of the order was finished. The parcel side was not. By the marketplace's own account, the laptop sat at a service point for about two months after the refund, and nothing in the process asked where it was or brought it back to the seller.&lt;/p&gt;

&lt;p&gt;A refund on an undelivered order is a moment when two records should agree: the payment and the parcel's location. Here, one record said "done", and the other was never asked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would work:&lt;/strong&gt; when an order is refunded before delivery, the same step tells the courier to return the parcel to the seller, and the case stays open until the seller confirms it has arrived.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A status became a statement about me
&lt;/h3&gt;

&lt;p&gt;Somewhere, a record said the parcel had been collected. Both the courier and the seller passed that on. My guess is that both were reading the same tracking entry, which would make two confirmations really one source repeated twice. But that is only a guess.&lt;/p&gt;

&lt;p&gt;What I do know is what happened next. "Collected" became "you have the device and the refund". A line in a tracking system turned into a statement about a person, and was sent to that person, without anyone asking where the proof was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would work:&lt;/strong&gt; before telling a customer they have something, attach the evidence: when, where, and who signed. If that evidence can't be found, ask the customer a question instead of telling them a fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The return started before anyone checked
&lt;/h3&gt;

&lt;p&gt;The message asking me to return the laptop came with everything needed to do it: a prepaid label, a link to the return instructions, and advice on photographing the device. The process had already moved on to the next step, built on a claim nobody had looked at.&lt;/p&gt;

&lt;p&gt;Automation is good at taking the next step quickly. That is exactly why steps that assume something about a person need a check in front of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would work:&lt;/strong&gt; when a return comes from a courier's status rather than from the customer's own request, ask the customer first. Create the label after they confirm, not before.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The message lived somewhere I wasn't looking
&lt;/h3&gt;

&lt;p&gt;The email didn't appear on the order in my account. My first job was working out whether it was even real, because an unexpected email saying you have something you shouldn't, with a link and an attachment, is exactly the shape of a scam. Only later did I find the conversation under a separate messages section.&lt;/p&gt;

&lt;p&gt;When a company tells someone they are holding something that isn't theirs, that message should be easy to verify and easy to answer. If it is hard to find, the person spends their effort proving the message is genuine before they can even reply to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would work:&lt;/strong&gt; attach every conversation about an order to that order, so the customer sees it in context and knows it's real.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The burden of proof landed on me
&lt;/h3&gt;

&lt;p&gt;I was asked, in effect, to account for something I had never had. That is asking someone to prove a negative, and it is very hard to do. I can't photograph a parcel I never saw.&lt;/p&gt;

&lt;p&gt;So I did the one thing I could: I asked for the proof that should have been there from the start. As soon as someone looked for it, the story changed. The parcel had never left the service point.&lt;/p&gt;

&lt;p&gt;That is the part I keep coming back to. The truth was available all along. It took a customer asking for evidence before anyone checked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would work:&lt;/strong&gt; the side making the claim holds the evidence. If a system says a customer has something, the system's own records should show it before the customer is ever contacted.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The cleanup was handed back to me
&lt;/h3&gt;

&lt;p&gt;Once it was clear I had never had the laptop, the request changed from "send it back" to "go and get it, then send it back". The parcel was with the courier. The laptop belonged to the seller. The one person in the chain who had no part in what went wrong was the person asked to put it right.&lt;/p&gt;

&lt;p&gt;It would have meant two trips, carrying a heavy laptop between them, for a parcel I never wanted to see again. It would also have meant signing for it, and that signature would have created exactly the record that started all this: proof that the parcel had been in my hands.&lt;/p&gt;

&lt;p&gt;The discount code was offered as goodwill. But a discount on a future purchase isn't payment for work, and this was work that was never mine to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would work:&lt;/strong&gt; send the physical fix to the parties who have custody. The seller and the courier can return an uncollected parcel between themselves, and the customer only needs to hear that it's done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;I want to be fair here, because one part of this worked well.&lt;/p&gt;

&lt;p&gt;The second agent read what I wrote, apologized, and opened an investigation the same day. They gave me a time frame and kept to it. When the facts came back, they told me plainly that the parcel had never been collected.&lt;/p&gt;

&lt;p&gt;That is what a good handoff looks like: a person takes the customer's account seriously, checks it, and reports back. The investigation did its job. What came after it is where the process went wrong, because the system's answer to "the customer was right" was still "so the customer should fix it".&lt;/p&gt;

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

&lt;p&gt;I said no, politely and in writing. I explained that I wouldn't collect the parcel or sign for it, and that returning it was a matter for the seller and the courier. I asked for three things: for them to arrange the return directly with the seller and the courier, for written confirmation that nothing more is needed from me, and for the note on my account saying I had collected the parcel to be corrected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of it
&lt;/h2&gt;

&lt;p&gt;Put the six points together, and a familiar shape appears:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A record is wrong.&lt;/li&gt;
&lt;li&gt;The system treats it as true and acts on it.&lt;/li&gt;
&lt;li&gt;The person on the other end has to show that it's wrong.&lt;/li&gt;
&lt;li&gt;When they do, the cost of fixing it still lands on them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this needed anyone to be careless or unkind. Everyone I dealt with was polite. The trouble lives in the joins: between the payment and the parcel, between tracking and customer care, and between "the customer was right" and "who fixes it". Each join is a place where a check could sit, and in this story none did.&lt;/p&gt;

&lt;p&gt;That is what I mean by No Silent Failure. A parcel sitting at a service point for two months is a silent failure. A wrong status is a silent failure. Neither made a sound until it reached me, and by then it arrived as an accusation.&lt;/p&gt;

&lt;h2&gt;
  
  
  My recommendations for businesses
&lt;/h2&gt;

&lt;p&gt;If you run a business that ships things, or any process where a record can be wrong, here is what I'd recommend. None of it is expensive. Most of it is about where you place a check, and who you ask first.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep the evidence with the status.&lt;/strong&gt; A status like "collected" or "delivered" should carry its proof: when, where, and who signed or showed ID. If the proof is missing, the status is a lead to look into, not a fact to act on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ask before you tell.&lt;/strong&gt; When your records and your customer's experience might disagree, start with a question: "Our records show this parcel was collected on this date. Does that match what you saw?" A question invites the truth. A statement makes the customer defend themselves before they can help you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Give every refund a matching return.&lt;/strong&gt; When you refund an order that never arrived, the same step should bring the goods back to the seller, and someone should confirm that they arrived. Money and goods should close together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pause automation before it points at a person.&lt;/strong&gt; Automate the routine steps freely. But when a step tells a customer they owe something, have something, or must do something, let a person or a clear rule confirm the facts first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch for things that sit still.&lt;/strong&gt; A parcel waiting at a service point for two months is a record that stopped moving. Set a normal time for each stage, and flag anything that stays longer, so you notice it before your customer does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep the conversation where the customer looks.&lt;/strong&gt; Put every message about an order on that order, in the customer's account. Then they can see at a glance that it's genuine, and answer it without first wondering whether it's a scam.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Send the fix to whoever holds the problem.&lt;/strong&gt; If the goods are with your courier or your seller, the fix belongs to them. The customer's part is simply to be told when it's done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make the apology match the mistake.&lt;/strong&gt; If a customer was told they had something they didn't, say so plainly, correct the record, and confirm it in writing. If you offer a goodwill gesture, make it a thank you for their patience, never a payment for more of their time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Happy customers don't need a business that never makes mistakes. They need a business that notices its own mistakes first, owns them, and puts them right without handing the work back.&lt;/p&gt;

&lt;p&gt;Here is a simple test: count what the customer had to do to fix your error. Every message they wrote, every proof they had to find and every trip they were asked to make shows how much of your process still rests on them. The goal is zero.&lt;/p&gt;

&lt;p&gt;A status is not proof. A refund is not a return. And the customer is not the courier.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>ecommerce</category>
      <category>automation</category>
      <category>ux</category>
    </item>
    <item>
      <title>I Am Not an Archivist: What Careless Automation Asks of Disabled People</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Mon, 12 Oct 2026 21:39:22 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/i-am-not-an-archivist-what-careless-automation-asks-of-disabled-people-hom</link>
      <guid>https://dev.to/dobybaxter127/i-am-not-an-archivist-what-careless-automation-asks-of-disabled-people-hom</guid>
      <description>&lt;p&gt;I'm writing this in a studio that has had no heating since early July.&lt;/p&gt;

&lt;p&gt;I have reported it by email, by phone, through a formal complaint form, and to the building's reception. This week, a phone call finally explained why nothing happened: my emails and calls had been going to a team in another city the whole time.&lt;/p&gt;

&lt;p&gt;Nobody noticed. Nobody told me. I only found out because I kept chasing.&lt;/p&gt;

&lt;p&gt;This piece isn't really about one cold studio. It's about the job that lands on you when a system fails and nobody owns the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The job nobody hired me for
&lt;/h2&gt;

&lt;p&gt;When an organization's software gets something wrong, the work of putting it right rarely stays with the organization. It moves to the person.&lt;/p&gt;

&lt;p&gt;You become the &lt;strong&gt;archivist&lt;/strong&gt;, keeping every email, screenshot, reference number and date, because one day you may need to prove what happened.&lt;/p&gt;

&lt;p&gt;You become the &lt;strong&gt;advocate&lt;/strong&gt;, writing the complaint, finding the escalation route, and quoting the organization's own policies back to it.&lt;/p&gt;

&lt;p&gt;You become the &lt;strong&gt;detective&lt;/strong&gt;, connecting the dots its systems never connect: why this charge, why this name, why this rejection.&lt;/p&gt;

&lt;p&gt;And you become the &lt;strong&gt;project manager&lt;/strong&gt;, tracking deadlines across a dozen institutions that never talk to each other.&lt;/p&gt;

&lt;p&gt;I have ADHD and autism, and I live with memory difficulties and other health conditions. I cannot do this job reliably on my own. Most people couldn't keep it up for long, and nobody should have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four failures, one shape
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The heating that was "logged on the system"
&lt;/h3&gt;

&lt;p&gt;Before that phone call, the most anyone had told me was that my issue was "logged on the system." No reference number, no name, no timeline.&lt;/p&gt;

&lt;p&gt;When I tried the out-of-hours number on the wall, it belonged to a separate estates company that looks after the building but not the flats, so they sent me back. The building's own receptionist told me she gets no replies from the company either. The direct number I was finally given shows as invalid on my phone.&lt;/p&gt;

&lt;p&gt;Every route pointed somewhere else. The only person who ever owned the problem was me.&lt;/p&gt;

&lt;h3&gt;
  
  
  The person who doesn't exist
&lt;/h3&gt;

&lt;p&gt;When I moved in, about three years ago, I had already changed my name by deed poll. The company managing the building at the time would not accept it and registered me under my old name.&lt;/p&gt;

&lt;p&gt;I complained in writing and in person. Staff at their local office told me to ignore the messages or block them.&lt;/p&gt;

&lt;p&gt;Then the building changed hands, and the new company inherited the old record. One day, two property inspectors walked into my studio while I was at my computer in my pajamas. Their records said nobody lived there.&lt;/p&gt;

&lt;p&gt;Once, when I was locked out of my flat, the agent on the phone wouldn't help me, because someone else was registered at my address under a different name.&lt;/p&gt;

&lt;p&gt;A deposit replacement scheme was about to take its once-a-year fee from me twice. After I canceled the Direct Debit, an automated email warned that I might have to pay twice, because more than one person was on my tenancy.&lt;/p&gt;

&lt;p&gt;I live alone. The "other person" is a record nobody corrected.&lt;/p&gt;

&lt;h3&gt;
  
  
  The emails I had to silence
&lt;/h3&gt;

&lt;p&gt;For about a year and a half, the new company's system sent me automated emails asking me to set up a Direct Debit, often several times a week. I asked many times for them to stop. There was no unsubscribe link.&lt;/p&gt;

&lt;p&gt;In the end, I blocked the sender so my main inbox could stay usable for the things that matter. Later, I learned that the same system also sends maintenance updates. To protect myself from the noise, I may have lost the signal.&lt;/p&gt;

&lt;p&gt;That trade should never be the user's to make.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rejection that arrived in a minute
&lt;/h3&gt;

&lt;p&gt;My passport is due for renewal, so I applied online through my home country's service. I was rejected five times. Each rejection arrived about a minute after I submitted, with identical wording and no reason.&lt;/p&gt;

&lt;p&gt;Staff on the phone said my biometric data needed updating. I made the long, expensive journey to the consulate in London, and the officer at the counter said my biometric data was valid. Nobody could explain the rejections.&lt;/p&gt;

&lt;p&gt;My best guess is that the first rejection left a flag on my record, and that flag rejects every new attempt automatically until a human clears it. But that is only a guess, because no one has looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Put those four stories side by side, and the same shape appears:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A system holds a record or makes a decision.&lt;/li&gt;
&lt;li&gt;The record is wrong, or the decision comes with no reason.&lt;/li&gt;
&lt;li&gt;Nobody inside the organization owns the error.&lt;/li&gt;
&lt;li&gt;The person is left to notice it, prove it and chase it, often while being treated as the problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these failures is complicated to fix. Route a report to the right team. Update a name when a deed poll arrives. Add an unsubscribe link. Attach a reason to a rejection.&lt;/p&gt;

&lt;p&gt;Each fix is small. What makes the failures heavy is that they never end, and they never stay inside one institution.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's a name for this
&lt;/h2&gt;

&lt;p&gt;Researchers call it &lt;strong&gt;administrative burden&lt;/strong&gt;. Donald Moynihan, Pamela Herd and Hope Harvey describe it as three kinds of cost that people carry when they deal with institutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learning costs:&lt;/strong&gt; working out what is required of you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance costs:&lt;/strong&gt; the forms, documents, calls and waiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Psychological costs:&lt;/strong&gt; the stress, frustration and stigma.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their work makes two points that match my life closely. These costs fall unevenly, and they land hardest on people with the least time, money and capacity to absorb them. And how much burden a person carries is often a matter of choice, not accident, even when nobody sits down and decides it.&lt;/p&gt;

&lt;p&gt;Disability multiplies all three costs. If your memory is unreliable, keeping records is harder. If phone calls are difficult, every escalation costs more. If you are already exhausted, each new failure arrives when you have the least to give.&lt;/p&gt;

&lt;h2&gt;
  
  
  It isn't just me
&lt;/h2&gt;

&lt;p&gt;At national scale, the same shape has caused enormous harm.&lt;/p&gt;

&lt;p&gt;In the UK, the Post Office prosecuted more than 700 subpostmasters between 1999 and 2015, using figures from its Horizon accounting system that showed money missing. In 2019, the High Court found that Horizon contained bugs, errors and defects capable of causing those shortfalls. In 2024, new laws quashed the convictions. For years, people were told the computer was right and they were wrong.&lt;/p&gt;

&lt;p&gt;In Australia, the Robodebt scheme ran from 2015 to 2019. It averaged people's annual income to calculate debts, and it raised about 1.73 billion Australian dollars in unlawful debts against more than 400,000 people. The job of proving whether a debt existed moved onto the people receiving the letters, often with income evidence from years before. The Royal Commission called it "a crude and cruel mechanism, neither fair nor legal."&lt;/p&gt;

&lt;p&gt;In the Netherlands, the tax authority wrongly accused tens of thousands of parents of childcare benefit fraud and demanded repayments that drove families into hardship. Dual nationality was used as one of its risk indicators. The government resigned over it in January 2021, and more than 33,000 people have since been officially recognized as victims.&lt;/p&gt;

&lt;p&gt;Each case has the same core. The system's record is treated as the truth, and the person has to prove otherwise, with documents nobody told them to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;The cost I feel most is not the hours. It's the vigilance.&lt;/p&gt;

&lt;p&gt;I find myself trying to predict where the next failure will come from, so I can keep the right records before I need them. When a failure does arrive, I often don't have the capacity to act on it, or the document I need is long gone.&lt;/p&gt;

&lt;p&gt;It is humiliating to explain the same thing again and again to people who have the records in front of them. It is heartbreaking to be treated as an inconvenience for asking for something basic, like heat in October.&lt;/p&gt;

&lt;p&gt;And it accumulates. Housing, banking, benefits, government, healthcare: every system I am registered with is another place where a wrong record can sit for years, waiting for me to find it.&lt;/p&gt;

&lt;p&gt;The only reason I can keep up at all is that I use AI tools to hold the threads for me: drafting the complaint, tracking the deadlines, remembering what I told whom. That is an accessibility tool, the same as glasses. But it shouldn't be the price of being treated fairly.&lt;/p&gt;

&lt;h2&gt;
  
  
  I am not an archivist
&lt;/h2&gt;

&lt;p&gt;I am not an archivist. I'm not a lawyer or a detective, either. I'm a person who needs heat, a correct name on a record, and an inbox I can use.&lt;/p&gt;

&lt;p&gt;I need to be able to live my life without preparing a defense for every ordinary interaction.&lt;/p&gt;

&lt;p&gt;When software fails, someone carries the cost. Right now, it is usually the person with the least capacity to carry it. The responsibility belongs with the people who build, buy and run these systems.&lt;/p&gt;

&lt;p&gt;Software should never fail a person in silence. And it should never leave that person to clean up after it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Moynihan, D., Herd, P. and Harvey, H. (2015). &lt;a href="https://doi.org/10.1093/jopart/muu009" rel="noopener noreferrer"&gt;Administrative Burden: Learning, Psychological, and Compliance Costs in Citizen-State Interactions&lt;/a&gt;. &lt;em&gt;Journal of Public Administration Research and Theory&lt;/em&gt;, 25(1).&lt;/li&gt;
&lt;li&gt;Herd, P. and Moynihan, D. (2018). &lt;em&gt;Administrative Burden: Policymaking by Other Means&lt;/em&gt;. Russell Sage Foundation.&lt;/li&gt;
&lt;li&gt;Bates v Post Office Ltd (No 6: Horizon Issues) [2019] EWHC 3408 (QB).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.legislation.gov.uk/ukpga/2024/14/contents" rel="noopener noreferrer"&gt;Post Office (Horizon System) Offences Act 2024&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Royal_Commission_into_the_Robodebt_Scheme" rel="noopener noreferrer"&gt;Royal Commission into the Robodebt Scheme&lt;/a&gt; (final report, July 2023).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Dutch_childcare_benefits_scandal" rel="noopener noreferrer"&gt;Dutch childcare benefits scandal&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;DutchNews (2024). &lt;a href="https://dutchnews.nl/2024/01/over-33000-families-acknowledged-as-benefit-scandal-victims" rel="noopener noreferrer"&gt;Over 33,000 families acknowledged as benefit scandal victims&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>automation</category>
      <category>ethics</category>
      <category>discuss</category>
    </item>
    <item>
      <title>A Label Is Not a Review: Following One Closed Merge Request</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Tue, 06 Oct 2026 19:44:18 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/a-label-is-not-a-review-following-one-closed-merge-request-25a2</link>
      <guid>https://dev.to/dobybaxter127/a-label-is-not-a-review-following-one-closed-merge-request-25a2</guid>
      <description>&lt;p&gt;This is a follow-up to two earlier pieces: &lt;a href="https://dev.to/dobybaxter127/why-this-time-is-different-following-the-chain-of-consequences-266d"&gt;Why This Time Is Different: Following the Chain of Consequences&lt;/a&gt; and &lt;a href="https://dev.to/dobybaxter127/learning-to-build-without-a-ladder-4bmm"&gt;Learning to Build Without a Ladder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the first one, I listed the assumptions that let the tech industry recover from past downturns. The last one was the easiest to miss: &lt;strong&gt;there was a way in.&lt;/strong&gt; Someone outside the industry could still reach a person who would let them in, and open source was one of the places where that still worked. I wrote about how automated gates on both sides are closing that door.&lt;/p&gt;

&lt;p&gt;This week I found an example that shows the door can close without any automation at all. A person closed it, with a quick look and one word. Human judgment is still one of the biggest gates in front of people who already have the skills, and a quick human check can fail in exactly the same way an automated filter does.&lt;/p&gt;

&lt;p&gt;So I want to follow this one the same way I followed the chain in that article: one link at a time, looking at what each step actually cost.&lt;/p&gt;

&lt;p&gt;I'm not naming the project or the people involved. Its maintainers do real, careful work, much of it for the public good, and nothing here is meant as a pile-on. I'll call the project SkyRelay. It's an open-source network of volunteer satellite ground stations, the kind of project I love, and one I was seriously considering contributing to.&lt;/p&gt;

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

&lt;p&gt;At the end of February 2026, a contributor opened a merge request to speed up one of SkyRelay's busiest pages. The page was slow to load, and the change went after the reasons why.&lt;/p&gt;

&lt;p&gt;A maintainer marked it as a draft. The comment said that a quick check showed signs the merge request was AI-produced, and that it was being set aside "in order to avoid spending review time for AI nonsense."&lt;/p&gt;

&lt;p&gt;To be fair to the maintainer, the comment didn't stop there. It apologized in case the guess was wrong, and asked for three things: describe the planned optimization on the related issue, make sure nothing that already works stops working, and run the pipeline checks locally first.&lt;/p&gt;

&lt;p&gt;The next day, the contributor closed the merge request. They also closed a second, much smaller fix they had opened the same day, which nobody had raised any concern about. They haven't opened another merge request in the project since.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually in the change
&lt;/h2&gt;

&lt;p&gt;I wanted to know whether the label fit, so I went through the diff file by file with my AI assistant.&lt;/p&gt;

&lt;p&gt;It was a real fix for a real problem. The page got its data from a list of satellites that made a separate database query for every single satellite, just to check one flag. The change replaced those thousands of queries with one. It also changed a large table so it loaded its rows on demand, instead of building every row into the page up front.&lt;/p&gt;

&lt;p&gt;It also had noise around it, the kind that needs tidying before anything is merged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A code style rule was switched off, instead of the new code being changed to follow it.&lt;/li&gt;
&lt;li&gt;A few package lock entries appeared that had nothing to do with the change.&lt;/li&gt;
&lt;li&gt;The pipeline failed.&lt;/li&gt;
&lt;li&gt;It changed how the page loads without that approach being agreed on the issue first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that deserves a review comment. None of it is unusual. It's what a merge request looks like when someone is working fast, on their own machine, without asking first. A reviewer could reasonably have said "this needs work before it can be merged." That is a very different sentence from "this is nonsense."&lt;/p&gt;

&lt;p&gt;Then I found something I hadn't expected. This wasn't a newcomer at all. The same person had more than twenty merged contributions across the project's organization a few years earlier, including an earlier speed-up of this very page. A quick check didn't show that. Only a closer look did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mine had signs too
&lt;/h2&gt;

&lt;p&gt;I recognized that merge request, because I've sent one like it.&lt;/p&gt;

&lt;p&gt;That same week, I was finishing a documentation contribution to GitLab. I was teaching myself Git from the command line, and I was working on three issues at the same time. Changes from my other issues kept slipping into the merge request. I couldn't see it from the terminal. It only showed up in the merge request's diff, after I had already pushed. It happened three pushes in a row.&lt;/p&gt;

&lt;p&gt;On top of that, my editor, Zed, quietly reformatted lines I had never touched whenever I saved a documentation file. I didn't see that coming at all, and I had to work out on my own where the extra changes were coming from.&lt;/p&gt;

&lt;p&gt;From a quick check, my merge request had signs too: unrelated files, formatting churn, a messy history. My reviewer looked past them. He stayed kind and waited for my final version, even though I could tell his patience was wearing thin, and I don't blame him for that. In the end I closed the merge request myself and opened a brand-new one with only the change I meant to make. It was merged a few days later and shipped in GitLab 18.10.&lt;/p&gt;

&lt;p&gt;The only real difference between my story and the SkyRelay contributor's is what the reviewer did with the signs. Mine read them as a person still learning. Theirs read them as a reason to stop reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the door stays open
&lt;/h2&gt;

&lt;p&gt;A few weeks later, I had the opposite experience, in a much harder spot.&lt;/p&gt;

&lt;p&gt;I was adding Kroki support to the GitLab Terraform provider, so teams who manage GitLab as code could switch on diagram rendering in their configuration instead of by hand. The provider is written in Go, a language I was still finding my feet in, and it took me three merge requests to get it right.&lt;/p&gt;

&lt;p&gt;The hardest part wasn't the code. My laptop at the time had 8 GB of RAM. Docker crashed it, WSL wouldn't start, and the project expects its documentation to be regenerated by a tool, not written by hand. I tried writing the docs manually anyway, and the pipeline kept failing, as it should.&lt;/p&gt;

&lt;p&gt;So I said exactly that, in the merge request. I explained my machine's limits, said I believed the implementation itself was complete, and asked whether someone could run the generation step for me or suggest another way.&lt;/p&gt;

&lt;p&gt;They did both. One maintainer explained that the docs step didn't need Docker at all, and pointed me to the command contributors are meant to run before opening a merge request. Another ran it for me, pushed the result, and suggested a remote development environment for next time. Later, when my diffs looked odd, they asked me to run Go's formatter, because my tabs and spaces were mixed up. Formatting again!&lt;/p&gt;

&lt;p&gt;With that help, I fixed a state drift problem that the acceptance tests showed, added a small safety check I noticed along the way, and traced the last pipeline failure to a mismatched file name in the CI setup rather than my change. In April 2026 it went onto the merge train and was merged. The request for that feature had been open for more than two years.&lt;/p&gt;

&lt;p&gt;I was so happy that I drew Kroki as a little crocodile riding the merge train, and shared it in the thread. Both of my Kroki drawings are in &lt;a href="https://gitlab-work-81a966.gitlab.io/contributions/kroki.html" rel="noopener noreferrer"&gt;my writeup of the contribution&lt;/a&gt;. And someone who had been waiting for the feature thanked me for my persistence and said they were looking forward to the release. I still smile about that.&lt;/p&gt;

&lt;p&gt;Nothing about that merge request was tidy. It took three attempts, it had a failing pipeline and mixed-up formatting, and I said openly that I couldn't run the project's own tooling. From a quick check, it had more signs than either of the other merge requests in this article. What it also had was transparency in both directions: I was honest about my limits, and the maintainers were honest about what I needed to do. That's all it took.&lt;/p&gt;

&lt;p&gt;That is the nature of this work. Technology moves fast, tools change, environments fail, and troubleshooting takes time, whether you started last month or twenty years ago. Most of software engineering is exactly this: troubleshooting, debugging, and trying again. A messy merge request isn't evidence that someone doesn't belong. It's evidence that they're doing the job.&lt;/p&gt;

&lt;p&gt;And a reviewer can't see who is on the other side of a merge request. It might be a newcomer, someone with years of history in the project, someone on an 8 GB laptop, or someone learning a new language or a new tool. That's exactly why the change is the right thing to look at. Nothing about what happened in SkyRelay was personal to the contributor. The label is what made it personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same chain, in one merge request
&lt;/h2&gt;

&lt;p&gt;Here is how I see the steps link together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick check stands in for a review.&lt;/strong&gt; Maintainers are flooded, and a fast filter feels like self-defense. But a quick check is a classifier, and like any classifier it has false positives. The signs it looks for (noise, unrelated files, a failing pipeline) are exactly the signs of someone learning, someone in a hurry, someone on limited hardware, or someone whose tools did something they didn't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The label lands on the person, not the change.&lt;/strong&gt; "This needs work" tells you what to fix. "AI nonsense" tells you what someone thinks of you. Only the first one gives you anything to act on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The person leaves, quietly.&lt;/strong&gt; Nobody argued and nobody complained. The merge request was simply closed, and nothing anywhere records that a working fix walked out the door. In the first article I called this Link 6: failure goes quiet. It's the same silence, just in a review thread instead of on a dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem stays, and costs more later.&lt;/strong&gt; In October 2026, seven months on, another maintainer opened a new merge request for the same page's slow loading. It's careful, well-measured work, and I'm glad it's happening. One of the causes it names is that same per-satellite query, and the part it doesn't cover yet is listed as a follow-up, measured at over nine seconds on production-sized data. The closed merge request had already offered an answer to that query. A review would have been a chance to learn from it, or to explain why it didn't fit. That's Link 7: the correction comes late, and it costs more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The way in narrows.&lt;/strong&gt; Every newcomer who reads that thread learns something before they write a single line: the first response here might be a label. I know, because I read it, and I decided not to contribute. That's assumption F failing, one person at a time.&lt;/p&gt;

&lt;p&gt;Notice that no step in this chain needed bad intentions. Each one makes sense on its own. That's exactly why it's worth writing down.&lt;/p&gt;

&lt;h2&gt;
  
  
  "No capacity" is a complete answer
&lt;/h2&gt;

&lt;p&gt;I understand where maintainers are. Many of them work unpaid, or look after far more code than any one person should. Reviewing a large change properly takes real time, and nobody is owed that time.&lt;/p&gt;

&lt;p&gt;So here is what I'd ask for instead, and it's small: if there isn't capacity to review, say that. It's honest, it's kind, and it's a complete answer. Something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We don't have review capacity for a change this size right now. Could you describe the plan on the issue first, so we can agree on the approach before you spend more time on it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That reply takes the same few seconds as a label. It keeps the person, keeps the fix, and keeps the door open. And the second half of the SkyRelay comment was already most of the way there. If those three requests had been the whole comment, I don't think this article would exist.&lt;/p&gt;

&lt;p&gt;Since then, SkyRelay has published an AI policy for contributors. It says plainly that the project can't police how a merge request was written, so it doesn't try. It allows AI-assisted contributions, as long as a person understands the change and takes full responsibility for it, and it's firm about repeated low-quality submissions, which is fair. I think that's the right answer. It moves the question from "how was this written?" to "do you understand it, and do you stand behind it?", and the second question is one a person can actually answer. A first response built on that question would have gone very differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a review gives that a label can't
&lt;/h2&gt;

&lt;p&gt;In Learning to Build Without a Ladder, I wrote that open source still works as an apprenticeship. You send something small, someone reviews it and tells you plainly what is wrong, and you improve. For a lot of us, that loop is how we became engineers after the junior roles disappeared.&lt;/p&gt;

&lt;p&gt;A label skips the one step that makes the loop work. A review tells you the truth about your code. A label tells you someone's guess about you.&lt;/p&gt;

&lt;p&gt;For people who learn the way I do, self-taught, neurodivergent, building our own apprenticeship in public, early merge requests are often noisy. That noise is what learning looks like from the outside. If the first response to it is a label, the people who most need that apprenticeship are the first ones it turns away.&lt;/p&gt;

&lt;p&gt;I also wrote that judgment is the skill that matters now: can a person understand, test, and improve what a tool produced. I still believe that, and I've realized it applies to reviewers as much as to contributors. What a reviewer can answer is whether the change works, and whether the person behind it can explain it. Whether a tool was involved is something nobody can see from a diff, and it was never the most useful question to ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I build my work around one line: software should never fail a person in silence. A merge request closed under a label is a quiet failure of exactly that kind. Nobody sees an error message. A person just stops coming back, and the problem they solved waits seven months for someone else.&lt;/p&gt;

&lt;p&gt;I've seen both sides of that door this year. When a maintainer reads the change and asks what the person needs, a feature people waited two years for gets merged. When a label comes first, a working fix walks away.&lt;/p&gt;

&lt;p&gt;Reviewing the change instead of the person isn't glamorous, and it isn't free. But it's the cheapest way I know to keep the door open, for the next contributor, and for the people who will look after these projects once today's maintainers step back.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Built to Stop Bots, Built to Stop Me: How CAPTCHAs Fail the Humans They Claim to Protect</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Mon, 05 Oct 2026 18:21:53 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/built-to-stop-bots-built-to-stop-me-how-captchas-fail-the-humans-they-claim-to-protect-e3b</link>
      <guid>https://dev.to/dobybaxter127/built-to-stop-bots-built-to-stop-me-how-captchas-fail-the-humans-they-claim-to-protect-e3b</guid>
      <description>&lt;h2&gt;
  
  
  The gate I could not pass
&lt;/h2&gt;

&lt;p&gt;I was invited to join a community. Between me and the people who invited me stood one question: are you human?&lt;/p&gt;

&lt;p&gt;I am. I could not prove it. The images moved faster than I could track, and I failed again and again until I gave up and emailed the organizer instead.&lt;/p&gt;

&lt;p&gt;I build validation systems for a living, so this stung twice. A validator built to stop software had rejected a real person. And it was far from the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What failing looks like
&lt;/h2&gt;

&lt;p&gt;As an autistic person with ADHD and sensory processing sensitivities, a challenge built on rapidly changing images is not a mild annoyance. It is a task designed around exactly the kind of processing I find hardest.&lt;/p&gt;

&lt;p&gt;I have failed every kind of CAPTCHA, honestly and repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Distorted text.&lt;/strong&gt; Sometimes I simply cannot tell what the word is meant to be. No amount of staring helps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image grids.&lt;/strong&gt; I select every traffic light I can see, and it is never what the system wants. Does a light cut off at the tile edge count? Sometimes yes, sometimes no, and nothing tells you the rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotating and moving puzzles.&lt;/strong&gt; Hard to track, and sometimes rejected even when my answer is correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invisible scoring.&lt;/strong&gt; When it decides I look suspicious, it sends me straight back to the puzzles above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CAPTCHAs have stopped me more times than I can count. They have never once stopped a bot on my behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Machines now pass more easily than people
&lt;/h2&gt;

&lt;p&gt;In 2023, UC Irvine researchers had 1,400 people solve 14,000 real CAPTCHAs (&lt;a href="https://www.usenix.org/conference/usenixsecurity23/presentation/searles" rel="noopener noreferrer"&gt;USENIX Security 2023&lt;/a&gt;). Bots beat humans on both speed and accuracy across many types. On distorted text, bots were close to 100% accurate in under a second, while humans managed 50% to 84% and took up to 15 seconds (&lt;a href="https://techxplore.com/news/2023-08-bots-captcha-humans.html" rel="noopener noreferrer"&gt;coverage&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In 2024, ETH Zurich researchers solved 100% of Google's reCAPTCHAv2 image challenges with off-the-shelf object-detection models (&lt;a href="https://arxiv.org/abs/2409.08831" rel="noopener noreferrer"&gt;Breaking reCAPTCHAv2&lt;/a&gt;). What really decided the outcome was cookies and browsing history, not the puzzle.&lt;/p&gt;

&lt;p&gt;Attackers who do not want to build a solver can simply pay CAPTCHA farms, where low-paid workers solve challenges at scale. So the bot gets in. The paid stranger gets in. The person who just wants to join may not.&lt;/p&gt;

&lt;h2&gt;
  
  
  A validator with a broken oracle
&lt;/h2&gt;

&lt;p&gt;Any validator makes two kinds of mistake: letting bad input through, and rejecting good input. A CAPTCHA now does both badly. It is weak against bots and strict against people who see, move, or process differently, including blind and low-vision users, people with motor disabilities, and neurodivergent people like me.&lt;/p&gt;

&lt;p&gt;The W3C has warned about this since 2005 in &lt;a href="https://www.w3.org/TR/turingtest/" rel="noopener noreferrer"&gt;Inaccessibility of CAPTCHA&lt;/a&gt;, and &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication.html" rel="noopener noreferrer"&gt;WCAG 2.2 criterion 3.3.8&lt;/a&gt; says authentication should not depend on a puzzle without an alternative. A timed, moving puzzle is far from that spirit.&lt;/p&gt;

&lt;p&gt;That is why it feels dehumanizing. The system is asked to recognize a human, fails to recognize a real one, and then tells them they are the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better gates
&lt;/h2&gt;

&lt;p&gt;The fix is to stop asking humans to out-perform machines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; per IP, device, and time window, since most abuse is about volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof of work&lt;/strong&gt; that asks the browser, not the person, to spend computation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email or magic-link verification&lt;/strong&gt; instead of perception tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honeypot fields&lt;/strong&gt; that humans never see and naive bots fill in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human fallback path&lt;/strong&gt;, a clear "I can't complete this" route to a real person.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bolt-on fixes like hCaptcha's &lt;a href="https://www.hcaptcha.com/learning/accessibility/hcaptcha-accessibility/" rel="noopener noreferrer"&gt;accessibility cookie&lt;/a&gt; help some people, but they push extra work onto the person already locked out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question the test
&lt;/h2&gt;

&lt;p&gt;I did get through in the end, by emailing a person. That is the lesson. A bot can now prove it is human more easily than I can. That should not make anyone question my humanity. It should make us question the test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Searles et al., &lt;a href="https://www.usenix.org/conference/usenixsecurity23/presentation/searles" rel="noopener noreferrer"&gt;An Empirical Study &amp;amp; Evaluation of Modern CAPTCHAs&lt;/a&gt;, USENIX Security 2023&lt;/li&gt;
&lt;li&gt;Tech Xplore, &lt;a href="https://techxplore.com/news/2023-08-bots-captcha-humans.html" rel="noopener noreferrer"&gt;Bots are better at CAPTCHA than humans, researchers find&lt;/a&gt;, August 2023&lt;/li&gt;
&lt;li&gt;Plesner, Vontobel and Wattenhofer, &lt;a href="https://arxiv.org/abs/2409.08831" rel="noopener noreferrer"&gt;Breaking reCAPTCHAv2&lt;/a&gt;, ETH Zurich, 2024&lt;/li&gt;
&lt;li&gt;W3C, &lt;a href="https://www.w3.org/TR/turingtest/" rel="noopener noreferrer"&gt;Inaccessibility of CAPTCHA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;W3C, &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication.html" rel="noopener noreferrer"&gt;Understanding SC 3.3.8: Accessible Authentication (Minimum)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;hCaptcha, &lt;a href="https://www.hcaptcha.com/learning/accessibility/hcaptcha-accessibility/" rel="noopener noreferrer"&gt;Accessibility: Accommodation Options and Support Path&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>security</category>
      <category>webdev</category>
      <category>neurodiversity</category>
    </item>
    <item>
      <title>Thank You, Every Time: What the Pyxel Team at ESA Taught Me That No Employer Ever Did</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Thu, 01 Oct 2026 23:24:22 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/thank-you-every-time-what-the-pyxel-team-at-esa-taught-me-that-no-employer-ever-did-c8c</link>
      <guid>https://dev.to/dobybaxter127/thank-you-every-time-what-the-pyxel-team-at-esa-taught-me-that-no-employer-ever-did-c8c</guid>
      <description>&lt;p&gt;Every time I sent the Pyxel team something, they said thank you.&lt;/p&gt;

&lt;p&gt;Not once at the end, as a formality. Every time. A fix, a new version of the tool I was building, an answer to a question: thank you. I want to write about that, because in all my years of working and applying for work, no employer ever thanked me for my work, and I didn't know how much I needed it until it happened.&lt;/p&gt;

&lt;p&gt;This isn't a technical post. I've written about &lt;a href="https://dev.to/dobybaxter127/learning-to-build-without-a-ladder-4bmm"&gt;what I built for Pyxel&lt;/a&gt; before. This one is about how it felt to build it, and what it taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://esa.gitlab.io/pyxel/doc" rel="noopener noreferrer"&gt;Pyxel&lt;/a&gt; is an open-source detector simulation framework developed by a team at the European Space Agency. It's used by ESA, the European Southern Observatory and institutes around the world. I started the way anyone can start on an open-source project: I contributed on GitLab, and then I emailed the maintainer.&lt;/p&gt;

&lt;p&gt;I expected a short reply, maybe. What I got was an invitation to meet the whole team online. I introduced myself, and then each of them introduced themselves to me.&lt;/p&gt;

&lt;p&gt;That detail matters to me more than almost anything else in this story. It wasn't an interview, where one side asks the questions and the other side performs. It was a team meeting a new person and treating that person as someone worth knowing.&lt;/p&gt;

&lt;p&gt;They were grateful from the very first conversation. I hadn't proven anything yet. They were simply glad someone wanted to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  What working together looked like
&lt;/h2&gt;

&lt;p&gt;I was building &lt;a href="https://pyxel-config-lab-ede25c.gitlab.io/" rel="noopener noreferrer"&gt;Pyxel Config Lab&lt;/a&gt;, a tool that helps people write and check Pyxel configurations without fighting the file format. The team wrote me extensive feedback on it. Not a quick "looks good", and not a list of everything wrong handed down from above. Careful, detailed notes from people who had clearly sat down with the tool and used it.&lt;/p&gt;

&lt;p&gt;Good feedback is a form of care. It says: I took your work seriously enough to spend my time on it.&lt;/p&gt;

&lt;p&gt;So I kept working. I sent the next version, read the next round of notes, and improved it again. Nobody told me the iterations were a waste of time. Iteration was the point. That's how good software gets made, and they knew it.&lt;/p&gt;

&lt;p&gt;And every time, they thanked me.&lt;/p&gt;

&lt;p&gt;I was included. I was part of their work, not a ticket to be closed. They were respectful to me as a person and as an engineer, and open to ideas that didn't start with them.&lt;/p&gt;

&lt;p&gt;The tool shipped alongside the &lt;a href="https://esa.gitlab.io/pyxel/post/2025-10-30_version_2_14/" rel="noopener noreferrer"&gt;official Pyxel 2.14 release&lt;/a&gt;. It was later named in the team's peer-reviewed &lt;a href="https://doi.org/10.1117/12.3104656" rel="noopener noreferrer"&gt;Pyxel 3.0 paper&lt;/a&gt; (Proc. SPIE 14157), which records that I led the development of Pyxel's new GUI. The maintainer also wrote me a reference, with a line in it I treasure:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Doby Baxter joined the Pyxel project on his own initiative and quickly became a key contributor."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are the achievements, and I'm proud of them. But they aren't what I treasure most. What I treasure is how it felt along the way. To this day, it's the best collaboration I've ever had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other side
&lt;/h2&gt;

&lt;p&gt;Here is the comparison, as plainly as I can put it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At Pyxel&lt;/strong&gt;, I emailed one person and was invited to meet the whole team. &lt;strong&gt;In job applications&lt;/strong&gt;, I mostly got automated rejections, or no reply at all, often before a person had read a word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At Pyxel&lt;/strong&gt;, the team introduced themselves to me. &lt;strong&gt;In hiring&lt;/strong&gt;, I was the only one expected to explain myself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At Pyxel&lt;/strong&gt;, my work got detailed feedback and a thank you. &lt;strong&gt;From employers&lt;/strong&gt;, it got silence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At Pyxel&lt;/strong&gt;, iteration was welcome. &lt;strong&gt;With employers&lt;/strong&gt;, anything beyond exactly what I'd been told to do wasn't seen as care or initiative. It was a problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At Pyxel&lt;/strong&gt;, I was included. &lt;strong&gt;Elsewhere&lt;/strong&gt;, I was replaceable, and so was everyone around me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At Pyxel&lt;/strong&gt;, I was valued for what I brought. &lt;strong&gt;Elsewhere&lt;/strong&gt;, some employers were interested in my skills right up until they reached the part-time hours and adjustments I need. One role ended because adjustments that had been agreed were never put in place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I could bring the same passion I brought to Pyxel to an employer, and it would be dismissed.&lt;/p&gt;

&lt;p&gt;Same person. Same passion. Same standard of work. Two completely different outcomes. The difference was never me. It was whether the people on the other side were open-minded or closed-minded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The opposite of competition
&lt;/h2&gt;

&lt;p&gt;Corporate work taught me that work is a competition: for roles, for credit, for being seen as the one who did it.&lt;/p&gt;

&lt;p&gt;The Pyxel team taught me the opposite. Respect. Mutual care. Collaboration in the real sense of the word, where everyone's work makes everyone else's better, and people say so out loud.&lt;/p&gt;

&lt;p&gt;None of that cost them anything. Saying thank you is free. Introducing yourself is free. Reading someone's work carefully costs an hour. And it changed everything for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's the reason I kept going
&lt;/h2&gt;

&lt;p&gt;I've stopped applying for jobs. That decision is made, and I'm at peace with it.&lt;/p&gt;

&lt;p&gt;But giving up on employment and giving up on engineering are two very different things, and the Pyxel team is the difference between them. Without them, every signal I'd received would have said the same thing: your work doesn't matter to anyone. With them, I have proof of the opposite. Good people exist. Open-minded people exist. People who will appreciate careful work, say so, and treat the person who made it with respect.&lt;/p&gt;

&lt;p&gt;I don't believe that because I hope it. I believe it because I've lived it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I carry into my own work
&lt;/h2&gt;

&lt;p&gt;That belief is what my business is built on. I now work for myself, doing reliability reviews and engineering for small businesses, and the clients I hope to work with are people like the Pyxel team: open, respectful, and glad when something is done with care.&lt;/p&gt;

&lt;p&gt;It goes both ways. I try to work the way they worked with me. Thank people for their time. Give feedback that shows I actually read the thing. Explain, and never talk down. Treat someone's system, and someone's work, as theirs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you
&lt;/h2&gt;

&lt;p&gt;So, to the Pyxel team at ESA: thank you. For the welcome, the feedback, the patience, and every single thank you. You didn't just help me build a tool. You showed me what working together is supposed to feel like, and you gave me a reason to keep going. I hope everyone gets to work with people like you at least once.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>career</category>
      <category>community</category>
      <category>space</category>
    </item>
    <item>
      <title>Why This Time Is Different: Following the Chain of Consequences</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Tue, 29 Sep 2026 23:49:34 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/why-this-time-is-different-following-the-chain-of-consequences-266d</link>
      <guid>https://dev.to/dobybaxter127/why-this-time-is-different-following-the-chain-of-consequences-266d</guid>
      <description>&lt;p&gt;I have been thinking a lot about where software engineering is heading, and I keep arriving at the same conclusion: this time is different.&lt;/p&gt;

&lt;p&gt;I know how that sounds. Whenever people worry about the tech industry, the usual reassurance is that we have been here before. There was the dot-com crash, then the financial crisis, and both times the industry recovered and came back bigger.&lt;/p&gt;

&lt;p&gt;I do not think that reassurance holds anymore, and I want to show why rather than just say it. When I want to rely on a system, I list what it depends on and check whether each dependency still holds. So that is how I approach this. First I describe how I see past recoveries working, then I follow what is happening now one link at a time, and look at which of those dependencies are still in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the old recovery loop worked
&lt;/h2&gt;

&lt;p&gt;The way I see it, past downturns followed a recognizable sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Demand fell, or money got expensive.&lt;/li&gt;
&lt;li&gt;Companies reduced staff to match.&lt;/li&gt;
&lt;li&gt;The people who left kept their skills, and many stayed close to the industry.&lt;/li&gt;
&lt;li&gt;Demand returned.&lt;/li&gt;
&lt;li&gt;Companies rehired, and because the work still needed juniors, juniors were trained again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It was painful, but it was a loop, and loops correct themselves. For this one to close, four things had to be true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A. The cut was about demand, not about the work.&lt;/strong&gt; The jobs were paused, not retired. When demand came back, the roles came back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B. Knowledge survived the cut.&lt;/strong&gt; It lived in people who could be rehired, or who could teach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C. The training pipeline restarted.&lt;/strong&gt; Seniors taught juniors on real work, because real work still had to be done by people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D. Failure was loud.&lt;/strong&gt; When a company cut too deep, something visibly stopped working, and that signal reached the people who make budget decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Underneath all four sits a quieter fifth assumption:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E. Change moved at human speed.&lt;/strong&gt; Systems changed about as fast as people could understand the changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And a sixth, about the people outside the loop, which is easy to miss because it held for so long:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;F. There was a way in.&lt;/strong&gt; Someone outside the industry could still reach a person who would let them in: an entry-level role, an education that led to one, a maintainer with time to mentor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the chain. Each link below is a decision or a consequence, and each one touches at least one of these assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 1: The cut is framed as substitution, not demand
&lt;/h2&gt;

&lt;p&gt;Some companies now explain workforce reductions with a new reason. Not "demand has fallen", but "this work will be done by AI."&lt;/p&gt;

&lt;p&gt;That changes what the cut means. A demand cut carries an implicit plan to rehire when demand returns. A substitution cut carries the opposite plan: the role is considered finished. There is no condition under which it comes back, because the reasoning never depended on demand in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; the trigger that used to reopen roles is gone. Assumption A no longer holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 2: Substitution starts with the entry-level work
&lt;/h2&gt;

&lt;p&gt;Which tasks are easiest to hand to an AI tool? Well-defined ones. Boilerplate, small fixes, writing tests, updating documentation, triaging tickets, first drafts of configuration.&lt;/p&gt;

&lt;p&gt;Those are exactly the tasks juniors used to learn on. They were never only output. They were the training ground: small enough to be safe, real enough to teach how the whole system fits together.&lt;/p&gt;

&lt;p&gt;Nobody has to decide to stop training people for training to stop. It is enough to automate the work that the training happened inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; the apprenticeship layer thins out as a side effect. Assumption C no longer holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 3: Knowledge leaves, and it was never written down
&lt;/h2&gt;

&lt;p&gt;Every long-lived system carries knowledge that is not in the code. Someone knows that an odd-looking setting is deliberate. Someone remembers the incident that explains why a particular validation exists. Someone knows which customer workflow cannot tolerate a certain kind of change.&lt;/p&gt;

&lt;p&gt;That knowledge is part of the system. It just is not stored anywhere a tool can read.&lt;/p&gt;

&lt;p&gt;In a demand cut, that knowledge sat in people who could be hired back. In a substitution cut, fewer seniors remain, no juniors are absorbing what they know, and the tools that replaced them can only read what was written. What was never written is simply gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; the system now encodes more about itself, in behavior nobody can explain, than its operators know about it. Assumption B weakens sharply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 4: The rate of change goes up
&lt;/h2&gt;

&lt;p&gt;Automation is very good at producing change. An agent can open more pull requests, generate more configuration and apply more fixes in a day than a small team could in a week.&lt;/p&gt;

&lt;p&gt;So at the same moment that understanding of the system is shrinking (Links 2 and 3), the amount of change flowing into it is growing. The ratio of changes to people who understand those changes moves in the wrong direction from both ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; systems start changing faster than anyone can follow. Assumption E no longer holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 5: Errors travel instead of stopping
&lt;/h2&gt;

&lt;p&gt;This is the technical heart of the chain.&lt;/p&gt;

&lt;p&gt;When traditional software fails, it usually fails in a recognizable way. It throws an exception, crashes, times out, or returns something malformed that a type check or a schema rejects. The failure stops at a boundary.&lt;/p&gt;

&lt;p&gt;A language model fails differently. Its wrong answers are well-formed, confident and plausible. They have the right shape, so any check that only looks at shape lets them through.&lt;/p&gt;

&lt;p&gt;Now put several automated steps in a row. Step one makes a small contextual mistake. Step two treats step one's output as fact, because it looks like fact. Step three builds on step two. By the end, a misunderstanding has become state: records written, decisions taken, configuration applied.&lt;/p&gt;

&lt;p&gt;Distributed systems have always had cascading failures. What is new is a participant whose mistakes look exactly like its successes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; a small error in context becomes a system-wide error in state, and nothing along the way objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 6: Failure goes quiet
&lt;/h2&gt;

&lt;p&gt;Put Links 3, 4 and 5 together.&lt;/p&gt;

&lt;p&gt;Fewer people know what correct looks like. Change arrives faster than they can review it. And the component producing the errors makes them look correct.&lt;/p&gt;

&lt;p&gt;The result is not an outage. Outages are loud, and loud is useful: an outage is a signal that travels all the way up to whoever controls the budget. The result is drift. A record that is slightly wrong. A customer who is quietly mis-served and leaves without saying why. A report that is off by an amount nobody thinks to check.&lt;/p&gt;

&lt;p&gt;From the outside, the organization looks fine. The dashboards are green. So the conclusion drawn is that the cuts worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; the signal that used to tell a company it had cut too deep never arrives. Assumption D no longer holds.&lt;/p&gt;

&lt;p&gt;To me, this is the most important link. The old recovery loop never depended on companies being wise. It depended on failure being visible. Take away visibility and the loop has nothing to respond to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 7: The correction comes late, and it costs more
&lt;/h2&gt;

&lt;p&gt;Drift does surface eventually. A regulator asks a question, an audit finds numbers that do not reconcile, or a customer raises something that cannot be ignored.&lt;/p&gt;

&lt;p&gt;At that point the organization needs people who understand the system. The ones who did have moved on. The juniors who would have become those people were never hired, and it takes years to grow an engineer who can reason about a system end to end. That capability cannot be bought back quickly, and what can be brought in from outside comes at a premium.&lt;/p&gt;

&lt;p&gt;So the correction is delayed twice: once by the time it takes a silent failure to become visible, and again by the time it takes to rebuild the skills to fix it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; the recovery, when it comes, is slower and more expensive than the savings that started the chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link 8: Automation answers automation
&lt;/h2&gt;

&lt;p&gt;Go back to Links 1 and 2, and look at them from outside the industry.&lt;/p&gt;

&lt;p&gt;The roles people used to enter through are being retired. Graduates finish degrees that no longer lead anywhere in particular. People who were laid off are looking at the same shrinking list. The old promise, that a good education and real skills lead to a stable career, no longer holds for a great many people who did everything it asked.&lt;/p&gt;

&lt;p&gt;And the advice they all hear is the same: apply widely, and build experience in open source.&lt;/p&gt;

&lt;p&gt;Meanwhile, the gates themselves have been automated. Applications are filtered by software before a person sees them. Many public services decide by form logic, with nobody to explain a rejection. As a disabled person I have met many gates like that, from government forms to CAPTCHAs, and I know how much each attempt costs when there is no person to reach.&lt;/p&gt;

&lt;p&gt;So people respond in the only way that scales: with automation of their own. Applications are generated and sent everywhere. Pull requests are generated and opened everywhere. Most of this is not malicious. It is a reasonable response to a gate nobody can talk to.&lt;/p&gt;

&lt;p&gt;Then the other side responds in kind. One HR person, now doing the work of a whole department, cannot read a thousand generated applications, so the screening becomes more automated. A volunteer maintainer, often doing the work of ten people for free, cannot review a flood of generated pull requests by hand, so review gets AI help too.&lt;/p&gt;

&lt;p&gt;Several open source projects now say outright that they cannot tell how a contribution was written. What they ask for instead is a person who understands the change and stands behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pyopensci.org/blog/pycon-us-2026-generative-ai-open-source.html" rel="noopener noreferrer"&gt;pyOpenSci's write-up of PyCon US 2026&lt;/a&gt; describes the same pressure from the maintainers' side: reviewer burden, burnout, and contributor pipelines under strain. Amanda Casari's closing keynote, citing Charity Majors, put it in one line: "Disposable code is here to stay, but durable code is what runs the world."&lt;/p&gt;

&lt;p&gt;I agree with both halves. Disposable code is not the problem. The problem is that durable code needs people who understand it, and this loop wears out the people who do while making it harder for new ones to get in.&lt;/p&gt;

&lt;p&gt;Each step makes sense on its own. Together they form a loop that nobody chose and nobody wins: the more each side automates, the less either side reaches a person.&lt;/p&gt;

&lt;p&gt;What is new here is the timing. In earlier downturns, someone shut out of one industry usually had another way in: a different sector that was hiring, a qualification that still led to work, an entry-level role somewhere else. This time the same pressures are arriving in every sector and in every country at once, so there is nowhere obvious to wait it out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consequence:&lt;/strong&gt; the doors that used to let new people in now sit behind automation on both sides, and the human contact that made them work is disappearing. Assumption F no longer holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chain at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assumption the old loop relied on&lt;/th&gt;
&lt;th&gt;Past downturns&lt;/th&gt;
&lt;th&gt;Now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A. Cuts follow demand, so roles return&lt;/td&gt;
&lt;td&gt;Largely held&lt;/td&gt;
&lt;td&gt;Roles framed as retired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B. Knowledge survives in people&lt;/td&gt;
&lt;td&gt;Largely held&lt;/td&gt;
&lt;td&gt;Leaves, and tools cannot read what was never written&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C. Training restarts&lt;/td&gt;
&lt;td&gt;Held, after a dip&lt;/td&gt;
&lt;td&gt;The training ground itself is automated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D. Failure is loud&lt;/td&gt;
&lt;td&gt;Largely held&lt;/td&gt;
&lt;td&gt;Plausible errors produce drift, not outages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E. Change moves at human speed&lt;/td&gt;
&lt;td&gt;Held&lt;/td&gt;
&lt;td&gt;Change outpaces understanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F. There is a way in that reaches a person&lt;/td&gt;
&lt;td&gt;Largely held&lt;/td&gt;
&lt;td&gt;Gates automated on both sides&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So when someone tells me "we have been here before," this is my answer. In earlier downturns, the thing that shrank was demand. The capability to do the work stayed intact, waiting for demand to return. This time the capability itself is being taken apart, and the mechanism that would normally tell us to rebuild it is going quiet at the same time. Even the way back in, for the people who could help rebuild it, now runs through automated gates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What would prove me wrong
&lt;/h2&gt;

&lt;p&gt;This is my opinion, and I would rather name where it could be wrong than pretend it is certain. Here is where I could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If organizations write down their implicit knowledge, Link 3 weakens considerably, and AI tools get the context they currently lack.&lt;/li&gt;
&lt;li&gt;If companies deliberately keep hiring juniors and give them meaningful work, Link 2 no longer follows from Link 1.&lt;/li&gt;
&lt;li&gt;If failures in AI-assisted systems are made visible by design, Link 6 does not happen, and the old feedback loop returns.&lt;/li&gt;
&lt;li&gt;If automated gates explain themselves, with a reason a person can act on and a route to a human when the gate cannot help, Link 8 loses most of its pull. People mostly turn to automation when nothing answers them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice something about that list. None of it depends on AI getting smarter. Every item is a decision about the system around the AI. That is the part I find genuinely hopeful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where engineers can repair the chain
&lt;/h2&gt;

&lt;p&gt;Every link above is something engineering already knows how to address. None of it is glamorous, and all of it matters more now than it did before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep people learning inside the loop (Link 2).&lt;/strong&gt; Reviewing AI output is real engineering work, and it teaches. A junior who reviews each change an agent proposes, with a senior reviewing that review, can learn a system faster than one who spent a year writing boilerplate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write down the why (Link 3).&lt;/strong&gt; Decision records, runbooks, and comments that explain intent rather than restate the code. Written context outlives the person who held it, and it has a second benefit: it is exactly the context an AI tool needs to stop making suggestions that are locally reasonable and globally wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put deterministic boundaries around probabilistic parts (Links 4 and 5).&lt;/strong&gt; Validate meaning, not only shape. Run preflight checks before a change is applied. Keep human approval for consequential actions: production infrastructure, financial records, anything a person relies on. The architecture should always know the difference between "the AI suggested this" and "the system has verified this is safe to do." Those are two completely different statements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make failure loud again (Link 6).&lt;/strong&gt; Every failure should produce a signal a person can read and act on: a clear message, a logged reason, an alert that reaches someone. This is the standard I build my own work around. I call it No Silent Failure, and it comes down to one line: software should never fail a person in silence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep a person reachable (Link 8).&lt;/strong&gt; A rejection with no reason is a silent failure too, just one that lands on a person. Every automated gate, whether it screens applications, forms or pull requests, should say why it said no and offer a route to a human when it cannot help. Open source projects are already showing one way to do this: a published contribution policy that says clearly what is welcome tells people how to reach a reviewer, instead of leaving them to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;AI is often described as a force arriving from outside and changing everything it touches. I think it is closer to a mirror with an amplifier attached. Give it a well-run organization, with good tests, written context, sensible permissions and careful review, and it amplifies all of that. Give it an organization reducing costs faster than it can understand its own systems, and it amplifies that instead.&lt;/p&gt;

&lt;p&gt;This time is different, but not because the technology is so much smarter. It is different because the loop that used to correct our mistakes depended on four things: people who understood the systems, a pipeline that produced more of them, failures loud enough to notice, and a way in that reached a person. The current path is removing all four at once.&lt;/p&gt;

&lt;p&gt;The good news is that each of those is something we know how to build. It is careful, unglamorous work, and I think it is the most important engineering work of the next few years.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Odoo 19 Quietly Drops Your _sql_constraints: How to Find Them Before Your Data Does</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Sun, 27 Sep 2026 14:00:57 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/odoo-19-quietly-drops-your-sqlconstraints-how-to-find-them-before-your-data-does-3jeb</link>
      <guid>https://dev.to/dobybaxter127/odoo-19-quietly-drops-your-sqlconstraints-how-to-find-them-before-your-data-does-3jeb</guid>
      <description>&lt;p&gt;Odoo 19 changed how a model declares a database constraint, and it made the change quietly. A module that still uses &lt;code&gt;_sql_constraints&lt;/code&gt; keeps installing, keeps updating and keeps passing its tests. The only sign is one warning in the server log, and the rule the module declared is no longer enforced by anything.&lt;/p&gt;

&lt;p&gt;That is the kind of failure I care most about: nothing breaks on the day, and the cost turns up months later as duplicate records nobody can explain. This post walks through what the 19.0 source does with the old attribute, what happens to a constraint that already existed before the upgrade, and how to check your own modules and database.&lt;/p&gt;

&lt;p&gt;This article was first published &lt;a href="https://dobybaxter127.gitlab.io/articles/odoo-19-drops-your-sql-constraints/" rel="noopener noreferrer"&gt;on my site&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;Up to Odoo 18, a model declared its SQL constraints as a list of tuples:&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;class&lt;/span&gt; &lt;span class="nc"&gt;TradeAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade.account&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;_sql_constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reference_unique&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;UNIQUE(reference)&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;Trade account references must be unique.&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;In 18.0 there is no other way to write it. Odoo's own 18.0 core declares &lt;code&gt;_sql_constraints&lt;/code&gt; in 174 files and has no &lt;code&gt;models.Constraint&lt;/code&gt; anywhere, so nearly every custom module written before 19 uses the old form.&lt;/p&gt;

&lt;p&gt;Odoo 19 replaces the list with declarations on the class itself: &lt;code&gt;models.Constraint&lt;/code&gt;, &lt;code&gt;models.Index&lt;/code&gt; and &lt;code&gt;models.UniqueIndex&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TradeAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trade.account&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;_reference_unique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNIQUE(reference)&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;Trade account references must be unique.&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;h2&gt;
  
  
  What Odoo 19 does with the old attribute
&lt;/h2&gt;

&lt;p&gt;When 19.0 builds a model class and finds &lt;code&gt;_sql_constraints&lt;/code&gt; on it, it logs this and carries on (&lt;code&gt;odoo/orm/model_classes.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model attribute '_sql_constraints' is no longer supported, please define models.Constraint on the model.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not convert the list. Constraints are now applied from the model's table objects, which are the &lt;code&gt;models.Constraint&lt;/code&gt; and &lt;code&gt;models.Index&lt;/code&gt; declarations, and nothing reads &lt;code&gt;_sql_constraints&lt;/code&gt; any more. For a module written for 19 that still uses the old form, the constraint is simply never created.&lt;/p&gt;

&lt;h2&gt;
  
  
  On an upgraded database, the update removes it
&lt;/h2&gt;

&lt;p&gt;It is reasonable to hope that a database upgraded from 18 keeps the constraints it already had. Reading the 19.0 source, a module update removes them instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At the end of loading, Odoo reflects each model's table objects into &lt;code&gt;ir.model.constraint&lt;/code&gt; and marks their external ids as loaded (&lt;code&gt;_reflect_model&lt;/code&gt; in &lt;code&gt;ir_model.py&lt;/code&gt;). A constraint declared only in &lt;code&gt;_sql_constraints&lt;/code&gt; is not a table object, so its external id is never marked.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ir.model.data._process_end&lt;/code&gt; then deletes whatever the updated module no longer provides, and a constraint whose external id was not loaded counts as no longer provided.&lt;/li&gt;
&lt;li&gt;Deleting an &lt;code&gt;ir.model.constraint&lt;/code&gt; record runs &lt;code&gt;ALTER TABLE ... DROP CONSTRAINT&lt;/code&gt; and logs &lt;code&gt;Dropped CONSTRAINT&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the update that brings a custom module onto 19 is the update that drops its old constraints, and the log records it as routine housekeeping. I traced this through the 19.0 source rather than on a live upgrade, so check your own database with the query further down rather than relying on my reading or on the hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  It happens in real projects
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openspp.org/en/" rel="noopener noreferrer"&gt;OpenSPP&lt;/a&gt;, a social-protection platform built on Odoo, runs on 19. A May 2026 refactor migrated three of its &lt;code&gt;_sql_constraints&lt;/code&gt; declarations to &lt;code&gt;models.Constraint&lt;/code&gt;. Another, the rule that each change request type has only one document rule per reason, stayed in the old form until an August 2026 fix (#395), and until then duplicates could be saved. The project now runs a lint check of its own for Odoo 19 patterns. None of their tests had failed, because nothing fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to find them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  In the code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"_sql_constraints"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.py addons/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any hit in a module whose &lt;code&gt;__manifest__.py&lt;/code&gt; version starts with &lt;code&gt;19.&lt;/code&gt; is a constraint that does not exist. In a 16, 17 or 18 module it is correct, and changing it there would break the module.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the log
&lt;/h3&gt;

&lt;p&gt;Restart the server and search the log for &lt;code&gt;is no longer supported&lt;/code&gt;. There is one line per model still using the old form. After a module update, also search for &lt;code&gt;Dropped CONSTRAINT&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the database
&lt;/h3&gt;

&lt;p&gt;List what PostgreSQL actually enforces on the table, and compare it with what the module declares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;conname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pg_get_constraintdef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_constraint&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;conrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'trade_account'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;contype&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'u'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a uniqueness rule is missing, find out whether duplicates have already got in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;trade_account&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With a checker
&lt;/h3&gt;

&lt;p&gt;This is one of the rules in &lt;a href="https://gitlab.com/dobybaxter127/nexa" rel="noopener noreferrer"&gt;Nexa&lt;/a&gt;, the Odoo checker I build, as &lt;code&gt;odoo.sql-constraints-are-ignored-from-19&lt;/code&gt;. It reads the file and the series in the module's manifest, and it stays silent on 16 to 18, where the old form is the only form. Run over OpenSPP's history, it fires on all four of the fixed declarations as the files stood before each fix, and on none after. With no model involved, it takes a fraction of a second:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nexa check models/trade_account.py --no-model

  impact     likelihood
  high       defect     line 18    odoo.sql-constraints-are-ignored-from-19
    TradeAccount declares _sql_constraints, which Odoo 19 ignores: the registry logs a
    warning and creates no constraint, so nothing enforces it -- declare each one as
    models.Constraint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migrating without losing the rule a second time
&lt;/h2&gt;

&lt;p&gt;The change itself is small, as the two examples at the top show. Three details decide whether the rule survives it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear the duplicates first.&lt;/strong&gt; If rows already break the rule, adding the constraint fails. Odoo 19 treats that as a warning, "not a deployment showstopper" in the words of its own comment in &lt;code&gt;registry.py&lt;/code&gt;, and carries on without the constraint. So migrating the declaration is not enough on its own: run the duplicates query, merge or fix what it finds, and only then update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the name.&lt;/strong&gt; Odoo 18 named the constraint &lt;code&gt;{table}_{key}&lt;/code&gt;. Odoo 19 names it &lt;code&gt;{table}_{attribute}&lt;/code&gt;, with the leading underscore removed. So &lt;code&gt;("reference_unique", ...)&lt;/code&gt; becomes &lt;code&gt;_reference_unique = models.Constraint(...)&lt;/code&gt;, and both produce &lt;code&gt;trade_account_reference_unique&lt;/code&gt;. With the same name, the update recognizes the existing constraint as the one you declared instead of removing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the definition text identical.&lt;/strong&gt; Odoo stores the definition as a comment on the constraint and compares it with what you declare. If the text differs, even in spacing, it drops the constraint and adds it again, and that re-add is exactly the step that fails quietly when duplicates exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then run the &lt;code&gt;pg_constraint&lt;/code&gt; query again after the update. The constraint should be there, under the name you expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general lesson
&lt;/h2&gt;

&lt;p&gt;A framework upgrade does not only break things loudly. Sometimes it retires a way of saying something, keeps accepting the old way, and records the difference in a log line that nobody reads. The only defense is to check what the database actually enforces, not what the code appears to declare.&lt;/p&gt;

&lt;p&gt;If you run Odoo and would rather have someone read your custom modules for this and the other failures that raise no error, that is what my &lt;a href="https://dobybaxter127.gitlab.io/services/#odoo" rel="noopener noreferrer"&gt;Odoo reliability reviews&lt;/a&gt; are for. They are read-only, fixed-price, and start at £200.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>python</category>
      <category>postgres</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Software Is Where My Actions Count: What a Special Interest Actually Does</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Sun, 20 Sep 2026 13:25:49 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/software-is-where-my-actions-count-what-a-special-interest-actually-does-4cgl</link>
      <guid>https://dev.to/dobybaxter127/software-is-where-my-actions-count-what-a-special-interest-actually-does-4cgl</guid>
      <description>&lt;p&gt;&lt;em&gt;People assume a special interest is just a hobby turned up too loud. It isn't. It's the one environment where my actions reliably produce consequences I can see, understand, and stand behind. Mine maps onto software, and that single fact explains more about my career than any decision I ever consciously made.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a special interest actually is
&lt;/h2&gt;

&lt;p&gt;The casual version of the term is something like "an autistic person who is really into trains." Technically true, completely missing the mechanism.&lt;/p&gt;

&lt;p&gt;What's actually happening is that a particular domain becomes disproportionately capable of activating things that are otherwise hard for me to reach: motivation, sustained attention, emotional regulation, a sense of competence, a sense of coherence. Not because the subject is objectively more important than everything else in my life, but because my nervous system responds to it in a way it doesn't respond to almost anything else.&lt;/p&gt;

&lt;p&gt;That's a very different claim from "I enjoy this." Plenty of things are enjoyable. Very few things reliably switch me on.&lt;/p&gt;

&lt;p&gt;There's a name for this, and it isn't mine. &lt;a href="https://monotropism.org/murray-lesser-lawson/" rel="noopener noreferrer"&gt;Monotropism&lt;/a&gt;, developed by the autistic researchers Dinah Murray, Mike Lesser, and Wenn Lawson, treats attention as quantitatively limited: it pools into a small number of intense channels instead of spreading thinly across many. The image in the original work is a torch beam rather than diffused light, and the state it produces is an &lt;em&gt;attention tunnel&lt;/em&gt;. Once you model attention that way, most of what follows here stops being a personality quirk and starts being a predictable consequence of how the attention is allocated.&lt;/p&gt;

&lt;p&gt;So when someone asks why I'll work on a problem at 2am while physically exhausted, the honest answer isn't discipline and it isn't recklessness. &lt;strong&gt;It's that this is one of the few places where my brain will actually start.&lt;/strong&gt; Telling me to apply that energy to something more sensible assumes the energy is fungible. It isn't. It's tunnel-bound, and the tunnel picked me rather than the other way around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same wiring, different surface
&lt;/h2&gt;

&lt;p&gt;I read an account once by an autistic person describing why she spent enormous amounts of time in video games. The word she reached for wasn't "addictive." It was control. Agency. Autonomy. A world where her actions had consequences, the rules were legible, competence was achievable, progress was visible, and mistakes were recoverable. Everything her actual environment, at home and in institutions, did not reliably offer.&lt;/p&gt;

&lt;p&gt;That turns out not to be an idiosyncratic account. It's close to a consensus finding in the qualitative research on autistic gamers, to the point where one study in the &lt;em&gt;British Journal of Developmental Psychology&lt;/em&gt; takes its title straight from a participant: &lt;a href="https://bpspsychub.onlinelibrary.wiley.com/doi/10.1111/bjdp.12424" rel="noopener noreferrer"&gt;"I can actually do it without any help or someone watching over me all the time and giving me constant instruction"&lt;/a&gt;. A &lt;a href="https://dl.acm.org/doi/full/10.1145/3803421" rel="noopener noreferrer"&gt;large survey of autistic gamers&lt;/a&gt; puts stress relief and a sense of achievement at the top of the list of reasons, and &lt;a href="https://www.sciencedirect.com/science/article/abs/pii/S0747563222004381" rel="noopener noreferrer"&gt;work on escapism in autistic adults&lt;/a&gt; frames it as a self-regulation strategy rather than a pathology.&lt;/p&gt;

&lt;p&gt;I recognized my own relationship with software in all of it immediately, because structurally it's the same thing.&lt;/p&gt;

&lt;p&gt;A game gives you a bounded world that responds to you. Software gives you almost exactly the same properties, except the thing you produce exists outside the world you were playing in. You think of something, build it, hit a wall, learn, solve it, see it work, publish it, and someone can use it.&lt;/p&gt;

&lt;p&gt;Same wiring. Different surface. &lt;strong&gt;The pull was never the subject matter, it was the structure: a domain where my actions count.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What my brain wants, and what software supplies
&lt;/h2&gt;

&lt;p&gt;Here's the mapping, as concretely as I can state it.&lt;/p&gt;

&lt;p&gt;My brain wants &lt;strong&gt;legible rules&lt;/strong&gt;. Software is a system where the rules, however complex, are ultimately knowable. When something breaks, there is a reason. That reason can be found. Nothing about it depends on correctly reading a room.&lt;/p&gt;

&lt;p&gt;My brain wants &lt;strong&gt;fast, honest feedback&lt;/strong&gt;. Software tells you unambiguously and quickly whether what you did worked. The test passes or it doesn't. The build succeeds or it doesn't. No ambiguity, no social interpretation layer, no waiting weeks to find out whether you misjudged something.&lt;/p&gt;

&lt;p&gt;My brain wants &lt;strong&gt;depth without a ceiling&lt;/strong&gt;. Most interests eventually exhaust what's available to learn about them. Software does not. However far down I want to go, there is always another layer, and going deeper is rewarded rather than treated as excessive. A domain that never says "that's enough detail now" is an unusually comfortable place to put a tunnel.&lt;/p&gt;

&lt;p&gt;My brain wants &lt;strong&gt;self-directed scope&lt;/strong&gt;. I choose the problem, the approach, the architecture, the stopping point. Nobody assigns me the next thing. That matters enormously, because externally imposed structure is exactly what my executive function handles worst, and self-chosen structure is what it handles best.&lt;/p&gt;

&lt;p&gt;My brain wants &lt;strong&gt;recoverable mistakes&lt;/strong&gt;. Version control means almost nothing I do is irreversible. Being able to try something badly, see what happens, and roll it back is what makes exploration possible for someone who cannot reliably plan the whole route in advance.&lt;/p&gt;

&lt;p&gt;My brain wants &lt;strong&gt;something that persists&lt;/strong&gt;. At the end, an artifact exists. A repository, a release, a service, a skill I didn't have last week.&lt;/p&gt;

&lt;p&gt;Put those six together and you get a description of an environment that could have been designed for how I work. It wasn't, obviously. It just happens to be the shape it is, and my attention found it and stayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The autonomy I don't get anywhere else
&lt;/h2&gt;

&lt;p&gt;This is the part that's hardest to explain to people who assume I'm describing a preference.&lt;/p&gt;

&lt;p&gt;I actually have a lot of control over my physical environment. I live alone. I arrange my space exactly how I need it. I eat a small, consistent set of foods that work. I go outside at night, when it's quiet and dim and there are no people, because the daytime version of that same errand costs more than my nervous system can consistently pay.&lt;/p&gt;

&lt;p&gt;That's real autonomy and it genuinely helps. But &lt;strong&gt;controlling my environment is not the same as being able to participate in the world.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every one of those adaptations still reads as "I can do this, but only under these conditions." I didn't choose 2am because it's my favorite hour to be alive. I chose it because it's when the cost drops to something payable. So even inside a life I've deliberately optimized around myself, I'm permanently aware of the set of things that remain out of reach, and no amount of rearranging my flat changes that.&lt;/p&gt;

&lt;p&gt;Software doesn't work that way. In software the barrier between what I intend and what I can execute is thin and getting thinner, because the entire discipline is built out of tools whose whole purpose is to absorb load. I don't have to hold everything in working memory. I don't have to remember every API. I don't have to independently sustain a research thread across days, which is precisely the kind of continuous perseverance I'm worst at. The tooling carries the part I can't, and I do the part I can: the judgment, the design, the decisions, the responsibility for what ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's the difference. Everywhere else I'm adapting the world to the boundaries of my nervous system. Here I'm using tools that let me cross them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So when the interest activates, it isn't activating in a vacuum. It's activating the only reliable supply of something scarce in the rest of my life: I decided this, I did this, it worked, and it's mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the artifact matters so much
&lt;/h2&gt;

&lt;p&gt;There's an extra layer on top of the loop, and I underrated it for years.&lt;/p&gt;

&lt;p&gt;The work leaves evidence.&lt;/p&gt;

&lt;p&gt;Each finished thing is a durable record that says: I learned this, I solved this, I built this, I can stand behind this. That matters disproportionately to someone whose memory doesn't reliably preserve their own history, and whose sense of capability fluctuates with energy levels that swing hard. On a bad day I can't feel competent. But I can look at something I built and read the receipt.&lt;/p&gt;

&lt;p&gt;That's also why the interest didn't stay private. Publishing the work turns the internal loop into something with an external footprint: a portfolio, a service, a skill someone else can benefit from. &lt;strong&gt;A special interest that produces artifacts stops looking like a fixation and starts looking like a career&lt;/strong&gt;, which is a fairly arbitrary distinction, but it's the one the world actually uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why leaving is hard, and why "addictive" is the wrong word
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the cost, because the same mechanism that makes this good for me can consume everything I have.&lt;/p&gt;

&lt;p&gt;Addiction imports a set of claims that don't fit here. What's actually happening is simpler: &lt;strong&gt;an activity becomes extraordinarily reinforcing when it supplies something that's scarce everywhere else in your life.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monotropism gives a cleaner account of the exit cost than willpower ever did. Leaving an attention tunnel isn't like closing a tab, because you aren't next to the thing, you're inside it. The original paper puts it starkly: for someone in an attention tunnel, unanticipated change is briefly catastrophic. That's not a metaphor for mild annoyance. It's a description of why being pulled out of something feels the way it does.&lt;/p&gt;

&lt;p&gt;There's a companion term for the other half of it. &lt;a href="https://www.psychologicalscience.org/publications/observer/student-notebook-autistic-inertia-srinivasan.html" rel="noopener noreferrer"&gt;Autistic inertia&lt;/a&gt; covers both the difficulty of starting and the difficulty of stopping, the latter sometimes called termination resistance, and &lt;a href="https://www.frontiersin.org/journals/psychology/articles/10.3389/fpsyg.2021.741421/full" rel="noopener noreferrer"&gt;autistic people describe it as among the most disabling parts of being autistic&lt;/a&gt; while simultaneously describing deep absorption as one of the most restorative states available to them. Both things are true at once, which is exactly why this is hard to talk about honestly.&lt;/p&gt;

&lt;p&gt;It's also worth separating two things that often get merged. Hyperfocus describes an unusually intense stretch of attention, hours or maybe days. It doesn't explain why someone returns to the same domain for years. A special interest is the long-horizon pattern, and it can become a genuine organizing feature of a life and an identity. The two interact, but they're not the same phenomenon, and collapsing them makes the whole thing sound more pathological and less structural than it is.&lt;/p&gt;

&lt;p&gt;The consequence is that stopping isn't experienced as "I'm done for today." It lands closer to "I'm leaving the place where my actions count." That's a far stronger pull than enjoyment alone, and it's why the physical exhaustion always arrives long before the motivation does. There's no backpressure in the loop. Nothing in it signals that the upstream resource is depleted.&lt;/p&gt;

&lt;p&gt;The only off-ramp I've found that actually works is making stopping cheap. Before I close everything, I leave myself enough state to resume without reconstructing the whole mental picture: the symptom, the current hypothesis, the last thing I changed, the next concrete step. That note isn't a reminder that the task exists. It's proof the thread hasn't been lost, which is the specific fear that keeps me working when I should be asleep. Once my brain accepts that the tunnel will still be there tomorrow, it will let me put it down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interest is not the problem
&lt;/h2&gt;

&lt;p&gt;If there's a failure mode here, it isn't "I care about software too much."&lt;/p&gt;

&lt;p&gt;It's that a single domain is carrying the entire burden of proving that my actions matter. That's a load-balancing problem, and the fix is not to take away the thing that works. Autistic accounts consistently point the other way: &lt;a href="https://autismdetect.co.uk/how-special-interests-help-prevent-autism-burnout/" rel="noopener noreferrer"&gt;special interests function as protection against burnout&lt;/a&gt; precisely because they restore a sense of control. The fix is to widen the set of places where agency is available, even in small, unimpressive ways, so the interest gets to be one strong source instead of the only one.&lt;/p&gt;

&lt;p&gt;What I'd resist entirely is the framing where a special interest is something to be moderated on principle, or grown out of. Mine is where my motivation lives. It's where I'm most competent, most regulated, and most recognizably myself. It produced a skill set, a body of work, and an income.&lt;/p&gt;

&lt;p&gt;So if one idea survives this whole essay, let it be this:&lt;/p&gt;

&lt;p&gt;I didn't build a career and then discover I enjoyed it. My attention found the one environment where it could reliably act, went all the way in, and the world happened to call the result a profession.&lt;/p&gt;

&lt;p&gt;That's not a hobby that got out of hand. That's the only place my agency was ever fully available, and I'd be foolish to treat it as anything less.&lt;/p&gt;

</description>
      <category>neurodiversity</category>
      <category>a11y</category>
      <category>softwareengineering</category>
      <category>career</category>
    </item>
    <item>
      <title>AI as a Fluency Machine: What Software Engineering Taught Me About Checking a Model's Answer</title>
      <dc:creator>Doby Baxter</dc:creator>
      <pubDate>Sun, 13 Sep 2026 16:33:01 +0000</pubDate>
      <link>https://dev.to/dobybaxter127/ai-as-a-fluency-machine-what-software-engineering-taught-me-about-checking-a-models-answer-mgj</link>
      <guid>https://dev.to/dobybaxter127/ai-as-a-fluency-machine-what-software-engineering-taught-me-about-checking-a-models-answer-mgj</guid>
      <description>&lt;p&gt;&lt;em&gt;We refuse to install a dependency without a lockfile, a checksum, and a license audit. Then we paste a language model's answer straight into a model file and call it velocity. The scrutiny didn't disappear — it just stopped applying to the thing writing the code.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The paradox
&lt;/h2&gt;

&lt;p&gt;We have built an extraordinary apparatus of distrust around our own work. Lockfiles pin transitive dependencies to exact versions. Checksums verify the bytes arrived intact. Signed commits prove authorship. CI refuses a merge over one failing test. Code review exists because we decided, collectively, that no engineer's confidence in their own code is sufficient evidence that the code is correct.&lt;/p&gt;

&lt;p&gt;Then a model produces forty lines, and they go in.&lt;/p&gt;

&lt;p&gt;Not because anyone sat down and decided generated code deserves less scrutiny. It's that every mechanism we built assumes a &lt;em&gt;human author&lt;/em&gt; on the other end — someone who can be asked why, who hedges when unsure, who owns the thing afterwards. Generated code walks past all of it, because none of it was pointed at that door.&lt;/p&gt;

&lt;p&gt;The standards haven't changed. The risk hasn't changed. Only one thing has: the single participant in the process that cannot be held responsible for being wrong is now also the one we check the least.&lt;/p&gt;

&lt;p&gt;As a software engineer, that asymmetry tells me the real disagreement isn't about whether AI writes good code. It's about what we are willing to call &lt;strong&gt;checked&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hallucination isn't the interesting question
&lt;/h2&gt;

&lt;p&gt;Most debates about AI and code ask whether the model hallucinates. Fair enough — it does. But hallucination is the &lt;em&gt;easy&lt;/em&gt; failure, because it announces itself. An invented API doesn't exist. The import fails, the type checker complains, the test goes red. The system you already have catches it.&lt;/p&gt;

&lt;p&gt;The failure that matters is quieter.&lt;/p&gt;

&lt;p&gt;During development, my 7B cited &lt;code&gt;odoo.onchange-is-not-validation&lt;/code&gt; — a real rule, in my corpus, traceable to upstream documentation — and then wrote an &lt;code&gt;@api.onchange&lt;/code&gt; to guard the value. Every citation verified. The answer was still wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Citation existence is not rule compliance.&lt;/strong&gt; That one sentence is what the whole project is built on.&lt;/p&gt;

&lt;p&gt;I've come to think of the underlying machine as a &lt;em&gt;fluency machine&lt;/em&gt;. I use the word deliberately. It doesn't manufacture wrong answers — wrong answers predate it by the entire history of the profession. What it manufactures is the &lt;strong&gt;appearance of having checked&lt;/strong&gt;. A junior engineer who is unsure hedges, and you can hear it in the hedge. A model that is unsure produces the same fluent, well-structured, confidently-cited paragraph as a model that is right. The uncertainty signal we spent careers learning to read simply isn't in the output.&lt;/p&gt;

&lt;p&gt;Fluency is not a confidence interval. It just renders like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A prompt is not a specification
&lt;/h2&gt;

&lt;p&gt;Here's where software engineering has something to say. Our entire discipline runs on abstraction — interfaces, APIs, frameworks, platforms — each layer letting one engineer reason about one problem without drowning in all the others. A prompt is an abstraction too. It compresses everything you know about a domain into prose and hands it over as though prose were a contract.&lt;/p&gt;

&lt;p&gt;But every abstraction hides what it discarded, and prose hides more than most, because &lt;strong&gt;prose has no schema&lt;/strong&gt;. You can't diff it meaningfully, you can't validate it, and you cannot test whether a paragraph still means what it meant before you edited it.&lt;/p&gt;

&lt;p&gt;I found the edge of that the ordinary way. Prompt tuning plateaued after two iterations. Hardening the Odoo rule about returning &lt;code&gt;false&lt;/code&gt; for an empty field fixed the scalar cases and simultaneously broke the Many2one tuple handling that had been correct the day before. A 7B holds a bounded number of constraints; past that point, more instruction text just trades one error for another.&lt;/p&gt;

&lt;p&gt;The same limit showed up in routing. My first attempt used a single system prompt covering both Odoo and TypeScript. Asked a plainly Odoo question — &lt;em&gt;cap a sale order line quantity&lt;/em&gt; — it answered entirely in TypeScript with a Zod schema. The instructions bleed. A small model doesn't reliably route between two stacks described in one paragraph.&lt;/p&gt;

&lt;p&gt;So I moved the rules out of the prompt and into data. Each rule lives in &lt;code&gt;corpus/rules/*.json&lt;/code&gt; against a JSON Schema: a citable id, a severity, the failure it prevents, a wrong/correct code pair, an upstream documentation URL where one exists, and where possible a regex that detects the violation in generated code. The system prompts and the retrieval index became &lt;strong&gt;build artifacts&lt;/strong&gt;, generated from that corpus by &lt;code&gt;nexa build&lt;/code&gt;. Same principle as &lt;code&gt;z.infer&amp;lt;typeof Schema&amp;gt;&lt;/code&gt; — one definition, derived artifacts, no drift.&lt;/p&gt;

&lt;p&gt;That didn't make the model smarter. It changed the failure mode. Instead of rewriting prose and hoping, a rule is written once and flows to the prompt, the retrieval index, and the compliance checker together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nobody misconfigured it, but the system decayed
&lt;/h2&gt;

&lt;p&gt;Distributed systems engineers know the shape of the next part. A latency bug in one service cascades through a queue, triggers retries, saturates a database, and surfaces as an outage somewhere else entirely. Every component behaved to spec. The system as a whole did something nobody designed.&lt;/p&gt;

&lt;p&gt;I benchmarked the 7B at 37 tok/s. Four instrumented runs later, the report showed roughly 10. I offered three diagnoses; the first two were wrong.&lt;/p&gt;

&lt;p&gt;The actual cause was that Ollama's memory estimator is conservative and leaves layers on the CPU with VRAM still free — &lt;strong&gt;11.7 tok/s automatic versus 38.6 forced&lt;/strong&gt;, same model, same context. My Modelfiles carried &lt;code&gt;num_gpu 99&lt;/code&gt;, but the pipeline was calling the raw base model, which has no such parameter. Meanwhile the KV cache environment variables had been set while the server was already running, so they never took effect at all. And two orphaned &lt;code&gt;llama-server&lt;/code&gt; processes were sitting there holding 1.4 GB of VRAM, left behind when their parent was killed.&lt;/p&gt;

&lt;p&gt;Nobody misconfigured anything. Ollama's estimator was being careful. Windows was doing what it does with environment variables set after a process starts. The orphans were doing what orphans do. Collectively, the system ran at roughly a third of its speed for most of a working session — while I read a number I had measured once and believed ever since.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An unmeasured number decays into a claim.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the entire reason &lt;code&gt;nexa doctor&lt;/code&gt; exists, and why it checks GPU residency and each of those traps by name rather than just confirming that Ollama is up. There's an old rule in operations: someone owns every service. Complexity raises the effort of attribution. It never removes the need for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns inherit their author's blind spots
&lt;/h2&gt;

&lt;p&gt;You can't check for "is this code correct" — the phrase is too vague to be an objective. So, exactly as we do everywhere in engineering, we check &lt;em&gt;proxies&lt;/em&gt;. A regex matching &lt;code&gt;@api.onchange&lt;/code&gt; in a validation context is a proxy for "this code is using the wrong mechanism to hold an invariant."&lt;/p&gt;

&lt;p&gt;Individually, these are reasonable. The problem is that a proxy always discards information, and it quietly inherits the assumptions of whoever wrote it.&lt;/p&gt;

&lt;p&gt;So the patterns get tested too. Every rule already carries a wrong and a correct sample, which means the test data existed for free. &lt;code&gt;nexa validate&lt;/code&gt; fails the build on three faults: a pattern that doesn't match its own wrong sample catches nothing; a pattern that fires on its own correct sample cries wolf; a &lt;code&gt;satisfied_pattern&lt;/code&gt; that clears its own wrong sample is inert by construction.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;nexa crosscheck&lt;/code&gt; fires every pattern at every &lt;em&gt;other&lt;/em&gt; rule's correct sample — because self-testing proves a pattern handles its own case and says nothing about whether it goes off on unrelated good code. The first run found three real defects. &lt;code&gt;float-comparison&lt;/code&gt; fired on &lt;code&gt;qty &amp;gt; 0&lt;/code&gt;, including inside SQL where &lt;code&gt;float_compare&lt;/code&gt; doesn't apply; narrowed to equality, which is the actual trap. &lt;code&gt;odoo-read-returns-array&lt;/code&gt; fired on any &lt;code&gt;Schema.parse(await ...)&lt;/code&gt;. And &lt;code&gt;ondelete-is-a-decision&lt;/code&gt; correctly flagged a sample &lt;strong&gt;in my own corpus&lt;/strong&gt; that didn't follow its own rule.&lt;/p&gt;

&lt;p&gt;That last one is the one I'd keep. The checker was right and the corpus was wrong.&lt;/p&gt;

&lt;p&gt;It's also the loop worth being nervous about: my rules become the system prompt, the system prompt shapes the answer, and the answer is then checked against the same rules that produced it. A rule that is subtly wrong is wrong in three places simultaneously, and the pipeline will report VERIFIED at every step. All models are wrong but some are useful — the danger was never the simplification. It's forgetting what got discarded in it.&lt;/p&gt;

&lt;p&gt;Which is why the limitation goes in the docs, in plain words: 27 of 32 rules trace to upstream documentation, and the rest rest on general knowledge and deserve more scepticism. These rules were written by an assistant, not harvested from production incidents. Rules drawn from real incidents would be worth more.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Verified" can be theatre
&lt;/h2&gt;

&lt;p&gt;The standard reassurance in this space is that the tool validates its own output. From an engineering perspective, that tells you almost nothing about how the system behaves. Inserting a step into a pipeline doesn't guarantee verification, any more than a deployment approval guarantees oversight — an approval only matters if the reviewer has the information to spot risk, the authority to say no, and the time to actually look.&lt;/p&gt;

&lt;p&gt;We have a name for the human half of this: &lt;strong&gt;automation bias&lt;/strong&gt;. Once software earns a reputation for being usually right, reviewers start confirming its output instead of interrogating it.&lt;/p&gt;

&lt;p&gt;The checker half has a sharper version of the same problem. A checker that fires on good code is worse than no checker, because it teaches you to dismiss it — and once you're dismissing it, it will be dismissed on the day it was right. So five of my rules deliberately carry &lt;strong&gt;no pattern at all&lt;/strong&gt;, with the reason recorded for each. Two need set comparison between a decorator's arguments and the attributes its body reads, which is AST work, not regex work. One lives in a diff between module versions. Two depend on context a snippet doesn't contain. Those rules return &lt;strong&gt;no verdict&lt;/strong&gt; rather than a false pass. 84% coverage with an honest list of the gap beats 100% with five patterns producing noise.&lt;/p&gt;

&lt;p&gt;So "did it verify?" is the wrong question. The right one is: &lt;em&gt;what exactly was checked, and what does the absence of a finding actually prove?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Nexa, VERIFIED means the checks that exist found nothing. It does not mean correct. Compliance is only checked against rules retrieved for that question, plus pinned ones — so a rule that was never retrieved is a rule that was never enforced. I'd rather ship that sentence than a green checkmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small and local is not a lesser tool
&lt;/h2&gt;

&lt;p&gt;Now flip the paradox around.&lt;/p&gt;

&lt;p&gt;A 7B-to-30B local model does not match a frontier model on hard architectural reasoning. That gap is a function of scale, and no amount of prompting closes it. I put that in the README, near the top, deliberately.&lt;/p&gt;

&lt;p&gt;But engineers make a distinction when optimizing: removing overhead is not the same as increasing capability. Stripping out overhead lets existing resources do more — it doesn't upgrade the hardware. Nexa doesn't make a small model smarter. It removes the thing that made a small model untrustworthy: the gap between an answer and any means of checking it.&lt;/p&gt;

&lt;p&gt;And for this particular job, that trade isn't obviously a loss. The frontier model has more capability. The local one has more &lt;strong&gt;provenance&lt;/strong&gt; — it answers from rules I can read, cite, version and test, on hardware I own, with client code that never leaves the laptop. No API key, no network call, nothing phoning home. On a machine used for client work that isn't a feature, it's a precondition, which is also why telemetry records rule ids, counts, model names and timings, and not question or answer text unless you explicitly turn it on. A telemetry file is the last place client code should quietly accumulate.&lt;/p&gt;

&lt;p&gt;Same technology, different question. One asks how capable this can be. The other asks how wrong it can be without me finding out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification cannot be delegated to fluency
&lt;/h2&gt;

&lt;p&gt;Every mature engineering discipline eventually learns the same lesson: a system can't be trusted merely because it functions. It has to be observable, auditable, and — this is the part we keep skipping with generated code — &lt;em&gt;falsifiable&lt;/em&gt;. We'd never run production without logs, traces and deployment history, because once causality goes opaque, reliability becomes impossible. Yet we routinely accept generated code on terms we'd never accept from a library: no provenance, no stated assumptions, no way to ask what it was checked against.&lt;/p&gt;

&lt;p&gt;Observability can't be bolted on after a system fails. Verifiability follows the same rule. If a tool isn't designed to make its own answers checkable from the start, no amount of downstream review will conjure that property later — you'll just be reading fluent paragraphs faster.&lt;/p&gt;

&lt;p&gt;So if one idea survives this whole article, let it be this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The true measure of a coding assistant is not how often it is right. It's how reliably you find out when it is wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Correctness can't be prompted into existence. It can't be inferred from formatting, borrowed from a real-looking citation, or assumed because the prose was confident and the rule id resolved. Someone still has to write the rule down in a form that can be checked, decide what a failing check means, and accept what the gaps cost. Those decisions stay irreducibly human — which is precisely what I want from a tool I let anywhere near a client's codebase.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Nexa&lt;/strong&gt; is source-available under PolyForm Noncommercial 1.0.0 — free to read, run, modify and evaluate for any noncommercial purpose. It's a few hundred kilobytes of Python (stdlib only) that orchestrates open models you pull from Ollama; no weights ship with it. 32 rules today across Odoo, TypeScript and JavaScript, 27 with an automated check, 0 cross-hits.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/dobybaxter127/nexa" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/DBax127/nexa" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://gitlab.com/dobybaxter127/nexa/-/blob/main/ARCHITECTURE.md" rel="noopener noreferrer"&gt;ARCHITECTURE.md&lt;/a&gt; records every decision with the evidence behind it, including the ones that were wrong first — and an honest account of what it cannot do. If you only read one file, read the limitations section.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
