Skip to content

feat(source): pdf and audio sources with page/timestamp receipts - #720

Merged
plind-junior merged 3 commits into
vouchdev:testfrom
minion1227:feat/media-sources
Jul 31, 2026
Merged

feat(source): pdf and audio sources with page/timestamp receipts#720
plind-junior merged 3 commits into
vouchdev:testfrom
minion1227:feat/media-sources

Conversation

@minion1227

Copy link
Copy Markdown
Contributor

closes #613

why

a spec, a paper, a recorded call could not become citable material. a receipt is
the byte span [byte_start, byte_end) into a source's stored bytes, and the
bytes of a pdf or an mp3 do not spell the sentence anyone wants to quote. so that
knowledge either stayed out of the kb entirely or entered as an unattributable
paraphrase — the failure the issue describes.

what

vouch source add spec.pdf (or call.mp3) extracts the text layer / transcript
and stores that as the content-addressed artifact. byte-offset receipts keep
verifying by == with no new code path, so ingest, the receipt gate,
kb.source_verify and receipt coverage all work on media sources unchanged.

what is new is a coordinate map on Source.metadata, recording which byte
range came from which page or which point in the recording. a verified receipt
therefore also resolves to a real location in the original:

$ vouch source add postmortem.pdf
9f2c…
$ vouch source locate 9f2c… "rollback took eleven minutes"
p7@b1200-1340

the byte span is still the part that verifies; the p7 / t=00:14:23 prefix is
what makes the citation checkable by a human holding the original. --raw
registers the binary untouched.

the two hard requirements from the issue

no new hard dependency. pypdf is the optional [pdf] extra, imported lazily
inside extract_pdf_pages and never touched unless someone registers a pdf.
audio needs no extra at all: transcription is a configured command
(sources.transcribe_cmd, following the compile.llm_cmd pattern), so vouch
never bundles a speech model. the command emits webvtt or subrip; both parse.

fail loudly. a scanned pdf with no text layer raises rather than registering
an empty source, and there is no silent ocr fallback — knowledge that quietly
became empty is worse than knowledge that refused to enter.

the part that touches stored shape

the issue flags the receipt coordinate model as the one delicate piece. the
approach here is deliberately additive: Evidence is unchanged, receipts remain
byte offsets, and the map lives in Source.metadata as plain scalars that diff
readably like everything else under .vouch/. receipt_for_quote takes an
optional coordinates argument that only enriches the human-facing locator. a
malformed or missing map degrades to "no coordinate", never to an exception.

verify.py now re-checks the original binary against a recorded
origin_sha256, so vouch source verify still notices a pdf or a recording
changing under a claim that cites it. extracting to text by hand severs exactly
that link, which is what makes doing it inside vouch worth the module.

tests

56 tests in tests/test_media.py, none of which need pypdf installed or a
speech model on PATH — the pdf reader is injected as a fake module and the
transcription command is a shell one-liner, which is the point of both being
optional. covers byte-vs-character offsets on multibyte text, page/timestamp
resolution including the separator gap that belongs to no page, malformed
coordinate maps, webvtt and subrip parsing, the scanned-pdf refusal, the missing
extra, command failure and timeout, drift detection against the original, and
the cli surface.

pytest / mypy src / ruff check src tests green, diff coverage 100%.

one thing to flag

this branch also carries the two-line capture.py coerce_numeric fix that is
already on the queue in #693. a string min_observations raises ValueError out
of capture.load_config on the current test branch, which reddens the suite
for every pr opened against it — ci only runs on pull_request, so it went
unnoticed on the branch itself. included so this pr is green on its own merits;
it is an identical hunk and resolves cleanly whichever lands first. happy to drop
it if you would rather it stayed in one place.

a spec, a paper, a recorded call could not become citable material. a
receipt is the byte span [byte_start, byte_end) into a source's stored
bytes, and the bytes of a pdf or an mp3 do not spell the sentence anyone
wants to quote — so that knowledge either stayed out of the kb or entered
as an unattributable paraphrase.

the fix keeps the receipt primitive untouched. `vouch source add spec.pdf`
(or call.mp3) extracts the text layer / transcript and stores *that* as
the content-addressed artifact, so byte-offset receipts keep verifying by
`==` with no new code path — ingest, the receipt gate, kb.source_verify
and receipt coverage all work on it unchanged. what is new is a coordinate
map on Source.metadata recording which byte range came from which page or
which point in the recording, so a verified receipt also resolves to `p7`
or `t=00:14:23` in the original. `vouch source locate <id> <quote>` prints
it; --raw registers the binary untouched.

no new hard dependency, as the issue requires. pypdf is the optional [pdf]
extra imported lazily, and transcription is a configured command
(sources.transcribe_cmd) following the compile.llm_cmd pattern, so vouch
never bundles a speech model. a scanned pdf with no text layer raises
rather than registering an empty source — ocr is explicitly out of scope,
and knowledge that silently became empty is worse than knowledge that
refused to enter.

the original's sha256 is recorded, so verify.py re-checks the pdf or the
recording for drift. extracting to text by hand severs exactly that link,
which is what makes doing it inside vouch worth the module.

also carries the capture.py coerce_numeric fix already on the queue: a
string min_observations raises ValueError out of capture.load_config on
the current test branch and reddens the suite for every pr opened against
it. included so this pr's ci is green on its own merits; it is a two-line
overlap that resolves cleanly if the other lands first.

closes vouchdev#613

Co-authored-by: Cursor <cursoragent@cursor.com>
@minion1227
minion1227 requested a review from plind-junior as a code owner July 31, 2026 12:25
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface packaging packaging, build metadata, and make targets tests tests and fixtures size: XL 1000 or more changed non-doc lines labels Jul 31, 2026
@plind-junior
plind-junior merged commit be75dc4 into vouchdev:test Jul 31, 2026
12 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

diff coverage: n/a — this PR changes no python under src/vouch/, so there is nothing for the gate to measure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: passing ci is green cli command line interface docs documentation, specs, examples, and repo guidance packaging packaging, build metadata, and make targets size: XL 1000 or more changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(source): pdf and audio sources with page/timestamp receipts

2 participants