Back

What Stops an AI From Making Up a Number

Provenance, tiers, and a forgery guard

SE

Shariff Elkordy

July 2026

There's a reasonable objection to every "AI for structured credit" pitch, and it's worth stating plainly: everyone has tools now. Code interpreters, calculators, engine APIs. Bolt a real cashflow model onto a language model and the arithmetic stops being guesswork.

That objection is right, and it doesn't go far enough. A deterministic engine fixes the math. It does nothing about the reporting. A model with a perfect calculator can still write an unsourced number into a term sheet — a coupon it invented, a threshold it half-remembered, a loss severity it defaulted to — formatted identically to the figures that came out of the engine, sitting in the same table, carrying the same confident tone.

That's the failure that actually costs you. Not "the model can't do math," but "I can't tell which of these numbers I'm allowed to defend." So the interesting question isn't whether an AI has an engine. It's what happens to a number between the engine and the page.

Loan tapeYour questionGraamCode writerwrites Python to your intentCashflow enginePerformance dataRating methodologiesPriced dealstress-testedreproducible — it rerunsProvenance & anti-fabricationevery figure traces to a source, or it's refusedWAC 20.4% ← the tapeF-1 100% writedown ← waterfall4.212% coupon ← stated, labeled
Amber writes the code; teal are the deterministic primitives it can only compose. Every figure traces to a source, or it's refused.

1. A figure is an object, not a number

In Graam, a primitive doesn't return a float. It returns a typed result that carries its own provenance — permanently attached, and frozen once stamped:

  • source_fn — the function that produced it (pool_wac, sp_rmbs_ff)
  • source_ref — the canonical source key, down to the version (sp_rmbs_criteria.yaml@aaa_ff_pct)
  • inputs — the resolved parameters: which file, which filters. Scope disclosure, so you know what population the number describes
  • basis, as_of, table_used — how it was weighted, the data as-of date, the table read
  • upstream — the lineage, when this value was derived from other values
  • bridge_version — the contract revision that produced it

The number and its origin are the same object. You cannot pass the number along and drop where it came from, because there is no bare number to pass.

2. Provenance has tiers — and the model may not author the hard ones

Not all origins are equally strong, and pretending otherwise would itself be a form of fabrication. So provenance is tiered by how strongly the origin is guaranteed:

TierWhat it means
deterministicProduced by a vetted engine function. The model may not author it.
canonicalA constant bound from a repo source — a rating-agency criteria file, for instance
derivedComputed at runtime from other results; lineage tracked and checked

Read the first row again. It isn't a request. The deterministic and canonical tiers can only be minted inside the engine layer — the guard isn't importable from the outside, and live result objects don't expose it. Constructing one from outside is refused as forgery.

This is the difference between a system that has been told not to invent figures and a system in which inventing them is not an available move. Prompts are advice. This is a wall.

3. The exit is guarded too

A guard at the source is worth little if anything can be appended at the end. So the reporting layer applies the same rule: hand it a bare number, a hand-built table, a DataFrame you assembled yourself, and it refuses the deliverable outright — that figure isn't a typed result, so it doesn't ship.

If you genuinely need to surface something computed at runtime, you wrap it explicitly and declare what it came from, and that lineage has to terminate in deterministic or canonical results. You can compute new things. You cannot launder an invented number into the report by computing with it.

4. Derived and stated are never blurred

Here's the part that matters most in practice, and the one most systems get wrong. In the consumer-ABS deal I worked through in the previous article, the A-1 coupon is 4.212%.

Graam did not compute that number. I told it. It was my stated input — my coupon schedule, handed to the model as a constraint. And the deal says so. A caller-stated coupon is marked as stated and disclosed as the user's number. An engine-derived placeholder coupon — the kind anchored to the pool's WAC because nobody supplied a real one — carries an explicit "not market-priced" label instead. The two are never presented as the same kind of fact.

FigureWhere it came from
WAC 20.40%measured from the tape
F-1 100% writedownthe cashflow waterfall, at 15.74% CNL
A-1 coupon 4.212%stated by me — labeled as stated

Two of those I can defend as computation. One I can only defend as an assumption I supplied. A tool that renders all three identically has quietly made the third one indefensible.

5. When it can't source something, it refuses

The last piece is the least glamorous and the most important: what happens at the edge, where a helpful-sounding system reaches for a plausible default.

  • No pool balance resolvable from the tape? It raises an error naming the problem. It does not select a round number.
  • No tape, no document, and no stated pool size? It asks — once — rather than inventing a balance and a coupon and proceeding on top of them.
  • Asked for a metric the asset class doesn't have — an LTV or occupancy split on an unsecured consumer pool? Rejected as fabrication; those fields don't exist for that collateral.
  • No source for a trigger threshold? It describes the mechanic and states that thresholds are deal-specific covenants set at pricing. It does not print an invented number.

A system that fails loudly is more useful than one that fails plausibly. The plausible failure is the one that reaches the term sheet.

6. What we don't claim

An article about anti-fabrication should be careful not to fabricate its own guarantees. The derived tier is explicitly the soft one: values computed at runtime are provenance-tracked and checked, but not structurally proven the way an engine-minted figure is. We label that difference rather than papering over it, and the tier is visible on the value itself.

So the claim is not "every number is proven." It's narrower and more useful: every number tells you how strongly it can be trusted, and the strong tiers cannot be forged. You always know which kind you're holding.

7. Why it matters

Flexibility is what makes an AI analyst usable — you describe the work in plain language and it writes the code. Provenance is what makes the output defensible. Most of the industry is racing to solve the first problem and treating the second as a documentation exercise. It isn't. It's a type system, a tier model, and a forgery guard — enforced where the value is created and again where the report is assembled.

The worked deal that produced the figures above — tape to stratification to capital stack to stressed waterfall — is live and rerunnable. Every number in it will tell you where it came from.
What Stops an AI From Making Up a Number — Graam