Release

Untested is not passed

1.14.0 is mostly one idea applied in several places: a step that reports success should have earned it. Build slices are now held to what they promised to produce, verification runs in the environment that produced the work, and a failing slice fails instead of restarting forever. Along the way we found a release that offered to update itself to the version it already was.

14 August 2026 · André

A declared type that nobody checked

The build stage splits work into slices and hands results between them. Each slice declares the shape of what it produces. That declaration was decorative. Nothing checked it, so a slice could promise an object and return a string, the run continued, and the failure surfaced three steps later somewhere unrelated.

Worse, an explicit null counted as a produced value. A slice that ran, did nothing useful and returned nothing was indistinguishable from one that worked. Both are real checks now. A slice is gated on delivering the ports it declared, so the run stops at the boundary where the promise was broken rather than at the place where the consequence happened to show up.

Restarting forever is not a failure state

A slice that could not finish used to restart. Then restart again. There was no point at which the system said this is not going to work. From the outside that looks exactly like work in progress, which is the most expensive kind of wrong, because you leave it running on the grounds that it appears to be trying.

A failing slice now has a real terminal state. It is allowed to fail.

Verify where the work ran

The Sandbox Verify module is wired into the ticket panel, and Loops runs are bridged into GitVM sandbox verification. The plumbing is not the point. The point is that a result gets checked in the environment that produced it, instead of being reported complete and taken at its word somewhere else.

The same release adds a decision log. Decisions are recorded, an agent summarises them over the build log, and a brief reads that back. Settled entries are marked, because a summariser that keeps reporting fixed bugs as live is worse than no summary at all. Agents also read what past runs learned, scoped strictly to the current project, so one project's lessons do not quietly become another project's context.

The update banner that offered you what you already had

This one is worth telling properly, because it is two bugs and the second is the one that mattered.

xNAUT checked for updates by comparing the latest release against a constant in the source. That constant was last edited at 1.5.0. So every release after 1.5.0 concluded that an update existed, including the release you were already running. The app offered to update 1.13.10 to 1.13.10.

Embarrassing, and harmless. The second half was not. The banner was pinned to the top of the window with no layout offset, so it did not push the toolbar down, it covered it. We measured rather than assumed, asking the browser what is actually underneath each coordinate: all nine top-bar controls returned the banner instead of themselves. Every control along the top was unclickable and the only way out was the small dismiss button. That half would have reached every user the moment a genuine update existed.

The version now comes from the app itself, and when that cannot be answered the check says nothing rather than offering an update it cannot justify. The banner sits in the layout, above the toolbar.

Your work log survives closing the app

Start the work log, close xNAUT, reopen it, and the session was gone. The monitor stopped recording silently and the hours disappeared from the project dashboard.

The session was never actually lost. It is written to disk on every logged command. Only the in-memory pointer to it died with the process, and nothing read the file back, so the record sat there marked as still running, forever, invisible.

xNAUT now offers it back on restart, newest first, and asks. It does not resume by itself, for two reasons. Time passes between closing the app and reopening it that nobody worked, so a silent resume would bill a coffee break. And where several logs were left open, adopting the newest without asking would quietly close whichever one was real. Resuming appends a marker, so the gap is visible in the log instead of folded into the hours.

The endpoint you configured is the endpoint we should dial

If you run LM Studio on a port other than the default, xNAUT told you the connection was refused and named a port that appears nowhere in your settings. The address was a literal in the source. Your setting was right and the code was not reading it. It reads it now, and when a local provider is unreachable the message names the endpoint actually dialled, so you can tell a wrong setting from a service that is down.

AntBot is gone as well. Both AI paths tried it before the provider you had configured, which is why a missing binary was the first half of every AI error message you saw. The Explain command used to type a CLI invocation into your terminal, so it only worked if that CLI happened to be installed, and it left a command in your shell history that you never wrote.

Why the title

Our own release test kept reporting scenarios as untested. It would have been easy to fold those into the pass column and enjoy a green page. Two of them turned out not to be untestable at all. The Designer screens and the project workspace were unreachable because of gaps in our test harness rather than gaps in the app, and one of those gaps had already been reported three times as a fault in the product.

So the browser suite went from 17 tests to 66, alongside 367 in the backend. More useful than the count: every new test was checked by deliberately breaking the rule it guards and confirming it went red. A test that stays green when you break the thing it covers is not a weak test. It is decoration, and it buys confidence it never earned.

The scenarios that genuinely need a real machine are still marked untested, in amber, on our own dashboard. That is the honest colour.