The cross-source fold exceeds its 3h ceiling without document extraction's help — and the doc-needs precompute it feeds is 87% dead weight by rows.
On top of the ceiling fix that merged as PR #3594 (6.65M → ~865K rows): keep the best K≈20 needs per document — the worst rank any caller ever reads is 7, so K=20 carries ~3× headroom. The reads are top-1-per-document (DISTINCT ON … ORDER BY distance); the store carries 453 needs per document to answer it. The row reduction is a direct Supabase IO/disk-charge cut on a table rebuilt every full pass.
Measured on Regina prod: 0.071 s vs 0.98 s per probe. 🪤 The two traps that make the obvious query WRONG: pgvector only uses the HNSW index for a constant/InitPlan probe — a joined-relation probe silently falls back to a full sort slower than brute force (8.66 s); and the index is tenant-partial but not node_type-partial, so the document filter discards every candidate and returns zero rows in 17 ms — fast, green, and wrong. The node_type-partial index is the safer fix and the natural builder for #3601's top-K store. Sequenced after it for that reason.
🟢 Merged 2026-08-26. The sole caller never passes p_max_distance and the reader clamps to ≤0.35, so the 0.30–0.35 band was 5,789,296 rows (87%, ~2.2 GB) no caller could reach — pure dead weight rebuilt every full pass. Zero semantic change: the store ceiling now equals the read default.
Completeness gap closed; sizing is what remains. The enqueue gap (#3600) was silent data-not-picked-up — the same class as the incident that spawned this track — and shipped and verified live in prod 2026-08-31, so it no longer sets the order. What is left is the sizing pair: the top-K store (#3601) is independent and delivers the row reduction on its own; the HNSW build (#3602) is the accelerator for it and carries the index migration, so it lands second.
Deliberately ignored: the ~14× speed headline (a fast build of an oversized store is the wrong win); filing order; the REFUTED option 4 (drop the table, serve on demand — measured at ~7 minutes per request, the precompute is legitimately required).
#3602. A closed issue named as current work is the drift this section exists to surface — re-derive the prose, or move the reference into History.
Evidence below is generated from the read-only audit of 2026-08-29 and reconciled against the open issues at build time. Every claim cites the issue it came from. Nothing here is a decision — rulings stay in the prose above, which is written by hand.
Only the DOCUMENT leg was scoped to delta: commit 900ef0436 on origin/main ([fold-efficiency] fix(backend,db): scope fold document reads to per-conversation delta with an independent sync watermark (#3495) (#3521)). Its own commit message states 'PersonMaterializer is intentionally left untouched ... Flagged as owner-gated follow-up, not solved here.' No commit on origin/main since 2026-08-18 addresses the typed-item leg (git log origin/main --since=2026-08-18 -i --grep 3495 returns exactly two commits: 900ef0436 and dc71c85b1, the latter being CI wiring for #3502). Prod corroboration (public.ingestion_runs, last 5 days, grouped by tenant/day): the cost is still fleet-wide and heavy — 2026-08-28 tenant e9d96b09 (Regina) 637 runs / 51 failed / avg 1,724s / max 33,117s; tenant be0f3db0 178 runs / 18 failed / avg 559s / max 25,944s; 2026-08-27 avg 2,620s Regina and 2,280s on tenant 23d86e0d. Caveat: ingestion_runs durations are whole-run, not the graph_materialization substep alone.
origin/main:backend/app/slices/graph_building/repositories/node_index_repository.py:106-145 — `_PERSON_ATTRIBUTE_DIGEST_SQL` still runs five independent full-tenant aggregates (person_valuable_message_counts, person_impersonation_verdicts, person_automated_verdicts, whatsapp_jid_mappings, person_identities), each `string_agg`+`md5` with no watermark/delta predicate. Reachable on every fold: `graph_materializer.py:409` and `materialize_scan_planner_service.py:141` both call `person_attribute_input_digest()` unconditionally. `git log origin/main -- node_index_repository.py` shows the newest touch is #3355 (2026-07, WIRE-TRUTH), nothing after the 2026-08-22 filing.
Query on prod `public.ingestion_runs` (phase_times JSON), runs since 2026-08-22: cross-conv vs fold seconds — Regina e9d96b09: 5403 vs 726 (08-29 08:06Z), 4622 vs 1445 (08-29 04:09Z), 5694 vs 372 (08-28 23:11Z), 5315 vs 2038 (08-28 17:39Z); contrast tenant 23d86e0d: 128 vs 41, 109 vs 40, 97 vs 30. The premise holds and has WORSENED: on Regina cross-conv is 3-15x the fold and is measured in thousands of seconds. Code side: `git log origin/main --date=short -- backend/app/slices/graph_building/phases/graph_cross_conv_matching_phase.py` shows only #3594 (doc_need ceiling) and #3526 since filing — no diagnosis or perf change to propose/judge/write_edges.
Installed writer in prod carries NO top-K: select pg_get_functiondef(oid) like '%LIMIT%' for public.refresh_library_doc_need_matches_for_docs -> false; the body is the unbounded INSERT ... SELECT from nodes_v2 d JOIN nodes_v2 nd WHERE distance < 0.30 (origin/main:supabase/migrations/20260826145848_shrink_doc_need_ceiling_to_read_default.sql lines 99-118). The prerequisite ceiling fix IS in effect: only 94 of 1,684,087 rows in public.doc_need_matches sit at distance >= 0.30. Measured top-K impact today (prod, row_number() over (partition by document_node_id order by distance)): K=20 keeps 330,639 of 1,684,087 rows across 21,151 documents — an 80.4% row cut, larger than the issue's ~865K -> ~316K projection because the live table is 1.68M rows, not 865K. NAMING DEFECT in the issue: the store table is public.doc_need_matches (2.19 GB, second-largest relation); public.library_doc_needs is NOT a table — it does not exist as any relation in any schema — it is the reader RPC (function). Retitle before acting.
6 assessed issues closed since the audit and are not shown — a closed issue's evidence is history, and printing it as current is how a page starts lying.
docs/exploration/20260826-regina-fold-cost-attribution.md — scoped to "can we defer document extraction out of the fold" and answered that the question is moot: extraction is per-source (ADR-118's elastic pool), never in the fold, and the fold overruns the ceiling without it (the 08-25 run died at 4.33h with no document-extraction phase). Cost attribution: the two graph phases are 83.4%. The exploration also found the enqueue gap (#3600), measured the HNSW alternative (14×/probe, two silent traps), and ranked the precompute-sizing alternatives that became #3601/#3602 after PR #3594 landed option 1. Its §6 explicitly does NOT recommend: deferring document extraction, raising the 3h backstop, starting the extractor pool (already started), or scaling out (19/19).
#3600 was closed after a verification pass, not a triage pass — triage had proposed 26 closures on this batch of issues and verification held 13 of them, this one included, confirmed by an adversarial critic that re-derived the p0/p1 verdicts rather than accepting them. The fix: doc_extract_drain.py's _arm_catch_up_folds calls enqueue_cross_source per tenant per batch, on both normal and exception exits from run_drain. Verified reachable (scheduled by both worker.py and extractor.py) and verified exercised (30 work_queue rows with trigger=doc_extract_batch, newest 2026-08-31).