Skip to content

The trace is the UI

An assurance tool's most valuable interface is portable evidence, not an app. Evarness renders a graph, a run, and its verdicts into one self-contained HTML file — the event-sourced trace is the animation feed, the digest in the header is recomputable from the file alone, and the file itself obeys the product's own honesty rules. This post is the design argument, and every claim in it links to an artifact this site's build just generated and checked.


The constraint as identity

Evarness ships with two runtime dependencies. That is not a footnote — it is part of the public claim: a proof you can verify offline, on an air-gapped machine, from a wheel you built yourself. A package.json in this repo would contradict that story before anyone finished the README.

So there is no app. When you run

evarness run graph.json --fixture happy.yaml --html replay.html

the "UI" you get is a generated artifact: one HTML file, inline SVG, vanilla JavaScript, zero external requests. Save it, email it, open it offline — portability is the point. An interface you must install answers "how do I look at this run?"; a file you can hand to a reviewer answers "how do I show a third party?" — and the second question is the one an assurance tool exists for.

The blocked run is the product in one picture

Open the blocked run → — generated by this site's build, from the packaged governed_email_assistant pattern.

A hostile scenario is stopped by the governance layer. The interceptor node glows red. Everything downstream of it — including the LLM node — stays dim, because it was never reached.

"A blocked run never reaches the model" is a temporal contract in this repo (no-model-call-after-block). In most tools that sentence would be a log line you have to trust. Here it is something you can see — dim means never lit, even in a static screenshot — and then check: the digest in the header, c1:sha256:66d4021c…, is the canonical digest of the blocked run's event stream, and the end-to-end walkthrough shows the command that reproduces it on your machine.

The happy path replay is the same file format with a different verdict: scrub the playhead and watch nodes go dim → glow → done as the canonical events replay, c1:sha256:e0513e74… in the header.

The artifact obeys the honesty rules

The render is not a separate product surface with separate ethics. Every rule the library enforces shows up in the page:

  • The digest is recomputable from the file alone. The data island embeds the canonical events verbatim — not a prettified copy. Extract them, recompute, and the header digest either reproduces or the file is lying. Two renderings of one source cannot drift; a screenshot and a log always can.
  • Evidence and judgment stay separated. Events render in the replay pane; invariant verdicts render in their own pane, from their own data key. Checking a run never alters its digest, and the page layout says so.
  • The footer states what was not established. A paused run says "invariants never evaluated". A graph with zero contracts says "nothing asserted". The most dangerous kind of pass is a green checkmark over nothing — so the artifact refuses to render one.

XSS is the threat model, not an afterthought

Who opens these files? Auditors. Reviewers. CI systems attaching them to build results. That makes a rendered artifact an attack surface: fixture content is data, and data can be hostile.

The page ships a Content-Security-Policy of default-src 'none' — it can request nothing, run nothing external. Every < in the data island is emitted as the JSON escape \u003c, because the obvious defense (escaping just </) loses to the script-data double-escape trick. Everything the JavaScript writes into the DOM goes through textContent, never innerHTML. And none of this is a convention: each rule is enforced by a test that greps the generated output. A safety property you don't test is a safety property you used to have.

A regression-testable UI

Deterministic runs produce byte-identical artifacts — canonical events only, no wall clock, layout derived from the same topological order as the determinism contract. So the renderer is pinned the way runs are pinned: under a golden hash, in the test suite, with a version (r3) that is deliberately outside the c1 digest contract.

That rule earned its keep almost immediately. During review, an automated suggestion changed the rendered bytes — accessibility labels, a genuinely good change — without bumping the version. Same version, different golden: exactly the drift the digest machinery exists to prevent, arriving through the UI door. The rule is now absolute: any change to rendered bytes bumps the r-version, and the golden pin moves only with the version. The pin travels by rule, not by habit.

The bundle becomes a page

Open the proof browser → · raw bundle

Phase two of the same thesis: if the artifact is the UI, then the proof bundle — the product's central object — deserves the same treatment. evarness render proof.json produces one page: the tri-state verdict badge (HOLDS / PENDING / FAILED), one viewer per scenario with its own playhead and reproduction status, and the bundle's not_proven section rendered verbatim as the footer. Three rules govern the page:

  1. The canvas never lies about identity. A graph is drawn only when its hash matches the bundle's pinned graph_sha256. No match — honest omission, never substitution. A canvas drawn from an unproven graph would be the trace lying about what ran, in visual form.
  2. The page never vouches for itself. A signed bundle is labeled "signature NOT checked by this page". Vouching is verify's job; a page asserting its own integrity is worth exactly nothing.
  3. The island IS the bundle. The page embeds the entire proof bundle in its data island. Extract the bundle key and run evarness verify — the proof re-checks offline, signature included, from the HTML file alone.

Read that last rule again, because it is the whole post in one sentence: the HTML file is not a report about evidence — it is a carrier of verifiable evidence. This documentation site publishes one, and the build fails if the proof inside it stops verifying.

The honest boundary

What this does not claim:

  • Rendering is not verification. The page shows; evarness verify checks. Opening a pretty artifact proves nothing until you run the verifier against what it carries.
  • The artifact is a view over evidence, not a generator of it. If the underlying run was not proven, the page says so — it cannot upgrade a trace into a proof.
  • Byte-stability is scoped. The golden pin covers deterministic, simulation-mode runs — the same scope as the digest contract itself.

What's next

The generated-artifact rule has a corollary the decision log states plainly: anything interactive enough to need a build toolchain lives in a separate repo, never here. That repo now exists — Evarness Studio, the product's visual surface: a pattern library, a drag-and-drop graph builder with type-checked connections, and proof-bundle import that draws a canvas only when the bundle's pinned graph hash matches. It grows as the product grows. On the library side, evarness export unpacks a verified bundle into standard interchange formats — and refuses to unpack one that fails verification, because an exporter that launders tampered bundles would undo everything above.

Both will get their own posts, with their own receipts.


Everything here is checkable: the demos on this site are generated by the product at build time, and the build fails if their digests drift from the values this post cites. Reproducible, or it didn't happen.