Tech9/21/2026·5 min read

Verification Belongs to the Writer

Our input engine hashed 80 MB of shipped data on every launch — 557 ms, against a 224 microsecond keystroke decode. The data had already been proven once, by the writer.

Two orders of magnitude

Our input engine ships as a bundle: a lexicon, a static language model, a long-tail expert model, and the manifests that bind them together. It is the largest thing the app loads, and on launch the runtime opened it by walking every artifact, reading every byte, and hashing it against the manifest.

That is the right thing to do exactly once. It is the wrong thing to do on every cold start, and the difference is not subtle:

Decode one keystroke224 µs
Assemble the entire engine4.7 ms
Verify every byte of the bundle557 ms

Measured on a fast machine, warm page cache, with the compiler tuned for the local CPU — that is, under conditions that flatter the check. The verification cost more than assembling the whole engine by a factor of a hundred.

And it could not be moved somewhere harmless. The engine's input queue sits behind startup, so keystrokes typed before it is ready are buffered and replayed. The entire cost of hashing lands between the user typing their first letter and the first row of candidates appearing — on device, a window that runs to tens of seconds.

It was not protecting anything

The interesting part is that the check was never buying anything.

Those bytes are proven exactly once, by the process that wrote them. A build-time release gate hashes the bundle before it is allowed to ship. On install, the thing that unpacks assets to disk hashes what it just wrote. When an update finishes downloading, the thing that downloaded it verifies the download before accepting it. Each of those is the only moment at which the content could have been wrong, and each of them is a moment when somebody is actually present and able to do something about a failure.

After that the data is read-only. Re-deriving the proof on every read cannot discover corruption that did not happen between two reads, because a read does not modify anything. The reader learns nothing the writer did not already know, and pays for it on the critical path.

On iOS the argument is stronger still and almost embarrassing in hindsight: the shipped data lives inside a code-signed bundle. Changing a single byte means the app does not launch. Anyone with the ability to change it already has code execution.

So the runtime stopped hashing. What it does now is check structure — that each file is a regular file, that each length matches its manifest, that the manifests agree with each other. It verifies that the bundle is coherent. It does not verify that it is unaltered, because on iOS that is already guaranteed one layer up and on Android it was guaranteed at install time.

Two grades, chosen by the caller

The change was not "delete the verification." That would have been the wrong instinct and would have thrown away the check that genuinely matters in the places it genuinely runs.

Instead there are two grades, and the caller picks:

pub enum ArtifactIntegrity {
    /// Read every byte and compare length and SHA-256 against the manifest.
    Measured,
    /// Check structure only: regular files, lengths, cross-manifest binding.
    Declared,
}

Everything that has to read the whole dataset anyway keeps full measurement — the publisher, the installer, the quality evaluator, every offline tool. None of them is paying a latency cost for a check they would perform anyway; the data is already in memory. The runtime asks for the cheaper grade.

That distinction is what makes this a rule rather than an optimization. Once the cheap path exists, the expensive one has to be asked for, which means every future caller has to make a decision instead of inheriting one.

The guard admits its own exception

An invariant that has no failure story decays into a rule people route around, so this one is enforced — with forbidden globs over the call sites that reintroduce full verification:

forbid = [
  "*runtimebundle::open(*",
  "*runtimebundle::open_for_production(*",
  "*verify_runtime_bundle(*",
  "*ensure_unchanged(*",
]

The interesting detail is the shape of those globs. They pin the measurement-taking entry points. open_installed — the one the runtime calls — does not match, which is deliberate and is the entire reason the rule can be written at all: a guard that cannot express the exception it exists to permit is a guard that gets deleted the first time it blocks real work.

So the rule reads, in effect: prove it where you wrote it, and nowhere else. And the set of places where a byte-for-byte check is still legal is written down rather than remembered, which is the only reason it survives the next person who thinks full verification sounds safer.

Why this generalizes

The pattern is older than shipping model bundles. Any immutable artifact gets verified on read because verifying is easy to justify and nobody has measured what it costs on the path that matters:

  • a checksum re-computed on every read of an immutable file;
  • a signature re-verified on every parse of a signed blob;
  • a test suite re-derived from scratch on every run;
  • a schema validated against an instance on every request when it was validated against the code at build time.

In every case the question is the same, and it is a question about ownership rather than about performance:

Who is the party that could have made this wrong, and when did they last have the chance?

If the answer is a specific process at a specific moment, the proof belongs to that process, once, and everyone downstream is reading something already accounted for. If the answer is "anybody, any time," then the data is not immutable and the check has to stay.

The most common failure is not skipping the check. It is running it in the wrong place — paying, in the latency of a first letter or a first frame, for a guarantee that was already established earlier by someone who could have stopped the line.

Takeaways

Measure the cost of a verification before defending it. Ours was two orders of magnitude more expensive than the thing it guarded, on the one path in the product that a user notices. "It is cheap" is an assumption, and this class of assumption is almost never measured.

Ask where the cost lands, not just how big it is. The 557 ms was not spread across a launch; it fell entirely between the first keystroke and the first candidate, because the engine's queue buffers input typed before startup. Any check added to an initialization path is a latency decision.

Then ask who could have made the data wrong. If a single writer could, at a known moment, with a listener present, that is where the proof belongs — once. After that, re-deriving it is not caution; it is the same computation, billed to the reader, on the critical path, forever.

Finally, keep the cheap path explicit and the expensive path on request. Do not delete the full check — keep it for the callers who read everything anyway, make the runtime ask for structure, and write the rule so that it blocks the wrong call sites while permitting the right one. An enforcement that cannot name its own exception gets deleted, and then everyone goes back to hashing eighty megabytes on every launch.

Products in this post

Keep reading