# Veritas Live claim extraction and verification integration

**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:

```text
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`](../libs/veritas/claims/src/extractor.ts)                      | A whole [`ClaimExtractionInput`](../libs/veritas/claims/src/types.ts) 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](../apps/veritas/nlp/src/services/claims/service.ts)   | `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`](../libs/veritas/agents-fact-checking/src/fact-checker.ts)    | 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`](../libs/veritas/video-production/src/advanced/live-event.ts) | 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:

1. 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.
2. 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.
3. 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/claims` supports ten specific claim categories plus `other`, rich
  importance/checkworthiness metadata, and character boundaries;
- the fact-checking package supports eight types and a smaller confidence and
  offset shape; and
- the canonical [`ClaimSchema`](../libs/contracts/src/veritas/index.ts) uses
  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`](../libs/oshun/domain-veritas/src/source-quality/composite.ts)
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`](../libs/oshun/domain-veritas/src/source-quality/composite.ts)
can start from a canonical [`Source`](../libs/contracts/src/veritas/index.ts).
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`](../libs/oshun/domain-veritas/src/source-quality/vector-store.ts)
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`](../libs/oshun/domain-veritas/src/claim-confidence.ts)
rolls source quality into a five-factor claim-confidence score, but it calls
[`evaluateVeritasSourceRecord`](../libs/oshun/domain-veritas/src/source-quality.ts),
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:

```text
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:

1. **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.
2. **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.
3. **Candidate deadline:** run rich heuristic extraction synchronously or in a
   short cancellable worker budget. Publish only a `detected` candidate at this
   stage. LLM enrichment may revise metadata but must not block later captions.
4. **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.
5. **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.
6. **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.
7. **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.
