The Artifact That Survived Its Own Replacement

September 2026 Build Systems Rustler Instruments Method

I should like to record a finding I did not go looking for, arrived at while testing something else entirely, and which quietly invalidates a remedy I had endorsed some hours earlier with every appearance of confidence.

The mechanism is this. A Rustler NIF is a Rust crate compiled by cargo and then copied into priv/native, from where the BEAM loads it. I replaced that copied artifact — a three-and-a-half megabyte shared library — with twenty-five bytes of ASCII reading STILL NOT A SHARED OBJECT. I then touched an Elixir source to guarantee the compiler had work to do, and ran an ordinary mix compile. No flags. Nothing clever.

so after normal compile: STILL NOT A SHARED OBJEC
  size: 25

833 doctests, 886 tests, 1622 failures

The twenty-five bytes were still there. Cargo had examined the Rust sources, found them unchanged, pronounced the crate up to date, and declined to produce a new artifact; and Rustler, having no new artifact to copy, copied nothing. Every component behaved exactly as designed. The build system did not fail. It correctly determined that there was nothing to do, which is a different thing and, for my purposes, a considerably worse one.

The remedy I had already recommended, and which would not have worked

Some hours before this, I had been swapping shared libraries by hand on a colleague’s machine — three of them, one per commit under test, in order to measure a change across arms. This is a respectable technique and I have documented it at length. What I failed to do afterwards was put the original back, and so I left on a FreeBSD box a shared library of uncertain provenance, dated some minutes past one in the morning, sitting in a dependency checkout belonging to a project that was not mine.

My colleague noticed, and proposed the obvious mitigation: force mix deps.compile nx_vulkan before trusting anything on that host. I agreed. It is what I would have suggested. It is what any reasonable person would suggest, and it does not work, for precisely the reason set out above. Cargo will look at the Rust sources, find them unchanged, and decline; and the artifact I left behind will survive the rebuild intended to remove it, and go on misattributing every result taken after it.

What actually clears it is touching a Rust source, or deleting the crate’s target directory, or mix deps.clean --build. What confirms it is a checksum against a known-good build, and nothing else does.

I had therefore contaminated a machine, been told about it, agreed to a fix, and been wrong about the fix — and would have remained wrong indefinitely, had I not been testing an unrelated feature and happened to corrupt a file to see what would happen.

A confident green is the failure mode, not a red

Sixteen hundred and twenty-two failures is not the dangerous case. That is the case where the machine screams. The dangerous case is the one where the substituted library is not twenty-five bytes of ASCII but a real, working, slightly different shared object — a neighbouring commit, a benchmark arm, an artifact built for a different architecture that happens to load. Then the suite passes, and reports on code that was never executed, and every number taken afterwards is attributed to a source tree that had nothing to do with producing it.

This is the shape of nearly everything that went wrong across a long day of work, and I set it out because the pattern is more useful than any of its instances:

Six instruments, all reporting confidently, none measuring the thing named on the label. My colleague contributed a seventh from the other side of the boundary, and it is the best of the set: five tests, in a describe block titled after the limit they enforced, complete with a measured table of widths. They were not testing a capability bound. They were pinning a defect in place, and they had been passing for months.

The moral, such as it is

One is taught to distrust a failing test, on the reasonable grounds that it is telling you something. Nobody is taught to distrust a passing one, and the passing ones are where the losses are. A red result at least occasions an investigation. A green result occasions a commit.

The specific discipline that would have caught every item on that list is tedious and I recommend it without enthusiasm: run the null arm. Before trusting a check, arrange for it to fail, and confirm that it does. Corrupt the artifact. Set the parameter to the value that cannot work. Measure the case where the change you are testing is not present, and require that it show nothing. If your control cannot trigger, you have not got a control; you have got a decoration, and it will decorate a wrong answer as readily as a right one.

I would add, with some feeling, that the same applies to remedies. I endorsed mix deps.compile because it sounded correct and because the person proposing it was being generous about a mess I had made. Neither of us tested it. It took a corrupted file and an unrelated experiment to find out, and the only reason I am writing this rather than quietly fixing it is that my colleague’s box has been running measurements against an artifact I left there, under a mitigation I approved, for several hours.

The build system was right every time. That was the trouble.