Last quarter a risk report landed on my desk covering a mid-cap lending market. Nine sections. Forty-one fields. Every cell read N/A. The analyst attached a note calling the asset clean โ no flags, no red marks, nothing to worry about. That is a category error, and I have now watched it happen in three separate engagements over eighteen months. The report was not telling me the protocol was safe. It was telling me the ingestion layer had returned an empty array, and nobody upstream had noticed.
The pipeline that produces these reports has four stages. A fetcher retrieves the source document. A renderer executes whatever client-side JavaScript is required to expose the body. A deconstructor extracts structured information points โ supply figures, unlock schedules, contract addresses, integration partners, jurisdictional footprint. A scoring model consumes those points and emits a verdict.
Every stage is a trust boundary. Most teams audit only the last one.
When stage one fails, nothing raises. The fetcher returns a 200 with a paywall interstitial. The renderer returns a shell with an empty div. The deconstructor, handed a document it cannot parse, does what every well-behaved parser does under pressure: it emits a valid object populated entirely with defaults. Empty list. Unclassified domain. Confidence unassessed. Author stance undetermined. All schema-valid. All semantically void.
The failure then propagates silently because the downstream model has no representation for "unknown." It has a representation for "zero," and it has a representation for "neutral," and those two are close enough in most scoring rubrics that the distinction disappears at render time.
I have seen this exact shape on-chain. A price feed that returns zero instead of reverting. A liquidation engine that reads zero as a legitimate price rather than a missing one, and settles every position in the book. The arithmetic is correct. The semantics are fatal.
So the first thing I check in any risk artifact is not the verdict. It is the length of the information point array. If that array is empty, every downstream number is a language model's best guess dressed in a schema, and the correct output is an error code โ not a report.
Three layers fail independently, and they fail with different signatures. Fetch-layer failures look like encoding corruption or a truncated body; the byte count is wrong before the parser ever sees it. Render-layer failures look like a document that is structurally valid but content-free, usually because the page is a JavaScript shell or a consent wall. Mapping-layer failures are the quietest: the upstream JSON uses a key the downstream deserializer does not recognize, the deserializer silently falls back to its default, and the default happens to be "unclassified." The document is fine. The wiring is wrong. You get a complete, well-formed, entirely fictional profile of an asset nobody actually read about.
That last one should scare you, because it is deterministic. It reproduces. It will produce the same confident emptiness for every input, and it will never once throw.
In 2018 I spent four hundred hours inside EtherDelta's matching engine and filed twelve bugs, most of them integer arithmetic. The one that mattered was an underflow: subtract past zero and the unsigned integer wraps to 2^256-1, a number large enough to pass every sanity check downstream. The engine did not crash. It produced a plausible, enormous, wrong number and kept trading. Missing data behaves the same way. Coerce null to zero and you have not lost information โ you have manufactured it, in a format that looks identical to the real thing.
The gate is cheap. Make "information points โฅ 1" a hard precondition. Reject at the boundary. Return an error code to the caller and let the caller decide whether to retry, escalate, or abstain. Abstention is a legitimate output. It is the only honest one when the input is empty.
I audited an AI-inference ZK protocol in 2025 and found a fifteen percent overhead in the constraint system; we cut gas forty percent with recursive proof aggregation. The protocol proved, cryptographically, that the model executed correctly on the given input. It proved nothing about the input. A valid proof over an empty witness is still a valid proof. Correctness of computation and correctness of premise are different claims, and the industry's tooling overwhelmingly attests the first while assuming the second.
The same assumption sits inside every lending market I have reviewed. Aave and Compound do not discover an interest rate; they evaluate a piecewise curve whose kink and slope were chosen by a human in a governance forum. The curve is not a measurement of supply and demand. It is an opinion, hardcoded. Feed that opinion a utilization figure derived from a stale or coerced input and it will emit a rate with four decimal places and no uncertainty attached. Precision is not accuracy. The dashboard cannot tell the difference either.
The test I run now takes an afternoon. I feed the pipeline a document with the body stripped and a valid HTTP status. A correct implementation returns a non-zero exit code and writes nothing to the report store. An incorrect one returns nine sections of N/A and a note that says clean. I then feed it a document with a single renamed key and confirm the divergence is detectable at the boundary rather than three layers downstream. Chaos testing for ingestion is not exotic. It is a unit test with a hostile fixture, and almost no team has one.
The cost asymmetry justifies the paranoia. A false positive in a risk model โ flagging a healthy protocol โ costs you an opportunity. A false negative costs you the position. A coerced N/A is the purest false negative available: it is not a wrong answer, it is the absence of an answer wearing the costume of a reassuring one. The highest-severity finding in my last three audits was not a reentrancy path. It was a field that had never been populated.
Here is the part that makes auditors unpopular. The industry has built an entire verification economy around computation โ formal verification of contracts, proofs of execution, attestation of model outputs โ and almost none of it touches ingestion. We prove that the function returns what the code says it returns. We do not prove that the argument was ever supplied.
And the rendering layer launders the ambiguity. N/A in grey, set at the same weight as a green checkmark, read by a human in three seconds and by an agent in three milliseconds. Neither of them distinguishes "assessed, found neutral" from "never assessed." The distinction exists in the schema and dies in the UI.
There is a governance layer to this too, and it is the one nobody audits. The schema mapping โ which key maps to which field, what the default is, what counts as missing โ is a configuration file. It is not on-chain. It is not in the repository's review path. In every case I have examined, write access to that file sits with two or three engineers and a multi-signature wallet that has never once been rotated. "The data layer is decentralized" is a claim about the source, not about the pipe. The pipe has admins. The admins have defaults.
Watch the ingestion layer, not the verdict. When the next report tells you an asset is clean, ask for the array length. Ask what happens when the fetcher hits a consent wall. Ask who can edit the schema mapping, and when that key was last rotated. Resilience isn't audited in the winter โ it is audited in the empty response, at 3 a.m., when the pipeline returns nothing and something downstream still has to decide whether to trade. The bottleneck isn't the model. The code doesn't care how confident the report looked.


