The helper TRUNCATEd papers CASCADE and only then ran ingest_all.sh, which
failed on the missing chunks.section column (audit M-1) — leaving the corpus
wiped. Add a preflight that verifies chunks.section exists BEFORE the
destructive step and aborts early (no TRUNCATE) with the owner-level ALTER to
run, since the app user cannot alter postgres-owned tables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One-time, guarded migration for the C-7 id-format change: a plain re-ingest
would duplicate every paper (new bare-id row beside the old URL-id PK), so this
wipes (TRUNCATE papers CASCADE) and rebuilds via ingest_all.sh.
Safety: requires the SSH tunnel, prints a BEFORE snapshot, gates the TRUNCATE
behind an explicit 'MIGRATE' confirmation, then prints AFTER verification —
C-7 (url_form_ids should be 0) and C-1 via the real resolver-based
discovery_leads() (ingested papers leaked should be 0). Uses .venv psycopg
(psql is not installed); DATABASE_URL is sourced, never echoed.
Joins PR #13 (Wave 2). Read-only verification SQL validated against the live DB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ADR's GO rested on a live-corpus spike (478 dangling, Bobenko/Springborn
rank 7, 5.7% spread) measured before the resolution + canonicalisation landed.
Add an audit addendum: mark the 'Known Limitation: ID-Format Mismatch' as
resolved (C-1 shared resolver + C-7 canonical ids), and flag the Spike Result
numbers as pending re-measurement after the C-7 re-ingest. History preserved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
extract_formulas/extract_figures derive paper_id from Path(pdf_path).stem, which
need not equal papers.id (and won't, given canonical ids). Inserting that stem
violated the formulas/figures -> papers(id) FK on rich (.pdf) ingest. The DELETE
already used paper.id; the INSERTs now do too.
Strengthened the rich-ingest test: the fake formula/figure carry a filename-stem
paper_id distinct from papers.id, and the test asserts the inserted rows are
keyed on papers.id.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
openalex.fetch_paper fills paper.id from OpenAlex's URL-form doi/id
(e.g. https://doi.org/10.48550/arxiv.math/0603097), and ingest upserted that as
papers.id. Live DB confirmed 35/36 ids were URL-form — breaking bare-id lookups
(graph related / discover citing / search by id) and leaving cited_id matching
to rely solely on openalex_id.
ingest now sets paper.id to the caller-supplied id (the arXiv id / DOI the CLI
and ingest scripts use); OpenAlex's work id is retained as openalex_id and in
paper_identifiers. Regression test: fetch_paper returns a URL-form id, papers.id
upsert uses the bare caller id.
T-3: the OpenAlex test fixture used a bare 'doi' (not the URL form the API
emits) and never asserted paper.id, which masked this. Fixture now uses the URL
form and the test pins fetch_paper's mapping.
NOTE: existing rows keep their URL-form ids until re-ingested — run ingest_all.sh
to migrate the 36-paper corpus (chosen migration path).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>