# The claim bench `/browse//claim/`. The clip bench asks *"where exactly does this cut?"*. This one asks *"what did he mean by that?"* — and the two want opposite things. A clip wants sample-accurate edges; a claim wants **room**. It exists because a `ledger[]` entry used to carry an undocumented interpretation in its `company` field, and four different hazards were riding on it. ## Why the quote is not enough The standing corpus rule: a first-person quote is routinely the host **reading someone else's words**, or being sarcastic, and neither is visible inside the quote. One claim in the employee-count corpus is a *guest's* payroll recorded as the subject's, and it reads identically until you listen either side of it. So the page opens on ±90 s of cues with the cited one marked **inside** the paragraph. That mark is the whole point: it shows the quote had someone else talking round it. `CLAIM_CONTEXT_PAD = 90`, overridable with `?pad=` up to 600. ## The six fields | field | vocabulary | what it settles | |---|---|---| | `scope` | `media` `coffee` `publica` `all` | which payroll the number is about | | `scopeBasis` | free text, **required non-blank** | the phrase from the context that settles it | | `scopeConfidence` | `clear` `read` `unresolved` | did the quote settle it, the context, or nothing | | `population` | `employees` `full-time` `salaried` `contractor` `1099` `people` | the denominator | | `valueKind` | `uttered` `derived` `synthetic` | is the number his, our sum, or our midpoint | | `flags` | array, **always written** even when empty | anything a predicate cannot compute | Two of these are load-bearing in a way that is easy to miss: - **`scopeBasis` is refused when blank.** An adjudication with no basis is an opinion, and a reviewer cannot check an opinion against the audio. - **`flags: []` is written, not omitted.** An absent array reads as "nobody has looked", which is exactly the state an adjudication is supposed to leave behind. **`scopeConfidence: "unresolved"` is a legitimate outcome**, not a failure to finish. It feeds *neither* total. Forcing a reading on a genuinely ambiguous sentence is the failure mode this whole surface exists to prevent. ## The seventh field, which is optional `roles` records **who** he named, when he enumerated them rather than counting them. ```jsonc "roles": [{ "role": "video editor", "count": 2, "verbatim": "two video editors" }] ``` The bench edits it as one line per role — `count | role | his words` — because most claims have none and the handful that do are a two-line list; a repeater widget would be more chrome than the field it edits. The preview under the box shows what the rail will draw (`2 editors · 1 designer`), and a line that will not parse disables the save rather than writing half a roster. **`verbatim` is required and is the point of the field.** The count and the role name are our reading; without his own words beside them nobody can check the reading against the audio — the same rule `scopeBasis` exists for. **It is deliberately NOT one of the six.** Most claims are a number and nothing else, and gating the inbox on a field only a handful of entries can ever carry would leave it permanently red. `adjudicationGaps()` ignores it; `rolesGaps()` checks it when it is there, and a bad roster is a **400**. Why it is worth collecting at all: in the employee-count corpus the roster is the control. Five times he names who works for him and five times it is two video editors and a graphics designer; the totals he attaches are three, then four, then ten. The rail draws the roster under the tally precisely so it can be seen standing still while the numbers above it move. ## The vocabularies are imported, never restated `SCOPES` / `SCOPE_CONFIDENCE` / `POPULATIONS` / `VALUE_KINDS` come from `report-to-video/ledger-totals`, into both the page and `updateClaim()`. A page offering a seventh population the arithmetic has never heard of is the silent divergence the shared module exists to stop. A value outside the vocabulary is a **400**, never a coercion. ## Writing Same contract as the clip bench: `updateClaim()` re-reads inside a process-wide lock, writes tmp+rename, preserves the CLI's `JSON.stringify(m, null, 2)` formatting, and **guards on the manifest's own mtime**. A stale token is a `409`, never a silent overwrite — somebody may have run `resolve-windows --write` in between, and losing that is losing human judgement. It is deliberately *not* `updateClip()` with more fields. A clip edit moves a window; a claim edit records a ruling on what a sentence meant. Sharing a function would mean one of them could quietly write the other's fields. ## Audio, when there is any A claim is a **moment**, not a window, so its playable candidates are the cached `out/clips-raw` files that *contain* its `cite`. Most claims have one already, because the build over-fetches around every clip. When none does, the page offers a fetch that runs the pipeline's own `--fetch-only`, which now accepts a **ledger id** as well as a timeline id: it synthesises a hair-wide entry around `cite` and lets `--pad` do the rest. The file lands in `clips-raw` under the build's own naming and a later build reuses it. That path needs `channel` / `video` / `cite` on the ledger entry. They were recoverable for the employee-count corpus from the markdown report's own links — but 20 of 50 needed the **Rumble site-id → URL-slug** remap first (see [report-video.md](report-video.md)). ## Sign-off is "the inbox is empty" `claim-unadjudicated` is **blocking** and is emitted one row per entry. Work it to empty; see [decisions.md](decisions.md). Never auto-apply an adjudication. Every ruling carries its `scopeBasis` precisely so the next person can check it against the audio in one click.