From a1e18bdcfd1db07e77724ff733a5b59319977bc1 Mon Sep 17 00:00:00 2001 From: Sinity Date: Mon, 13 Jul 2026 09:21:11 +0200 Subject: [PATCH] fix(storage): normalize empty origin filters Problem: public origin-filter entry points documented empty input as the safe unknown-origin scope, but strict enum conversion raised before SQL ran. What changed: route an empty origin to unknown-export in both SQL filter normalizers. Exercise the SQL builder and both real ArchiveStore-backed tool-usage and coverage routes so the filter remains scoped rather than becoming an unfiltered read. Compatibility: non-empty public filters remain canonical Origin values; provider tokens are still rejected at these boundaries. Co-Authored-By: Codex --- .../storage/sqlite/archive_tiers/archive.py | 2 ++ .../storage/sqlite/queries/filter_builder.py | 2 ++ tests/unit/insights/test_tool_usage.py | 36 +++++++++++++++++++ tests/unit/storage/test_schema_safety.py | 2 ++ 4 files changed, 42 insertions(+) diff --git a/polylogue/storage/sqlite/archive_tiers/archive.py b/polylogue/storage/sqlite/archive_tiers/archive.py index 1b85253d2b..d1eec6d14d 100644 --- a/polylogue/storage/sqlite/archive_tiers/archive.py +++ b/polylogue/storage/sqlite/archive_tiers/archive.py @@ -8711,6 +8711,8 @@ def _learning_correction_from_archive_row(row: sqlite3.Row | tuple[object, ...]) def _origin_value(origin: str | None) -> str | None: if origin is None: return None + if origin == "": + return Origin.UNKNOWN_EXPORT.value return Origin(origin).value diff --git a/polylogue/storage/sqlite/queries/filter_builder.py b/polylogue/storage/sqlite/queries/filter_builder.py index 4afe487338..0d829815bf 100644 --- a/polylogue/storage/sqlite/queries/filter_builder.py +++ b/polylogue/storage/sqlite/queries/filter_builder.py @@ -30,6 +30,8 @@ def _iso_to_epoch(iso_str: str) -> float: def _origin_value(value: str) -> str: + if value == "": + return Origin.UNKNOWN_EXPORT.value return Origin(value).value diff --git a/tests/unit/insights/test_tool_usage.py b/tests/unit/insights/test_tool_usage.py index 2221a0b1bd..3d190bce0f 100644 --- a/tests/unit/insights/test_tool_usage.py +++ b/tests/unit/insights/test_tool_usage.py @@ -24,6 +24,7 @@ import pytest from polylogue import Polylogue +from polylogue.insights.archive import ArchiveCoverageInsightQuery from polylogue.insights.tool_usage import ( TOOL_USAGE_INSIGHT_VERSION, ToolUsageInsight, @@ -309,6 +310,41 @@ async def test_aggregates_per_origin_and_tool(self, tmp_path: Path) -> None: origins = {entry.source_name for entry in insight.origin_coverage} assert origins == {"claude-code-session", "codex-session"} + async def test_empty_origin_selects_unknown_export_in_tool_and_coverage_routes(self, tmp_path: Path) -> None: + """An empty public filter is the explicit unknown-origin scope, not an error or unfiltered read.""" + archive = _archive(tmp_path) + db_path = archive.archive_root / "index.db" + ( + SessionBuilder(db_path, "unknown-1") + .provider("unknown") + .add_message( + "unknown-msg", + role="assistant", + text="Unknown provenance tool call", + blocks=[{"type": "tool_use", "name": "Inspect", "id": "unknown-tool"}], + ) + .save() + ) + ( + SessionBuilder(db_path, "codex-1") + .provider("codex") + .add_message( + "codex-msg", + role="assistant", + text="Codex tool call", + blocks=[{"type": "tool_use", "name": "ApplyPatch", "id": "codex-tool"}], + ) + .save() + ) + + [tool_usage] = await archive.list_tool_usage_insights(ToolUsageInsightQuery(origin="")) + coverage = await archive.list_archive_coverage_insights(ArchiveCoverageInsightQuery(origin="")) + + assert [(entry.source_name, entry.normalized_tool_name) for entry in tool_usage.entries] == [ + ("unknown-export", "inspect") + ] + assert [(entry.bucket, entry.session_count) for entry in coverage] == [("unknown-export", 1)] + async def test_coverage_reports_origin_without_actions(self, tmp_path: Path) -> None: archive = _archive(tmp_path) db_path = archive.archive_root / "index.db" diff --git a/tests/unit/storage/test_schema_safety.py b/tests/unit/storage/test_schema_safety.py index d589bac2d5..162a801398 100644 --- a/tests/unit/storage/test_schema_safety.py +++ b/tests/unit/storage/test_schema_safety.py @@ -384,6 +384,8 @@ def test_build_filters_with_empty_origin(self) -> None: where_clause, params = _build_session_filters(origin="") assert isinstance(where_clause, str) assert isinstance(params, list) + assert "origin = ?" in where_clause + assert params == ["unknown-export"] def test_build_filters_with_no_args(self) -> None: """No filters should produce empty/trivial WHERE clause."""