Vivarium Contract v1
The reproduction-verdict surface that every Vivarium-compatible reproduction page emits. Currently at revision 3 — see the revision history at the foot of this page.
At a glance
A page conforming to Vivarium Contract v1 publishes:
- A constant version declaration:
<meta name="vivarium-contract" content="v1">in<head>."contract": "v1"in any JSON envelope it emits (in-page__VIVARIUM_RESULT__global, or theverdict.jsonfile).
- A verdict — one of
"reproduced"(the upstream bug was reproduced),"unreproduced"(it was not), or"pending"(run not yet finished). - A result envelope describing the bug, the runtime, and the page-specific output.
How those are exposed depends on the layer:
The DOM/global surface is therefore the same across all three layers; the file snapshot only exists for Layer 2 and Layer 3.
Verdict semantics
reproduced means the upstream bug was reproduced in this run.
The page demonstrates the failure the upstream report describes; the
reproduction is doing its job.
unreproduced means the bug did not reproduce. Either the
runtime shipped a fix the page picked up (e.g., Pyodide upgraded its
bundled pandas past the buggy release), or the runtime regressed in
a different way before producing a verdict at all. Either reading is
worth investigating — the page is no longer demonstrating what its
README claims.
pending is the default state until the reproduction code (Layer 1)
or the verdict-snapshot fetch (Layer 2 / 3) settles.
In-page surface (all layers)
HTML meta tag
Required in <head> of every reproduction page.
DOM verdict element
The element with id="verdict" carries:
The reproduction code transitions the element from "pending" to
"reproduced" or "unreproduced" exactly once per page load.
JavaScript globals
__VIVARIUM_VERDICT__ mirrors #verdict[data-verdict] — they are
written together by the helper in
src/layer1_wasm/_shared/verdict.ts.
A divergence between them indicates a broken page; tests cross-check
both.
__VIVARIUM_RESULT__ is the structured envelope (next section). It
is set when the page produces (Layer 1) or fetches (Layer 2 / 3) its
verdict.
DOM evidence element (optional, revision 2+)
The #evidence container is optional. Pages predating revision 2
omit it; v1 consumers ignore the absence. When present, it carries
machine-readable run evidence used by tooling such as the
reproduction-comparison UI to render side-by-side panels.
A page MAY emit a subset of these children. Consumers MUST treat a
missing [data-evidence="<key>"] as null / absent, not as an
error. The hidden attribute keeps the surface invisible in
default rendering; presentation components style and reveal it.
The stdout / stderr text MAY be truncated to bound page size; by
convention, Layer 1 helpers cap each at 4 KiB to match the existing
Layer 2 / 3 verdict.json#stderr_tail truncation rule.
Result envelope (VivariumResultV1)
Type, expressed in TypeScript:
runtime.name is free-form, but the values currently in use across
the gallery are:
External reproductions are free to add new values; downstream
tooling treats runtime.name as opaque.
result is intentionally Record<string, unknown> — its shape is
per-page (e.g. pandas reproduction may put { wrong_value, expected_value },
a regex reproduction may put { matched, expected_match }). Pages
document their own result shape in their README; the contract only
guarantees the field exists.
evidence is optional and was added in revision 2. Consumers MUST
feature-detect it (if (result.evidence) …) — pages predating
revision 2 omit the field entirely. The evidence.stdout and
evidence.stderr strings on this envelope correspond to the DOM
[data-evidence="stdout"] / [data-evidence="stderr"] children
described above; tooling reading the structured envelope and tooling
reading the DOM see the same data through two paths.
Verdict snapshot file (verdict.json)
For Layer 2 and Layer 3, the gallery page does not run the
reproduction live — it consumes a snapshot file that CI (Layer 2)
or the maintainer (Layer 3) wrote at the time of the last
reproduction attempt. The file is fetched on page load and lifted
into the in-page surface (__VIVARIUM_RESULT__ etc.) by
src/layer2_docker/_layer2-shared/layer2.js.
Schema: verdict.schema.json (JSON Schema
draft 2020-12).
Field summary:
The schema enforces the v1 invariant: contract === "v1" and
verdict ∈ {"reproduced", "unreproduced"}. Layer 1 does not ship
a verdict.json; its verdict is live in-page.
Lift to the in-page evidence surface (revision 2+)
Layer 2 / Layer 3 pages do not duplicate the snapshot's evidence fields into their own DOM at write time — the snapshot is the source. The gallery loader lifts them into the DOM evidence element on page load:
The stderr_tail → evidence.stderr rename keeps the in-page
contract surface uniform with Layer 1 (where the reproduction code
emits the text it wants captured directly, with no "tail" framing).
The 4 KiB front-back truncation rule is a property of the source
field; the in-page surface inherits whatever bound the source applied.
The schema is not amended for revision 2 — the source fields were already present at the snapshot's top level and are reused as-is.
Why no "pending" in the file
The snapshot is written after the recorded program / replay
finishes. There is no captured "pending" snapshot — by the time
CI or the maintainer is writing the file, the run has already
settled. A "pending" value would mean a writer bug.
Versioning
The version is carried in two places:
<meta name="vivarium-contract" content="v1">— in-page.verdict.json#contract— file snapshot.
Both must agree on every page that ships both. A page declaring
v1 MUST conform to this specification.
The contract evolves under a two-tier policy:
- Major bump (v2) — required for changes to existing v1 fields (renamed, removed, type changed, semantics changed, optional → required). A v2 ships a new spec page and a new JSON Schema sibling; consumers can support v1 and v2 simultaneously by dispatching on the version literal.
- Minor revision (within v1) — used for optional, additive
surface that v1 consumers can ignore. The version literal stays
"v1"(nometachange, noverdict.json#contractchange), the same spec page is updated, and the revision history below records the addition with date. Consumers feature-detect the new surface (e.g.if (result.evidence) …).
There is no current v2.
Conformance
A reproduction page conforms to Vivarium Contract v1 when:
- It includes
<meta name="vivarium-contract" content="v1">in<head>. - It exposes the verdict via
#verdict[data-verdict]and__VIVARIUM_VERDICT__together (matching values). - It exposes the structured envelope via
__VIVARIUM_RESULT__, conforming to theVivariumResultV1type. - If it ships a
verdict.json, the file validates againstverdict.schema.json.
CI enforces these clauses mechanically — currently via
src/layer1_wasm/tests/repro.spec.ts
(Playwright assertions on clauses 1–3) and the
jq -e '.contract == "v1" and …' predicates in
.github/workflows/repro-regression.yml
(clause 4). The follow-up PR for Issue #109
will replace clause 4's jq validators with an ajv-cli schema
validator pointed at the schema file above, keeping clause-4
enforcement single-sourced.
The optional revision-2 evidence surface deliberately has no
conformance clause: gating an optional surface on CI would create
the wrong incentive (page authors padding empty evidence elements
just to satisfy the gate). When the gallery's first non-PoC page emits evidence,
a follow-up Issue can decide whether to enforce shape (e.g. "if
#evidence exists, it must contain at least one
[data-evidence] child") at that point.
Revision history
The version literal carried in <meta name="vivarium-contract"> and
verdict.json#contract is "v1"; the revisions below are
non-breaking, additive evolutions of v1's surface. Pre-revision-2
pages stay conformant unchanged.
References
src/layer1_wasm/_shared/verdict.ts— TypeScript helpers for the in-page surface.src/layer1_wasm/tests/repro.spec.ts— Playwright assertions on the surface.src/layer2_docker/_layer2-shared/layer2.js— gallery-sideverdict.json→ in-page surface lift..github/workflows/repro-regression.yml— currentjq -evalidators (target for replacement in PR 2)..github/workflows/deploy-docs.yml— Layer 2 build/run/snapshot workflow.