Series D

Silence is never success

The zero that means no measurement, the null that gets formatted into a report, and the security setting a backend accepts and never enforces. Three shapes of the same defect, and the rule that closes all of them.

28 August 2026Level 38 min read

There is a design rule that this whole body of work keeps arriving at from different directions: silence and success must never be the same value.

The clearest instance is the vacuous check — a verdict computed over an empty set of conditions, which passes because there was nothing there to fail. That one is well known once you have been bitten by it, and it is findable by reading your own code.

This post is about the three harder instances, the ones that survive the audit you do after learning the first. They are harder because in each case the value is not obviously empty. It is a real number, or a legitimate null, or a configuration that was genuinely accepted. Nothing looks wrong at the point of production. The lie is assembled downstream.

Shape one: the zero that means nothing happened

Somewhere in most evaluation stacks there is a function that constructs a failed result. Ours did, and it looked entirely reasonable:

return JudgeResult(passed=False, score=0.0, reason=...)

It is called when a judge cannot run. The provider errored, the response did not parse, the criterion was not reachable. And the object it returns is byte-for-byte identical to the object a judge returns when it ran perfectly and the model's answer was worthless.

Everything downstream is now working with a number that has two meanings and no way to distinguish them. Average those scores and you have mixed the model did badly with we could not look, in a proportion that depends on your provider's error rate that afternoon. The aggregate moves when your infrastructure has a bad day, and it moves in the direction that makes the model look worse, and nobody investigates a slightly lower score.

The fix is not to return a different number. There is no number that means no measurement, which is the entire problem. The fix is that a result object needs a third state — measured and failing, measured and passing, and not measured — and that the third state has to be a distinct value that arithmetic refuses to consume.

Once you have that, a second question arrives immediately, and it is the useful one: what is the denominator? An episode that failed a deterministic check before the judge was ever invoked has an unscored criterion, and that criterion is neither passing nor failing. Our answer is that the rate is computed only over the episodes that reached judgement, and criteria that reached judgement and still went unscored are counted separately under their own name. It is one more bucket to carry around and it is the difference between a rate that means what it says and one that quietly means something else.

Shape two: the null that gets formatted

Having learned to return an absence rather than a fabricated number, you now have absences flowing through your system, and a new failure mode.

A null gets rendered. Somebody writes a report template, and somewhere in it is a formatting expression with a fallback, and the fallback is a dash or an empty cell or a zero. The absence you were so careful to preserve is converted back into a number at the last possible moment, by a line of presentation code that nobody reviews as though it were part of the measurement.

Two defences, and we use both.

The first is that a null is never bare. Every degenerate statistic in our reliability code returns no value and a named reason, from a closed enumerated set — fewer than two raters, no pairable units, every verdict was advisory, only one category was ever used, no expected disagreement to correct against. A reason is hard to format away by accident, because there is no obvious numeric default for a sentence.

The second is stronger and reserved for the cases where publishing the absence as a number would be actively misleading. When a measurement has been withheld — for instance because a detection experiment's control arm failed, so no sensitivity figure is defensible — asking for that figure raises rather than returning a null.

Raising is unfriendly and that is what it is for. It is the only construction that makes it impossible to put the withheld number into a document by accident. A null can be formatted; an exception has to be caught, and catching it is a decision somebody makes in a diff that a reviewer can see.

Shape three: the setting the backend accepts and ignores

The third shape is the one I would not have predicted, and it is the most dangerous, because the silence is not in your code at all.

We let a task declare an egress policy: which destinations the sandbox is permitted to reach. That policy is signed into the run's contract and printed on the customer's document. It is one of the more consequential things on there.

One hosted sandbox backend takes deny rules as a plain array of strings. Its documentation says that only addresses and address ranges are honoured, and that domain names are not supported for deny rules. But the API schema is just an array of strings, with no pattern constraint on the elements.

So if you hand it a domain name, it does not reject it. It accepts the request, boots the sandbox, and returns success. There is no error, no warning, no field in the response indicating that one of your rules was discarded. Every observable available to us says the policy is in force, and the policy is not in force, and the difference would only ever surface as traffic reaching a destination you told a customer it could not reach.

This is the same defect class as the empty checklist, relocated across an organisational boundary. The backend converted we did not enforce this into accepted, and we were about to convert that into a line on a certificate.

The only available fix is to refuse in front of it. We classify every rule before a run — address, range, domain, wildcard domain, or unclassified — and we refuse to start a run whose declared policy contains rules the chosen backend cannot enforce. The classifier is deliberately asymmetric: it is quick to say this is a domain and slow to say this is definitely an address, because the two errors have very different costs. A false refusal wastes an engineer's afternoon. A false acceptance ships an unenforced security claim with a signature on it.

I will note the bug we found inside that classifier, because it is a small perfect illustration. Our domain pattern was anchored with an end-of-string marker that, in the regex flavour we were using, also matches immediately before a trailing newline. So a rule with a stray newline on the end read as a well-formed address and rode straight through the check that existed to catch it. Anchoring on a full match fixed it. The gate had been slightly open the entire time and nothing anywhere would ever have told us.

The declared field that nothing reads

There is a quieter member of the same family, and it is worth separating because the honest response to it is different.

Our environment specification carries a runtime block — resource requests, an isolation tier. It is signed into the contract. It is printed in the customer's document. And on the hosted backends, nothing on the execution path reads several of those fields.

That is not a bug in a backend. It is us describing an environment more precisely than we can deliver it, which produces a document that promises something no machine ever consulted.

Here we did not refuse, and I want to be precise about why, because the inconsistency with the egress case is deliberate. Refusing a run requires knowing that the declared field would have changed the run's behaviour, and to know that you need a measurement. Nobody has taken one. So the unapplied fields are recorded as unapplied — they travel with the run, visible, named — rather than triggering a refusal we cannot yet justify. A gate needs a measurement behind it. Building one that fires on a suspicion produces a refusal that people learn to override, and an overridable refusal is worse than none.

The two places where we do act are instructive by contrast. Where an image would not boot in a way the agent under test could observe, that is a refusal, because the mismatch is a fact about the run rather than a fact about our metadata. And the environment record that appears on the document is built from a measurement — a probe that asks the machine, from inside the guest, what it actually is — rather than from the fields that were requested. What was asked for and what was delivered are different quantities, and only one of them belongs on a certificate.

One more asymmetry: the local development backend gets no preflight check at all, because it makes no claim about resources or isolation in the first place. A gate on a claim nobody made would be a gate that cannot fail, which is the defect again, wearing a high-visibility jacket.

The rule, stated once

For every value your system can produce, ask whether there is an input under which it produces that value while having measured nothing. If there is, the value is overloaded, and somewhere downstream a person is going to read it as a measurement.

The corollaries have all appeared above, but they are worth having in one place:

  • A score of zero must not be reachable by a judge that never ran.
  • An absence must travel with a reason, and a withheld figure should raise rather than return.
  • A rate needs a denominator that excludes what was never measured, and a named bucket for what was measurable and went unmeasured.
  • A configuration your backend accepts is not a configuration your backend enforces, and the accept is not evidence.
  • A declared field nothing reads should be visible as unapplied, not silently signed.
  • A check that cannot fail is not a check.

None of these is difficult. All of them cost a little more structure than the obvious implementation, and every one of them is the difference between a system that reports what it knows and one that reports what it was hoping.

The one-line version

Every silent thing in your system eventually gets rendered as a number, and the number always reads as success — so make silence a distinct value with a name on it, and refuse the configuration your backend will accept and never enforce.