Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ All notable changes to vouch are documented here. Format follows
the same tarball. `import_apply`, `import_check`, and `export_check`
now validate every member path and raise on unsafe names.
- Fix `vouch search` CLI: assign backend label per code path so substring fallback results are no longer mislabelled as `fts5`; update stale docstring to reflect multi-backend search surface (#52).
- `vouch crystallize` now indexes its session-summary page into FTS5 so it
surfaces from `vouch search` / `kb.search` / `kb.context` without a
`vouch index` rebuild. Previously the summary was written via
`store.put_page()` only, so on KBs with a populated `state.db` it was
silently absent from search results (#60).
- Bundle export uses POSIX `/` separators in `manifest.json` and tar member
names on every platform. Previously on Windows the manifest stored
`sources\<sha>\meta.yaml` while the tarball stored `sources/<sha>/meta.yaml`,
Expand Down
7 changes: 6 additions & 1 deletion src/vouch/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import uuid
from datetime import UTC, datetime

from . import audit
from . import audit, index_db
from .models import Page, PageType, ProposalStatus, Session
from .proposals import approve
from .storage import KBStore
Expand Down Expand Up @@ -107,6 +107,11 @@ def crystallize(
],
)
store.put_page(page)
with index_db.open_db(store.kb_dir) as conn:
index_db.index_page(
conn, id=page.id, title=page.title, body=page.body,
type=page.type.value, tags=page.tags,
)
Comment on lines +110 to +114

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle index-write failures without aborting crystallization.

On Line 110, an indexing DB error will raise out of crystallize() after approvals and store.put_page(page) have already succeeded. That makes this secondary index write a hard failure for the whole operation.

Suggested hardening
+import sqlite3
...
-        with index_db.open_db(store.kb_dir) as conn:
-            index_db.index_page(
-                conn, id=page.id, title=page.title, body=page.body,
-                type=page.type.value, tags=page.tags,
-            )
+        try:
+            with index_db.open_db(store.kb_dir) as conn:
+                index_db.index_page(
+                    conn, id=page.id, title=page.title, body=page.body,
+                    type=page.type.value, tags=page.tags,
+                )
+        except sqlite3.Error:
+            logger.exception(
+                "crystallize: failed to index summary page %s", page.id
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with index_db.open_db(store.kb_dir) as conn:
index_db.index_page(
conn, id=page.id, title=page.title, body=page.body,
type=page.type.value, tags=page.tags,
)
try:
with index_db.open_db(store.kb_dir) as conn:
index_db.index_page(
conn, id=page.id, title=page.title, body=page.body,
type=page.type.value, tags=page.tags,
)
except sqlite3.Error:
logger.exception(
"crystallize: failed to index summary page %s", page.id
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/sessions.py` around lines 110 - 114, The index write in
crystallize() currently calls index_db.open_db(...) and index_db.index_page(...)
after approvals and store.put_page(page) and any exception there will abort the
whole crystallize flow; wrap the index write in a safe, non-fatal try/except so
index errors are logged but do not raise (e.g., try: with
index_db.open_db(store.kb_dir) as conn: index_db.index_page(conn, id=page.id,
...) except Exception as e: logger.error("index_page failed for page=%s: %s",
page.id, e) ), ensuring approvals and store.put_page remain successful even if
index_db.open_db or index_db.index_page fails and use the existing logger to
record the failure for later investigation.

summary_page_id = page.id

audit.log_event(
Expand Down
15 changes: 15 additions & 0 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pytest

from vouch import index_db
from vouch import sessions as sess_mod
from vouch.proposals import approve, propose_claim
from vouch.storage import KBStore
Expand Down Expand Up @@ -46,6 +47,20 @@ def test_crystallize_skips_already_approved(store: KBStore) -> None:
assert result["approved"] == [] # already handled


def test_crystallize_summary_page_is_fts5_indexed(store: KBStore) -> None:
src = store.put_source(b"e")
sess = sess_mod.session_start(store, agent="claude-code")
propose_claim(store, text="findable claim", evidence=[src.id],
proposed_by="claude-code", session_id=sess.id)
sess_mod.session_end(store, sess.id)
result = sess_mod.crystallize(store, sess.id, approver="u")

summary_id = result["summary_page_id"]
assert summary_id is not None
hits = index_db.search(store.kb_dir, sess.id, limit=10)
assert any(kind == "page" and hid == summary_id for kind, hid, _, _ in hits)


def test_crystallize_collects_approval_failures(store: KBStore) -> None:
src = store.put_source(b"e")
sess = sess_mod.session_start(store, agent="a", task="t")
Expand Down