/blog · the engineering closure between stochastic models and deterministic audits

ENTRY № 16 · ENGINEERING · STOCHASTIC EVIDENCE
PUBLISHED 2026-05-09 · ~12-MIN READ · WARRANT ENGINEERING

the stochastic evidence problem.

An LLM is non-deterministic by design. A regulator audit is deterministic by definition. Producing evidence that survives the audit-replay test from a stochastic model is the central engineering problem of agentic compliance. Four moves narrow the gap: trace canonicalisation under RFC 8785, a pinned pipeline configuration hashed into the signed bytes, eval-set anchoring, and residual-uncertainty disclosure. None of them closes it, and this page is careful about which half is proven.

THE PROBLEM
stochastic ⇒ deterministic· replay-test failure
An auditor who runs the same trace through your pipeline twice and gets different evidence has caught you. The model is non-deterministic; the evidence must not be.
THE FOUR MOVES
canon · config · eval · uncertainty· partial closure
RFC 8785 JCS canonicalisation · a pinned configuration digest inside the signed bytes · eval-set anchoring · residual-uncertainty disclosure. Each narrows one part of the gap; two of the four are not built yet.
WHAT IS PROVEN
the canonical form· not the accuracy
RFC 8785 makes the evidence record's hash a function of the value, not the encoding — that part is deterministic by specification and anyone can check it. Pipeline classification accuracy is not yet measured. No accuracy figure is published on this page.
W
FOUR MOVES · CANONICAL FORM DETERMINISTIC BY SPEC · ACCURACY AND REPLAY NOT MEASURED
RFC 8785 JCS canonicalisation, a pinned configuration digest inside the signed bytes, eval-anchoring, residual-uncertainty disclosure · what can be checked today, and what cannot.
01 · THE AUDIT-REPLAY TEST

Hand my staff the same trace. Do they produce the same evidence?

The regulator's mental model · deterministic by definition · the load-bearing claim of the entire engineering effort

The regulator's mental model of an audit is simple. Hand my staff the same trace you handed your pipeline, and the staff should produce the same evidence claims. If they do not, your evidence is not evidence, it is a probabilistic guess that happened to land somewhere defensible the day you ran it. The auditor does not need to be hostile to surface this failure mode. They only need a second machine and a Tuesday.

Most engineering effort in this category is spent shipping fast, not reproducible. The Anthropic SDK call returns a structured response, the prompt looked right in staging, the PDF rendered, ship it. A replay test was never wired up. The pipeline is one-shot, the trace logs are present, and any second run is presumed to behave identically because the inputs are the same. They do not behave identically. Two API calls into Claude with the same trace, the same prompt, and the same model id can and do return different bytes. Sometimes the difference is one whitespace token, sometimes it is one citation, sometimes it is one risk-tier classification flipped from limited to high.

The audit-replay test is the simplest possible discipline a regulated AI product can hold itself to. Take an attested package, ingest its trace through the pipeline a second time, and assert that the evidence-bearing artefact is byte-equivalent to the first run on every claim that does not declare itself probabilistic. Everything that follows is in service of one outcome: that the evidence record reduces to the same bytes on a second run. That is a property of the record, and it holds by specification. Whether the model stages replay identically is a separate and currently unmeasured question — the four moves narrow it, they do not close it, and no claim on this page should be read as saying they do.

Determinism is not a property of the model. It is a property of the evidence record produced from the model. The shape of the engineering closure · why the four moves matter
02 · WHY LLMS FAIL THE REPLAY TEST

Non-determinism comes from at least four places.

Sampling temperature · GPU kernel non-determinism · provider-side load balancing · prompt-cache state

An LLM API call is not a function call. It is a network request to a fleet of inference servers, each running attention kernels on a non-deterministic GPU, behind a load balancer that picks a replica without telling you which one, with a prompt-cache layer that may or may not be warm. None of that is exposed in the SDK signature. The signature looks pure. The execution is not.

Randomised sampling

The next-token sampler is randomised, so every call can sample a different completion. This is the variance source an API consumer thinks of first. It is also the one Warrant has no handle on: the parameters that would constrain it are not accepted by the model the pipeline runs.

GPU kernel non-determinism in attention

CUDA reductions in attention are order-dependent. Two runs of the same kernel on the same GPU, with the same inputs, can produce slightly different floating-point outputs because the reduction order across thread blocks is not guaranteed. The variance is small per token, but it can accumulate into a different next-token argmax.

Provider-side load balancing

The provider routes a request to whichever replica has spare capacity. Replicas may be on different hardware revisions, with different kernel libraries, with different floating-point reduction strategies. The routing is invisible to the caller. Two identical API calls one millisecond apart can land on two different replicas.

Prompt-cache state

Prompt caching is documented as a prefix-match cost and latency optimisation, not as something that changes response content. What it does change is the execution path: a cache read and a cache miss are not the same journey through the stack. The usage block returns cache_read_input_tokens and cache_creation_input_tokens, and a caller has to log them explicitly to reason about a divergence after the fact.

Here is the part that reorders the whole problem. Anthropic's current models do not expose the knobs an engineer reaches for first: temperature, top_p and top_k are rejected outright on Opus 4.7 and later — including claude-opus-5, which is what all four pipeline stages run — and the Messages API has no seed parameter at all, so there is nothing to pin. As of the Anthropic Messages API reference, 6 August 2026. An earlier version of this page described setting a temperature and fixing a seed; neither is possible on the system it was describing, and both have been removed rather than restated. Without engineering intervention two identical API calls can return different outputs, and the intervention cannot happen at the sampling call. It has to happen in the record.

The engineering question is not how do we make the LLM deterministic. The engineering question is given an LLM that is not deterministic, how do we produce evidence that is. The four moves below are the answer.

03 · MOVE 1 · TRACE CANONICALISATION (RFC 8785)

Canonical bytes are the precondition for everything.

RFC 8785 JSON Canonicalization Scheme · IEEE 754 number form · recursive sorting by UTF-16 code unit · minimal-escape rule

Before any hashing or comparison happens, the JSON record has to land in a canonical form. The JSON Canonicalization Scheme defined in RFC 8785 is the answer the standards community has converged on. The opening of the spec is exact about what canonicalisation means.

"Cryptographic operations like hashing and signing need the data to be expressed in an invariant format so that the operations are reliably repeatable. … The output from JCS is a 'hashable' representation of JSON data that can be used by cryptographic methods." RFC 8785 · §1 · Introduction · as of June 2020

JCS produces a unique deterministic byte serialisation of equivalent JSON values. Two documents that mean the same thing serialise to the same bytes; two documents that differ in any meaningful way serialise to different bytes. The properties JCS pins down are the ones that vary across naive encoders: IEEE 754 number form, member-name sorting at every nesting depth, escape-sequence canonicalisation, and whitespace stripping. One property it deliberately does not touch is Unicode normalisation. §3.1 is explicit: "Although the Unicode standard offers the possibility of rearranging certain character sequences, referred to as 'Unicode Normalization' [UCNORM], JCS-compliant string processing does not take this into consideration. That is, all components involved in a scheme depending on JCS MUST preserve Unicode string data 'as is'." So a precomposed é and a decomposed e + combining acute remain distinct under JCS, and a caller that needs them to compare equal has to normalise before canonicalising. That is a real constraint on the producer, not a feature of the scheme — and an earlier version of this page had it exactly backwards.

python · the canonical form is a one-liner
import rfc8785
import hashlib

# JCS canonicalisation: stable across Python versions, OS locale, dict-insertion
# order. Pins IEEE 754 number form and sorts member names by UTF-16 code unit
# at every nesting depth. RFC 8785 §1 notes that JWS took the other route —
# base64url per RFC 7515 — so that data need not stay JSON. JCS exists for
# the case where it must.
canonical_bytes = rfc8785.dumps(trace_dict)
trace_hash = hashlib.sha256(canonical_bytes).hexdigest()

# trace_hash is now a function of the JSON value, not of the encoding.
# Two engineers on different machines, different Python versions,
# different locales, get the same hash for the same logical trace.

The temptation, when an engineer first touches this problem, is to reach for json.dumps(trace, sort_keys=True). That is closer to canonical than the default, and it is not enough. It sorts by Python code point, where JCS §3.2.3 requires UTF-16 code units — the two orders disagree for any member name above the Basic Multilingual Plane. It does not pin float form, so a number can serialise differently depending on the interpreter's repr (the notation thresholds around 1e21 and 1e-7 are where this bites). Its escaping depends on the ensure_ascii flag rather than on a fixed minimal-escape rule. And none of its behaviour is specified as a stable contract, so it is a property of the interpreter rather than of a standard. Each of those is a divergence a regulator can find by accident, and each breaks the record's checkability downstream. Both encoders leave Unicode normalisation to the caller — that is the one line where they agree.

Property RFC 8785 JCS json.dumps(sort_keys=True)
Unicode normalisation No — §3.1 requires string data preserved as-is No — same behaviour
IEEE 754 number form Yes, single canonical representation No, depends on Python repr
Recursive key sorting at every depth Yes, by UTF-16 code unit (§3.2.3) Yes at top level, by code point, and depends on nested types
Escape-sequence canonicalisation Yes, minimal-escape rule No, ASCII vs ensure_ascii flag-dependent
Cross-version stability Specified, immutable Not specified — a property of the interpreter, not of a standard

Everything downstream assumes a canonical byte string. The canonical form is what makes the evidence record independently verifiable without contacting Warrant, regardless of who reconstructs it. The companion pillar at /blog/four-layer-evidence-stack sits on the same assumption: that the canonical form is reproducible. JCS makes that assumption real.

The property is testable, and it is tested against the RFC rather than against our own expectations. The canonicaliser is gated by the worked example in RFC 8785 §3.2.3 — the spec's own input and its own expected output, byte for byte — plus a test for the case a naive implementation gets wrong: object keys whose UTF-16 order and code-point order disagree. That is the whole reason the sorting basis matters in practice, so it is worth showing rather than asserting.

python · tests/test_cosign_verification.py
# RFC 8785 conformance - the gate on the canonicaliser

def test_rfc8785_section_3_2_3_sample() -> None:
    """RFC 8785 section 3.2.3 worked example: its input, its expected output."""
    value = {
        "numbers": [333333333.33333329, 1e30, 4.50, 2e-3, 1e-27],
        "string": sample_string,
        "literals": [None, True, False],
    }
    expected = (
        '{"literals":[null,true,false],'
        '"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],'
        '"string":' + expected_string_field + "}"
    ).encode()

    assert canonicalize(value) == expected


def test_object_keys_sort_by_utf16_code_unit_not_code_point() -> None:
    """The non-BMP case, which a naive sort_keys=True inverts.

    U+1F600 is UTF-16 D83D DE00, so it sorts BEFORE U+FB33 by code unit
    and AFTER it by code point. Python's own sort gets this backwards.
    """

One canonical form, infinite valid encodings before it, one byte string after. That is the move.

04 · MOVE 2 · CONFIGURATION PROVENANCE

Pin what produced the verdict. Hash it into the record.

Model id per stage · prompt bytes · corpus digest · confidence floors · one digest, inside the signed bytes

The next move is at the model boundary, and it is not the one this section used to describe. Sampling parameters are the part of the contract a caller would reach for if the caller could; on the models this pipeline runs, it cannot. What remains is provenance. Pin every input that can change a verdict, hash the whole set, and put the digest inside the signed bytes. A reader who cannot re-run the model can still tell whether the thing that produced the verdict changed between two packages — which is the question an auditor actually asks.

All four stages run the same model, claude-opus-5, pinned in one place. That single source of truth matters more than it sounds: before it was consolidated, a prose table said one thing, the code ran another, and the eval harness scored a third — an eval grading a model the pipeline did not run. A digest over the configuration is what turns that class of drift from an internal embarrassment into something visible on the artefact.

python · api/spec/pipeline_config.py
# The only source of truth for which model each stage runs.
STAGE_MODELS: dict[Stage, str] = {
    "stage1_classify":              "claude-opus-5",
    "stage2_extract_actions":       "claude-opus-5",
    "stage3_assess_authorization":  "claude-opus-5",
    "stage4_map_obligations":       "claude-opus-5",
}

# Prompt file per stage. Their CONTENTS are hashed, not their names — a
# prompt edit changes verdicts and must change the digest.

# pipeline_config_sha256 is a digest over everything that can change a
# verdict, and it sits inside the signed bytes:
canonical = json.dumps(
    {
        "models": models,
        "prompt_digests": prompt_digests,
        "regulations_corpus_sha256": regulations_corpus_sha256,
        "satisfied_confidence_floor": satisfied_confidence_floor,
        "hard_refusal_confidence_floor": hard_refusal_confidence_floor,
        "inference_provider": inference_provider,
        "inference_model": inference_model,
    },
    sort_keys=True,
)

# No temperature and no seed appear anywhere in this file, because
# neither is accepted by the model: temperature/top_p/top_k are rejected
# on Opus 4.7 and later, and the Messages API has no seed parameter.

Note what is bound and what is not. The digest covers the model ids, the exact bytes of each stage's prompt, the digest of the regulation corpus the verdict was mapped against, and the confidence floors that decide whether a finding is downgraded or refused. It does not cover the model's weights, which Warrant does not hold, or the provider's serving stack, which Warrant cannot see. So the digest answers "was this the same pipeline" and never "was this the same computation". Conflating those two is the mistake this section previously made.

One non-negotiable point: the model id is pinned to a specific version. claude-opus-5 is a versioned model, not an alias. A model change has to be a code change to STAGE_MODELS, and because those strings are hashed into the config digest that sits inside the signed bytes, the change is visible in the record rather than silent. Two packages that disagree on the digest were produced by different pipelines, and a reader can see that without being told. The shape of the upgrade is discussed in the companion routing pillar at /blog/multi-model-routing.

05 · MOVE 3 · EVAL-SET ANCHORING

A regression set is the deterministic anchor. Ours is not built yet.

Correction, 2026-07-28 · this section previously published agreement scores, a citation-precision floor and a set of blocked model bumps that were never measured. Those claims are withdrawn.

The argument in this section is sound and the implementation is not finished, so read it as design rather than as telemetry. An eval set anchors a stochastic pipeline because it converts "the model changed" into a number you can gate a merge on. Without one, a model-version bump is an unbounded change: the prompts are the same, the code is the same, and the behaviour is different in ways nobody has bounded.

What exists today is the structural half. The model id is pinned to a specific version, so an upgrade has to be a code change and cannot arrive by alias drift. Every citation in the corpus is checked to resolve to enacted text on the regulator's own domain, so an amended or moved regulator page shows up as a failure rather than as a quiet wrong citation. Both of those are real and both are narrow.

What does not exist is the measured half. There is no labelled regression set, no human-agreement score, and no accuracy floor at the merge gate. Stage 1 classification accuracy is not yet measured. A measured number with named failure classes — which inputs the classifier gets wrong, and how — is the work in progress, and this page will carry it when it is real rather than before.

Stage Gated on accuracy today Failure mode the eval is being built to catch
01 · classify_trace No — not yet measured Risk-tier flip on edge-case lending traces
02 · extract_actions No — not yet measured Action-actor confusion on multi-actor traces
03 · assess_authorization No — not yet measured Reversibility flag on partial-execution actions
04 · map_obligations Structural only — every citation must resolve to enacted text Sub-clause drift on amended regulator texts

The right-hand column is the target list, not a record of catches. The Map stage is the only one carrying any gate today, and note what that gate cannot see: it establishes that the cited text exists at the regulator's URL, not that the pipeline picked the clause the action actually triggers. Those are different properties, and only the first one is currently enforced.

python · the gate that exists, and the one that does not
import pytest
from warrant.corpus import resolve_all_citations

def test_every_citation_resolves_to_enacted_text():
    # this gate is live. it is structural, not an accuracy gate:
    # it proves the cited text is at the regulator's URL. it does
    # NOT prove the pipeline chose the right clause.
    unresolved = resolve_all_citations()
    assert not unresolved, (
        f"{len(unresolved)} citations no longer resolve; merge blocked"
    )

@pytest.mark.skip(reason="no labelled regression set exists yet")
def test_stage_accuracy_against_human_labels():
    # the accuracy gate is not implemented. there is no labelled
    # set to score against and therefore no floor to enforce.
    # building it — with named failure classes — is the open work.
    ...

An eval-set anchor is what would make the rest of the system safe to upgrade. The model is non-deterministic in the small; a regression set is how you find out whether the small variance produced a meaningful change in the large. Until that set exists, a model upgrade here is reviewed by reading diffs, which is a weaker control and should be described as one. The honest statement of the current position: the canonicalisation and decoding moves are implemented and checkable, the eval-anchoring move is not.

06 · MOVE 4 · RESIDUAL-UNCERTAINTY DISCLOSURE

Refusal is a recorded outcome.

Per-action confidence and refusal flags · receipt-level uncertain and oversight counts · what the schema carries

The regulator does not need a deterministic answer. The regulator needs an honest one, and needs to see where the honesty runs out. That means the record has to carry its own uncertainty on its face rather than presenting every finding at the same confidence and leaving the reader to guess which ones were close calls.

Here is what the published schema actually carries, which is narrower than an earlier version of this page claimed. Each Stage 3 authorization row has a confidence number between 0 and 1, a refusal boolean, and an optional refusal_reason, alongside the four judgement fields — whether the action was within purpose, whether preconditions were met, whether it was reversible, and whether human oversight was appropriate. The receipt then lifts the aggregate to the top level: uncertain_count for Stage 4 obligations marked uncertain, incomplete_oversight_count for actions where oversight was judged absent or uncertain, gaps_count, and a refusal_reason set when the pipeline halted fail-closed. There is no uncertainty enum, no top_alternatives field, and no attestation-incomplete package status. The Messages API returns no logprobs or alternative completions, so a top-five-completions field could not be populated even if the schema declared one.

json · api/spec/warrant-v1-evidence.schema.json
// Stage 3 per-action authorization rows
"authorizations": {
  "type": "array",
  "items": {
    "type": "object",
    "required": ["action_id"],
    "additionalProperties": false,
    "properties": {
      "action_id":                  {"type": "string"},
      "within_purpose":             {"type": ["string", "null"]},
      "preconditions_met":          {"type": ["string", "null"]},
      "human_oversight_appropriate": {"type": ["string", "null"]},
      "reversible":                 {"type": ["string", "null"]},
      "justification":              {"type": "string"},
      "confidence":  {"type": "number", "minimum": 0, "maximum": 1},
      "refusal":     {"type": "boolean"},
      "refusal_reason": {"type": ["string", "null"]}
    }
  }
}

// and on the receipt, the aggregates a reader sees first:
//   uncertain_count            - Stage 4 compliance='uncertain' count
//   incomplete_oversight_count - oversight 'no' or 'uncertain' count
//   refusal_reason             - set when the pipeline halted fail-closed

A refusal is not an error. It is an outcome, and the package records it the way it records any other: the reason is stated, the actions that were assessable keep their findings, and the reader can see which is which. An honest gap is more defensible than a confident invention.

There is exactly one refusal in this system that is deterministic, and it is worth separating from the rest. A trace that exceeds the ingestion cap is refused before the model is called at all — the check is a pre-model gate, so the outcome does not depend on sampling, on the provider's stack, or on anything the model does. Every truncated trace refuses, every time. That is the only determinism claim about refusal this page makes.

Whether a trace that clears the gate is signed or refused is not deterministic, and saying otherwise would be the same failure the rest of this page is trying to avoid. An internal evaluation returned different outcomes across repeated runs against identical code, and a separate case was signed where refusal was the expected result. That is an open finding on the record, not a solved problem, and it is the reason no claim on this page describes the pipeline as deterministic end to end. Tracking a refusal rate over time and triggering a regression run on a spike would be a reasonable control to build; it does not exist today, and an earlier version of this page said it did.

07 · WHAT IS PROVEN, AND WHAT IS NOT

Two of these are checkable today. The rest is design.

Correction, 2026-07-28 · this section previously reported a production-telemetry table. That telemetry was never collected and the figures are withdrawn.

An earlier version of this page printed reproducibility and precision percentages here as if they came from a production run. They did not. There is no eval run behind them, so they have been removed rather than restated, and nothing replaces them until there is a run to report. What follows is the honest split between the properties a reader can check and the ones still open.

checkable · canonical form
CANONICAL HASH
RFC 8785 makes the hash a function of the JSON value, not the encoding. This holds by specification. Anyone can verify it against the RFC and against their own encoder without taking our word for it.
checkable · every citation
CITATION RESOLUTION
Every citation in the corpus resolves to enacted text on the regulator's own domain. Open one and read it. This proves the text is there — not that the pipeline picked the right clause.
open · not measured
CLASSIFICATION ACCURACY
Stage 1 classification accuracy is not yet measured. No labelled set, no agreement score, no floor at the merge gate. A measured number with named failure classes is in progress.
open · not measured
REPLAY STABILITY
No sampling controls are available on the model, so there is no greedy-decoding guarantee to appeal to, and no replay study has been run. Whether a trace is signed or refused has come out differently across runs on identical code. Open finding, not a result.

The first property is the strongest one on this page, and it is strong precisely because it does not depend on us. JCS does what RFC 8785 specifies. Two engineers on different operating systems, in different time zones, with different Python versions, reduce the same trace to the same canonical form. A reader who doubts it can implement the RFC and compare. That is what makes the evidence record independently verifiable without contacting Warrant.

The second is narrower than it sounds and the narrowness matters. A citation check establishes that the passage exists at the regulator's URL. It says nothing about whether the obligation cited is the one the agent's action actually triggered. Conflating those two is how a compliance product ends up confidently wrong, so the distinction is drawn here rather than left implied.

The third and fourth are open. Publishing an accuracy figure before it is measured would defeat the point of the whole post: an artefact a regulator can rely on is one whose claims she can check, and an unmeasured number fails that test no matter which direction it is wrong in.

08 · NIST AI RMF · MEASURE FUNCTION

Where this hooks into RMF MEASURE 2.5 and 2.13.

MEASURE 2.5 valid and reliable · MEASURE 2.13 effectiveness of TEVV metrics and processes · as of AI RMF 1.0, 26 January 2023

The NIST AI Risk Management Framework asks, after the MEASURE function is complete, that "objective, repeatable, or scalable test, evaluation, verification, and validation (TEVV) processes including metrics, methods, and methodologies are in place, followed, and documented". Two sub-categories carry the weight for this post. MEASURE 2.5: "The AI system to be deployed is demonstrated to be valid and reliable." And MEASURE 2.13: "Effectiveness of the employed TEVV metrics and processes in the MEASURE function are evaluated and documented." As of AI RMF 1.0, 26 January 2023. The four moves above are a partial TEVV instantiation against those two — partial because the measured half is the half that does not exist yet.

  • Reliability (MEASURE 2.5) · the canonical form is stable by specification, and that is a statement about the record rather than a measured system property. There is no trace-hash stability metric behind it, because none is needed for a guarantee that comes from the RFC — and none exists for the part that would need measuring, which is the model stages.
  • Validity (MEASURE 2.5) · this is the gap. 2.5 wants the deployed system demonstrated to be valid and reliable, and Warrant cannot demonstrate that yet: classification accuracy is not measured and no accuracy floor is enforced at the merge gate. The citation-resolution check is enforced, but it is a source-integrity control, not an accuracy control.
  • Effectiveness of TEVV (MEASURE 2.13) · also a gap, and the more honest one to name. 2.13 asks whether the evaluation process itself is working. An eval that returns different verdicts across runs on identical code is exactly the kind of thing 2.13 exists to surface, and it did.
  • Security and resilience (MEASURE 2.7) · this is what 2.7 actually covers, and it is not what this post addresses. Nothing above is a resilience control. An earlier version of this page mapped all four bullets to 2.7 and cited a dual-provider replay as the resilience statement; no dual-provider replay is built — the pipeline resolves one provider per run and nothing compares two — so that claim has been removed rather than restated.

The deeper hook is documented in the companion regulator pillar at /blog/nist-ai-rmf. The relevant point here is that the engineering moves are not auxiliary; they are where a TEVV process would live, expressed in code. Two of the four are built and checkable. The two that a NIST assessor would press hardest on — a demonstrated accuracy figure, and evidence that the evaluation process is itself effective — are the two that are not.

09 · ISO/IEC 42001 · ANNEX A.6

Where this hooks into ISO 42001 V&V.

ISO/IEC 42001:2023 Annex A.6 — AI system life cycle · paywalled standard, not part of the mapped corpus

ISO/IEC 42001 is the AI management-system standard, and one limit has to be stated before anything else in this section: it is a paywalled standard. The full text sits behind an ISO licence, so a reader cannot open this citation and check that we characterised it correctly. That fails the test every other citation on this site is held to, which is why paywalled standards are not part of the corpus Warrant maps against. This section cites Annex A.6, the AI system life cycle group, rather than a sub-control beneath it — a pinpoint into a document the reader cannot open is a claim that cannot be checked, whatever the disclosure says around it. Treat what follows as commentary on a standard you would have to buy, not as a verifiable mapping. On the substance: the canonicalisation and configuration-provenance moves are a partial V&V contribution. They are not a complete V&V package, because the measured half — evidence that the system performs to its stated specification under representative conditions — is exactly the part not yet built.

The relevant property for this post is that ISO 42001 V&V evidence is auditable at the management-system level, not just the system-instance level. An auditor reviewing the AIMS asks show me the V&V process, show me when it ran, show me what it caught, show me what was rolled back, show me how the rollback decision was made. Of those, Warrant can answer the model-version-pin history today — the configuration digest inside the signed bytes is exactly a record of what was in force when a package was issued. The rest, the V&V process an ISO auditor would actually ask to see, is the part not yet built, which is the same gap section 05 names.

10 · EU AI ACT · ARTICLE 12

Where this hooks into Article 12 logging.

EU AI Act, Regulation (EU) 2024/1689, Article 12 · applies from 2 December 2027 (Annex III), as deferred by Regulation (EU) 2026/1744 Art 1(40)(b)

Article 12 of the EU AI Act sets the logging duty in two layers, and the difference between them decides what actually applies here. Article 12(2) binds every high-risk system: logging capabilities must enable the recording of events relevant for identifying situations that may result in a risk within the meaning of Article 79(1) or in a substantial modification, for facilitating the post-market monitoring referred to in Article 72, and for monitoring the operation of systems referred to in Article 26(5). Article 12(3) then adds a minimum list — the period of each use, the reference database checked against, the input data for which the search led to a match, and the identification of the natural persons involved in the verification of the results under Article 14(5) — but only for the systems in point 1(a) of Annex III, which is remote biometric identification. A trace-attestation pipeline sits under 12(2), not 12(3), and three of the four items on that minimum list have no application here. An earlier version of this page presented the 12(3) list as Article 12's general requirements; that was an overbroad reading and it has been corrected.

"High-risk AI systems shall technically allow for the automatic recording of events (logs) over the lifetime of the system." EU AI Act · Regulation (EU) 2024/1689 · Article 12(1)

The logging requirement is the part the four moves bear on. Without canonicalisation, two equivalent traces hash differently and the audit-replay test fails: a reader cannot tie a logged event to the canonical record. Without configuration provenance, the record does not say what produced a verdict, so a change in the pipeline is indistinguishable from a change in the facts. Without eval-anchoring, model-version drift silently changes categorisation outcomes. Without residual-uncertainty disclosure, a categorisation is presented with false confidence and the record misleads.

Two limits on that, both of which matter more than the mapping. Article 12 does not bite yet. Regulation (EU) 2026/1744, in force 27 July 2026, replaced Article 113's third paragraph point (c): Chapter III Section 2 — which is where Article 12 sits — applies from 2 December 2027 for systems classified as high-risk under Article 6(2) and Annex III. Building the logging capability now is a choice about lead time, not a response to a live deadline. And the duty is not Warrant's. Article 12(1) binds the provider of the high-risk system, not a third party recording evidence about it — the corpus ties Article 12 to the obligations of providers under Article 16. The four moves are how a provider's logs survive being turned into an artefact someone else can check; Warrant's side of that is that the record it produces does not drift, not that it discharges anyone's duty. The companion regulator pillar at /blog/eu-ai-act-article-12 walks the full article and the per-clause mapping.

11 · THE RESIDUAL GAP

Three things still imperfect today. And no ship date for them.

Backend non-determinism · prompt-cache state · refusal drift · none of the three has a built mitigation

The four moves narrow the gap. They do not close it. Three sources of residual variance remain, and the honest framing is that each has a candidate mitigation rather than one that ships — an earlier version of this section described all three as shipping or in flight, which was not true of any of them.

Backend non-determinism beyond Warrant's control

GPU kernel non-determinism in the model provider's stack, plus replica-routing variance at the load balancer, plus floating-point reduction order across hardware revisions. Candidate mitigation: run the same trace through two providers and flag any divergence. That path is not built. A provider abstraction does exist — one environment variable repoints all four stages across five backends — but it resolves a single provider per run, and the attestation route refuses to sign when it resolves to anything other than Anthropic, on the ground that the receipt would otherwise name four models that did not produce the verdict. What is absent is the comparison: nothing runs one trace through two backends and diffs the results. How often divergence fires in practice is not something we have measured, and we will not invent a figure for it.

Prompt-cache state

Prompt caching is documented as a cost and latency optimisation, not as something that alters response content, so the residual risk here is narrower than this page once claimed: a cache read and a cache miss are different execution paths. Candidate mitigation: log cache_read_input_tokens and cache_creation_input_tokens — the fields the usage block actually returns — so a divergence can be attributed rather than guessed at. There is no cache_hit field to log.

Refusal drift

As models are retrained on safety, refusal patterns change over time. A prompt that was assessable last quarter may be declined this quarter. Candidate mitigation: track a refusal rate over time and trigger a regression run on a spike. No such metric is tracked today and no such trigger exists. The nearer-term problem is not drift but variance — the same code has produced different sign-or-refuse outcomes across runs, and a rate is not meaningful until that is understood.

What would independent deterministic replay actually take? Three things, and Warrant has the third. First, a model snapshot a third party can address and re-run — the pinned model id names a version, but a reader cannot re-run it without their own account, and no frozen-snapshot interface exists to point at. Whether one ever ships is a vendor roadmap question this page is not in a position to answer, and an earlier version of it asserted a ship window that was never announced. Second, a measured replay-stability figure, which does not exist. Third, a record of everything on Warrant's side that determined the verdict, which is what pipeline_config_sha256 is for. The third is real and the first two are not, so the honest description of the position is that one of three prerequisites is met.

Until that capability exists there is no published replay measurement, and this page will not imply one. /trust states the property the canonical record has by construction; it does not report a replay run, because there has not been one to report. The gap is documented, and it is not narrow.

12 · THE SHAPE OF THE CLOSURE

The model is probabilistic. The artefact is not.

The four moves do not make an LLM deterministic · they make the evidence deterministic given the LLM's outputs

The closure is worth stating cleanly. The four moves do not make an LLM deterministic. They make the evidence deterministic given the LLM's outputs. The model can still be probabilistic. The artefact the regulator reads is reproducible.

Two engineers with the same trace and the same Warrant package_id can independently confirm the same canonical record, the same regulator citations, and the same configuration digest, without contacting Warrant. They cannot verify the same per-token attention activations, and they do not need to. The chain of trust is not I trust the kernel. It is I can reduce the trace to its canonical form, confirm the record is the one that was attested, and read what produced it. Those three steps are independently runnable and none depends on a non-reproducible execution. A fourth step — re-run the eval suite and confirm the system still passes its contract — is what would close the loop, and that is the step this page has spent four sections explaining is not built.

That is the shape of the closure between a stochastic model and a deterministic audit, and it is a partial shape. The model is one thing a regulator cannot run; the evidence record is something she can. Trace canonicalisation under RFC 8785 is the byte-form discipline, and it holds by specification. Configuration provenance is the model-boundary discipline, and it ships. Eval-set anchoring is the system-level discipline, and it is not built. Residual-uncertainty disclosure is the artefact-level discipline, and it is partial — the counts are real, the enum this page once described was not. Two of four, then, with the two that are missing being the two that would let anyone measure the other two. A record that can be reduced to canonical bytes and read three years later is worth having on its own. It is not the same as a record whose accuracy has been demonstrated, and this post would be doing the thing it warns against if it ended by conflating them.

The model can still be probabilistic. The artefact the regulator reads is reproducible. The closure · why stochastic evidence is a solvable problem
13 · FAQ

Questions an architect asks first.

FAQ · sourced from inbound from platform and ML engineering teams during the May 2026 launch week
Why can't the randomness just be switched off?

Because the controls an engineer reaches for first are not available. On Anthropic's current models temperature, top-p and top-k are rejected outright — a request carrying any of them fails on claude-opus-5, which is the model all four pipeline stages run — and the Messages API has no seed parameter at all. Even on a model that accepted them, greedy sampling would not make execution deterministic: GPU kernel non-determinism in attention, provider-side load balancing across replicas, and floating-point reduction order across devices all leak variance into the response. Two identical calls can return different bytes. So determinism has to be sought in the evidence record rather than in the sampling call. As of the Anthropic Messages API reference, 6 August 2026.

What is RFC 8785 and why does it matter?

RFC 8785 is the JSON Canonicalization Scheme, published June 2020. It produces a unique deterministic byte serialisation of equivalent JSON values, regardless of Python version, OS locale, dict-insertion order, or float-formatting rules. It pins IEEE 754 number form, escape-sequence form, and recursive member-name sorting by UTF-16 code unit at every nesting depth. It deliberately does not normalise Unicode: section 3.1 requires that all components involved in a scheme depending on JCS preserve Unicode string data as is, so a precomposed and a decomposed form of the same character stay distinct. Callers that need them to compare equal must normalise before canonicalising. Two JSON documents that mean the same thing reduce to the same canonical bytes under JCS; two that differ in any meaningful way reduce to different bytes. The evidence record relies on JCS being the canonical form, which is what lets a reader check the record without contacting Warrant.

Can two replays of the same trace produce different evidence?

The canonical evidence record cannot: RFC 8785 makes the hash a function of the JSON value rather than of the encoding, so equivalent records reduce to the same bytes by specification. The model stages are a different question and the honest answer is that we do not know. Sampling parameters are not settable on the model the pipeline runs, so there is no greedy-decoding guarantee to appeal to, and no replay-stability measurement is published behind any expectation of byte-identical model output. One narrow determinism claim does hold: a trace that exceeds the ingestion cap is refused before the model is called at all, so that outcome is deterministic by construction. Whether a given trace is signed or refused overall is not — an internal evaluation returned different outcomes across runs on identical code, and that finding is on the record rather than resolved. Do not read design intent as a measured result.

What does the eval gate actually enforce today?

Less than an earlier version of this page claimed, and that claim is withdrawn. What runs today is structural: every citation in the corpus must resolve to enacted text on the regulator's own domain, and the model id is pinned to a specific version so an upgrade cannot arrive by alias drift. What does not exist is a measured agreement score between the pipeline and a human reviewer. Stage 1 classification accuracy is not yet measured, so there is no accuracy floor to enforce and none is published. A measured number with named failure classes is the open work.

What happens when the model refuses to answer?

Refusal is a recorded outcome, not an error. What the schema actually carries: each Stage 3 authorization row has a refusal boolean, an optional refusal_reason and a confidence number; the receipt carries a top-level refusal_reason set when the pipeline halted fail-closed, an uncertain_count for Stage 4 obligations marked uncertain, and an incomplete_oversight_count for actions where human oversight was judged absent or uncertain. There is no uncertainty enum and no attestation-incomplete package status — those would be a different design than the one that ships. An honest gap is more defensible than a confident invention, which is why the counts sit on the face of the receipt rather than in a footnote.

Is byte-identical replay actually achievable in practice?

On the canonical evidence record: yes, by construction. JCS makes the hash a function of the JSON value, not of the encoding, so that half needs no study behind it. On the model stage outputs: not demonstrated, and not currently claimable. The provider's stack carries non-determinism from kernel behaviour and load balancing that Warrant does not control, the sampling parameters that would narrow it are not accepted by the model, and no replay study measuring how often divergence leaks through has been run or published. Running the same trace through a second provider and flagging divergence would be one way to bound it; that path is not built. A provider abstraction exists, but it resolves one backend per run, and the attestation route refuses to sign when that backend is not the one the receipt names — nothing replays a trace across two and compares them.

How does this connect to Anthropic's prompt caching?

Prompt caching is documented as a prefix-match cost and latency optimisation: identical leading bytes are billed and served more cheaply. It is not documented to change response content, and an earlier version of this page said it could — that claim was not supported and has been removed. The defensible caveat is narrower: a cache read and a cache miss are different execution paths, so a record that wants to explain a divergence should log which one occurred. The fields the API actually returns for that are cache_read_input_tokens and cache_creation_input_tokens in the usage block. There is no cache_hit field.

What would independent deterministic replay require?

Three things, of which Warrant has the third. First, a model snapshot a third party can address and re-run: the pinned model id names a version, but a regulator cannot re-run it without an account, and no frozen-snapshot interface is offered to point at. Second, a measured replay-stability figure, which does not exist. Third, a record of everything on Warrant's side that determined the verdict — pipeline_config_sha256 is a digest over the model each stage ran, the exact bytes of each stage's prompt, the regulation-corpus digest and the confidence floors, and it sits inside the signed bytes, so a configuration change is visible in the record rather than silent. That third piece is what makes the question answerable at all. Calling the result independent deterministic replay today would overstate it.

14 · READ THE SOURCE

Read the source directly.

SEE THE PIPELINE RUN

Drop a trace. Replay it. Read the same canonical record twice.

One precision before you try it, because this page would otherwise fail its own test. RFC 8785 canonicalisation is implemented and gated by the RFC's own conformance vectors — that part is not aspirational. What is narrower than the argument above implies is where it is applied: the live pipeline identifies a submission by hashing the trace exactly as uploaded, so re-submitting the identical file reproduces the identical package, while a re-serialised version of the same trace — same meaning, different byte order — does not. Run a sample trace through the live demo and download the package by all means; what you can check today is that the same bytes give the same package, that the signature verifies against the published key, and that the configuration digest inside the signed bytes tells you what produced the verdict. Canonicalising the submission itself, rather than only the record built from it, is the remaining step.