Series · the legibility of AI systems

Each piece applies Seeing Like a Dashboard to one surface an AI system presents about itself:

  1. Who Tests the AI’s Tests? (the tests)
  2. A Compromised Node Will Attest That It’s Healthy (the self-report)
  3. Plausible Deniability Didn’t Die (the accountability)
  4. Human in the Loop Is Theater (the oversight)

The traditional testing taxonomy was built for human failure modes. Humans introduce bugs through fatigue, misreading, and misunderstanding, and the test suite exists to catch what the tired brain missed. Every practice in the standard playbook (unit tests, integration tests, coverage targets, code review) assumes that the person writing the tests has an intent that differs from the code in useful ways: the tests encode what the author meant, and failures reveal where the code diverged from that.

AI coding agents break that assumption at the root. An agent produces plausible, compiling, test-passing code that is subtly wrong in ways that are hard to see and hard to test for, because the agent also wrote the tests. And here is the core problem, the one sentence this whole article unpacks:

The AI satisfies tests by construction. If the same agent writes the code and the tests, the tests describe what the agent did, not what you intended.

This is a circular guarantee. The code passes the tests because both came from the same place. Code review and coverage metrics both fail here, for the same reason: the code looks clean and the numbers look good.

What Stops Working

Coverage metrics degrade from a signal to a floor. An agent will reach 90% line coverage without being asked twice, and the number means very little: the agent knows what coverage tools measure and will write tests that hit lines. Coverage confirms the code was executed. It says nothing about whether the execution checked the right thing. It still has one honest use, finding dead spots that nothing exercises, but as a quality score it is now actively misleading.

AI-generated unit tests are happy-path confirmations by default. They verify that the function does what the agent thinks it should do, which is exactly the thing in question. They are better than nothing, and they have real value as regression guards once validated, but they cannot be trusted as a quality signal on their own.

Code review loses its cheapest wins. Traditional review catches style problems and obvious logic errors, and AI code has neither: it is idiomatic, well-formatted, and smell-free by default. Review has to shift altitude, from "is this code clean" to "does this function do what the spec requires." That is a harder, slower kind of review, and most teams have not made the shift.

What the New Stack Looks Like

  1. A written specification, external to the code. This is now load-bearing rather than nice-to-have. If there is no spec that exists outside the agent's output, then you are testing whether the implementation is self-consistent, and the agent can be wrong in the code and the tests in the same way. An RFC, a reference implementation, a formal protocol description: anything the agent did not produce. Without it, all testing is circular.
  2. Mutation testing moves from academic curiosity to the most important check in the pipeline. It injects small faults (flip a comparison, remove a return, negate a condition) and verifies the suite kills each mutant. It measures test quality, not code quality, and that is precisely the thing you can no longer assume. A suite that does not kill a mutant flipping >= to > in a timestamp check is worthless regardless of its coverage number. The question is no longer "does the test run?" but "does the test care?" A suite with a mutation score under 60% is not a safety net, however many tests it contains.
  3. Property-based testing becomes essential for the same reason. Example-based tests check specific cases the author thought of, and AI authors also only check specific cases: the ones in the training distribution. Properties express invariants that must hold for all inputs ("for any nonce, timestamp, and key, parsing the serialized packet recovers the original fields exactly"). An agent cannot satisfy a property by pattern-matching to familiar examples. It has to actually implement the invariant.
  4. Fuzzing earns a promotion from security tool to general quality tool. AI-generated parsers look correct on training-distribution inputs, which are roughly the test cases you showed the model. A coverage-guided fuzzer generates inputs from outside that distribution, malformed in ways no training corpus contains. It is a genuinely blind test of the implementation.
  5. Published test vectors for anything cryptographic are non-negotiable. Agents implement standard algorithms well but occasionally produce a subtly wrong variant: wrong endianness, a missing domain separator, an off-by-one in key length. RFC and NIST vectors are the only ground truth that exists outside the model's training data.
  6. Differential testing used to be a luxury because writing two independent implementations was expensive. AI makes it almost free: ask two different models, or the same model with different prompts, to implement the same function, then run both against the same inputs. Any divergence is a bug in one of them. This is particularly effective for parsers and crypto primitives, where the spec is precise enough that two correct implementations must agree.
  7. Adversarial red-teaming with a second model. Prompt a separate AI to attack what the first one built. This is not penetration testing, it is a fast first pass that catches the cases where the builder model and the attacker model have different priors about what "weird input" looks like.

Three Practices With No Name in the Old Taxonomy

Behavioural diff on regeneration. When an agent rewrites a module (a refactor, a port, an optimisation pass), do not trust the test suite to catch drift; the agent probably wrote that too. Keep a snapshot corpus of inputs and compare old and new behaviour across it directly. The corpus plus a comparison harness is the right primitive, not the suite.

Spec divergence audit. Periodically hand the implementation to a separate model and ask it to write down what it thinks the spec is. Diff that inferred spec against your actual one. Where they disagree is exactly where the implementation drifted from intent without any test noticing.

Test quality scoring before trusting green. Before treating a passing CI run as information, score the suite itself: a sampled mutation score, the ratio of property tests to example tests. A green run from a suite that cannot kill mutants is a green light wired to nothing.

This Is Not Theoretical

We run a small security tool, a single-packet-authorization daemon written in Zig, where the parsing code and most of its tests were AI-assisted. The unit tests passed. The end-to-end suite, 52 scenarios, passed. Coverage looked respectable. Then we pointed a fuzzer at the three parsers, in the dumbest possible mode, no coverage instrumentation, just mutated bytes on stdin. It found three pre-authentication bugs in seconds each:

  • A u16 arithmetic overflow in the packet length check: with a crafted length field of 0xFFFF, the guard wrapped around and a release build would over-read the heap.
  • A denial of service in certificate parsing: the standard library's DER parser panics on malformed input rather than returning an error, and the daemon called it on attacker-supplied bytes before any authentication.
  • An integer-width inference bug where the compiler chose a 6-bit integer for a length calculation, which overflowed for names longer than 55 bytes.

Every one of these was in the "looks right" category. Every one passed review. Every one sat behind a test suite that was green, because the tests exercised well-formed packets, which is what the code was written against, which is what the tests were derived from. The circle was complete and the bugs lived happily inside it. The fuzzing harness that broke the circle took about a day to write.

Where the Old Metrics Still Fit

The classical machinery does not disappear; it gets demoted to a targeting system. Cyclomatic complexity (the number of independent paths through a function) still tells you which functions are riskiest, and the CRAP score, which combines complexity with coverage, still ranks where a change is most likely to break something unseen. Use them to decide where to spend mutation and fuzzing effort, not as evidence of quality. A complexity report takes half an hour and hands you the shortlist.

The Inverted Pyramid

Put together, the priority order for an AI-assisted codebase inverts the traditional pyramid. The spec comes first, because without it everything else is circular. Mutation testing comes second, because it validates the tests rather than the code. Properties third, fuzzing fourth for anything that parses external input, test vectors and differential runs for the security-sensitive core. Integration and end-to-end tests keep their place as system-level confirmation. Coverage drops to the bottom, a floor check for dead code. And a behavioural diff guards every significant regeneration, because the agent that rewrote the module also rewrote its alibi.

The deeper shift is in what a green build means. With human authors, green meant "the code does what the author intended, as far as the author could check." With AI authors, green means "the code does what the code does." Recovering the first meaning takes deliberate machinery: an external spec, tests that are themselves tested, inputs nobody chose, and a second opinion that does not share the first one's blind spots.

The whole testing playbook assumes the tester meant something different from the code, so a failing test marks where the code drifted from intent. AI dissolves that at the root: when one agent writes both the code and its tests, the tests describe what the agent built, not what you meant. The green build is circular, and review and coverage miss it, because the code looks clean and the numbers look good.

What stops working

The cheap, reliable signals degrade at once:

  • Coverage becomes a floor, not a score. An agent hits 90% on demand; the number proves lines ran, not that anything was checked. Keep it for spotting dead code.
  • AI unit tests are happy-path confirmations of the agent's own assumptions. Fine as regression guards once validated, useless alone.
  • Review loses its cheap wins: AI code is idiomatic and smell-free, so it must climb from "is this clean" to "does this match the spec."

The new stack starts with a spec

Everything turns on one artifact the agent did not write: an RFC, a reference implementation, a formal description. Without it you are only testing self-consistency, where the agent can be wrong in code and tests the same way. With it you can probe what the model never saw.

Mutation testing becomes the top check: inject faults, confirm the suite kills each, and treat anything under 60% as no net. Property tests and fuzzing hit invariants over all inputs and malformed inputs outside the training distribution, while published NIST vectors and two independent AI implementations checked against each other supply outside ground truth.

The question is no longer "does the test run?" but "does the test care?"

Three checks have no name in the old taxonomy: a behavioural diff of old against new output on each regeneration, a spec-divergence audit where a separate model writes down the spec it infers, and scoring the suite before trusting green.

What green means now

This is not theoretical. We pointed a dumb byte-fuzzer (just mutated bytes on stdin) at three parsers in a Zig auth daemon whose AI-written tests were all green. It found three pre-authentication bugs in seconds: a length field that wrapped a bounds check, a DER parser that panics on malformed input, and an inferred-width integer that overflowed past 55 bytes. Each passed review and sat behind a green suite exercising only well-formed packets.

With human authors, green meant "the code does what the author intended, as far as they could check." With AI authors, green means "the code does what the code does." Recovering the first takes deliberate machinery: an external spec, tests that are themselves tested, inputs nobody chose, and a second opinion without the first one's blind spots.