Audit date: 2026-07-17
Scope: RB.1 source-first integration note for extracting claims from a live transcript, applying the Veritas nine-factor source-quality composite, and setting honest latency expectations for the Rail overlay.
Integration decision#
There is no single live-transcript-to-verdict pipeline to connect to the Rail today. The repository has real pieces, but their boundaries do not yet line up:
final caption / configured STT segment
-> Rail transcript-window adapter (new)
-> @veritas/claims heuristic extraction (candidate path)
-> stable Rail claim identity + transcript locator (new)
-> evidence retrieval and verification worker (new orchestration)
-> existing Veritas search, ranking, and fact-check integrations
-> governed provisional evidence -> attested canonical Source records
-> nine-factor source-quality vectors
-> claim state update: detected -> checking -> evidence-qualified result
The first ticker response should use the synchronous heuristic path over a bounded window of final transcript segments. LLM/hybrid extraction and the fact-checking agent are whole-response, multi-request operations and must run off the caption-ingest path. A detected claim is not a verified claim; the UI must preserve that distinction while evidence work is pending or unavailable.
The nine-factor composite is a source score, not a claim verdict. It is a
separate, deterministic domain utility today. The current fact-checking agent
does not call it, and the existing claim-confidence evaluator still calls the
older seven-factor source evaluator. RB.1 therefore needs an explicit adapter
from retrieved evidence to canonical Source records and score-vector revisions
before the ticker can truthfully display nine-factor-backed source quality.
Audited public surfaces#
| Surface | Input and result | What is usable now | Live boundary |
|---|---|---|---|
@veritas/claims |
A whole ClaimExtractionInput containing text and optional document context; returns rich claims with transcript-relative boundaries, entities, categories, six importance factors, checkworthiness, and measured wall time |
In-process heuristic, LLM, and hybrid extraction. The default is hybrid, at most 15 claims, minimum extraction confidence 0.3, and minimum checkworthiness 0.2. extractClaimsHeuristicOnly is the synchronous no-credential entry point. |
It accepts no segment id, speaker, timecode, interim/final state, stream cursor, cancellation signal, or deadline. Hybrid computes heuristics first but does not return them until the LLM pass finishes. Generated claim ids are random UUIDs, so repeated overlapping windows need a Rail-owned stable identity and deduplication rule. |
| Veritas NLP claim service | POST /claims/extract or /claims/extract/batch; returns a smaller claim shape with text, normalized text, optional character position, provider metadata, and LLM latency |
A deployable HTTP seam. The default provider is heuristic; LLM choices are OpenRouter, explicit OpenAI, or OpenCode CLI. LLM output must be valid, non-empty JSON or the request fails. |
The HTTP schema accepts document snapshots, not a stream. A single item is capped at 50,000 characters and a batch at 20 items by the route schemas, even though lower-level service limits default to 200,000 characters and 200 items. Heuristic responses report latencyMs: null. |
FactCheckerAgent |
A whole-text VerificationRequest; returns claims, evidence, external checks, verdicts, stage history, and total processing time |
Real ClaimBuster/Google Fact Check integration, optional Serper/Brave evidence search, LLM ranking and verdict generation, plus heuristic fallbacks | This is a sequential editorial pipeline, not a per-caption request path. It has its own narrower Claim model and extraction heuristic, rather than consuming @veritas/claims. Its source analysis is not the nine-factor composite. |
LiveEventManager |
An injected STTService emits TranscriptionSegment values with event id, start/end times, speaker, confidence, language, and isFinal |
A useful transcript event contract and recent-segment accumulation model | It is an interface seam, not a configured STT implementation, and no production implementation was found under apps/veritas or libs/veritas. It feeds optional key-moment detection only; no claim extractor consumes its events. The NLP app declares an STT base URL/capability in configuration but exposes no STT implementation or route. |
Extraction available for a transcript today#
Given a caller that already has transcript text, three extractors can operate on it now:
- The rich library's heuristic path splits text into sentences and applies quantitative, temporal, comparison, attribution, factual, causal, prediction, definition, existence, correlation, and Ghana-specific patterns. It then calculates boundaries, entities, categories, importance, and checkworthiness without a network call.
- The NLP service's default heuristic path provides a smaller production HTTP response and uses the optional title and summary alongside the text. It is a different implementation and output model from the rich library.
- The fact-checking agent sends the whole text to ClaimBuster when configured, then falls back to its own six-pattern sentence filter if ClaimBuster is absent or fails. It later scores each selected claim again and independently.
The rich library is the best fit for the ticker candidate path because it keeps boundaries and extraction factors and has an explicit synchronous API. The NLP HTTP route is a viable isolation boundary if deployment constraints require a service, but choosing it would give the ticker less claim metadata and would still require transcript-window and identity adapters.
None of the three consumes an incremental transcript protocol. Calling one for every caption would lose cross-segment sentences and repeatedly rediscover the same claim; continually resubmitting the entire transcript would make offsets stable but cost and latency grow with the event. RB.1 should instead buffer only final segments into bounded, overlapping windows, close a window at sentence or silence boundaries, preserve the contributing segment/time range, and dedupe on a deterministic key such as stream id plus normalized claim text and a bounded time bucket. Interim-caption revisions must not create public claims.
Claim representation seam#
Three incompatible claim representations coexist:
@veritas/claimssupports ten specific claim categories plusother, rich importance/checkworthiness metadata, and character boundaries;- the fact-checking package supports eight types and a smaller confidence and offset shape; and
- the canonical
ClaimSchemauses nine editorial claim classes, canonical source ids, confidence/retraction bands, and extraction provenance.
The Rail adapter must map deliberately rather than cast between them. In particular, it must retain the rich extraction record and transcript locator, assign a stable identity, map to a canonical claim class, and record the extraction method/confidence separately from later verification confidence.
What the nine-factor score consumes#
computeSourceQualityComposite
accepts a CompositeScorerInput and returns a SourceQualityScoreVector. The
numeric composite is a weighted geometric mean on [0, 100]; domain tables
select different weights by canonical claim class or an explicit domain such as
medical or journalism.
The nine scored inputs are:
| Factor | Required value |
|---|---|
peerReviewStatus |
peer-reviewed, preprint, institutional-report, journalism, press-release, opinion, social, or unattributed |
primacy |
primary, secondary, or tertiary |
editorialStandards |
positive, mixed, unknown, or negative |
retractionHistory |
The same four-state rating |
expertiseMatch |
The same four-state rating |
recency |
The same four-state rating; the composite does not derive it from dates |
rightsClarity |
The same four-state rating |
independence |
The same four-state rating |
crossCorroboration |
A number intended as a non-negative independent-source count; 0, 1, 2, 3, 4, and 5+ map to progressively stronger normalized values, while invalid/negative values fall to the score floor |
It also consumes source kind, publication and assessment timestamps, explicit retraction state/time/reason, reviewer-attestation presence, optional claim class/domain, and an optional exact domain-weight version. These are not ten more quality factors: source kind and publication date are context carried in the input, attestation absence is recorded as a reason but does not currently change the numeric score or band, and retraction/domain data drive overrides or weight selection.
The output retains every raw and normalized factor, weight, logarithmic
contribution, composite, band, domain table id/version, reasons, applied hard
overrides, attestation presence, and assessment time. Band thresholds are
high >= 82, mixed >= 60, low >= 35, and contested < 35. An unattributed
source is capped at low; a retraction within twelve months drops one band; a
currently retracted source is forced to contested.
Canonical-source derivation#
deriveCompositeInputFromSource
can start from a canonical Source.
For a transcript source it infers institutional-report and primary, copies
the stored editorial, retraction-history, expertise, recency, and rights
ratings, and reads attestation/retraction metadata. It cannot infer source
independence or corroboration: unless the caller supplies them, they become
unknown and 0. It also does not turn publishedAt into a recency rating.
The canonical SourceSchema requires a non-null reviewer attestation, so the
derivation helper always reports attestation as captured for a valid canonical
source. The direct composite input can truthfully set
reviewerAttestationCaptured: false, but that does not make an unreviewed
evidence result a valid canonical Source. RB.1 therefore needs a governed
provisional-evidence/review transition or a deliberate contract change; it must
not manufacture a reviewer id or attestation merely to satisfy the schema.
That default is intentionally conservative but would systematically depress an unreviewed live transcript. RB.1 must not replace missing assessments with positive values. Transcript attribution, publisher/rights records, independent evidence grouping, and corroborating-source counts have to be collected or the factor must remain unknown/zero.
Current wiring gap#
Production search found only the composite's revision recorder calling the
nine-factor function.
recordSourceQualityRevision
can append immutable per-source vectors and reconstruct the vector current at a
past time, but no fact-checking agent or app invokes that recorder today.
The current agent instead:
- assigns evidence credibility from a static URL-domain table;
- combines credibility and word-overlap relevance, with source-count and known fact-check-organization bonuses;
- may ask an LLM for evidence stance and a verdict; and
- calculates an AI-enhanced source analysis whose resulting map is currently
passed to verdict generation but deliberately unused as
_credibilityMap.
Separately,
evaluateVeritasClaimConfidence
rolls source quality into a five-factor claim-confidence score, but it calls
evaluateVeritasSourceRecord,
the older seven-factor additive evaluator. It is not evidence that the
nine-factor composite already influences claim confidence.
RB.1 needs to make one canonical path explicit:
evidence result
-> provisional assessment/review -> attested canonical Source + locator
-> derive CompositeScorerInput with explicit independence/corroboration
-> compute and record nine-factor vector revision
-> evidence stance/claim-verdict aggregation that consumes those vectors
The source vector and claim verdict should both remain visible in provenance; one must never be presented as the other.
Latency characteristics#
No audited extractor or end-to-end verification test establishes a live-ticker latency service level. Existing durations are instrumentation, not an SLO.
| Path | Execution behavior | Bounds and gaps |
|---|---|---|
| Rich heuristic extraction | Synchronous sentence/pattern work, followed by local post-processing; wall time is returned as processingTimeMs |
No network dependency and suitable for a bounded caption window. The package has a Vitest target but no claim-extractor test files or benchmark, so no percentile or maximum latency is established. |
| Rich LLM/hybrid extraction | Non-streaming, whole JSON completion. Hybrid runs the heuristic first, then waits for the LLM and merges before returning. Initialization, request, and parse failures fall back to heuristic with a warning. | The underlying journalism LLM client defaults to a 120-second timeout per attempt, three primary attempts, and up to two fallback attempts plus backoff. There is no tighter extraction deadline or cancellation input. A caller requesting LLM can therefore receive heuristic output after a long wait without a typed degraded-status result. |
| NLP HTTP heuristic | Local extraction in one request | The response currently reports latencyMs: null, so the service does not expose its heuristic processing duration. |
| NLP HTTP LLM | Non-streaming OpenAI-compatible completion, capped at 900 output tokens; malformed or empty output fails with 502 | The claim service does not pass its configured OPENAI_TIMEOUT_MS (45 seconds) or attempt count into the shared client. The effective shared OpenAI-compatible defaults are a 60-second request timeout and three SDK retries; router fallback only adds another registered provider when one exists. There is no claim-service-level deadline or partial result. |
| Nine-factor source score | Pure synchronous normalization, domain lookup, geometric mean, and band overrides | Deterministic unit coverage exists, but no benchmark. Runtime is not the live risk; acquiring and assessing the nine inputs is. |
| Fact-checking agent | Claims, per-claim checkworthiness, evidence queries, LLM ranking, per-domain analysis, external fact-check lookup, and verdicts run mostly in nested sequential loops | ClaimBuster and Google Fact Check each default to a 15-second request timeout. Serper/Brave evidence fetches in this agent have no abort timeout. The configured verificationTimeoutMs of five minutes and request deadline are declared but never enforced by the pipeline. Up to ten claims and five evidence queries per claim make this unsuitable for the caption callback. |
The agent records total and stage durations, and the LLM ranker/verdict generator record their own processing times. Those metrics should be retained when RB.1 adds bounded workers, but they do not solve cancellation, queue age, or a public deadline.
Required RB.1 adapter contract#
The implementation following this note should make these boundaries testable:
- Transcript ingress: accept only final caption segments from a real caption source or a configured STT engine. Preserve stream/event id, segment ids, speaker, language, confidence, and media-clock start/end. If no engine or caption source is configured, fail loud rather than fabricate text.
- Windowing and identity: assemble bounded overlapping text windows, preserve an offset-to-segment map, dedupe overlap, and generate stable claim identities independent of the extractors' random UUIDs.
- Candidate deadline: run rich heuristic extraction synchronously or in a
short cancellable worker budget. Publish only a
detectedcandidate at this stage. LLM enrichment may revise metadata but must not block later captions. - Verification deadline: enqueue evidence work with explicit cancellation, queue-age, per-provider, and end-to-end deadlines plus conservative concurrency. A timeout or missing provider becomes a typed unavailable or still-checking state, never a positive verdict.
- Canonical scoring: put every evidence item through an explicit provisional/review transition, create a canonical source only when its required attestation is real, and record the exact nine-factor input/vector/version. Keep unknown factors unknown and count corroboration by independent source, not raw URL count.
- Claim aggregation: map source vectors and evidence stances into one governed claim-confidence/verdict path. Do not mix extraction confidence, source quality, evidence relevance, and verdict confidence into one number.
- Observability and tests: measure caption-final-to-candidate, queue wait, provider calls, candidate-to-first-evidence, and candidate-to-terminal-state percentiles. Add deterministic window/dedupe tests, malformed/partial caption tests, provider timeout/cancellation tests, score-vector provenance tests, and Playwright coverage proving detected/checking/qualified/unavailable states are visually and semantically distinct.
This preserves the useful Veritas implementation already present while making the absent live orchestration and source-scoring integration explicit rather than implying that a live verified ticker already exists.