From 036c27a8546c43108d2a3df129d00cf8a9b5eb35 Mon Sep 17 00:00:00 2001 From: kurosawareiji7007-hub Date: Fri, 31 Jul 2026 00:05:14 -0700 Subject: [PATCH 1/2] fix(session-split): exclude archived pages from taken topics Prompts and draft collision treated archived session pages as taken, same one-way trap compile had before #700. Reuse compile._live_pages. Fixes #712 --- CHANGELOG.md | 4 ++++ src/vouch/session_split.py | 6 ++--- tests/test_session_split_renarrate.py | 32 ++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed8832f..43840627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,10 @@ All notable changes to vouch are documented here. Format follows artifact the caller could not already retrieve, and it touches no write path. ### Fixed +- **session-split ignores archived pages in TAKEN TOPICS / collisions** (#712): + prompts and `_file_drafts` treated every on-disk page as taken, so archiving + a session summary permanently blocked redraft under the same title. Both + now reuse `compile._live_pages` (same live set as compile post-#700). - **`reset()`/`deindex()` now clear the legacy `embeddings` table too** (#543 reopened, root-caused): both functions' own docstrings promise to remove every embedding row for a reindex or a deleted artifact, but diff --git a/src/vouch/session_split.py b/src/vouch/session_split.py index 9e566443..01949854 100644 --- a/src/vouch/session_split.py +++ b/src/vouch/session_split.py @@ -368,7 +368,7 @@ def build_split_prompt( if git_stat: lines += ["GIT STAT:", "```", git_stat, "```", ""] - pages = store.list_pages() + pages = compile_mod._live_pages(store) pending = compile_mod._pending_page_names(store) taken = [f"- {p.title}" for p in pages] + [f"- {n} [pending]" for n in sorted(pending)] lines += ["TAKEN TOPICS (do NOT redraft any of these):"] @@ -434,7 +434,7 @@ def build_renarrate_prompt(store: KBStore, body: str, *, title: str, max_pages: if title: lines += [f"SESSION RECORD TITLE: {title}", ""] lines += ["SESSION RECORD (markdown):", body, ""] - pages = store.list_pages() + pages = compile_mod._live_pages(store) pending = compile_mod._pending_page_names(store) taken = [f"- {p.title}" for p in pages] + [f"- {n} [pending]" for n in sorted(pending)] lines += ["TAKEN TOPICS (do NOT redraft any of these):"] @@ -492,7 +492,7 @@ def _file_drafts( max_pages: int, origin: Path | None = None, ) -> tuple[list[str], list[dict[str, Any]]]: - existing = store.list_pages() + existing = compile_mod._live_pages(store) taken = {p.title.strip().lower() for p in existing} taken |= {p.id.strip().lower() for p in existing} taken |= compile_mod._pending_page_names(store) diff --git a/tests/test_session_split_renarrate.py b/tests/test_session_split_renarrate.py index 6e120570..847fafb2 100644 --- a/tests/test_session_split_renarrate.py +++ b/tests/test_session_split_renarrate.py @@ -21,7 +21,7 @@ from vouch import capture, session_split from vouch import compile as compile_mod from vouch.llm_draft import LLMDraftError -from vouch.models import Page, Proposal, ProposalKind, ProposalStatus +from vouch.models import Page, PageStatus, Proposal, ProposalKind, ProposalStatus from vouch.session_split import SPLIT_ACTOR, SplitConfig, load_split_config from vouch.storage import KBStore @@ -154,6 +154,36 @@ def test_renarrate_prompt_lists_durable_and_pending_topics( assert "- retrieval backends [pending]" in prompt +def test_renarrate_prompt_and_file_drafts_ignore_archived_pages( + store: KBStore, +) -> None: + """Archived pages must not block TAKEN TOPICS or draft collision (#712).""" + store.put_page(Page( + id="archived-session-topic", + title="retired session topic XYZ", + status=PageStatus.ARCHIVED, + )) + store.put_page(Page( + id="live-topic", + title="landed the review gate", + status=PageStatus.ACTIVE, + )) + prompt = session_split.build_renarrate_prompt( + store, "body", title="t", max_pages=3, + ) + assert "retired session topic XYZ" not in prompt + assert "landed the review gate" in prompt + + ids, dropped = session_split._file_drafts( + store, + "s-arch", + [{"title": "retired session topic XYZ", "body": "rewrote the retired topic"}], + max_pages=3, + ) + assert len(ids) == 1 + assert dropped == [] + + # --- _try_renarrate ------------------------------------------------------ From 99280261f2c37a2ea6111ec502dc8031045e0581 Mon Sep 17 00:00:00 2001 From: kurosawareiji7007-hub Date: Fri, 31 Jul 2026 00:22:54 -0700 Subject: [PATCH 2/2] fix(capture): wire coerce_numeric for load_config numerics Upstream imported coerce_numeric but still used bare int()/float(), which left an unused-import ruff F401 that failed CI on #713. --- src/vouch/capture.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vouch/capture.py b/src/vouch/capture.py index 9aeb98b8..ec99b654 100644 --- a/src/vouch/capture.py +++ b/src/vouch/capture.py @@ -75,9 +75,15 @@ def load_config(store: KBStore) -> CaptureConfig: return CaptureConfig( enabled=coerce_bool(raw.get("enabled", DEFAULT_ENABLED), DEFAULT_ENABLED), realtime=coerce_bool(raw.get("realtime", DEFAULT_REALTIME), DEFAULT_REALTIME), - min_observations=int(raw.get("min_observations", DEFAULT_MIN_OBSERVATIONS)), - dedup_window_seconds=float( - raw.get("dedup_window_seconds", DEFAULT_DEDUP_WINDOW_SECONDS) + min_observations=coerce_numeric( + raw.get("min_observations", DEFAULT_MIN_OBSERVATIONS), + DEFAULT_MIN_OBSERVATIONS, + int, + ), + dedup_window_seconds=coerce_numeric( + raw.get("dedup_window_seconds", DEFAULT_DEDUP_WINDOW_SECONDS), + DEFAULT_DEDUP_WINDOW_SECONDS, + float, ), answer_mode=answer_mode, )