fix(generation): persist a subject reuse key so interrupted runs can be topped up (#1089) - #1961
fix(generation): persist a subject reuse key so interrupted runs can be topped up (#1089)#1961sloemo01 wants to merge 2 commits into
Conversation
|
✅ Health of changed files: 4.1 → 4.4 (+0.4) 📋 At a glance Files & modules (2)
✅ Health gate: passed 📌 Before you merge
🎯 Blast radius (symbols whose signature this PR changed, and who calls them)
🔎 More signals (3)🗺️ Change map flowchart LR
subgraph PR ["Changed in this PR (2 modules)"]
m_packages_cli["packages/cli (4 files)"]:::changed
m_packages_core["packages/core (5 files)"]:::changed
end
d_packages_cli["packages/cli"]
m_packages_core -->|7 files| d_packages_cli
d_packages_server["packages/server"]
m_packages_core -->|2 files| d_packages_server
classDef changed fill:#dbeafe,stroke:#1d4ed8,color:#1e3a5f
classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
classDef guard fill:#dcfce7,stroke:#15803d,color:#14532d
Solid arrows: code that imports the changed files (138 direct dependents, from the last indexed snapshot). Dashed: history/tests. 🔥 Hotspots touched (5)
2 more
🔗 Hidden coupling (3 files)
👀 Suggested reviewers @RaghavChamadiya 📊 See the full report for this PR |
What
Fixes #1089. A run interrupted by a provider outage can never be topped up: pages are always regenerated, and the missing ones are unreachable.
Root cause
The cross-run reuse gate in
PageGenerator._call_providerkeyed exclusively on the freshly-rendered prompt'ssource_hash. But the prompt drifts run to run — RAG context is rebuilt and populated concurrently each run — so on a full re-run the gate almost never matched, every unchanged page re-billed, and the pages a failed run left behind were never reachable as a cheap top-up (a stub row was either regenerated from scratch or hidden entirely). The maintainer's guidance (issue thread) named the shape of the fix: a stable subject key + routingrepowise generatethrough the same reuse path.Changes
wiki_pages.content_hashcolumn (Alembic0057, chained after0056). Both migration files (0056coverage mapping_partial,0057content_hash) are included on this branch, so the chain0055 → 0056 → 0057is valid against main regardless of merge order — 0056 is byte-identical to the one in PR fix(coverage): flag severe path-mapping loss instead of treating it as success (#1746) #1955, so no conflict either way. Computed assha256(subject_material : renderer_fingerprint)— the file's byte hash / group's member list / repo name folded with the prompt-template source, system prompt, language, style, and a hand-bumped generation version. Unlikesource_hashit survives RAG-context drift, so an unchanged page is reused instead of re-billed._call_providermatches oncontent_hashwhen both sides have one (falling back to the prompt hash for pre-key pages), and never reuses a row a model didn't write —provider_name == 'template'rows (keyless stubs, or stubs a failed provider call left) always re-call the model, so a top-up actually completes the missing pages.repowise generatefeeds prior pages into the gate (scoped_generationloadsload_prior_pagesand passes them through), so scoped refills reuse unchanged siblings instead of re-billing; a load failure degrades to no-reuse, never a failed run._structural_content_hash, and--force/workspace/upgrade call sites pass the key through.Tests
test_reuse_gate.py(6): subject-key hit, template-stub never reused, fallback to prompt hash, fingerprint change forces regen, empty-material falls back.test_content_hash_roundtrip.py(3): column persists, round-trips throughload_prior_pages, legacy rows default empty.test_scoped_generation_prior_pages.py(2):generatepasses prior pages in; load failure degrades.test_generation_persist.py(5): key survives persist. Generation + persistence + pipeline suites: 2,231 passed; ruff clean.