commit a7f8c246dd3b4ec28a0a577fd9bc45f6c58ff945
parent 24f433897458fe32a80b221ccc48d4f16d3499e5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 5 Oct 2026 14:03:59 -0400
reports: a citation's origin — what the report added comes first and marked, what the subject gave folds away
- citations schema: optional `origin` ("subject" | "added") on every kind;
a report with no `subject` may carry none (validate.ts); docs regenerated
- CitationView.origin; CitationCard marks an added card with the project mark
(BrandMark, ICON_PALETTES.archilyzer) and "Not in the article"/"Not in the
source" (data-citation-added); a reference entry or preview, the mark alone
- ReportArticle: a claim's evidence is added, then unknown, then the subject's
own in a closed <details> "In the article (n)"; the flag pill wears the mark;
the reference list keeps every citation
- fixture: c01 added, w01 subject, au1 listed under claim-1; unit + e2e; changelog
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
14 files changed, 175 insertions(+), 17 deletions(-)
diff --git a/CITATIONS.md b/CITATIONS.md
@@ -27,6 +27,7 @@ Regenerate this file with `pnpm --filter yt-dlp-transcript-common exec tsx bin/f
| `date` | no | When the quote was said or written: `YYYY`, `YYYY-MM`, `YYYY-MM-DD` or an ISO 8601 date-time. Absent = the record's own date. |
| `label` | no | A short display name for the citation (one line), used where its number alone would be too little. |
| `note` | no | An editorial note shown with the citation (plain text): context the quote needs. |
+| `origin` | no | In a report that reviews a document (`subject`): where this evidence came from. `"subject"` — the document itself gave it (its link, its embedded clip, its picture); `"added"` — the report's author found it, the document did not give it. Absent = not known. A report with no `subject` carries none. |
| `verification` | no | COMPUTED, not authored: what checking this citation found, written by compose. A hand-typed block that could not have come from a check (a score outside 0–1, a score without its time) is a validation problem. |
#### `verification`
diff --git a/common/components/citations/CitationCard.tsx b/common/components/citations/CitationCard.tsx
@@ -1,5 +1,7 @@
import { Archive, ExternalLink, FileText, Globe, MessageSquareQuote, Mic, Play, Video } from "lucide-react";
import type { CitationKind } from "../../lib/citations/schema";
+import { ICON_PALETTES } from "../../lib/brand";
+import { BrandMark } from "../BrandMark";
import {
CITATION_KIND_LABELS,
sourceAnchor,
@@ -180,15 +182,27 @@ function PageLinks({ c }: { c: PageCitationView }) {
);
}
+// Evidence the report's author found — the document under review did not give
+// it (`origin: "added"`): the project's mark and the words, e.g. "Not in the
+// article". A card shows both; a reference entry or a preview, the mark alone
+// (the words for a screen reader). The claim's flag pill wears the same mark.
+export function AddedMark({ className }: { className?: string }) {
+ return <BrandMark palette={ICON_PALETTES.archilyzer} className={cn("size-3.5 shrink-0", className)} />;
+}
+
export function CitationCard({
citation: c,
variant = "card",
className,
+ addedLabel = "Not in the source",
}: {
citation: CitationView;
variant?: CitationCardVariant;
className?: string;
+ // The words for added evidence: "Not in the article", "Not in the source".
+ addedLabel?: string;
}) {
+ const added = c.origin === "added";
const Icon = KIND_ICON[c.kind];
const { title, meta } = heading(c);
const showNumber = variant !== "reference" && c.number !== undefined;
@@ -204,6 +218,15 @@ export function CitationCard({
className,
)}
>
+ {added && variant === "card" && (
+ <p
+ data-citation-added=""
+ className="flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground"
+ >
+ <AddedMark />
+ {addedLabel}
+ </p>
+ )}
<div className="flex min-w-0 items-start gap-2">
{showNumber && (
<span className="mt-px shrink-0 rounded bg-brand-soft px-1.5 font-mono text-xs font-semibold leading-5 text-brand-strong">
@@ -214,6 +237,12 @@ export function CitationCard({
<div className="flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground">
<Icon className="size-3 shrink-0" aria-hidden />
{CITATION_KIND_LABELS[c.kind]}
+ {added && variant !== "card" && (
+ <span data-citation-added="" title={addedLabel} className="inline-flex items-center">
+ <AddedMark className="size-3" />
+ <span className="sr-only">{addedLabel}</span>
+ </span>
+ )}
{c.label && <span className="truncate normal-case tracking-normal">· {c.label}</span>}
</div>
<div className="font-medium leading-snug text-foreground break-words">{title}</div>
diff --git a/common/components/citations/ReferenceList.tsx b/common/components/citations/ReferenceList.tsx
@@ -16,10 +16,13 @@ export function ReferenceList({
citations,
anchors = true,
className,
+ addedLabel,
}: {
citations: readonly CitationView[];
anchors?: boolean;
className?: string;
+ // The words behind an added citation's mark (CitationCard).
+ addedLabel?: string;
}) {
if (citations.length === 0) return null;
return (
@@ -35,7 +38,7 @@ export function ReferenceList({
<span className="w-7 shrink-0 pt-0.5 text-right font-mono text-sm font-semibold text-brand-strong">
{c.number !== undefined ? `${c.number}.` : "·"}
</span>
- <CitationCard citation={c} variant="reference" className="flex-1" />
+ <CitationCard citation={c} variant="reference" className="flex-1" addedLabel={addedLabel} />
</li>
))}
</ol>
diff --git a/common/lib/citations/schema.ts b/common/lib/citations/schema.ts
@@ -67,6 +67,12 @@ export const CITATION_VERIFICATION_FIELD_DOCS: FieldDocs<CitationVerification> =
method: "What did the checking, e.g. the cue-window comparison and its version. Free text, one line.",
};
+// Where a report's evidence came from, relative to the document it reviews:
+// the document itself gave it ("subject"), or the report's author found it
+// ("added"). Absent = not known.
+export const CITATION_ORIGINS = ["subject", "added"] as const;
+export type CitationOrigin = (typeof CITATION_ORIGINS)[number];
+
const common = {
quote: text,
speaker: text.optional(),
@@ -74,6 +80,7 @@ const common = {
label: text.optional(),
note: text.optional(),
verification: verificationSchema.optional(),
+ origin: z.enum(CITATION_ORIGINS).optional(),
};
const pad = z.strictObject({
@@ -149,6 +156,8 @@ export const CITATION_COMMON_FIELD_DOCS: FieldDocs<CitationCommon> = {
date: "When the quote was said or written: `YYYY`, `YYYY-MM`, `YYYY-MM-DD` or an ISO 8601 date-time. Absent = the record's own date.",
label: "A short display name for the citation (one line), used where its number alone would be too little.",
note: "An editorial note shown with the citation (plain text): context the quote needs.",
+ origin:
+ 'In a report that reviews a document (`subject`): where this evidence came from. `"subject"` — the document itself gave it (its link, its embedded clip, its picture); `"added"` — the report\'s author found it, the document did not give it. Absent = not known. A report with no `subject` carries none.',
verification:
"COMPUTED, not authored: what checking this citation found, written by compose. A hand-typed block that could not have come from a check (a score outside 0–1, a score without its time) is a validation problem.",
};
diff --git a/common/lib/report/validate.ts b/common/lib/report/validate.ts
@@ -95,6 +95,13 @@ function reportProblems(report: Report, opts: ReportValidateOptions): Problem[]
}
out.push(...citationMapProblems(citations, report.sources));
+ if (!report.subject) {
+ for (const [id, c] of Object.entries(citations)) {
+ if (c.origin !== undefined) {
+ out.push(problem(["citations", id, "origin"], "says where evidence came from relative to the document under review; the report has no `subject`"));
+ }
+ }
+ }
const anchors = new Map<string, string>();
const anchor = (id: string, at: PathSegment[]) => {
diff --git a/common/lib/report/views.test.ts b/common/lib/report/views.test.ts
@@ -352,3 +352,22 @@ test("a source's accent rides its view and its sentences' views; only #rrggbb is
assert.ok(validateReport(b).some((p) => p.path === "sources.s0.accent"), bad);
}
});
+
+test("a citation's origin rides its view; a report with no subject carries none", () => {
+ const withOrigin = structuredClone(report);
+ const ids = Object.keys(withOrigin.citations!);
+ withOrigin.citations![ids[0]].origin = "added";
+ withOrigin.citations![ids[1]].origin = "subject";
+ assert.deepEqual(validateReport(withOrigin), []);
+ const v = buildReportPageView(withOrigin, { record: (c) => record(c.channel, c.id) });
+ assert.equal(v.citations[ids[0]]?.origin, "added");
+ assert.equal(v.citations[ids[1]]?.origin, "subject");
+ assert.ok(Object.values(view.citations).every((c) => !("origin" in c)), "absent when not given");
+ const noSubject = structuredClone(withOrigin);
+ delete noSubject.subject;
+ const paths = validateReport(noSubject).map((p) => p.path);
+ assert.ok(paths.includes(`citations.${ids[0]}.origin`), paths.join());
+ const bad = structuredClone(report);
+ (bad.citations![ids[0]] as Record<string, unknown>).origin = "found";
+ assert.ok(validateReport(bad).length > 0);
+});
diff --git a/common/lib/report/views.ts b/common/lib/report/views.ts
@@ -42,6 +42,7 @@ import {
} from "../citations/moments";
import type {
CitationKind,
+ CitationOrigin,
CitationVerification,
PostCitation,
Source,
@@ -149,6 +150,9 @@ type CitationViewCommon = {
label?: string;
note?: string;
verification?: CitationVerification;
+ // Where the evidence came from relative to the document under review: it
+ // gave it ("subject") or the report's author found it ("added").
+ origin?: CitationOrigin;
};
export type SpanCitationView = CitationViewCommon & {
@@ -395,6 +399,7 @@ export function buildReportPageView(report: Report, resolve: ReportViewResolver)
label: c.label,
note: c.note,
verification: c.verification,
+ origin: c.origin,
});
switch (c.kind) {
case "video":
diff --git a/export/CHANGELOG.md b/export/CHANGELOG.md
@@ -1,7 +1,7 @@
# Changelog
## [Unreleased]
-- **A report's claim can carry a flag, its title a byline, and a site with one report names it in the browser tab.** `report.json` claim `flag` (one line, at most 60 characters) shows as a small pill in the accent colour beside the claim's verdict, e.g. "No source given". On a report-only site with one report, the home page's tab title is the report's, as on the report's own page, where it was the site's title alone. A report's page follows its title with a byline from the document under review, "by <author> · <publisher>", in the same line when it fits; the publisher (or, with none, the author) links to the document. Under it, a step apart, the page names its own: "Fact-check by <site title>" ("Report by …"), then the dates, then the subtitle; the kind's label no longer sits above the title on the report's page. A claim's sentence from the document under review no longer links up to the document's box: it sits on a rail in the document's colour, and the box's left edge wears the same colour (a source's `accent`, `"#rrggbb"`; without one, the border colour). A sentence of another document keeps its "from <title>" link. Needs a rebuild and deploy of the site.
+- **A report's claim can carry a flag, its title a byline, and a site with one report names it in the browser tab.** `report.json` claim `flag` (one line, at most 60 characters) shows as a small pill in the accent colour beside the claim's verdict, e.g. "No source given". On a report-only site with one report, the home page's tab title is the report's, as on the report's own page, where it was the site's title alone. A report's page follows its title with a byline from the document under review, "by <author> · <publisher>", in the same line when it fits; the publisher (or, with none, the author) links to the document. Under it, a step apart, the page names its own: "Fact-check by <site title>" ("Report by …"), then the dates, then the subtitle; the kind's label no longer sits above the title on the report's page. A claim's sentence from the document under review no longer links up to the document's box: it sits on a rail in the document's colour, and the box's left edge wears the same colour (a source's `accent`, `"#rrggbb"`; without one, the border colour). A sentence of another document keeps its "from <title>" link. A report's citation can say where its evidence came from (`origin`: `"subject"`, the document under review gave it; `"added"`, the report's author found it). A claim lists what the report added first, each card marked with the Archilyzer mark and "Not in the article" ("Not in the source"), then evidence of unknown origin, then what the document gave itself folded under "In the article (n)"; the reference list marks an added citation with the mark alone, and a claim's flag pill wears the same mark. Needs a rebuild and deploy of the site.
- **A report-only site with one report opens on that report.** Its home page is the report itself, its header links nothing, and `/reports/` forwards home: there is no index of one. With more reports the home page is the list, without repeating the site's title under the header; a list entry is the report's name, subtitle and dates (its counts and tally are on its page). Pages a report-only site does not have link home.
- **A report can belong to a series.** `report.json` `series` is shown on its own line above the report's title, in the accent colour, in place of the kind's label ("Fact-check"); a page title, a cited-in link, `llms.txt` and the MCP name it `<series>: <title>`.
- **`pnpm start:export` serves a built site's moment pages.** It used `serve`, which listed a video or audio moment's directory (`3126.00-3151.00`) instead of serving its page; it now runs `export/scripts/serve-out.mjs`, which serves directories as Cloudflare Pages does, on `EXPORT_DEV_PORT` (3000).
diff --git a/export/app/components/reports/ReportArticle.tsx b/export/app/components/reports/ReportArticle.tsx
@@ -1,5 +1,5 @@
import { ArrowDownToLine } from "lucide-react";
-import { CitationCard } from "yt-dlp-transcript-common/components/citations/CitationCard";
+import { AddedMark, CitationCard } from "yt-dlp-transcript-common/components/citations/CitationCard";
import { CitationsProvider } from "yt-dlp-transcript-common/components/citations/CitationsContext";
import { CitedMarkdown } from "yt-dlp-transcript-common/components/citations/CitedMarkdown";
import { ReferenceList } from "yt-dlp-transcript-common/components/citations/ReferenceList";
@@ -25,7 +25,8 @@ import { ArchiveList, ReportName, SourceBlock, dateLabel, textLink } from "./par
// dates, the subtitle, the document under review with its archive links), a
// fact-check's tally, the summary, the sections and their claims — each claim
// its verdict and flag, the document's own sentence (its still), the findings with
-// their inline citations, and the evidence cards — then the numbered
+// their inline citations, and the evidence cards (what the report added first,
+// marked; what the document gave itself folded away last) — then the numbered
// reference list the inline markers jump to, and the citations as files.
const proseClass = "text-[0.95rem] leading-relaxed text-foreground";
@@ -113,16 +114,26 @@ function Claim({
claim,
view,
subjectLabel,
+ subjectNoun,
}: {
claim: ClaimView;
view: ReportPageView;
subjectLabel: string;
+ // What the document under review is called: "article" or "source".
+ subjectNoun: string;
}) {
const sentence = claim.sourceQuote ? view.citations[claim.sourceQuote] : undefined;
const evidence = claim.citations
.filter((id) => id !== claim.sourceQuote)
.map((id) => view.citations[id])
.filter((c): c is CitationView => !!c);
+ // What the report adds comes first, marked; then evidence of unknown origin;
+ // what the document under review gave itself is folded away at the end.
+ const added = evidence.filter((c) => c.origin === "added");
+ const unknown = evidence.filter((c) => c.origin === undefined);
+ const given = evidence.filter((c) => c.origin === "subject");
+ const shown = [...added, ...unknown];
+ const addedLabel = `Not in the ${subjectNoun}`;
return (
<article
id={claim.id}
@@ -134,8 +145,9 @@ function Claim({
{claim.flag && (
<span
data-claim-flag=""
- className="mt-0.5 inline-flex shrink-0 items-center rounded-full border border-brand/40 bg-brand-soft px-2.5 py-0.5 text-xs font-medium text-brand"
+ className="mt-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full border border-brand/40 bg-brand-soft px-2.5 py-0.5 text-xs font-medium text-brand"
>
+ <AddedMark className="size-3" />
{claim.flag}
</span>
)}
@@ -161,11 +173,25 @@ function Claim({
{evidence.length > 0 && (
<div className="flex flex-col gap-2">
<p className="font-mono text-[10px] uppercase tracking-[0.16em] text-muted-foreground">Evidence</p>
- <div className="grid gap-3 sm:grid-cols-2">
- {evidence.map((c) => (
- <CitationCard key={c.id} citation={c} />
- ))}
- </div>
+ {shown.length > 0 && (
+ <div className="grid gap-3 sm:grid-cols-2">
+ {shown.map((c) => (
+ <CitationCard key={c.id} citation={c} addedLabel={addedLabel} />
+ ))}
+ </div>
+ )}
+ {given.length > 0 && (
+ <details data-subject-evidence="" className="group">
+ <summary className="cursor-pointer select-none font-mono text-xs text-muted-foreground hover:text-foreground">
+ {`In the ${subjectNoun} (${given.length})`}
+ </summary>
+ <div className="mt-2 grid gap-3 sm:grid-cols-2">
+ {given.map((c) => (
+ <CitationCard key={c.id} citation={c} addedLabel={addedLabel} />
+ ))}
+ </div>
+ </details>
+ )}
</div>
)}
</article>
@@ -180,7 +206,8 @@ export default function ReportArticle({ view, siteTitle }: { view: ReportPageVie
const references = orderedCitations(view);
const byline = reportByline(view);
const subject = view.subject ? view.sources[view.subject] : undefined;
- const subjectLabel = subject?.kind === "article" ? "The article says" : "The source says";
+ const subjectNoun = subject?.kind === "article" ? "article" : "source";
+ const subjectLabel = `The ${subjectNoun} says`;
const dates = [
view.published ? `Published ${dateLabel(view.published)}` : null,
view.updated && view.updated !== view.published ? `Updated ${dateLabel(view.updated)}` : null,
@@ -255,7 +282,7 @@ export default function ReportArticle({ view, siteTitle }: { view: ReportPageVie
</CitedMarkdown>
)}
{s.claims.map((c) => (
- <Claim key={c.id} claim={c} view={view} subjectLabel={subjectLabel} />
+ <Claim key={c.id} claim={c} view={view} subjectLabel={subjectLabel} subjectNoun={subjectNoun} />
))}
</section>
))}
@@ -276,7 +303,7 @@ export default function ReportArticle({ view, siteTitle }: { view: ReportPageVie
<h2 id="references-heading" className="font-display text-2xl font-semibold tracking-tight text-foreground">
References
</h2>
- <ReferenceList citations={references} />
+ <ReferenceList citations={references} addedLabel={`Not in the ${subjectNoun}`} />
</section>
)}
diff --git a/export/app/lib/reports.test.ts b/export/app/lib/reports.test.ts
@@ -126,7 +126,20 @@ test("the report page: header, tally, sections and claims, inline cites, referen
const meta = await reportPage.generateMetadata({ params });
assert.equal(meta.title, "Checking an example article");
// a claim's flag: a pill on its card; an unflagged claim carries none
- assert.match(html, /<article id="claim-4"[^]*?<span data-claim-flag=""[^>]*>No source given<\/span>/);
+ assert.match(html, /<article id="claim-4"[^]*?<span data-claim-flag=""[^>]*><svg[^>]*data-brand-mark=""[^]*?<\/svg>No source given<\/span>/);
+ // a claim's evidence: what the report added first, marked; then evidence of
+ // unknown origin, unmarked; what the article gave itself folded away, unmarked
+ const claim1 = html.slice(html.indexOf('<article id="claim-1"'), html.indexOf("</article>", html.indexOf('<article id="claim-1"')));
+ const order = [...claim1.matchAll(/data-citation="([^"]+)"/g)].map((m) => m[1]);
+ assert.deepEqual(order, ["c01", "au1", "w01"]);
+ assert.match(claim1, /<div data-citation="c01"[^>]*><p data-citation-added=""[^>]*><svg[^>]*data-brand-mark=""[^]*?<\/svg>Not in the article<\/p>/);
+ assert.equal([...claim1.matchAll(/data-citation-added=/g)].length, 1, "only the added card is marked");
+ assert.match(claim1, /<details data-subject-evidence=""[^>]*><summary[^>]*>In the article \(1\)<\/summary>[^]*?data-citation="w01"/);
+ assert.doesNotMatch(claim1, /<details[^>]* open/, "folded by default");
+ // the reference list marks the added citation with the mark alone
+ const refList = html.slice(html.indexOf("data-reference-list"));
+ assert.match(refList, /<li id="c-c01"[^]*?<span data-citation-added="" title="Not in the article"[^>]*><svg/);
+ assert.equal([...refList.matchAll(/data-citation-added=/g)].length, 1);
assert.equal([...html.matchAll(/data-claim-flag=/g)].length, 1);
});
diff --git a/export/e2e-report/report-site.spec.ts b/export/e2e-report/report-site.spec.ts
@@ -94,6 +94,29 @@ test("the report: verdict chips, the tally, the source's sentence as a still, nu
}
});
+test("a claim's evidence: what the report added first and marked, the article's own folded away", async ({ page }) => {
+ await page.goto(REPORT);
+ const claim = page.locator("article#claim-1");
+ const cards = claim.locator("[data-citation]");
+ await expect(cards).toHaveCount(3);
+ expect(await cards.evaluateAll((els) => els.map((e) => e.getAttribute("data-citation")))).toEqual(["c01", "au1", "w01"]);
+ // Added: the project's mark and the words; nothing else in the claim is marked.
+ const added = claim.locator('[data-citation="c01"] [data-citation-added]');
+ await expect(added).toHaveText("Not in the article");
+ await expect(added.locator("svg[data-brand-mark]")).toBeVisible();
+ await expect(claim.locator("[data-citation-added]")).toHaveCount(1);
+ // The flag pill wears the same mark.
+ await expect(page.locator("article#claim-4 [data-claim-flag] svg[data-brand-mark]")).toBeVisible();
+ // What the article gave itself: folded until asked for.
+ const given = claim.locator("details[data-subject-evidence]");
+ await expect(given.locator("summary")).toHaveText("In the article (1)");
+ await expect(given).not.toHaveAttribute("open", /.*/);
+ await expect(given.locator('[data-citation="w01"]')).toBeHidden();
+ await given.locator("summary").click();
+ await expect(given.locator('[data-citation="w01"]')).toBeVisible();
+ await expect(given.locator("[data-citation-added]")).toHaveCount(0);
+});
+
test("hovering an inline citation previews it; leaving closes it", async ({ page }) => {
await page.goto(REPORT);
for (const [id, kind, text] of [
diff --git a/export/fixtures/report-site/public/m/demo-podcast/ep-042/610.50-628.00/moment.json b/export/fixtures/report-site/public/m/demo-podcast/ep-042/610.50-628.00/moment.json
@@ -57,6 +57,23 @@
{
"reportId": "demo-factcheck",
"sectionId": "bridge",
+ "claimId": "claim-1",
+ "citationId": "au1",
+ "href": "/reports/demo-factcheck/#claim-1",
+ "reportTitle": "Checking an example article",
+ "sectionTitle": "The bridge",
+ "claimTitle": "“He opened it himself”",
+ "claimText": "He opened the bridge himself in 2018.",
+ "verdict": "CONTRADICTED",
+ "verdictStyle": {
+ "label": "Contradicted",
+ "color": "#e5534b"
+ },
+ "number": 2
+ },
+ {
+ "reportId": "demo-factcheck",
+ "sectionId": "bridge",
"claimId": "claim-2",
"citationId": "au1",
"href": "/reports/demo-factcheck/#claim-2",
diff --git a/export/fixtures/report-site/public/reports/demo-factcheck/page.json b/export/fixtures/report-site/public/reports/demo-factcheck/page.json
@@ -63,6 +63,7 @@
"voiceChecked": true,
"method": "cue-window v1"
},
+ "origin": "added",
"moment": "demo-channel/abc123/3126.00-3151.00",
"href": "/m/demo-channel/abc123/3126.00-3151.00/",
"start": 3126,
@@ -118,6 +119,7 @@
"number": 4,
"quote": "Opened to traffic: April 2019.",
"note": "The city's own record of the opening.",
+ "origin": "subject",
"href": "https://example.org/city/bridge-history",
"title": "Bridge history — city records",
"archiveUrl": "https://web.archive.org/web/2026/https://example.org/city/bridge-history"
@@ -168,7 +170,8 @@
"findings": "He was there, but the bridge opened in 2019: he says so [on stream](cite:c01), and the [city's record](cite:w01) agrees.",
"citations": [
"c01",
- "w01"
+ "w01",
+ "au1"
]
},
{
diff --git a/export/fixtures/report-site/source/demo-factcheck/report.json b/export/fixtures/report-site/source/demo-factcheck/report.json
@@ -33,6 +33,7 @@
"end": 3151,
"pad": { "before": 5, "after": 5 },
"quote": "The bridge opened in the spring of twenty nineteen, I was there for it.",
+ "origin": "added",
"verification": { "quoteScore": 0.97, "quoteCheckedAt": "2026-10-04T12:00:00Z", "voiceChecked": true, "method": "cue-window v1" }
},
"au1": {
@@ -69,7 +70,8 @@
"title": "Bridge history — city records",
"archiveUrl": "https://web.archive.org/web/2026/https://example.org/city/bridge-history",
"quote": "Opened to traffic: April 2019.",
- "note": "The city's own record of the opening."
+ "note": "The city's own record of the opening.",
+ "origin": "subject"
},
"unused": {
"kind": "page",
@@ -90,7 +92,7 @@
"verdict": "CONTRADICTED",
"sourceQuote": { "citation": "a01" },
"findings": "He was there, but the bridge opened in 2019: he says so [on stream](cite:c01), and the [city's record](cite:w01) agrees.",
- "citations": ["c01", "w01"]
+ "citations": ["c01", "w01", "au1"]
},
{
"id": "claim-2",