6ab497fa fixed the right thing — the limit now bounds work rather than embeddings (#351) — but
the selection it introduced is O(N) in converted filings, and sec ask runs it twice on
every question.
ask.ts:112-116 builds the index implicitly unless --no-index, so each question runs
selectDocumentsToIndex and countAlreadyIndexed. Both scan filing_document, and the first
also sorts it:
src/task/kb/selectDocumentsToIndex.ts:127-136
SELECT d.* FROM `filing_document` d
LEFT JOIN `kb_document` k ON k.`doc_id` = d.`accession_number` || ':' || d.`doc_file`
WHERE d.`section_count` > 0 AND k.`doc_id` IS NULL
ORDER BY d.`filing_date` DESC, d.`accession_number` DESC
LIMIT ?
filing_document carries exactly two indexes (storageRegistry.ts:239):
["form","converter_version","is_primary"] and ["converted_at"]. Neither covers
filing_date, so the LIMIT cannot terminate the scan.
Measured
Faithful copy of the real DDL and both real indexes, 300,000 rows, 99% already in kb_document
(the steady state ask runs in), LIMIT 26 (DEFAULT_ASK_INDEX_LIMIT + 1):
QP: SCAN d
QP: SEARCH k USING COVERING INDEX sqlite_autoindex_kb_document_1 (doc_id=?) LEFT-JOIN
QP: USE TEMP B-TREE FOR ORDER BY
selectDocumentsToIndex LIMIT 26, no filing_date index: 415 ms / 416 ms
CREATE INDEX fd_filing_date ON filing_document (filing_date DESC, accession_number DESC);
QP: SCAN d USING INDEX fd_filing_date
QP: SEARCH k USING COVERING INDEX sqlite_autoindex_kb_document_1 (doc_id=?) LEFT-JOIN
selectDocumentsToIndex LIMIT 26, WITH filing_date index: 21 ms / 12 ms
20–35× on an in-memory database, and the indexed plan is O(limit) rather than O(N) — the
gap widens with the corpus and widens again on disk with a cold page cache. This sits on top of
the chunk-scan cost in the sibling issue, on the same question.
The second scan
countAlreadyIndexed (:152-189) is a full filing_document ⋈ kb_document COUNT, run
unconditionally whenever force !== true, to populate skipped. The ask renderer never prints
skipped; only sec index does (ask.ts:71). So half the per-question cost is computed for a
field the caller discards.
Fix
- Add
["filing_date", "accession_number"] to filing_document's indexes in
src/config/storageRegistry.ts — one entry, and db setup / addMissingColumns already loop
the registry.
- Skip
countAlreadyIndexed when the caller will not render it (an option on the task input, or
compute it lazily), and consider whether ask's implicit index needs a selection at all when
kb_chunk is non-empty and the scope is unchanged.
Smaller, same file
countAlreadyIndexed omits the section_count > 0 clause the selection applies, so skipped
can count documents the selection would never return.
selectDocumentsToIndex's JSDoc says "newest first"; the repository arm selectByScan
(:205-222) returns repository order, so under --limit N the two paths select different
documents.
Found during the 2026-09-14 review. Snapshot: workglow-dev/prd → analysis/grades/2026-09-14/sec-detailed.md §4.6.
6ab497fafixed the right thing — the limit now bounds work rather than embeddings (#351) — butthe selection it introduced is
O(N)in converted filings, andsec askruns it twice onevery question.
ask.ts:112-116builds the index implicitly unless--no-index, so each question runsselectDocumentsToIndexandcountAlreadyIndexed. Both scanfiling_document, and the firstalso sorts it:
src/task/kb/selectDocumentsToIndex.ts:127-136filing_documentcarries exactly two indexes (storageRegistry.ts:239):["form","converter_version","is_primary"]and["converted_at"]. Neither coversfiling_date, so theLIMITcannot terminate the scan.Measured
Faithful copy of the real DDL and both real indexes, 300,000 rows, 99% already in
kb_document(the steady state
askruns in),LIMIT 26(DEFAULT_ASK_INDEX_LIMIT + 1):20–35× on an in-memory database, and the indexed plan is
O(limit)rather thanO(N)— thegap widens with the corpus and widens again on disk with a cold page cache. This sits on top of
the chunk-scan cost in the sibling issue, on the same question.
The second scan
countAlreadyIndexed(:152-189) is a fullfiling_document ⋈ kb_documentCOUNT, rununconditionally whenever
force !== true, to populateskipped. Theaskrenderer never printsskipped; onlysec indexdoes (ask.ts:71). So half the per-question cost is computed for afield the caller discards.
Fix
["filing_date", "accession_number"]tofiling_document'sindexesinsrc/config/storageRegistry.ts— one entry, anddb setup/addMissingColumnsalready loopthe registry.
countAlreadyIndexedwhen the caller will not render it (an option on the task input, orcompute it lazily), and consider whether
ask's implicit index needs a selection at all whenkb_chunkis non-empty and the scope is unchanged.Smaller, same file
countAlreadyIndexedomits thesection_count > 0clause the selection applies, soskippedcan count documents the selection would never return.
selectDocumentsToIndex's JSDoc says "newest first"; the repository armselectByScan(
:205-222) returns repository order, so under--limit Nthe two paths select differentdocuments.
Found during the 2026-09-14 review. Snapshot:
workglow-dev/prd→analysis/grades/2026-09-14/sec-detailed.md§4.6.