The Luce reproducibility bundle
schemaVersion: luce-claim-bundle-2
One JSON file per published board cell, per wave, at:
/bundle/{waveId}/{cellId}.jsonIt is the machine-readable form of the claim page at /claim/{waveId}/{cellId}, and it is the same payload: the page renders this object, so a number on the page that the bundle cannot produce is a build failure. No one has to discover a discrepancy later.
While its address remains in the archive, an emitted bundle stays byte for byte the file it was. Its claim page renders that frozen bundle through today's template, so its numbers cannot move and its wording can. Every closed wave keeps its address: /claim/{waveId}/{cellId} and /bundle/{waveId}/{cellId}.json resolve for every wave this board has closed. One exception: a snapshot build published without the database behind it carries the current wave and the one before it, and drops older claims. This board publishes its serving mode at /api/health, where pg is the database-backed board and wave is the snapshot build. Once a wave has closed, it may not republish a cell with different numbers under the same id: the build refuses, and a corrected read closes under a new wave id. That refusal is enforced against the stored archive. The section below states what the archive check does not cover on the Postgres path.
/claim/{cellId} exists as an alias that follows the newest wave which measures the cell. It answers with a temporary 302 redirect because it is built to keep moving. When the current wave stops measuring a cell, the alias stays live by falling back to the last wave that published it. The page it lands on says which wave it belongs to and that it has been superseded.
What "immutable" covers, exactly
A stored bundle is frozen. Its published rates, intervals, samples, tiers, brand labels, prompt ids and fragment ids are re-emitted verbatim. A later wave cannot rewrite the stored archive. The Postgres serving path re-derives archived bundles on each build, and its limits are stated below.
Four limits to that freeze are stated here:
- The renderer. The claim page renders the stored bundle through today's template, so a rendering fix reaches every wave. That is the point: a display defect on an archived page has to be fixable, and freezing rendered HTML would freeze the defect with it. The consequence is that the guarantee on the claim page is "these numbers for this retained wave, as this renderer prints them", while the JSON guarantee covers byte identity until a field is added. A citation that needs the stronger guarantee cites the bundle.
- A wave republished under its own id. Re-running a wave under its own id may only re-emit what it already published. A build that would land different bytes at an address the archive already holds is refused with a message naming the remedy: a corrected read closes under a new wave id. The comparison is against the archive the serving path hands the build (
SiteData.claimArchive): a stored archive is compared byte for byte, and the Postgres path re-derives past waves from their mart rows on every build, so a mart row rewritten under a wave id that has already closed is not a difference this build can see. The rule is the same on both paths; only the stored archive enforces it. - Additive schema fields. A new field changes the bytes at an existing address.
schemaVersiontracks the shape a consumer can rely on. It says nothing about byte identity. - Postgres raw evidence. The database freeze compares published mart rows. A write may replace raw measurement rows when those marts remain identical. The Postgres serving path re-derives
promptIdsandfragmentIdsfrom those rows, so those identifiers can change until the bundle is stored.
What this file does not let you recompute
It carries the results and everything needed to audit them: which prompts, which captured answers, which estimator, which gate, and every figure with its interval and its sample. It omits the per-prompt observation vectors, the bootstrap seed and the replicate count, so you cannot re-run the resampling from this file alone and land on the same interval. gate.parameters is null for the same reason and says so in gate.parametersNote. Calling that out here is the honest reading of the word "reproducibility": this is an audit trail with the identifiers to follow, and the raw observation layer is a separate publication.
Reuse is under CC BY 4.0, with one term that matters here, and it is the sentence every bundle carries in license.note: a figure is quoted accurately with the wave id, rank range and 95% CI it was published with, and a figure published without an interval has none to quote. A rate lifted out of what it was published with is not this dataset.
Veliu sells services to brands measured here. That disclosure ships inside every bundle, because a conflict of interest that does not travel with the quote is not disclosed.
Fields
| Field | Type | What it is |
|---|---|---|
$schema | string | The address of the published schema, /bundle-schema, which is this document rendered as a page. A consumer can follow it to resolve the shape it is reading. |
schemaVersion | string | Bumped when a field is removed or changes meaning. Adding a field does not bump it. |
methodologyVersion | string | The methodology version the wave declared for itself. A figure is quoted with it, so a reader knows which set of rules produced the number. |
measured | object | startedAt is an ISO-8601 timestamp, or null when the serving path carries no wave row. The citation prints it as measured from, because no close time is published: the serving paths hold no close time in common, so it is not a portable measured fact and this file states none. |
waveId | string | The wave this payload belongs to. It never moves. |
cellId | string | The cell's stable slug, the same one the claim page is addressed by. |
cellKey | string | The board's own cell key ({category}::{locale}::{surface}::{branded}), for joining back to a rendered cell exactly. |
category | string | Category slug. |
categoryTitle | string | The category's display title. |
measuredSurface | string | The engine surface the cell was measured on. Never a blend of surfaces. |
surfaceLabel | string | That surface's display name. |
locale | string | The market this cell was measured in. |
title | string | The claim sentence, as published. No winner language, no point rank. |
illustrative | string or null | Non-null only on an illustrative wave: the honesty band, in the file. A machine never sees the page's sample banner, so a wave of invented brands has to say so in its own bytes. Null on a measured wave. |
honesty | string | The banner tag and sentence from this bundle's wave, retained with its figures when a newer wave is served. |
probeProvenance | string | The probe provenance statement carried with the cell, qualifying the prompts behind the answers. |
measuring | boolean | True exactly when the cell sits below the precision gate, which is the same fact gate.passed carries with the opposite sign. It does not mean the cell published no rates: read members for what was published. The claim page states the same thing once: a cell that cleared the gate says so in its sample row, and a cell below it says so in its headline. |
n | integer | Prompt clusters behind the cell. The cluster is the independent unit; each engine call is an observation within it. |
tier | string | The cell's provenance tier (L0, L1, L2, L3). One tier per cell. Every cell keeps its own tier. |
members | array | One entry per brand in the cell. See below. |
promptIds | string[] | The prompts this cell was computed from, in the wave's own order. Empty when the serving path carries no probes; an empty list records a real absence. |
fragmentIds | string[] | The captured answers behind this cell (/answers/{id}). Empty when the wave captured no UI answers. |
evidenceNote | string | Which kind of absence an empty list is. Every cell has exactly one empty list by construction (an API cell has probe rows and no captured answers; a consumer_ui cell has the reverse), so the empty array is the normal case and this sentence says which shape it is. |
estimator | object | name, interval, level, note. What produced the intervals. |
gate | object | name, rule, passed, parameters, parametersNote. The precision gate the cell was held to. |
license | object | id, url, note. The same licence the API envelope and the page's Dataset JSON-LD declare. |
disclosure | string | The governance line, verbatim. |
members[]
| Field | Type | What it is |
|---|---|---|
brand | string | Brand id. The claim page anchors it at #brand-{brand}. |
brandLabel | string | The brand's display name emitted in this bundle. A stored archive re-emits that value. The Postgres serving path rebuilds archived bundles from mart rows and reads the label from the live taxonomy, so a taxonomy rename can change the label until bundles are stored. |
rate | number | Mention rate as a fraction in [0,1]. The page prints it as a whole percent, except that a nonzero rate never prints as 0% and a rate below 1 never prints as 100%: those would read as "zero mentions" and "a mention in every answer", claims no rate supports. |
ci | {lo, hi} | The 95% interval, same units as rate. A figure never travels without it. |
n | integer | The cell's cluster count, repeated here so a member lifted out of the file keeps its sample. |
tier | string | The cell's provenance tier, repeated for the same reason. |
lowPrecision | boolean | True when the brand missed the relative-precision gate and renders in the low-precision band. |
league | integer, optional | Zero-based identity of the published league, used to print the board's league label on the official embed. This is a group identity. It carries no point rank. Absent on low-precision members and on archived payloads that did not record league membership. |
estimator
The intervals come from a joint cluster bootstrap over prompt clusters, at the 95% percentile interval. One resample of cluster indices per replicate, shared across the brands of a cell, so the members of one cell are drawn together and their ordering keeps its dependence. Bootstrapping each brand independently would destroy exactly that, and the rank ranges built on top of it.
gate
required-n, a relative-precision gate: the interval half-width must be at most a fraction of the rate it measures, so a brand sitting at 2% share needs far more prompts than one at 30%. An absolute gate is meaningless at small shares, which is where the unbranded headline lives.
passed: false does not hide the cell. It publishes, flagged as still measuring, and its brands are never described as settled.
parameters is null on purpose. The published cell does not carry the z and relative half-width the wave was sized with, so the bundle states the rule and the outcome. The estimator's compiled-in defaults do not belong to this wave. parametersNote says so in the file.
What is deliberately absent
- No point rank, and no sortable score. A rank is a range everywhere in Luce, and a bundle that shipped a scalar would be the one place a consumer could sort on.
- No pooled or cross-engine figure. One cell is one surface at one tier. Divergence between engines is its own published quantity and is never folded back into a cell.
- No invented evidence.
promptIdsandfragmentIdsare exactly what the served wave holds.