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."""