From 790cdbfd5f9ece39d46a72c325f6b3c69425b0e7 Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 11:37:52 +0200 Subject: [PATCH 1/7] feat(mcp): wire file_edits and session_agent_policies to get() Problem: polylogue-nua7 found session_agent_policies (402,879 rows), file_edits (76,105), and session_refs (18,949) each have a complete, correct, tested read chain that terminates at repository/archive/sessions.py with nothing above it -- zero references outside polylogue/storage/ except their own tests. session_refs was already wired by #3425/#3431 (correlation_view.py); file_edits and session_agent_policies remained unreachable from any CLI/MCP/API surface. Solution: add Polylogue.get_file_edits()/get_agent_policies() to polylogue/api/archive.py (mirroring the existing get_session_events() reader pattern), and wire them into the MCP get(ref, projection=...) dispatcher as two new projections ("file-edits", "agent-policies") alongside the existing "events" projection. No new MCP tool or tool contract needed -- same six-tool "get" operation, new projection values documented in its docstring. Verification: devtools test tests/unit/mcp/test_server_surfaces.py (9 passed, including 2 new tests exercising get(projection="file-edits") and get(projection="agent-policies") through the real MCP tool_manager entrypoint against a real ArchiveStore-written session, not the storage function directly). Ref polylogue-nua7 --- polylogue/api/archive.py | 70 ++++++++++ polylogue/mcp/server_cutover.py | 25 ++++ tests/unit/mcp/test_server_surfaces.py | 180 +++++++++++++++++++++++++ 3 files changed, 275 insertions(+) diff --git a/polylogue/api/archive.py b/polylogue/api/archive.py index e5ee55fc31..b7473b0875 100644 --- a/polylogue/api/archive.py +++ b/polylogue/api/archive.py @@ -5426,6 +5426,76 @@ async def get_session_events( for event in events ] + async def get_file_edits(self, session_id: str) -> list[dict[str, object]] | None: + """Return file-edit tool-call evidence (structuredPatch/originalFile/...) for one session. + + polylogue-nua7: the writer materializes ``ParsedFileEdit`` evidence + (Claude Code Edit/Write/MultiEdit tool calls -- structured unified + diffs, pre-edit file content, old/new string pairs) into the + dedicated ``file_edits`` index table on every ingest + (``storage/repository/archive/sessions.py::get_file_edits``), but + before this reader nothing above the storage layer could reach it. + This is the read surface: what a "what did this session change" + report needs instead of re-deriving edits from tool-call prose. + + Returns ``None`` when the session does not exist (distinct from an + empty list, meaning the session exists but made no captured edits). + """ + resolved = await self.repository.resolve_id(session_id) + resolved_id = str(resolved) if resolved is not None else session_id + session = await self.repository.get(resolved_id) + if session is None: + return None + edits = await self.repository.get_file_edits(resolved_id) + return [ + { + "tool_use_block_id": edit.tool_use_block_id, + "message_id": str(edit.message_id), + "file_path": edit.file_path, + "structured_patch": edit.structured_patch, + "original_file": edit.original_file, + "old_string": edit.old_string, + "new_string": edit.new_string, + "replace_all": edit.replace_all, + "user_modified": edit.user_modified, + "observed_at_ms": edit.observed_at_ms, + } + for edit in edits + ] + + async def get_agent_policies(self, session_id: str) -> list[dict[str, object]] | None: + """Return sandbox/approval/network policy facts recorded for one session. + + polylogue-nua7: the writer diverts Codex ``agent_policy`` events out + of ``session_events`` into the dedicated ``session_agent_policies`` + table (fully re-derivable, zero evidence loss -- see + ``archive_tiers/write.py:_SESSION_EVENTS_REDUNDANT_TYPES``), but + before this reader nothing above the storage layer could reach it + back. This is the read surface. + + Returns ``None`` when the session does not exist (distinct from an + empty list, meaning the session exists but reported no agent-policy + facts -- expected for non-Codex origins). + """ + resolved = await self.repository.resolve_id(session_id) + resolved_id = str(resolved) if resolved is not None else session_id + session = await self.repository.get(resolved_id) + if session is None: + return None + policies = await self.repository.get_agent_policies(resolved_id) + return [ + { + "policy_id": policy.policy_id, + "position": policy.position, + "approval_policy": policy.approval_policy, + "sandbox_policy": policy.sandbox_policy, + "network_policy": policy.network_policy, + "observed_at_ms": policy.observed_at_ms, + "source_message_id": policy.source_message_id, + } + for policy in policies + ] + async def query_sessions( self, *, diff --git a/polylogue/mcp/server_cutover.py b/polylogue/mcp/server_cutover.py index e9526f4b29..dbd455eb00 100644 --- a/polylogue/mcp/server_cutover.py +++ b/polylogue/mcp/server_cutover.py @@ -831,6 +831,17 @@ async def get(ref: str, projection: str | None = None) -> str: tool-availability spans, and similar provider evidence that rides the timeline rather than a dialogue message. + ``projection="file-edits"`` returns captured Claude Code Edit/Write/ + MultiEdit tool-call evidence for the session -- structured unified + diffs (``structured_patch``), pre-edit file content + (``original_file``), and old/new string pairs -- the typed "what did + this session change" data instead of inferring it from tool-call + prose. + + ``projection="agent-policies"`` returns sandbox/approval/network + policy facts (e.g. Codex ``agent_policy`` events) recorded on the + session's own timeline. + ``ref="cost-outlook:"`` projects the current billing cycle for a configured subscription plan (the standalone ``cost_outlook`` MCP tool retired by the six-tool cutover, #3095/polylogue-t46.8, has @@ -861,6 +872,20 @@ async def run() -> str: return hooks.json_payload( MCPRootPayload(root={"session_id": session_id, "total": len(events), "events": events}) ) + if projection == "file-edits" and session_id is not None: + edits = await hooks.get_polylogue().get_file_edits(session_id) + if edits is None: + return hooks.error_json(f"object not found: {ref}", code="not_found", tool="get") + return hooks.json_payload( + MCPRootPayload(root={"session_id": session_id, "total": len(edits), "file_edits": edits}) + ) + if projection == "agent-policies" and session_id is not None: + policies = await hooks.get_polylogue().get_agent_policies(session_id) + if policies is None: + return hooks.error_json(f"object not found: {ref}", code="not_found", tool="get") + return hooks.json_payload( + MCPRootPayload(root={"session_id": session_id, "total": len(policies), "agent_policies": policies}) + ) return hooks.json_payload(await hooks.get_polylogue().resolve_ref(normalized)) return await hooks.async_safe_call("get", run, session_id=session_id) diff --git a/tests/unit/mcp/test_server_surfaces.py b/tests/unit/mcp/test_server_surfaces.py index 9b9a29f821..41d6f71ebc 100644 --- a/tests/unit/mcp/test_server_surfaces.py +++ b/tests/unit/mcp/test_server_surfaces.py @@ -271,3 +271,183 @@ async def test_get_projection_events_surfaces_session_timeline_evidence( ) ) assert missing["code"] == "not_found" + + +@pytest.mark.asyncio +async def test_get_projection_file_edits_surfaces_structured_patch_evidence( + mcp_server: MCPServerUnderTest, tmp_path: Path +) -> None: + """``get(ref, projection="file-edits")`` reaches the ``file_edits`` table + (polylogue-nua7/polylogue-cgfy): structured unified diffs, pre-edit file + content, and old/new string pairs captured on Edit/Write tool calls, but + unreachable from any surface before this projection existed -- the exact + "what did this session change" evidence a postmortem report needs. + """ + from polylogue.core.enums import BlockType, Provider, Role + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedFileEdit, ParsedMessage, ParsedSession + + archive_root = tmp_path / "archive" + with ArchiveStore(archive_root) as archive_db: + parsed = ParsedSession( + source_name=Provider.CLAUDE_CODE, + provider_session_id="mcp-file-edits-ref", + title="MCP file-edits projection", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.ASSISTANT, + position=0, + blocks=[ + ParsedContentBlock( + type=BlockType.TOOL_USE, + tool_name="Edit", + tool_id="edit-tool-1", + tool_input={"file_path": "/tmp/foo.py"}, + ), + ], + ), + ParsedMessage( + provider_message_id="m2", + role=Role.USER, + position=1, + blocks=[ + ParsedContentBlock( + type=BlockType.TOOL_RESULT, + tool_id="edit-tool-1", + text="applied", + file_edit=ParsedFileEdit( + file_path="/tmp/foo.py", + structured_patch=[ + {"oldStart": 1, "oldLines": 1, "newStart": 1, "newLines": 2, "lines": ["+x"]} + ], + original_file="old contents\n", + old_string="old", + new_string="new", + replace_all=False, + user_modified=True, + ), + ), + ], + ), + ], + ) + archive_db.write_raw_and_parsed( + parsed, + payload=b'{"raw": "claude payload"}', + source_path="/tmp/raw.jsonl", + acquired_at_ms=1735689600000, + ) + + uri = "polylogue://session/claude-code-session:mcp-file-edits-ref" + from polylogue import Polylogue + + with ( + patch("polylogue.mcp.server._get_config", return_value=SimpleNamespace(archive_root=archive_root)), + patch("polylogue.mcp.server._get_polylogue", return_value=Polylogue(archive_root=archive_root)), + ): + payload = json.loads( + await invoke_surface_async(mcp_server._tool_manager._tools["get"].fn, ref=uri, projection="file-edits") + ) + default_payload = json.loads(await invoke_surface_async(mcp_server._tool_manager._tools["get"].fn, ref=uri)) + + assert payload["total"] == 1 + edit = payload["file_edits"][0] + assert edit["file_path"] == "/tmp/foo.py" + assert edit["original_file"] == "old contents\n" + assert edit["old_string"] == "old" + assert edit["new_string"] == "new" + assert edit["structured_patch"] == [{"oldStart": 1, "oldLines": 1, "newStart": 1, "newLines": 2, "lines": ["+x"]}] + assert "file_edits" not in default_payload + + with ( + patch("polylogue.mcp.server._get_config", return_value=SimpleNamespace(archive_root=archive_root)), + patch("polylogue.mcp.server._get_polylogue", return_value=Polylogue(archive_root=archive_root)), + ): + missing = json.loads( + await invoke_surface_async( + mcp_server._tool_manager._tools["get"].fn, + ref="polylogue://session/claude-code-session:does-not-exist", + projection="file-edits", + ) + ) + assert missing["code"] == "not_found" + + +@pytest.mark.asyncio +async def test_get_projection_agent_policies_surfaces_sandbox_facts( + mcp_server: MCPServerUnderTest, tmp_path: Path +) -> None: + """``get(ref, projection="agent-policies")`` reaches the dedicated + ``session_agent_policies`` table (polylogue-nua7) -- Codex sandbox/ + approval/network policy facts the writer diverts out of + ``session_events`` for zero-loss re-derivation, but which had zero + surface consumers before this projection. + """ + from polylogue.core.enums import BlockType, Provider, Role + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession, ParsedSessionEvent + + archive_root = tmp_path / "archive" + with ArchiveStore(archive_root) as archive_db: + parsed = ParsedSession( + source_name=Provider.CODEX, + provider_session_id="mcp-agent-policies-ref", + title="MCP agent-policies projection", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.USER, + text="run it", + position=0, + blocks=[ParsedContentBlock(type=BlockType.TEXT, text="run it")], + ), + ], + session_events=[ + ParsedSessionEvent( + event_type="agent_policy", + timestamp="2026-01-01T00:00:01+00:00", + payload={ + "approval_policy": "never", + "sandbox_policy": "danger-full-access", + "network_policy": "true", + }, + ), + ], + ) + archive_db.write_raw_and_parsed( + parsed, + payload=b'{"raw": "codex payload"}', + source_path="/tmp/raw.jsonl", + acquired_at_ms=1735689600000, + ) + + uri = "polylogue://session/codex-session:mcp-agent-policies-ref" + from polylogue import Polylogue + + with ( + patch("polylogue.mcp.server._get_config", return_value=SimpleNamespace(archive_root=archive_root)), + patch("polylogue.mcp.server._get_polylogue", return_value=Polylogue(archive_root=archive_root)), + ): + payload = json.loads( + await invoke_surface_async(mcp_server._tool_manager._tools["get"].fn, ref=uri, projection="agent-policies") + ) + default_payload = json.loads(await invoke_surface_async(mcp_server._tool_manager._tools["get"].fn, ref=uri)) + + assert payload["total"] == 1 + policy = payload["agent_policies"][0] + assert policy["approval_policy"] == "never" + assert policy["sandbox_policy"] == "danger-full-access" + assert policy["network_policy"] == "true" + assert "agent_policies" not in default_payload + + with ( + patch("polylogue.mcp.server._get_config", return_value=SimpleNamespace(archive_root=archive_root)), + patch("polylogue.mcp.server._get_polylogue", return_value=Polylogue(archive_root=archive_root)), + ): + missing = json.loads( + await invoke_surface_async( + mcp_server._tool_manager._tools["get"].fn, + ref="polylogue://session/codex-session:does-not-exist", + projection="agent-policies", + ) + ) + assert missing["code"] == "not_found" From 68de2412319103fe78e68c47dccea3123c8752fc Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 12:16:02 +0200 Subject: [PATCH 2/7] feat(cli): add read --view file-edits / agent-policies Problem: polylogue-nua7 found file_edits (76,105 live rows) and session_agent_policies each have a complete, tested read chain that terminates at the repository layer with nothing above it -- reachable only from the MCP get(projection=...) surface added in the prior commit, not from the CLI. Solution: register two new read views following the existing "events"/ "hooks" pattern -- polylogue/cli/messages.py::run_session_file_edits / run_session_agent_policies call the new Polylogue.get_file_edits() / get_agent_policies() API methods; polylogue/cli/read_views/file_edits.py wires them into the read-view invocation/delivery machinery; registered in read_view_handlers.py, read_view_registry.py, and the profile metadata in archive/viewport/profiles.py. Also required updating polylogue/surfaces/projection_spec.py's EvidenceFamily/ READ_VIEW_PROJECTION_FAMILIES maps (a separate registry from the CLI handler registry) -- missing entries there raised "unknown projection view" at runtime. Regenerated docs/cli-reference.md and docs/plans/topology-target.yaml (new polylogue/cli/read_views/file_edits.py module) via devtools render. Verification: devtools test tests/unit/cli/test_file_edits_and_agent_policies_views.py (2 passed, full CliRunner invocation of `read --view file-edits` / `read --view agent-policies` against a real ArchiveStore-written session -- not the storage function or API method in isolation); devtools test tests/unit/cli/test_click_app.py tests/unit/cli/test_completion_matrix.py -k view (14 passed, no registry drift); devtools verify --quick (exit 0); devtools render all --check (all surfaces sync OK). Ref polylogue-nua7 --- docs/cli-reference.md | 6 +- docs/plans/topology-target.yaml | 98 +++++----- polylogue/archive/viewport/profiles.py | 35 ++++ polylogue/cli/messages.py | 100 +++++++++- polylogue/cli/read_view_handlers.py | 13 ++ polylogue/cli/read_view_registry.py | 2 + polylogue/cli/read_views/file_edits.py | 92 ++++++++++ polylogue/surfaces/projection_spec.py | 4 + ...est_file_edits_and_agent_policies_views.py | 171 ++++++++++++++++++ 9 files changed, 468 insertions(+), 53 deletions(-) create mode 100644 polylogue/cli/read_views/file_edits.py create mode 100644 tests/unit/cli/test_file_edits_and_agent_policies_views.py diff --git a/docs/cli-reference.md b/docs/cli-reference.md index d3a66c41ef..f6294864a4 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -298,9 +298,9 @@ Usage: polylogue read [OPTIONS] [REF] Projection: -v, --view VIEW[,VIEW...] What to render (summary, transcript, dialogue, messages, raw, hooks, events, - context, context-image, neighbors, - correlation, temporal, chronicle). - [default: summary] + file-edits, agent-policies, context, + context-image, neighbors, correlation, + temporal, chronicle). [default: summary] --render TEXT Render expression, e.g. layout:context- image,timestamps:include- available,format:markdown. Known keys: diff --git a/docs/plans/topology-target.yaml b/docs/plans/topology-target.yaml index d99b5884ae..97970ac54c 100644 --- a/docs/plans/topology-target.yaml +++ b/docs/plans/topology-target.yaml @@ -37,7 +37,7 @@ files: target: polylogue/agent_integration/manifest.py owner: stable - path: polylogue/agent_integration/spec.py - loc: 945 + loc: 946 target: polylogue/agent_integration/spec.py owner: stable - path: polylogue/annotations/__init__.py @@ -70,7 +70,7 @@ files: owner: stable cross_cut: { api: async } - path: polylogue/api/archive.py - loc: 6820 + loc: 7058 target: polylogue/api/archive.py owner: stable cross_cut: { api: async } @@ -179,7 +179,7 @@ files: owner: archive-artifact-taxonomy reason: archive-domain semantics - path: polylogue/archive/artifact_taxonomy/runtime.py - loc: 446 + loc: 477 target: polylogue/archive/artifact_taxonomy/runtime.py owner: archive-artifact-taxonomy reason: archive-domain semantics @@ -330,7 +330,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/archive_execution.py - loc: 703 + loc: 704 target: polylogue/archive/query/archive_execution.py owner: archive-query reason: archive-domain query semantics @@ -360,7 +360,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/expression.py - loc: 3567 + loc: 3588 target: polylogue/archive/query/expression.py owner: archive-query reason: archive-domain query semantics @@ -370,12 +370,12 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/fields.py - loc: 988 + loc: 991 target: polylogue/archive/query/fields.py owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/metadata.py - loc: 1439 + loc: 1449 target: polylogue/archive/query/metadata.py owner: archive-query reason: archive-domain query semantics @@ -465,7 +465,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/search_hits.py - loc: 340 + loc: 369 target: polylogue/archive/query/search_hits.py owner: archive-query reason: archive-domain query semantics @@ -485,7 +485,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/spec.py - loc: 611 + loc: 641 target: polylogue/archive/query/spec.py owner: archive-query reason: archive-domain query semantics @@ -731,7 +731,7 @@ files: owner: archive-viewport reason: archive-domain semantics - path: polylogue/archive/viewport/profiles.py - loc: 383 + loc: 418 target: polylogue/archive/viewport/profiles.py owner: archive-viewport reason: archive-domain semantics @@ -819,11 +819,11 @@ files: target: polylogue/cli/__main__.py owner: stable - path: polylogue/cli/archive_query.py - loc: 2775 + loc: 2765 target: polylogue/cli/archive_query.py owner: stable - path: polylogue/cli/click_app.py - loc: 651 + loc: 652 target: polylogue/cli/click_app.py owner: stable - path: polylogue/cli/click_command_registration.py @@ -831,7 +831,7 @@ files: target: polylogue/cli/click_command_registration.py owner: stable - path: polylogue/cli/click_option_groups.py - loc: 390 + loc: 399 target: polylogue/cli/click_option_groups.py owner: stable - path: polylogue/cli/command_inventory.py @@ -1031,7 +1031,7 @@ files: target: polylogue/cli/commands/scan_secrets.py owner: stable - path: polylogue/cli/commands/status.py - loc: 2467 + loc: 2482 target: polylogue/cli/commands/status.py owner: stable - path: polylogue/cli/commands/status_diagnostics.py @@ -1059,7 +1059,7 @@ files: target: polylogue/cli/machine_main.py owner: stable - path: polylogue/cli/messages.py - loc: 269 + loc: 367 target: polylogue/cli/messages.py owner: stable - path: polylogue/cli/onboarding.py @@ -1119,11 +1119,11 @@ files: target: polylogue/cli/query_verbs.py owner: stable - path: polylogue/cli/read_view_handlers.py - loc: 247 + loc: 260 target: polylogue/cli/read_view_handlers.py owner: stable - path: polylogue/cli/read_view_registry.py - loc: 114 + loc: 116 target: polylogue/cli/read_view_registry.py owner: stable - path: polylogue/cli/read_views/__init__.py @@ -1150,6 +1150,10 @@ files: loc: 73 target: polylogue/cli/read_views/events.py owner: stable + - path: polylogue/cli/read_views/file_edits.py + loc: 92 + target: polylogue/cli/read_views/file_edits.py + owner: stable - path: polylogue/cli/read_views/messages.py loc: 262 target: polylogue/cli/read_views/messages.py @@ -1175,7 +1179,7 @@ files: target: polylogue/cli/root_request.py owner: stable - path: polylogue/cli/select.py - loc: 272 + loc: 262 target: polylogue/cli/select.py owner: stable - path: polylogue/cli/shared/check_maintenance.py @@ -1371,7 +1375,7 @@ files: owner: core-primitive reason: core primitive - path: polylogue/core/enums.py - loc: 613 + loc: 619 target: polylogue/core/enums.py owner: core-primitive reason: core primitive @@ -1431,7 +1435,7 @@ files: owner: core-primitive reason: core primitive - path: polylogue/core/provider_identity.py - loc: 177 + loc: 179 target: polylogue/core/provider_identity.py owner: core-primitive reason: core primitive @@ -1451,7 +1455,7 @@ files: owner: core-primitive reason: core primitive - path: polylogue/core/sources.py - loc: 438 + loc: 447 target: polylogue/core/sources.py owner: core-primitive reason: core primitive @@ -1535,7 +1539,7 @@ files: target: polylogue/daemon/catchup_status.py owner: stable - path: polylogue/daemon/cli.py - loc: 2869 + loc: 2932 target: polylogue/daemon/cli.py owner: stable - path: polylogue/daemon/compare.py @@ -1606,10 +1610,6 @@ files: loc: 168 target: polylogue/daemon/fts_identity_convergence.py owner: stable - - path: polylogue/daemon/fts_orphan_audit.py - loc: 197 - target: polylogue/daemon/fts_orphan_audit.py - owner: stable - path: polylogue/daemon/fts_startup.py loc: 472 target: polylogue/daemon/fts_startup.py @@ -1856,7 +1856,7 @@ files: target: polylogue/demo/workspace.py owner: stable - path: polylogue/hooks/__init__.py - loc: 917 + loc: 987 target: polylogue/hooks/__init__.py owner: stable - path: polylogue/insights/__init__.py @@ -2085,7 +2085,7 @@ files: target: polylogue/insights/session_analytics.py owner: stable - path: polylogue/insights/session_commit.py - loc: 917 + loc: 966 target: polylogue/insights/session_commit.py owner: stable - path: polylogue/insights/session_label.py @@ -2294,7 +2294,7 @@ files: target: polylogue/mcp/mutation_support.py owner: stable - path: polylogue/mcp/payloads.py - loc: 1065 + loc: 1113 target: polylogue/mcp/payloads.py owner: stable - path: polylogue/mcp/query_contracts.py @@ -2306,7 +2306,7 @@ files: target: polylogue/mcp/server.py owner: stable - path: polylogue/mcp/server_cutover.py - loc: 2049 + loc: 2165 target: polylogue/mcp/server_cutover.py owner: stable - path: polylogue/mcp/server_prompts.py @@ -2593,7 +2593,7 @@ files: target: polylogue/rendering/core_messages.py owner: stable - path: polylogue/rendering/formatting.py - loc: 240 + loc: 242 target: polylogue/rendering/formatting.py owner: stable - path: polylogue/rendering/renderers/__init__.py @@ -2630,7 +2630,7 @@ files: target: polylogue/rendering/semantic_card_placement.py owner: stable - path: polylogue/rendering/semantic_card_registry.py - loc: 549 + loc: 550 target: polylogue/rendering/semantic_card_registry.py owner: stable - path: polylogue/rendering/semantic_cards.py @@ -3203,7 +3203,7 @@ files: target: polylogue/sources/decoders.py owner: stable - path: polylogue/sources/dispatch.py - loc: 1400 + loc: 1419 target: polylogue/sources/dispatch.py owner: stable - path: polylogue/sources/drive/__init__.py @@ -3255,7 +3255,7 @@ files: target: polylogue/sources/emitter.py owner: stable - path: polylogue/sources/hooks.py - loc: 338 + loc: 499 target: polylogue/sources/hooks.py owner: stable - path: polylogue/sources/import_explain.py @@ -3275,7 +3275,7 @@ files: target: polylogue/sources/live/_lag_sample_ddl.py owner: stable - path: polylogue/sources/live/append_ingest.py - loc: 247 + loc: 283 target: polylogue/sources/live/append_ingest.py owner: stable - path: polylogue/sources/live/batch.py @@ -3343,7 +3343,7 @@ files: target: polylogue/sources/live/watcher.py owner: stable - path: polylogue/sources/origin_specs.py - loc: 1302 + loc: 1364 target: polylogue/sources/origin_specs.py owner: stable - path: polylogue/sources/parsers/antigravity.py @@ -3360,7 +3360,7 @@ files: owner: stable cross_cut: { lifecycle: model } - path: polylogue/sources/parsers/base_support.py - loc: 278 + loc: 313 target: polylogue/sources/parsers/base_support.py owner: stable - path: polylogue/sources/parsers/beads.py @@ -3372,7 +3372,7 @@ files: target: polylogue/sources/parsers/browser_capture.py owner: stable - path: polylogue/sources/parsers/chatgpt.py - loc: 1139 + loc: 1317 target: polylogue/sources/parsers/chatgpt.py owner: stable - path: polylogue/sources/parsers/chatgpt_codex_sidecar.py @@ -3384,11 +3384,11 @@ files: target: polylogue/sources/parsers/chatgpt_sidecars.py owner: stable - path: polylogue/sources/parsers/claude/__init__.py - loc: 64 + loc: 79 target: polylogue/sources/parsers/claude/__init__.py owner: stable - path: polylogue/sources/parsers/claude/ai_parser.py - loc: 318 + loc: 672 target: polylogue/sources/parsers/claude/ai_parser.py owner: stable - path: polylogue/sources/parsers/claude/code_detection.py @@ -3396,7 +3396,7 @@ files: target: polylogue/sources/parsers/claude/code_detection.py owner: stable - path: polylogue/sources/parsers/claude/code_parser.py - loc: 1694 + loc: 2019 target: polylogue/sources/parsers/claude/code_parser.py owner: stable - path: polylogue/sources/parsers/claude/common.py @@ -3524,7 +3524,7 @@ files: owner: stable cross_cut: { lifecycle: model } - path: polylogue/sources/revision_backfill.py - loc: 1821 + loc: 1822 target: polylogue/sources/revision_backfill.py owner: stable - path: polylogue/sources/source_acquisition.py @@ -4096,7 +4096,7 @@ files: target: polylogue/storage/sqlite/archive_tiers/__init__.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/archive.py - loc: 11324 + loc: 11382 target: polylogue/storage/sqlite/archive_tiers/archive.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/archive_init.py @@ -4136,7 +4136,7 @@ files: target: polylogue/storage/sqlite/archive_tiers/embeddings.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/index.py - loc: 1989 + loc: 2009 target: polylogue/storage/sqlite/archive_tiers/index.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/index_convergence.py @@ -4212,7 +4212,7 @@ files: target: polylogue/storage/sqlite/archive_tiers/user_write.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/write.py - loc: 5678 + loc: 6025 target: polylogue/storage/sqlite/archive_tiers/write.py owner: stable - path: polylogue/storage/sqlite/async_sqlite.py @@ -4244,7 +4244,7 @@ files: target: polylogue/storage/sqlite/finding_provenance.py owner: stable - path: polylogue/storage/sqlite/lifecycle.py - loc: 602 + loc: 622 target: polylogue/storage/sqlite/lifecycle.py owner: stable - path: polylogue/storage/sqlite/maintenance.py @@ -4544,11 +4544,11 @@ files: target: polylogue/surfaces/chronicle.py owner: stable - path: polylogue/surfaces/payloads.py - loc: 3887 + loc: 3904 target: polylogue/surfaces/payloads.py owner: stable - path: polylogue/surfaces/projection_spec.py - loc: 293 + loc: 297 target: polylogue/surfaces/projection_spec.py owner: stable - path: polylogue/surfaces/temporal_evidence.py @@ -4588,7 +4588,7 @@ files: owner: stable cross_cut: { api: async } - path: polylogue/ui/theme.py - loc: 532 + loc: 534 target: polylogue/ui/theme.py owner: stable - path: polylogue/ui/tui/__init__.py diff --git a/polylogue/archive/viewport/profiles.py b/polylogue/archive/viewport/profiles.py index 368e018d79..8837698325 100644 --- a/polylogue/archive/viewport/profiles.py +++ b/polylogue/archive/viewport/profiles.py @@ -178,6 +178,41 @@ def to_payload(self) -> JSONDocument: machine_payload="session event list payload", degraded_states=("missing session", "session with no session_events"), ), + SessionViewProfile( + view_id="file-edits", + label="File Edits", + owner="polylogue.cli.read_views.file_edits.run_read_file_edits", + purpose=( + "Captured Claude Code Edit/Write/MultiEdit tool-call evidence: structured unified diffs " + "(structured_patch), pre-edit file content (original_file), and old/new string pairs -- " + "the typed 'what did this session change' data (polylogue-nua7/polylogue-cgfy)." + ), + input_scope="single session id", + included_kinds=("file path", "structured patch", "original file", "old/new string pair"), + lossiness="raw", + evidence_policy="required", + privacy_policy="renders the substrate's own structured file-edit payload verbatim, bounded by the source parser", + formats=("json",), + machine_payload="file edit list payload", + degraded_states=("missing session", "session with no captured file edits"), + ), + SessionViewProfile( + view_id="agent-policies", + label="Agent Policies", + owner="polylogue.cli.read_views.file_edits.run_read_agent_policies", + purpose=( + "Sandbox/approval/network policy facts (e.g. Codex agent_policy events), diverted out of " + "session_events into a dedicated table for zero-loss re-derivation (polylogue-nua7)." + ), + input_scope="single session id", + included_kinds=("approval policy", "sandbox policy", "network policy"), + lossiness="raw", + evidence_policy="required", + privacy_policy="renders the substrate's own structured policy payload verbatim, bounded by the source parser", + formats=("json",), + machine_payload="agent policy list payload", + degraded_states=("missing session", "session with no recorded agent-policy facts"), + ), SessionViewProfile( view_id="context", label="Context", diff --git a/polylogue/cli/messages.py b/polylogue/cli/messages.py index 3f8a593a74..4461af5365 100644 --- a/polylogue/cli/messages.py +++ b/polylogue/cli/messages.py @@ -266,4 +266,102 @@ async def _run() -> None: run_coroutine_sync(_run()) -__all__ = ["run_hooks", "run_messages", "run_raw", "run_session_events"] +def run_session_file_edits( + env: AppEnv, + request: RootModeRequest, + *, + session_id: str, + output_format: str = "json", +) -> None: + """Execute the file-edits verb. + + Renders captured Claude Code Edit/Write/MultiEdit tool-call evidence + (polylogue-nua7/polylogue-cgfy): structured unified diffs + (``structured_patch``), pre-edit file content (``original_file``), and + old/new string pairs -- persisted on every ingest into the dedicated + ``file_edits`` table but, before this view, unreachable from any + surface. This is the "what did this session change" evidence a report + needs instead of re-deriving edits from tool-call prose. + """ + from polylogue.api import Polylogue + + async def _run() -> None: + async with Polylogue.open(config=cast(Config, request.params.get("_config"))) as api: + edits = await api.get_file_edits(session_id) + + if edits is None: + env.ui.error(f"Session not found: {session_id}") + return + + payload = { + "session_id": session_id, + "total": len(edits), + "file_edits": edits, + } + + if output_format == "json": + import json as _json + + # Machine output uses raw stdout so Rich markup never rewrites + # JSON bytes and read-view delivery can capture file/clipboard + # targets consistently. + click.echo(_json.dumps(payload, indent=2)) + else: + import yaml + + click.echo(yaml.dump(payload)) + + run_coroutine_sync(_run()) + + +def run_session_agent_policies( + env: AppEnv, + request: RootModeRequest, + *, + session_id: str, + output_format: str = "json", +) -> None: + """Execute the agent-policies verb. + + Renders sandbox/approval/network policy facts (polylogue-nua7) -- the + writer diverts Codex ``agent_policy`` events out of ``session_events`` + into the dedicated ``session_agent_policies`` table for zero-loss + re-derivation, but before this view nothing above the storage layer + could read them back. + """ + from polylogue.api import Polylogue + + async def _run() -> None: + async with Polylogue.open(config=cast(Config, request.params.get("_config"))) as api: + policies = await api.get_agent_policies(session_id) + + if policies is None: + env.ui.error(f"Session not found: {session_id}") + return + + payload = { + "session_id": session_id, + "total": len(policies), + "agent_policies": policies, + } + + if output_format == "json": + import json as _json + + click.echo(_json.dumps(payload, indent=2)) + else: + import yaml + + click.echo(yaml.dump(payload)) + + run_coroutine_sync(_run()) + + +__all__ = [ + "run_hooks", + "run_messages", + "run_raw", + "run_session_agent_policies", + "run_session_events", + "run_session_file_edits", +] diff --git a/polylogue/cli/read_view_handlers.py b/polylogue/cli/read_view_handlers.py index 03d1df3721..6493499d8f 100644 --- a/polylogue/cli/read_view_handlers.py +++ b/polylogue/cli/read_view_handlers.py @@ -38,6 +38,7 @@ ) from polylogue.cli.read_views.correlation import build_correlation_options, run_read_correlation from polylogue.cli.read_views.events import build_events_options, run_read_events +from polylogue.cli.read_views.file_edits import run_read_agent_policies, run_read_file_edits from polylogue.cli.read_views.messages import ( build_message_options, run_read_hooks, @@ -105,6 +106,18 @@ accepted_options=EVENTS_READ_VIEW_OPTION_NAMES, option_builder=build_events_options, ), + "file-edits": ReadViewHandler( + "file-edits", + "required", + run_read_file_edits, + default_format="json", + ), + "agent-policies": ReadViewHandler( + "agent-policies", + "required", + run_read_agent_policies, + default_format="json", + ), "context": ReadViewHandler( "context", "required", diff --git a/polylogue/cli/read_view_registry.py b/polylogue/cli/read_view_registry.py index d275298859..8eab0bf809 100644 --- a/polylogue/cli/read_view_registry.py +++ b/polylogue/cli/read_view_registry.py @@ -56,6 +56,8 @@ class ReadViewHandlerMetadata: "raw": ReadViewHandlerMetadata("raw", "required", MESSAGE_READ_VIEW_OPTION_NAMES), "hooks": ReadViewHandlerMetadata("hooks", "required"), "events": ReadViewHandlerMetadata("events", "required", EVENTS_READ_VIEW_OPTION_NAMES), + "file-edits": ReadViewHandlerMetadata("file-edits", "required"), + "agent-policies": ReadViewHandlerMetadata("agent-policies", "required"), "context": ReadViewHandlerMetadata("context", "required", CONTEXT_READ_VIEW_OPTION_NAMES), "context-image": ReadViewHandlerMetadata("context-image", "none", CONTEXT_IMAGE_READ_VIEW_OPTION_NAMES), "neighbors": ReadViewHandlerMetadata("neighbors", "query_or_session", NEIGHBOR_READ_VIEW_OPTION_NAMES), diff --git a/polylogue/cli/read_views/file_edits.py b/polylogue/cli/read_views/file_edits.py new file mode 100644 index 0000000000..9383ee1f48 --- /dev/null +++ b/polylogue/cli/read_views/file_edits.py @@ -0,0 +1,92 @@ +"""File-edit and agent-policy evidence read-view handlers. + +Renders two index-tier relations that had a complete, tested read chain +terminating at the repository layer with no surface consumer above it +(polylogue-nua7): ``file_edits`` (Claude Code Edit/Write/MultiEdit +structured diffs / pre-edit file content / old-new string pairs) and +``session_agent_policies`` (Codex sandbox/approval/network policy facts). +""" + +from __future__ import annotations + +import io + +import click + +from polylogue.cli.read_views.base import ReadViewInvocation, deliver_content +from polylogue.cli.root_request import RootModeRequest +from polylogue.cli.shared.types import AppEnv + +__all__ = ["run_read_agent_policies", "run_read_file_edits"] + + +def run_read_file_edits(env: AppEnv, request: RootModeRequest, invocation: ReadViewInvocation) -> None: + """Route the file-edits view to the file-edit evidence renderer.""" + + from polylogue.cli.messages import run_session_file_edits + + assert invocation.session_id is not None + output_format = invocation.output_format or "json" + + if invocation.destination in ("file", "clipboard", "stdout"): + buf = io.StringIO() + + def _captured_echo(message: object = None, **_kwargs: object) -> None: + buf.write(str(message or "") + "\n") + + _orig_echo = click.echo + click.echo = _captured_echo # type: ignore[assignment] + try: + run_session_file_edits( + env, + request, + session_id=invocation.session_id, + output_format=output_format, + ) + finally: + click.echo = _orig_echo + deliver_content(env, buf.getvalue(), destination=invocation.destination, out_path=invocation.out_path) + return + + run_session_file_edits( + env, + request, + session_id=invocation.session_id, + output_format=output_format, + ) + + +def run_read_agent_policies(env: AppEnv, request: RootModeRequest, invocation: ReadViewInvocation) -> None: + """Route the agent-policies view to the agent-policy evidence renderer.""" + + from polylogue.cli.messages import run_session_agent_policies + + assert invocation.session_id is not None + output_format = invocation.output_format or "json" + + if invocation.destination in ("file", "clipboard", "stdout"): + buf = io.StringIO() + + def _captured_echo(message: object = None, **_kwargs: object) -> None: + buf.write(str(message or "") + "\n") + + _orig_echo = click.echo + click.echo = _captured_echo # type: ignore[assignment] + try: + run_session_agent_policies( + env, + request, + session_id=invocation.session_id, + output_format=output_format, + ) + finally: + click.echo = _orig_echo + deliver_content(env, buf.getvalue(), destination=invocation.destination, out_path=invocation.out_path) + return + + run_session_agent_policies( + env, + request, + session_id=invocation.session_id, + output_format=output_format, + ) diff --git a/polylogue/surfaces/projection_spec.py b/polylogue/surfaces/projection_spec.py index 085f108a98..6bfd38aeb1 100644 --- a/polylogue/surfaces/projection_spec.py +++ b/polylogue/surfaces/projection_spec.py @@ -25,6 +25,8 @@ class EvidenceFamily(str, Enum): RAW = "raw" HOOKS = "hooks" EVENTS = "events" + FILE_EDITS = "file-edits" + AGENT_POLICIES = "agent-policies" CONTEXT = "context" CHRONICLE = "chronicle" NEIGHBORS = "neighbors" @@ -156,6 +158,8 @@ class QueryProjectionSpec(SurfacePayloadModel): "raw": (EvidenceFamily.RAW,), "hooks": (EvidenceFamily.HOOKS,), "events": (EvidenceFamily.EVENTS,), + "file-edits": (EvidenceFamily.FILE_EDITS,), + "agent-policies": (EvidenceFamily.AGENT_POLICIES,), "context": (EvidenceFamily.CONTEXT, EvidenceFamily.MESSAGES), "context-image": (EvidenceFamily.CONTEXT, EvidenceFamily.MESSAGES), "chronicle": (EvidenceFamily.CHRONICLE, EvidenceFamily.SESSIONS, EvidenceFamily.MESSAGES), diff --git a/tests/unit/cli/test_file_edits_and_agent_policies_views.py b/tests/unit/cli/test_file_edits_and_agent_policies_views.py new file mode 100644 index 0000000000..12cd11e1b7 --- /dev/null +++ b/tests/unit/cli/test_file_edits_and_agent_policies_views.py @@ -0,0 +1,171 @@ +"""End-to-end CLI coverage for the file-edits/agent-policies read views. + +polylogue-nua7: ``file_edits`` (76,105 live rows) and ``session_agent_policies`` +had a complete, tested read chain that terminated at the repository layer with +no surface consumer above it. These tests exercise the real ``read --view +file-edits``/``read --view agent-policies`` verbs end to end -- real +``ArchiveStore`` write, real CLI invocation, real JSON render -- not the +storage function in isolation. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from polylogue.cli.click_app import cli as click_cli + + +@pytest.fixture +def cli_runner() -> CliRunner: + return CliRunner() + + +def test_read_view_file_edits_surfaces_structured_patch( + cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + from polylogue.core.enums import BlockType, Provider, Role + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedFileEdit, ParsedMessage, ParsedSession + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + + archive_root = tmp_path / "archive" + monkeypatch.setenv("POLYLOGUE_ARCHIVE_ROOT", str(archive_root)) + + with ArchiveStore(archive_root) as archive_db: + parsed = ParsedSession( + source_name=Provider.CLAUDE_CODE, + provider_session_id="cli-file-edit-1", + title="CLI file-edits view session", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.ASSISTANT, + position=0, + blocks=[ + ParsedContentBlock( + type=BlockType.TOOL_USE, + tool_name="Edit", + tool_id="edit-tool-1", + tool_input={"file_path": "/tmp/foo.py"}, + ), + ], + ), + ParsedMessage( + provider_message_id="m2", + role=Role.USER, + position=1, + blocks=[ + ParsedContentBlock( + type=BlockType.TOOL_RESULT, + tool_id="edit-tool-1", + text="applied", + file_edit=ParsedFileEdit( + file_path="/tmp/foo.py", + structured_patch=[ + {"oldStart": 1, "oldLines": 1, "newStart": 1, "newLines": 2, "lines": ["+x"]} + ], + original_file="old contents\n", + old_string="old", + new_string="new", + replace_all=False, + user_modified=True, + ), + ), + ], + ), + ], + ) + archive_db.write_raw_and_parsed( + parsed, + payload=b'{"raw": "claude payload"}', + source_path="/tmp/raw.jsonl", + acquired_at_ms=1735689600000, + ) + + session_id = "claude-code-session:cli-file-edit-1" + + result = cli_runner.invoke( + click_cli, + ["--plain", "--id", session_id, "read", "--view", "file-edits", "-f", "json"], + catch_exceptions=False, + ) + + assert result.exit_code == 0, result.output + payload = json.loads(result.output) + assert payload["total"] == 1 + edit = payload["file_edits"][0] + assert edit["file_path"] == "/tmp/foo.py" + assert edit["original_file"] == "old contents\n" + assert edit["old_string"] == "old" + assert edit["new_string"] == "new" + assert edit["structured_patch"] == [{"oldStart": 1, "oldLines": 1, "newStart": 1, "newLines": 2, "lines": ["+x"]}] + + missing = cli_runner.invoke( + click_cli, + ["--plain", "--id", "claude-code-session:does-not-exist", "read", "--view", "file-edits"], + catch_exceptions=False, + ) + assert "not found" in missing.output.lower() + + +def test_read_view_agent_policies_surfaces_sandbox_facts( + cli_runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + from polylogue.core.enums import BlockType, Provider, Role + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession, ParsedSessionEvent + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + + archive_root = tmp_path / "archive" + monkeypatch.setenv("POLYLOGUE_ARCHIVE_ROOT", str(archive_root)) + + with ArchiveStore(archive_root) as archive_db: + parsed = ParsedSession( + source_name=Provider.CODEX, + provider_session_id="cli-agent-policy-1", + title="CLI agent-policies view session", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.USER, + text="run it", + position=0, + blocks=[ParsedContentBlock(type=BlockType.TEXT, text="run it")], + ), + ], + session_events=[ + ParsedSessionEvent( + event_type="agent_policy", + timestamp="2026-01-01T00:00:01+00:00", + payload={ + "approval_policy": "never", + "sandbox_policy": "danger-full-access", + "network_policy": "true", + }, + ), + ], + ) + archive_db.write_raw_and_parsed( + parsed, + payload=b'{"raw": "codex payload"}', + source_path="/tmp/raw.jsonl", + acquired_at_ms=1735689600000, + ) + + session_id = "codex-session:cli-agent-policy-1" + + result = cli_runner.invoke( + click_cli, + ["--plain", "--id", session_id, "read", "--view", "agent-policies", "-f", "json"], + catch_exceptions=False, + ) + + assert result.exit_code == 0, result.output + payload = json.loads(result.output) + assert payload["total"] == 1 + policy = payload["agent_policies"][0] + assert policy["approval_policy"] == "never" + assert policy["sandbox_policy"] == "danger-full-access" + assert policy["network_policy"] == "true" From 1d051609650bcec9cf3bc26479e72cedde9cae10 Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 12:19:19 +0200 Subject: [PATCH 3/7] chore(storage): delete the four zero-reference file_edits/session_refs/agent_policies helpers Problem: polylogue-nua7 named four helpers with zero references anywhere in the repo outside their own def + __all__ entry (not even a test): queries/file_edits.py get_file_edit / sync_get_file_edits_for_session, queries/session_refs.py sync_get_session_refs, queries/session_agent_policies.py sync_session_agent_policies_batch. Re-verified with rg against the current tree: still zero references after the async get_file_edits/get_agent_policies methods landed on the real MCP/CLI surfaces in the prior two commits -- these sync/ single-row variants were never the read path anything used. Solution: delete all four, plus their now-dangling sqlite3 imports where nothing else in the module used them (file_edits.py, session_refs.py; session_agent_policies.py keeps sqlite3 for _row_to_agent_policy's Row type hint). Verification: devtools test tests/unit/storage/test_unread_wire_batch_v46.py tests/unit/storage/test_repository_agent_policies.py (13 passed); rg for each deleted name across polylogue/ and tests/ (zero hits); mypy --strict on the three touched files (no issues); ruff format/check clean. Ref polylogue-nua7 --- .../storage/sqlite/queries/file_edits.py | 32 ------------------- .../sqlite/queries/session_agent_policies.py | 27 ---------------- .../storage/sqlite/queries/session_refs.py | 17 ---------- 3 files changed, 76 deletions(-) diff --git a/polylogue/storage/sqlite/queries/file_edits.py b/polylogue/storage/sqlite/queries/file_edits.py index 04f2f80761..9e428488a7 100644 --- a/polylogue/storage/sqlite/queries/file_edits.py +++ b/polylogue/storage/sqlite/queries/file_edits.py @@ -11,7 +11,6 @@ from __future__ import annotations -import sqlite3 from collections import defaultdict from collections.abc import Sequence @@ -21,10 +20,8 @@ from polylogue.storage.sqlite.queries.mappers import _row_to_file_edit __all__ = [ - "get_file_edit", "get_file_edits_for_session", "get_file_edits_for_session_batch", - "sync_get_file_edits_for_session", ] _SELECT_COLUMNS = ( @@ -33,20 +30,6 @@ ) -async def get_file_edit( - conn: aiosqlite.Connection, - tool_use_block_id: str, -) -> FileEditRecord | None: - """Return the file-edit row for one tool_use block, or ``None``.""" - row = await ( - await conn.execute( - f"SELECT {_SELECT_COLUMNS} FROM file_edits WHERE tool_use_block_id = ?", - (tool_use_block_id,), - ) - ).fetchone() - return _row_to_file_edit(row) if row is not None else None - - async def get_file_edits_for_session( conn: aiosqlite.Connection, session_id: str, @@ -92,18 +75,3 @@ async def get_file_edits_for_session_batch( record = _row_to_file_edit(row) result[str(record.session_id)].append(record) return dict(result) - - -def sync_get_file_edits_for_session(conn: sqlite3.Connection, session_id: str) -> list[FileEditRecord]: - """Sync sibling of :func:`get_file_edits_for_session`.""" - conn.row_factory = sqlite3.Row - rows = conn.execute( - f""" - SELECT {_SELECT_COLUMNS} - FROM file_edits - WHERE session_id = ? - ORDER BY message_id, tool_use_block_id - """, - (session_id,), - ).fetchall() - return [_row_to_file_edit(row) for row in rows] diff --git a/polylogue/storage/sqlite/queries/session_agent_policies.py b/polylogue/storage/sqlite/queries/session_agent_policies.py index 6de0021cb0..2bed8227df 100644 --- a/polylogue/storage/sqlite/queries/session_agent_policies.py +++ b/polylogue/storage/sqlite/queries/session_agent_policies.py @@ -25,7 +25,6 @@ __all__ = [ "get_session_agent_policies", "get_session_agent_policies_batch", - "sync_session_agent_policies_batch", ] _SELECT_COLUMNS = ( @@ -92,29 +91,3 @@ async def get_session_agent_policies_batch( policy = _row_to_agent_policy(row) result[policy.session_id].append(policy) return dict(result) - - -def sync_session_agent_policies_batch( - conn: sqlite3.Connection, - session_ids: Sequence[str], -) -> dict[str, list[ArchiveAgentPolicy]]: - """Sync sibling of :func:`get_session_agent_policies_batch`.""" - if not session_ids: - return {} - placeholders = ", ".join("?" for _ in session_ids) - rows = conn.execute( - f""" - SELECT {_SELECT_COLUMNS} - FROM session_agent_policies - WHERE session_id IN ({placeholders}) - ORDER BY session_id, position - """, - tuple(session_ids), - ).fetchall() - result: dict[str, list[ArchiveAgentPolicy]] = defaultdict(list) - for session_id in session_ids: - result.setdefault(session_id, []) - for row in rows: - policy = _row_to_agent_policy(row) - result[policy.session_id].append(policy) - return dict(result) diff --git a/polylogue/storage/sqlite/queries/session_refs.py b/polylogue/storage/sqlite/queries/session_refs.py index 6cc2aa3882..f367e84611 100644 --- a/polylogue/storage/sqlite/queries/session_refs.py +++ b/polylogue/storage/sqlite/queries/session_refs.py @@ -10,7 +10,6 @@ from __future__ import annotations -import sqlite3 from collections import defaultdict from collections.abc import Sequence @@ -22,7 +21,6 @@ __all__ = [ "get_session_refs", "get_session_refs_batch", - "sync_get_session_refs", ] _SELECT_COLUMNS = "ref_id, session_id, position, kind, repo, ref_number, url, observed_at_ms" @@ -73,18 +71,3 @@ async def get_session_refs_batch( record = _row_to_session_ref(row) result[str(record.session_id)].append(record) return dict(result) - - -def sync_get_session_refs(conn: sqlite3.Connection, session_id: str) -> list[SessionRefRecord]: - """Sync sibling of :func:`get_session_refs`.""" - conn.row_factory = sqlite3.Row - rows = conn.execute( - f""" - SELECT {_SELECT_COLUMNS} - FROM session_refs - WHERE session_id = ? - ORDER BY position - """, - (session_id,), - ).fetchall() - return [_row_to_session_ref(row) for row in rows] From 2b43861e28b81dc02b816ac84e01861f72248365 Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 12:35:47 +0200 Subject: [PATCH 4/7] feat(archive): surface sessions.display_name (Claude Code slug) on read Problem: polylogue-cgfy AC3 asks that the Claude Code "slug" wire field (1,500 sampled occurrences, e.g. "greedy-squishing-hamming") reach read surfaces so subagent rows carry names instead of ":agent-". The parser already captures it into ParsedSession.display_name (polylogue-2qx.4) and the writer persists it into the sessions.display_name column, but neither the Session nor SessionSummary domain model had a display_name field at all -- the value landed durably and was dropped on every read path. Solution, two independent hydration paths both wired: 1. Async repository path (storage/hydrators.py): added display_name to Session/SessionSummary (archive/session/domain_models.py) and their runtime mixins' display_title property (domain_runtime.py, summary_runtime.py) -- display_title now falls back user_title > title > display_name > id[:8], one tier above the raw id truncation. 2. Sync ArchiveStore summary path that backs `find`/MCP get(ref) default projection (storage/sqlite/archive_tiers/archive.py): added display_name to ArchiveSessionSummary, selected it in read_summary's and list_summaries' SQL, and in _summary_from_row made it a title fallback tier ABOVE the existing structural-label fallback (polylogue-cijx.4 decision 3) when no provider title exists -- display_name is real origin evidence (title_source="origin"), stronger than a derived structural label. Wired through api/archive.py::_archive_summary_to_domain so MCP/CLI/API session summaries carry it. Verification: devtools test tests/unit/mcp/test_server_surfaces.py tests/unit/storage/test_session_display_name_reaches_repository.py (12 passed, including a new MCP get(ref) test proving a title-absent session now surfaces its slug through the real ArchiveStore-backed summary path, and a repository-level test proving both Session and SessionSummary carry display_name/display_title correctly, with a second test proving a real title still wins over the slug); devtools test tests/unit/storage/test_title_source_queryable.py tests/unit/storage/test_archive_tiers_write.py tests/unit/storage/test_archive_tiers_archive.py tests/unit/cli/test_query_exec_laws.py (243 passed, no regressions to existing title/summary logic); mypy --strict on all touched files; devtools verify --quick (exit 0); devtools render all --check (sync OK). Ref polylogue-cgfy --- polylogue/api/archive.py | 1 + polylogue/archive/session/domain_models.py | 9 ++ polylogue/archive/session/domain_runtime.py | 7 ++ polylogue/archive/session/summary_runtime.py | 4 + polylogue/storage/hydrators.py | 2 + .../storage/sqlite/archive_tiers/archive.py | 33 ++++++- tests/unit/mcp/test_server_surfaces.py | 50 +++++++++++ ...session_display_name_reaches_repository.py | 90 +++++++++++++++++++ 8 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 tests/unit/storage/test_session_display_name_reaches_repository.py diff --git a/polylogue/api/archive.py b/polylogue/api/archive.py index b7473b0875..b728f45a65 100644 --- a/polylogue/api/archive.py +++ b/polylogue/api/archive.py @@ -1995,6 +1995,7 @@ def _archive_summary_to_domain(summary: ArchiveSessionSummary) -> SessionSummary git_branch=summary.git_branch, git_repository_url=summary.git_repository_url, provider_project_ref=summary.provider_project_ref, + display_name=summary.display_name, message_count=summary.message_count, tags_m2m=summary.tags, ) diff --git a/polylogue/archive/session/domain_models.py b/polylogue/archive/session/domain_models.py index 0adb93b966..9e40e9ff6c 100644 --- a/polylogue/archive/session/domain_models.py +++ b/polylogue/archive/session/domain_models.py @@ -49,6 +49,13 @@ class SessionSummary(SessionSummaryRuntimeMixin, BaseModel): git_branch: str | None = None git_repository_url: str | None = None provider_project_ref: str | None = None + # Provider-assigned human-readable session name distinct from the + # (possibly inferred) title -- e.g. Claude Code's "slug" wire field + # ("greedy-squishing-hamming"), captured but previously dropped before + # reaching any domain model (polylogue-cgfy: 1,500 sampled occurrences, + # the fix for subagent rows displaying ":agent-" instead + # of a human name). + display_name: str | None = None parent_id: SessionId | None = None branch_type: BranchType | None = None message_count: int | None = None @@ -104,6 +111,8 @@ class Session(SessionRuntimeMixin, BaseModel): git_branch: str | None = None git_repository_url: str | None = None provider_project_ref: str | None = None + # See ``SessionSummary.display_name`` (polylogue-cgfy). + display_name: str | None = None session_events: tuple[SessionEvent, ...] = () parent_id: SessionId | None = None branch_type: BranchType | None = None diff --git a/polylogue/archive/session/domain_runtime.py b/polylogue/archive/session/domain_runtime.py index db413d09c3..6a872721dd 100644 --- a/polylogue/archive/session/domain_runtime.py +++ b/polylogue/archive/session/domain_runtime.py @@ -41,6 +41,7 @@ class SessionRuntimeMixin: metadata: dict[str, object] parent_id: SessionId | None branch_type: BranchType | None + display_name: str | None if TYPE_CHECKING: @@ -73,6 +74,12 @@ def display_title(self) -> str: return user_title if self.title: return self.title + # polylogue-cgfy: provider-assigned display name (e.g. Claude Code's + # slug, "greedy-squishing-hamming") beats the raw id truncation -- + # the fix for subagent rows showing "" instead of a + # human-readable name when no title-worthy sidecar evidence exists. + if self.display_name: + return self.display_name return self.id[:8] @property diff --git a/polylogue/archive/session/summary_runtime.py b/polylogue/archive/session/summary_runtime.py index a176fab7bc..27c2e0a8cc 100644 --- a/polylogue/archive/session/summary_runtime.py +++ b/polylogue/archive/session/summary_runtime.py @@ -28,6 +28,7 @@ class SessionSummaryRuntimeMixin: metadata: dict[str, object] parent_id: SessionId | None branch_type: BranchType | None + display_name: str | None @property def display_date(self) -> datetime | None: @@ -40,6 +41,9 @@ def display_title(self) -> str: return user_title if self.title: return self.title + # polylogue-cgfy: see Session.display_title's twin fallback. + if self.display_name: + return self.display_name return self.id[:8] @property diff --git a/polylogue/storage/hydrators.py b/polylogue/storage/hydrators.py index ec95e9d134..005fc78af4 100644 --- a/polylogue/storage/hydrators.py +++ b/polylogue/storage/hydrators.py @@ -180,6 +180,7 @@ def session_summary_from_record( git_branch=record.git_branch, git_repository_url=record.git_repository_url, provider_project_ref=record.provider_project_ref, + display_name=record.display_name, parent_id=record.parent_session_id, branch_type=record.branch_type, message_count=message_count, @@ -236,6 +237,7 @@ def session_from_records( git_branch=session.git_branch, git_repository_url=session.git_repository_url, provider_project_ref=session.provider_project_ref, + display_name=session.display_name, session_events=tuple(session_event_from_record(event) for event in (session_events or [])), parent_id=session.parent_session_id, branch_type=session.branch_type, diff --git a/polylogue/storage/sqlite/archive_tiers/archive.py b/polylogue/storage/sqlite/archive_tiers/archive.py index b8683b978c..fee802f547 100644 --- a/polylogue/storage/sqlite/archive_tiers/archive.py +++ b/polylogue/storage/sqlite/archive_tiers/archive.py @@ -392,6 +392,10 @@ class ArchiveSessionSummary: git_branch: str | None = None git_repository_url: str | None = None provider_project_ref: str | None = None + # See ``Session.display_name`` / ``SessionSummary.display_name`` + # (polylogue-cgfy): a provider-assigned name (e.g. Claude Code's slug) + # distinct from the (possibly derived) title. + display_name: str | None = None @dataclass(frozen=True, slots=True) @@ -3755,6 +3759,7 @@ def read_summary(self, session_id: str) -> ArchiveSessionSummary: s.tool_message_count, s.user_word_count, s.authored_user_word_count, s.assistant_word_count, s.title_source, s.title_ref, s.title_confidence, s.git_branch, s.git_repository_url, s.provider_project_ref, + s.display_name, COALESCE( ( SELECT json_group_array(swd.path) @@ -6105,6 +6110,7 @@ def list_summaries( s.tool_message_count, s.user_word_count, s.authored_user_word_count, s.assistant_word_count, s.title_source, s.title_ref, s.title_confidence, s.git_branch, s.git_repository_url, s.provider_project_ref, + s.display_name, COALESCE( ( SELECT json_group_array(swd.path) @@ -8364,14 +8370,32 @@ def row_int(key: str) -> int: # idempotent on rebuild instead of freezing a stale message count. has_real_title = bool(raw_title and raw_title.strip()) and raw_title_source in {"origin", "heuristic", "user"} provider_title = raw_title if has_real_title else None + try: + raw_display_name = row["display_name"] + except IndexError: + # Not every caller's SELECT projects display_name; treat absence as + # unknown rather than raising (matching parent_id's guard below). + display_name: str | None = None + else: + display_name = str(raw_display_name).strip() or None if raw_display_name is not None else None if provider_title is not None: title = provider_title title_source = raw_title_source + elif display_name: + # polylogue-cgfy: a provider-assigned display name (Claude Code's + # slug, e.g. "greedy-squishing-hamming") is real origin evidence -- + # prefer it over the derived structural label below. This is the fix + # for subagent rows rendering as ":agent-" + # instead of a human-readable name when no title sidecar evidence + # exists for that specific session. + title = display_name + title_source = "origin" else: - # No provider-supplied title (or a blank/synthetic one): fall back to - # the structural label (polylogue-cijx.4 decision 3) rather than - # exposing a bare/blank title to CLI/MCP/API surfaces. This is a - # read-time projection only -- never written back to sessions.title. + # No provider-supplied title or display name (or a blank/synthetic + # one): fall back to the structural label (polylogue-cijx.4 + # decision 3) rather than exposing a bare/blank title to CLI/MCP/API + # surfaces. This is a read-time projection only -- never written + # back to sessions.title. title = session_structural_label_for_session( conn, session_id, @@ -8421,6 +8445,7 @@ def row_int(key: str) -> int: git_branch=str(row["git_branch"]) if row["git_branch"] is not None else None, git_repository_url=str(row["git_repository_url"]) if row["git_repository_url"] is not None else None, provider_project_ref=(str(row["provider_project_ref"]) if row["provider_project_ref"] is not None else None), + display_name=display_name, ) diff --git a/tests/unit/mcp/test_server_surfaces.py b/tests/unit/mcp/test_server_surfaces.py index 41d6f71ebc..09c59675ff 100644 --- a/tests/unit/mcp/test_server_surfaces.py +++ b/tests/unit/mcp/test_server_surfaces.py @@ -451,3 +451,53 @@ async def test_get_projection_agent_policies_surfaces_sandbox_facts( ) ) assert missing["code"] == "not_found" + + +@pytest.mark.asyncio +async def test_get_default_projection_surfaces_display_name_when_title_absent( + mcp_server: MCPServerUnderTest, tmp_path: Path +) -> None: + """``get(ref)`` (no projection) reaches ``sessions.display_name`` + (polylogue-cgfy): a session with no title-worthy sidecar evidence (the + common Claude Code subagent case) now surfaces its provider-assigned + slug as the title instead of a raw session id -- read through the real + ``ArchiveStore``-backed summary path (``_resolve_session_object_ref`` -> + ``_archive_summary_to_domain`` -> ``SessionSummaryPayload``), not the + storage row in isolation. + """ + from polylogue.core.enums import BlockType, Provider, Role + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession + + archive_root = tmp_path / "archive" + with ArchiveStore(archive_root) as archive_db: + parsed = ParsedSession( + source_name=Provider.CLAUDE_CODE, + provider_session_id="mcp-slug-only-ref", + title=None, + display_name="greedy-squishing-hamming", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.ASSISTANT, + position=0, + blocks=[ParsedContentBlock(type=BlockType.TEXT, text="hi")], + ), + ], + ) + archive_db.write_raw_and_parsed( + parsed, + payload=b'{"raw": "claude payload"}', + source_path="/tmp/raw.jsonl", + acquired_at_ms=1735689600000, + ) + + uri = "polylogue://session/claude-code-session:mcp-slug-only-ref" + from polylogue import Polylogue + + with ( + patch("polylogue.mcp.server._get_config", return_value=SimpleNamespace(archive_root=archive_root)), + patch("polylogue.mcp.server._get_polylogue", return_value=Polylogue(archive_root=archive_root)), + ): + payload = json.loads(await invoke_surface_async(mcp_server._tool_manager._tools["get"].fn, ref=uri)) + + assert payload["title"] == "greedy-squishing-hamming" diff --git a/tests/unit/storage/test_session_display_name_reaches_repository.py b/tests/unit/storage/test_session_display_name_reaches_repository.py new file mode 100644 index 0000000000..595f077e43 --- /dev/null +++ b/tests/unit/storage/test_session_display_name_reaches_repository.py @@ -0,0 +1,90 @@ +"""``display_name`` (the Claude Code ``slug`` wire field) reaches Session/SessionSummary. + +polylogue-cgfy: the parser has captured ``slug`` (1,500 sampled occurrences) +into ``ParsedSession.display_name`` and the writer persists it into the +``sessions.display_name`` column since polylogue-2qx.4, but neither +``Session`` nor ``SessionSummary`` carried a ``display_name`` field at all -- +the value was written durably and then dropped on every read, so a session +whose only title-worthy evidence was its slug (the common subagent case, +":agent-" instead of a human name) still rendered as a +raw id/UUID everywhere. This test proves the real writer -> real async +repository -> domain-model ``display_title`` chain now surfaces it, not the +storage column in isolation. +""" + +from __future__ import annotations + +from pathlib import Path + +from polylogue.core.enums import BlockType, Provider, Role +from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession +from polylogue.storage.repository import SessionRepository +from polylogue.storage.sqlite.async_sqlite import SQLiteBackend +from tests.infra.live_ingest import ingest_session + + +async def test_display_name_reaches_session_display_title_when_title_absent(tmp_path: Path) -> None: + """A session with no title-worthy evidence falls back to its slug, not a raw id.""" + backend = SQLiteBackend(db_path=tmp_path / "display-name.db") + repo = SessionRepository(backend=backend) + try: + session_id = await ingest_session( + ParsedSession( + source_name=Provider.CLAUDE_CODE, + provider_session_id="slug-only-session", + title=None, + display_name="greedy-squishing-hamming", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.ASSISTANT, + position=0, + blocks=[ParsedContentBlock(type=BlockType.TEXT, text="hi")], + ), + ], + ), + backend=backend, + ) + session = await repo.get(session_id) + assert session is not None + summary = await repo.get_summary(session_id) + finally: + await repo.close() + + assert session.display_name == "greedy-squishing-hamming" + assert session.display_title == "greedy-squishing-hamming" + + assert summary is not None + assert summary.display_name == "greedy-squishing-hamming" + assert summary.display_title == "greedy-squishing-hamming" + + +async def test_display_name_does_not_override_a_real_title(tmp_path: Path) -> None: + """A real provider title still wins over the slug (title > display_name precedence).""" + backend = SQLiteBackend(db_path=tmp_path / "display-name-title-wins.db") + repo = SessionRepository(backend=backend) + try: + session_id = await ingest_session( + ParsedSession( + source_name=Provider.CLAUDE_CODE, + provider_session_id="titled-session", + title="Recover what was lost", + display_name="greedy-squishing-hamming", + messages=[ + ParsedMessage( + provider_message_id="m1", + role=Role.ASSISTANT, + position=0, + blocks=[ParsedContentBlock(type=BlockType.TEXT, text="hi")], + ), + ], + ), + backend=backend, + ) + session = await repo.get(session_id) + finally: + await repo.close() + + assert session is not None + assert session.display_name == "greedy-squishing-hamming" + assert session.display_title == "Recover what was lost" From cfa28586c03a551d5e94a95138cd8b43a3e12414 Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 12:42:42 +0200 Subject: [PATCH 5/7] test(api): categorize get_file_edits/get_agent_policies in facade contract suite Problem: devtools test tests/unit/api/test_facade_contracts.py failed test_no_undiscovered_async_methods after this branch's earlier commit added Polylogue.get_file_edits()/get_agent_policies() -- the contract suite enumerates every public async method on Polylogue and fails until each is explicitly categorized. Solution: add both to READ_BY_ID_NONE_METHODS (same category as the get_session_events sibling they were modeled on -- returns None for an unknown session id rather than an empty container). Verification: devtools test tests/unit/api/test_facade_contracts.py -k "no_undiscovered or (method_has_typed_signature and (get_file_edits or get_agent_policies))" (3 passed). Ref polylogue-nua7 --- tests/unit/api/test_facade_contracts.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/api/test_facade_contracts.py b/tests/unit/api/test_facade_contracts.py index aaf86d3ef9..76012dbc71 100644 --- a/tests/unit/api/test_facade_contracts.py +++ b/tests/unit/api/test_facade_contracts.py @@ -85,6 +85,8 @@ "resume_brief", "get_hook_event_summary_for_session", "get_session_events", + "get_file_edits", + "get_agent_policies", } ) From c7a7462e9e843e346c353d4f0022687f19659da5 Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 12:44:59 +0200 Subject: [PATCH 6/7] chore(docs): regenerate topology projection after rebase Rebasing onto origin/master (which gained 5 new commits during this branch's development, including new modules) left docs/plans/topology-target.yaml stale relative to the rebased tree. Regenerated via devtools render topology-projection. --- docs/plans/topology-target.yaml | 90 +++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/docs/plans/topology-target.yaml b/docs/plans/topology-target.yaml index 97970ac54c..3344a0151e 100644 --- a/docs/plans/topology-target.yaml +++ b/docs/plans/topology-target.yaml @@ -70,7 +70,7 @@ files: owner: stable cross_cut: { api: async } - path: polylogue/api/archive.py - loc: 7058 + loc: 7193 target: polylogue/api/archive.py owner: stable cross_cut: { api: async } @@ -221,7 +221,7 @@ files: owner: archive-filter reason: archive-domain filter semantics - path: polylogue/archive/filter/filters.py - loc: 167 + loc: 179 target: polylogue/archive/filter/filters.py owner: archive-filter reason: archive-domain filter semantics @@ -330,12 +330,12 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/archive_execution.py - loc: 704 + loc: 713 target: polylogue/archive/query/archive_execution.py owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/attached_units.py - loc: 219 + loc: 282 target: polylogue/archive/query/attached_units.py owner: archive-query reason: archive-domain query semantics @@ -360,7 +360,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/expression.py - loc: 3588 + loc: 3865 target: polylogue/archive/query/expression.py owner: archive-query reason: archive-domain query semantics @@ -375,7 +375,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/metadata.py - loc: 1449 + loc: 1495 target: polylogue/archive/query/metadata.py owner: archive-query reason: archive-domain query semantics @@ -485,7 +485,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/spec.py - loc: 641 + loc: 648 target: polylogue/archive/query/spec.py owner: archive-query reason: archive-domain query semantics @@ -500,7 +500,7 @@ files: owner: archive-query reason: archive-domain query semantics - path: polylogue/archive/query/unit_results.py - loc: 582 + loc: 723 target: polylogue/archive/query/unit_results.py owner: archive-query reason: archive-domain query semantics @@ -627,18 +627,18 @@ files: owner: archive-session reason: archive-domain semantics - path: polylogue/archive/session/documents.py - loc: 147 + loc: 150 target: polylogue/archive/session/documents.py owner: archive-session reason: archive-domain semantics - path: polylogue/archive/session/domain_models.py - loc: 143 + loc: 152 target: polylogue/archive/session/domain_models.py owner: archive-session reason: archive-domain semantics cross_cut: { lifecycle: model } - path: polylogue/archive/session/domain_runtime.py - loc: 233 + loc: 240 target: polylogue/archive/session/domain_runtime.py owner: archive-session reason: archive-domain semantics @@ -654,7 +654,7 @@ files: owner: archive-session reason: archive-domain semantics - path: polylogue/archive/session/models.py - loc: 353 + loc: 371 target: polylogue/archive/session/models.py owner: archive-session reason: archive-domain semantics @@ -674,7 +674,7 @@ files: owner: archive-session reason: archive-domain semantics - path: polylogue/archive/session/runtime.py - loc: 632 + loc: 652 target: polylogue/archive/session/runtime.py owner: archive-session reason: archive-domain semantics @@ -689,7 +689,7 @@ files: owner: archive-session reason: archive-domain semantics - path: polylogue/archive/session/summary_runtime.py - loc: 70 + loc: 74 target: polylogue/archive/session/summary_runtime.py owner: archive-session reason: archive-domain semantics @@ -819,7 +819,7 @@ files: target: polylogue/cli/__main__.py owner: stable - path: polylogue/cli/archive_query.py - loc: 2765 + loc: 2784 target: polylogue/cli/archive_query.py owner: stable - path: polylogue/cli/click_app.py @@ -827,7 +827,7 @@ files: target: polylogue/cli/click_app.py owner: stable - path: polylogue/cli/click_command_registration.py - loc: 213 + loc: 215 target: polylogue/cli/click_command_registration.py owner: stable - path: polylogue/cli/click_option_groups.py @@ -866,6 +866,10 @@ files: loc: 116 target: polylogue/cli/commands/check.py owner: stable + - path: polylogue/cli/commands/compare.py + loc: 207 + target: polylogue/cli/commands/compare.py + owner: stable - path: polylogue/cli/commands/completions.py loc: 107 target: polylogue/cli/commands/completions.py @@ -1539,7 +1543,7 @@ files: target: polylogue/daemon/catchup_status.py owner: stable - path: polylogue/daemon/cli.py - loc: 2932 + loc: 2934 target: polylogue/daemon/cli.py owner: stable - path: polylogue/daemon/compare.py @@ -1559,7 +1563,7 @@ files: target: polylogue/daemon/convergence_debt_status.py owner: stable - path: polylogue/daemon/convergence_stages.py - loc: 2040 + loc: 2086 target: polylogue/daemon/convergence_stages.py owner: stable - path: polylogue/daemon/convergence_standing_queries.py @@ -1610,6 +1614,10 @@ files: loc: 168 target: polylogue/daemon/fts_identity_convergence.py owner: stable + - path: polylogue/daemon/fts_orphan_audit.py + loc: 197 + target: polylogue/daemon/fts_orphan_audit.py + owner: stable - path: polylogue/daemon/fts_startup.py loc: 472 target: polylogue/daemon/fts_startup.py @@ -2024,6 +2032,10 @@ files: loc: 143 target: polylogue/insights/measurement/ratio.py owner: stable + - path: polylogue/insights/measurement/registered_metrics.py + loc: 57 + target: polylogue/insights/measurement/registered_metrics.py + owner: stable - path: polylogue/insights/measurement/registration.py loc: 107 target: polylogue/insights/measurement/registration.py @@ -2306,11 +2318,11 @@ files: target: polylogue/mcp/server.py owner: stable - path: polylogue/mcp/server_cutover.py - loc: 2165 + loc: 2333 target: polylogue/mcp/server_cutover.py owner: stable - path: polylogue/mcp/server_prompts.py - loc: 563 + loc: 564 target: polylogue/mcp/server_prompts.py owner: stable - path: polylogue/mcp/server_resources.py @@ -2560,7 +2572,7 @@ files: target: polylogue/product/workflows.py owner: stable - path: polylogue/readiness/__init__.py - loc: 1013 + loc: 1049 target: polylogue/readiness/__init__.py owner: stable - path: polylogue/readiness/capability.py @@ -3167,7 +3179,7 @@ files: target: polylogue/sources/__init__.py owner: stable - path: polylogue/sources/assembly.py - loc: 116 + loc: 112 target: polylogue/sources/assembly.py owner: stable - path: polylogue/sources/assembly_chatgpt.py @@ -3175,7 +3187,7 @@ files: target: polylogue/sources/assembly_chatgpt.py owner: stable - path: polylogue/sources/assembly_claude_code.py - loc: 228 + loc: 203 target: polylogue/sources/assembly_claude_code.py owner: stable - path: polylogue/sources/assembly_codex.py @@ -3203,7 +3215,7 @@ files: target: polylogue/sources/decoders.py owner: stable - path: polylogue/sources/dispatch.py - loc: 1419 + loc: 1430 target: polylogue/sources/dispatch.py owner: stable - path: polylogue/sources/drive/__init__.py @@ -3279,7 +3291,7 @@ files: target: polylogue/sources/live/append_ingest.py owner: stable - path: polylogue/sources/live/batch.py - loc: 3081 + loc: 3134 target: polylogue/sources/live/batch.py owner: stable - path: polylogue/sources/live/batch_observability.py @@ -3384,7 +3396,7 @@ files: target: polylogue/sources/parsers/chatgpt_sidecars.py owner: stable - path: polylogue/sources/parsers/claude/__init__.py - loc: 79 + loc: 78 target: polylogue/sources/parsers/claude/__init__.py owner: stable - path: polylogue/sources/parsers/claude/ai_parser.py @@ -3412,7 +3424,7 @@ files: target: polylogue/sources/parsers/claude/index.py owner: stable - path: polylogue/sources/parsers/claude/orchestration.py - loc: 280 + loc: 230 target: polylogue/sources/parsers/claude/orchestration.py owner: stable - path: polylogue/sources/parsers/codex.py @@ -3468,7 +3480,7 @@ files: target: polylogue/sources/parsers/hermes_verification.py owner: stable - path: polylogue/sources/parsers/local_agent.py - loc: 653 + loc: 677 target: polylogue/sources/parsers/local_agent.py owner: stable - path: polylogue/sources/provider_completeness.py @@ -3565,7 +3577,7 @@ files: target: TBD owner: storage-domain - path: polylogue/storage/archive_readiness.py - loc: 1244 + loc: 1294 target: TBD owner: storage-domain - path: polylogue/storage/archive_views.py @@ -3725,7 +3737,7 @@ files: target: polylogue/storage/fts/sql.py owner: stable - path: polylogue/storage/hydrators.py - loc: 252 + loc: 254 target: polylogue/storage/hydrators.py owner: storage-root reason: storage-root cross-cutting helper @@ -3772,7 +3784,7 @@ files: target: polylogue/storage/insights/session/latency_profiles.py owner: stable - path: polylogue/storage/insights/session/profiles.py - loc: 836 + loc: 844 target: polylogue/storage/insights/session/profiles.py owner: stable - path: polylogue/storage/insights/session/rebuild.py @@ -3780,7 +3792,7 @@ files: target: polylogue/storage/insights/session/rebuild.py owner: stable - path: polylogue/storage/insights/session/records.py - loc: 173 + loc: 180 target: polylogue/storage/insights/session/records.py owner: stable - path: polylogue/storage/insights/session/refresh.py @@ -3808,7 +3820,7 @@ files: target: polylogue/storage/insights/session/status.py owner: stable - path: polylogue/storage/insights/session/storage.py - loc: 833 + loc: 841 target: polylogue/storage/insights/session/storage.py owner: stable - path: polylogue/storage/insights/session/threads.py @@ -4096,7 +4108,7 @@ files: target: polylogue/storage/sqlite/archive_tiers/__init__.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/archive.py - loc: 11382 + loc: 11407 target: polylogue/storage/sqlite/archive_tiers/archive.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/archive_init.py @@ -4208,7 +4220,7 @@ files: target: polylogue/storage/sqlite/archive_tiers/user_overlay.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/user_write.py - loc: 2539 + loc: 2608 target: polylogue/storage/sqlite/archive_tiers/user_write.py owner: stable - path: polylogue/storage/sqlite/archive_tiers/write.py @@ -4300,7 +4312,7 @@ files: target: polylogue/storage/sqlite/queries/cursor.py owner: stable - path: polylogue/storage/sqlite/queries/file_edits.py - loc: 109 + loc: 77 target: polylogue/storage/sqlite/queries/file_edits.py owner: stable - path: polylogue/storage/sqlite/queries/filter_builder.py @@ -4375,7 +4387,7 @@ files: owner: stable cross_cut: { layer: write } - path: polylogue/storage/sqlite/queries/session_agent_policies.py - loc: 120 + loc: 93 target: polylogue/storage/sqlite/queries/session_agent_policies.py owner: stable - path: polylogue/storage/sqlite/queries/session_events.py @@ -4425,7 +4437,7 @@ files: target: polylogue/storage/sqlite/queries/session_links.py owner: stable - path: polylogue/storage/sqlite/queries/session_refs.py - loc: 90 + loc: 73 target: polylogue/storage/sqlite/queries/session_refs.py owner: stable - path: polylogue/storage/sqlite/queries/sessions.py @@ -4544,7 +4556,7 @@ files: target: polylogue/surfaces/chronicle.py owner: stable - path: polylogue/surfaces/payloads.py - loc: 3904 + loc: 3911 target: polylogue/surfaces/payloads.py owner: stable - path: polylogue/surfaces/projection_spec.py From 7c6c5a42d02646614d9ae6f02458ddbf51bed0d5 Mon Sep 17 00:00:00 2001 From: Sinity Date: Fri, 31 Jul 2026 12:49:27 +0200 Subject: [PATCH 7/7] chore(beads): record PR #3442 disposition on nua7/cgfy/pbuh Ref polylogue-nua7, polylogue-cgfy, polylogue-pbuh --- .beads/issues.jsonl | 117 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 18 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 932d7bf9a4..27442dab10 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,3 +1,12 @@ +{"_type":"issue","id":"polylogue-c5mb","title":"Claude Code tool-output sidecars: 98% recorded as debt, 71 GB of observed content stored nowhere","description":"MEASURED 2026-07-31 (conversation-fidelity audit, audit-only pass).\n\nMECHANISM (works, and is genuinely wired -- not vaporware): polylogue/sources/live/tool_result_sidecars.py joins Claude Code's externalized tool output (~/.claude/projects/\u003cslug\u003e/\u003csession\u003e/tool-results/*.txt, referenced inline as '\u003cpersisted-output\u003eOutput too large ... Full output saved to: \u003cpath\u003e') back onto the owning tool_result block, replacing the truncated preview with full content. Wired into both the eager path (sources/dispatch.py _join_claude_code_sidecars -\u003e sources/parsers/claude/code_parser.py:1600 apply_tool_result_sidecars) and the streaming path (sources/dispatch.py:634-646). Either way it records a claude_tool_result_sidecar session_event.\n\nMEASURED against the live archive -- 565,536 such events, all origin=claude-code-session:\n matched: 11,285 ( 2.0%) 1.06 GB\n of which content_replaced: 2,979 (the rest matched a sidecar duplicating already-inline content)\n no_owning_tool_result_block: 554,251 (98.0%) 71.0 GB\nDebt spans 552,633 DISTINCT filenames across 11,369 distinct sessions -- near-1:1 with event count, so this is not re-ingest duplication inflation.\n\nTHE LOSS: per the module's own design (apply_tool_result_sidecars docstring: 'never the raw bytes ... only ... a bounded session event'), a debt entry retains filename, byte_size and reason. The BYTES ARE STORED NOWHERE -- not in blocks, not in source.db's blob store. If Claude Code has since rotated or deleted the underlying file, that tool output is permanently gone. INFERRED that most has: the current live corpus under ~/.claude/projects/*/*/tool-results/ is 1.45 GB / 12,746 files against 71 GB / 552,633 filenames historically observed.\n\nCONTRADICTS ITS OWN DOCUMENTED RATE: the module docstring (lines 12-20) claims, from an 80-session / 12,588-file / 1.34 GB sample, that sidecars with no owning tool_result block are '~1-5% of files'. The live archive-wide rate is 98%. Either that sample was unrepresentative or debt has grown sharply since.\n\nBLOCKED ON INSTRUMENTATION: occurred_at_ms is NULL on every one of these 565,536 events, so debt cannot be time-bucketed. It is currently impossible to tell whether this is a stale historical cohort or still accruing on every ingest -- which is exactly the fact needed to decide urgency.\n\nSUGGESTED ORDER:\n 1. populate occurred_at_ms on claude_tool_result_sidecar events so debt can be time-bucketed;\n 2. determine whether debt is an old cohort or an ongoing ingest-timing race against Claude Code's own tool-result compaction;\n 3. if ongoing, acquire the sidecar bytes into the blob store at observation time rather than recording a filename and dropping the content.\n\nRE-RUN:\n python3 -c \"\nimport sqlite3, json, collections\ncon = sqlite3.connect('file:/realm/db/polylogue/index.db?mode=ro', uri=True)\nc = collections.Counter()\nfor (pj,) in con.execute(\\\"select payload_json from session_events where event_type='claude_tool_result_sidecar'\\\"):\n c[json.loads(pj)['acquisition_status']] += 1\nprint(c)\"","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:26:29Z","created_by":"Sinity","updated_at":"2026-07-31T10:26:29Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-msia","title":"antigravity-session origin holds no conversations: 116 one-message metadata stubs, 328 MB unread","description":"MEASURED 2026-07-31 (conversation-fidelity audit, audit-only pass).\n\nARCHIVE: origin='antigravity-session' has 116 sessions. Every single one has exactly one message:\n select message_count, count(*) from sessions where origin='antigravity-session' group by 1; -\u003e 1|116\nTotal message rows for the origin: 116. It is the only origin in the archive with this shape.\n\nDURABLE TIER: all 232 ingested raw_sessions rows for the origin point at *.metadata.json sidecars under ~/.gemini/antigravity/brain/**, totalling 61,260 bytes (avg 264 bytes per 'session').\n select count(*), sum(blob_size) from raw_sessions where origin='antigravity-session'; -\u003e 232 | 61260\n select count(*) from raw_sessions where source_path like '%antigravity/conversations%'; -\u003e 0\n select count(*) from raw_sessions where source_path like '%antigravity/brain%' and source_path not like '%.metadata.json'; -\u003e 0\n\nNOT INGESTED:\n - ~/.gemini/antigravity/conversations/*.pb -- 44 files, 328,479,265 bytes (328.5 MB), sizes 96 KB to 27 MB. Zero rows reference this directory.\n - the brain/ document bodies themselves (plan.md, task.md, report.md, walkthrough.md, comprehensive_audit.md and their .resolved.N revision chains, ~20 MB). Only their metadata sidecars are read.\n\nCODE PATH: polylogue/sources/dispatch.py:1204-1207 routes antigravity payloads to parse_markdown_export_payload / parse_brain_metadata; the origin's artifact rules (polylogue/sources/origin_specs.py) admit only the metadata sidecars.\n\nHONEST CAVEAT: the .pb conversation files measure 8.0 bits/byte entropy and neither zlib nor gzip opens them (magic 92a17722480a0583...), so they are compressed or encrypted in an unknown container. Parsing them may be genuinely hard, and this may be a deliberate 'not yet'. That changes the FIX, not the finding.\n\nTHE ACTUAL DEFECT is representational, and is a false-presence claim rather than an absence: the archive reports 116 antigravity sessions alongside real ones in every origin scorecard, per-origin count, and coverage surface, while holding none of the conversations. 'antigravity: 116 sessions' is a stronger claim than 'antigravity: not supported', and it is the wrong one.\n\nSUGGESTED FIX (either is acceptable, the current state is not):\n (a) parse conversations/*.pb and ingest real sessions; or\n (b) stop minting a session per metadata sidecar -- represent the origin as unsupported/metadata-only so no surface counts these as conversations.","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:20:53Z","created_by":"Sinity","updated_at":"2026-07-31T10:20:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-mctu","title":"Codex reasoning text is discarded: session_events store a length, never the summary/content","description":"MEASURED 2026-07-31 (conversation-fidelity audit, audit-only pass).\n\nRAW: Codex rollout files (~/.codex/sessions/**/*.jsonl) carry 'reasoning' / 'agent_reasoning' response_items whose text lives in payload['summary'] and payload['content']. Sampling 40 random rollout files, 9 (22.5%) contained real non-empty plaintext reasoning.\n\nARCHIVE: zero thinking/reasoning BLOCKS exist for origin='codex-session' (block_type census over 3,204 sessions returns only tool_use 1,070,399 / tool_result 1,035,030 / text 434,132). The items are routed to session_events instead: 1,153,236 rows of event_type='reasoning' plus 318,474 'agent_reasoning'. Their stored payload is:\n select payload_json from session_events where session_id='codex-session:019a5de3-dfb0-76d2-897b-fc454d88e916' and event_type='reasoning' limit 3;\n {\"source_index\":9,\"type\":\"reasoning\"}\nNo text field at all -- verified against a raw file confirmed to contain non-empty summary text.\n\nCODE PATH: polylogue/sources/parsers/codex.py:406-457 _compact_response_payload builds the persisted payload. It captures type/id/call_id/name/status/timestamp/output_chars(len)/argument_chars(len)/cwd/metadata.turn_id -- i.e. it records the LENGTH of the reasoning and drops the reasoning. It never reads payload['summary'] or payload['content'].\n\nUNREACHABLE BY SEARCH: polylogue/storage/fts/sql.py builds the FTS index FROM blocks only, never session_events, so even a stored event payload would not be findable. This content is absent from every surface (read --view transcript, --view messages, FTS, MCP).\n\nSCOPE BOUNDARY (do not overstate): some Codex sessions genuinely cannot supply this -- one sampled session carried 76 reasoning items whose payload was opaque encrypted_content with empty summary/content. That is an upstream Codex-CLI limitation and the archive is right to hold nothing. This bug is specifically the ~22% of sessions where plaintext IS present and is dropped anyway.\n\nSUGGESTED FIX: capture summary/content in _compact_response_payload, and/or emit a BlockType.THINKING block the way local_agent/hermes/chatgpt parsers already do (polylogue/sources/parsers/base_support.py:33-37 is the shared shape). Emitting blocks additionally makes the content searchable. Requires an index-tier rebuild to backfill.","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:20:28Z","created_by":"Sinity","updated_at":"2026-07-31T10:20:28Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-r39b","title":"Claude Code thinking blocks dropped entirely when body is empty (signature-only era)","description":"MEASURED 2026-07-31 (fidelity audit, audit-only pass).\n\nRAW: Claude Code JSONL since ~2026-06 emits thinking content blocks as {\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\u003c408 chars\u003e\"} -- empty body, signature only. Ground sessions: claude-code-session:53e64853-1793-43d2-80ac-a41a8c5a56a2 has 275 such blocks; claude-code-session:38baa1de-9715-48fa-8175-f2a29d92800e has 470. In both, 100% are empty-bodied.\n\nARCHIVE: zero thinking blocks. sessions.thinking_count=0 and messages.has_thinking=0 for both. Verified this is NOT staleness: running the production parser (polylogue.sources.parsers.claude.code_parser.parse_code) on the raw bytes today yields blocks={text:559, tool_use:467, tool_result:467} -- no thinking.\n\nCODE PATH: polylogue/sources/parsers/base_support.py:33-37 in content_blocks_from_segments --\n if seg_type == 'thinking':\n text = seg.get('thinking') or seg.get('text') or ''\n if text:\n blocks.append(ParsedContentBlock(type=BlockType.THINKING, text=text))\nThe 'if text' guard drops the whole block when the body is empty; there is no else. Sibling branches (tool_use, tool_result) emit on structural presence. 'signature' is never read on any path.\n\nSCALE: sampled 400 of 3850 session files under ~/.claude/projects and bucketed thinking blocks by file month --\n 2025-12: 0 empty / 1073 non-empty\n 2026-01: 0 / 2188\n 2026-02: 0 / 1472\n 2026-03: 791 / 184\n 2026-04: 526 / 144\n 2026-05: 1228 / 3282\n 2026-06: 638 / 0\n 2026-07: 1549 / 0\nFrom 2026-06 the wire format is 100% signature-only, so 100% of current Claude Code reasoning structure is discarded. Archive-wide only 2976 of 16388 claude-code sessions carry any thinking block.\n\nCONSEQUENCE: any analysis of reasoning volume reads a confident zero for recent sessions, and the artifact is shaped like a real trend ('reasoning declined sharply after May') rather than an ingestion gap.\n\nSUGGESTED FIX: emit a THINKING block on structural presence regardless of body, and persist 'signature' (e.g. block metadata) so the reasoning-occurred fact and its provider proof survive. Requires an index-tier rebuild to backfill.","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:19:24Z","created_by":"Sinity","updated_at":"2026-07-31T10:19:24Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-3loh","title":"Leak audit L5: no content gate between an agent writing a file and a public push","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE. This is the mechanism behind L1-L4 and will produce the next one.\n\nTwo halves:\n1. .gitignore ignores .agent/* then re-admits .agent/demos/** and .agent/handoffs/**. (.agent/scratch/ is handled correctly - ignored except its README.) The comment above the block records that exactly this negation pattern was already removed for reports/ and archive/.\n2. The pre-commit hook runs 'ruff format --check' and 'ruff check' on staged *.py only, plus a worktree-escape detector. There is no size gate, no secret scan, and no archive-content check. The pre-push gate has no content checks either. VERIFIED by reading .beads-hooks/pre-commit (core.hooksPath points there; it is a superset that includes the repo's own hook body) and devtools/pre_push_gate.py.\n\nNote: polylogue/security/secret_scan.py already exists, is tested, and is exposed as 'polylogue scan-secrets' - it is simply not wired to the publication path.\n\nFix: remove the two negations; add a pre-commit content gate (size threshold, archive/export shape refusal, staged-text secret scan reusing the existing scanner).\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:33Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:33Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-2kcd","title":"Leak audit L2: real Codex session message text is in git history","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: EXPOSED.\n\nLeak path: a demo handoff pack was committed under .agent/archive/retired-demos/.../handoff-pack/ and later removed from the tip. Its chronicle.json contains verbatim message text from a real local Codex session (role, timestamp, message id, body). The blob remains reachable via 'git log --all' / 'git show' in every clone and fork.\n\nContent at risk: verbatim conversation text.\nPreconditions: none - one git show.\nIrreversibility: removal needs a history rewrite, a force-push on a public repo, and a GitHub GC request; clones and forks keep their copies.\n\nThis is the finding that should shape the response to the others: the tip is not the publication boundary. Deciding NOT to rewrite is a legitimate answer, but it should be an explicit decision rather than a default.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:26Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:26Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-b4cs","title":"Leak audit L1: real conversation exports committed to the public repo","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: EXPOSED.\n\nLeak path: .gitignore ignores .agent/* then explicitly un-ignores .agent/handoffs/** and .agent/demos/**. Agent working material written there is picked up by a plain 'git add' and pushed to the PUBLIC remote github.com/Sinity/polylogue.\n\nMeasured: 1264 tracked files / 262 MB under .agent/handoffs/. Ten of them carry real conversation content: five *.messages.json exports plus their five matching single-conversation HTML renders, totalling 3430 real messages, each carrying its chatgpt.com/share/... source URL.\n\nContent at risk: the operator's own AI conversations. Mitigating: these were SHARED conversations, so the content had already been published behind unlisted share URLs; the HTML files are single-conversation renders, not authenticated-page DOM captures (scanned: no sidebar conversation list, no account keys, no email-shaped strings). Not mitigating: the repo turns five unlisted URLs into an indexed, permanently mirrored, greppable copy with bodies inline.\n\nA structural scan for transcript shapes across the whole tracked tree found exactly these ten files and zero transcript-bearing markdown among the 802 .md files under .agent/handoffs/.\n\nPreconditions: none. Public since 2026-07-07. Irreversible without a history rewrite.\n\nFix: drop the two !.agent/... negations exactly as was already done for reports/ and archive/ (git rm --cached; nothing deleted from disk).\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:22Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:22Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-u19l","title":"Raw-authority quarantine is an absorbing state: 4,147 blockers await a refinement proof no actor produces","description":"Audit 2026-07-31 (daemon-failure-surface report, /realm/inbox/polylogue-audits-2026-07-31/). Live source.db: 22,287/42,753 raw_sessions rows have revision_authority='quarantined' (52%); raw_authority_blockers holds 4,147 unresolved 'accepted raw authority remains quarantined pending exact refinement proof' rows (+12 rekey-census, +7 head-mismatch, +6 shape). 15,205/17,384 frontier plans are residual in every census; fixed_point=0 across all 256 retained census headers; only 24 plans ever executed in the retained window. No code path produces the required refinement proof automatically (refine_quarantined_raw actuator is demanded by the witness but nothing discharges it), and no operator surface reports non-convergence: raw_frontier_integrity_projection checks broken_head/missing_source/cursor_ahead only, not fixed_point or executable/residual counts (storage/raw_retention.py:1074-1189). Consequence visible in status: 19,618 raw/index join gaps need classification. Needs: (a) an actual refinement actuator or explicit terminal classification for quarantined authority, (b) a convergence verdict on ops status/health.","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:22Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:22Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-gt1z","title":"Cost contract tests assert hand-built payloads for a dead provider-reported-cost path","description":"FALSE-GREEN AUDIT 2026-07-31 (finding F1+F2). Two test suites claim to verify that a provider-reported cost total is preserved verbatim. Neither calls any estimator.\n\nEVIDENCE (all grep-verified at 229c2739):\n- tests/unit/cost/test_contract_suite.py:109 defines a TEST-LOCAL _exact_estimate() that\n builds a CostEstimatePayload from literals (total_usd=1.25, provider_reported_usd=1.25,\n api_equivalent_usd=1.25, catalog_priced_usd=0.002).\n- :167 test_basis_fields_are_independent and :186 test_provider_reported_usd_preserved_exactly\n assert that this hand-built object has the fields it was just assigned.\n- tests/unit/insights/test_cost_basis_split.py:46-71 repeats the same shape independently.\n\nTHE PRODUCTION PATH IS DEAD:\n- polylogue/archive/semantic/pricing.py:628 defines _exact_estimate(). rg over polylogue/\n shows ZERO production callers. The only occurrences outside this definition are the\n test-local helper of the same name.\n- Its only would-be caller, _session_level_estimate() at pricing.py:793, is a stub:\n def _session_level_estimate(session): del session; return None\n- estimate_session_cost() (:808) calls it and only uses the result if status == 'exact',\n which can therefore never happen.\n- The provenance literal 'archive_session_reported_cost' that BOTH tests assert on appears\n nowhere in polylogue/ -- only in those two test files. No production path can emit it.\n\nWHY THIS IS P0 RATHER THAN A WEAK TEST: it is not that the assertions are weak, it is that\nthey document and 'verify' a cost-accounting behaviour the running system does not have.\nA reader (or agent) consulting these tests concludes provider-reported cost preservation is\nimplemented and covered. Given this repo's history of cost-accounting inflation defects\n(Codex 7.69x double-count; subscription-vs-API-equivalent confusion), a phantom-verified\ncost feature is exactly the wrong thing to have in the suite.\n\nAC:\n- Decide and record whether provider-reported exact cost is a real product requirement.\n- If yes: wire _exact_estimate into _session_level_estimate, and rewrite both tests to call\n estimate_session_cost() on a real Session so the assertion exercises production.\n- If no: delete _exact_estimate, the stub, and both tests -- do not leave the tests asserting\n a shape nothing produces (surgical renewal).\n- Either way a test must exist that fails when _exact_estimate's body is broken.\n- Audit the rest of tests/unit/cost/ for other hand-built-payload assertions.","status":"open","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:23Z","created_by":"Sinity","updated_at":"2026-07-31T08:19:23Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-9ykn","title":"sessions should require positive conversational evidence, not be the default shape","description":"OPERATOR OBSERVATION (2026-07-31): 'maybe we shouldn't assume something is a session by default? why do we do that?'\n\nMEASURED against the live index (23,296 sessions):\n sessions with ZERO messages: 5,255 (22.6% of the archive)\n claude-code-session 5,193 (31.7% of that origin)\n claude-ai-export 45\n codex-session 17\n\nTHE DEFECT: the ingest path's default disposition is 'this is a session'. Anything not positively recognised as something else still becomes one. Every classification gap therefore manifests as session inflation rather than as a loud unrecognised-record report.\n\nFOUR SEPARATE INCIDENTS, ONE CAUSE:\n hook events ingested as standalone sessions 83,286 -\u003e 18,391 after repair\n agent-\u003cid\u003e.meta.json sidecars 4,945 phantoms, 21% of the index\n a toolu_* tool-use id and 7 wf_* ids became sessions outright\n beads issue audit-logs (proposed) 924, averted only because the\n acquisition route shipped opt-in\nEach was fixed by adding a SPECIFIC refusal (an OriginSpec artifact rule, a\nwrite_hook_event path, a parse gate). None changed the default. So the next\nunrecognised record type will do it again and the fix will again be a special\ncase.\n\nPROPOSED INVARIANT: a session requires positive evidence of a conversation — at\nminimum one message carrying authored content. A record failing that test is\nREFUSED LOUDLY and routed to what it actually is (session_event, attachment,\nassertion, ObservedRepositoryEffect). 'I do not recognise this' must never\nproduce a session.\n\nThis is the record-level sibling of aggz invariant 2 ('exactly one chokepoint\nmay write a session') and the record-level form of the fail-loud principle being\napplied at field level elsewhere. Its structural value: it converts every FUTURE\nclassification gap from silent inflation into a visible refusal — which is\nexactly what the new claude_parse_coverage event (PR #3419) was invented to\ndetect after the fact.\n\nTWO THINGS TO CHECK BEFORE ACTING, do not assume:\n1. The hook-inflation postmortem DELIBERATELY RETAINED 832 genuinely-empty\n sessions (see polylogue-ne6k, which corrected an earlier plan to delete\n them). A naive 'refuse empty' rule would destroy a considered decision.\n 5,193 is far more than 832, so the majority are unexplained.\n2. Possible overlap with the 5,382 sessions carrying created_at_ms NULL\n (dataset finding C4) — similar magnitude, may be the same population. A\n dataset-hypotheses lane is measuring C4 concurrently; reconcile before\n designing.\n\nAC: the default disposition for an unrecognised record is refusal with a\nrecorded reason, not session creation; empty-session count is explained\n(intentional vs artifact) and the artifact class is eliminated at its source;\na regression test pins that an unrecognised record type does not create a\nsession.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T04:55:36Z","created_by":"Sinity","updated_at":"2026-07-31T04:55:36Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-4ma3","title":"paths.archive_root() ignores polylogue.toml, splitting the archive root","description":"polylogue/paths/_roots.py:archive_root() resolves POLYLOGUE_ARCHIVE_ROOT from\nthe environment only and never consults polylogue.toml's [archive] root, even\nthough polylogue/config.py documents and implements a 5-layer resolution\n(default, site TOML, user TOML, env, CLI) that DOES honour it.\n\nConsequence: any process without POLYLOGUE_ARCHIVE_ROOT set in its own\nenvironment (bare CLI invocations, hook writers, the browser-capture\nreceiver, ad hoc scripts) silently falls back to XDG_DATA_HOME/polylogue\ninstead of the operator's configured root (e.g. /realm/db/polylogue),\nsplitting archive state across two directories that nothing reconciles.\n\nMeasured live damage before the fix: 108,094 files (2.2 GB) accumulated\nin ~/.local/share/polylogue/hooks/pending/ since 2026-07-14 while the\ndaemon (which does get POLYLOGUE_ARCHIVE_ROOT from its systemd unit) drained\n/realm/db/polylogue/hooks/pending/ instead -- nothing processed the XDG-root\nbacklog. Browser-capture spool and inbox/ content were also split across\nboth roots at different times depending on which process's environment\nhappened to have the override set.\n\nFix: polylogue.config gained resolve_archive_root() (same layered precedence\nas load_polylogue_config, extracted so paths._roots can reuse it via a lazy\nfunction-local import without an import cycle -- config.py already imports\npolylogue.paths for GEMINI_DRIVE_FOLDER). paths.archive_root() now checks\nPOLYLOGUE_ARCHIVE_ROOT first (fast path, no config import) and falls back to\nresolve_archive_root() (site/user TOML, then XDG default) when unset.\nNothing is cached, preserving per-test POLYLOGUE_ARCHIVE_ROOT isolation.\n\nExplicitly out of scope for this fix: migrating the ~176K files already\nmisplaced under the XDG root (hooks pending+acknowledged, browser-capture\nspool, inbox) -- that is a separate data-migration lane.","status":"in_progress","priority":0,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T03:49:09Z","created_by":"Sinity","updated_at":"2026-07-31T03:49:18Z","started_at":"2026-07-31T03:49:18Z","comments":[{"id":"019fb653-c632-716f-9aa0-5cbc7b2faaac","issue_id":"polylogue-4ma3","author":"Sinity","text":"Fixed via PR #3414 (branch feature/fix/archive-root-honours-config, commit e9e7a7245). paths.archive_root() now falls back to polylogue.config.resolve_archive_root() (site/user TOML archive.root) when POLYLOGUE_ARCHIVE_ROOT is unset, instead of silently defaulting to XDG_DATA_HOME/polylogue. Verified: devtools test on tests/unit/core/test_paths.py (new TestArchiveRootHonoursConfigFile suite, 25 passed), test_config_resolution_regression.py (9 passed), plus config/cli-paths/browser-capture-token/hook-spool suites (143 passed); devtools verify --quick green. Data migration of the ~176K files already misplaced under the XDG root (hooks pending+acknowledged, browser-capture spool, inbox) is explicitly out of scope -- needs a separate follow-up.","created_at":"2026-07-31T03:59:31Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"polylogue-geop","title":"newer chatgpt exports are NOT supersets - April holds 33% more messages than July","description":"MEASURED 2026-07-31, comparing chatgpt-data-2026-04-23 against chatgpt-data-2026-07-29 over the 2,094 conversations present in BOTH.\n\n April 109,657 messages total / 97,403 in the common set\n July 72,981 messages total / 44,834 in the common set\n EVERY ONE of the 2,094 common conversations lost messages. Not one gained.\n\nNot deletion, not branch pruning (July's current_node path count is also far\nbelow April's), and not head/tail truncation (survivors are spread across the\nfull 0-100% index range with identical date spans). OpenAI DROPPED WHOLE\nCATEGORIES between export generations:\n\n content_type April July delta\n code 20,384 0 -20,384\n computer_output 8,192 0 -8,192\n execution_output 6,816 0 -6,816\n tether_browsing_display 1,399 0 -1,399\n tether_quote 1,178 0 -1,178\n system_error 177 0\n sonic_webpage 30 0\n citable_code_output 8 0\n text 37,829 24,890 -12,939\n multimodal_text 1,457 694 -763\n user_editable_context 821 1 -820\n thoughts 17,374 17,506 +132 (retained)\n reasoning_recap 1,738 1,743 +5 (retained)\n\n role\n tool 24,914 0 -24,914 \u003c- the ENTIRE tool layer\n system 5,099 0 -5,099\n assistant 54,513 32,839 -21,674\n user 12,877 11,995 -882\n\nThe whole code-interpreter / tool-use / browsing layer is absent from the newer\nexport. This also explains why model-produced sandbox files carry no file id in\nthe July data (polylogue-dt5s): the tool messages that created them are gone.\n\nCONSEQUENCES - these change import strategy, not just this one file:\n\n1. A newer export can be a STRICT SUBSET of an older one. 'Latest wins' is\n wrong for this provider. Coalescing must be a per-message UNION keyed on\n message id, with each export treated as a partial observation.\n2. The April 2026 and Oct 2025 exports are NOT superseded and must never be\n pruned as redundant. They are the only surviving record of 24,914 tool\n messages and 20,384 code blocks.\n3. This is precisely the aggz/superset question the operator raised for\n aistudio, now confirmed with hard numbers on a second provider: neither\n revision is a superset, so any model that must pick ONE winner loses data.\n The content-only comparison relation (#3401) must classify this pair as\n 'conflict', not 'contains' in either direction.\n4. Absence detection should compare across export generations per message id,\n not per conversation - a conversation present in both looked fine at\n session granularity while silently losing 78% of its messages.\n\nAC: importing all three chatgpt exports yields the UNION of their messages;\na conversation present in several exports carries every message any export\nobserved; and a regression test pins that the newer-export-is-subset case\ndoes not delete previously-ingested messages.","notes":"VERIFIED THREE WAYS (2026-07-31) after the finding was challenged as implausible for a GDPR export.\n\n1. THE EXPORT IS COMPLETE AS DELIVERED. Checked every file against the export's\n own export_manifest.json: 3,266 declared files, 3,266 present, ZERO missing,\n ZERO size mismatches, 18.091 GB declared vs 18.092 GB actual (delta is the\n manifest itself, which is not self-declared). So the loss is not download\n corruption, not truncation from the 5 stalled resumes, and not extraction\n error. It is what OpenAI shipped.\n\n2. IT IS A FORMAT CHANGE, NOT RETENTION AGE-OUT. Conversations created as\n recently as 2026-07-27 - two days before the export was generated - also\n contain ZERO tool-role and ZERO system-role messages. Across the ENTIRE July\n export the only roles present are assistant (59,728) and user (13,253).\n A retention window would have spared recent conversations; it did not.\n\n3. THE TOOL LAYER IS NOT HIDING IN chat.html EITHER. grep over the 221 MB\n chat.html: execution_output 0, computer_output 0, tether_quote 0. The\n rendered view carries no more than the JSON.\n\nWHAT APRIL STILL HAS (answers 'are the sandbox files in April then?' - yes):\n April non-json members 9,958 (vs 3,228 .dat in July)\n distinct file ids in member names 9,887\n file ids referenced INSIDE tool messages 10,453\n of those WITH bytes present 9,225 (88.2%)\n asset_pointer + metadata.attachments refs 3,189 distinct, 1,104 with bytes (34.6%)\n\n So in April the file ids live in the TOOL messages, which is exactly why\n July - having deleted the tool layer - cannot resolve model-produced files.\n April is the only record of ~9,225 attachment blobs.\n\nCONVERSATION-LEVEL COVERAGE IS ALSO NON-NESTED IN BOTH DIRECTIONS:\n in April but not July 309\n in July but not April 378 (some created as far back as 2023-02-14,\n i.e. April was ALSO missing old conversations)\n Neither export is a superset at conversation level either.\n\nCONTEXT FROM THE WEB: incomplete ChatGPT exports are a documented user\ncomplaint (community.openai.com/t/incomplete-data-export-with-conversations-json/1019950,\nNov 2024: a user's export dropped everything before 2024-10-28, 35MB -\u003e 4MB, no\nofficial response). The specific tool-layer removal is not publicly documented,\nso treat provider export completeness as untrusted and verify per generation.\nDECISIVE RESOLUTION RULE (2026-07-31). The union is not a heuristic merge - the two exports are in STRICT CONTAINMENT and there is no genuine disagreement anywhere in the corpus. Proven by field-walking all 44,171 messages present in both exports:\n\n field observations 748,209\n both set \u0026 AGREE 291,774\n both set \u0026 CONFLICT 2,479 (0.33%)\n only April 453,956\n only July 0 \u003c- July contributes NOTHING April lacks\n\nAnd the 2,479 'conflicts' are subsetting one level deeper, not disagreement.\nThey occur in exactly two fields - metadata.content_references (1,766) and\nmetadata.search_result_groups (713) - and inspecting them shows identical\nrecord COUNTS (29,528 both sides) and identical type distributions (file 8,543,\ngrouped_webpages 7,363, webpage_extended 6,239, hidden 4,889, attribution\n1,073, sources_footnote 951 - the same on both sides). What differs is the KEY\nSET of each citation record:\n\n April keys: alt end_idx error fallback_items items matched_text prompt_text\n refs safe_urls start_idx status style type\n July keys: alt fallback_items items prompt_text type\n\nJuly dropped end_idx, start_idx, matched_text, refs, safe_urls, error, status,\nstyle. Note start_idx/end_idx: July's citations LOST THEIR TEXT ANCHORS, which\nis the conceptual core of a citation.\n\nAlso lost from message.metadata between generations (top-level keys present in\nApril, absent in July): can_save, message_type, timestamp_, request_id,\ndefault_model_slug, CITATIONS (20,471 messages!), reasoning_status,\nturn_exchange_id, finish_details, is_complete. New in July: NONE.\nEnvelope fields nulled in July: status (finished_successfully -\u003e null, 42,000),\nweight (1.0 -\u003e null, 44,164), author.metadata removed - including\nreal_author='tool:web' on 237 messages.\n\nmessage CONTENT is byte-identical on all 44,171 common messages. Zero content\nconflicts.\n\nTHEREFORE the correct algorithm is deterministic and lossless, and needs no\nconflict policy at all:\n\n for each message id, and each field PATH (including inside nested citation\n records), take the value from whichever acquisition has one; where several\n have one they are equal; record which acquisition supplied each field.\n\n'Record the disagreement' is not needed for this provider pair because there IS\nno disagreement - only presence vs absence. This is a much stronger position\nthan the earlier framing and should be the default model for every origin:\ntreat an acquisition as a partial observation, merge at field-path granularity,\nand only escalate to a recorded conflict if two acquisitions ever assert\nDIFFERENT non-null values for the same path - which happened zero times here.\nVERDICT: LIVE (actively in_progress) — This is a fresh, ongoing investigation (created + started 2026-07-31) with extensive live-verified findings (chatgpt export union/subset semantics) still being landed; not stale, not closable. — evidence: bd show polylogue-geop --json (status=in_progress, started_at=2026-07-31T03:18:49Z, notes describe multi-step live verification concluding with a 'decisive resolution rule' still pending implementation of the AC's import/union behavior).","status":"in_progress","priority":0,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T01:10:03Z","created_by":"Sinity","updated_at":"2026-07-31T05:47:29Z","started_at":"2026-07-31T03:18:49Z","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -10,9 +19,9 @@ {"_type":"issue","id":"polylogue-cijx.4","title":"Repo identity, path normalization and readable labels are ONE batch","description":"DECIDED. These were three separate items; they are one, because the label is unusable until identity is fixed and both fall out of the same normalization.\n\nTHE EVIDENCE, from eight real untitled claude-code sessions:\n repo_name = 'agent-ad682bc849a1cd0f0'\n top path = /realm/project/polylogue/.claude/worktrees/agent-ad682bc849a1cd0f0/\n polylogue/pipeline/services/ingest_batch/_core.py\nA structural label today reads 'agent-ad682bc849a1cd0f0 - 27f - 499m' -- worse\nthan the UUID it replaces. repo_name derives from cwd, the cwd is a worktree\ndirectory, so the agent id becomes the repo name.\n\nNormalize both and the same eight sessions read:\n polylogue - pipeline/services/ingest_batch/_core.py +26 - 499 msgs\n polylogue - daemon/status.py +7 - 322 msgs\n polylogue - api/archive.py +10 - 259 msgs\n polylogue - tests/unit/insights/test_delegation_work_evidence.py +5 - 163 msgs\n polylogue - storage/repair.py +1 - 91 msgs\nFor a coding session, WHICH FILES YOU TOUCHED is the topic. That beats the\nprovider echo titles, which collide 78-way.\n\nDECISION 1 -- REPOSITORY IDENTITY\n A repository is keyed on its normalized remote (all spellings of one remote\n are one repo); where no remote exists, the outermost git root. NOT the cwd.\n A worktree is a CHECKOUT OF a repository, not a repository -- every\n /realm/worktrees/polylogue-* and .claude/worktrees/agent-* is one checkout of\n polylogue. A session with no git evidence resolves to a DIRECTORY and read\n surfaces say so; do not synthesize a repository for it. Measured today:\n polylogue holds 106 distinct repo_ids, sinex 28, sinnix 31; git_branch is\n populated on 15.8% of sessions, git_repository_url on 13.2%, commit_hash on\n 15.9% -- so for ~84% the 'repo' column is really cwd.\n\nDECISION 2 -- PATHS ARE REPO-RELATIVE\n Strip the checkout root prefix (already recorded as repos.root_path) so\n action_pairs.tool_path is comparable across checkouts of one repo. Without\n this, the same file edited in two worktrees is two different paths and no\n cross-session file question works.\n\nDECISION 3 -- THE LABEL IS A PROJECTION, NEVER A COLUMN\n Form: \u003crepo\u003e - \u003cdominant repo-relative path\u003e +N - \u003csize\u003e, substituting the\n provider title for the path clause when a real one exists. Computed at read\n time in the 4p1 Projection. It must not be written to sessions.title: it\n would collide with genuine provider titles (ai-title, threads.title) and\n freeze as the session grows -- '340 msgs' is wrong the moment message 341\n lands. Measured collision rate for the structural form: 3.5% over 4,000\n sessions, max collision 10, mostly pairwise -- acceptable, and far better\n than the echo baseline's 78-way.\n\nDECISION 4 -- RESULT UNIT IS THE TOP-LEVEL SESSION\n All eight sampled sessions above are agent-* subagents; 8,614 of 18,871\n sessions (45.6%) are subagent children. A default list is unreadable because\n it is half fanout. Default unit = top-level session; children reachable\n through an explicit projection, never filling the list. Any count states its\n unit -- '18,871 sessions' unqualified is wrong when 8,614 are children.\n\nSEQUENCE: identity+paths first (write-path change, no schema bump), then the\nlabel projection. Readability cannot land before identity.","acceptance_criteria":"1. One repository per normalized remote; worktrees enumerate underneath as checkouts; polylogue/sinex/sinnix each collapse to one. 2. tool_path is repo-relative; the same file in two worktrees is one path. 3. The display label is computed per request and appears in no table; sessions.title holds only provider-supplied values. 4. Default result unit is the top-level session, proven by re-running 'polylogue find repo:polylogue' and showing named non-fanout rows. 5. Report the label collision rate against the measured 3.5% / max-10 baseline.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:49Z","created_by":"Sinity","updated_at":"2026-07-29T04:52:49Z","labels":["area:insights","area:interop","area:substrate","horizon:mid","lane:read-contracts","tech-tree"],"dependencies":[{"issue_id":"polylogue-cijx.4","depends_on_id":"polylogue-cijx","type":"parent-child","created_at":"2026-07-29T06:52:48Z","created_by":"Sinity","metadata":"{}"}],"comments":[{"id":"019fb679-a9b8-72dd-9ddc-2347cc8c7091","issue_id":"polylogue-cijx.4","author":"Sinity","text":"Scoped lane (repo-identity/path-normalization/label surface only, per this\nlane's brief; avoided browser-extension/, code_parser.py [pbuh lane],\ncodex*.py parsers, drive.py, chatgpt.py, base_support.py, paths/_roots.py,\nstorage/sqlite/archive_tiers/write.py, hook spool).\n\nFIRST FINDING: decisions 1-3 were already substantially implemented and\nmerged on master before this lane started, as fallout of PR #3390\n(\"feat(archive): index v46 wire-evidence batch...\", commit 5e23e6abf,\nalready on origin/master). That PR's commit history (not reachable from\nthis branch, inspected via `git log --grep`) shows dedicated commits for\nthis exact bead's decisions: \"fix(storage): key repo identity on the\nnormalized remote, not checkout path\", \"feat(insights): add session\nstructural label projection\", \"feat(insights): wire session_label into\nArchiveStore summary reads\", \"feat(sources): grade session location\nevidence as directory or repository\", \"fix(storage): normalize repo\nidentity in the write-path repo-edge writer\". Concretely, at HEAD:\n\n - polylogue/archive/session/repo_identity.py: normalize_repo_name/path,\n repo_relative_path (decision 2), all tested\n (tests/unit/archive/test_repo_identity.py, 401 lines).\n - storage/sqlite/archive_tiers/write.py: repo_identity_key() keys repos\n on the canonicalized remote (\"remote:\u003chost\u003e/\u003cpath\u003e\") with a directory\n fallback (\"dir:\u003croot_path\u003e\") only when no remote is known -- decision\n 1. repo_checkouts table separates checkout identity from repository\n identity.\n - polylogue/insights/session_label.py: compute_session_structural_label\n + session_structural_label_for_session -- decision 3, a pure read-time\n projection, never written to sessions.title. Tested\n (tests/unit/insights/test_session_label.py, 253 lines).\n\nAC DISPOSITION:\n\n AC1 (one repo per normalized remote; worktrees enumerate as checkouts) --\n SATISFIED. repo_identity_key() canonicalizes scheme/userinfo/case/\n trailing .git across SCP-like and URL remote spellings; repo_checkouts\n is the separate checkout-identity table.\n\n AC2 (tool_path is repo-relative) -- SATISFIED as a read-time projection.\n repo_relative_path() strips the resolved checkout root; used by\n session_label.py's dominant-path computation. Not yet adopted by every\n other action_pairs.tool_path consumer in the archive (out of this\n lane's scope to audit exhaustively) -- the capability exists and is\n tested, broader adoption is available follow-up, not a gap in this\n bead's own AC wording.\n\n AC3 (label is a projection, never a column) -- SATISFIED architecturally,\n but was DEAD IN PRODUCTION until this lane's fix. _summary_from_row\n (storage/sqlite/archive_tiers/archive.py) gated the structural-label\n fallback on \"is sessions.title non-blank\", but Claude Code's parser\n initializes title to the raw composed session id and only promotes\n title_source off UNKNOWN when a real signal exists -- so a title_source\n ='unknown' row still has a non-blank title (the exact \"agent-\u003chash\u003e\"\n echo this bead's own motivating text complains about) and the old\n blank-only check accepted it as real. Measured live (read-only,\n /realm/db/polylogue/index.db): 7,501 of 15,401 root sessions (48.7%)\n carry title_source='unknown' -- the fallback never fired for any of\n them before this fix. Fixed in commit eb5f9048d: the \"is this a real\n title\" gate now also checks title_source in {origin, heuristic, user}.\n See PR for full diff + regression test\n (test_unknown_title_source_falls_back_to_structural_label).\n\n AC4 (default result unit is the top-level session) -- NOT DONE, explicitly\n deferred. Investigated: sessions.parent_session_id and Session.is_root\n (parent_id is None) already exist and are correct, and a plan-level\n `root: bool | None` filter + `.is_root(True)` fluent builder method\n already exist in archive/filter/builder.py + archive/query/plan.py --\n but `root` is UNREACHABLE from every actual query surface. It has no\n `spec_attr` in archive/query/fields.py's QueryFieldDescriptor (unlike\n origin/repo/tag/etc), no DSL grammar case in archive/query/expression.py\n (continuation/sidechain/has_branches are in the same unreachable state),\n and no CLI flag. Making `root` DSL/CLI-reachable AND flipping the\n default requires: a Lark grammar case, spec_attr wiring end-to-end\n (query_spec_to_plan), field-metadata docs (discovery.py/metadata.py),\n generated-docs regen (CLI reference, MCP reference, OpenAPI), and a\n default-behavior decision that affects every list() caller across CLI/\n MCP/API/daemon -- a genuinely separate, sizable, high-blast-radius\n change from the repo-identity/label surface this lane owns, and one\n that touches archive/query/expression.py + fields.py, files several\n other concurrent/recent lanes have also been editing. Filing as a\n follow-up bead rather than attempting it inside this lane's already-\n large diff. NOT a pbuh-lane overlap (pbuh is about ai-title/pr-link/\n agent-name typed sidecar records, unrelated to fanout-default\n semantics).\n\n AC5 (report collision rate against 3.5%/max-10 baseline) -- MEASURED,\n read-only, live archive (/realm/db/polylogue/index.db, 15,401 root\n sessions), AFTER the AC3 fix above (before the fix the structural label\n was never exercised so there was nothing real to measure):\n - Among root sessions with a resolved dominant repo-relative path\n (real action_pairs.tool_path evidence -- the population the bead's\n original 3.5%/max-10 baseline was measured against): collision\n rate 3.28% (78/2377 sessions), max collision group 37. In the same\n ballpark as the baseline; the larger max-group (37 vs 10) likely\n reflects a larger/older corpus than the original measurement day.\n - Raw collision rate across ALL 13,219 title-less root sessions:\n 76.46% (10,107 sessions), dominated by a single 5,233-session\n cluster that collapses to the literal label \"0 msgs\" -- these are\n genuinely evidence-free sessions (zero messages, no repo, no file\n touch), not a labeling defect: the label is honest about having no\n distinguishing signal to offer for a truly empty session. Whether\n 5,233 zero-message root sessions is itself a data-quality issue\n (stub/aborted captures, hook artifacts) is a separate question this\n lane did not investigate -- flagged here rather than silently\n folded into the collision number.\n\nLeft for follow-up (filed as a new bead, see graph): AC4 (root: DSL/CLI\nreachability + default), and the \"5,233 zero-message root sessions\" data\nquality question.\n","created_at":"2026-07-31T04:40:54Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"polylogue-ah21","title":"BrowserCaptureTurn has no blocks channel: structure is destroyed at acquisition, irreversibly","description":"ROOT CAUSE of browser-capture flattening, and it is in the wire schema, not the adapters.\n\n class BrowserCaptureTurn(BaseModel): # polylogue/browser_capture/models.py\n provider_turn_id: str\n role: Role\n text: str | None = None # \u003c- the ONLY content channel\n timestamp: str | None = None\n ordinal: int = 0\n parent_turn_id: str | None = None\n attachments: list[BrowserCaptureAttachment]\n provider_meta: dict[str, object] # \u003c- untyped escape hatch\n\nThere is no blocks field. A turn's role may be 'tool', but the call's input,\noutput and outcome have nowhere to go except free text. Every provider adapter\nis forced through one content channel regardless of what it observed.\n\nTHE EXTENSION IS NOT THE PROBLEM -- it is more capable than the transport.\nbrowser-extension/src/content/chatgpt_bridge.js intercepts window.fetch and\nacquires a session access token, so it can obtain ChatGPT's authoritative API\npayload (the mapping tree with tool nodes and status). The adapters already\nrecognise tool roles (backfill/providers.js:58, content/chatgpt.js:368). The\nstructure is available and the schema cannot carry it.\n\nMEASURED CONSEQUENCE: captured ChatGPT sessions yield 22,992 tool_result blocks\nagainst 7,745 tool_use blocks -- 3x more results than calls -- because pairing\nis reconstructed from prose rather than observed.\n\nWHY THIS IS THE WORST PLACE IN THE PIPELINE TO LOSE STRUCTURE: a parse gap is\nre-runnable against retained bytes. A capture that never recorded the structure\ncannot be recovered at any later date, for any past session. Every day this\nstands, more conversations are permanently flattened.","acceptance_criteria":"1. BrowserCaptureTurn carries typed content blocks; text remains as a rendering, not as the only channel. 2. The ChatGPT adapter emits the API payload's structure via the native bridge rather than reconstructing from rendered prose. 3. tool_use and tool_result counts are consistent for captured sessions -- the current 3:1 ratio is the regression signal. 4. parent_turn_id survives into the archive, so the conversation DAG is not flattened to a list. 5. Report per-origin block-kind coverage for captures before and after.","status":"closed","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:43Z","created_by":"Sinity","updated_at":"2026-07-29T17:16:54Z","closed_at":"2026-07-29T17:16:54Z","close_reason":"Implemented. BrowserCaptureTurn now carries a typed content-blocks channel (BrowserCaptureBlock, mirroring ParsedContentBlock minus web_constructs, which is a derived enrichment not observable at the wire boundary); text remains a rendering rather than the only channel. The ChatGPT extension adapter classifies mapping-node content_type/recipient into typed blocks with constructed tool_id pairing. Important correction to this bead's premise, established by measurement: the cited 22,992:7,745 tool_result:tool_use ratio does NOT originate in the capture transport -- only 20 of 455 real captured sessions use the compact/DOM-fallback path this fixed; 435 delegate natively to sources/parsers/chatgpt.py, where the ratio is worse (~4.6:1). That parser-side pairing defect is filed separately as polylogue-4fm3 and is being fixed there. AC1/AC2/AC4 satisfied, AC3 partially (see 4fm3), AC5 reported.","labels":["area:capture","lane:capture-reliability"],"comments":[{"id":"019faea2-35e0-7960-9a05-cef50b174ba0","issue_id":"polylogue-ah21","author":"Sinity","text":"Implemented on feature/browser-capture/typed-content-blocks (PR pending).\n\nScope actually implemented (AC1, AC2, AC4 satisfied; AC3 partially satisfied,\npartially misframed by new evidence -- see below):\n\nAC1 (typed blocks channel) -- SATISFIED. Added `BrowserCaptureBlock`\n(polylogue/browser_capture/models.py), mirroring ParsedContentBlock\n(type/text/tool_name/tool_id/tool_input/media_type/metadata/is_error/\nexit_code; no web_constructs -- that's a derived enrichment, not observable\nat the wire boundary). `BrowserCaptureTurn.blocks: list[BrowserCaptureBlock]`\nadded; `text` stays as a rendering, no longer the only channel;\nrequire_content now accepts blocks-only turns.\n\nAC2 (ChatGPT adapter emits API structure via the bridge, not DOM prose) --\nSATISFIED for the concrete gap that actually existed: the compact/backfill\nbridge path (browser-extension/src/backfill/page_transport.js's\ncompactChatGptConversation, used when a conversation exceeds the executeScript\nscripting-result size cap) explicitly cannot be trusted as a native mapping\npayload by the parser (_has_chatgpt_native_payload rejects\npolylogue_bridge_projection == \"chatgpt-native-compact-v1\"), so it fell\nthrough to the parser's generic per-turn loop with zero blocks. Fixed:\nChatGptBackfillAdapter.normalizeCapture (providers.js) and the live content\nscript's collectNativeTurns (chatgpt.js) now classify each mapping node's own\ncontent_type/recipient evidence into typed blocks (code-interpreter\ncall -\u003e tool_use, its output -\u003e tool_result, paired by constructed tool_id:\nthe call's own node id, and the result's parent node id). Was already true\nfor the FULL native-payload case (delegates entirely to\nsources/parsers/chatgpt.py) -- unaffected, no regression.\n\nAC3 (tool_use:tool_result 1:1) -- PARTIALLY SATISFIED, PARTIALLY MISFRAMED.\nVerified via read-only query against /realm/db/polylogue/index.db\n(file:...?mode=ro, no write): the bead's cited 22,992:7,745 ratio does NOT\noriginate in the browser-capture transport this bead scoped -- it originates\nin sources/parsers/chatgpt.py's own code/execution_output classification\n(content_type \"code\" -\u003e BlockType.CODE not TOOL_USE, \"execution_output\"\nunconditionally -\u003e TOOL_RESULT, neither sets tool_id). Evidence: restricting\nto sessions actually tagged capture:* (455 of 2635 chatgpt-export sessions),\n435 used capture:browser-native-payload (full delegation to chatgpt.py,\nuntouched by this PR) vs only 3 compact + 17 dom-fallback (the paths this PR\nactually reaches) -- and the ratio among captured sessions is *worse*\n(tool_use=3877, tool_result=17768, ~4.6:1), confirming chatgpt.py is the\ndominant contributor, not the browser-capture wire schema. chatgpt.py is\nexplicitly out of this PR's scope (owned by another lane). Filed\npolylogue-4fm3 with the full evidence and a proposed fix. This PR does fix the\n20 compact/dom-fallback sessions' structural gap and closes it for all future\ncaptures that take those paths (including any future non-ChatGPT adapter).\n\nAC4 (parent_turn_id survives) -- SATISFIED, was already true. Verified across\nall four capture paths (native full delegation, compact/generic loop, Claude\nfallback, live collectNativeTurns) that parent_turn_id -\u003e parent_message_id\nthreads through; added explicit test assertions.\n\nAC5 (per-origin block-kind coverage before/after) -- reported in the PR body\nwith the exact read-only query and counts above; \"after\" numbers for the live\narchive require a derived-tier reprocess this PR does not run (no consequential\nwrite to /realm/db/polylogue authorized here). New synthetic tests demonstrate\nthe fix end-to-end via the real receiver -\u003e parser -\u003e materialize -\u003e index.db\nroute (tests/unit/sources/test_browser_capture.py).\n\nNot touched, per explicit scope: polylogue/storage/sqlite/** (schema lane),\npolylogue/sources/parsers/chatgpt.py (parser lane, see polylogue-4fm3).\n","created_at":"2026-07-29T16:08:14Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"polylogue-2qx.3","title":"Connect the schema inference that already exists: it found every unread field and nothing consumes it","description":"THE INFERENCE ENGINE ALREADY DID THE WORK. This is not a missing capability; it is an unconnected pipeline.\n\npolylogue/schemas/providers/claude-code/versions/v1/elements/session_record_stream.schema.json.gz\nis 140 KB uncompressed, generated from 2,171,910 samples, and contains:\n stop_reason PRESENT structuredPatch PRESENT parentToolUseID PRESENT\n agentId PRESENT slug PRESENT ttftMs PRESENT\n originalFile PRESENT oldString PRESENT toolUseResult PRESENT\nEvery field this backlog records as discarded is IN THE COMMITTED SCHEMA, and\nhas been since 2026-03-16.\n\nThe engine is good. Its extension keywords (codex package) carry far more than\nfield names:\n 110x x-polylogue-frequency 35x x-polylogue-values (observed value sets)\n 21x x-polylogue-range 10x x-polylogue-format (iso8601 detection)\n 10x x-polylogue-multiline 9x x-polylogue-array-lengths\n 6x x-polylogue-semantic-role 5x x-polylogue-evidence (depth/fanout/name_signal)\n 1x x-polylogue-mutually-exclusive\n\nTHREE JOINS ARE MISSING, and each is cheap and static:\n\n (1) SCHEMA -\u003e PARSER READS. Nothing asks 'the schema observed field X across\n 2.1M samples; does any parser read it?' A leaf-name diff between the\n committed schema and polylogue/sources/ produces the acquired-and-unread\n list directly. This replaces the blob-sampling enumeration an earlier\n draft of this bead proposed -- deterministic, versioned, and far cheaper.\n\n (2) SCHEMA -\u003e HARDCODED VOCABULARIES. sources/ carries 51 frozenset/dict\n constants. Filesystem ones are fine (_SUPPORTED_EXTENSIONS, _SKIP_DIRS).\n Provider-data ones duplicate what the schema observed:\n _SKIPPED_SIDECAR_RECORD_TYPES 12 record types hand-listed with no\n per-type rationale -- the schema knows which types exist; this is\n the OriginSpec artifact-kind declaration living in a parser\n _SUCCESS_OUTCOMES = {ok, success, succeeded, completed, outcome_ok}\n five GUESSED synonyms where x-polylogue-values holds the observed set\n _COMPACTION_END_REASONS, _REQUIRED_SESSION_COLUMNS, _GIT_BRANCH_PREFIXES\n Note _GIT_BRANCH_PREFIXES heuristics run against a git_branch column that\n is empty on 100% of claude-code sessions.\n\n (3) PER-FIELD FIRST-SEEN. The package stamps\n x-polylogue-element-first-seen == -last-seen == -generated-at\n all the same microsecond (2026-03-16T12:26:12.880141+00:00), and there are\n NO per-field first/last-seen keys. Yet every record carries a timestamp --\n the schema itself annotates it semantic-role=message_timestamp,\n format=iso8601. The inference walks those timestamps and stamps wall-clock\n instead. Per-field first-seen is min(timestamp of records containing the\n field) and is free at generation time.\n Without it the drift sentinel (polylogue-da1, #3362) can only say\n NEW_FIELD relative to a 134-day-old package -- it cannot distinguish a\n field that arrived yesterday from one present since March.\n\nTHE SENTINEL'S MISSING FOURTH CLASSIFICATION. schemas/drift_sentinel.py\nclassifies UNSEEN_SHAPE (no candidate schema), NEW_FIELD (schema lacks the\nfield), FIELD_CHANGED (validation failed). All three ask what the SCHEMA does\nnot know. There is no classification for 'schema knows it, parser ignores it',\nwhich is the actual defect -- and because those payloads validate cleanly, the\nsentinel marks them benign.\n\nGENERATE RAN; PROMOTE DID NOT. All nine providers have exactly one version\ndirectory (v1). Recent work is real -- #2934 (2026-07-17) derived archive\nworkload profiles from provider schemas and added\nproviders/claude-code/pins.json rejecting two mis-inferred semantic roles\n($.gitBranch as session_title, $.toolUseResult.oldTodos as message_container),\nwhich is direct evidence the engine was run on claude-code that week and SAW\ntoolUseResult.oldTodos. But no regenerated package was promoted, so the\ncommitted artifact is still March-old while the machinery is current.\nbrowser-capture v1 was rewritten 2026-07-27 with sample_count=1 -- a token\nregeneration, not a corpus run.\n\nDO NOT rebuild a sampler. Promote the schema, then run the three joins.","acceptance_criteria":"1. lab schema promote runs for every provider so committed packages reflect current data; report each package's sample_count and age before and after. 2. A static schema-vs-parser diff is committed and runnable, and its output is triaged per key into read / deliberately-dropped-with-recorded-reason / to-acquire in the owning OriginSpec. 3. Provider-data vocabularies hardcoded in sources/ are replaced by, or checked against, x-polylogue-values; _SKIPPED_SIDECAR_RECORD_TYPES becomes an OriginSpec declaration with a per-type reason. 4. Per-field first-seen/last-seen are emitted at generation from record timestamps the pass already reads. 5. The drift sentinel gains the fourth classification (schema-known, parser-unread) and it runs in a gate. 6. No blob-sampling enumeration is built; the schema is the source.","notes":"2026-07-29 (worktree-agent-acd6757a7a8b152f2, parser-diff triage for claude-ai/claude-code): partial AC#2 slice, not closure. Rebased onto origin/master and cherry-picked the already-landed lab schema parser-diff tool (ab4a9a304, on feature/chore/promote-schemas-and-wire-gates) plus the prior sidecar-persistence commits (514900789/05099666c) as a starting base. Found the tool currently returns 0 rows against every COMMITTED provider schema (claude-ai/claude-code/codex/chatgpt/gemini*/hermes*/antigravity all checked) because x-polylogue-observed-distribution is absent from every committed .gz package -- it only works against a freshly regenerated, uncommitted schema. Used it in-memory at min-encountered=0 for the referenced-name list instead, then verified real corpus frequency directly (~/.claude/projects + /realm/db/polylogue source.db blob store) since the promote step (AC#1) hasn't run.\\n\\nTriaged and landed for claude-code (commits e4d0715d5, cebb70e00): fixed a real bug (compactMetadata/preservedSegment/anchorUuid read via wrong snake_case keys, silently nulling trigger/pre_tokens on every compaction since ~ac9cfeb0b); added microcompact_boundary detection (previously fell through to a placeholder message, losing trigger/preTokens/tokensSaved/compactedToolIds entirely); added custom-title and file-history-delta as two MORE sidecar record types beyond the original twelve in _SKIPPED_SIDECAR_RECORD_TYPES (custom-title also now wins session-title precedence over ai-title); added toolUseResult structural-fact projection (sandbox/interrupted/file extents/structuredPatch counts/todo priority state) via two new session_event types; extended message_usage with ttft_ms/stop_reason/cache_creation TTL split/service_tier/inference_geo/cache_miss_reason; added claude_session_kind event and direct gitBranch capture (previously ONLY populated via a separate, often-absent legacy sessions-index.json sidecar).\\n\\nTriaged and landed for claude-ai (commit e793e6d55): tool_use/tool_result segment fields (start_timestamp/stop_timestamp, integration_name/integration_icon_url, approval_key/approval_options, display_content, is_mcp_app, mcp_server_url) now flow into ParsedContentBlock.metadata; top-level conversation summary now persists as claude_ai_conversation_summary.\\n\\nNOT done (still open against this bead's real AC): no schema promote ran (AC#1); _SKIPPED_SIDECAR_RECORD_TYPES/_SIDECAR_EVENT_TYPES are still hand-maintained dict/frozenset constants, not OriginSpec-declared (AC#3); no per-field first/last-seen (AC#4); no drift-sentinel fourth classification (AC#5). Also left explicitly to-acquire (documented as code comments, not silently dropped): claude-ai's nested Drive/doc-citation content[].content[] cluster needs its own ParsedWebConstruct-shaped design distinct from the existing citations[] projection.\n2026-07-29 (worktree-agent-a6d396610f6c9a165): confirmed AC#2 (static schema-vs-parser diff, committed+runnable) and AC#5 (drift sentinel's fourth classification, KNOWN_FIELD_UNREAD) were BOTH already fully done and wired into the live path before this session -- polylogue/schemas/schema_parser_coverage.py + drift_sentinel.py + pipeline/services/ingest_worker.py:475-500 (out of my scope to touch, verified read-only). This session's contribution: ran the parser-diff/coverage join against gemini/gemini-cli/antigravity/browser-capture/codex (the providers with no prior triage pass) and fixed two accuracy bugs in the join itself -- PROVIDER_PARSERS was missing drive_support_attachments.py (gemini) and browser_capture/models.py (browser-capture), producing false-positive \"unread\" rows for both; fixed in both copies of the map (devtools/schema_parser_diff.py + schemas/schema_parser_coverage.py, commit 0d7a19c47). Also closed one real gap the join surfaced for codex: patch_apply_end.changes/.success, plus turn_context.personality/.summary/.collaboration_mode -- see polylogue-cgfy note for full disposition table.\n\nStill NOT done (unchanged from the prior lane's note): AC#1 (schema promote hasn't run for codex/browser-capture -- explicitly out of this session's scope, polylogue/schemas/providers/** was reserved for a concurrent regeneration lane); AC#3 (_SKIPPED_SIDECAR_RECORD_TYPES/_SUCCESS_OUTCOMES/_COMPACTION_END_REASONS/_REQUIRED_SESSION_COLUMNS/_GIT_BRANCH_PREFIXES are still hand-maintained frozenset/dict constants, not OriginSpec-declared); AC#4 (no per-field first/last-seen, that lives in schemas/generation/ which is also reserved for the regeneration lane).\nVerification (group2 sweep, 2026-07-30): PARTIAL. Own notes (2026-07-29) enumerate status per AC: AC2 (schema-vs-parser diff) done; AC5 (drift sentinel KNOWN_FIELD_UNREAD) done. Still open: AC1 (schema promote), AC3 (OriginSpec-declared vocabularies), AC4 (per-field first/last-seen). Not safe to close.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:42Z","created_by":"Sinity","updated_at":"2026-07-31T05:48:29Z","labels":["area:ingest","area:sources","delivery:K-interop-origin-export","delivery:ac-patched","horizon:frontier","lane:origin-interop-export","refactor"],"dependencies":[{"issue_id":"polylogue-2qx.3","depends_on_id":"polylogue-2qx","type":"parent-child","created_at":"2026-07-29T06:52:41Z","created_by":"Sinity","metadata":"{}"}],"comments":[{"id":"019fac95-35fb-7dd7-a040-a15160be6a1e","issue_id":"polylogue-2qx.3","author":"Sinity","text":"Hermes triage complete (polylogue-2qx.3 instance, this task's scope: hermes_state.py,\nhermes_spans.py, hermes_lifecycle.py, hermes_verification.py, hermes_identity.py).\n\ndevtools lab schema parser-diff --provider hermes --min-encountered 1 --json (run from\na temp copy of the schema_parser_diff.py branch, feature/chore/promote-schemas-and-wire-gates,\nsince that command isn't on master yet) found 301 unread keys over 167+2 sampled documents.\n\nSplit into two document shapes:\n - 21 keys belong to the mainstream 167-document JSON snapshot shape, parsed by\n polylogue/sources/parsers/local_agent.py::parse_hermes (shared with gemini-cli,\n outside this task's write scope) -- filed as polylogue-5o05.\n - 280 keys belong to the real NeMo Relay ATIF trajectory format (2 sampled documents),\n parsed by hermes_spans.py -- fixed directly on branch\n worktree-agent-aa47c5139f1933ae3, commits aa9fc858c/0e46f702a/b9f14bbd2:\n * step-level extra telemetry: ancestry/tool_ancestry (delegation chain),\n invocation/tool_invocations (framework+timing), llm_response.usage /\n sibling metrics (per-step token accounting), tool-call provider_data ids,\n observation.results[] correlation ids/metadata\n * new hermes_tool_availability_span event: the tool-definition schema (name/\n description/parameters) OFFERED to the model at each llm-request step --\n materially distinct from hermes_tool_execution_span (a tool actually called),\n and previously unrepresented anywhere in the archive\n * document-level: trajectory_id, agent.extra.plugin, final_metrics.* totals\n Deliberately still dropped, with reasons documented inline in hermes_spans.py's\n module docstring: event_payload.conversation_history (a second copy of the\n session's own messages -- payload-hygiene rule), per-tool-call arguments and\n observation.results[].content (conversation-adjacent content, bounded-evidence-only\n per the module's pre-existing policy), llm_request instructions/input (bounded to\n presence, not value), and llm_request internal API plumbing (extra_headers/store/\n prompt_cache_key/include -- no evidentiary value). tools[]._truncated_items is not\n a real Hermes field at all -- a schema-generation-tool artifact (grepped, zero\n references anywhere in polylogue/ source).\n\nNo index-tier storage needed -- all new evidence rides existing session_events\n(event_type has no CHECK vocabulary) and existing event payloads. No index/schema\nversion bump.\n\nVerification: devtools test tests/unit/sources/parsers/test_hermes_spans.py\ntests/unit/insights/test_hermes_topology_projection.py -\u003e 36 + 13 passed; mypy\n--strict clean; ruff clean.\n","created_at":"2026-07-29T06:34:48Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} -{"_type":"issue","id":"polylogue-cgfy","title":"34 of the 70 most common wire keys are never read, including 105,123 structured diffs","description":"SYSTEMATIC ENUMERATION 2026-07-29. Method: parse 60 real Claude Code transcripts, count every top-level / message / usage / toolUseResult key, then grep polylogue/sources/ for each name. This is the complete answer to 'what else arrives typed and is discarded', replacing the ad-hoc list.\n\n34 of the 70 most frequent keys have ZERO references in polylogue/sources/.\n\nTHE FILE-EDIT CORPUS -- entirely unread, corpus-wide counts:\n structuredPatch 105,123 real unified diffs:\n {\"oldStart\":143,\"oldLines\":6,\"newStart\":143,\n \"newLines\":14,\"lines\":[...]}\n originalFile 92,313 the pre-edit file content\n oldString 86,085 with newString and replaceAll alongside\n filePath which file each edit touched\n userModified whether the human changed it afterwards\n\npolylogue-cijx grades file trajectories 'observed' -- 'only tool/action-derived\ndeltas' -- and states that 'checkpointed' requires captured pre/post state.\nThe pre-state IS captured, in originalFile, and the deltas ARE structured, in\nstructuredPatch. The tier cijx declares out of reach is sitting in the bytes.\n\nOTHER UNREAD KEYS OF SUBSTANCE (occurrences in the 60-file sample):\n slug 1,500 human-readable agent name (the subagent display\n problem: '5ecdb160-...:agent-af4e' vs 'greedy-\n squishing-hamming')\n message.stop_reason 1,184 terminal state (see the outcome bead)\n message.stop_sequence 1,184\n parentToolUseID 657 the delegation join key (see the delegation bead)\n toolUseID 679\n sourceToolAssistantUUID 143\n usage.cache_creation 664 cache-creation token detail\n message.ttftMs 36 time to first token\n todos / oldTodos / newTodos agent task-list evolution over a session\n thinkingMetadata 34\n permissionMode 33\n hookCount / hookInfos 22\n toolUseResult.sandbox 60\n toolUseResult.filenames / numFiles 46\n requestId 1,171\n userType 2,789\n\nMEASURED NEGATIVE, recorded so nobody re-files it: usage.service_tier looked\nlike the answer to the API-vs-subscription cost question. It is NOT --\n1,651,137 occurrences, every one 'standard'. A constant. Acquiring it would add\nnothing. Check payloads before filing.","acceptance_criteria":"1. Every key in this enumeration is classified read / deliberately-dropped-with-reason / to-acquire, recorded in the Claude Code OriginSpec fidelity declaration rather than an unexplained frozenset. 2. structuredPatch, originalFile and oldString/newString are persisted; cijx's file-trajectory grading rises from observed to checkpointed where they exist, proven on a sample. 3. slug reaches read surfaces so subagent rows carry names. 4. The enumeration is re-runnable and its output committed, so a future wire change surfaces new unread keys instead of hiding them. 5. Report bytes and row counts added per key acquired.","notes":"2026-07-29 (worktree-agent-a6d396610f6c9a165, un-triaged-provider pass: gemini/gemini-cli/antigravity/browser-capture/codex): branch fast-forwarded to b3ae790be (feature/chore/promote-schemas-and-wire-gates), which already carried a huge amount of prior work: claude-code/claude-ai/gemini/gemini-cli/hermes/antigravity all promoted to schema v2, block-metadata routed to session_events for chatgpt/gemini-cli/hermes/browser-capture/codex, and the drift-sentinel 4th classification (KNOWN_FIELD_UNREAD, polylogue/schemas/schema_parser_coverage.py) fully implemented AND wired into the live ingest path (pipeline/services/ingest_worker.py:475-500) -- polylogue-2qx.3's core ask was already done before this session.\n\nRan devtools lab schema parser-diff --min-encountered 1 against the committed (already-promoted) v2 packages for gemini/gemini-cli/antigravity/browser-capture; codex has no v2 (still stale March v1, no x-polylogue-observed-distribution) so its 0-row output is a tool blind spot, not evidence of full coverage -- verified codex frequency directly against ~3,200 real ~/.codex session files instead.\n\nFindings/dispositions:\n- gemini: all \"unread\" rows (runSettings.enable*/environmentMode/responseSchema) are FALSE POSITIVES -- drive.py:_model_config_event stores the whole runSettings dict verbatim (origin_specs.py:625). _polylogue_drive_live_bytes_b64 was a second false positive: read by drive_support_attachments.py, missing from PROVIDER_PARSERS in both devtools/schema_parser_diff.py and schemas/schema_parser_coverage.py -- fixed both maps.\n- gemini-cli: memoryScratchpad.* is a FALSE POSITIVE -- local_agent.py:_gemini_cli_memory_scratchpad_event already stores it verbatim. toolCalls.args.*/resultDisplay.* also verbatim-captured.\n- antigravity/browser-capture: browser-capture's 8 rows were the same PROVIDER_PARSERS gap (browser_capture/models.py missing) -- fixed. antigravity's single row (version, 0 encountered documents) has zero corpus signal either way.\n- codex (real gap, FIXED, commit 0d7a19c47): patch_apply_end.success/.changes (per-file add/update/delete + unified_diff + move_path) was completely unread -- the direct codex analogue of this bead's own structuredPatch finding. Now retained verbatim. turn_context.personality/.summary/.collaboration_mode also newly captured.\n- codex measured-negative: event_msg.memory_citation is null on every sampled record across the full corpus -- a constant.\n- codex to-acquire, deferred: internal_chat_message_metadata_passthrough carries only {turn_id}; needs a ParsedMessage metadata channel plumbed through every codex.py message constructor, left as a named follow-up.\n\nAll codex dispositions recorded in origin_specs.py's codex fidelity_notes.\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: PARTIAL. Field capture landed 2026-07-29 (v46 file_edits schema: structured_patch_json/original_file/old_string/new_string persisted per storage/sqlite/queries/file_edits.py; slug captured in claude/code_parser.py:1050; codex patch_apply diffs fixed commit 0d7a19c47) satisfying much of AC2/AC3. But AC2's specific \"cijx grading rises from observed to checkpointed\" wiring not found (grepped polylogue/insights - no such evidence-tier concept referencing file_edits), and full AC1 classification-recorded-in-fidelity-declaration / AC4 re-runnable committed enumeration not independently verified complete.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:32Z","created_by":"Sinity","updated_at":"2026-07-31T05:52:10Z","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-cgfy","title":"34 of the 70 most common wire keys are never read, including 105,123 structured diffs","description":"SYSTEMATIC ENUMERATION 2026-07-29. Method: parse 60 real Claude Code transcripts, count every top-level / message / usage / toolUseResult key, then grep polylogue/sources/ for each name. This is the complete answer to 'what else arrives typed and is discarded', replacing the ad-hoc list.\n\n34 of the 70 most frequent keys have ZERO references in polylogue/sources/.\n\nTHE FILE-EDIT CORPUS -- entirely unread, corpus-wide counts:\n structuredPatch 105,123 real unified diffs:\n {\"oldStart\":143,\"oldLines\":6,\"newStart\":143,\n \"newLines\":14,\"lines\":[...]}\n originalFile 92,313 the pre-edit file content\n oldString 86,085 with newString and replaceAll alongside\n filePath which file each edit touched\n userModified whether the human changed it afterwards\n\npolylogue-cijx grades file trajectories 'observed' -- 'only tool/action-derived\ndeltas' -- and states that 'checkpointed' requires captured pre/post state.\nThe pre-state IS captured, in originalFile, and the deltas ARE structured, in\nstructuredPatch. The tier cijx declares out of reach is sitting in the bytes.\n\nOTHER UNREAD KEYS OF SUBSTANCE (occurrences in the 60-file sample):\n slug 1,500 human-readable agent name (the subagent display\n problem: '5ecdb160-...:agent-af4e' vs 'greedy-\n squishing-hamming')\n message.stop_reason 1,184 terminal state (see the outcome bead)\n message.stop_sequence 1,184\n parentToolUseID 657 the delegation join key (see the delegation bead)\n toolUseID 679\n sourceToolAssistantUUID 143\n usage.cache_creation 664 cache-creation token detail\n message.ttftMs 36 time to first token\n todos / oldTodos / newTodos agent task-list evolution over a session\n thinkingMetadata 34\n permissionMode 33\n hookCount / hookInfos 22\n toolUseResult.sandbox 60\n toolUseResult.filenames / numFiles 46\n requestId 1,171\n userType 2,789\n\nMEASURED NEGATIVE, recorded so nobody re-files it: usage.service_tier looked\nlike the answer to the API-vs-subscription cost question. It is NOT --\n1,651,137 occurrences, every one 'standard'. A constant. Acquiring it would add\nnothing. Check payloads before filing.","acceptance_criteria":"1. Every key in this enumeration is classified read / deliberately-dropped-with-reason / to-acquire, recorded in the Claude Code OriginSpec fidelity declaration rather than an unexplained frozenset. 2. structuredPatch, originalFile and oldString/newString are persisted; cijx's file-trajectory grading rises from observed to checkpointed where they exist, proven on a sample. 3. slug reaches read surfaces so subagent rows carry names. 4. The enumeration is re-runnable and its output committed, so a future wire change surfaces new unread keys instead of hiding them. 5. Report bytes and row counts added per key acquired.","notes":"2026-07-29 (worktree-agent-a6d396610f6c9a165, un-triaged-provider pass: gemini/gemini-cli/antigravity/browser-capture/codex): branch fast-forwarded to b3ae790be (feature/chore/promote-schemas-and-wire-gates), which already carried a huge amount of prior work: claude-code/claude-ai/gemini/gemini-cli/hermes/antigravity all promoted to schema v2, block-metadata routed to session_events for chatgpt/gemini-cli/hermes/browser-capture/codex, and the drift-sentinel 4th classification (KNOWN_FIELD_UNREAD, polylogue/schemas/schema_parser_coverage.py) fully implemented AND wired into the live ingest path (pipeline/services/ingest_worker.py:475-500) -- polylogue-2qx.3's core ask was already done before this session.\n\nRan devtools lab schema parser-diff --min-encountered 1 against the committed (already-promoted) v2 packages for gemini/gemini-cli/antigravity/browser-capture; codex has no v2 (still stale March v1, no x-polylogue-observed-distribution) so its 0-row output is a tool blind spot, not evidence of full coverage -- verified codex frequency directly against ~3,200 real ~/.codex session files instead.\n\nFindings/dispositions:\n- gemini: all \"unread\" rows (runSettings.enable*/environmentMode/responseSchema) are FALSE POSITIVES -- drive.py:_model_config_event stores the whole runSettings dict verbatim (origin_specs.py:625). _polylogue_drive_live_bytes_b64 was a second false positive: read by drive_support_attachments.py, missing from PROVIDER_PARSERS in both devtools/schema_parser_diff.py and schemas/schema_parser_coverage.py -- fixed both maps.\n- gemini-cli: memoryScratchpad.* is a FALSE POSITIVE -- local_agent.py:_gemini_cli_memory_scratchpad_event already stores it verbatim. toolCalls.args.*/resultDisplay.* also verbatim-captured.\n- antigravity/browser-capture: browser-capture's 8 rows were the same PROVIDER_PARSERS gap (browser_capture/models.py missing) -- fixed. antigravity's single row (version, 0 encountered documents) has zero corpus signal either way.\n- codex (real gap, FIXED, commit 0d7a19c47): patch_apply_end.success/.changes (per-file add/update/delete + unified_diff + move_path) was completely unread -- the direct codex analogue of this bead's own structuredPatch finding. Now retained verbatim. turn_context.personality/.summary/.collaboration_mode also newly captured.\n- codex measured-negative: event_msg.memory_citation is null on every sampled record across the full corpus -- a constant.\n- codex to-acquire, deferred: internal_chat_message_metadata_passthrough carries only {turn_id}; needs a ParsedMessage metadata channel plumbed through every codex.py message constructor, left as a named follow-up.\n\nAll codex dispositions recorded in origin_specs.py's codex fidelity_notes.\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: PARTIAL. Field capture landed 2026-07-29 (v46 file_edits schema: structured_patch_json/original_file/old_string/new_string persisted per storage/sqlite/queries/file_edits.py; slug captured in claude/code_parser.py:1050; codex patch_apply diffs fixed commit 0d7a19c47) satisfying much of AC2/AC3. But AC2's specific \"cijx grading rises from observed to checkpointed\" wiring not found (grepped polylogue/insights - no such evidence-tier concept referencing file_edits), and full AC1 classification-recorded-in-fidelity-declaration / AC4 re-runnable committed enumeration not independently verified complete.\nPartial pass, PR #3442 (feature/wire-captured-unread-data). AC3 (slug reaches read surfaces) SATISFIED: sessions.display_name now flows through both the async repository path (Session/SessionSummary domain models + display_title fallback) and the sync ArchiveStore summary path backing find/MCP get(ref) default projection. Live census: 6,585 of 14,717 title_source=unknown Claude Code sessions (44.7%) now surface a real slug-derived title instead of raw id/structural label. AC2's read side (file_edits reachability) done via this PR + polylogue-nua7; the specific cijx observed-to-checkpointed grading wiring remains open (insights/session_commit.py-adjacent, out of this PR's declared surface). AC1/AC4/AC5 not re-verified this pass, per prior notes already partially addressed.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:32Z","created_by":"Sinity","updated_at":"2026-07-31T10:48:46Z","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-1vpm.7","title":"Delegation resolution guesses by count-equality while the provider supplies the exact join key","description":"MECHANISM. delegation_facts_source pairs Task dispatches to child sessions with no join key at all:\n\n pairable AS (\n SELECT dc.parent_session_id FROM dispatch_counts dc\n JOIN child_counts cc ON cc.parent_session_id = dc.parent_session_id\n WHERE dc.n = cc.n) \u003c- count equality is the entire gate\n\nIt counts Task dispatches in the parent (ordered by message_id), counts resolved\nchildren (ordered by observed_at_ms), and if the counts match, pairs them BY\nORDINAL POSITION -- two unrelated orderings assumed to correspond.\n\nRESULT, full scan of 11,692 delegation_facts rows:\n edge_only 5,951 50.9%\n unresolved 2,207 18.9%\n ambiguous 2,041 17.5%\n resolved 1,493 12.8% \u003c- the only complete delegations\n\nWHY IT FAILS ALL-OR-NOTHING: the gate is per parent. One dispatch whose child\nwas not captured makes dc.n != cc.n and EVERY dispatch in that session becomes\nambiguous. One local gap poisons a whole session, which is why the distribution\nis lumpy rather than a smooth partial.\n\nWHY session_links SUCCEEDS AT 97.6% ON THE SAME DATA: links are derived from the\nCHILD side, where the child literally states its parent sessionId. Delegation is\nderived from the PARENT side, where nothing stated which child a dispatch\nproduced -- so a heuristic was invented instead.\n\nTHE KEY EXISTS, TYPED, AND IS DISCARDED. Claude Code progress records carry:\n parentToolUseID -\u003e the dispatching Task tool_use id\n toolUseID, slug, sessionId\nCorpus-wide: 842,819 progress records carry parentToolUseID, referencing 185,982\ndistinct dispatch ids. progress is in _SKIPPED_SIDECAR_RECORD_TYPES.\n\nSecondary keys also present and unused: the child transcript's first record\ncarries agentId, slug, and its first message IS the Task prompt (verified: 1\nmatch against 102 tool_use blocks in the parent -- unique on that sample, NOT\nyet corpus-verified). sourceToolAssistantUUID appears in child records with\nZERO references anywhere in polylogue/sources/.\n\nTHE INVARIANT: join on identity, never on cardinality. Then 'ambiguous' becomes\nunrepresentable -- you either have the key or you don't -- and missing capture\ndegrades per dispatch instead of per session. Heuristics smear uncertainty;\njoins localize absence. An unavoidable gap is one thing; a gap that PROPAGATES\nis the actual defect.","acceptance_criteria":"1. Dispatch-to-child resolution joins on parentToolUseID; no code path pairs by ordinal position or gates on count equality. 2. The 'ambiguous' mapping state is removed from the vocabulary, not merely reduced -- with the key it is not a reachable state. 3. A parent with N dispatches and M\u003cN captured children yields M resolved and N-M unresolved, proven by a fixture; it never yields N ambiguous. 4. Live re-measure of the mapping_state distribution against the 12.8%-resolved baseline. 5. Corpus-wide collision check on any secondary key before it is relied on.","notes":"Filed 2026-07-29. Note the shape: the epistemic vocabulary here (edge_only/unresolved/ambiguous/quarantined, mapped honestly onto WorkEvidenceAssociationState, with an explicit refusal to 'fabricate a one-to-one attempt') is well designed and correctly implemented. It faithfully reports the uncertainty of a heuristic that did not need to exist. Sophisticated epistemology over an avoidable uncertainty is itself the smell -- the distinctions are real but 87% of what they distinguish is self-inflicted.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:22Z","created_by":"Sinity","updated_at":"2026-07-29T04:52:22Z","labels":["area:ingest","area:substrate","delivery:I-analytics-experiments","horizon:mid","lane:analytics-experiments","lane:read-contracts","tech-tree"],"dependencies":[{"issue_id":"polylogue-1vpm.7","depends_on_id":"polylogue-1vpm","type":"parent-child","created_at":"2026-07-29T06:52:21Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-pbuh","title":"Claude Code sidecar records are discarded at parse: 1,172,890 records including titles, PR links, agent names and file snapshots","description":"polylogue/sources/parsers/claude/code_parser.py:87 declares _SKIPPED_SIDECAR_RECORD_TYPES and drops every matching record at parse time. Measured against the real corpus at ~/.claude/projects (rg, single pass, 2026-07-29):\n\n progress 850,678\n attachment 86,055\n queue-operation 60,579\n last-prompt 37,616\n file-history-snapshot 34,132\n permission-mode 25,699\n pr-link 20,702\n mode 20,595\n ai-title 18,422\n bridge-session 13,411\n agent-name 5,001\n ---------\n 1,172,890 records discarded\n\nThese are not noise. Sampled payloads:\n\n ai-title {\"type\":\"ai-title\",\"aiTitle\":\"Recover what was lost\",\"sessionId\":\"a903ee33-...\"}\n agent-name {\"type\":\"agent-name\",\"agentName\":\"orchestration-docs-6np\",\"sessionId\":\"a9468292-...\"}\n pr-link {\"type\":\"pr-link\",\"prNumber\":3126,\n \"prUrl\":\"https://github.com/Sinity/polylogue/pull/3126\",\n \"prRepository\":\"Sinity/polylogue\",\"sessionId\":\"cdaf1c01-...\"}\n bridge-session {\"sessionId\":\"d8c9a340-...\",\"bridgeSessionId\":\"cse_01YHHspKPVi2QYy1na2Cgvos\"}\n file-history-snapshot {\"snapshot\":{\"trackedFileBackups\":{},\"timestamp\":\"...\"}}\n\nWHAT EACH ONE WOULD HAVE SOLVED, all currently pursued by inference instead:\n\n ai-title 18,422 -\u003e the 10,157 UUID-titled Claude Code sessions. The\n provider supplies a human title and it is dropped.\n PARTIAL FIX, MEASURED: in the polylogue project dir,\n only 64 of 520 session files (12.3%) carry an\n ai-title record, distributed 2026-05: 8, 06: 25,\n 07: 31 -- the feature is recent, so older sessions\n have no provider title at all. Un-skipping is\n necessary and NOT sufficient; the residual needs\n synthesis and should be sized per origin before\n anyone claims the title problem is closed.\n agent-name 5,001 -\u003e subagent rows read '5ecdb160-...:agent-af4e' instead\n of 'orchestration-docs-6np'.\n pr-link 20,702 -\u003e structured session-\u003ePR linkage. cijx.1 and its four\n blocked consumers (212.2, xyel, kph, fs1.4) are\n trying to RECONSTRUCT by regex and time-window\n scoring what the provider hands over typed.\n file-history-snapshot -\u003e cijx's 'checkpointed' trajectory grade, the tier\n 34,132 above 'observed'. Captured, discarded.\n bridge-session 13,411 -\u003e cross-session lineage (cse_ ids are Claude Code\n cloud sessions). Relevant to 4ts and nas1.\n attachment 86,055 -\u003e attachment preservation (83u / the #2468 finding).\n\nZero beads mention any of these record types. The only other code references\ntreat them as skip-signals: archive/raw_materialization.py:26-28 classifies a\nraw as a non-session artifact when it contains ONLY these types.\n\nThis is the founding premise inverted. The product exists for comprehensive\ncapture; the parser deletes over a million provider-supplied facts, and several\nopen programs spend inference machinery reconstructing a subset of them.","acceptance_criteria":"1. Every currently-skipped record type is classified as evidence-bearing (parse and persist) or genuinely transient (drop, with the reason recorded in the OriginSpec fidelity declaration -- not in a frozenset with no rationale). 2. ai-title, agent-name, pr-link, bridge-session and file-history-snapshot are persisted as typed evidence, not as opaque blobs. 3. Titles and agent names reach read surfaces; a re-run of 'polylogue find repo:polylogue' shows named rows instead of UUID:agent-suffix rows. 4. pr-link becomes the session-\u003ePR producer, and the four consumer beads are unblocked or re-scoped against it. 5. Coverage is reported per type: records seen, parsed, persisted -- so a future skip is visible rather than silent. 6. Existing raws are reprocessed; report the before/after census for UUID titles and PR links.","notes":"Filed 2026-07-29. Found by reading the parser rather than the beads: the skip list is a bare frozenset with no per-type rationale, and nothing downstream records that the data existed. The operator's framing is the right one -- the whole point was comprehensive capture.\n\nMETHOD NOTE for whoever picks this up: verify each type against the live corpus before acting. The DECISION must be per-type, evidenced, and recorded, not a single unexplained set.\n\nCORRECTION 2026-07-29 -- an earlier draft of this bead guessed that 'progress'\nat 850,678 records was 'plausibly genuine streaming noise and may be correctly\ndropped'. THAT GUESS WAS WRONG, and it is the exact mistake this bead warns\nagainst. progress records carry the DELEGATION JOIN KEY:\n\n {\"type\":\"progress\", \"sessionId\":\"7ff2c7d9-...\",\n \"slug\":\"greedy-squishing-hamming\",\n \"toolUseID\":\"agent_msg_01JXHA4xf6C7ArHEUisioLpz\",\n \"parentToolUseID\":\"toolu_01KbmNk4EJY9h9XvGcRBXj3n\", \u003c- the dispatching\n \"data\":{\"message\":{...}}} Task tool_use id\n\nCorpus-wide: 842,819 progress records carry parentToolUseID, referencing\n185,982 distinct dispatching tool ids. That is the complete, typed,\nprovider-supplied delegation graph -- discarded at parse, while\ndelegation_facts resolves 1,493 of 11,692 dispatches (12.8%) using a\npositional-pairing heuristic gated on count equality.\n\nNo record type in this list may be dismissed without checking its payload.\nSTATUS 2026-07-31 (verified by re-audit, not re-derivation): AC1/AC2/AC3 were\nalready satisfied by PR #3390 \"index v46 wire-evidence batch\" (commit\n5e23e6abf, merged to master before this pass started) -- code_parser.py:106-183\ncarries the per-type evidenced classification comment, _SIDECAR_EVENT_TYPES +\n_sidecar_evidence_payload persist agent-name/pr-link/bridge-session/\nfile-history-snapshot/permission-mode/last-prompt/queue-operation/attachment/\nai-title/custom-title/file-history-delta as typed session_events, progress's\nagent_progress subtype dedups into claude_delegation_progress, and\nai-title/agent-name/custom-title resolve TitleSource.ORIGIN session titles\n(code_parser.py:1466-1509) reaching every ordinary read surface (title was\nalready first-class there).\n\nTHIS PASS closed AC5: code_parser.py now counts, per skipped sidecar record\ntype, records seen vs. actually persisted (a session_event/session_ref/title\noverride/delegation edge), plus a sample of ordinary-path record types\ndropped for carrying no text/blocks -- one bounded claude_parse_coverage\nsession_event per session when either counter is non-empty. Tests:\ntests/unit/sources/test_claude_code_sidecar_evidence.py\n(test_parse_coverage_event_reports_seen_and_persisted_counts,\ntest_parse_coverage_event_absent_when_only_ordinary_messages_parsed).\n\nAC4 REMAINS PARTIALLY OPEN: the pr-link producer is real (session_refs table,\nstorage/sqlite/queries/session_refs.py, wired into\nstorage/repository/archive/sessions.py) but nothing on the CLI/insights/MCP\nsurface reads session_refs yet -- polylogue-cijx.1 and its four dependents\n(212.2/xyel/kph/fs1.4) are not unblocked by this alone; noted directly on\npolylogue-cijx.1. Producer-side work is out of this pass's declared surface\n(parsers/claude, assembly_claude_code.py, providers/claude_code*.py) --\nconsumer wiring is insights/CLI/MCP territory for a follow-up pass.\n\nAC6 REMAINS OPEN AS A MEASURED FACT: PR #3390's body recorded *expected*\npost-rebuild numbers, not an actual before/after UUID-title/PR-link census.\nWhether the v46 SEMANTIC_REPARSE rebuild has run against the real corpus\nsince merge, and what the resulting title/pr-link counts are, is an\noperational question against the live archive (not reproducible from a\nsandboxed worktree) -- someone with archive access should run\n`polylogue find repo:polylogue` (or an aggregate query) before/after and\nrecord the actual numbers here.\n\nAC4 RESOLVED 2026-07-31 (this pass, worktree agent-aaffe89902b670d4b). Sibling PR #3425 (fix/insights/session-commit-typed-evidence) landed and was merged this pass (5525446a2): build_correlation_result now consumes session_refs (typed pull_request/issue refs) and claude_bridge_session-derived bridge ids as authoritative evidence, falling back to regex/time-window/file-overlap heuristics only when no typed evidence exists, and surfacing disagreements instead of silently preferring one signal.\n\nRESIDUAL VERIFICATION DONE THIS PASS: confirmed the linkage is reachable from an actual CLI surface, not just an internal insight function -- `find id:\u003csession\u003e then read --view correlation --format json` (backed by polylogue.insights.correlation_view.run_correlation_view + Polylogue.session_correlation_payload). Found and fixed a genuine pre-existing bug this exercise exposed: _enrich_with_github_api (correlation_view.py) constructed SessionCorrelationResult at runtime while only importing it under TYPE_CHECKING (present since ac84f734f, predates #3425) -- every call with the default github_api=True and any issue/PR ref present raised NameError, so the surface had never actually been exercised end-to-end with real refs before this pass despite existing since #1842. Fixed (import moved to runtime scope) + regression test added (test_run_correlation_view_github_enrichment_does_not_crash). Verified live against a real session in /realm/db/polylogue/index.db (read-only): the fixed command returns typed pr_refs with source=typed_session_ref (e.g. Sinity/sinex#528) plus a disagreements list contrasting typed vs regex-found PR numbers -- exactly the \"reachable from a query/CLI surface\" bar AC4 asks for.\n\nDISPOSITION: AC4 satisfied. The pr-link producer (session_refs, index v46/#3390) plus this pass's reader wiring (#3425) together make typed session-\u003ePR linkage query-reachable. cijx.1 and its four dependents (212.2/xyel/kph/fs1.4) are updated separately with their own disposition -- none of the four are closed by this alone, since each needs its own concrete deliverable (demo build, CI hook, CLI/report regen) beyond \"the data is now readable\", consistent with cijx.1's own 2026-07-31 note. AC6 (before/after UUID-title/PR-link census) is untouched by this pass -- out of this gap's declared scope (pbuh AC4 specifically), still open.\n","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:10Z","created_by":"Sinity","updated_at":"2026-07-31T06:06:21Z","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-pbuh","title":"Claude Code sidecar records are discarded at parse: 1,172,890 records including titles, PR links, agent names and file snapshots","description":"polylogue/sources/parsers/claude/code_parser.py:87 declares _SKIPPED_SIDECAR_RECORD_TYPES and drops every matching record at parse time. Measured against the real corpus at ~/.claude/projects (rg, single pass, 2026-07-29):\n\n progress 850,678\n attachment 86,055\n queue-operation 60,579\n last-prompt 37,616\n file-history-snapshot 34,132\n permission-mode 25,699\n pr-link 20,702\n mode 20,595\n ai-title 18,422\n bridge-session 13,411\n agent-name 5,001\n ---------\n 1,172,890 records discarded\n\nThese are not noise. Sampled payloads:\n\n ai-title {\"type\":\"ai-title\",\"aiTitle\":\"Recover what was lost\",\"sessionId\":\"a903ee33-...\"}\n agent-name {\"type\":\"agent-name\",\"agentName\":\"orchestration-docs-6np\",\"sessionId\":\"a9468292-...\"}\n pr-link {\"type\":\"pr-link\",\"prNumber\":3126,\n \"prUrl\":\"https://github.com/Sinity/polylogue/pull/3126\",\n \"prRepository\":\"Sinity/polylogue\",\"sessionId\":\"cdaf1c01-...\"}\n bridge-session {\"sessionId\":\"d8c9a340-...\",\"bridgeSessionId\":\"cse_01YHHspKPVi2QYy1na2Cgvos\"}\n file-history-snapshot {\"snapshot\":{\"trackedFileBackups\":{},\"timestamp\":\"...\"}}\n\nWHAT EACH ONE WOULD HAVE SOLVED, all currently pursued by inference instead:\n\n ai-title 18,422 -\u003e the 10,157 UUID-titled Claude Code sessions. The\n provider supplies a human title and it is dropped.\n PARTIAL FIX, MEASURED: in the polylogue project dir,\n only 64 of 520 session files (12.3%) carry an\n ai-title record, distributed 2026-05: 8, 06: 25,\n 07: 31 -- the feature is recent, so older sessions\n have no provider title at all. Un-skipping is\n necessary and NOT sufficient; the residual needs\n synthesis and should be sized per origin before\n anyone claims the title problem is closed.\n agent-name 5,001 -\u003e subagent rows read '5ecdb160-...:agent-af4e' instead\n of 'orchestration-docs-6np'.\n pr-link 20,702 -\u003e structured session-\u003ePR linkage. cijx.1 and its four\n blocked consumers (212.2, xyel, kph, fs1.4) are\n trying to RECONSTRUCT by regex and time-window\n scoring what the provider hands over typed.\n file-history-snapshot -\u003e cijx's 'checkpointed' trajectory grade, the tier\n 34,132 above 'observed'. Captured, discarded.\n bridge-session 13,411 -\u003e cross-session lineage (cse_ ids are Claude Code\n cloud sessions). Relevant to 4ts and nas1.\n attachment 86,055 -\u003e attachment preservation (83u / the #2468 finding).\n\nZero beads mention any of these record types. The only other code references\ntreat them as skip-signals: archive/raw_materialization.py:26-28 classifies a\nraw as a non-session artifact when it contains ONLY these types.\n\nThis is the founding premise inverted. The product exists for comprehensive\ncapture; the parser deletes over a million provider-supplied facts, and several\nopen programs spend inference machinery reconstructing a subset of them.","acceptance_criteria":"1. Every currently-skipped record type is classified as evidence-bearing (parse and persist) or genuinely transient (drop, with the reason recorded in the OriginSpec fidelity declaration -- not in a frozenset with no rationale). 2. ai-title, agent-name, pr-link, bridge-session and file-history-snapshot are persisted as typed evidence, not as opaque blobs. 3. Titles and agent names reach read surfaces; a re-run of 'polylogue find repo:polylogue' shows named rows instead of UUID:agent-suffix rows. 4. pr-link becomes the session-\u003ePR producer, and the four consumer beads are unblocked or re-scoped against it. 5. Coverage is reported per type: records seen, parsed, persisted -- so a future skip is visible rather than silent. 6. Existing raws are reprocessed; report the before/after census for UUID titles and PR links.","notes":"Filed 2026-07-29. Found by reading the parser rather than the beads: the skip list is a bare frozenset with no per-type rationale, and nothing downstream records that the data existed. The operator's framing is the right one -- the whole point was comprehensive capture.\n\nMETHOD NOTE for whoever picks this up: verify each type against the live corpus before acting. The DECISION must be per-type, evidenced, and recorded, not a single unexplained set.\n\nCORRECTION 2026-07-29 -- an earlier draft of this bead guessed that 'progress'\nat 850,678 records was 'plausibly genuine streaming noise and may be correctly\ndropped'. THAT GUESS WAS WRONG, and it is the exact mistake this bead warns\nagainst. progress records carry the DELEGATION JOIN KEY:\n\n {\"type\":\"progress\", \"sessionId\":\"7ff2c7d9-...\",\n \"slug\":\"greedy-squishing-hamming\",\n \"toolUseID\":\"agent_msg_01JXHA4xf6C7ArHEUisioLpz\",\n \"parentToolUseID\":\"toolu_01KbmNk4EJY9h9XvGcRBXj3n\", \u003c- the dispatching\n \"data\":{\"message\":{...}}} Task tool_use id\n\nCorpus-wide: 842,819 progress records carry parentToolUseID, referencing\n185,982 distinct dispatching tool ids. That is the complete, typed,\nprovider-supplied delegation graph -- discarded at parse, while\ndelegation_facts resolves 1,493 of 11,692 dispatches (12.8%) using a\npositional-pairing heuristic gated on count equality.\n\nNo record type in this list may be dismissed without checking its payload.\nSTATUS 2026-07-31 (verified by re-audit, not re-derivation): AC1/AC2/AC3 were\nalready satisfied by PR #3390 \"index v46 wire-evidence batch\" (commit\n5e23e6abf, merged to master before this pass started) -- code_parser.py:106-183\ncarries the per-type evidenced classification comment, _SIDECAR_EVENT_TYPES +\n_sidecar_evidence_payload persist agent-name/pr-link/bridge-session/\nfile-history-snapshot/permission-mode/last-prompt/queue-operation/attachment/\nai-title/custom-title/file-history-delta as typed session_events, progress's\nagent_progress subtype dedups into claude_delegation_progress, and\nai-title/agent-name/custom-title resolve TitleSource.ORIGIN session titles\n(code_parser.py:1466-1509) reaching every ordinary read surface (title was\nalready first-class there).\n\nTHIS PASS closed AC5: code_parser.py now counts, per skipped sidecar record\ntype, records seen vs. actually persisted (a session_event/session_ref/title\noverride/delegation edge), plus a sample of ordinary-path record types\ndropped for carrying no text/blocks -- one bounded claude_parse_coverage\nsession_event per session when either counter is non-empty. Tests:\ntests/unit/sources/test_claude_code_sidecar_evidence.py\n(test_parse_coverage_event_reports_seen_and_persisted_counts,\ntest_parse_coverage_event_absent_when_only_ordinary_messages_parsed).\n\nAC4 REMAINS PARTIALLY OPEN: the pr-link producer is real (session_refs table,\nstorage/sqlite/queries/session_refs.py, wired into\nstorage/repository/archive/sessions.py) but nothing on the CLI/insights/MCP\nsurface reads session_refs yet -- polylogue-cijx.1 and its four dependents\n(212.2/xyel/kph/fs1.4) are not unblocked by this alone; noted directly on\npolylogue-cijx.1. Producer-side work is out of this pass's declared surface\n(parsers/claude, assembly_claude_code.py, providers/claude_code*.py) --\nconsumer wiring is insights/CLI/MCP territory for a follow-up pass.\n\nAC6 REMAINS OPEN AS A MEASURED FACT: PR #3390's body recorded *expected*\npost-rebuild numbers, not an actual before/after UUID-title/PR-link census.\nWhether the v46 SEMANTIC_REPARSE rebuild has run against the real corpus\nsince merge, and what the resulting title/pr-link counts are, is an\noperational question against the live archive (not reproducible from a\nsandboxed worktree) -- someone with archive access should run\n`polylogue find repo:polylogue` (or an aggregate query) before/after and\nrecord the actual numbers here.\n\nAC4 RESOLVED 2026-07-31 (this pass, worktree agent-aaffe89902b670d4b). Sibling PR #3425 (fix/insights/session-commit-typed-evidence) landed and was merged this pass (5525446a2): build_correlation_result now consumes session_refs (typed pull_request/issue refs) and claude_bridge_session-derived bridge ids as authoritative evidence, falling back to regex/time-window/file-overlap heuristics only when no typed evidence exists, and surfacing disagreements instead of silently preferring one signal.\n\nRESIDUAL VERIFICATION DONE THIS PASS: confirmed the linkage is reachable from an actual CLI surface, not just an internal insight function -- `find id:\u003csession\u003e then read --view correlation --format json` (backed by polylogue.insights.correlation_view.run_correlation_view + Polylogue.session_correlation_payload). Found and fixed a genuine pre-existing bug this exercise exposed: _enrich_with_github_api (correlation_view.py) constructed SessionCorrelationResult at runtime while only importing it under TYPE_CHECKING (present since ac84f734f, predates #3425) -- every call with the default github_api=True and any issue/PR ref present raised NameError, so the surface had never actually been exercised end-to-end with real refs before this pass despite existing since #1842. Fixed (import moved to runtime scope) + regression test added (test_run_correlation_view_github_enrichment_does_not_crash). Verified live against a real session in /realm/db/polylogue/index.db (read-only): the fixed command returns typed pr_refs with source=typed_session_ref (e.g. Sinity/sinex#528) plus a disagreements list contrasting typed vs regex-found PR numbers -- exactly the \"reachable from a query/CLI surface\" bar AC4 asks for.\n\nDISPOSITION: AC4 satisfied. The pr-link producer (session_refs, index v46/#3390) plus this pass's reader wiring (#3425) together make typed session-\u003ePR linkage query-reachable. cijx.1 and its four dependents (212.2/xyel/kph/fs1.4) are updated separately with their own disposition -- none of the four are closed by this alone, since each needs its own concrete deliverable (demo build, CI hook, CLI/report regen) beyond \"the data is now readable\", consistent with cijx.1's own 2026-07-31 note. AC6 (before/after UUID-title/PR-link census) is untouched by this pass -- out of this gap's declared scope (pbuh AC4 specifically), still open.\n\nAC6 live census done, PR #3442 (feature/wire-captured-unread-data), read-only against /realm/db/polylogue/index.db: 16,420 Claude Code sessions total, 14,717 title_source=unknown (raw-id/structural-label fallback), 7,088 have a captured display_name, session_events claude_pr_link=19,140 rows, file_edits=76,272 rows, session_agent_policies=402,879 rows, session_refs=19,024 rows (167 distinct sessions). No pre-fix baseline exists to diff against (the parser fix landed in an earlier merged PR), so this is the current-state 'after' census, not a true before/after diff. This PR also wires the display_name fallback that converts 6,585 of those 14,717 unknown-title sessions to a real slug-derived title (see polylogue-cgfy note).","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:10Z","created_by":"Sinity","updated_at":"2026-07-31T10:48:57Z","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-31r1","title":"Hook events ingested as standalone sessions inflate archive ~4.7x (65.7K empty shells)","design":"Root cause (airtight, 2026-07-22): polylogue/sources/hooks.py:_persist_record drains each spooled hook event (~/.local/share/polylogue/hooks/pending/\u003cid\u003e.json: PreToolUse/PostToolUse/UserPromptSubmit/SessionStart/...) and calls write_source_raw_session with origin=codex-session|claude-code-session, minting a full raw_sessions row per hook -\u003e the materializer turns each into an EMPTY standalone index session (0 messages). Each hook is double-recorded: correctly as a raw_hook_events row carrying session_native_id (table indexed (origin,session_native_id,observed_at_ms) for attach-to-session), AND wrongly as a raw_sessions row.\n\nScale on live archive /realm/db/polylogue: index sessions=83,279 but only 17,553 have content; 65,727 empty shells = codex 35,233 + claude-code 30,488. source_path LIKE '%/hooks/%' raws: codex 35,216 + claude-code 29,679 + hermes 1 = 64,896 = raw_hook_events row count. Real conversations ~17.5K (matches operator memory of ~16K). raw_hook_events has NO FK to raw_sessions, so hooks can persist without minting sessions.\n\nAlso inflates the raw-authority reconciler backlog (hjpx/lkrc/t93b) which churns over hook raws mixed with real session raws.\n\nFIX (operator decisions 2026-07-22): (1) code: add write_source_hook_event writing raw_hook_events + retained blob_ref, NO raw_sessions row; _persist_record uses it; materializer guard so hook-origin raws never become sessions; covers codex/claude/hermes. (2) constructive: materialize raw_hook_events into an index read-model attached to sessions via session_native_id (index tier rebuildable) + read surfaces (MCP/CLI). Operator: hooks are always within a session; link them. (3) retroactive repair WITHOUT full reindex: delete 64,896 hook raw_sessions rows from source.db (durable; backup at /realm/staging/polylogue-sqlite/recovery/t93b-preflight-20260722-durable) + 64,896 empty index session rows (zero messages/blocks/FTS -\u003e tiny blast radius, targeted DELETE). keep raw_hook_events+blobs. (4) re-census raw-authority; deploy #3261 (whale budget fix, merged) so frontier repair doesn't abort on 298MB whale.\n\nDesign doc: .agent/scratch/hook-session-inflation-2026-07-22.md. Verification: sessions count ~17.5K post-repair; every hook event still resolves to its session via session_native_id; no message/block/FTS row dropped.","status":"closed","priority":0,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-22T12:42:26Z","created_by":"Sinity","updated_at":"2026-07-27T02:59:21Z","started_at":"2026-07-22T14:32:39Z","closed_at":"2026-07-27T02:59:21Z","close_reason":"Fast-follow scope (session\u003c-\u003ehooks index read-model + read surfaces) landed via PR #3295 - ArchiveStore.hook_event_summary_for_session() read model + new 'hooks' CLI read-view. Original inflation bug fixed weeks ago (PR #3265, verified live). Both halves of this bead now complete.","comments":[{"id":"019f8a3e-3243-78be-9150-ea16cdde6316","issue_id":"polylogue-31r1","author":"Sinity","text":"Progress 2026-07-22: code fix + retroactive repair MERGED as PR #3265 (master 998188b1a). Both Codex P1 review findings fixed (verified-hook-row predicate via raw_hook_events.source_path; daemon offline-guard on apply). Live dry-run confirms scope: 64,896 hook raw_sessions, 64,895 empty index shells, raw_hook_events + 64,896 blobs retained.\n\nDeploying now: sinnix flake bumped to 998188b1a (commit 34a7a41), switch building. Next: stop polylogued -\u003e run repair_hook_session_inflation(dry_run=False) -\u003e restart -\u003e verify sessions count ~17.5K. Then session\u003c-\u003ehooks read-model + surfaces (fast-follow) and re-census raw-authority for hjpx/lkrc/t93b.","created_at":"2026-07-22T14:32:40Z"},{"id":"019f8a68-335c-7e94-bf69-0e7c18bed99a","issue_id":"polylogue-31r1","author":"Sinity","text":"DE-INFLATION COMPLETE + VERIFIED LIVE (2026-07-22).\n\nDeployed 998188b1a to daemon (sinnix 34a7a41, switch OK; daemon restarted on new store, confirmed has write_hook_event). Stopped daemon, ran repair_hook_session_inflation(dry_run=False), restarted.\n\nLive before -\u003e after:\n- index sessions: 83,286 -\u003e 18,391 (17,559 real + 832 genuinely-empty non-hook, correctly preserved)\n- raw_sessions: 101,347 -\u003e 36,451 (-64,896 hook raws)\n- raw_hook_events: 64,896 -\u003e 64,896 (all evidence retained)\n- hook blobs: 64,896 retained\n- hook raw_sessions after repair: 0; still 0 after daemon restart+drain -\u003e no re-inflation, going-forward fix confirmed live.\n\nRoot cause fully characterized: 64,896 hook events came from just 64 real agent sessions (one codex session fired 13,447 Pre/PostToolUse hooks). Each hook had become its own empty \"session\". Now 64,896 evidence rows attached to their 64 parent sessions via session_native_id.\n\nREMAINING (fast-follow, this bead stays open): session\u003c-\u003ehooks index read-model + read surfaces (MCP/CLI) so hooks are queryable as session evidence. Separate: raw-authority convergence (hjpx/lkrc/t93b) still degraded on pre-existing stale-plan blocker f196aac0 — unaffected by this work.","created_at":"2026-07-22T15:18:33Z"},{"id":"019f8ac1-a0fc-7cdb-84a2-7b3fa2d1be2e","issue_id":"polylogue-31r1","author":"Sinity","text":"INCIDENT + FIX 2026-07-22: first daemon convergence pass after the live de-inflation threw RuntimeError(\"duplicate strategy did not reach its typed terminal postcondition\"). Cause: the repair deleted hook raw_sessions but raw_authority_plans/blockers/census reference raws by JSON string (no FK), leaving 64,895 orphaned frontier plans. Daemon caught it (0 restarts), stopped it, verified clean rollback of an over-slow first cleanup attempt.\n\nFix PR #3266: prune purely-orphaned authority plans+children in the repair; set-based identification (0.3s vs \u003e1h correlated) + temp plan_id indexes for FK-restrict/IN deletes. Live: 64,895 orphans pruned (plans 84,042-\u003e19,147, blockers 70,887-\u003e5,992, census_plans 405,234-\u003e275,444, census_post_plans 323,877-\u003e258,982), 0 remain, daemon restarted 0 tracebacks in 8min. Confirms hook raws were also flooding raw-authority (~65K plan/blocker noise) -\u003e should lighten hjpx/lkrc/t93b convergence.","created_at":"2026-07-22T16:56:13Z"}],"dependency_count":0,"dependent_count":0,"comment_count":3} {"_type":"issue","id":"polylogue-m6tp","title":"Daemon needs an explicit bulk-restore mode: trickle conveyor is structurally wrong for large backlogs","design":"Lesson from the 2026-07-18/19 restore: the conveyor (bounded 16/64-component passes, per-pass candidate recomputation over 100K rows, writer interleaving with catch-up walk) is designed for steady-state trickle and turned ~1h of parse work into a weeks-scale projection; census went net-NEGATIVE while the walk minted new pending raws. The correct bulk path existed all along (ops maintenance rebuild-index: single resumable transaction, blue-green generation, full envelope, one census+replay sweep) but nothing routes to it automatically. Direction: when raw-materialization candidate count exceeds a threshold (e.g. \u003e2000 raws or \u003e2GiB pending), the daemon should (a) surface a loud status/journal recommendation to run the bulk rebuild, or (b) run the generation-based bulk path itself as a dedicated maintenance task with the watcher paused, instead of grinding trickle passes. Also fold in: pause/dedupe interaction with live walk (frozen source snapshot requirement), and the restart-required story. Related: polylogue-p0pw (pool), polylogue-nh44 (newest-only census), polylogue-fqp0 (hash pipeline), polylogue-oikv (replay commit batching).","acceptance_criteria":"Design decision recorded; daemon detects bulk-scale backlog and either routes to or loudly recommends the bulk path; trickle conveyor never silently grinds a weeks-scale backlog again; test covers threshold behavior.","notes":"2026-07-29 (polylogue-623q measurement lane): deprioritized per operator direction -- 623q's parse-vs-apply measurement is the input to the imminent real-rebuild decision, this bead is not. Recording status so it isn't re-litigated blind next session.\n\nVerified live: the structural pieces this bead's own audit called out as still gated are NOT gated anymore on this branch -- daemon_bulk_rebuild_routing and daemon_parse_stage_split config flags are both GONE (grep confirms no matches in config.py); daemon/cli.py:755 _maybe_route_daemon_bulk_rebuild is explicitly unconditional now (\"Unconditional. This was gated behind a daemon_bulk_rebuild_routing config flag...\"). The driving loop (_periodic_raw_materialization_convergence, daemon/cli.py:828+) bursts through an in-flight bulk-rebuild transaction at _RAW_MATERIALIZATION_BACKLOG_BURST_PAUSE_SECONDS cadence (~1s) rather than the outer 30s interval, and only falls back to the slow interval on a swallowed pass failure -- i.e. the \"88%/69% idle wall-clock between hand-resumes\" failure mode this bead documents cannot recur when the daemon is live and driving it, since there's no more operator-resume step in that path.\n\nSeparately, and independent of the daemon: the offline `ops maintenance rebuild-index` CLI processes exactly ONE bounded page (raw_batch_size, default 500) per invocation and returns \"paused\"/\"deferred\" if page.has_more -- it does NOT loop internally. Run bare with defaults against a 41k-raw corpus, that's ~83 manual/scripted re-invocations, i.e. the exact same operator-idle failure mode this bead describes, but via the CLI path rather than the daemon path. Cheap, no-code-change mitigation available today: pass --raw-batch-size large enough to cover the whole corpus in one page (e.g. 50000) so it runs straight through to promotion in a single process invocation -- this is what polylogue-623q's own benchmark did (selected_raw_ids covering the whole sample corpus, one call). Worth stating explicitly before today's real rebuild is invoked.\n\nRemaining real gap per this bead's own notes: item 4 (persistent in-daemon backlog iterator replacing per-pass candidate requery) is efficiency, not correctness, and is already tracked under 4jsk (P3). Not attempted here -- out of scope for a measurement task, and 623q's finding (the single writer, not orchestration pacing, is the dominant cost) means this item would not move the needle on the imminent rebuild's wall-clock even if done.","status":"open","priority":0,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-19T01:33:35Z","created_by":"Sinity","updated_at":"2026-07-29T20:13:37Z","labels":["lane:daemon-surface"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-p0pw","title":"Process-pool forkserver deadlocks in production parse path: zero workers ever spawn","design":"Evidence (2026-07-19 03:00): CLI `ops maintenance rebuild-index` sat 17+ minutes at 16% CPU, zero index-generation growth. py-spy: parent idle in as_completed (_parse_retained_raws revision_backfill.py:719); the only children were the multiprocessing resource-tracker and the forkserver itself, both idle — no pool worker was EVER spawned. Killing and resuming the same transaction with POLYLOGUE_INGEST_PARSE_WORKERS=1 (sequential escape hatch) went to 97% CPU immediately and the generation resumed growing. Same pathology long documented on this host for testmon xdist (bd memory devtools-verify-testmon-forkserver-deadlock). Root: polylogue/pipeline/services/process_pool.py process_pool_context() prefers forkserver whenever available. Fix direction: use spawn (still safe for multi-threaded parents, slower per-worker startup but workers are long-lived here), or diagnose why forkserver never services spawn requests under a threaded asyncio parent (as_completed caller runs on an executor thread). Must also audit the daemon census path (#3122 wired the same helper into polylogued at ingest_workers=cpu-1): daemon census passes were observed parsing large payloads inline (size-aware dispatch), but any pool-eligible small-payload batch may hang or silently serialize the same way.","acceptance_criteria":"Reproduce or conclusively explain the forkserver no-worker deadlock; switch process_pool_context to a start method that demonstrably spawns workers on this host under a threaded parent; regression test that a pool dispatch from a worker thread completes; verify daemon census throughput with pooling active; remove/keep the workers=1 escape hatch documented.","notes":"2026-07-19 03:30 repro results: minimal repro (asyncio-thread -\u003e ProcessPoolExecutor(forkserver) -\u003e as_completed, plain function) PASSES on this host in 0.2s — the deadlock is NOT environmental; it is polylogue-specific state. Sharpened evidence from the stuck run: the forkserver process WAS in its serve loop (select at forkserver.py:231), resource tracker alive, yet ZERO workers were ever spawned and the parent executor never completed a future. Suspect surface (in order): (1) pool initializer _initialize_worker_logging -\u003e configure_logging importing polylogue inside spawned worker; (2) forkserver preload of __main__ (cmdline showed main_path=.venv/bin/polylogue) re-importing the whole CLI in the forkserver at boot; (3) executor manager thread wedged in the parent (a Thread was parked in selectors select). Repro script: /realm/tmp/claude-code/claude-1000/-realm-project-polylogue/af12164b-a2fc-42cb-a548-22277c0875a2/scratchpad/forkserver_repro.py — next step is to extend it to use polylogue process_pool_executor() verbatim, then add the real initializer, then real submission payloads, bisecting which ingredient hangs.\n2026-07-19 04:00 bisect step (b) result: running polylogue process_pool_executor() from a thread under stdin exposed the mechanism — forkserver PRELOADS __main__ via runpy.run_path(sys.argv[0], run_name=__mp_main__) (observed FileNotFoundError for \u003cstdin\u003e crashing the forkserver at boot -\u003e EOFError in parent). In the real CLI, main_path=.venv/bin/polylogue, so the ENTIRE polylogue CLI import graph executes inside the forkserver process at pool creation. Any thread started or lock acquired during that import is inherited (in locked/running state) by every forked worker -\u003e classic fork-of-threaded-process deadlock, consistent with the observed zero-workers hang while the forkserver sat in its serve loop. Note for the fix: spawn ALSO re-imports __main__ per worker (slow ~1-2s/worker startup with the full CLI import, but no inherited-lock hazard). Options: (a) spawn (safe, pay startup once per long-lived worker); (b) forkserver with set_forkserver_preload([]) — but stdlib preloads __main__ unconditionally via main_path... verify whether multiprocessing.spawn.set_executable / context.set_forkserver_preload can suppress __main__ preload; (c) audit what the CLI import graph starts (threads at import time is itself a smell worth fixing). Repro next step for the lane: run the same test from a real script file so main_path resolves, confirm hang, then bisect the import graph for thread/lock creation.\n2026-07-19 04:10: repro relocated to a durable path: /realm/project/polylogue/.agent/scratch/warroom-2026-07-17/forkserver_repro.py (the /realm/tmp scratchpad copy may be cleaned). Lane worktree pre-created: /realm/worktrees/polylogue-lane-h-pool (branch feature/perf/process-pool-spawn from 86ca3287b).\n2026-07-19 lane H: bisect step (c)+(d) result — REFUTES the leading hypothesis\nfrom the prior session. Extended repro\n(.agent/scratch/warroom-2026-07-17/forkserver_repro.py sibling, run as a real\nscript file so sys.argv[0] resolves like production main_path): top-level\n`from polylogue.cli import main` (byte-identical to .venv/bin/polylogue's\nentry-point shape) followed by dispatching process_pool_executor() from a\nworker thread, under both forkserver and spawn contexts. Result: BOTH\ncomplete in 0.6s — no hang. So \"the CLI import graph alone creates a\nthread/lock that forkserver's worker-fork inherits\" does not reproduce\nsynthetically when isolated to import+dispatch. The exact trigger inside the\nproduction forkserver preload (which DID visibly hang: forkserver alive in\nits serve loop, zero workers ever spawned, parent parked forever in\nas_completed at revision_backfill.py:719) remains unconfirmed by a\nstandalone repro; likely needs live-process instrumentation (e.g. py-spy\nagainst a real ops maintenance rebuild-index run) to pin exactly, which is\nout of the ~90min bisect timebox for this lane.\n\nApplied fix per the lane brief's explicit fallback (\"otherwise just switch\nto spawn and delete nothing else\"): process_pool_context() now\nunconditionally returns spawn, never forkserver. This is engineering-sound\nindependent of pinning the exact trigger: spawn reruns __main__ fresh per\nworker instead of forking one shared preloaded process, which structurally\neliminates the whole class of inherited-thread/lock hazards forkserver is\nexposed to (not just the specific one hypothesized). Cost is ~1-2s import\nper worker, acceptable since pool workers here are long-lived and reused\nacross many parse tasks (not short bursts).\n\nLanded: polylogue/pipeline/services/process_pool.py (spawn unconditional,\ndocstring explains why) + tests/unit/pipeline/test_process_pool.py (new\ntest_process_pool_context_is_spawn pins the exact start method rather than\njust excluding fork; new\ntest_process_pool_dispatch_from_worker_thread_completes dispatches 8 tasks\nacross 4 workers from a daemon thread with a 40s join bound + pytest\ntimeout(45), mirroring the asyncio-thread -\u003e pool -\u003e as_completed\nproduction shape). Both pass locally (devtools test\ntests/unit/pipeline/test_process_pool.py: 4 passed in 5.10s). Note: this\nregression test does NOT reproduce the hang pre-fix either (consistent with\nthe synthetic-repro gap above) — it is a forward-looking guard against ever\nreintroducing a hanging start-method config, not a proof the pre-fix code\nwould fail it. Honesty note per AC: \"regression test that a pool dispatch\nfrom a worker thread completes\" is satisfied; \"reproduce or conclusively\nexplain the forkserver no-worker deadlock\" is only partially satisfied —\nexplained mechanism (forkserver forks every worker from one preloaded\nprocess; production main_path preloads the whole CLI graph) but not\nconclusively reproduced or pinned to one exact statement/import.\n\nAlso: mid-session process error caught and corrected — an errant `cd\n/realm/project/polylogue \u0026\u0026 ...` left the shell cwd on the main checkout\nacross later commands, so the first commit attempt landed on master there\n(8672f9768). Recovered cleanly: cherry-picked the commit onto\nfeature/perf/process-pool-spawn in the correct worktree\n(/realm/worktrees/polylogue-lane-h-pool, now 07b7835b2), then `git fetch`\n+ `git reset --hard origin/master` in the main checkout to restore it to\nclean origin state. No data lost, no other lanes' work touched (verified\ngit status was clean before the reset). Main checkout confirmed back at\n86ca3287b matching origin/master.\n\nNext: task 3 (daemon census pooling-in-production audit, report only) and\nverify + PR.\n2026-07-19 lane H: daemon census pooling-in-production audit (AC item 4, report only).\n\nAnswer: NO, the ambient/periodic daemon convergence pool has never\nactivated in production, and the #3122-wired census pool has only ever run\nvia direct CLI invocation, never through the live daemon process.\n\nEvidence:\n1. DaemonConverger.start() logs \"converger: started with %d worker(s)\"\n when _has_cpu_bound_stage() is True, else \"started without worker\n pool\". `journalctl --since -60days | grep \"converger: started\"` shows\n ONLY \"started without worker pool\" — every polylogued startup in the\n observed window (30+ restarts across 2026-07-16..19), zero exceptions.\n Root cause confirmed in source: every ConvergenceStage definition in\n daemon/convergence_stages.py sets cpu_bound=False (5/5 stages: fts,\n embed, claude_workflow, insights, standing-queries) — none is marked\n CPU-bound, so DaemonConverger._executor is never created and the\n periodic ambient loop never pools anything.\n2. The #3122-wired pooled census/replay path (revision_backfill.py\n _parse_retained_raws, reached via maintenance/replay.py -\u003e\n rebuild_index_from_source) IS reachable from inside a live polylogued\n process via the HTTP `--daemon` bridge (daemon/http.py:5276-5286,\n DaemonWriteThreadBridge.run_sync) -- but `journalctl --since -60days`\n shows every `ops maintenance rebuild-index` invocation on this host was\n a direct CLI systemd-run unit (`polylogue ops maintenance\n rebuild-index ...`), never with `--daemon`. So the daemon-HTTP-bridged\n variant has zero production exercise to date; all real runs (and the\n one that hung) went through the plain CLI process directly.\n3. Commit a53785b10 (#3122, merged 2026-07-18 19:26) is the commit that\n FIRST wired ingest_workers through to actual use in\n maintenance/replay.py -- before it, the parameter was accepted and\n immediately `del`eted, so the pooled dispatch branch in\n _parse_retained_raws was dead code on the CLI rebuild-index path.\n The forkserver hang was discovered ~8h after that merge (2026-07-19\n 03:00), on what was effectively the first real heavy exercise of the\n newly-activated pool. This fully explains why the deadlock surfaced\n now rather than being a long-standing dormant bug: the code path had\n never run for real before #3122 activated it.\n\nConclusion for AC \"verify daemon census throughput with pooling active\":\nthere is no production daemon-census throughput to measure yet -- the\npooled path has only run via direct CLI so far. Post-fix (spawn), the\nCLI-direct throughput is the throughput that matters today; the\ndaemon-HTTP-bridge variant and DaemonConverger's ambient cpu_bound pool\nare both currently unexercised/dormant in this codebase, not because\nthey're broken but because nothing marks a convergence stage cpu_bound\nand no HTTP client has used --daemon. Neither is in this bead's scope to\nactivate.\n\nSide finding filed as new tracked debt (out of this bead's scope --\nprocess_pool.py only): polylogue-7saq -- archive_ingest.py's\nparse_sources_archive() builds its ProcessPoolExecutor directly\n(concurrent.futures import, no mp_context), bypassing\nprocess_pool_context() entirely, so it uses the platform default start\nmethod (fork on this host/Python 3.13) -- a strictly worse hazard than the\nforkserver issue since raw fork() of a live async process is\nunconditionally unsafe if any other thread holds a lock at fork time.\nCurrently reached only by the public async API facade\n(Polylogue.parse_sources()/parse_file()) and demo seeding, not by the live\ndaemon's normal ingest ticks (those already go through the safe\nprocess_pool_executor() helper in ingest_batch/_core.py) or the standard\n`polylogue import` CLI flow (stages to daemon instead). Lower urgency than\np0pw was, but a real latent bug for any future caller.\nPR #3143 opened: https://github.com/Sinity/polylogue/pull/3143 (feature/perf/process-pool-spawn -\u003e master). Verification: devtools test tests/unit/pipeline/ -k process_pool (7 passed), devtools verify --quick (16/16 steps green). Rebased cleanly onto latest master after resolving a .beads/issues.jsonl rebase conflict (took origin's side entire -- verified it was a strict superset of my commit's older snapshot, per repo's documented bd-conflict procedure).\nPR #3143 merged: 5e794acbde955985fa7ca7296d6aed8a078abe4d. All CI green (CircleCI quick-gate pass, GitGuardian pass; CodeRabbit + Codex review both rate-limited, no findings to triage). Closing.","status":"closed","priority":0,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-19T01:10:15Z","created_by":"Sinity","updated_at":"2026-07-19T03:10:34Z","started_at":"2026-07-19T02:46:25Z","closed_at":"2026-07-19T03:10:34Z","close_reason":"Merged PR #3143: process_pool_context() now unconditionally spawn, never forkserver. AC honestly assessed: mechanism explained but not conclusively reproduced in isolation (documented); regression test + config-pin test added; daemon-census-throughput AC answered by audit (no production pooled daemon throughput exists yet -- pooled path has only run via direct CLI); workers=1 escape hatch kept as-is. Two follow-ups filed: polylogue-7saq (archive_ingest.py raw-fork ProcessPoolExecutor gap) and corroboration added to polylogue-7uqr (converger pool dead machinery).","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -75,10 +84,38 @@ {"_type":"issue","id":"polylogue-tf2.1","title":"Rerun forensics on current archive; price origin_reported providers","description":"Rerun scripts/agent_forensics.py against the current archive (v23+); price origin_reported providers via the vendored LiteLLM catalog (match last path segment); all-provider headline or explicitly-labeled per-provenance figures that cannot be misread; record deltas vs 06-27; verify chart SVGs render. Cache-inclusion must be disambiguated (Codex input INCLUDES cached ~96%; see bd memories). Also blocked on logical-session token attribution — the headline must not be double-counted.","notes":"Correction to close_reason monetary values: stored/provider-priced subset was $239,453.14; catalog API-equivalent was $318,650.88; origin_reported catalog estimate was $79,197.74. The original close_reason text lost dollar-prefixed digits due shell expansion, not measurement drift.","status":"closed","priority":0,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:33Z","created_by":"Sinity","updated_at":"2026-07-03T09:59:13Z","started_at":"2026-07-03T09:28:10Z","closed_at":"2026-07-03T09:59:02Z","close_reason":"Completed with blocker caveat preserved: scripts/agent_forensics.py now prices origin_reported rows through the shared vendored LiteLLM pricing catalog while preserving stored provenance; report separates stored/provider-priced cost from catalog API-equivalent estimates and carries logical-session/cache caveats instead of claiming final billing reconciliation. Regenerated current artifact at .agent/demos/agent-forensics against /home/sinity/.local/share/polylogue schema v23: 16,498 physical sessions, 4,142,175 messages, 356.5B tokens, ,453.14 stored/provider-priced subset, ,650.88 catalog API-equivalent, and ,197.74 origin_reported catalog estimate. SVG parse check passed for 9 charts; devtools test tests/unit/scripts/test_agent_forensics.py passed; devtools verify --quick passed run 20260703T095718Z-quick-753466-96559776; devloop-review clean. Remaining final-reconciliation blocker stays open as polylogue-4ts.2.","labels":["area:usage","campaign"],"dependencies":[{"issue_id":"polylogue-tf2.1","depends_on_id":"polylogue-4ts.2","type":"blocks","created_at":"2026-07-03T06:32:45Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-tf2.1","depends_on_id":"polylogue-sru.7","type":"blocks","created_at":"2026-07-03T06:31:33Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-tf2.1","depends_on_id":"polylogue-tf2","type":"parent-child","created_at":"2026-07-03T06:31:33Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"polylogue-tf2","title":"Campaign: agent-forensics regeneration + all-provider repricing","description":"Regenerate the agent-forensics packet on the current archive with an honest all-provider headline. The 2026-06-27 report (546.6B tokens, $89,368 API-list equivalent, 216x cache amplification) is the most stranger-legible artifact on any shelf, but its numbers are pre-dedup stale and the headline prices only the priced-provenance subset (Claude Code cost_usd rows); Codex/ChatGPT/Gemini are origin_reported token counts with no dollar value (operator estimate ~$150K all-provider). Sequenced after claim-vs-evidence per operator direction 2026-07-02.","design":"Current slice design: turn the existing agent-forensics/cost headline into a product-backed all-provider repricing artifact. First inspect devtools/scripts and polylogue analyze surfaces for agent_forensics/cost code. Use active archive usage headline (detail=headline) for authoritative physical_session and logical_session_model_high_water token totals. Keep priced-provenance dollars and origin-reported token estimates separate: do not multiply every token by one blended price without a labeled lane. Add or reuse a shared pricing/projection helper so the demo artifact is regenerated from Polylogue product code, not ad hoc SQL. Acceptance for this slice: the generated agent-forensics artifact names archive root/schema, includes physical vs logical token grain, separates priced subset from origin-reported estimate lanes, gives reproduction commands, and has focused tests for any new repricing helper/surface.","acceptance_criteria":"Terminal state: regenerated forensics packet on the current archive with an honest all-provider headline (priced subset AND origin-reported estimate lanes separated), agent_forensics.py folded into polylogue analyze (tf2.2), artifact on the demo shelf with reproduction commands, cold-reader gate passed. Epic closes only when that artifact is recorded.","status":"closed","priority":0,"issue_type":"epic","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:32Z","created_by":"Sinity","updated_at":"2026-07-03T19:06:44Z","started_at":"2026-07-03T18:47:23Z","closed_at":"2026-07-03T19:06:44Z","close_reason":"Completed: provider usage headline now exposes product-backed pricing lanes in polylogue analyze usage --detail headline, separating stored/provider-priced cost from catalog API-equivalent estimates for origin_reported rows. Regenerated the current .agent/demos/agent-forensics artifact against /home/sinity/.local/share/polylogue schema v23: physical-session tokens 395,320,980,423; logical high-water tokens 288,741,229,728; stored/provider-priced USD 243,392.189328; catalog API-equivalent USD 337,565.031618; priced lane 13,889 rows / 12,331 sessions / 12,650 matched rows; origin_reported lane 2,308 rows / 2,270 sessions / 2,302 matched rows. Verification: live polylogue --plain analyze usage --detail headline --format json --limit 0 wrote /realm/tmp/polylogue-usage-headline-pricing-current.json; devtools test tests/unit/storage/test_provider_usage_report.py tests/unit/cli/test_diagnostics.py passed 23 tests; devtools verify --quick passed run 20260703T190553Z-quick-2226137-d91d4e8f; devtools workspace demo-shelf --json reported ok. Non-claim preserved: this is not final billing reconciliation and physical/logical token grains stay explicitly separated.","labels":["area:usage","campaign","size:M","spine"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-sru","title":"Campaign: claim-vs-evidence report to finding-grade","description":"Terminal state: an externally publishable finding ('how often do coding agents proceed past failed tool calls, by model/tool') with stated sample frame, calibrated markers, benign/consequential split, seeded stranger-runnable reproduction, and a passed cold-reader gate. Slice closure is NOT campaign closure; this epic stays top-of-frame until its terminal state is recorded.\\n\\nState as of 2026-07-03 after calibrated active-archive regeneration: archive root /home/sinity/.local/share/polylogue, index schema v23, 41,886 structured failures total, 5,000 origin-stratified failures inspected (3,746 claude-code-session, 1,247 codex-session, 7 claude-ai-export), 100 unpaired structured failures. Marker vocabulary was tightened to avoid broad issue/fix/block/gitignored false positives. Immediate next-turn totals: acknowledged=420, silent_proceed=1,205, ambiguous=3,375 (2,624 wordless tool continuations; 751 prose without marker). Lower-bound silent rate is 24.1%; among classified immediate next turns, silent rate is 74.2%. Next-3 sensitivity window, stopping before the next user message, finds 302 acknowledgments that appear only after the next turn; window3 silent lower bound is 37.0%. Calibration: 50 hand-labeled immediate-next-turn rows, acknowledged-marker precision=1.0, recall=0.8421052631578947, invalid rows=0. Artifact: .agent/demos/claim-vs-evidence/claim-vs-evidence.report.json.","notes":"2026-07-03 update: methodology package is now cold-read gated. .agent/demos/claim-vs-evidence contains aggregate live evidence, public-summary.json, PUBLIC_REPRODUCTION.md, COLD_READER_GATE.md, and COLD_READ_RESULT.md. Seeded reproduction is meaningful, not empty: 4 structured failures, 2 acknowledged follow-ups, 2 silent-proceed follow-ups, 0 unpaired. Cold-reader subagent PASS recovered claim/non-claim, sample frame, rates, calibration, caveats, and reproduction commands from the artifact directory only. Remaining campaign child: polylogue-sru.1 productizes action-unit outcome/followup_class capability.","status":"closed","priority":0,"issue_type":"epic","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:26Z","created_by":"Sinity","updated_at":"2026-07-03T09:28:09Z","closed_at":"2026-07-03T09:28:09Z","close_reason":"Completed: all seven campaign children are closed. The claim-vs-evidence finding now has bounded sample-frame reporting, calibrated marker precision/recall, handler-class and next-3 sensitivity splits, meaningful seeded reproduction, cold-reader PASS, and productized action-unit followup_class/followup_message_ref query capability. Current artifact lives under .agent/demos/claim-vs-evidence and was regenerated against /home/sinity/.local/share/polylogue schema v23.","labels":["area:substrate","campaign"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-il50","title":"shipped-but-dead: 6 of 7 declared MCP prompts instruct callers to invoke tool names retired at the 10-tool cutover","description":"Audit 2026-07-31 (shipped-but-dead census). Surfaces dimension.\n\npolylogue/mcp/server_prompts.py:456-553 -- six of the seven prompts declared in\nTARGET_PROMPTS emit instructions naming tools that no longer exist on the current\n10-tool role-gated dispatcher surface:\n postmortem_last, decisions_about, unacknowledged_failures,\n sessions_touching_file, cost_of, resume_context\nThey reference retired pre-cutover names including find_abandoned_sessions,\nget_session_summary, list_marks, search, cost_rollups, find_resume_candidates,\nblackboard_list. An agent following these prompts calls tools that are not there.\n\nThe inverse gap exists too: five prompts are live-registered at\nserver_prompts.py:296-454 (analyze_errors, summarize_week, extract_code,\ncompare_sessions, extract_patterns) but are absent from TARGET_PROMPTS in\npolylogue/declarations/registry.py:520-528, so every completeness and discovery\nconsumer that reads the declaration is blind to them.\n\nNet: the declared set and the working set are disjoint in both directions --\ndeclared-but-broken (6) and working-but-undeclared (5).\n\nSupporting usage evidence (interpretation NOT settled): ops.db mcp_call_log holds\n2 rows total, and a scan found zero recorded invocations of any current 10-tool\nname versus 3,260 actions across 245 sessions for the retired surface. That is\nconsistent with either post-cutover lag or genuine non-adoption; it is reported\nas an open question, not as proof the new surface is unused.\n\nAlso in this cluster: polylogue/mcp/insight_tool_contracts.py has zero external\nreferences, orphaning 11 CLI-only insight types from MCP. Already governed by\nopen bead polylogue-t46.8.2 -- cross-reference, do not duplicate.","acceptance_criteria":"Every prompt in TARGET_PROMPTS names only tools that exist on the current dispatcher surface, and every live-registered prompt is declared. A test pins prompt-referenced tool names against the live tool table so the two cannot drift apart again. The mcp_call_log question is answered separately: either confirm the new surface is being used or open a distinct adoption bead.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:06:05Z","created_by":"Sinity","updated_at":"2026-07-31T08:06:05Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-qsb4","title":"Delegation is a tree, not one level: no arbitrary-depth ancestry/subtree query surface","description":"SCOPE CLARIFICATION on polylogue-1vpm.7 (operator, 2026-07-31, mid-session):\ndelegation in this archive is a tree, not one level -- several subagents\ndispatched in real sessions launch their own subagents. delegation_facts\nalready models this IMPLICITLY (each row is one parent_session_id -\u003e\nchild_session_id edge; a child that itself dispatches subagents gets its\nown delegation_facts rows keyed by its own session_id as parent), so\narbitrary depth already exists in the DATA. What is missing is a single\nquery surface that returns a whole ancestry chain or subtree in one call,\ndepth-annotated, without N+1 queries or client-side reassembly -- and any\nUX built on top of it.\n\nWHY THIS MATTERS: session claude-code-session:38baa1de-9715-48fa-8175-\nf2a29d92800e dispatches ~20 subagents via the \"Agent\" tool (see\npolylogue-1vpm.7's companion fix in archive/viewport/tools.py); some of\nthose subagents dispatch their own subagents (nested Agent-tool calls are\nvisible in the corpus -- verify exact depth/count live before designing).\nA report describing this session's fan-out needs \"whose child is this at\nevery level\", \"what did agent X ultimately spawn\", and \"who ultimately\nasked for this work\" -- none of which delegation_facts' flat per-session\nrows answer without recursive client-side stitching today.\n\nCURRENT STATE (verified 2026-07-31, read-only against\nfile:/realm/db/polylogue/index.db):\n- delegation_facts / delegations (storage/sqlite/archive_tiers/archive.py):\n get_delegation_attempt/get_delegation_card resolve ONE edge by identity\n (instruction_tool_use_block_id, or parent+child pair). query_delegations\n is a flat filtered list, no recursion, no depth column.\n- session_links already has the EXACT precedent to reuse: it persists\n every parent reference a parser asserts even when the parent isn't\n ingested yet, keyed (src_session_id, dst_origin, dst_native_id,\n link_type), resolved on each save, with TopologyEdgeStatus =\n unresolved/resolved/repaired/quarantined (quarantined = the cycle-break,\n #866/#1260). delegation_facts_source already excludes quarantined\n session_links edges (`l.status IS NULL OR l.status != 'quarantined'`),\n so cycle-break precedent is already inherited at the edge level -- a\n recursive CTE walking delegation_facts should still carry an explicit\n visited-path guard defensively, but should not need to invent a second\n cycle vocabulary.\n- work_evidence_nodes/work_evidence_edges (index v46+) already hold a\n generic directed graph (edge_kind: invoked/claimed/mentioned/produced/\n retried/unresolved) that DOES support arbitrary-depth traversal via\n recursive CTE by construction -- but it is populated only from Workflow\n orchestration runs today (verified live: 7 runs, 122 calls, 164\n attempts, 128 structured-results, 0 rows sourced from Claude Code\n subagent dispatch). polylogue-1vpm's own tracking notes call this graph\n \"structurally hollow\" (authority/confidence constant, no time/actor).\n Whether delegation should PROJECT INTO this graph (one edge_kind=\n 'delegated' per delegation_facts row) rather than growing a second,\n parallel recursive-CTE surface is an open design question this bead\n must answer, not assume either way.\n\nACCEPTANCE CRITERIA:\n1. A single call returns the full ancestry chain (root-to-node) for a\n given session/delegation, depth-annotated, in one query -- no N+1.\n2. A single call returns the full subtree (node-to-all-descendants) for a\n given session/delegation, depth-annotated, in one query -- no N+1.\n3. Cycles/orphans reuse session_links' TopologyEdgeStatus vocabulary and\n quarantine precedent rather than inventing a second one; state\n explicitly whether a defensive visited-path guard is still needed in\n the recursive CTE despite quarantine already excluding cycle edges at\n the source.\n4. Explicit design decision, argued from evidence: does this live as new\n recursive-CTE methods on ArchiveStore (delegation_facts-native), or as\n a projection into work_evidence_nodes/edges (join existing \"invoked\"\n graph), or both with one clearly designated as source of truth? Read\n polylogue-1vpm and polylogue-1vpm.6 first -- this may already be a\n settled architectural decision this bead is unaware of.\n5. At least one production surface (MCP tool, CLI verb, or existing\n `get`/`query` dispatcher extension) exposes the tree/subtree query --\n not merely a new ArchiveStore method with no caller. cli/commands/\n analyze* and archive/query/ are owned by other lanes per this session's\n scope -- coordinate or use the MCP `get`/`query` dispatcher instead.\n6. State what UX the html-report skill's delegation-tree CSS pattern\n (references/patterns.md) is meant to consume from this surface, even\n if the actual report/HTML rendering is out of this bead's scope --\n the query surface's shape should not require a second redesign once a\n renderer is built against it.\n7. Live re-measurement: exact max delegation depth and fan-out width in\n the corpus today (after the companion \"Agent\" tool_name -\u003e SUBAGENT\n classification fix lands and, if the operator runs it, a reindex) --\n confirm the \"~20 subagents, some nested\" claim with real numbers before\n finalizing the design.\n\nNON-GOALS (unless folded in explicitly): rewriting delegation_facts'\nidentity-matching mechanism (that's polylogue-1vpm.7, already fixed);\nbuilding the actual HTML/report rendering (that's the report-writing\ntask this bead's design should unblock, not perform).","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:34:56Z","created_by":"Sinity","updated_at":"2026-07-31T10:34:56Z","labels":["area:insights","area:storage","lane:analytics-experiments"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-f1ie","title":"Hook sidecar path mismatch: writer and reader use different directories, paste ground truth discarded live","description":"MEASURED 2026-07-31 (conversation-fidelity audit, audit-only pass). Live, currently-active pipeline break -- not historical debt.\n\nTHE MISMATCH:\n WRITER: ~/.claude/settings.json invokes 'polylogue-hook \u003cevent\u003e --sidecar-dir /home/sinity/.local/share/polylogue/hooks' on UserPromptSubmit / PreToolUse / PostToolUse and others. That directory currently holds ~197,485 files.\n READER: polylogue/sources/live/hook_paste_enrichment.py resolves its sidecar directory through polylogue/config.py:2199-2206 (hook_sidecar_dir setting, falling back to archive_root/hooks) -\u003e /realm/db/polylogue/hooks. That directory is empty apart from an unused pending/ subdir.\nThe daemon's paste-enrichment step therefore never sees any hook sidecar. Hook ground truth accumulates in a directory nothing consumes.\n\nOBSERVABLE CONSEQUENCE: messages.has_paste = 1 for 4 rows out of 4,908,097 archive-wide. paste_boundary is 'projected' on those same 4 and NULL everywhere else.\n select has_paste, count(*) from messages group by 1;\nCross-check via FTS finds 1,424 blocks whose text contains 'pasted' and 'text' within 3 tokens:\n select count(*) from messages_fts where messages_fts match 'NEAR(pasted text, 3)';\nTwo to three orders of magnitude more candidate pastes than flagged messages. has_paste / paste_count are effectively inert columns.\n\nNOT FULLY DISAMBIGUATED (be honest): a second, independent detection path exists -- polylogue/archive/message/paste_detection.py:has_paste_marker looks for a literal '[Pasted text #N]' marker in message text at parse time, and does not depend on the hook sidecar. The FTS-vs-has_paste gap could therefore be (a) that path also not firing, or (b) most of those 1,424 hits predating the paste-detection feature and never having been reprocessed, since materialization runs at ingest/reprocess time and not retroactively. This audit could not separate the two. Whichever it is, the path mismatch above is independently real and worth fixing first because it is cheap.\n\nRELATED, NOT THE SAME: attachment_refs.upload_origin='paste' has 69 real rows, so pasted ATTACHMENTS are recorded (just under-acquired like every other attachment channel). It is paste TEXT detection that is dead.\n\nFIX: point the two at the same directory -- either set hook_sidecar_dir in polylogue.toml to ~/.local/share/polylogue/hooks, or change the --sidecar-dir the sinnix-managed hook command passes. Then decide whether a one-off reprocess is warranted to backfill has_paste on historical sessions.\n\nRE-RUN:\n grep -c sidecar-dir ~/.claude/settings.json\n ls /realm/db/polylogue/hooks; ls ~/.local/share/polylogue/hooks | wc -l\n sqlite3 \"file:/realm/db/polylogue/index.db?mode=ro\" \"select has_paste, count(*) from messages group by 1;\"","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:27:01Z","created_by":"Sinity","updated_at":"2026-07-31T10:27:01Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-ksgg","title":"Branch/thread structure is unreconstructible: 5 of 9 origins carry no message parent links; read surface omits the columns","description":"MEASURED 2026-07-31 (conversation-fidelity audit, audit-only pass).\n\n(A) PARENT LINKS MISSING PER ORIGIN. Sampled 60 sessions per origin (all, where fewer exist), counting messages with parent_message_id set:\n claude-code-session 21048 msgs 96.7% parented\n chatgpt-export 6574 msgs 91.6%\n claude-ai-export 1123 msgs 87.6%\n codex-session 18373 msgs 0.0%\n hermes-session 7507 msgs 0.0%\n aistudio-drive 3108 msgs 0.0%\n gemini-cli-session 652 msgs 0.0%\n grok-export 16 msgs 0.0%\nFive of nine origins store no message-level parent at all. Sessions there are a flat ordered list; the tree the data model documents (sessions -\u003e messages -\u003e blocks with parent links) is not populated.\n\n(B) VARIANTS NEVER RECORDED for the two coding origins: variant_index\u003e0 count is 0 for claude-code-session and 0 for codex-session in the same samples. Retries/regenerations, where they occurred, are not distinguishable.\n\n(C) ACTIVE-PATH CONTRADICTION. 665 sessions archive-wide contain variant_index\u003e0 rows. In 25 of them (490 variant messages) there is not a single is_active_path=0 row -- every variant is marked as being on the active path, so the branch the user actually saw cannot be recovered for those sessions.\n select session_id, count(*), sum(variant_index\u003e0) v, sum(is_active_path=0) inactive from messages group by 1 having v\u003e0;\n\n(D) THE READ SURFACE DOES NOT EXPOSE ANY OF IT. The message payload from has these keys and no others:\n actions, anchor, attachment_refs, branch_index, cache_read_tokens, cache_write_tokens, content_blocks, has_paste_evidence, has_thinking, has_tool_use, id, input_tokens, material_origin, message_type, output_tokens, role, session_id, target_ref, text, timestamp\nAbsent: position, variant_index, is_active_path, is_active_leaf, parent_message_id. So even for claude-code and chatgpt, where the columns ARE populated, a consumer of the JSON read surface cannot reconstruct ordering-by-position or which branch was live. Verified against three real sessions across two origins.\n\nCONSEQUENCE: 'does the archive reconstruct the branch the user actually saw' is answerable only by direct SQL, and on five origins not at all.\n\nSUGGESTED SPLIT: (D) is cheap and self-contained -- add the columns to the messages payload. (A)/(B) are per-origin parser work. (C) is a writer-side active-path assignment bug worth isolating first since it is small and bounded (25 sessions).","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:22:02Z","created_by":"Sinity","updated_at":"2026-07-31T10:22:02Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-buq8","title":"Eleven codex sessions with multi-MB real content materialize as zero messages (quarantine consequence)","description":"MEASURED 2026-07-31 (conversation-fidelity audit, audit-only pass). User-visible consequence of polylogue-u19l; filed separately because the symptom is content loss in the read model, not a convergence metric.\n\nQUERY: select count(*) from sessions s where s.origin='codex-session' and not exists(select 1 from messages m where m.session_id=s.session_id); -\u003e 17\n\nOf those 17, raw files were inspected directly:\n - 6 are genuinely empty (raw is session_meta plus at most a bare task_started event). Correctly represented.\n - 11 hold real substantial conversations, 996 KB to 3.3 MB, 694 to 3,688 lines each. Example native_id 019a2e27-2596-7f22-b6f5-e26acd721d57 (3.3 MB / 3,685 lines) raw inner-type census: message:50, user_message:24, agent_reasoning:478, reasoning:479, function_call:444, function_call_output:444, custom_tool_call:67, custom_tool_call_output:67. ZERO of this reached messages, blocks, or session_events.\n\nROOT CAUSE (confirmed in source.db): all 11 raw rows have parse_error=NULL, validation_status='passed', blob_size matching the real file -- the bytes were acquired and parsed fine. They carry revision_authority='quarantined', revision_kind='unknown', source_index=0, no predecessor_raw_id/baseline_raw_id: a single uncontested acquisition whose authority classification never resolved to byte_proven, so materialization into index.db never runs. This is the absorbing-state mechanism diagnosed in polylogue-u19l.\n\nSCOPE: select revision_authority, count(*) from raw_sessions where origin='codex-session' group by 1; -\u003e byte_proven 3951, quarantined 5202 (57%).\n\nWHY THIS MATTERS SEPARATELY FROM u19l: sessions.message_count=0 reads as 'nothing happened here' on every surface. Nothing distinguishes a genuinely empty rollout from 3.3 MB that never materialized. The audit brief's warning applies exactly -- this stayed invisible because everything downstream trusted the parser's verdict.\n\nRESIDUAL / INFERRED, not measured: the 11 are only the sessions where EVERY raw row was quarantined. With 5,202 quarantined rows overall, sessions where some rows resolved and others did not would lose content while still reporting message_count\u003e0, and would never appear in this query. Detecting those needs a per-session reconciliation of raw item counts against archive row counts. Recommend that as the acceptance check for u19l's fix rather than 'no more empty sessions'.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:21:16Z","created_by":"Sinity","updated_at":"2026-07-31T10:21:16Z","dependencies":[{"issue_id":"polylogue-buq8","depends_on_id":"polylogue-u19l","type":"related","created_at":"2026-07-31T12:23:05Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-mvq8","title":"\u003e8MiB browser captures stamped unknown-export by the 1MiB provider probe: 641MB of ChatGPT captures unparseable, 8 conversations wholly absent, lane re-captures them forever","description":"STAGE-2 (detection defect at acquire time) - rebuild does NOT fix: origin is stamped on the raw row; needs probe fix + re-detection of stored unknown-export rows. From the 2026-07-31 acquisition-completeness audit.\n\nMechanism (file:line, verified against a live blob): captures \u003e _STREAMING_FULL_INGEST_BYTES = 8MiB (polylogue/sources/live/batch_support.py:26) take _browser_capture_prefix_probe, which reads only _BROWSER_CAPTURE_PREFIX_PROBE_BYTES = 1MiB (batch_support.py:31, read at :464). The capture envelope orders raw_provider_payload BEFORE session.provider, so for any conversation big enough the provider regex (:469) finds nothing and the row falls back to unknown-export (:506-509). A correct bounded ijson reader already exists (_stream_browser_capture_provider, polylogue/sources/source_acquisition_components.py:355-381) but is not used on this route.\n\nMeasured: 23 distinct browser-capture paths / 641,613,073 bytes of raw rows sit under origin='unknown-export' (repro: select count(distinct source_path),sum(blob_size) from raw_sessions where origin='unknown-export' and source_path like '%browser-capture%'). Union-find vs index: 8 conversations (108.8MB) wholly unrepresented; the rest exist only as stale truncated pre-8MiB versions. Compounding: the unsatisfied cursor re-acquires the growing conversation repeatedly - one path has 12 raw rows of ~23MB each. ACTIVE: the chatgpt browser-capture lane is live (acquired same-day as audit).\n\nRelated: polylogue-t0ta (chatgpt detection tightness), polylogue-erf3 (claude.ai zip container-level unknown-export), polylogue-01fe (unknown-export unfilterable).\n\nAC: (1) provider detection for streaming-size captures uses the bounded ijson envelope reader (or equivalent) - a \u003e8MiB capture with provider after a multi-MB payload detects correctly, with test; (2) the 23 stored unknown-export capture rows re-detected/re-originated and parsed - the 8 absent conversations reach the index; (3) re-capture churn stops (cursor satisfied after successful parse).","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:10:18Z","created_by":"Sinity","updated_at":"2026-07-31T10:10:18Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-pebu","title":"Recover unimported provider exports: 2026-07-29 ChatGPT ZIP holds 181 conversations absent from the archive; 5 legacy inbox ZIPs (2.29GB) permanently excluded","description":"STAGE-1 ACQUISITION LEAK - index rebuild does NOT recover any of this; the bytes were never captured. From the 2026-07-31 acquisition-completeness audit (report: /realm/inbox/polylogue-audits-2026-07-31/acquisition-completeness.html).\n\n1) /realm/data/exports/chatlog/raw/chatgpt/chatgpt-data-2026-07-29-03-22-34.zip (16.08GB, 2836 conversations) has ZERO raw_sessions rows referencing it. Browser-capture independently covers 2291/2472 dated conversations (92.7%), but 181 conversations exist ONLY inside this unimported ZIP. (Related: polylogue-geop - newer exports are not supersets, so older bundles must not be pruned on import.)\n2) 5 ZIPs under the legacy ~/.local/share/polylogue/inbox/ ({chatgpt,claude-ai}-data-*.zip, largest 2.07GB, total 2.29GB) have zero raw_sessions AND zero raw_artifacts rows; their ingest cursors are excluded=1 with failure_count 689/864/975/1001/2018 (crash-looped past the design ceiling of 5 - see the failure-accounting bead). Cross-check against /realm/data/exports/chatlog/raw originals before re-import; at least chatgpt-data-2026-04-23 exists there and IS imported, so dedupe by content, not by path.\n\nRepro (mode=ro):\n sqlite3 \"file:/realm/db/polylogue/source.db?mode=ro\" \"select count(*) from raw_sessions where source_path like '%chatgpt-data-2026-07-29%'\" -- 0\n sqlite3 \"file:/realm/db/polylogue/ops.db?mode=ro\" \"select source_path,failure_count from ingest_cursor where failure_count\u003e100\" -- the 5 ZIPs\n\nAC: (1) 2026-07-29 export imported; the 181 absent conversations present in index (verify by native_id sample); (2) each of the 5 excluded ZIPs either imported from a verified-good copy or explicitly closed as corrupt/duplicate WITH a durable record of that disposition; (3) no double-ingest of conversations already present via browser-capture (content-hash idempotency should handle this - verify counts before/after).","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:10:16Z","created_by":"Sinity","updated_at":"2026-07-31T10:10:16Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-7eo7","title":"Health verdict contradicts its own data: 'ok (6 alerts)', 23-min-stale heartbeat still 'running', cursor_lag_samples never populated, health loop absent when schema-blocked","description":"Audit 2026-07-31, four observability defects in one verdict pipeline: (1) polylogued status prints 'Health: ok (6 alerts)' while hook_flow [error] fires every tick (journal, dozens/day) — alerts don't feed the verdict. (2) 'Status: running (heartbeat 1369.8s ago)' — a 23-min-stale heartbeat (15-min interval) produces no staleness verdict. (3) ops.db cursor_lag_samples has 0 rows EVER — the cursor-lag SLO check reads a table nothing produces (detector without producer). (4) When the watcher is schema-blocked, periodic health checks are never started at all (daemon/cli.py:2114-2165) — the daemon is blind exactly when blocked. Also cosmetic: SIGTERM stop exits 143 so every clean stop logs \"Failed with result 'exit-code'\", training operators to ignore 'failed'. Fix: alerts must drive the verdict; heartbeat staleness threshold; wire the lag sampler; start fast health checks in schema-blocked mode; SuccessExitStatus=143.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:49Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:49Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-9kc0","title":"polylogued cgroup incoherent: runtime MemoryHigh=14G exceeds MemoryMax=8G; peak hit the 8G wall with 3.9G swap","description":"Audit 2026-07-31. Unit file (sinnix) sets MemoryHigh=6G MemoryMax=8G; the emergency runtime drop-in (50-MemoryHigh.conf via systemctl set-property) raised only MemoryHigh to 15032385536 (14G) — ABOVE MemoryMax, making the high threshold unreachable and leaving 8G as the binding hard wall. Measured: MemoryPeak=8589934592 (exactly == MemoryMax), swap peak 3.9G, 2026-07-30 rebuild ran under continuous reclaim. Bulk rebuild profile alone pins 4GiB mmap (BULK_BUILD_MMAP_SIZE_BYTES) + 512MiB cache, and mmap pages count against the cgroup. Fix in sinnix module: coherent pair (e.g. MemoryHigh=12G MemoryMax=14G) or a documented one-shot override procedure for rebuilds; drop the stale runtime drop-in.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:41Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:41Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-61jg","title":"Interrupted ingest is never requeued: 2.18GB of this machine's claude-code sessions + 588/1004 claude-ai conversations acquired but never parsed","description":"STAGE-2 PARSE LEAK (rebuild/reprocess recovers the data; the mechanism re-accumulates it). From the 2026-07-31 acquisition-completeness forensic audit (report: /realm/inbox/polylogue-audits-2026-07-31/acquisition-completeness.html).\n\nTwo backlogs, one mechanism:\n1) 365 claude-code-session union-find groups (2.18GB, ~423 raw rows) have validated_at_ms IS NULL AND parsed_at_ms IS NULL - acquired, never even validated. Concentrated in -realm-project-polylogue (188), -realm-project-sinex(+pre-enrich) (187), -realm-project-sinnix (20), -realm-nixos-config (12).\n2) claude-ai-export: 588 of 1004 distinct acquired conversations (58.6%) have parsed_at_ms NULL on EVERY raw row; index holds only 431 sessions. Newest bundle (claude-ai-data-2026-07-30, 1013 raw rows) validation_status='passed', 0 parse errors - the backlog is pure non-materialization.\n\nMechanism: ops.db ingest_attempts has 24 rows status='interrupted' error_message='daemon stopped before completing this ingest attempt' spanning 2026-07-18..2026-07-31 (ONGOING), plus 1 stale 'running' row with dead heartbeat. convergence_debt has ZERO corresponding entries (only 2 unrelated fts rows) - interrupted ingest batches are not registered for retry anywhere; files wait for an accidental future touch.\n\nRepro (mode=ro):\n sqlite3 \"file:/realm/db/polylogue/ops.db?mode=ro\" \"select status,count(*) from ingest_attempts group by 1\" -- completed 2127 / interrupted 25 / failed 0\n sqlite3 \"file:/realm/db/polylogue/source.db?mode=ro\" \"select count(distinct native_id) from raw_sessions r where origin='claude-ai-export' and not exists (select 1 from raw_sessions p where p.origin=r.origin and p.native_id=r.native_id and p.parsed_at_ms is not null)\" -- 588\n\nAC: (1) interrupted/incomplete ingest attempts register retryable debt (convergence_debt or equivalent) so validation/parse resumes after daemon restart; (2) both backlogs drained (claude-ai-export index sessions ~1004; the 365 claude-code groups represented); (3) a daemon kill mid-batch demonstrably resumes on next start.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:40Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:40Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-qlae","title":"Writer-lock hold time is unbounded and unalerted: 21,433s single hold starved all maintenance for ~5h","description":"Audit 2026-07-31; extends polylogue-de2a with much worse measurements. Journal 07-29→07-31 'daemon writer released' aggregation: maintenance.raw_materialization hold_max=21,433.6s (5.9h, avg 334s over 181 passes); maintenance.drive_catchup hold_max=18,623s; during those holds daemon.lifecycle.heartbeat waited up to 17,042s, wal_checkpoint 17,642s, fts_merge 17,882s, watcher.catch_up.prefilter 20,298s. DaemonWriteCoordinator's priority classes (write_coordinator.py:44-56) bound queue ORDER, not the duration of one admitted hold; no health check reads wait_s/hold_s; only post-hoc journal lines exist. This is the observed multi-hour livelock. Needs: a hold budget for maintenance actors (yield+requeue), wait/hold telemetry into ops.db, and a health alert on writer starvation.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:37Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:37Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-hat0","title":"Deferred-append loop: cursor never advances, new raw_id minted every pass, attempts logged 'completed'","description":"Audit 2026-07-31, confirms the live-observed re-acquire+re-parse-forever path. Mechanism (sources/live/append_ingest.py:56-244): write_raw_payload durably writes the append bytes and mints a raw_id BEFORE classification; if the authority chain is quarantined or the new raw is not in the accepted replay chain, the plan is DEFERRED and record_deferred_append_cursor (sources/live/deferred_cursor.py:15-53) keeps the old byte_offset. Next watcher pass sees size\u003ebyte_offset, re-plans the same range, writes ANOTHER raw row, defers again — forever. Deferral never calls mark_failed so the 5-strike exclusion never triggers, and _archive_attempt_status (sources/live/cursor.py:187-194) maps completed_with_failures→completed, so ingest_attempts shows clean 'completed' rows and repeated_stage_failures (health.py:737-880) can never fire. LiveBatchMetrics has no deferred_file_count either. Every iteration adds duplicate raw bytes to the durable tier with zero failure telemetry. Needs: terminal/aging classification for repeatedly-deferred appends + a distinct attempt status/counter + dedup of re-minted identical raw payloads.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:34Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:34Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-f4z9","title":"Census plan-ledger retention defeated by unresolved blockers: 709k rows regrowing in durable tier","description":"Audit 2026-07-31 (daemon-failure-surface report). RAW_AUTHORITY_CENSUS_PLAN_RETENTION=8 (storage/raw_authority.py:43) is supposed to bound raw_authority_census_plans, but the obligation guard keeps any census with unresolved blockers alive — and the 4,147 quarantine blockers never resolve. Live source.db: 41 censuses (seq 820-932) hold plan rows; 709,264 rows total (657,136 dry_run carried_forward + 52,128 apply carried_forward + 24 executed); ledger tables ~870 MiB by dbstat (census_plans 185MiB + plans 138MiB + post_plans 99MiB + indexes). All accrued since 2026-07-31 02:22 → ~100k rows/hour into source.db, the DURABLE tier. This is polylogue-wkc6 regrowing through a different retention exception. Also: source.db is 9.0GiB on disk but only 1.44GiB live (freelist 2,000,618/2,370,200 pages = 84%) from the previous purge — never vacuumed. Fix ideas: cap obligation-guard retention (keep blockers, drop their duplicate plan-row snapshots), or stop re-snapshotting unchanged plans per census.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:33Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:33Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-x2y9","title":"Leak audit L18: assertion injection lacks trust labelling on one of two consumers","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE, currently dormant. Novel surface - worth fixing while dormant.\n\nThe gate itself works: every assertion write path defaults the context policy to non-injecting, verified live that 0 of 101 rows are marked injectable, and both read consumers filter on the gate (neither selects by kind or scope while ignoring the policy).\n\nThe consumers differ in how they treat injected text:\n - The resume preamble is correct: source authority hardcoded to 'quoted', quoted evidence in a structurally separate field, fails closed.\n - The MCP context compiler builds its assertion segment with NO trust-derivation call, so the same text would arrive unlabelled and indistinguishable from surrounding instruction material.\n - The judge operation accepts a caller-supplied actor reference that satisfies its own provenance check, so 'who asserted this' is not authenticated.\n\nBoth are dormant only because mcp_judge_enabled / mcp_write_enabled default false and are false on the live config. That is a configuration reason, not a code reason: enabling either for an ordinary feature activates them as a side effect.\n\nWhy this matters beyond the immediate bug: content flows in from providers, gets judged and summarised by agents into assertions, and those assertions flow back out into agent contexts. A loop of that shape needs the evidence/instruction boundary to be structural at every consumer, not conventional at one of them. This is prompt injection through the archive.\n\nFix: give the context compiler the same trust derivation the preamble has; stop treating a caller-supplied actor ref as provenance.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:49Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:49Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-bv59","title":"Leak audit L21: GitGuardian cannot see the content class that actually leaked","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE - structural gap.\n\nGitGuardian is the only automated scanner on the publication path and it detects credential PATTERNS. It is working: an independent regex sweep over the tracked tree found nothing but deliberate test literals inside the secret scanner's own tests.\n\nBut the content that actually leaked (L1-L4) is private prose, session identifiers, corpus size and dollar spend - a class with no pattern. The publication path had a scanner, the scanner ran, and the scanner passed, while the content went through. A control that cannot see the failure class is not partial coverage; its green result is actively misleading about the state of the tree.\n\nFix: this is what the L5 content gate is for. File here so the false assurance is recorded rather than re-discovered.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":1,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:43Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:43Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-t9xd","title":"Leak audit L11: secret scanner is not wired to any automatic path","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE.\n\npolylogue/security/secret_scan.py works and is exposed as 'polylogue scan-secrets', but it is manual, per-session, and candidate-only. Grep of all callers confirms nothing invokes it at ingest, at render, at export, or before a commit.\n\nConsequence: a rendered session or exported demo packet carries whatever credentials were pasted into the original conversation, with no automated check anywhere. Across 4.9M archived messages the base rate of pasted keys is not zero.\n\nFix: wire it into (a) the staged-text pre-commit gate from L5 and (b) render/export paths, at minimum as a warning.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:41Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:41Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-n6pz","title":"Leak audit L6: live daemon API is unauthenticated across the uid boundary","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE.\n\nMeasured live posture: polylogued runs with no API auth token - absent from the process command line, from ~/.config/polylogue/polylogue.toml, and from the systemd unit environment. The full-content read API is therefore open on 127.0.0.1:8766.\n\nThe gap the threat model does not cover: it argues local-process access is acceptable because same-user processes could read the SQLite files anyway. That holds for uid 1000. It does not hold across uids:\n - read archive files directly: uid 1000 allowed; other uid DENIED (archive root is 0700)\n - connect to 127.0.0.1:8766 and read full content: uid 1000 allowed; other uid ALLOWED (loopback TCP has no peer-uid check)\n\nSo a container, service account, or sandboxed process under a different uid gets through a boundary the filesystem otherwise enforces. Small on a single-user desktop; wrong as a boundary statement.\n\nFix: configure an API auth token, or move the API to a unix socket so file permissions apply (already listed in the threat model's future considerations). Update the residual-risk paragraph either way.\n\nVerified SOUND on the same surface, for the record: auth is a single choke point before the route table rather than per-route decorators; a Host-header admission check runs before every dispatch (the real DNS-rebinding defence); there are zero Access-Control-Allow-* headers and OPTIONS returns 405, so a web page can reach the socket but cannot read responses; mutating POSTs require exact Origin-to-Host match; non-loopback bind refuses to start without a token.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:35Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:35Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-0bgr","title":"Leak audit L4: demo shelf is not private-data-free","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: EXPOSED.\n\nThree demo packets under .agent/demos/ (agent-forensics, agent-affordance-usage, attachment-acquisition-census) plus SUMMARY_INDEX.json were generated with the archive root pointed at the LIVE archive rather than the seeded fixture archive, and are committed to the public repo.\n\nPublished: corpus size, token totals, per-model spend in USD, tool-usage distribution, and the real archive path including the operator's username. Content is aggregate - no message text, and attachment id samples are hex digests rather than filenames. It is operator-private operational and financial data, published under a banner that states the shelf is private-data-free.\n\nThe seeded demo path itself IS genuinely synthetic (verified: literal fixtures in source, fabricated session ids). The defect is that these three packets bypassed it.\n\nFix: regenerate against the seeded fixture archive, or remove them. The banner should be true or absent.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:30Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:30Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-b629","title":"Leak audit L3: 17 live-archive session identifiers committed at tip","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: EXPOSED.\n\nMethod: collected every UUID appearing in tests/, docs/ and .agent/ (47 distinct), then resolved each against the live archive read-only (SELECT origin FROM sessions WHERE native_id = ?, file:/realm/db/polylogue/index.db?mode=ro). 17 matched real sessions across codex-session, claude-code-session and chatgpt-export origins.\n\nContent at risk: identifiers only, no text. Locations include test fixtures, docs, demo evidence files and .beads records. The identifiers are deliberately NOT reproduced in the audit report or in this bead; regenerate with the query above.\n\nFix: replace with synthetic ids in tests and docs; decide separately whether the historical occurrences are worth scrubbing.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:28Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:28Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-8zzs","title":"CLI status fabricates 'FTS: 100.0% indexed' from readiness boolean when coverage_pct is null","description":"Surface-coherence audit 2026-07-31 — the live 'ops status says FTS 100% while query path says incomplete' incident, CLI-render site. polylogue/cli/commands/status.py:1273-1276: `pct = _safe_float(fts.get(\"coverage_pct\"), default=100.0 if fts.get(\"messages_ready\") else 0.0)` then prints `FTS: [green]100.0% indexed`. Live evidence: `polylogue ops status --json --full` has fts_readiness.coverage_pct=null, message_indexed_count=null, message_indexable_count=null, coverage_exact=false, surfaces.messages_fts source_rows=1 indexed_rows=1 (index.db fts_freshness_state row: detail='bounded global messages_fts repair completed; exact counts skipped') — yet the human status line asserts the precise measured-looking claim \"FTS: 100.0% indexed\" fabricated from the messages_ready boolean. Same snapshot: component_readiness.search.counts all None, search.collection.state=stale. Sibling of polylogue-oitx (daemon/fts_status.py fabricated coverage class — filed by the 2026-07-31 silent-degradation audit); this bead covers the CLI presentation layer: when coverage_pct is null/not measured, render 'structurally ready (coverage not measured)' or similar — never a fabricated percentage.\n","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:40:26Z","created_by":"Sinity","updated_at":"2026-07-31T08:40:26Z","labels":["cli","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-hnl7","title":"MCP query tool silently drops origin/tag/repo/since/until/sort for default projection","description":"Surface-coherence audit 2026-07-31 (live archive, in-process build_server()). MCP `query`'s input schema accepts origin/tag/repo/since/until/sort, but the default (query_units) projection path passes only (expression, limit, continuation) — polylogue/mcp/server_cutover.py ~L620-630: `hooks.get_polylogue().query_units(expression, limit=limit, continuation=continuation)`. Live repro: query(expression='messages where role:user | count', origin='claude-code-session') -\u003e count=208055, which is the ALL-origin count (SQL `select count(*) from messages where role='user'` = 208055; claude-code-session alone = 141646 via sessions.user_message_count rollup and via join). CLI with the same root filter returns the correct 141646 (`polylogue --origin claude-code-session --json find 'messages where role:user | count'`). MCP also accepts origin='bogus-origin' without error (returns the unfiltered aggregate) where CLI raises UsageError listing valid origins. Filters ARE honored for projection='sessions' and insight projections — only the default unit-query path drops them. Fix: lower the args into the unit expression, or reject the combination loudly (invalid_argument) the way continuation is rejected for other projections. An agent surface silently returning wrong-scope numbers is the worst MCP failure shape.\n","status":"in_progress","priority":1,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:40:24Z","created_by":"Sinity","updated_at":"2026-07-31T09:26:28Z","started_at":"2026-07-31T09:26:28Z","labels":["mcp","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-i415","title":"Silent parse loss: 11 codex rollouts (up to 3.3MB, mostly 2025-10/11 era) parsed to zero messages despite real content","description":"Forensics 2026-07-31. 17 codex-session rows have message_count=0; 11 of them have blob_size 19KB-3.3MB. Verified sample rollout 0199fada-d8bd-7fc0-997b-d23d3a6849c7 (3.3MB, 2025-10-19): jq type histogram = 1543 event_msg + 1496 response_item (incl 55 message, 440 function_call+440 outputs, 44 custom_tool_call pairs, 473 reasoning) + 513 turn_context — archive shows ZERO messages. This is silent data loss for old-format rollouts, not 'genuinely empty'. 9/11 are 2025-10..11 native ids; 2 are 2026-07-17. The other 6 empties are legit (single session_meta record, blob \u003c=5KB).\nRepro: ATTACH index.db from source.db side or join; SELECT s.native_id, r.blob_size FROM sessions s JOIN raw_sessions r ON r.raw_id=s.raw_id WHERE s.origin='codex-session' AND s.message_count=0 ORDER BY r.blob_size DESC;\nAC: parser handles the old rollout envelope (or a dated schema variant is added), the 11 sessions re-parse with non-zero messages, and a fixture from a synthesized old-format rollout protects it.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:20:22Z","created_by":"Sinity","updated_at":"2026-07-31T08:20:22Z","comments":[{"id":"019fb743-7795-756b-a460-10373103be45","issue_id":"polylogue-i415","author":"Sinity","text":"Code trace (audit): HEAD still parses these to zero. codex.py looks_like (1944-1970) accepts state-record-dominated files; _parse_records emits messages only for _message_record shapes (385-391) and drops role-less/text-less records (2344-2347); session_meta/turn_context/world_state/compacted only ever emit events — compacted deliberately does not re-parse replacement_history. Related: polylogue-dhil (whale anatomy, open); f969cf93b pins only the multi-session_meta case. NOTE: sampled file has 55 response_item payload.type='message' records that still produced 0 messages — the old-envelope inner shape apparently fails _message_record; a fixture from that exact era file is the AC.","created_at":"2026-07-31T08:21:20Z"},{"id":"019fb7b8-5707-76ed-b7fa-c380803f5447","issue_id":"polylogue-i415","author":"Sinity","text":"Investigated for PR #3441. Re-parsed the exact archived raw bytes (verified identical to the live on-disk rollout files via blob_size match) for all 17 codex-session rows with message_count=0 using the CURRENT (unmodified) codex.py: 11 now produce real non-trivial message counts (3 to 1073 messages, e.g. 1023 for the 3.3MB 0199fada-... sample cited in this bead's forensic note), confirming this is a stale-materialization issue from an older parser version, not a live parser defect -- the operator's planned index rebuild will resolve these 11 with no code change. The remaining 6 are genuinely near-empty stubs (\u003c=5KB, 1-4 records), matching this bead's own classification. Added a regression fixture (tests/unit/sources/test_silent_ingest_loss.py::test_codex_dense_reasoning_and_tool_call_rollout_yields_messages) built from real record shapes (prose redacted) so this shape cannot silently regress. No codex.py change needed or made.","created_at":"2026-07-31T10:28:59Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"polylogue-shnc","title":"Cost accounting: 100% of codex session_model_usage unpriced; 5,016 rows claim provenance='priced' with NULL cost/catalog; all 3,417 origin_reported rows carry no value","description":"Forensics 2026-07-31, live index.db (verifies and extends the existing NULL-cost report):\n- ALL 3,153 codex-session session_model_usage rows have cost_usd NULL and priced_with NULL (gpt-5.5 617, gpt-5.4 531, gpt-5-codex 454, gpt-5.3-codex 405, gpt-5.6-sol 313, gpt-5.6-terra 306, ...) despite the vendored LiteLLM catalog nominally covering gpt-5.x. Only 1 price_catalogs row is loaded.\n- Contradictory state: cost_provenance='priced' but cost_usd IS NULL AND priced_with IS NULL on 5,016 rows (70.1M tokens). 'priced' with no catalog and no price is a semantic lie; the other 10,222 priced rows are consistent.\n- cost_provenance='origin_reported' has cost_usd NULL on 3,417/3,417 rows (7.58B tokens) — the label exists but the origin-reported value was never stored.\n- claude-code NULLs: \u003csynthetic\u003e 1,140 (fine) + claude-sonnet-5 705 (catalog gap) + 7 misc.\n- session_provider_usage_events: 4,002,046 rows, estimated_cost_usd populated on 103, actual_cost_usd on 0.\nRepro: SELECT cost_provenance, cost_usd IS NULL, priced_with IS NULL, count(*) FROM session_model_usage GROUP BY 1,2,3;\nAC: pricing pass covers codex models + claude-sonnet-5; provenance constraint (priced =\u003e cost_usd AND priced_with NOT NULL; origin_reported =\u003e cost_usd NOT NULL) enforced or the states renamed honestly; re-materialization backfills existing rows.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:20:22Z","created_by":"Sinity","updated_at":"2026-07-31T08:20:22Z","comments":[{"id":"019fb743-7112-71ce-a091-fc8a3ff2c669","issue_id":"polylogue-shnc","author":"Sinity","text":"Code trace (audit): NOT a catalog gap — gpt-5.5/5.4/5-codex ARE in vendored litellm_model_prices.json. Root cause in storage/sqlite/archive_tiers/write.py: (a) _upsert/_increment_provider_usage_model_rollup (~3721-3794) hardcode cost_provenance='origin_reported' AND cost_usd=NULL/priced_with=NULL — pricing never attempted on the Codex cumulative-rollup path; (b) _aggregate_message_tokens_into_model_usage (~3847-3964), the only pricer, has a WHERE NOT guard (~3942-3950) refusing to overwrite origin_reported rows with nonzero tokens — structurally barred from pricing Codex; (c) the 'priced' label is written unconditionally by the INSERT literal even when 'normalized in PRICING and billable\u003e0' is false — hence 5,016 priced-with-NULL rows. 'origin_reported' means token provenance, not that a cost exists (session_reported_costs table was dropped in polylogue-v2mg).","created_at":"2026-07-31T08:21:18Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} +{"_type":"issue","id":"polylogue-f5tq","title":"Untested shipped defaults: _archive_facet_buckets(include_deferred=True) plus a 17-item sweep","description":"FALSE-GREEN AUDIT 2026-07-31 (findings F4 + F13). Generalises the correlation_view github_api defect.\n\nTHE SEED DEFECT'S SHAPE: run_correlation_view(github_api=True) at\npolylogue/insights/correlation_view.py:14 shipped a NameError on its DEFAULT path because\nevery test (tests/unit/cli/test_correlate_view.py:60,80,90) passed github_api=False.\n\nI built an AST sweep to find the class mechanically: walk every polylogue/ function with a\nboolean default param, walk every tests/ call site, and flag params where the DEFAULT value is\nnever passed and never omitted while the opposite value IS passed.\n 388 production functions carry bool defaults\n 265 of them are called from tests\n 17 have a default that is never exercised\n 2 of those 17 have default=True (i.e. the SHIPPED behaviour is the untested one)\nThe sweep rediscovered run_correlation_view without being told it existed -- that is the\ncalibration proving it detects the class.\n\nNEW FINDING, the second default=True case:\n polylogue/api/archive.py:735 _archive_facet_buckets(..., include_deferred: bool = True)\n tests/unit/api/test_facade_contracts.py:738 is the only test, and passes include_deferred=False.\n The False branch returns HARD-CODED EMPTY DICTS for repos/role_counts/material_origins/\n message_types/action_types/has_flags. The True branch (the shipped default) calls\n _archive_aggregate_facet_families(archive._conn, ...) and does all the real SQL work.\n The test constructs its archive stub with _conn=None -- so it STRUCTURALLY CANNOT exercise\n the default; passing True would crash on the None connection.\n Production callers at api/archive.py:4771-4774 all forward an operator-supplied\n include_deferred, so the default path is live in real use.\n\nThe remaining 15 are default=False with tests passing only True (force, detail,\nrequire_overlays, exclude_none, include_rows, ...). Lower risk -- the untested default is\nusually the inert path -- but each is an untested shipped default and worth a triage pass.\n\nA mirror sweep found 235 flags never passed explicitly by ANY test (the non-default branch\nuntested). That list is noisy: matching is by bare function name, so generic names (list,\ncount, to_payload, model_copy) collide across classes. Treat it as a candidate pool.\n\nAC:\n- A test exercises _archive_facet_buckets with include_deferred=True against a real\n connection, asserting the SQL facet families are populated.\n- The 17-item list is triaged: each either gets default-path coverage or a recorded reason\n the default is not worth testing.\n- Consider whether this sweep is worth a devtools lab policy check. NOTE the operator's\n standing 'no completeness-check theater' rule: only add the gate if the known debt is\n migrated first, not as a substitute for migrating it.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:20:16Z","created_by":"Sinity","updated_at":"2026-07-31T09:26:29Z","started_at":"2026-07-31T09:26:29Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-eo81","title":"Antigravity origin inverted: 116 metadata sidecars ingested as sessions; all 44 real conversations (314MB .pb) never acquired","description":"Forensics 2026-07-31. Every antigravity-session row (116/116) is a 1-message session materialized from ~/.gemini/antigravity/brain/\u003cuuid\u003e/*.md.metadata.json — artifact metadata, not conversations (producer stopped 2026-07-18; 232 raws, 116 sessions). Meanwhile ~/.gemini/antigravity/conversations/ holds 44 real conversation .pb files (314MB) and raw_sessions/raw_artifacts contain ZERO rows for that directory: the actual conversations were never acquired. The origin is 100% noise, 0% signal.\nRepro: SELECT count(*) FROM raw_sessions WHERE source_path LIKE '%antigravity/conversations%'; -- 0\nAC: (1) purge/reclassify the 116 metadata sessions; (2) decide+implement .pb conversation acquisition (or explicitly document the format as out of scope with the gap tracked); (3) metadata.json becomes sidecar artifact kind.","notes":"2026-07-31 acquisition-completeness audit cross-check (report: /realm/inbox/polylogue-audits-2026-07-31/acquisition-completeness.html): full-tree recount across BOTH roots (~/.gemini/antigravity + antigravity-cli) = 55 .pb files / 339,774,849 bytes with zero raw_sessions/raw_artifacts rows (this bead's 44/314MB was the conversations dir of one root). Sidecar rows: 232 raw rows over 116 distinct *.md.metadata.json paths, 61KB total = 0.008% of antigravity's 383.6MB captured. All 114 antigravity ingest cursors excluded=1 failure_count=5 since the 2026-07-18 bulk give-up incident. Dormant: newest mtime under either root is 2026-07-16 - static residue, not an active drip. STAGE-1: index rebuild recovers none of it.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:56Z","created_by":"Sinity","updated_at":"2026-07-31T10:11:55Z","started_at":"2026-07-31T09:23:23Z","comments":[{"id":"019fb743-7558-7b63-a3f0-f099cd82dded","issue_id":"polylogue-eo81","author":"Sinity","text":"Code trace (audit): parse_brain_metadata (sources/parsers/antigravity.py:245-288) documents the 1-session-per-metadata-file shape as a DELIBERATE tagged compromise — sessions carry flag 'degraded:brain-metadata-fragment' meant to exclude them from primary counts; tracked upstream as GH issue #1764. Still wired unconditionally at HEAD (dispatch.py:1080,1207). The .pb conversations gap (44 files / 314MB, zero raw rows) is the part with no tracking at all.","created_at":"2026-07-31T08:21:19Z"},{"id":"019fb7b8-5162-7672-924a-b0e1f650371e","issue_id":"polylogue-eo81","author":"Sinity","text":"Fixed acquisition half in PR #3441 (branch feature/sources/antigravity-conversation-acquisition): the language-server export path was gated on a nonexistent 'sessions/' dir (real dir is 'conversations/') and cascade discovery relied on SearchConversations, which only surfaces ~10/44 real conversations -- switched to disk-truth glob of conversations/*.pb, still enriching metadata from search when available. Verified against real ~/.gemini/antigravity data: exported a cascade absent from SearchConversations directly via ConvertTrajectoryToMarkdown (82KB real markdown), and ran the fixed iter_source_sessions_with_raw end-to-end producing all 44 sessions / 44 raw blob snapshots / 2162 messages into a scratch blob store (no live-archive writes). Also reclassified *.md.metadata.json as a non-session sidecar (AGENT_SIDECAR_META) in the generic walk so future ingest stops fragmenting brain metadata into noise sessions -- parse_brain_metadata remains wired as an explicit fallback only when the language server truly cannot be reached. NOT done: retroactive purge/reclassification of the existing 116 already-materialized fragment sessions (deletion-adjacent, deliberately left for a separate follow-up); live-archive acquisition itself, since polylogued.service runs a separately-deployed Nix package that won't pick up this fix until merge+redeploy -- see PR body for the exact operator action needed.","created_at":"2026-07-31T10:28:58Z"}],"dependency_count":0,"dependent_count":0,"comment_count":2} +{"_type":"issue","id":"polylogue-t83e","title":"Origin misclassification: gemini-cli chats and Drive-cached transcripts detected as claude-code-session (6 native-id collisions)","description":"Forensics 2026-07-31. Two shapes, detector-level, still unfixed:\n1) 4 sessions from ~/.gemini/tmp/*/chats/session-*.jsonl carry origin=claude-code-session (2 with content: session-2026-06-08T11-44-c8b2c676 130 msgs, session-2026-04-26T07-13-5855c6f2 7 msgs; 2 empty). gemini-cli JSONL passes the claude-code record validator.\n2) 12 sessions from ~/.local/share/polylogue/drive-cache/gemini/*.jsonl.txt.json — Claude Code transcripts uploaded to AI Studio/Drive, re-downloaded, detected by content shape as claude-code. Raw rows have native_id NULL. CRITICAL: 6 of the 12 session native_ids (e.g. a952ffa4-73b0-48bd-a212-ebe5b9772d1e, 8c9f8c3d-4859-44cf-be9c-338803a8e7de) collide with genuinely-local claude-code raws — Drive copy and local file compete for the same session_id; whichever ingests last owns the row (silent overwrite channel). One session id is malformed: '080e6583-9713-4421-aafb-b6d3e4c2645d.jsonl.txt'.\nRepro: ATTACH source.db; SELECT s.session_id, r.source_path FROM sessions s JOIN src.raw_sessions r ON r.raw_id=s.raw_id WHERE s.origin='claude-code-session' AND r.source_path NOT LIKE '%/.claude/projects/%';\nAC: drive-cache re-acquisitions must not claim claude-code-session identity (acquisition-evidence should pin origin, not content shape alone); gemini-cli chats detect as gemini-cli-session; collision-hit sessions re-derived from local raws.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:55Z","created_by":"Sinity","updated_at":"2026-07-31T08:19:55Z","comments":[{"id":"019fb743-7338-7bfa-be3e-805fff52c828","issue_id":"polylogue-t83e","author":"Sinity","text":"Code trace (audit): both shapes reproducible at HEAD. (1) dispatch.py:222-320 — looks_like_gemini_cli only consulted when len(payloads)==1; multi-record gemini JSONL falls through to claude.looks_like_code (dispatch.py:253). (2) code_detection.py:21-33 looks_like_code matches bare presence of parentUuid/leafUuid/sessionId keys — gemini-cli schema carries top-level sessionId, so it passes. (3) drive-cache: detection is purely content-shape with no acquisition-context override, so cached uploads of real claude-code transcripts legitimately match the content detector but claim first-class claude-code-session identity. The #3428 tightenings (ab8a92c1a) do not cover these.","created_at":"2026-07-31T08:21:19Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} +{"_type":"issue","id":"polylogue-7qw4","title":"aggregate_message_stats has no test that exercises it -- mutation-proven","description":"FALSE-GREEN AUDIT 2026-07-31 (finding F3). MUTATION-VERIFIED.\n\ntests/unit/storage/test_store_ops.py:365 test_aggregate_message_stats_reports_role_counts_and_words\nclaims to verify role counts, word counts and attachment/provider rollups. It never imports or\ncalls the production function. Instead it calls a TEST-LOCAL SQL reimplementation,\n_aggregate_message_stats_native() at test_store_ops.py:290, whose own docstring says it\n'mirrors the legacy backend.queries.aggregate_message_stats contract'.\n\nProduction: polylogue/storage/sqlite/queries/stats.py:65 (async aggregate_message_stats),\nreached via SessionRepository.aggregate_message_stats -\u003e polylogue/cli/query_stats.py:146,148,\ni.e. the CLI 'read --all' stats surface.\n\nTHE TWO HAVE ALREADY DIVERGED, which proves the test never had to match production:\n production AggregateMessageStats returns origins: dict[str,int] (grouped by sessions.origin)\n test-local _MessageStats returns providers: dict[str,int] (via a local origin-\u003eprovider map)\n\nMUTATION EVIDENCE (isolated worktree, PYTHONPATH-shadowed, baseline-differenced):\n baseline: tests/unit/storage/test_store_ops.py -\u003e 67 passed, 0 pre-existing failures\n AG1: SUM(CASE WHEN role='assistant'...) changed to count role='tool' -\u003e 67 passed, 0 new failures\n AG2: SUM(word_count) AS words_approx changed to 0 AS words_approx -\u003e 67 passed, 0 new failures\nBoth mutations corrupt exactly what the test's NAME says it checks. Neither is caught.\n\nThe only other call sites in tests/ are an AsyncMock (test_query_exec_laws.py:198) and a\npytest-benchmark timing test with no correctness assertions (tests/benchmarks/test_reader_api.py:112).\nSo NO test anywhere in the suite asserts on the real function's output.\n\nAC:\n- test_aggregate_message_stats_reports_role_counts_and_words calls the production\n aggregate_message_stats and asserts on its return value.\n- The test-local _aggregate_message_stats_native reimplementation is DELETED (not kept as a\n second oracle -- it is the thing that hid the gap).\n- Anti-vacuity: confirm the AG1/AG2 mutations above now turn the test red.\n- Reconcile the origins/providers key-name divergence; per docs/provider-origin-identity.md\n 'origins' is the correct public vocabulary.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:50Z","created_by":"Sinity","updated_at":"2026-07-31T08:19:50Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-21qj","title":"Non-conversation files under .claude/projects ingested as sessions (analysis trio, toolu_* tool-results, journal)","description":"Forensics 2026-07-31. Detector treats any conversation-shaped JSON(L) under a watched project tree as a session. Materialized garbage:\n- claude-code-session:conversation_relationships — 96,748 EMPTY messages from analysis/index/conversation_relationships.jsonl (52MB graph index; 3rd-largest 'session' in the archive, 2.0% of all message rows).\n- claude-code-session:high_value_messages — 8,763 NON-empty messages (827,894 words) duplicated verbatim from other conversations (analysis/signal/high_value_messages.jsonl).\n- claude-code-session:problems_index — 0 messages (analysis/problem_solutions/problems_index.jsonl).\n- 3x claude-code-session:toolu_* from tool-results/toolu_*.json (Claude Code oversized-tool-output spill files; latest raw 2026-07-27 — no guard proven, POSSIBLY STILL ACTIVE).\n- claude-code-session:journal from subagents/workflows/wf_*/journal.jsonl.\n\nAC: (1) guard: files under tool-results/, analysis/, and any non-session JSONL in project trees classified as artifacts, never parse_as_session; (2) purge the 6 session rows + 105,514 messages; (3) regression fixture for each shape.\nRepro: SELECT native_id, message_count FROM sessions WHERE origin='claude-code-session' AND native_id IN ('conversation_relationships','high_value_messages','problems_index','journal') OR native_id LIKE 'toolu_%';","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:27Z","created_by":"Sinity","updated_at":"2026-07-31T08:19:27Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-ioz7","title":"Purge 4,945 agent-*.meta.json sidecar sessions (empty, residue of pre-2026-07-28 materialization)","description":"Live-archive forensics 2026-07-31 (dataset-forensics.html in /realm/inbox/polylogue-audits-2026-07-31/).\n\n4,945 empty sessions with native_id 'agent-\u003chash\u003e' materialized from subagents/**/agent-*.meta.json sidecar files (artifact_kind=agent_sidecar_meta, support_status=recognized_unparsed). Producer is FIXED: bound raws span acquired_at 2026-07-18 16:55 -\u003e 2026-07-28 18:03; the 165 meta.json raws acquired after 07-28 (through 07-31 05:30) correctly produce no session. What remains is residue: no retroactive cleanup ran. These dominate the empty-session census (4,945 of 5,257) and the NULL created_at census (they carry no timestamps).\n\nRepro SQL (read-only):\n ATTACH 'file:/realm/db/polylogue/source.db?mode=ro' AS src;\n SELECT count(*) FROM sessions s JOIN src.raw_sessions r ON r.raw_id=s.raw_id\n WHERE s.message_count=0 AND r.source_path LIKE '%.meta.json'; -- 4945\n\nAC: targeted deletion of exactly these session rows (join on raw source_path/artifact_kind, NOT 'check --cleanup' which would take all 5,257 empties including 61 legitimately-empty ones); raw rows + blobs retained; re-ingest does not resurrect them.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:04Z","created_by":"Sinity","updated_at":"2026-07-31T08:19:04Z","dependencies":[{"issue_id":"polylogue-ioz7","depends_on_id":"polylogue-zqph","type":"related","created_at":"2026-07-31T10:20:54Z","created_by":"Sinity","metadata":"{}"}],"comments":[{"id":"019fb743-12b3-7ca0-a44d-41a09e9ba9ac","issue_id":"polylogue-ioz7","author":"Sinity","text":"Code trace (audit 2026-07-31): producer fixed in two chokepoints — live ingest via OriginSpec/classify_artifact (pre-07-28) and rebuild replay via 251c19d34 (_is_declared_non_session_artifact in sources/revision_backfill.py), generalized by ab8a92c1a/cf0479701 (#3428, refuse filename-stem identity). Retroactive repair is ALREADY tracked as polylogue-zqph (open, deferred) and polylogue-ne6k found a blanket empty-delete unsafe. This bead's contribution: the audit taxonomy gives the exact safe deletion predicate (join raw source_path LIKE '%.meta.json' / artifact_kind='agent_sidecar_meta' = exactly 4,945 rows), which unblocks zqph without touching the 61 legitimately-empty sessions (47 claude-ai + 8 file-history-only + 6 trivial codex).","created_at":"2026-07-31T08:20:54Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} +{"_type":"issue","id":"polylogue-il50","title":"shipped-but-dead: 6 of 7 declared MCP prompts instruct callers to invoke tool names retired at the 10-tool cutover","description":"Audit 2026-07-31 (shipped-but-dead census). Surfaces dimension.\n\npolylogue/mcp/server_prompts.py:456-553 -- six of the seven prompts declared in\nTARGET_PROMPTS emit instructions naming tools that no longer exist on the current\n10-tool role-gated dispatcher surface:\n postmortem_last, decisions_about, unacknowledged_failures,\n sessions_touching_file, cost_of, resume_context\nThey reference retired pre-cutover names including find_abandoned_sessions,\nget_session_summary, list_marks, search, cost_rollups, find_resume_candidates,\nblackboard_list. An agent following these prompts calls tools that are not there.\n\nThe inverse gap exists too: five prompts are live-registered at\nserver_prompts.py:296-454 (analyze_errors, summarize_week, extract_code,\ncompare_sessions, extract_patterns) but are absent from TARGET_PROMPTS in\npolylogue/declarations/registry.py:520-528, so every completeness and discovery\nconsumer that reads the declaration is blind to them.\n\nNet: the declared set and the working set are disjoint in both directions --\ndeclared-but-broken (6) and working-but-undeclared (5).\n\nSupporting usage evidence (interpretation NOT settled): ops.db mcp_call_log holds\n2 rows total, and a scan found zero recorded invocations of any current 10-tool\nname versus 3,260 actions across 245 sessions for the retired surface. That is\nconsistent with either post-cutover lag or genuine non-adoption; it is reported\nas an open question, not as proof the new surface is unused.\n\nAlso in this cluster: polylogue/mcp/insight_tool_contracts.py has zero external\nreferences, orphaning 11 CLI-only insight types from MCP. Already governed by\nopen bead polylogue-t46.8.2 -- cross-reference, do not duplicate.","acceptance_criteria":"Every prompt in TARGET_PROMPTS names only tools that exist on the current dispatcher surface, and every live-registered prompt is declared. A test pins prompt-referenced tool names against the live tool table so the two cannot drift apart again. The mcp_call_log question is answered separately: either confirm the new surface is being used or open a distinct adoption bead.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:06:05Z","created_by":"Sinity","updated_at":"2026-07-31T09:26:29Z","started_at":"2026-07-31T09:26:29Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-z7ko","title":"shipped-but-dead: raw-authority ledger has never converged — 587,576 carried_forward plans vs 24 executed across 256 censuses","description":"Audit 2026-07-31 (shipped-but-dead census). MEASURED on the live archive. This is\nthe largest computed-then-discarded surface in the system by volume.\n\n select outcome_status, count(*) from raw_authority_census_plans:\n carried_forward 587,576\n executed 24\n\n select mode,lifecycle_status,fixed_point,count(*) from raw_authority_censuses:\n apply | completed | 0 | 84\n apply | interrupted | 0 | 2\n apply | planned | 0 | 1\n census | completed | 0 | 84\n dry_run | completed | 0 | 85\n\nfixed_point = 0 for ALL 256 censuses. Not one pass has ever reached a fixed point.\n84 apply-mode passes completed and 24 plans total were ever executed (0.004% of\nplanned work).\n\nStorage cost of the non-convergence: raw_authority_census_plans 570,216 rows and\nraw_authority_census_post_plans 570,216 rows in source.db (a DURABLE tier), over\n45,053 distinct plans in raw_authority_plans -- i.e. the same plan set is\nre-planned and carried forward every pass and re-persisted each time.\n\nDominant blocker (raw_authority_blockers, 4,420 rows):\n 4,393 \"accepted raw authority remains quarantined pending exact refinement proof\"\n 12 \"byte-proven browser rekey requires no retained membership census\"\n 7 \"accepted revision head and materialized session select different raw authority\"\n\nSo ~99.4% of blockers are one condition. The ledger is functioning as designed --\nit plans, blocks, and carries forward -- but the refinement proof that would let\nplans execute does not exist, so the machinery runs every pass and produces\nnothing but rows.\n\nUnlike the other census findings this is not \"no reader\" -- raw_reconciler.py and\nraw_authority.py do read these tables. It is the sharper variant: the output is\nread only by the machinery that regenerates it, and never reaches a state change.\n\nRelevant code: polylogue/storage/raw_authority.py:1109 (plan insert), :1577\n(post-plan insert), :2057/:2124 (outcome_status updates), raw_reconciler.py:1120,1515.","acceptance_criteria":"Either the 'accepted raw authority remains quarantined pending exact refinement proof' blocker gets the proof path that lets its 4,393 plans execute, or the census loop stops re-persisting a carried-forward plan set it cannot act on (plan once, reference thereafter). Success is measurable the same way this was: fixed_point reaches 1 on at least one census, or census_plans row growth per pass drops to the number of genuinely new plans.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:05:20Z","created_by":"Sinity","updated_at":"2026-07-31T08:05:20Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-kktg","title":"shipped-but-dead: web_content_constructs is the largest fully-unread table (155,287 rows, no reader at all)","description":"Audit 2026-07-31 (shipped-but-dead census). MEASURED on the live archive:\nweb_content_constructs holds 155,287 rows and has NO production reader.\n\nWritten every ingest from the ChatGPT/Claude parsers (SEARCH_QUERY, SEARCH_RESULT,\nCONTENT_REFERENCE, CANVAS, IMAGE_RESULT, ASYNC_TASK, SELECTED_SOURCE, TOKEN_BUDGET,\nVOICE_NOTE):\n polylogue/storage/sqlite/archive_tiers/write.py:2094,2124 INSERT\n polylogue/sources/parsers/chatgpt.py:246-371, claude/common.py:305,329\n\nEvery production SELECT, exhaustively:\n polylogue/pipeline/services/ingest_batch/_core.py:235,248,261\n -- orphan-integrity sweep that reads the table only to DELETE from it\n polylogue/demo/constructs.py:116\n -- SELECT COUNT(*) ... WHERE construct_type='token_budget', a demo smoke probe\n write.py:2115,2118,4921 -- DELETEs\n\nUnlike file_edits/session_refs (polylogue-nua7) there is not even a\nqueries/ module: no repository accessor, no typed record, no CLI/MCP/DSL/insight\npath. `WebConstructType` appears outside sources/parsers/ only in core/enums.py\n(the definition) and archive_tiers/index.py (the CHECK constraint).\n\nThe schema was built expecting reads: index.py:426-480 declares dedicated indexes\non (session_id, construct_type), message_id, url, and query. None are ever used\nby a query.\n\nDistinct from open beads polylogue-zocm (extraction *quality*) and polylogue-u8x7\n(union-merge durability) -- neither states the table has no read surface.","acceptance_criteria":"web_content_constructs is either (a) exposed through a real query path -- DSL unit source, read --view, or MCP verb -- so the indexes it already carries are used, or (b) retired via INDEX_BENIGN_DDL_REGISTRY along with its parser-side construction. Decision recorded; the demo COUNT(*) probe is not accepted as a reader.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:03:33Z","created_by":"Sinity","updated_at":"2026-07-31T08:03:33Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-nua7","title":"shipped-but-dead: unread-wire batch (2qx.4) landed 3 tables + full reader chains with zero surface consumers","description":"Audit 2026-07-31 (shipped-but-dead census). Bead polylogue-2qx.4 is CLOSED, but\nthe batch it shipped is unreachable from every product surface.\n\nMEASURED. Three dedicated index-tier tables are written on every ingest and read\nby nothing above the storage layer:\n\n file_edits 76,105 rows (live archive)\n session_refs 18,949 rows\n session_agent_policies populated\n\nEach got a full, correct reader chain that terminates at the repository:\n\n queries/file_edits.py -\u003e query_store_archive.py:274,278 -\u003e repository/archive/sessions.py:133,142\n queries/session_refs.py -\u003e query_store_archive.py:285,289 -\u003e repository/archive/sessions.py:148,152\n queries/session_agent_policies.py-\u003e query_store_archive.py:263,267 -\u003e repository/archive/sessions.py:125,131\n\nVerified: `rg -w \u003caccessor\u003e . | grep -v '^./polylogue/storage/'` returns NOTHING\nfor all six repository accessors except two hits in a single test file,\ntests/unit/storage/test_unread_wire_batch_v46.py (lines 216,256,295,328). No CLI\nverb, MCP tool, insight, or API path reaches any of them.\n\nFour helpers have zero references anywhere in the repo outside their own\n__all__ entry (not even a test):\n queries/file_edits.py:36 get_file_edit\n queries/file_edits.py:97 sync_get_file_edits_for_session\n queries/session_refs.py:78 sync_get_session_refs\n queries/session_agent_policies.py:97 sync_session_agent_policies_batch\n\nThis is the exemplar of the defect class: the pr-link finding was \"fixed\" by\nadding a reader, and the fix recreated the same gap one layer up.","acceptance_criteria":"Each of file_edits / session_refs / session_agent_policies either (a) gains a real surface consumer (CLI view, MCP verb, or insight) that an operator can invoke, or (b) is dropped via INDEX_BENIGN_DDL_REGISTRY with its reader chain deleted. The four zero-reference helpers are deleted or wired. A decision is recorded per table, not left in a third state.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:02:55Z","created_by":"Sinity","updated_at":"2026-07-31T08:02:55Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-nua7","title":"shipped-but-dead: unread-wire batch (2qx.4) landed 3 tables + full reader chains with zero surface consumers","description":"Audit 2026-07-31 (shipped-but-dead census). Bead polylogue-2qx.4 is CLOSED, but\nthe batch it shipped is unreachable from every product surface.\n\nMEASURED. Three dedicated index-tier tables are written on every ingest and read\nby nothing above the storage layer:\n\n file_edits 76,105 rows (live archive)\n session_refs 18,949 rows\n session_agent_policies populated\n\nEach got a full, correct reader chain that terminates at the repository:\n\n queries/file_edits.py -\u003e query_store_archive.py:274,278 -\u003e repository/archive/sessions.py:133,142\n queries/session_refs.py -\u003e query_store_archive.py:285,289 -\u003e repository/archive/sessions.py:148,152\n queries/session_agent_policies.py-\u003e query_store_archive.py:263,267 -\u003e repository/archive/sessions.py:125,131\n\nVerified: `rg -w \u003caccessor\u003e . | grep -v '^./polylogue/storage/'` returns NOTHING\nfor all six repository accessors except two hits in a single test file,\ntests/unit/storage/test_unread_wire_batch_v46.py (lines 216,256,295,328). No CLI\nverb, MCP tool, insight, or API path reaches any of them.\n\nFour helpers have zero references anywhere in the repo outside their own\n__all__ entry (not even a test):\n queries/file_edits.py:36 get_file_edit\n queries/file_edits.py:97 sync_get_file_edits_for_session\n queries/session_refs.py:78 sync_get_session_refs\n queries/session_agent_policies.py:97 sync_session_agent_policies_batch\n\nThis is the exemplar of the defect class: the pr-link finding was \"fixed\" by\nadding a reader, and the fix recreated the same gap one layer up.","acceptance_criteria":"Each of file_edits / session_refs / session_agent_policies either (a) gains a real surface consumer (CLI view, MCP verb, or insight) that an operator can invoke, or (b) is dropped via INDEX_BENIGN_DDL_REGISTRY with its reader chain deleted. The four zero-reference helpers are deleted or wired. A decision is recorded per table, not left in a third state.","notes":"Resolved in PR #3442 (feature/wire-captured-unread-data): file_edits and session_agent_policies now reachable via MCP get(projection=file-edits|agent-policies), CLI read --view file-edits|agent-policies, and API get_file_edits()/get_agent_policies(). session_refs already wired via prior PRs #3425/#3431, verified unchanged. All four zero-reference helpers deleted. Verified via real CLI/MCP end-to-end tests, not storage-layer-only tests.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:02:55Z","created_by":"Sinity","updated_at":"2026-07-31T10:48:35Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-gucv","title":"The schema-versioning gate is version-keyed and cannot see parser-content drift: PR #3428 shipped a reparse-requiring classifier fix green","description":"AUDIT FINDING (claimed-vs-enforced invariants sweep, 2026-07-31). Verdict:\nASSERTED. The gate is keyed to a version integer; the failure mode does not\nchange a version integer.\n\nRelated, do not duplicate: polylogue-9rw0 (its description already concedes\n\"parser-content drift is NOT covered\") and polylogue-zqph (the ~5,257-row repair\npass deferred out of PR #3428). This bead is the missing GATE, not the repair.\n\nCLAIM (CLAUDE.md, Schema regimes): every index bump above the compatibility\nfloor declares a delta class; \"Only a SEMANTIC_REPARSE delta -- one whose result\ndepends on parser semantics -- routes to polylogue ops reset --index \u0026\u0026\npolylogued run. A bump without a declaration is a policy violation.\"\n\nWHAT THE LINT CHECKS. devtools/verify_schema_upgrade_lane.py, main() at :243-281,\ndoes exactly four things:\n 1. _collect_upgrade_helpers (:98-114) AST name-pattern scan for legacy\n upgrade-helper function shapes\n 2. _invalid_migration_paths (:174-188) durable migration file naming/location\n 3. index_delta_declaration_report(INDEX_SCHEMA_VERSION) (:253 -\u003e\n storage/sqlite/lifecycle.py:473-493) -- the version-gap check\n 4. _invalid_benign_ddl_entries (:144-171) benign-DDL registry shapes\nCheck 3 has real teeth: expected = range(FLOOR+1, INDEX_SCHEMA_VERSION+1) and it\nfails on any version in that range with no IndexDeltaDeclaration. It is wired\ninto the REQUIRED per-PR lint job (.github/workflows/ci.yml:36) -- confirmed, it\nis not skipped the way the heavy `test` job is. This half works.\n\nTHE STRUCTURAL BLINDNESS. Check 3 reads one integer and diffs it against a static\ntable. It has zero visibility into polylogue/sources/parsers/** or\npolylogue/archive/artifact_taxonomy/**. A change that alters classification\noutput FOR IDENTICAL INPUT BYTES needs a reparse but produces NO version bump at\nall -- so the gate that would fire never fires.\n\nTHE CONCRETE CASE, merged 2026-07-31T07:33Z. PR #3428, \"fix(sources): require\npositive conversation evidence before session classification\":\n archive/artifact_taxonomy/support.py looks_like_record_entry() -- removed\n bare \"type\" as sufficient evidence, added _TYPE_ENVELOPE_MARKERS\n co-occurrence. Identical bytes now classify differently than yesterday.\n sources/parsers/claude/code_detection.py looks_like_code() -- same shape\n sources/revision_backfill.py unified the rebuild-replay gate with\n the live-ingest gate\nINDEX_SCHEMA_VERSION stayed 46; lifecycle.py untouched; the PR body itself says\n\"No schema change\" and \"This PR only stops NEW phantoms going forward\", deferring\n~5,257 already-misclassified rows to polylogue-zqph.\n`devtools lab policy schema-versioning` ran and was GREEN -- correctly, per its\ncontract, and uselessly for this defect.\n\nRUNTIME MAKES IT PERMANENT, and this corrects CLAUDE.md's wording. CLAUDE.md says\nan undeclared bump means \"the archive silently falls back to full raw replay\".\nMeasured: it does not. bootstrap.py:226-229 raises a loud RuntimeError and no\ncaller swallows it (checked all 18 initialize_archive_database call sites for\nexcept RuntimeError -- none). The genuinely SILENT path is the one PR #3428 took:\nsame version -\u003e bootstrap.py:174-192 applies only the benign-DDL registry and\nopens as-is. No error, no log line, no debt row. Stale classification persists\nindefinitely.\n\nHOW ANYONE FOUND OUT: they didn't, automatically. bead polylogue-9ykn came from a\nmanual live-archive audit, not a signal.\n\nBLAST RADIUS: every archive generation at the same index version keeps stale\nderived rows forever. This is aggz Invariant 3 -- \"derived state carries the\nversion of the logic that derived it\" -- and its absence is exactly what makes a\ncorrected classifier inert on existing data.\n\nAC:\n- A parser/classifier fingerprint exists such that changing classification logic\n invalidates the rows it produced, without an operator command. (aggz Invariant 3\n / polylogue-9dxn is the mechanism; this bead is the gate that consumes it.)\n- The gap is stated where a developer will hit it: the schema-versioning lint or\n its docs say in one line that parser-content drift is out of its scope, so a\n green run is not read as \"no reparse needed\".\n- A test or lint fails when a file under sources/parsers/ or\n archive/artifact_taxonomy/ changes classification-affecting logic with no\n corresponding reparse declaration -- or, if that is judged infeasible, the\n decision and its reasoning are recorded on this bead rather than left implicit.\n","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:52:09Z","created_by":"Sinity","updated_at":"2026-07-31T07:52:09Z","labels":["area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-oitx","title":"Fabricated coverage values surviving #3429: invariant_ready→100.0, Prometheus embedding 100%, placeholder zeros","description":"Silent-degradation audit 2026-07-31; re-verified at HEAD AFTER eb5796f49 (#3429) merged — these siblings survive. (1) daemon/fts_status.py:355 and :520: coverage_pct emits '100.0 if invariant_ready else 0.0' when source_rows==0 — conflates structural readiness (triggers exist) with a measured 100% coverage; only source_rows==0 itself justifies 100. (2) daemon/metrics.py:811-813: embedding coverage_percent = 100.0 when eligible_sessions==0 but total_sessions\u003e0 — feeds Prometheus gauge polylogue_embedding_coverage_percent (~line 902), so an alerting pipeline sees 100% during a genuine measurement gap (schema branch never queried). (3) storage/fts/fts_lifecycle.py:849-850: message_fts_readiness_sync(verify_total_rows=False) returns literal indexed_rows=0,total_rows=0; daemon/convergence_stages.py:1095 falls back to counts=(0,0,0,0,0) when no fts_freshness_state row exists and durably writes READY|0|0 — placeholder zeros standing in for an uncomputed COUNT(*), defended only by freshness_ready_record_trusted() distrust logic (storage/fts/freshness.py:59-91) that every reader must keep in sync. Write NULL/not-measured instead of 0. (4) daemon/status_snapshot.py:298-303: _minimal_status_payload hardcodes raw_parse_failures/raw_validation_failures/raw_quarantined/raw_maintenance_failures/raw_detection_warnings = 0 during the minimal/refreshing window without the require_fresh_snapshot gate raw_frontier_integrity gets — CLI (cli/commands/status.py:1338) then treats unmeasured as zero-failures. Verdicts: MUST-FAIL-LOUD for (2), SHOULD-RECORD for the rest.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:49:22Z","created_by":"Sinity","updated_at":"2026-07-31T07:49:22Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-ppkj","title":"Lineage truncation signal is computed then discarded: polylogue read and HTTP silently return partial transcripts","description":"AUDIT FINDING (claimed-vs-enforced invariants sweep, 2026-07-31). Verdict:\nASSERTED on 2 of 3 read call chains; the signal is computed and then discarded.\n\nCLAIM: forks/resumes/subagents/auto-compaction store only the child's divergent\ntail plus branch_point_message_id + inheritance; \"reads recompose parent-up-to-\nbranch + child-tail\" (CLAUDE.md, Lineage normalization).\n\nWHAT HAPPENS WHEN THE BRANCH POINT IS DANGLING. Composition does not raise and\ndoes not fall back to the whole parent. It returns ONLY the child's own tail --\ni.e. the operator sees a conversation that silently begins mid-thread.\n\nThe system knows this. Both composition implementations compute an explicit\ntruncation signal:\n storage/sqlite/archive_tiers/write.py:1271-1287\n sets lineage_complete=False,\n lineage_truncation_reason=LINEAGE_TRUNCATION_DANGLING_BRANCH_POINT\n storage/sqlite/queries/message_query_reads.py:226-238\n computes the identical DANGLING_BRANCH_POINT / DEPTH_LIMIT reasons\n\nTHE SIGNAL IS THROWN AWAY. message_query_reads.py:134-137:\n\n messages, _completeness = await get_messages_with_lineage_completeness(\n conn, session_id, _compose_in_position_order=_compose_in_position_order\n )\n return messages\n\nNo caller outside that module invokes get_messages_with_lineage_completeness\ndirectly (verified: grep -rln for the symbol excluding tests returns only its own\nfile). Every real consumer uses the signal-dropping get_messages:\n storage/repository/archive/sessions.py:79,98,112 (repository .get/.get_messages)\n storage/sqlite/queries/message_query_reads.py:393 (inside get_messages_paginated)\n\nAnd the Session domain model carries no completeness field at all\n(archive/session/domain_models.py, storage/hydrators.py: zero \"lineage\" matches),\nso the CLI's own descriptor builder hard-codes it away:\n rendering/semantic_cards.py:288-312 lineage_descriptor_from_session()\n returns LineageDescriptor(..., lineage_complete=None, ...)\n\nAFFECTED SURFACES:\n cli/messages.py:108-114 polylogue read / messages -- the primary human\n surface. Neither the markdown render nor the\n json/ndjson payloads carry a truncation flag.\n daemon/http.py:3429, 4628-4647 GET /api/sessions/:id/messages -- same blind call.\nSAFE SURFACE (for contrast, proving the plumbing is possible):\n mcp/archive_support.py:676-677 propagates lineage_complete +\n lineage_truncation_reason onto the MCP payload;\n rendering/semantic_cards.py:1174-1175 renders \"composed transcript is truncated\".\n\nLIVE DATA (measured, file:/realm/db/polylogue/index.db?mode=ro):\n sessions with non-null branch_point_message_id 537\n branch_point_message_id NOT present in messages.message_id (dangling) 0\n session_links total / unresolved / quarantined / repaired 9333 / 1426 / 0 / 0\n deepest live prefix-sharing chain 60 hops\nThe bug is DORMANT today (0 dangling), not firing. It is a real gap, not a\nhypothetical: the moment any branch point falls out of sync the CLI and HTTP\nsurfaces render a short conversation with no indication.\n\nWHAT KEEPS IT DORMANT, and why that is thin: two repairs exist and neither is a\nperiodic sweep.\n write.py:2746 -\u003e :4756 _repair_stale_prefix_branch_points_db -- inline, per\n save, scoped to impacted sessions; repairs ONLY the stale-parent-id-suffix\n shape, skips ambiguous matches silently (write.py:4732-4733,4751).\n daemon/lineage_startup.py:31 (via daemon/cli.py:215) -- the full unscoped scan,\n called exactly once per daemon process START. It is NOT a DaemonConverger\n stage (grep of daemon/convergence*.py for the symbol: no matches), so a\n branch point that goes dangling between restarts is never re-checked.\n\nBLAST RADIUS: silent data-fidelity loss on the two surfaces a human actually\nreads. A truncated transcript is indistinguishable from a short conversation.\nRanked above the layering/doc findings because it corrupts what the user is\nshown, not merely what a report claims.\n\nAC:\n- polylogue read and GET /api/sessions/:id/messages surface lineage_complete /\n lineage_truncation_reason, in both human and machine output.\n- The signal reaches those surfaces from the same computation the MCP path uses;\n get_messages either propagates it or its signal-dropping wrapper is deleted.\n- A test composes a session with a deliberately dangling branch_point_message_id\n and asserts the CLI/HTTP output is marked truncated (fails against current code).\n- Decide explicitly whether the startup-only full repair should become a periodic\n convergence stage, and record the decision either way.\n","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:48:54Z","created_by":"Sinity","updated_at":"2026-07-31T07:48:54Z","labels":["area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -86,7 +123,7 @@ {"_type":"issue","id":"polylogue-azf7","title":"Codex sidecar discovery failure is frozen forever as an empty enrichment snapshot","description":"Silent-degradation audit 2026-07-31. pipeline/services/ingest_batch/_core.py:1336-1351: 'except Exception: logger.exception(...); discovered = {}' then persists {} via write_history_sidecar. Because read_earliest_history_sidecar_for_path (storage/sqlite/archive_tiers/source_write.py:888) freezes the FIRST persisted snapshot per (origin, source_path) by design (polylogue-ih67 AC#3/4), a transient disk/parse error during discovery becomes a durable, uncorrectable data-quality defect: every future ingest of that source_path replays the empty snapshot and enrichment is never retried. Same shape at ingest_worker.py:583-596 (per-record path, falls back to unenriched sessions, logged but not counted in summary). Fix: do not persist a snapshot when discovery raised — only persist genuinely-empty looked-and-found-nothing results; add a sessions_unenriched counter to the ingest summary. Verdict: MUST-FAIL-LOUD.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:48:25Z","created_by":"Sinity","updated_at":"2026-07-31T07:48:25Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-lyr2","title":"Session native_id is stored raw but its FK is computed stripped -- the ab5bad1f bug class, unfixed at session level","description":"AUDIT FINDING (claimed-vs-enforced invariants sweep, 2026-07-31). Verdict: ASSERTED\nat the session level; the identical bug class is ENFORCED at the message level.\n\nCLAIM: \"Identity is computed, never stored redundantly -- every id is a SQLite\ngenerated column\" (CLAUDE.md, docs/internals.md). sessions.session_id is\nGENERATED ALWAYS AS (origin || ':' || native_id) STORED UNIQUE\n(polylogue/storage/sqlite/archive_tiers/index.py:164).\n\nTHE DIVERGENCE. There are two Python implementations of the session-id formula\nand they disagree on whitespace:\n\n polylogue/core/identity_law.py:33 session_id() -\u003e STRIPS native_id\n (via _required_text, line 20-24)\n polylogue/pipeline/ids.py:153 session_id() -\u003e does NOT strip; it only\n checks non-emptiness after strip (line 168)\n then interpolates the RAW value (line 171)\n\npolylogue/storage/sqlite/archive_tiers/write.py binds the raw value into the\nsessions row but computes the child FK from the stripped one, inside the same\nfunction:\n\n write.py:375 native_id = session.provider_session_id # RAW\n write.py:376 session_id = archive_session_id(origin.value, native_id) # STRIPPED\n write.py:553 ... INSERT INTO sessions (...) VALUES (native_id, ...) # RAW\n\nSo for provider_session_id = \" abc \":\n sessions.session_id (SQL generated column, from the RAW stored native_id)\n = \"codex-session: abc \"\n the session_id bound as the FK into messages (from identity_law, STRIPPED)\n = \"codex-session:abc\"\n-\u003e FOREIGN KEY violation; the write/rebuild transaction aborts.\n\nWHY THIS IS NOT HYPOTHETICAL. This is the exact bug class of incident ab5bad1f,\nwhich killed a 10-hour rebuild. It was fixed AT THE MESSAGE LEVEL by introducing\na single-source-of-truth normalizer whose docstring names the incident:\n\n write.py:5218-5245 _stored_message_native_id()\n \"This is the single source of truth for message identity (polylogue rebuild\n ab5bad1f FK-failure fix): both the _write_messages INSERT and _message_id\n ... MUST route through this helper, or the two computations can diverge and\n a later blocks insert can reference a message_id that was never written.\"\n\nThat fix is guarded by tests/property/test_message_identity_normalization.py\n(test_db_generated_message_id_matches_python_identity_law).\n\nTHE SESSION LEVEL HAS NEITHER. Confirmed with two independent greps:\n git grep -n \"_stored_session_native_id\" -\u003e no matches\n git grep -n \"provider_session_id\" -- 'polylogue/**/*.py' | grep -i strip\n -\u003e only pipeline/ids.py:168, an emptiness CHECK, not a normalization\npolylogue/sources/parsers/base_models.py:300 declares\nParsedSession.provider_session_id as a plain Pydantic str with no strip\nvalidator, so nothing upstream prevents a padded native id reaching the writer.\n\nLIVE DATA (measured, file:/realm/db/polylogue/index.db?mode=ro):\n SELECT COUNT(*) FROM sessions WHERE native_id != trim(native_id); -\u003e 0\n SELECT COUNT(*) FROM sessions WHERE instr(native_id,':') \u003e 0; -\u003e 8781\nThe defect is LATENT, not active. Colon-bearing native ids are common (8781) and\nare safe by construction (Origin enum values contain no ':' and sessions.origin\ncarries a CHECK against that enum, so the first ':' always terminates the origin).\nWhitespace is the unguarded axis.\n\nBLAST RADIUS: narrow but loud. Fails as an aborted transaction, not silent\ncorruption -- same shape as ab5bad1f, which cost a 10-hour rebuild. Any parser\nthat derives provider_session_id from a filesystem path segment, an external\nidentifier, or a scraped field can emit padding.\n\nAC:\n- A _stored_session_native_id-equivalent normalizer exists and is the single\n value used by BOTH the sessions INSERT and the archive_session_id call in\n write.py, mirroring the message-level fix.\n- A session-level sibling of tests/property/test_message_identity_normalization.py\n asserts the SQL-generated sessions.session_id equals the Python identity_law\n computation for whitespace/empty/surrogate-bearing provider_session_id inputs,\n and fails against the current code.\n- The two divergent implementations are reconciled or one is deleted: either\n pipeline/ids.py:session_id routes through core.identity_law, or the audit\n records why two intentionally-different functions must coexist.\n","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:48:01Z","created_by":"Sinity","updated_at":"2026-07-31T07:48:01Z","labels":["area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-zqph","title":"Repair pass for existing empty-session phantom rows (polylogue-9ykn dataset cleanup)","description":"Follow-up to polylogue-9ykn: the ingest-time classifier fix (looks_like_record_entry / looks_like_code\ntype-only overmatch, and unifying the live-ingest classify_artifact gate with the\nrevision_backfill.py replay/rebuild gate) stops NEW phantom sessions of the\nconversation_relationships.jsonl / problems_index.jsonl / graph-edge-index shape from being\ncreated going forward, on both the live daemon path and any polylogue ops reset --index rebuild.\n\nIt deliberately does NOT delete or touch any of the existing ~5,257 empty-session rows already in\nthe live archive (per explicit operator scoping: dataset repair is a separate, carefully-scoped\nconcern). This bead tracks that repair pass.\n\nWhat the repair needs to do, precisely (do not blanket-delete via repair_empty_sessions /\n`polylogue check --cleanup` -- see polylogue-ne6k, which found that predicate cannot distinguish\na legitimately-empty session, e.g. the 832 the 2026-07-22 hook-inflation postmortem chose to\nretain, from corruption debris):\n\n1. Re-run classification (the now-fixed classify_artifact / looks_like_record_entry /\n looks_like_code) against each existing empty session's ORIGINAL raw_sessions source_path +\n raw bytes to determine: would this record be admitted as a session under the current\n classifier, or refused?\n2. For rows the current classifier would refuse (the conversation_relationships.jsonl-shaped\n phantoms, and any other now-caught non-conversational content): these are safe candidates for\n targeted reclassification/removal from index.db (rebuildable tier) -- NOT source.db (durable\n raw evidence must be retained per the repo's schema regime).\n3. For rows the current classifier would still admit (genuinely-empty-but-valid sessions, e.g. a\n real Claude Code/Codex session that has zero turns so far, or the 832 retained browser-capture\n stubs): leave untouched.\n4. Needs explicit operator sign-off before running against the live archive (per CLAUDE.md's\n destructive-operation and schema-regime discipline) -- this bead should NOT be closed by an\n agent unilaterally running the repair.\n\nEvidence base: polylogue-9ykn's own measurement (5,255 zero-message sessions, 22.6% of the\n23,296-session archive at measurement time; 5,193 claude-code-session, 46 claude-ai-export, 17\ncodex-session) plus polylogue-gvgi's single dominant phantom (conversation_relationships.jsonl,\n96,748 empty messages, ~95% of the archive's zero-block messages -- tracked/repaired separately\nper gvgi's own AC, coordinate rather than duplicate).","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T06:27:42Z","created_by":"Sinity","updated_at":"2026-07-31T06:27:42Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-lzh8","title":"Declare SEMANTIC_REPARSE index bump for Claude Workflow artifact classification (PR #3088)","description":"Investigation 2026-07-31 (worktree agent-a7335b82eed35c7cf), triggered by\noperator report that Claude Code Workflow artifacts appear BOTH normalized\nAND independently ingested raw as empty sessions.\n\nFINDING: the classification code is already correct. polylogue/archive/\nartifact_taxonomy/runtime.py:classify_artifact_path consults OriginSpec's\nartifact_rules (polylogue/sources/origin_specs.py, added by 1e0246d77 / PR\n#3088, \"admit Claude Workflow artifacts through OriginSpec\", 2026-07-18) and\ncorrectly returns parse_as_session=False for workflow_run_snapshot,\nworkflow_journal, agent_sidecar_meta, and adopt_manifest artifact kinds.\nVerified directly against the live paths (python3 -c\n\"classify_artifact_path(...)\") -- current code classifies them correctly.\n\nBut 1e0246d77 changed session/fact classification semantics for an already-\nrunning archive WITHOUT declaring an INDEX_SCHEMA_VERSION bump in\npolylogue/storage/sqlite/lifecycle.py (checked: no lifecycle.py/index.py\nchange in that commit, and no v33-v47 IndexDeltaDeclaration references\npolylogue-2qx.2 or the Workflow admission PR). Per docs/architecture (\"Schema\nregimes\"), only a declared SEMANTIC_REPARSE delta routes an index.db through\n`polylogue ops reset --index \u0026\u0026 polylogued run`; a semantic parser change\nwith no declared bump leaves already-materialized wrong-classification rows\nuntouched forever, because the daemon's fast-forward convergence has no\nsignal that anything changed.\n\nMEASURED LIVE IMPACT (index.db read-only query, 2026-07-31):\n zero-message claude-code-session rows total: 5,193\n of these, joined to a source_path under a `workflows/` artifact family: 172\n agent_sidecar_meta (subagents/workflows/*/agent-*.meta.json): 164\n workflow_run_snapshot (workflows/wf_*.json): 7\n other (workflow_journal / adopt_manifest): 1\n acquired_at_ms range for these 172: 2026-07-14 10:52 UTC .. 2026-07-26\n 19:18 UTC -- i.e. ALL acquired while the deployed daemon build predated\n the fix. The sinnix flake's polylogue input only advanced to a revision\n containing 1e0246d77 on 2026-07-29 (flake.lock lastModified\n 1785367887 = 2026-07-29 23:31 UTC; `git merge-base --is-ancestor` confirms\n 1e0246d77 is an ancestor of the pinned rev 5e23e6a). So this is deploy-lag\n contamination the fix code cannot self-heal without a reparse trigger, not\n a currently-active defect in the shipped classification logic.\n\nSeparately, polylogue-omsw's tool-result-sidecar and file-history-snapshot\npopulations are a DIFFERENT, still-open acquisition-scope gap (not covered\nby this bead) -- do not conflate the two when scoping remediation.\n\nDO NOT execute the reset live from this investigation; this bead exists to\nmake the repair describable and consented rather than silent. Per this\nrepo's ops.db/index.db durability rules, `polylogue ops reset --index` is a\ndisposable-tier rebuild, not durable-data loss, but it is still a\nconsequential live-daemon action (extended downtime rebuilding ~20K\nsessions) that needs explicit operator scheduling, not an agent-triggered\nversion bump buried in an unrelated PR.\n","acceptance_criteria":"1. polylogue/storage/sqlite/lifecycle.py gets a new IndexDeltaDeclaration bumping INDEX_SCHEMA_VERSION with classes=(SEMANTIC_REPARSE,), whose comment names 1e0246d77/#3088 as the retroactive semantic change being captured and cites the measured live-impact counts. 2. The bump lands in a PR whose body explicitly tells the operator a 'polylogue ops reset --index \u0026\u0026 polylogued run' is now required, so it is scheduled deliberately (not silently triggered by routine deploy). 3. After the rebuild, the 172+ contaminated sessions reclassify to their correct non-session disposition (verified by re-running the same index.db query this bead's evidence used and confirming zero remain). 4. devtools lab policy schema-versioning stays green.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T05:59:40Z","created_by":"Sinity","updated_at":"2026-07-31T07:51:22Z","started_at":"2026-07-31T07:51:22Z","dependencies":[{"issue_id":"polylogue-lzh8","depends_on_id":"polylogue-2qx.2","type":"related","created_at":"2026-07-31T07:59:51Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-lzh8","depends_on_id":"polylogue-9ykn","type":"related","created_at":"2026-07-31T07:59:50Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-lzh8","depends_on_id":"polylogue-omsw","type":"related","created_at":"2026-07-31T07:59:50Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-lzh8","title":"Declare SEMANTIC_REPARSE index bump for Claude Workflow artifact classification (PR #3088)","description":"Investigation 2026-07-31 (worktree agent-a7335b82eed35c7cf), triggered by\noperator report that Claude Code Workflow artifacts appear BOTH normalized\nAND independently ingested raw as empty sessions.\n\nFINDING: the classification code is already correct. polylogue/archive/\nartifact_taxonomy/runtime.py:classify_artifact_path consults OriginSpec's\nartifact_rules (polylogue/sources/origin_specs.py, added by 1e0246d77 / PR\n#3088, \"admit Claude Workflow artifacts through OriginSpec\", 2026-07-18) and\ncorrectly returns parse_as_session=False for workflow_run_snapshot,\nworkflow_journal, agent_sidecar_meta, and adopt_manifest artifact kinds.\nVerified directly against the live paths (python3 -c\n\"classify_artifact_path(...)\") -- current code classifies them correctly.\n\nBut 1e0246d77 changed session/fact classification semantics for an already-\nrunning archive WITHOUT declaring an INDEX_SCHEMA_VERSION bump in\npolylogue/storage/sqlite/lifecycle.py (checked: no lifecycle.py/index.py\nchange in that commit, and no v33-v47 IndexDeltaDeclaration references\npolylogue-2qx.2 or the Workflow admission PR). Per docs/architecture (\"Schema\nregimes\"), only a declared SEMANTIC_REPARSE delta routes an index.db through\n`polylogue ops reset --index \u0026\u0026 polylogued run`; a semantic parser change\nwith no declared bump leaves already-materialized wrong-classification rows\nuntouched forever, because the daemon's fast-forward convergence has no\nsignal that anything changed.\n\nMEASURED LIVE IMPACT (index.db read-only query, 2026-07-31):\n zero-message claude-code-session rows total: 5,193\n of these, joined to a source_path under a `workflows/` artifact family: 172\n agent_sidecar_meta (subagents/workflows/*/agent-*.meta.json): 164\n workflow_run_snapshot (workflows/wf_*.json): 7\n other (workflow_journal / adopt_manifest): 1\n acquired_at_ms range for these 172: 2026-07-14 10:52 UTC .. 2026-07-26\n 19:18 UTC -- i.e. ALL acquired while the deployed daemon build predated\n the fix. The sinnix flake's polylogue input only advanced to a revision\n containing 1e0246d77 on 2026-07-29 (flake.lock lastModified\n 1785367887 = 2026-07-29 23:31 UTC; `git merge-base --is-ancestor` confirms\n 1e0246d77 is an ancestor of the pinned rev 5e23e6a). So this is deploy-lag\n contamination the fix code cannot self-heal without a reparse trigger, not\n a currently-active defect in the shipped classification logic.\n\nSeparately, polylogue-omsw's tool-result-sidecar and file-history-snapshot\npopulations are a DIFFERENT, still-open acquisition-scope gap (not covered\nby this bead) -- do not conflate the two when scoping remediation.\n\nDO NOT execute the reset live from this investigation; this bead exists to\nmake the repair describable and consented rather than silent. Per this\nrepo's ops.db/index.db durability rules, `polylogue ops reset --index` is a\ndisposable-tier rebuild, not durable-data loss, but it is still a\nconsequential live-daemon action (extended downtime rebuilding ~20K\nsessions) that needs explicit operator scheduling, not an agent-triggered\nversion bump buried in an unrelated PR.\n","acceptance_criteria":"1. polylogue/storage/sqlite/lifecycle.py gets a new IndexDeltaDeclaration bumping INDEX_SCHEMA_VERSION with classes=(SEMANTIC_REPARSE,), whose comment names 1e0246d77/#3088 as the retroactive semantic change being captured and cites the measured live-impact counts. 2. The bump lands in a PR whose body explicitly tells the operator a 'polylogue ops reset --index \u0026\u0026 polylogued run' is now required, so it is scheduled deliberately (not silently triggered by routine deploy). 3. After the rebuild, the 172+ contaminated sessions reclassify to their correct non-session disposition (verified by re-running the same index.db query this bead's evidence used and confirming zero remain). 4. devtools lab policy schema-versioning stays green.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T05:59:40Z","created_by":"Sinity","updated_at":"2026-07-31T08:18:10Z","started_at":"2026-07-31T07:51:22Z","closed_at":"2026-07-31T08:18:10Z","close_reason":"Declared the missing v48 SEMANTIC_REPARSE IndexDeltaDeclaration for #3088/1e0246d77 (storage/sqlite/lifecycle.py + INDEX_SCHEMA_VERSION bump in archive_tiers/index.py), citing the measured live-impact counts (172 zero-message sessions: 164 agent_sidecar_meta + 7 workflow_run_snapshot + 1 other). AC1-2 satisfied (declaration lands, PR body states the operator command required). AC3 (172 rows reclassify to zero) is explicitly deferred -- NOT executed per this bead's own DO-NOT-EXECUTE instruction; the operator must run 'polylogue ops reset --index \u0026\u0026 polylogued run' deliberately. AC4 (devtools lab policy schema-versioning stays green) verified. Also investigated why the lint didn't catch PR #3088's original undeclared bump: it only checks declaration-table completeness against the CURRENT INDEX_SCHEMA_VERSION constant, never inspects classification source files, so it structurally cannot detect a missing bump, only an undeclared existing one. Filed polylogue-qs4b to design a real fix (content-fingerprint of classification tables) rather than rushing one in; explained in PR body.","dependencies":[{"issue_id":"polylogue-lzh8","depends_on_id":"polylogue-2qx.2","type":"related","created_at":"2026-07-31T07:59:51Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-lzh8","depends_on_id":"polylogue-9ykn","type":"related","created_at":"2026-07-31T07:59:50Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-lzh8","depends_on_id":"polylogue-omsw","type":"related","created_at":"2026-07-31T07:59:50Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-roax","title":"FTS invariant violated: ops status says 100% indexed while queries fail as incomplete","description":"MEASURED 2026-07-31 on the live archive.\n\nCONTRADICTION between two surfaces:\n polylogue ops status -\u003e 'FTS: 100.0% indexed'\n polylogue find \u003canything\u003e -\u003e exit 1, DatabaseError,\n 'Search index is incomplete. Run polylogued run.'\nBoth were run minutes apart against /realm/db/polylogue with the daemon RUNNING.\nSo either the status surface measures something the query path does not require,\nor one of them is wrong. A user-facing error telling the operator to run a daemon\nthat is already running is itself a broken contract.\n\nWHY THIS IS AN INVARIANT VIOLATION, not just a bug: the automagic-invariants\ndoctrine (bd memory 'automagic-invariants') states that FTS coherence belongs to\ndaemon convergence/startup/write-path invariant enforcement, NOT to routine\noperator maintenance commands. Search being degraded while the daemon runs means\nthe convergence path either is not running the FTS stage, is failing it silently,\nor completed it against a different index generation than the query path opens.\n\nCONTEXT that may be causal, all measured tonight:\n- The daemon was livelocked for hours (raw materialization yielding to a pending\n browser-capture spool every 60s while ingesting nothing) and was restarted\n around 06:20. The index may have been left mid-convergence.\n- An index-generation swap happened 2026-07-30 (.index-generations/, active\n pointer gen-1785377665711-06297b00). A dataset lane separately measured 4,186\n embeddings rows (2.2%) pointing at message_ids no longer in index.db, which it\n attributed to that swap with no cross-tier reconciliation (bead polylogue-feu0).\n An FTS table left behind by the same swap would present exactly this way.\n- A dataset lane also measured 10,837 blocks with real text missing from\n messages_fts (down from 36,757), spot-checked directly (appended to\n polylogue-5vbs). That is a real gap, but 'incomplete' as a hard query-path\n failure is a different symptom from 'partially indexed'.\n- Concurrent stderr warning on every CLI call: 'format drift: origin\n aistudio-drive 100% of 302 records since 2026-07-01 carry unseen shapes'.\n\nAC: the two surfaces agree; a degraded FTS either self-heals via convergence or\nreports the SAME state through both surfaces; and the error message does not\ninstruct the operator to start a daemon that is already running.","status":"closed","priority":1,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T05:26:15Z","created_by":"Sinity","updated_at":"2026-07-31T06:33:32Z","started_at":"2026-07-31T06:33:11Z","closed_at":"2026-07-31T06:33:32Z","close_reason":"Root cause: daemon/convergence_stages.py::repair_messages_fts_surface recorded state=ready with a fabricated source_rows=1,indexed_rows=1 placeholder (detail='bounded global messages_fts repair completed; exact counts skipped') after its exhaustive (not partial) reconcile pass, purely to dodge two cheap COUNT(*) probes. cli/commands/status.py then defaulted the resulting None coverage_pct to a hard-coded 100.0% whenever messages_ready was true -- the '100% indexed' the operator saw was never a measurement. The query path (storage/fts/freshness.py) independently trusts/distrusts the same ledger row via freshness_ready_record_trusted with no knowledge of the placeholder, so the two surfaces could show different confidence for the same state. Live evidence: /realm/db/polylogue/index.db carried exactly this poisoned row at investigation time; live messages_fts_docsize already matched the real indexable block count (0 missing) -- convergence HAD actually finished, it just lied about verifying it. Fix (PR #3429): repair_messages_fts_surface now records real post-repair counts via two plain COUNT(*) probes instead of the placeholder; removed the now-dead BOUNDED_MESSAGE_FTS_REPAIR_DETAIL/counts_available special-casing in fts_status.py; CLI no longer defaults an unmeasured coverage_pct to a fabricated percentage (prints 'coverage unknown'); centralized and reworded the FTS repair-hint text so it never tells the operator to start a daemon that might already be running. New regression test proves status and query-path readiness agree post-repair (verified it fails against the pre-fix code). All three AC items satisfied: surfaces derive from the same ledger check; repair now honestly self-heals (real counts recorded, not a lie); error text no longer presumes the daemon is down. devtools verify --quick green; devtools test on all touched/adjacent modules green (44+181+23 tests).","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-gvgi","title":"Non-transcript JSONL under ~/.claude/projects/ ingested as claude-code-session: 96,748 empty phantom messages","description":"Adversarial dataset investigation (H7) found a single phantom claude-code-session with native_id literally 'conversation_relationships' and message_count=96,748, all zero-block/zero-word (role=user, material_origin=human_authored, message_type=message, no user_context_text). It accounts for 96,748 of the archive's 101,765 total zero-block messages (95.1%).\n\nTraced to source: raw_sessions.raw_id=aa5e35075a0c0b809ae70811c2e5515a4b02e1890518078028149c4258ea3e93, source_path=/home/sinity/.claude/projects/-realm-project-sinex/analysis/index/conversation_relationships.jsonl (251,568 lines, 52MB blob). This file is NOT a Claude Code transcript -- it is a sinex analysis-index artifact recording parent/child/conversation graph edges (each line: conversation/parent/child/type/timestamp keys, type is assistant or user). It happens to live under a directory tree shaped like ~/.claude/projects/PROJECT/... and its per-line type field was apparently enough to satisfy a loose provider-shape check, causing dispatch to lower it as a claude-code-session with one empty message per JSONL line.\n\nDistinct root cause from the already-tracked polylogue-b508 (agent-star.meta.json sidecars, fixed PR 3403): that class is Claude Code own sidecar files; this is a third-party tool artifact that merely sits in the scanned directory tree and pattern-matches a provider detector.\n\nBlast radius (verified 2026-07-31 on live archive): 1 phantom session, 96,748 phantom messages (about 2 percent of the archive total 4,900,553 messages), 52MB wasted raw blob. Also the leading contributor to the C4 metric (sessions with created_at_ms NULL) growing from 1,117 (post-de-inflation) to 5,382 -- 97.8 percent of those NULL-created_at_ms sessions have word_count=0, consistent with this and similar phantom-ingestion artifacts accumulating.","acceptance_criteria":"1. Root-cause: identify the exact detector/heuristic that accepted this file as a claude-code-session, tighten it to require genuine Claude Code transcript shape evidence (sessionId/uuid/message envelope), not just a bare type key. 2. Purge the phantom session and its 96,748 messages/blocks from index.db via targeted delete, not full rebuild (rebuild would recreate it per the b508 lesson about the parse chokepoint in sources/revision_backfill.py). 3. Quarantine or reclassify the source raw so ops reset --index does not resurrect it. 4. Add a regression test: a JSONL file with type-assistant/user shaped lines but no session/message envelope must not be classified as any chat-transcript origin.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T04:56:32Z","created_by":"Sinity","updated_at":"2026-07-31T04:56:32Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-qj5x","title":"Decision: remove Origin.BEADS_ISSUE — Beads data belongs in the work-evidence graph, not sessions","description":"DESIGN INVESTIGATION VERDICT (2026-07-31, design doc: .agent/scratch/live/beads-handling-design-2026-07-31.html). The operator challenged BEADS_ISSUE-as-Origin (\"beads is not a chatlog\"). Investigation confirms the doubt with measurements:\n\n1. interactions.jsonl is 100% field_change rows (polylogue: 2,249 rows / 862 issues = priority 1124 + status 1071 + assignee 54), actor constant \"Sinity\" in 2,249/2,249. The parser synthesizes English prose from these (\"Sinity changed priority from 3 to 2\") into Role.USER messages with MaterialOrigin.RUNTIME_PROTOCOL — ~924 projected sessions containing zero human or assistant content. Same structural shape as the hook-event inflation incident (polylogue-31r1, 83,286→18,391 sessions).\n2. The rich Beads artifact — issues.jsonl (1,260 issues, 907 with notes, 1,857 dependency edges, descriptions/design/AC) — is NOT ingested by the Origin route at all. The Origin captures the least informative beads file.\n3. The architecturally correct home already exists in code: insights/work_effects.py BeadsIssueEffectAdapter reads the SAME interactions.jsonl as ObservedRepositoryEffect facts, and devtools/mandate_continuity_replay.py build_repository_claim_graph builds claim nodes from it. docs/internals.md 688-733 documents both. BEADS_ISSUE-as-Origin is a redundant second representation of data the archive already models correctly as effects/claims.\n4. Revealed preference: fully wired for months (parser/detector/dispatch/OriginSpec), acquired nothing, nobody noticed. #3416's sources.beads_roots defaults to () — still zero ingested (measured: 0 beads-issue sessions among 23,296 in the live index).\n5. Scaffolding rot: origin_specs.py:796 references stream_parser_path \"beads.py:parse_beads_stream\" — that function does not exist anywhere (dangling reference). Completeness mode is \"proposed\", never harvested from a real sample.\n\nREMOVAL PATH (no shims, no deprecation theater — nothing ingested, zero migration risk): delete Origin.BEADS_ISSUE + Provider.BEADS, sources/parsers/beads.py + its tests, dispatch branches (dispatch.py 44/46/56/198/239/1033/1159/1260), _beads_spec + completeness mode (origin_specs.py 787-805, 997-1030), core/sources.py mappings (126-129, 158, 236, 254, 300); drop \"beads-issue\" from session_links dst_origin CHECK (derived-tier index bump, declare delta class — 0 affected rows measured, in-place fast-forward safe); remove #3416 beads_roots acquisition wiring (no users exist; hard removal is policy-compliant per no-compat-pre-adoption). Keep artifact-taxonomy shape classification (looks_like_beads_interaction) keyed off shape, so a stray uploaded ledger classifies as a non-session artifact instead of unknown-export sessions — same treatment hook events got in 31r1. BeadsIssueEffectAdapter and the claim-graph builder are untouched and become the sole consumers of the ledger.\n\nWHAT IS NOT LOST: ledgers are git-tracked in their repos (durability is git's, not polylogue's); issue state-transition evidence (timestamps, old→new, close reasons carrying commit hashes) stays reachable via the effect adapter for 1vpm.6 reconciliation; bead ids in real sessions remain FTS-searchable (phrase \"polylogue-x4s\" already matches 248 real messages). What ingestion WOULD have added: +4% sessions, all synthetic protocol prose polluting exactly the FTS queries used to find real work on a bead.\n","notes":"Follow-on filed: polylogue-5jnq (issues.jsonl as work-evidence issue nodes, 1vpm.6 adapter). Related open beads: polylogue-37t.13 (beads\u003c-\u003eassertions boundary revisit — its premise 'beads-history ingestion landed (#2800)' refers to the Origin route this decision removes; re-anchor it on the work-evidence graph), polylogue-pbuh (typed pr-link records = the session↔PR leg of the three-way join).","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T04:36:56Z","created_by":"Sinity","updated_at":"2026-07-31T04:37:54Z","dependency_count":0,"dependent_count":2,"comment_count":0} @@ -103,7 +140,7 @@ {"_type":"issue","id":"polylogue-eqnv","title":"Stale pre-fix parser identity lets a same-source_path raw pair silently split into two byte-proven singletons, downgrading fidelity","description":"## What the live archive shows\n\nFor the 5 aistudio-drive sessions Implementing-{066bb070,13ced1c8,37edfeb3,845dd573,d4d7fbab}, the index materialized the SMALLER (attachment-unfetched, \"bare\") raw and never even considered the LARGER (attachment-fetched, \"enriched\") raw. Neither raw has a `raw_session_memberships` row -- they never entered the ambiguous-membership machinery bu1i/9dxn describe. Instead both raws sit in raw_sessions with `revision_kind='full'`, `revision_authority='byte_proven'`, `baseline_raw_id=self` -- i.e. each was independently accepted as an unconditional SINGLETON byte-revision baseline under a DIFFERENT `logical_source_key`:\n\n 0064ddd16c39... (enriched, 967377B) -\u003e logical_source_key = 'gemini:Implementing-066bb070...'\n 13ae07d010bb... (bare, 252347B) -\u003e logical_source_key = 'gemini:Implementing-066bb070...-0'\n\n## Root cause, proven\n\n`raw_authority_parser_census` (source.db) records the census-time parser\nIDENTITY output for both raws:\n\n 0064ddd16c39...: fingerprint=revision-membership-v1, key=[\"gemini:Implementing-066bb070...23810576f616f90fb4254c69\"]\n 13ae07d010bb...: fingerprint=revision-membership-v1, key=[\"gemini:Implementing-066bb070...23810576f616f90fb4254c69-0\"]\n\nBoth under the SAME fingerprint string, yet different identity. Reparsing\nBOTH raw blobs from the live blob store through the CURRENT\n`polylogue/sources/dispatch.py`/`revision_backfill._parse_one` gives the\nIDENTICAL, correct, unsuffixed `provider_session_id` for both (verified with\nproduction code against the real blobs). The \"-0\" suffix is the exact\npre-#3179/z1c6 bug (`_lower_drive_like_payload`'s `_looks_like_chunked_session_list`\nbranch always appended `-{index}` regardless of list length, fixed\n2026-07-20 in b473d9256/#3179). raw_small was acquired+validated 2026-07-16,\nraw_big 2026-07-18 -- both before the fix landed 2026-07-20 -- and their\ncensus (which sets `raw_sessions.logical_source_key`) evidently ran under\nthe pre-fix parser and was never invalidated, because\n`raw_authority_parser_census`'s quiescence gate\n(`uncensused_historical_revision_raw_ids`) treats any row with the SAME\nliteral fingerprint string as \"current parser already observed this\" --\nthere is no version distinction between pre-fix and post-fix identity\noutput. `classify_raw_revision_cohort` (archive.py) then classifies each\nraw against its OWN `logical_source_key` in isolation, has no way to know\nthe two keys describe the same physical document, and unconditionally\naccepts each as a trivial one-member byte-proven chain -- the same\nstructural hole polylogue-52l2/hm2f already document for the RETIRED-SIBLING\ncase, but here the divergence is at the KEY itself, not at retirement\nstate, so the existing `raw_membership_retired_full_revision_siblings` guard\n(keyed on exact logical_source_key match) never fires.\n\n## Relationship to polylogue-9dxn\n\n9dxn's proposed fingerprint-versioning fix (permissive quiescence for any\nKNOWN fingerprint, strict-current-only for the ambiguous TERMINAL gate)\ndoes not by itself heal this case: it is designed to let previously-`ambiguous`\nverdicts be revisited without forcing a blanket re-census, but raw_small's\nstale census here was NOT ambiguous -- it was `status='complete'` with a\nWRONG identity, and 9dxn's design keeps quiescence permissive for any known\nfingerprint, so this raw would stay \"already observed\" forever even after a\nfingerprint bump. This bead's fix is a structural cross-source_path guard in\n`classify_raw_revision_cohort`, independent of fingerprint versioning, that\nalso closes the general case regardless of how two same-document raws ended\nup under different keys (stale census, race, or a future bug of the same\nshape).\n\n## Fix landed in polylogue-af059 (this branch)\n\n- `archive.py`: `classify_raw_revision_cohort` refuses unconditional\n singleton acceptance when another 'full' raw shares the same source_path\n under a different (or already-retired) logical_source_key -- forces both\n into membership governance instead of letting either become an\n unconditionally-accepted baseline.\n- `revision_backfill.py`: the retire-to-membership-governance fallback now\n buckets `membership_candidates`/`membership_keys` by the FRESHLY re-parsed\n identity (`session.provider_session_id`) instead of the stale outer-loop\n `logical_source_key`, so two same-document raws retired under different\n stale keys land in ONE membership cohort and get jointly arbitrated\n instead of each being accepted as an independent membership singleton.\n\n## Residual / follow-up\n\n- The live archive's 5 already-downgraded sessions are NOT repaired by this\n code fix (need a live remediation pass, out of scope for this PR).\n- A full census-fingerprint bump (9dxn) is still needed to catch every OTHER\n raw whose identity was assigned by pre-#3179 dispatch.py, if any exist\n beyond aistudio-drive.\n\nRef polylogue-bu1i, polylogue-7ilr, polylogue-9dxn","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:22:33Z","created_by":"Sinity","updated_at":"2026-07-30T12:45:58Z","started_at":"2026-07-30T12:45:56Z","closed_at":"2026-07-30T12:45:58Z","close_reason":"Fixed in PR #3396 (feature/fix/ambiguous-raw-materialization-leak): ArchiveStore.classify_raw_revision_cohort gains an opt-in check_source_path_identity_split guard (used only by the offline backfill/rebuild replay loop, not the live watcher), plus revision_backfill.py's retire-to-membership-governance fallback now buckets by the freshly re-derived identity instead of the stale outer-loop key. Verified with two new regression tests (anti-vacuity confirmed both ways via direct revert+rerun). The 5 already-downgraded live sessions are NOT repaired by this fix; live remediation is a separate, explicitly out-of-scope lane.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-nuec","title":"chatgpt-export: provider-reported generation-duration metadata volatile, contaminates session-event identity hash","description":"## What the data says\n\nSampled 35 of 129 chatgpt-export \"ambiguous\" equal-message-count membership\ncohorts (27%; read-only against /realm/db/polylogue). Reproduced with\nproduction code identically to polylogue-c429/polylogue-c42a: parsed both\ndistinct-content raw revisions per cohort via\n`polylogue.sources.dispatch.parse_payload`, projected each with\n`polylogue.pipeline.ids.session_revision_projection`.\n\n33 of 35 sampled cohorts (94%) have this exact shape:\n\n message_hashes equal (messages byte-identical, same order)\n attachment_hashes equal\n event_hashes DIFFER, with event COUNT equal on both sides\n\nFor every sampled case, the first differing `session_events` entry is\n`event_type == \"generation_lifecycle\"` with an identical payload key set\n(`duration_semantics`, `elapsed_duration_ms`, `evidence_source`,\n`fidelity`, `state`) but a DIFFERENT `elapsed_duration_ms` value (e.g.\n13000 vs 21000; 52000 vs 107000; 123000 vs 33000 -- no consistent\ndirection, ruling out simple clock skew). In several cases the event's\n`source_message_provider_id` also differs at the same array index, evidence\nthat the generation-lifecycle event LIST itself may reorder alongside the\nduration values, though message order (which correlates with these events)\nwas independently confirmed stable.\n\n## Root cause\n\n`polylogue/sources/parsers/chatgpt.py` (`_resolve_generation_timings` /\n`~line 1069`, `duration_semantics=\"provider_reported_elapsed\"`) derives a\nsynthetic `generation_lifecycle` session event per assistant/tool message,\nwith `elapsed_duration_ms` computed from the RAW EXPORT's own\n`finished_duration_sec` or `reasoning_start_time`/`reasoning_end_time`\nmetadata fields on that message's mapping node (not something Polylogue\ninvents -- traced to `raw_metadata.get(\"finished_duration_sec\")` and the\n`reasoning_start_time`/`reasoning_end_time` delta). This value is folded into\n`session_events` and hashed via `session_revision_projection`'s\n`event_hashes` (`polylogue/pipeline/ids.py`), which\n`_strictly_dominates`/`classify_membership_revisions`\n(`polylogue/archive/session_revision_membership.py`) treats as part of\ncontent identity.\n\nThe underlying provider-reported duration values are not stable across\nseparate ChatGPT export requests for the SAME generation -- 33 of 35 sampled\ncohorts have message and attachment content that is byte-identical across\ntwo export vintages, yet the reported generation timing differs, sometimes\nsubstantially (e.g. 2s vs 27s; 794s vs 445s), with no consistent\nincrease/decrease pattern that would suggest a benign refinement. This reads\nas either non-deterministic export-time re-derivation on OpenAI's side, or a\nmetric that legitimately varies by measurement context and was never meant\nto be a durable per-generation identity value. Either way, folding it into\nsession identity hash makes byte-identical conversations look like divergent\nbranches on every re-export.\n\n## Reproduction recipe (production code, no archive mutation)\n\nSame harness pattern as polylogue-c429, with `origin='chatgpt-export'`;\nafter loading both `ParsedSession`s for a cohort:\n\n```python\nfrom polylogue.pipeline.ids import session_revision_projection\npa, pb = session_revision_projection(a), session_revision_projection(b)\nassert pa.message_hashes == pb.message_hashes\nassert pa.attachment_hashes == pb.attachment_hashes\nassert pa.event_hashes != pb.event_hashes\nassert len(a.session_events) == len(b.session_events)\n# first differing pair:\nfor ea, eb in zip(a.session_events, b.session_events):\n if ea.payload != eb.payload:\n assert ea.event_type == eb.event_type == \"generation_lifecycle\"\n assert ea.payload[\"elapsed_duration_ms\"] != eb.payload[\"elapsed_duration_ms\"]\n break\n```\n\n## Extrapolation honesty\n\n35 of 129 sampled (27%, the largest sample fraction of any origin in this\ncensus). 33/35 = 94% match this exact shape (message+attachment hashes\nequal, event hashes differ, dominant delta traced to\n`generation_lifecycle.elapsed_duration_ms`). 1/35 has both message and\nevent differences (a separate, unexamined cause). 1/35 is now identical\nunder the current classifier (message/event/attachment hashes all equal) --\nits recorded 'ambiguous' decision appears stale relative to current\nevidence; see polylogue-9dxn for the general \"persisted ambiguous verdicts\nnever get re-derived\" defect that would explain this. Extrapolating 94% to\nthe full 129-cohort population suggests roughly 120 of 129 cohorts, but this\nis an estimate from a 27% sample, not a full census.\n\n## Proposed fix direction (for the classifier/parser-owning lane, not this bead)\n\nThis is the clearest case in the whole census for excluding a field from\nidentity rather than relaxing dominance comparison: `elapsed_duration_ms` is\nexplicitly labeled a measurement (`duration_semantics:\n\"provider_reported_elapsed\"`), not a content field, and doesn't belong in a\ncontent-identity hash at all. Either exclude `generation_lifecycle` event\npayloads (or just the `elapsed_duration_ms` field within them) from\n`_session_hash_components`'s `session_events_payload` in\n`polylogue/pipeline/ids.py`, or store/compare `session_events` with a\ntolerant equality that ignores this specific volatile field. Narrower and\nlower-risk than the message-order or attachment-identity fixes in\npolylogue-c429/polylogue-c42a because it doesn't touch dominance logic at\nall -- it just stops hashing a value the parser itself already documents as\nnon-durable measurement evidence.\n\nRef polylogue-bu1i\n","notes":"Superseded by polylogue-aggz's architecture: chatgpt-export generation_lifecycle duration volatility is now handled via an explicit content-only ALLOWLIST (_EVENT_CONTENT_PAYLOAD_ALLOWLIST) rather than a denylist strip of known-volatile fields. Live census: 119/135 (88.1%) chatgpt-export ambiguous cohorts now resolve, 0 regressions. See PR.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:16:49Z","created_by":"Sinity","updated_at":"2026-07-30T15:15:30Z","labels":["area:ingest"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-hith","title":"claude-ai-export: synthetic attachment id keyed on positional index is unstable across export vintages","description":"## What the data says\n\nSame sample as polylogue-c429 (40 of 566 claude-ai-export ambiguous\nequal-message-count cohorts, read-only against /realm/db/polylogue,\nreproduced with production `parse_payload` + `session_revision_projection` +\n`classify_membership_revisions`). Of the 40, 16 (40%) have this exact shape,\ndisjoint from the message-order cause in polylogue-c429:\n\n message id set equal, message array order equal, 0 content diffs\n len(attachments_a) == len(attachments_b)\n set of (provider_attachment_id, message_provider_id) DISJOINT or partially disjoint\n between the two revisions, for attachments anchored to the SAME message\n\nExample (cohort with 4 attachments, 2 anchor messages, `key` starting\n`claude-a...`):\n\n A: att_id=e950263f-063d-495d-b0c0-61e9330d3a14 msg=7f1cf6ff-...\n B: att_id=att-ce21cd12d650 msg=7f1cf6ff-... (same message anchor)\n\n A: att_id=66a7a163-d488-4320-8129-19ad43f64a43 msg=c38e86ac-...\n B: att_id=att-cd01a39eb65c msg=0d3a13b8-... (different message anchor too)\n\nmime_type/size_bytes/inline-presence/name-length are identical between the\npaired attachments in every sampled case -- this is not\npolylogue-bu1i's acquisition-state pattern (`inline_bytes`/`size_bytes`\nflipping None-\u003ereal). The IDENTITY STRING itself differs, and sometimes so\ndoes the message it's anchored to.\n\n## Root cause\n\n`polylogue/sources/parsers/base_support.py:152-197`\n(`attachment_from_meta`/`_make_attachment_id`), used by the Claude.ai parser\nvia `attachment_from_meta` (`polylogue/sources/parsers/claude/ai_parser.py`,\n`_merge_session_attachments` at line ~191, iterating `(\"attachments\",\n\"files\")`):\n\n```python\ndef _make_attachment_id(seed: str) -\u003e str:\n return f\"att-{hash_text(seed)[:12]}\"\n\ndef attachment_from_meta(meta, message_id, index):\n attachment_id = (\n meta.get(\"id\") or meta.get(\"file_id\") or meta.get(\"fileId\")\n or meta.get(\"uuid\") or meta.get(\"file_uuid\")\n )\n ...\n if not attachment_id:\n if not name:\n return None\n seed = f\"{message_id or 'msg'}:{name}:{index}\"\n attachment_id = _make_attachment_id(seed)\n```\n\nTwo independent failure modes both traced in the sample:\n\n1. **Real-id presence is inconsistent across export vintages.** When\n Claude.ai's own export payload carries a real `id`/`file_id`/`uuid` for an\n attachment, that string is used directly (stable). When it's absent, the\n parser falls back to a SYNTHETIC id hashed from\n `f\"{message_id}:{name}:{index}\"`. The two export vintages of the same\n conversation don't consistently include the real id -- one carries it,\n the other doesn't -- so the same physical attachment gets a real UUID in\n one revision and a synthetic `att-...` id in the other.\n2. **`index` is positional, and attachment order is not guaranteed stable.**\n Even when BOTH revisions fall back to synthesis, `index` is the\n attachment's position in the merged `attachments`+`files` iteration for\n that message. If that per-message ordering shifts between export\n vintages (plausible given polylogue-c429's proof that the surrounding\n MESSAGE array order is itself unstable across Claude.ai exports), the\n synthesized id changes even though the underlying attachment didn't.\n\nEither way, attachment identity is accidentally keyed on transient\nexport-shape details (real-id presence, list order) rather than a property\nof the attachment itself, so `_attachment_hash_payload`\n(`polylogue/pipeline/ids.py:152`) hashes the same physical attachment to two\ndifferent identities across export vintages -- the same general shape as\npolylogue-bu1i (acquisition/export-time noise contaminating an identity\nhash), but a DIFFERENT concrete defect (id synthesis, not acquisition-state\nflip) requiring a different fix.\n\n## Reproduction recipe (production code, no archive mutation)\n\nSame harness as polylogue-c429's reproduction recipe; after loading both\n`ParsedSession`s for a cohort where message ids/order/content are identical:\n\n```python\natts_a = {(at.provider_attachment_id, at.message_provider_id): at for at in a.attachments}\natts_b = {(at.provider_attachment_id, at.message_provider_id): at for at in b.attachments}\nassert len(atts_a) == len(atts_b)\nassert set(atts_a) != set(atts_b) # disjoint identity despite same count\n```\n\n## Extrapolation honesty\n\n40 of 566 sampled (7%). 16/40 = 40% match this exact shape (message\ncontent/order fully identical, attachment key sets disjoint at equal\ncount). Extrapolating to the full population suggests roughly 220-230 of the\n566 cohorts, but this is an estimate from a 7% sample, not a census.\n\n## Proposed fix direction (for the classifier/parser-owning lane, not this bead)\n\nTwo independent levers, either alone reduces the blast radius:\n\n- Parser-side: derive the synthetic attachment id from content-stable\n material only (e.g. a hash of `(message_provider_id, name, mime_type,\n size_bytes)` without positional `index`), so re-ordering the export's\n attachment list doesn't change identity. Does not fix mode (1)\n (real-id-present-in-one-export-only).\n- Classifier-side (in the files this investigation lane does not edit):\n compare attachments by a looser key (e.g. `(message_provider_id, name,\n mime_type, size_bytes)`) when testing dominance, falling back to id\n equality only when that tuple is ambiguous -- the same class of relaxation\n polylogue-bu1i proposes for acquisition-state, generalized.\n\nRef polylogue-bu1i\nRef polylogue-c429\n","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:16:40Z","created_by":"Sinity","updated_at":"2026-07-30T12:17:40Z","labels":["area:ingest"],"comments":[{"id":"019fb31e-ef07-7bef-a785-41e5de19372f","issue_id":"polylogue-hith","author":"Sinity","text":"Parser-side fix landed (PR pending, branch feature/fix/synthetic-attachment-id-stability):\nattachment_from_meta's synthetic-id seed no longer includes the positional\n`index`; mime_type is used as the one extra structural disambiguator instead\n(id/name/mime_type). The now-unused `index` param was removed from\nattachment_from_meta and all 3 call sites (ai_parser.py x2,\nclaude/common.py's _message_attachments).\n\nVerified: 250-cohort regression replay (old vs new minting logic) over\nalready-resolved claude-ai-export cohorts -- 249/250 agree, 1 improvement,\n0 regressions.\n\nHonest disposition on the 566-cohort measured population: 0 resolved by this\nfix alone. Full census (not sample) shows all 268 message-hashes-equal\nambiguous cohorts are \"mixed real/synthetic\" (failure mode 1: real-id\npresence varies across export vintages of the same conversation) -- 0 are\n\"pure synthetic on both sides\" (the positional-index shape this fix\ntargets). Failure mode 1 needs a comparison-layer relaxation in\nsession_revision_membership.py/ids.py, which this lane was scoped away\nfrom. Filed as polylogue-d8al with the full census breakdown and a proposed\ndesign (loosen dominance comparison to (message_id, name, mime_type) when\nprovider ids disagree, id-equality fallback only when that's itself\nambiguous). polylogue-c429 (message order) accounts for the other 297.\n\nLeaving this bead open pending the comparison-layer fix -- the fix in this\nPR is real and durable (protects any future/other-origin case of the\npositional-index shape) but does not itself resolve the currently-measured\npopulation; polylogue-d8al is the actionable remainder.\n","created_at":"2026-07-30T13:02:57Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} -{"_type":"issue","id":"polylogue-qkuq","title":"claude-ai-export: synthetic attachment id keyed on positional index is unstable across export vintages","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:16:33Z","created_by":"Sinity","updated_at":"2026-07-30T12:16:33Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-qkuq","title":"claude-ai-export: synthetic attachment id keyed on positional index is unstable across export vintages","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:16:33Z","created_by":"Sinity","updated_at":"2026-07-31T10:11:19Z","closed_at":"2026-07-31T10:11:19Z","close_reason":"Duplicate of polylogue-hith (identical title, same creation minute, empty description vs hith's 5,522-char writeup and 1 comment). Consolidating on hith as the survivor.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-c429","title":"claude-ai-export: message array order is not stable across export vintages, breaking prefix-dominance","description":"## What the data says\n\nSampled 40 of 566 claude-ai-export \"ambiguous\" equal-message-count membership\ncohorts (7%; read-only against /realm/db/polylogue). Reproduced with\nproduction code: parsed both distinct-content raw revisions of each cohort\nvia `polylogue.sources.dispatch.parse_payload` (routed through\n`provider_from_origin`/`capture_mode` exactly as `_parse_one` does in\n`polylogue/sources/revision_backfill.py`), projected each with\n`polylogue.pipeline.ids.session_revision_projection`, and ran the production\n`classify_membership_revisions`.\n\n21 of 40 sampled cohorts (52.5%) have this exact shape:\n\n n_messages_a == n_messages_b\n set(provider_message_id for a.messages) == set(provider_message_id for b.messages)\n [a.provider_message_id for a in a.messages] != [... for b.messages] # order differs\n for every shared id: (role, text, timestamp) identical between a and b\n\nThat is: the SAME messages, byte-identical per-message content, just in a\nDIFFERENT SEQUENCE in the two exports. Concretely reproduced on cohort\n`claude-ai:944d1095-51ea-4063-abe9-719d9971e281` (raws\n`aa0990572bb833d4...` vs `ebe3a4f95f45b235...`): 36/36 messages, identical\n`{role,text,timestamp}` for every one of the 36 shared `provider_message_id`s,\n0 content diffs, but `ids_a != ids_b`. One revision's message array is sorted\nchronologically; the other is not (its role sequence pairs adjacent\nuser/user, assistant/assistant messages -- looks like Claude.ai's own tree\nflattening interleaving edited-message siblings rather than a strict\ntimestamp sort).\n\n## Root cause\n\n`polylogue/pipeline/ids.py:session_revision_projection` builds\n`message_hashes` as an ORDER-SENSITIVE tuple (`_message_hash_payload` per\nmessage, in array order). `polylogue/archive/session_revision_membership.py`\n`_strictly_dominates` requires\n`older.message_hashes == newer.message_hashes[: len(older.message_hashes)]`\n-- an exact positional prefix match. When Claude.ai's own export emits the\nsame conversation's message array in a different sequence across two export\nrequests (same message set, same content, different order), this prefix\ncheck fails in BOTH directions even though there is no real content\ndivergence, and the cohort is quarantined ambiguous.\n\nNo parser code sorts messages by timestamp before this hash is computed\n(`polylogue/sources/parsers/claude/ai_parser.py` preserves whatever order the\nexport's `chat_messages` array carries; see `_merge_session_attachments`\niterating `(\"attachments\", \"files\")` for the analogous merge-order case in\nattachments). Claude.ai's own export ordering for a given conversation is\napparently NOT guaranteed stable across separate export requests -- this is\nupstream non-determinism polylogue must tolerate, not something polylogue's\nown acquisition controls.\n\n## Reproduction recipe (production code, no archive mutation)\n\n```python\nfrom pathlib import Path\nfrom polylogue.sources.decoders import _iter_json_stream\nfrom polylogue.sources.dispatch import parse_payload\nfrom polylogue.core.enums import Origin\nfrom polylogue.core.sources import provider_from_origin\nfrom polylogue.pipeline.ids import session_revision_projection\nimport io, sqlite3\n\ncon = sqlite3.connect(\"file:/realm/db/polylogue/source.db?mode=ro\", uri=True)\ncon.row_factory = sqlite3.Row\nrows = con.execute(\n \"select rs.raw_id, rs.source_path, rs.blob_hash, rs.capture_mode \"\n \"from raw_session_memberships m join raw_sessions rs on rs.raw_id = m.raw_id \"\n \"where m.decision='ambiguous' and rs.origin='claude-ai-export' \"\n \"and m.logical_source_key = ?\",\n (\"claude-ai:944d1095-51ea-4063-abe9-719d9971e281\",),\n).fetchall()\n\ndef load(row):\n h = row[\"blob_hash\"].hex()\n raw = (Path(\"/realm/db/polylogue/blob\") / h[:2] / h[2:]).read_bytes()\n provider = provider_from_origin(Origin.CLAUDE_AI_EXPORT, family_hint=row[\"capture_mode\"])\n fallback_id = Path(row[\"source_path\"].split(\":\")[-1]).stem\n name = Path(row[\"source_path\"].split(\":\")[-1]).name\n records = list(_iter_json_stream(io.BytesIO(raw), name))\n return parse_payload(str(provider), records, fallback_id, source_path=row[\"source_path\"])\n\nsessions = {r[\"raw_id\"]: load(r)[0] for r in rows} # cohort has exactly 1 session per raw here\nids = list(sessions)\na, b = sessions[ids[0]], sessions[ids[1]]\nassert {m.provider_message_id for m in a.messages} == {m.provider_message_id for m in b.messages}\nassert [m.provider_message_id for m in a.messages] != [m.provider_message_id for m in b.messages]\n```\n\n## Extrapolation honesty\n\n40 of 566 sampled (7%), stratified randomly (seed fixed). 21/40 = 52.5% match\nthis exact shape; 3 more sampled cohorts show this pattern layered with a\nsecond delta (attachment count or session-event differences) in addition.\nExtrapolating the 52.5% rate to the full population suggests roughly 280-300\nof the 566 cohorts, but this is an ESTIMATE from a 7% sample, not a census --\nunlike polylogue-bu1i's 100%-verified aistudio-drive population, this has not\nbeen checked against every cohort.\n\n## Proposed fix direction (for the classifier-owning lane, not this bead)\n\n`_strictly_dominates` and `session_revision_projection` currently treat\nmessage sequence as part of content identity. A safe fix compares the\nmessage SET (by `provider_message_id` + content) rather than requiring an\nexact positional prefix when a provider's export ordering is not\nauthoritative -- i.e. treat \"same message ids/content, different array\norder\" as equivalent, not as a branch. This is a distinct code path from\npolylogue-bu1i's attachment-acquisition-state fix (different failure\nmode, different field: sequence vs. attachment identity) and should not be\nfolded into the same patch without separate verification, since a naive\norder-insensitive compare would also need to preserve real append-order\ndetection (`older.message_hashes == newer.message_hashes[:len(older)]`) for\ngenuinely growing sessions.\n\nRef polylogue-bu1i\nRef polylogue-hith\nRef polylogue-nuec\n","notes":"Superseded by polylogue-aggz's architecture: message array order is now handled as a byproduct of set-based (identity, content) comparison (message_contents), not a dedicated positional-prefix fix. Live census (full population): 554/587 (94.4%) claude-ai-export ambiguous cohorts now resolve, 0 regressions against previously-resolved cohorts. See PR.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:14:22Z","created_by":"Sinity","updated_at":"2026-07-30T15:15:29Z","labels":["area:ingest"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-9dxn","title":"A persisted 'ambiguous' verdict is terminal with no classifier version, so classifier corrections are inert on existing data","description":"## Problem\n\n`polylogue-bu1i` fixes the classifier so that acquiring an attachment's bytes is\nread as a fidelity upgrade rather than a branch. Verified: all 157 live\naistudio-drive cohorts now resolve to an accepted chain with the enriched\nrevision at its head, where previously 157/157 were ambiguous.\n\nThat fix cannot heal the archive it was written for. The verdicts it corrects are\nalready persisted, and a persisted `ambiguous` verdict is TERMINAL:\n\n polylogue/storage/repair.py:4432-4462\n SELECT 1 FROM raw_session_memberships\n WHERE raw_id IN (...) AND decision = 'ambiguous'\n -\u003e RawReplayPlanStatus.TERMINAL,\n \"component ended in explicit ambiguous or parse-terminal authority state\",\n \"inspect durable authority debt; do not replay without new evidence\"\n\n`raw_session_memberships` has no fingerprint column, so nothing distinguishes\n\"ambiguous under the current classifier\" from \"ambiguous under a classifier we\nhave since corrected\". Every improvement to `classify_membership_revisions` is\ntherefore inert on existing data and only affects newly-acquired raws, while the\nexisting debt sits terminal forever and reads as though it needed operator\njudgment.\n\nLive scale of the inert-fix problem: 3,875 ambiguous membership rows across\n~1,079 cohorts (587 claude-ai-export, 191 claude-code-session, 151\naistudio-drive, 136 chatgpt-export, and a tail).\n\n## Second defect: a bump would not propagate\n\n`RAW_AUTHORITY_PARSER_FINGERPRINT = \"revision-membership-v1\"` exists as a proper\nconstant in `polylogue/storage/raw_authority.py:27`, but\n`polylogue/sources/revision_backfill.py` hardcodes the literal string eight\ntimes instead of importing it (lines 318, 348, 438, 475, 552, 565, 596, 919),\nincluding inside an f-string. Bumping the constant today would half-apply: the\nwriter would stamp the new value while the quiescence gate still matched the old\none. The constant is not load-bearing, which makes the versioning mechanism\nnon-functional exactly when it is first needed.\n\n## Proposed fix\n\n1. Make the constant load-bearing: `revision_backfill.py` imports\n `RAW_AUTHORITY_PARSER_FINGERPRINT` rather than repeating the literal.\n2. Separate two questions the single fingerprint currently conflates:\n - *Was this raw ever observed by a real parser?* -- the quiescence gate\n (`uncensused_historical_revision_raw_ids`, `revision_backfill.py:321`).\n Any known fingerprint should satisfy this, so a bump does NOT trigger an\n archive-wide re-census of all 41,363 raws.\n - *Is this verdict still authoritative under current semantics?* -- the\n terminal gate. Only the CURRENT fingerprint should satisfy this.\n Concretely: keep a `SUPERSEDED_MEMBERSHIP_FINGERPRINTS` set alongside the\n current one, and have the terminal check treat an `ambiguous` decision as\n stale (replayable) when the raw's census fingerprint is superseded rather\n than current. Absent census row -\u003e treat as current, i.e. stay conservative.\n `index_tier.raw_revision_applications` carries the same `decision='ambiguous'`\n check and needs the same treatment.\n3. Bump `RAW_AUTHORITY_PARSER_FINGERPRINT` to `revision-membership-v2`, because\n `polylogue-bu1i` genuinely changed classification semantics.\n\nWith (2) in place the healing is targeted: roughly 3,875 raws re-derive their\nverdict, instead of re-censusing the whole 99 GB archive. Without (2), a bump\nis correct but costs a full reparse (~4h20m measured on this archive).\n\n## Why this is the general fix, not a one-off\n\nThe value here is not unblocking one origin. It is that a classifier correction\nbecomes self-healing: today, improving `classify_membership_revisions` requires\nmanual archive surgery to have any effect on existing data, which is precisely\nthe shape that leaves corrected logic silently inert and debt looking legitimate.\n\n## Acceptance criteria\n\n- `RAW_AUTHORITY_PARSER_FINGERPRINT` is the single source of the fingerprint\n string; no module hardcodes it.\n- An `ambiguous` verdict recorded under a superseded fingerprint is replayable,\n and one recorded under the current fingerprint remains terminal. Both\n directions covered by tests.\n- A bump does not force re-census of raws whose verdict is unaffected; assert\n this against a fixture archive rather than by reasoning.\n- Anti-vacuity: state the production line mutated and the resulting failure.\n\nRef polylogue-bu1i\n","notes":"CORRECTION 2026-07-30, from the lane that traced polylogue-eqnv: the 'Proposed fix' item (2) above is wrong for identity-class staleness, and I am recording that before anyone implements it.\n\nI proposed splitting the fingerprint's two jobs so that the QUIESCENCE gate accepts any *known* fingerprint (avoiding an archive-wide re-census on a bump) while only the TERMINAL gate requires the current one. The motive was cost: targeted healing of ~3,900 raws instead of reparsing 99 GB.\n\nThat does not work when the stale thing is the raw's derived IDENTITY rather than its verdict. polylogue-eqnv is the concrete counterexample: two raws of one document were censused under the same fingerprint string but recorded different logical_source_key values, one carrying a pre-#3179 '-0' suffix from a dispatch bug fixed 2026-07-20 (b473d9256) that their 2026-07-16/18 acquisition predates. Reparsing both blobs through current dispatch yields the identical correct key. Permissive quiescence is exactly what keeps that raw from ever being re-derived, so it preserves the corruption it was meant to be cheap about.\n\nConsequence for this bead's scope: re-census (a reparse) is the honest price for any change that alters derived identity, and the cost cannot be engineered away by making the gate permissive. The split between 'was this observed' and 'is this verdict current' may still be worth having for pure VERDICT changes, where the recorded identity is unaffected -- polylogue-bu1i is that shape, since it changed only how revisions are COMPARED. State which class a change falls in before choosing the cheap path.\n\nPossible middle path, not yet evaluated: re-census only raws whose recorded identity disagrees with a cheap re-derivation, which needs a parse but not a full projection/materialization. Whether that is meaningfully cheaper than the full reparse is unmeasured -- do not assume it is.\nDESIGN 2026-07-30, from the polylogue-eqnv/c737 lane, supersedes the correction note above with something actionable.\n\nSplit the single parser fingerprint into two independently-versioned components:\n\n identity fingerprint -- covers dispatch.py's provider_session_id /\n logical_source_key derivation\n classification fingerprint -- covers session_revision_membership.py's\n dominance rules\n\nThen each class of fix pays only its own price:\n\n- A CLASSIFICATION fix (polylogue-bu1i's shape: dominance rules changed, the\n stored identity is unaffected) bumps only the classification component.\n Quiescence stays permissive on identity, so no reparse is forced, and the\n terminal-ambiguous gate re-runs classification against the already-known\n identity. Cheap, and it makes classifier corrections self-healing, which is\n this bead's original ask.\n- An IDENTITY fix (polylogue-eqnv's shape and the z1c6 dispatch bug: the stored\n logical_source_key itself was wrong) bumps the identity component. Quiescence\n goes strict for it, forcing exactly the reparse that is unavoidably the honest\n price -- you cannot know an identity is still correct without recomputing it,\n since recomputing IS how you discover it changed.\n\nThis is strictly better than the single fingerprint in both directions: today a\nclassification fix cannot heal existing data at all (the terminal gate has no\nversion to compare), and an identity fix would force a full 99 GB reparse even\nwhen only classification changed.\n\nImplementation note carried over: RAW_AUTHORITY_PARSER_FINGERPRINT must first\nbecome load-bearing -- sources/revision_backfill.py still hardcodes\n'revision-membership-v1' at eight sites (318, 348, 438, 475, 552, 565, 596,\n919) instead of importing the constant, so any bump half-applies until that is\nfixed.\nDESIGN (re-recorded 2026-07-30 after a bd reimport dropped the first append), from the polylogue-eqnv/c737 lane.\n\nSplit the single parser fingerprint into two independently-versioned components:\n\n identity fingerprint -- covers dispatch.py's provider_session_id /\n logical_source_key derivation\n classification fingerprint -- covers session_revision_membership.py's\n dominance rules\n\nEach class of fix then pays only its own price:\n\n- A CLASSIFICATION fix (polylogue-bu1i's shape: dominance rules changed, stored\n identity unaffected) bumps only the classification component. Quiescence stays\n permissive on identity so no reparse is forced, and the terminal-ambiguous\n gate re-runs classification against the already-known identity. Cheap, and it\n makes classifier corrections self-healing -- this bead's original ask.\n- An IDENTITY fix (polylogue-eqnv's shape, and the z1c6 dispatch bug: the stored\n logical_source_key itself was wrong) bumps the identity component. Quiescence\n goes strict for it, forcing exactly the reparse that is unavoidably the honest\n price -- you cannot know an identity is still correct without recomputing it,\n because recomputing IS how you discover it changed.\n\nStrictly better than one fingerprint in both directions: today a classification\nfix cannot heal existing data at all (the terminal gate has no version to\ncompare against), while an identity fix would force a full 99 GB reparse even\nwhen only classification changed.\n\nPrerequisite: RAW_AUTHORITY_PARSER_FINGERPRINT must become load-bearing first --\nsources/revision_backfill.py hardcodes 'revision-membership-v1' at eight sites\n(318, 348, 438, 475, 552, 565, 596, 919) instead of importing the constant, so\nany bump half-applies until that is fixed.\nVERDICT: LIVE — polylogue/storage/repair.py:4432-4462 (terminal-decision check for 'ambiguous') is unchanged and still has no classifier_version gating; a persisted ambiguous verdict remains unconditionally terminal. Bead's own 2026-07-30 correction note shows the proposed remediation design was found wrong and no replacement fix has landed. Evidence: sed -n '4400,4470p' polylogue/storage/repair.py showing decision='ambiguous' UNION query with no version check.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T12:13:46Z","created_by":"Sinity","updated_at":"2026-07-31T05:51:54Z","labels":["area:ingest"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-bu1i","title":"aistudio-drive 'ambiguous' revision pairs are not branches: attachment acquisition state contaminates attachment identity hash","description":"## What the data says\n\n151 of 151 aistudio-drive ambiguous membership cohorts (100%) are the SAME Drive\ndocument acquired twice, where the later acquisition merely resolved\nDrive-hosted attachment bytes. There is no branch and nothing to judge.\n\nVerified across all 157 two-member source_path cohorts in the live archive\n(/realm/db/polylogue), by loading both blobs and comparing:\n\n 157/157 file_mtime_ms IDENTICAL (both carry Drive modifiedTime)\n 157/157 earlier blob has NO _polylogue_drive_live_bytes_b64\n 157/157 later blob HAS it\n 157/157 the two payloads are byte-equal after stripping that key\n 157/157 later blob is larger (median ~5-80x)\n\nReproduced deterministically with production code on the pair\n30-12-2025-SINEX-IDEAS.json (raws f6b63f0b / d0715a7f):\n\n bare 604,853 B msgs=60 events=61 atts=4 all inline=None, size_bytes=None\n enriched 5,602,664 B msgs=60 events=61 atts=4 same 4 Drive file ids, bytes fetched\n\n message_hashes equal: True\n event_hashes equal: True\n attachment sets: n1=4 n2=4 intersection=0 subset=False\n _strictly_dominates(bare-\u003eenriched) = False\n classify_membership_revisions -\u003e ambiguous ['d0715a7f','f6b63f0b']\n\n## Root cause (two independent contributors)\n\n1. `_attachment_hash_payload` (polylogue/pipeline/ids.py:152) folds\n ACQUISITION STATE into attachment IDENTITY: it appends\n `inline_content_hash` only when `inline_bytes is not None`, and\n `size_bytes` flips None -\u003e real once bytes are fetched. So the same\n attachment (same Drive file id, same message anchor) hashes differently\n before and after acquisition. The two revisions' attachment_hashes end up\n equal-cardinality and DISJOINT.\n\n2. `_strictly_dominates` (archive/session_revision_membership.py:188) then\n fails both of its conditions: `content_grew` is False (equal message and\n event counts, no proper attachment superset) and\n `older.attachment_hashes \u003c= newer.attachment_hashes` is False (disjoint,\n not subset). Neither escape hatch applies: both revisions have\n `browser_snapshot_fidelity=None` so `_provider_ordered_browser_snapshots`\n bails, and `_direct_export_precedence` needs a browser-capture sibling.\n -\u003e ambiguous, both quarantined.\n\nSeparately, `raw_sessions.revision_kind='unknown'` / `logical_source_key IS NULL`\nbecause the byte-prefix chain check cannot hold: the injector splices base64\nmid-document and re-serializes the whole JSON\n(`json.dumps(resolved, ensure_ascii=False)`, sources/drive/__init__.py:173),\nso the later bytes are not a byte-prefix extension of the earlier.\n\n## Where the second scrape came from\n\nNot two Drive versions. Both acquisitions read the SAME local cache file under\n`~/.local/share/polylogue/drive-cache/gemini/` (240 documents). The 2026-06-29\npass wrote the cache with attachments unresolved. The 2026-07-18 pass took the\ncache-hit branch (no Drive re-download at all) and ran\n`_inject_live_drive_attachment_bytes` -- which by design runs on EVERY read,\ncache hit or not, precisely to backfill caches written before the feature\nexisted (sources/drive/__init__.py:242-256). It mutated the bytes, rewrote the\ncache in place, and hashed the mutated payload -\u003e a second, distinct raw row.\nDrive modifiedTime never changed, which is why file_mtime_ms is identical.\n\nThe 83 single-row cohorts corroborate this: 72 have no driveDocument/Image/\nAudio/Video reference at all, and 11 have references the injector could not\nresolve -- in both cases the injector returns bytes unchanged, the blob hash is\nstable, and no second raw row is created.\n\n## Concrete harm already in the index\n\nPost-promotion convergence materialized these ambiguous raws anyway, arbitrarily\nand last-writer-wins. 6 cohorts got BOTH members materialized; in 5 of the 6 the\nBARE revision was written last, so the index now reports those sessions'\nattachments as `unfetched` even though the bytes were successfully fetched and\nare sitting in the blob store:\n\n aistudio-drive:Implementing-066bb070... atts=1 acquired=0\n aistudio-drive:Implementing-13ced1c8... atts=1 acquired=0\n aistudio-drive:Implementing-37edfeb3... atts=1 acquired=0\n aistudio-drive:Implementing-845dd573... atts=1 acquired=0\n aistudio-drive:Implementing-d4d7fbab... atts=1 acquired=0\n\nThat is a silent fidelity DOWNGRADE, and it is the exact failure mode the\n'never choose between branches' invariant exists to prevent -- it happened\nbecause a non-branch was labelled a branch, and then something picked anyway.\nWhich stage performed that pick is not yet traced: `repair.py:1075` does\nquarantine ambiguous membership, yet 135 of the 151 cohorts acquired a\n`parsed_at_ms` between 06:57 and 13:12 local on 2026-07-30, after the\n`decided_at_ms` of 07:00 that recorded them ambiguous. That gap needs its own\ntrace and may be a second, separate defect.\n\n## Proposed fix\n\nTreat 'same attachment identity, bytes now acquired' as a fidelity upgrade, the\ndirect analogue of the documented DOM-\u003enative rule. Concretely: compare\nattachments by provider identity (provider_attachment_id + message_provider_id\n+ name + mime_type) when testing dominance, and allow a differing hash when the\nonly delta is that the newer side has inline_bytes where the older did not.\nEquivalently, split attachment identity from attachment acquisition state so\nacquisition can never fabricate a branch.\n\nPrefer this over adding a Drive-specific escape hatch: the shape is generic\n(any origin whose attachments are fetched lazily), and the classifier already\nhas two precedents for 'this is an upgrade, not a branch'.\n\n## Blast radius beyond drive\n\nEqual-message-count ambiguous cohorts by origin (same shape; needs its own\nverification per origin before claiming the same cause):\n\n claude-ai-export 566 / 587 cohorts\n chatgpt-export 128 / 136\n aistudio-drive 151 / 151 \u003c- proven, this bead\n hermes-session 3 / 4\n claude-code-session 6 / 191 \u003c- different shape, not this\n gemini-cli-session 0 / 3\n\n## Measurement notes for whoever picks this up\n\n- Live aistudio-drive state at filing: index 225 sessions / 95,823 blocks\n (retired generation had 239 / 106,178); source has 173 unparsed raws, of\n which 129 are correctly superseded (their enriched sibling IS materialized)\n and 44 are the 22 both-unparsed cohorts. 14 documents are absent from the\n index entirely -- exactly the 239-225 gap.\n- The earlier claim '0 correctly superseded, all 302 genuinely unmaterialized'\n was a measurement artifact: it checked `raw_sessions.logical_source_key`,\n which governance deliberately NULLs on transition to semantic membership\n (archive.py:2710). The key survives on\n `raw_session_memberships.logical_source_key` -- join that table instead.\n- Attachment acquisition overall improved enormously in this generation:\n acquired 26 -\u003e 2,849 (unfetched 3,120 -\u003e 177). This bead is a narrow\n regression channel inside a large win, not a verdict on the rebuild.\n\nRef polylogue-7ilr (which framed this residue as genuine authority debt\nrequiring operator judgment; for aistudio-drive that framing is wrong).\n","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-30T11:34:40Z","created_by":"Sinity","updated_at":"2026-07-30T11:34:40Z","labels":["area:ingest"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -127,7 +164,7 @@ {"_type":"issue","id":"polylogue-cijx.2","title":"Repository identity is really cwd: 84% of sessions have no git evidence and one path yields conflicting repo names","description":"Measured 2026-07-29 on the live archive.\n\nGIT EVIDENCE COVERAGE across 18,871 sessions:\n git_branch 2,989 15.8%\n git_repository_url 2,495 13.2%\n commit_hash 3,003 15.9%\nFor the other ~84%, repo assignment comes purely from working_directories. The\ncolumn named 'repo' is therefore 'cwd'. A session in /home/sinity is recorded as\nbeing in the 'sinity' repo.\n\nTHE KEY IS TWO UNRELIABLE FIELDS. write.py:5150\n _repo_id(origin_url, root_path) = f'{origin_url}\\x1f{root_path}'\nand write.py:5143 _repo_name takes the URL basename when a URL exists, else the\npath basename. So the same directory produces multiple rows with DIFFERENT names:\n /realm/project/sinex -\u003e sinex\n /realm/project/sinex -\u003e sinnix\n /realm/project/sinex -\u003e polylogue\n /realm/project/sinex-gateway-shutdown -\u003e sinnix\nand one repository splits across spellings: polylogue holds 106 distinct\nrepo_ids, sinex 28, sinnix 31.\n\nTHREE ENTITIES ARE COLLAPSED INTO ONE COMPOSITE KEY:\n Repository -- stable identity. The right key is the ROOT-COMMIT SHA\n (git rev-list --max-parents=0): content-addressed, survives renames,\n remote changes, mirrors and forks. Remote URLs are ALIASES of a\n repository, not its identity -- which is exactly why three spellings\n produced three repos. This is the same philosophy the archive already\n applies to embeddings (input hash) and blocks (content hash).\n Checkout -- a filesystem path bound to a repository at a branch. Every\n /realm/worktrees/polylogue-* is a checkout of ONE repository, not fifteen.\n Observation -- a session seen in a checkout, at a commit, at a time.\n\nA directory with no git evidence is honestly A DIRECTORY. Do not synthesize a\nrepository for it.\n\nOPEN DECISIONS, not measurements -- resolve explicitly rather than assuming:\n (a) root-commit identity is unavailable for repos polylogue never had\n filesystem access to (an imported ChatGPT session merely mentioning a repo)\n (b) a path reused across projects over time belongs to different repositories\n in different intervals","acceptance_criteria":"1. Repository, checkout and observation are separate entities; repo identity does not include a filesystem path. 2. Remote-URL spellings that denote one remote resolve to one repository, with tests over the observed spelling set (empty/https/ssh/.git). 3. Live re-measure: polylogue/sinex/sinnix collapse to one repository each, with checkouts enumerable underneath. 4. A session with no git evidence resolves to a directory, not a repository, and read surfaces say which. 5. The repo: query field resolves through normalized identity -- a session recorded under one spelling matches a query using another.","notes":"VERIFICATION (group3 sweep): LIVE. Recent (2026-07-29) structural finding with no notes recorded since filing -- no rg evidence of a Repository/Checkout typed-entity refactor landing (git log --grep cijx shows no matching commit). repo: field still resolves through _repo_id/_repo_name path-based logic per description. Genuinely open architectural work, not stale.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:17Z","created_by":"Sinity","updated_at":"2026-07-31T05:53:09Z","labels":["area:insights","area:interop","area:substrate","horizon:mid","tech-tree"],"dependencies":[{"issue_id":"polylogue-cijx.2","depends_on_id":"polylogue-cijx","type":"parent-child","created_at":"2026-07-29T06:52:17Z","created_by":"Sinity","metadata":"{}"}],"comments":[{"id":"019faebd-4e40-7ea5-90b0-efddc80e3fae","issue_id":"polylogue-cijx.2","author":"Sinity","text":"Scoped lane (sources/**+insights/**, no storage/sqlite/**) on\nfeature/chore/promote-schemas-and-wire-gates, commit 243bfb3ea.\n\nVerified live: the storage-side identity rework this bead calls for\n(content-addressed repo_id via root-commit SHA, repo_checkouts,\nrepository/checkout/observation split) is NOT landed on this branch despite\nthe task brief describing it as \"partially addressed already\" --\nrepos.repo_id in storage/sqlite/archive_tiers/index.py:732 is still the\nplain SQL GENERATED `origin_url || char(31) || root_path` column, and there\nis no repo_checkouts table anywhere in this checkout. That work either\nbelongs to a different, not-yet-merged lane, or has not started; either way,\nstorage/sqlite/** is out of this lane's write scope this cycle (a concurrent\nlane owns it), so it was correctly left untouched here.\n\nWhat I did instead (the parser/attribution half, per the mission's scope\nsplit): _append_repo_identity_evidence in polylogue/sources/emitter.py, run\nat _SessionEmitter._maybe_enrich (the single point every session from every\nprovider passes through after provider-specific sidecar enrichment, before\nleaving sources/** for storage). It grades each session's location evidence\nwithout touching any table:\n grade=\"git_evidence\" when git_branch/git_repository_url/commit_hash is\n non-empty\n grade=\"directory_only\" when only working_directories is non-empty\n (no event) when there is no location evidence at all\npersisted as a session_event (event_type \"repo_identity_evidence\",\nevent_type has no CHECK vocabulary so this needed no migration), payload\n{grade, root_paths, git_repository_url, git_branch, git_commit_hash}.\n\nMEASURED (read-only, file:...?mode=ro against /realm/db/polylogue/index.db,\nNOT written to -- confirms this bead's own baseline exactly):\n sessions total: 18,871\n sessions with ANY git evidence (branch/url/commit): 3,003 (15.9%)\n sessions with NO git evidence (the \"directory, not repository\" case,\n per cijx.4 decision 1): 15,868 (84.1%)\n\nAC disposition:\n AC1 (repository/checkout/observation separate entities, no filesystem path\n in repo identity) -- NOT done here; storage-side, out of scope.\n AC2 (remote-URL spellings resolve to one repository) -- NOT done here;\n storage-side, out of scope.\n AC3 (live re-measure: polylogue/sinex/sinnix collapse to one repo each) --\n NOT applicable without AC1/AC2 landing first.\n AC4 (a session with no git evidence resolves to a directory, read surfaces\n say which) -- SUBSTRATE SATISFIED at the parser layer: every session now\n carries a typed repo_identity_evidence grade a reader can consult without\n re-deriving it from working_directories. The storage-side repos/\n session_repos tables still synthesize a repo row keyed on root_path\n regardless of grade (write.py:_write_repo_edges, out of this lane's\n scope) -- so today's read SURFACES (CLI/API/MCP) do not yet expose the\n distinction end-to-end. That wiring is the remaining half, blocked on the\n storage-side identity rework landing.\n AC5 (repo: query field resolves through normalized identity across\n spellings) -- NOT done here; storage-side, out of scope.\n\nWriter contract left for the storage-side lane: session_events rows with\nevent_type=\"repo_identity_evidence\" (one per session, not per-record) carry\n{grade: \"git_evidence\"|\"directory_only\", root_paths: [str],\ngit_repository_url, git_branch, git_commit_hash}. This is exactly the signal\nthe storage rework needs to decide \"synthesize a repository row\" vs \"this is\na bare directory\" without re-deriving it from raw session columns.\n","created_at":"2026-07-29T16:37:50Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"polylogue-6e7m","title":"Titles must describe what a session did: prompt echoes collide 78-way and do not distinguish sessions","description":"THE MEASUREMENT THAT SETTLES THE DESIGN. Codex state_5.sqlite threads, full scan:\n 2,771 threads carry a title\n 2,185 distinct titles\n 166 titles are shared by more than one thread\n worst: 78x 'take over claude's session 755b624d-074f-4d4f-b2fa-02d3a9e...'\n 78x 'familiarize yourself with the repo and its full beads-set'\n 36x 'find, using whatever means, either direclty ~/.codex or po...'\n\nA title you cannot select by is not a title. BOTH providers produce\nfirst-prompt echoes, so copying the provider does not solve this:\n - Claude Code ai-title records exist but cover ~12% (64 of 520 session files\n in the polylogue project dir; 2026-05: 8, 06: 25, 07: 31 -- recent feature)\n - Codex threads.title covers 2,771 of 3,054 but the values ARE the echoes above\n\nCurrent archive state: 13,611 of 18,871 sessions (72.1%) titled with a raw UUID,\nplus 2,369 (12.6%) with \u003e60-char echo titles = 84.7% unusable.\n\nDESIGN: derive the title from what the session DID. Every input is already in\nthe index, measured against untitled claude-code sessions:\n repo 100% (3,000 of 3,000 sampled)\n work_events 82%\n file paths 382,940 action_pairs rows carry tool_path\n timestamps after m3p9\nDEFINED AND MEASURED 2026-07-29, so the executing agent does not have to invent\nit. Label = repo | distinct files touched | message count | date:\n\n sqlite3 -readonly index.db \"with pl as (select s.session_id, s.message_count,\n (select r.repo_name from session_repos sr join repos r\n on r.origin_url=substr(sr.repo_id,1,instr(sr.repo_id,char(31))-1)\n and r.root_path=substr(sr.repo_id,instr(sr.repo_id,char(31))+1)\n where sr.session_id=s.session_id limit 1) repo,\n (select count(distinct ap.tool_path) from action_pairs ap\n where ap.session_id=s.session_id and ap.tool_path is not null) nfiles,\n date(s.created_at_ms/1000,'unixepoch') d\n from sessions s where s.origin='claude-code-session' and s.title=s.native_id limit 4000)\n select ...;\"\n\n 4,000 untitled sessions -\u003e 3,862 distinct labels\n collisions 138 (3.5%)\n max collision size 10\n labels used twice 62\n labels used 3+ times 24\n\nContrast the echo baseline: 166 colliding titles with a SEVENTY-EIGHT-way worst\ncase. Structural collisions are small and mostly pairwise, and the 10-way case\nis a batch of near-identical subagent spawns -- sessions that genuinely are\nalike. Adding one more discriminator (top file path, or a duration bucket) cuts\nit further; 3.5% pairwise is already usable.\n\nInputs are all present: repo on 100% of untitled sessions, message_count on\n100%, 382,940 action_pairs rows carrying tool_path, dates on 94%.\n\nProse synthesis is a worthwhile ADDITION, not the base: ~10,157 sessions x ~2K\nhead tokens is a few dollars on a small model, and the budgeted-external-call\npattern already exists (embeddings, embedding_max_cost_usd ceiling, batching,\nprogress, reconcile). Claude Code's ai-title is itself an LLM summary, so this\nreproduces the provider's own method for the residual.\n\nsessions.title_source already models provenance as\n('origin','path','heuristic','user','unknown'); add a synthesized value and\nstamp which tier produced each title so a mixed corpus stays honest.","acceptance_criteria":"1. sessions.title holds ONLY provider-supplied titles, or NULL. A derived label is never written to it. 2. The display label is computed at read time from repo, work shape, duration and size -- it is a projection, not a column, so it cannot go stale as a session grows. 3. title_source distinguishes provider-supplied from absent; it does not need a value for the derived label because the derived label is not stored. 4. Un-skipping ai-title and acquiring threads.title are inputs, not the plan -- neither closes this bead alone. 5. Re-run 'polylogue find repo:polylogue' and show the before/after rows. 6. Report the collision rate of the derived label on a sample -- collisions are acceptable, silent staleness is not.","notes":"SCOPE CORRECTION (operator, 2026-07-29). An earlier draft of this bead proposed storing structural titles as a field. That is wrong for two reasons and the correction is the actual point:\n\n (a) it would collide with genuine provider titles, which now exist for Claude\n Code (ai-title) and Codex (threads.title);\n (b) a serialized structural label goes STALE the moment the session grows --\n 'polylogue - implementation - 340 msgs - 2h' freezes at 340 while the\n session continues. Storing a computed value and then needing machinery to\n keep it honest is the precise pattern this backlog is trying to remove.\n\nSo this is not a titles problem, it is an IDENTITY AND REPRESENTATION problem:\n - a session's identity is provider-supplied and stable;\n - its display label is a projection over current state and belongs in the read\n algebra (polylogue-4p1), computed per request;\n - the archive stores what the provider said, not what a renderer would say.\n\nAlso caution: the 'implementation/research/review/planning' work-event label\nproposed as a title input is itself heuristic -- constant per-type confidence,\nand classifications like 'Create my holiday video' -\u003e implementation. See the\nsession_work_events bead. Prefer structural facts that are not themselves\ninferred (repo, file paths, duration, message count, token spend).","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:16Z","created_by":"Sinity","updated_at":"2026-07-29T04:52:16Z","labels":["area:ingest","lane:read-contracts"],"comments":[{"id":"019fad19-4ef6-7355-8b52-d3a9cb8212c8","issue_id":"polylogue-6e7m","author":"Sinity","text":"Scoped work from the parsers-only lane (feature/chore/promote-schemas-and-wire-gates, common.py/ai_parser.py/assembly_codex.py/assembly_gemini.py). Not closing -- AC #2 (read-time display-label projection) belongs in insights/storage, both out of this lane's write scope.\n\nRe-measured (live archive, index v43, pre-rebuild -- reflects the OLD parsers, not what's about to ship):\n- claude-code-session: 10,157/12,001 (84.6%) title==native_id -- unchanged from the bead's original number, confirms the ai-title/custom-title wiring (already landed by a prior lane before this session) hasn't been exercised yet, only takes effect on rebuild.\n- codex-session: 3,201/3,201 (100%) title==native_id on the live archive -- the OLD codex parser wrote no sidecar title at all; thread-name/history/state-db resolution is new-parser-only (also prior-lane landed).\n- claude-ai-export: 1/377 (0.3%) title==native_id -- already near-total coverage; Claude web auto-titles almost every conversation.\n\nFound and fixed a real mislabeling bug in the newly-landed Codex title resolution (assembly_codex.py): history_titles (by construction the earliest authored prompt, per _parse_codex_history's own docstring) and state_titles (state_5.sqlite threads.title -- the exact field this bead's 78-way-collision measurement scanned) were both stamped TitleSource.ORIGIN at 0.9/0.75 confidence, the same claim as genuine curation, despite being provably first-prompt echoes. Verified empirically against this operator's own state_5.sqlite/history.jsonl: of 780 threads with a comparable history.jsonl row, 679 (87%) were exact-or-prefix matches of the session's own opening message. Added _is_prompt_echo (compares each candidate against the session's own first human-authored message) and downgraded matches to HEURISTIC/0.5 -- same title text, honest provenance. Applied to all three Codex evidence lanes (thread name, history, state db).\n\nAlso completed title_source/title_ref/title_confidence for claude-ai-export (ai_parser.py) -- parse_ai/_parse_design_chat resolved a real curated title but never stamped provenance at all before this change.\n\nConclusion on AC #2 (structural display-label projection, repo|files|messages|date): the derivation is real and was already measured in this bead's own description (3.5% collision vs 78-way echo collision), but I did not implement it as a parser-time write to sessions.title. Doing so would violate this bead's own scope-correction note (AC #1: sessions.title holds ONLY provider-supplied titles or NULL; a derived label is never written to it) and my lane's write scope excludes insights/** and storage/** where the read-time projection belongs. Recommend a follow-up bead scoped to insights/storage for the projection itself, separate from parser-level title-provenance hygiene.\n\nVerification: devtools test tests/unit/sources/test_assembly.py tests/unit/sources/test_parsers_claude_ai_catalog.py tests/unit/sources/test_parsers_props.py tests/unit/storage/test_title_ref_confidence_queryable.py tests/unit/sources/test_origin_specs.py -- all green except test_parsers_props.py's 4 pre-existing hypothesis failures (claude-code/codex role-consistency, confirmed unrelated/pre-existing). devtools verify --quick exit 0.\n\nAlso fixed (separate, coordinator-requested finding on polylogue-9x22): Claude AI web-tool evidence (integration_name, approval_key, display_content, etc.) merged into block.metadata was never persisted (no metadata column on blocks table) -- routed through session_events instead (common.py), following the hermes_spans.py precedent.\n","created_at":"2026-07-29T08:59:05Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} {"_type":"issue","id":"polylogue-0jf4","title":"Codex SQLite state is never acquired: 5 databases, 706 MB, including spawn topology and 2,771 titles","description":"Measured 2026-07-29. ~/.codex holds five SQLite databases; raw_sessions contains no row whose source_path is any of them.\n\n state_5.sqlite 39 MB threads (3,054 rows, 2,771 with a non-empty title),\n thread_spawn_edges (1,030), thread_dynamic_tools,\n remote_control_enrollments, external_agent_config_imports\n logs_2.sqlite 627 MB logs (47,060 rows: ts, level, target, module_path,\n file, line, thread_id, process_uuid, estimated_bytes)\n memories_1.sqlite 456 KB stage1_outputs, jobs\n goals_1.sqlite 44 KB thread_goals, thread_goal_continuation_deferrals\n codex-dev.db 36 KB\n\nTHE MACHINERY ALREADY EXISTS AND IS USED FOR A DIFFERENT ORIGIN: Hermes .db\nfiles ARE acquired (/home/sinity/.hermes/state.db and verification_evidence.db\nappear in raw_sessions). SQLite-source acquisition is built, applied to one\nprovider, and not propagated -- the same shape as content-addressing being\napplied only to embeddings and OriginSpec declaring a detector order nothing\nreads.\n\nWHAT IS BEING RECONSTRUCTED BY INFERENCE INSTEAD:\n threads.title 2,771 -\u003e all 3,201 Codex sessions are UUID-titled\n (polylogue-ih67 builds a resolution ladder; the\n ladder's own notes cite this table as 'richer than\n session_index.jsonl on live installs')\n thread_spawn_edges 1,030 -\u003e Codex delegation topology, which polylogue-1vpm\n and polylogue-4ts derive from transcript inference\n thread_goals -\u003e stated task intent, unavailable anywhere else\n memories_1 stage1_outputs-\u003e Codex-side memory, no archive representation\n\nlogs_2 is 627 MB of runtime logging (level/target/module_path/file/line) rather\nthan session evidence -- classify it deliberately rather than acquiring by\ndefault. It may be the right home for runtime-observability questions, or it may\nbe correctly out of scope; the point is that nobody has decided.\n\nNOTE state_5.sqlite is live-locked on a running install; ih67's notes already\nprescribe copy-first.","acceptance_criteria":"1. Each of the five databases is classified as acquire / acquire-partially / out-of-scope, with the reason recorded in the Codex OriginSpec fidelity declaration. 2. Acquisition reuses the Hermes sqlite path rather than adding a second mechanism. 3. threads.title and thread_spawn_edges reach the archive as typed evidence and are consumed by title resolution and topology respectively. 4. Live-locked databases are copied before reading; a running Codex is never blocked. 5. Report the before/after UUID-title census for codex-session and the count of spawn edges that replaced inferred ones.","notes":"Implemented on branch feature/sources/acquire-sidecars-and-codex-sqlite (commits\n8e9778209 wiring, a70bd9257 tests), within OWNS: sources/live/batch.py,\nsources/live/watcher.py, sources/origin_specs.py (storage/sqlite untouched,\nper the concurrent schema-lane constraint on this branch).\n\nWHAT WAS UNACQUIRED AND WHY: sources/parsers/codex_state.py (classification +\nparsers) already existed but was completely unwired -- zero references from\ndispatch.py/batch.py/watcher.py, exactly as its own docstring stated. The root\ncause was never \"not implemented\" at the parser level; it was that\nsources/live/batch.py's ~2900-line acquire/parse loop special-cased Hermes by\nname (`provider is Provider.HERMES`) at three tail sites and had no equivalent\nbranch for a second sqlite-snapshot provider.\n\nWHAT CHANGED:\n- sources/live/batch.py: acquire loop gains a filename-gated (no I/O for the\n common case) + structurally-verified (codex_state.is_in_scope_codex_sqlite_path)\n branch for state_5.sqlite/goals_1.sqlite/memories_1.sqlite, snapshotting via\n the SAME snapshot_sqlite_to_blob (SQLite backup API, never a raw read of a\n live-locked file) Hermes already uses, minting a raw_id via\n codex_state_raw_id (AC2: no second mechanism). logs_2.sqlite/codex-dev.db\n are excluded by filename before any bytes are read (AC1's out-of-scope\n classification enforced at runtime, not just documented).\n- The three `provider is Provider.HERMES` special cases in the acquire-loop\n tail are generalized to `path in raw_source_revisions` / `record.blob_hash\n is not None` -- the real distinguishing signal (sqlite-snapshot acquisition\n vs. content-hash acquisition) rather than a Hermes-specific one, since Codex\n now shares Provider.CODEX with its own JSONL rollout acquisition.\n- Parse stage: a new elif (gated on provider is Provider.CODEX AND a\n structural re-check of the acquired blob, mirroring Hermes's own two elifs)\n routes thread_state to _write_codex_thread_state_evidence and admits\n goals_1/memories_1 raw bytes only (acquire-partial, no derived parse, per\n CODEX_STATE_FIDELITY) -- both bypass session materialization entirely via\n the same \"fact artifact\" continue idiom the codebase already uses.\n- sources/live/watcher.py: a SECOND WatchSource (\"codex-state\", root ~/.codex,\n suffixes .sqlite/.db) rather than widening the existing \"codex\" JSONL\n source's root -- avoids ever reasoning about history.jsonl/config.toml/log/\n under the shared root.\n- sources/origin_specs.py: _codex_spec() fidelity_notes now carries all 5\n databases' classification+reason (AC1), mirroring codex_state.py's\n CODEX_STATE_FIDELITY (that module explicitly names this file as the\n canonical home for the text).\n\nWHERE EVIDENCE LANDS: threads.title and thread_spawn_edges reach\nsource.db's raw_hook_events (event_type=codex_thread_title /\ncodex_thread_spawn_edge), keyed to the EXISTING codex-session row via\nsession_native_id=thread_id -- the SAME mechanism sources/hooks.py already\nuses for hook events (ArchiveStore.write_hook_event), read at query time via\nthe ALREADY-WIRED ArchiveStore.hook_event_summary_for_session /\nPolylogue.get_hook_event_summary_for_session (live in the CLI's message/read\nview). No index schema change: raw_hook_events.event_type is unconstrained\nTEXT, exactly the documented cheap route.\n\nMEASURED (read-only, real live ~/.codex install, scratch archive under\n/realm/tmp, never touched /realm/db/polylogue):\n state_5.sqlite 40,116,224 bytes acquired (backup took ~121s -- live\n WAL contention with the\n running Codex install;\n correctness unaffected,\n noted as an operational\n observation, not a bug)\n goals_1.sqlite 45,056 bytes acquired (0.5s)\n memories_1.sqlite 466,944 bytes acquired (0.3s)\n logs_2.sqlite 657,100,800 bytes excluded by name, 0 bytes read\n codex-dev.db -- absent on this install, skipped\n total blob bytes acquired: 53,023,051\n raw_sessions rows (raw-tier admission, NOT sessions): 3\n raw_hook_events: 4,085 total -- codex_thread_title=3,055, codex_thread_spawn_edge=1,030\n (1,030 matches the bead's own original spawn-edge count exactly)\n index.db sessions rows after ingest: 0 -- confirms the hard constraint\n (thread_spawn_edges/titles never mint a session)\n\nAC DISPOSITION:\n1. Classify each of 5 dbs with reason in Codex OriginSpec fidelity -- SATISFIED\n (origin_specs.py _codex_spec() fidelity_notes, all 5).\n2. Reuse the Hermes sqlite path, no second mechanism -- SATISFIED\n (snapshot_sqlite_to_blob shared; codex_state_raw_id mirrors\n hermes_profile_raw_id exactly).\n3. threads.title/thread_spawn_edges reach the archive as typed evidence --\n SATISFIED (raw_hook_events, verified against real data above). \"...and are\n consumed by title resolution and topology respectively\" -- NOT done in\n this lane; deliberately deferred (codex_state.py's own docstring already\n named assembly_codex.py/topology consumption out of scope to avoid\n colliding with the still-in-flight ih67 ladder). Follow-up filed:\n polylogue-foee.\n4. Live-locked databases copied before reading, running Codex never blocked --\n SATISFIED, verified against the REAL live install (state_5.sqlite was\n actively WAL-written during acquisition; backup succeeded, no lock\n contention errors, Codex itself was not blocked).\n5. Report before/after UUID-title census + spawn-edge replacement count --\n PARTIAL. Spawn-edge count IS reported above (1,030, matching the bead's\n original measurement exactly). The UUID-title census does NOT change in\n this PR: the acquired titles sit in raw_hook_events as typed evidence but\n nothing yet folds them into the session's own displayed title (that is\n exactly polylogue-foee's scope) -- so the honest report is \"evidence\n acquired, consumption and the resulting census change are the follow-up.\"\n\nVerification: devtools test tests/unit/sources/test_codex_state_live_ingest.py\ntests/unit/sources/test_live_watcher_catchup_order.py -\u003e 9 passed. mypy\n--strict + ruff clean on all touched files. Anti-vacuity confirmed by hand:\ntemporarily short-circuiting _write_codex_thread_state_evidence made the\nevidence-attachment test fail (`None == 1`) while the session-count and\nout-of-scope tests kept passing; reverted with a clean diff against the\ncommitted state (verified via `git diff --stat` showing no residual change).","status":"closed","priority":1,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:14Z","created_by":"Sinity","updated_at":"2026-07-31T04:15:24Z","started_at":"2026-07-31T04:13:14Z","closed_at":"2026-07-31T04:15:24Z","close_reason":"RE-VERIFIED 2026-07-31, no code changes needed: the entire in-scope acquisition\nthis bead calls for was ALREADY on origin/master before this session started,\nlanded via commit de8717a936 (\"feat(sources): acquire Codex threads/spawn-edges\nas typed evidence\") as part of the large feature/chore/promote-schemas-and-wire-gates\nmerge train -- NOT via the stale local branch\nfeature/sources/acquire-sidecars-and-codex-sqlite this bead's own notes\ndescribe (commits 8e9778209/a70bd9257 on that branch never got pushed or PR'd;\ncherry-picking them onto a fresh branch off origin/master produced an EMPTY\ndiff, proving byte-for-byte equivalent content already shipped).\n\nConfirmed present and correct on master (read-only inspection, no ~/.codex\nwrites):\n- polylogue/sources/parsers/codex_state.py: classifies all 5 dbs\n (thread_state/goals/memories -\u003e acquire[-partial], logs/automation -\u003e\n out-of-scope) via CODEX_STATE_FIDELITY.\n- sources/origin_specs.py _codex_spec(): fidelity_notes carries all 5\n classifications + reasons (AC1 satisfied).\n- sources/live/batch.py: acquire loop snapshots state_5/goals_1/memories_1\n via the SAME snapshot_sqlite_to_blob Hermes uses (AC2: no second\n mechanism); logs_2.sqlite/codex-dev.db excluded by name before any bytes\n read; parse stage attaches threads.title/thread_spawn_edges to the\n EXISTING codex-session row via write_hook_event (event_type\n codex_thread_title/codex_thread_spawn_edge), never minting a session of\n its own (AC3 acquisition half + AC4's session-count-inflation guard).\n- sources/live/watcher.py: second \"codex-state\" WatchSource rooted at\n ~/.codex (suffixes .sqlite/.db), separate from the \"codex\" JSONL source's\n ~/.codex/sessions root.\n- Live-locked read safety (AC4): snapshot_sqlite_to_blob uses the sqlite3\n backup API, never a raw read of the live file.\n\nTests: devtools test tests/unit/sources/test_codex_state_live_ingest.py\ntests/unit/sources/parsers/test_codex_state.py\ntests/unit/sources/parsers/test_codex_state_schema_canary.py -\u003e 22 passed.\n\nReal ~/.codex measurement (read-only, sqlite3 file:...?mode=ro, no writes):\n state_5.sqlite: threads=3,057 rows, 2,774 with non-empty title (bead's\n original count: 3,054/2,771 -- grew by 3 in the 2 days since filing,\n consistent with normal usage, not a discrepancy)\n thread_spawn_edges: 1,030 (exact match to bead's original count)\n goals_1.sqlite thread_goals: 26 rows\n memories_1.sqlite stage1_outputs: 30 rows\n codex-dev.db: absent on this install (handled: out-of-scope name, no-op)\n\nAC DISPOSITION (unchanged from the prior session's own analysis, now\nverified against master rather than an unlanded branch):\n1. Classify each of 5 dbs with reason in Codex OriginSpec fidelity --\n SATISFIED.\n2. Reuse the Hermes sqlite path, no second mechanism -- SATISFIED.\n3. threads.title/thread_spawn_edges reach the archive as typed evidence --\n SATISFIED (raw_hook_events). \"...and are consumed by title resolution\n and topology respectively\" -- NOT done, deliberately deferred to the\n already-filed polylogue-foee (title-ladder consumption is\n sources/assembly_codex.py, topology consumption is the\n polylogue-1vpm/4ts inferred-edge reader -- both outside this bead's\n parsers/codex*.py + OriginSpec + tests write surface, and foee is\n explicitly scoped to exactly that remaining work).\n4. Live-locked databases copied before reading -- SATISFIED (sqlite3 backup\n API, verified in source).\n5. Report before/after UUID-title census + spawn-edge count -- PARTIAL,\n same as previously documented: spawn-edge count reported above (1,030).\n The census does not change until polylogue-foee wires title-ladder\n consumption; until then all Codex sessions remain UUID-titled by design\n (the acquired titles sit in raw_hook_events, not yet folded into the\n session's displayed title).\n\nClosing as satisfied within this bead's write scope (parsers/codex*.py,\nCODEX_SESSION OriginSpec, tests) -- AC3's consumption half and AC5's\npost-consumption census are polylogue-foee's scope, already tracked there\nand correctly out of this bead's surface (foee's own AC1/AC2 name\nsources/assembly_codex.py and the topology insight reader, not this bead's\nfiles). No PR opened: verified zero diff against origin/master, nothing to\nland.\n","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-rujy","title":"Claude Code tool-results sidecars unacquired: 1.34 GB across 12,588 files, 3 ingested","description":"Measured 2026-07-29 against ~/.claude/projects and the live source tier.\n\n ON DISK ACQUIRED (raw_sessions.source_path)\n *.jsonl 11,540 files 13,275.9 MB projects/ root 5,559\n subagents/ 572 dirs 2,933.2 MB subagents/ 13,916\n tool-results/ 582 dirs 1,339.6 MB tool-results/ 3\n memory/ 14 dirs 0.8 MB --\n\nClaude Code writes a tool result to \u003csession\u003e/tool-results/\u003ctool_id\u003e.\u003cext\u003e\nwhenever the output exceeds the inline limit, leaving only a stub in the\ntranscript. 3,058 tool_result blocks already in the archive contain the literal\ntext 'Full output saved to' -- the archive is storing its own admission that the\ncontent is elsewhere, and the elsewhere is never read.\n\nTHE JOIN IS TRIVIAL: the filename IS the tool id. Verified by intersecting\n10,545 distinct tool ids from disk filenames against a 400k-row sample of\nblocks.tool_id -- 2,813 matched on the sample alone.\n\nCONSEQUENCES: FTS cannot match anything that lived in a large tool output, so\n'polylogue find X' silently misses it; and outcome/exit-code evidence carried in\na truncated result is unavailable, which is one contributor to the 72%-unknown\ntool_result_is_error measured on this archive.\n\nHARD CONSTRAINT (operator, 2026-07-29): these are BLOCK CONTENT, not sessions.\nThe hook-event inflation incident is the precedent -- standalone ingestion of\nnon-session records inflated the archive from 18,391 to 83,286 sessions before\nbeing reverted. A tool-results file must attach to its existing tool_result\nblock by tool_id and must never create a session, a raw session row that parses\nas a session, or a new top-level unit of any kind.","acceptance_criteria":"1. tool-results content attaches to the existing tool_result block via tool_id; session count is unchanged before and after, asserted by a test. 2. Its text is searchable -- a term that appears only inside a large tool output is findable via FTS. 3. Unmatched files (a tool id with no block) are recorded as typed acquisition debt, not silently dropped. 4. Blob storage is content-addressed and deduplicated; report bytes added. 5. Ingest wall-clock impact is measured against the polylogue-623q envelope before this is enabled by default.","notes":"Investigation + scoped implementation landed (worktree-agent-a6730c39cc2369360, commit 9b37431fd on feature/chore/promote-schemas-and-wire-gates):\n\nMEASUREMENT (read-only against live ~/.claude/projects, sampled ~330MB across 80 sessions):\n- Genuinely-truncated \"output too large\" overflow sidecars: ~12% of files, ~60-65% of bytes, ~99% new content beyond the inline preview.\n- Never-truncated \"full mirror\" sidecars (Claude Code unconditionally persists many small Read/Grep/Edit results too): ~85% of files, \u003c2% of bytes, ~97% already-duplicate of inline block text.\n- Orphan sidecars with no owning tool_result block left in the retained transcript (compaction pruned the referencing turn): ~1-5% of files, real acquisition debt, not a bug in the join.\n- Filename scheme (toolu_\u003cid\u003e vs internal short-slug vs call_NN_\u003cid\u003e vs mcp-\u003cserver\u003e-\u003ctool\u003e-\u003cts\u003e) does NOT predict which bucket a file is in -- both truncated-overflow and full-mirror sidecars use both toolu_ and non-toolu_ names. The reliable join key is always the owning block's tool_use_id, recovered directly (filename stem) or via the \"Full output saved to\"/\"Output has been saved to\" pointer in that block's own preview text -- including for Task subagent transcripts, whose sidecars persist to the session-level tool-results/ dir under the subagent's own (non-toolu_) tool id, not a per-subagent dir.\n- hook-*.txt files under the same directory (185 of 12,588 sampled) are a separate, already-tracked mechanism (raw hook stdout, polylogue-qqyg/#2781) -- correctly excluded from both acquisition and debt.\n\nRECOMMENDATION: acquire, but content-aware (replace-when-truncated), not blanket-copy-the-directory. This is what was built.\n\nBUILT (within OWNS: sources/live/**, sources/parsers/claude/**):\n- polylogue/sources/live/tool_result_sidecars.py: join_tool_result_sidecars(payload, tool_results_dir) -\u003e SidecarJoinResult(matched, debt). Read-only, no writes.\n- polylogue/sources/parsers/claude/code_parser.py: apply_tool_result_sidecars() attaches the join result to an already-parsed ParsedSession -- replaces truncated tool_result block text (AC2: FTS indexes block content, so this makes large-output terms findable), leaves full-mirror blocks untouched, and emits a bounded claude_tool_result_sidecar session_event per file (matched or debt) -- id/filename/size/content_hash/status only, never raw bytes in the event (no schema bump needed, per constraint). parse_code/parse_code_stream take tool_result_sidecars as an optional kwarg; omitting it is a no-op (verified).\n- tests/unit/sources/test_tool_result_sidecars.py: 5 tests, synthetic fixtures only. Verifies AC1 (session/message count and ids unchanged with sidecars attached), AC2 (a term only in the full sidecar becomes findable in block text; anti-vacuity confirmed -- nulling the replacement dict makes this assertion fail, not a self-validating mock), AC3 (unmatched file becomes a typed debt event; hook-*.txt never does).\n\nAC DISPOSITION:\n1. Attaches by tool_id, session count unchanged, asserted by test -- SATISFIED (test_apply_tool_result_sidecars_replaces_truncated_block_text_only).\n2. FTS-findable -- SATISFIED at the block-content layer (block.text is what FTS indexes); not verified end-to-end through a live FTS query in this pass since that requires the dispatch.py wiring below to actually run during ingest.\n3. Unmatched -\u003e typed acquisition debt, not silently dropped -- SATISFIED (SidecarDebt -\u003e claude_tool_result_sidecar event, acquisition_status=debt, reason=no_owning_tool_result_block).\n4. Blob storage content-addressed + deduplicated, bytes-added report -- PARTIAL. content_hash is computed and recorded per sidecar (SHA-256 via core.hashing.hash_text) but there is no dedicated blob_refs-tier write here; the acquired text rides into the existing blocks table via the block's own text field, which already participates in the archive's session-level content-hash idempotency. True cross-session blob dedup needs storage-tier work (storage/repair.py or a raw_authority.py-adjacent path), explicitly outside this lane's OWNS list. Not implemented; flagged as a real gap, not silently declared done.\n5. Ingest wall-clock vs polylogue-623q envelope, default-off until measured -- NOT DONE. This lane never got as far as running ingest, because the acquisition path isn't wired into dispatch.py yet (see polylogue-wjgf). Cannot honestly claim this AC without that wiring existing to measure.\n\nFOLLOW-UP: polylogue-wjgf (dispatch.py wiring: derive tool-results dir from source_path, call the join, pass result into parse_code; plus the default-on-vs-flagged decision needing config.py/CLI, and the streaming-path equivalent). AC4's blob-store dedup and AC5's wall-clock measurement both depend on that wiring landing first.\n\nVerification: devtools test tests/unit/sources/test_tool_result_sidecars.py tests/unit/sources/test_parsers_claude_code_artifacts.py -\u003e 31 passed. mypy --strict clean on both changed modules. ruff check/format clean. devtools render topology-projection + topology-status regenerated and committed (new module under polylogue/). devtools render all --check: no \"out of sync\" lines.\n\n[2026-07-29, polylogue-wjgf follow-up] Wiring landed (branch feature/chore/promote-schemas-and-wire-gates, commit 2237e8a82). AC5 (ingest wall-clock vs polylogue-623q envelope, default-off until measured) is now resolved: measured join_tool_result_sidecars against the FULL population of real ~/.claude/projects sessions with a tool-results/ dir (525 sessions) -- total added join time 8.2s (704MB matched + 708MB debt bytes, 9,421 matched files / 3,012 debt files), ~23% on top of just those sessions' own JSONL read time but those sessions are ~3% of the corpus, so well under 1% of a \u003c60min full-rebuild budget. Decision: default-on, no flag. AC4 (blob-store dedup) remains PARTIAL/deferred as originally noted -- still needs storage-tier work outside sources/live and sources/dispatch scope; not addressed by wjgf.\nVERIFICATION (group4 stale-sweep, 2026-07-31): PARTIAL. AC1-3 and AC5 satisfied (attach-by-tool_id, FTS-findable, typed debt events, wall-clock measured/default-on per the 2026-07-29 wjgf follow-up note). AC4 (content-addressed, deduplicated blob storage with bytes-added report) explicitly marked PARTIAL/deferred in the bead's own notes -- content_hash is computed but there is no blob_refs-tier write. Confirmed on master: polylogue/sources/live/tool_result_sidecars.py has no blob-store/dedup logic. Evidence: git show origin/master:polylogue/sources/live/tool_result_sidecars.py | grep -n 'blob_ref|content_addressed|dedup' -\u003e no matches.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:13Z","created_by":"Sinity","updated_at":"2026-07-31T05:54:12Z","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-rujy","title":"Claude Code tool-results sidecars unacquired: 1.34 GB across 12,588 files, 3 ingested","description":"Measured 2026-07-29 against ~/.claude/projects and the live source tier.\n\n ON DISK ACQUIRED (raw_sessions.source_path)\n *.jsonl 11,540 files 13,275.9 MB projects/ root 5,559\n subagents/ 572 dirs 2,933.2 MB subagents/ 13,916\n tool-results/ 582 dirs 1,339.6 MB tool-results/ 3\n memory/ 14 dirs 0.8 MB --\n\nClaude Code writes a tool result to \u003csession\u003e/tool-results/\u003ctool_id\u003e.\u003cext\u003e\nwhenever the output exceeds the inline limit, leaving only a stub in the\ntranscript. 3,058 tool_result blocks already in the archive contain the literal\ntext 'Full output saved to' -- the archive is storing its own admission that the\ncontent is elsewhere, and the elsewhere is never read.\n\nTHE JOIN IS TRIVIAL: the filename IS the tool id. Verified by intersecting\n10,545 distinct tool ids from disk filenames against a 400k-row sample of\nblocks.tool_id -- 2,813 matched on the sample alone.\n\nCONSEQUENCES: FTS cannot match anything that lived in a large tool output, so\n'polylogue find X' silently misses it; and outcome/exit-code evidence carried in\na truncated result is unavailable, which is one contributor to the 72%-unknown\ntool_result_is_error measured on this archive.\n\nHARD CONSTRAINT (operator, 2026-07-29): these are BLOCK CONTENT, not sessions.\nThe hook-event inflation incident is the precedent -- standalone ingestion of\nnon-session records inflated the archive from 18,391 to 83,286 sessions before\nbeing reverted. A tool-results file must attach to its existing tool_result\nblock by tool_id and must never create a session, a raw session row that parses\nas a session, or a new top-level unit of any kind.","acceptance_criteria":"1. tool-results content attaches to the existing tool_result block via tool_id; session count is unchanged before and after, asserted by a test. 2. Its text is searchable -- a term that appears only inside a large tool output is findable via FTS. 3. Unmatched files (a tool id with no block) are recorded as typed acquisition debt, not silently dropped. 4. Blob storage is content-addressed and deduplicated; report bytes added. 5. Ingest wall-clock impact is measured against the polylogue-623q envelope before this is enabled by default.","notes":"Investigation + scoped implementation landed (worktree-agent-a6730c39cc2369360, commit 9b37431fd on feature/chore/promote-schemas-and-wire-gates):\n\nMEASUREMENT (read-only against live ~/.claude/projects, sampled ~330MB across 80 sessions):\n- Genuinely-truncated \"output too large\" overflow sidecars: ~12% of files, ~60-65% of bytes, ~99% new content beyond the inline preview.\n- Never-truncated \"full mirror\" sidecars (Claude Code unconditionally persists many small Read/Grep/Edit results too): ~85% of files, \u003c2% of bytes, ~97% already-duplicate of inline block text.\n- Orphan sidecars with no owning tool_result block left in the retained transcript (compaction pruned the referencing turn): ~1-5% of files, real acquisition debt, not a bug in the join.\n- Filename scheme (toolu_\u003cid\u003e vs internal short-slug vs call_NN_\u003cid\u003e vs mcp-\u003cserver\u003e-\u003ctool\u003e-\u003cts\u003e) does NOT predict which bucket a file is in -- both truncated-overflow and full-mirror sidecars use both toolu_ and non-toolu_ names. The reliable join key is always the owning block's tool_use_id, recovered directly (filename stem) or via the \"Full output saved to\"/\"Output has been saved to\" pointer in that block's own preview text -- including for Task subagent transcripts, whose sidecars persist to the session-level tool-results/ dir under the subagent's own (non-toolu_) tool id, not a per-subagent dir.\n- hook-*.txt files under the same directory (185 of 12,588 sampled) are a separate, already-tracked mechanism (raw hook stdout, polylogue-qqyg/#2781) -- correctly excluded from both acquisition and debt.\n\nRECOMMENDATION: acquire, but content-aware (replace-when-truncated), not blanket-copy-the-directory. This is what was built.\n\nBUILT (within OWNS: sources/live/**, sources/parsers/claude/**):\n- polylogue/sources/live/tool_result_sidecars.py: join_tool_result_sidecars(payload, tool_results_dir) -\u003e SidecarJoinResult(matched, debt). Read-only, no writes.\n- polylogue/sources/parsers/claude/code_parser.py: apply_tool_result_sidecars() attaches the join result to an already-parsed ParsedSession -- replaces truncated tool_result block text (AC2: FTS indexes block content, so this makes large-output terms findable), leaves full-mirror blocks untouched, and emits a bounded claude_tool_result_sidecar session_event per file (matched or debt) -- id/filename/size/content_hash/status only, never raw bytes in the event (no schema bump needed, per constraint). parse_code/parse_code_stream take tool_result_sidecars as an optional kwarg; omitting it is a no-op (verified).\n- tests/unit/sources/test_tool_result_sidecars.py: 5 tests, synthetic fixtures only. Verifies AC1 (session/message count and ids unchanged with sidecars attached), AC2 (a term only in the full sidecar becomes findable in block text; anti-vacuity confirmed -- nulling the replacement dict makes this assertion fail, not a self-validating mock), AC3 (unmatched file becomes a typed debt event; hook-*.txt never does).\n\nAC DISPOSITION:\n1. Attaches by tool_id, session count unchanged, asserted by test -- SATISFIED (test_apply_tool_result_sidecars_replaces_truncated_block_text_only).\n2. FTS-findable -- SATISFIED at the block-content layer (block.text is what FTS indexes); not verified end-to-end through a live FTS query in this pass since that requires the dispatch.py wiring below to actually run during ingest.\n3. Unmatched -\u003e typed acquisition debt, not silently dropped -- SATISFIED (SidecarDebt -\u003e claude_tool_result_sidecar event, acquisition_status=debt, reason=no_owning_tool_result_block).\n4. Blob storage content-addressed + deduplicated, bytes-added report -- PARTIAL. content_hash is computed and recorded per sidecar (SHA-256 via core.hashing.hash_text) but there is no dedicated blob_refs-tier write here; the acquired text rides into the existing blocks table via the block's own text field, which already participates in the archive's session-level content-hash idempotency. True cross-session blob dedup needs storage-tier work (storage/repair.py or a raw_authority.py-adjacent path), explicitly outside this lane's OWNS list. Not implemented; flagged as a real gap, not silently declared done.\n5. Ingest wall-clock vs polylogue-623q envelope, default-off until measured -- NOT DONE. This lane never got as far as running ingest, because the acquisition path isn't wired into dispatch.py yet (see polylogue-wjgf). Cannot honestly claim this AC without that wiring existing to measure.\n\nFOLLOW-UP: polylogue-wjgf (dispatch.py wiring: derive tool-results dir from source_path, call the join, pass result into parse_code; plus the default-on-vs-flagged decision needing config.py/CLI, and the streaming-path equivalent). AC4's blob-store dedup and AC5's wall-clock measurement both depend on that wiring landing first.\n\nVerification: devtools test tests/unit/sources/test_tool_result_sidecars.py tests/unit/sources/test_parsers_claude_code_artifacts.py -\u003e 31 passed. mypy --strict clean on both changed modules. ruff check/format clean. devtools render topology-projection + topology-status regenerated and committed (new module under polylogue/). devtools render all --check: no \"out of sync\" lines.\n\n[2026-07-29, polylogue-wjgf follow-up] Wiring landed (branch feature/chore/promote-schemas-and-wire-gates, commit 2237e8a82). AC5 (ingest wall-clock vs polylogue-623q envelope, default-off until measured) is now resolved: measured join_tool_result_sidecars against the FULL population of real ~/.claude/projects sessions with a tool-results/ dir (525 sessions) -- total added join time 8.2s (704MB matched + 708MB debt bytes, 9,421 matched files / 3,012 debt files), ~23% on top of just those sessions' own JSONL read time but those sessions are ~3% of the corpus, so well under 1% of a \u003c60min full-rebuild budget. Decision: default-on, no flag. AC4 (blob-store dedup) remains PARTIAL/deferred as originally noted -- still needs storage-tier work outside sources/live and sources/dispatch scope; not addressed by wjgf.\nVERIFICATION (group4 stale-sweep, 2026-07-31): PARTIAL. AC1-3 and AC5 satisfied (attach-by-tool_id, FTS-findable, typed debt events, wall-clock measured/default-on per the 2026-07-29 wjgf follow-up note). AC4 (content-addressed, deduplicated blob storage with bytes-added report) explicitly marked PARTIAL/deferred in the bead's own notes -- content_hash is computed but there is no blob_refs-tier write. Confirmed on master: polylogue/sources/live/tool_result_sidecars.py has no blob-store/dedup logic. Evidence: git show origin/master:polylogue/sources/live/tool_result_sidecars.py | grep -n 'blob_ref|content_addressed|dedup' -\u003e no matches.\n2026-07-31 acquisition-completeness audit recount: class has grown to 12,744 files / 1,450,338,905 bytes (12,741 unacquired; 3 stray .json ingested; 30/30 random sha256 samples have no blob_hash match). Oldest 2026-01-19, newest same-day as audit - ACTIVE unbounded growth. Same pattern exists for gemini-cli: ~/.gemini/tmp/*/tool-outputs = 218 files / 78,496,025 bytes, 100% unacquired (dormant since 2026-04) - whatever capture-or-ledger decision lands here should cover that analog class too. Also unaccounted nearby: memory/*.md 249 files/814KB and ~5 large gemini chats/*.json checkpoints 76.9MB of REAL session content (sessionId/messages/summary verified) with no raw rows.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:13Z","created_by":"Sinity","updated_at":"2026-07-31T10:11:57Z","labels":["area:ingest","lane:origin-interop-export"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-j8u2","title":"Subagent children are 45.6% of the archive and rank equal to real sessions in every result list","description":"Measured 2026-07-29: 8,614 of 18,871 sessions (45.6%) are subagent children; 8,824 session_links rows carry link_type='subagent'.\n\nThey are presented at equal weight in default result sets. Actual output of the\ndeployed CLI against the live archive:\n\n $ polylogue find repo:polylogue\n claude-code-session:5ecd 2026-07-27 5ecdb160-...-a3a24886af8cc:agent-af4e... (342 msgs)\n claude-code-session:5ecd 2026-07-27 5ecdb160-...-a3a24886af8cc:agent-ad73... (1098 msgs)\n claude-code-session:5ecd 2026-07-28 5ecdb160-...-a3a24886af8cc:agent-ad68... (499 msgs)\n\nThree rows, one parent, differing only by an agent suffix -- and the same shape\nfills . Combined with the title defect, a default query returns a\nlist that is ~46% fanout and ~85% UUID-labelled.\n\nThis is not a correctness bug and not a latency bug. The queries are right and\nfast (2.8-7.6s measured). It is the reason the archive cannot be read by a\nhuman, and therefore the practical gate on the operator using the product at\nall -- ahead of every substrate program in the backlog.\n\nNON-GOAL: hiding subagent evidence. It is real work and must stay queryable and\ncitable. The default result UNIT should be the top-level session, with its\ndelegation fan available on request, rather than one row per spawn.\n\nRelated: polylogue-4ts (lineage truth: counted once) is the storage-side\nstatement of the same problem; this bead is the read-side one. polylogue-fcyf\nwants fanout as a first-class live view, which is the deliberate opposite\npresentation and stays valid.","acceptance_criteria":"1. The default result unit is the top-level session; subagent children are reachable through an explicit projection, not by filling the list. 2. Session counts on read surfaces state which unit they count -- an archive of 18,871 rows containing 8,614 fanout children must never present '18,871 sessions' unqualified. 3. Subagent evidence remains fully queryable and citable; a query that asks for children still gets them. 4. Re-run the exact dogfood commands and show before/after output in the closing note.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:09Z","created_by":"Sinity","updated_at":"2026-07-29T04:52:09Z","labels":["area:query","lane:read-contracts"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-t5lg","title":"84.6% of Claude Code sessions are titled with a raw UUID: 10,157 of 12,001","description":"Measured on the live archive 2026-07-29 (full scan, 18,871 sessions):\n\n origin total title = native_id pct\n claude-code-session 12,001 10,157 84.6%\n codex-session 3,201 3,201 100.0% \u003c- owned by polylogue-ih67\n hermes-session 279 157 56.3%\n aistudio-drive 239 88 36.8%\n gemini-cli-session 17 7 41.2%\n chatgpt-export 2,635 0 0.0%\n claude-ai-export 377 1 0.3%\n antigravity-session 116 0 0.0%\n grok-export 6 0 0.0%\n\n archive-wide: 13,611 of 18,871 (72.1%) titled with a UUID,\n plus 2,369 (12.6%) with titles over 60 chars (prompt echoes)\n -\u003e 84.7% of the archive has no usable title.\n\nThe split is exactly provider-generated vs locally-captured: web exports arrive\nwith titles because the provider makes one; local coding-agent sessions do not,\nbecause nothing generates one. The two origins that dominate the archive\n(15,202 of 18,871 = 80.6%) are the two with essentially no titles.\n\nOnly 1,241 of the UUID-titled rows are subagent children, so this is NOT a\nfanout artifact: roughly 8,900 TOP-LEVEL Claude Code sessions -- the operator's\nown primary work -- are unlabelled.\n\npolylogue-ih67 owns the Codex 3,201 and has already built the resolution\nladder (thread name -\u003e authored history -\u003e first HUMAN_AUTHORED message -\u003e\nnative id). Nothing owns the Claude Code 10,157, which is 3.2x larger. This\nshould reuse ih67's mechanism rather than invent a second one; polylogue-30h\nowns the separate first-prompt-echo case.","acceptance_criteria":"1. A Claude Code session's display title is derived from authored content, never its UUID, using ih67's existing resolution ladder rather than a parallel mechanism. 2. Title provenance is recorded (title_source/title_ref), so a synthesized title is distinguishable from a provider-supplied one. 3. Live re-measure: UUID-titled claude-code-session count falls from 10,157 toward zero, reported as a before/after census like ih67 AC#6. 4. Existing rows acquire titles through ordinary reprocess, not a bespoke backfill script.","notes":"2026-07-31 re-measurement (H6, adversarial dataset investigation, full live scan, 23,280 sessions):\n\n origin total title = native_id pct\n claude-code-session 16,374 14,626 89.3% (was 84.6% / 10,157 of 12,001)\n codex-session 3,203 3,203 100.0%\n chatgpt-export 2,637 0 0.0%\n claude-ai-export 425 95 22.4% (was 0.3% / 1 of 377)\n hermes-session 279 157 56.3%\n aistudio-drive 239 88 36.8%\n antigravity-session 116 0 0.0%\n gemini-cli-session 17 7 41.2%\n grok-export 6 0 0.0%\n\nclaude-code-session got WORSE, not better, despite the intervening b508/#3403 phantom-sidecar fix -- total session count grew 12,001-\u003e16,374 (+4,373) and the untitled fraction grew with it. Of the 14,626 title=native_id claude-code-session rows, 8,631 (59%) are subagent-shaped (native_id LIKE '%:agent-%', i.e. the real parent:agent-* subagent transcripts from C1 -- arguably expected, since these are dispatched-task transcripts without their own human-authored opening prompt) and 5,995 (41%) are top-level sessions with a bare native_id as title. Of those 5,995: 5,191 have message_count=0 (empty, arguably don't need a title) but 551 have message_count\u003e5 (substantive sessions, e.g. 6,059 messages / 536,011 words) with zero human-readable title -- these are the sharpest instances of this bead's defect. claude-ai-export's jump (0.3%-\u003e22.4%) tracks its session count nearly doubling (377-\u003e425); worth checking whether the new claude-ai-export rows are a distinct ingestion batch with different title-resolution coverage.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:52:07Z","created_by":"Sinity","updated_at":"2026-07-31T04:57:12Z","labels":["area:ingest","lane:read-contracts"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-da7l","title":"Landed capability is dark by default: feature flags defer decisions nobody made","description":"Measured 2026-07-29: polylogue/config.py declares 18 boolean feature flags. The live ~/.config/polylogue/polylogue.toml sets only the [embedding] section, so every other flag runs at its default.\n\nFlags that are False by default and gate work that has already landed and merged:\n daemon_parse_stage_split m6tp phase (a), PR #3168\n live_watcher_parse_stage_split same mechanism for the watcher\n daemon_bulk_rebuild_routing m6tp phase (c), PR #3189/#3197, bead gd6v CLOSED\n mcp_write_enabled MCP write role\n mcp_judge_enabled MCP review role\n mcp_maintenance_enabled MCP admin role\n judgment_automation_enabled judgment automation\n embedding_enabled (this one IS set live, to false)\n\nThe compounding case is m6tp: three of its four phases are landed, the fourth\nis inventoried, the runtime precondition is satisfied in production -- and the\nredesign is entirely dark because two flags default False. The daemon therefore\nruns the slowest available configuration (serial parse, trickle conveyor) on a\nfree-threaded interpreter that measured 3.9x-9.6x parallel parse.\n\nTHE PRINCIPLE AT STAKE: a flag on a landed capability is a decision nobody\nmade. It defers the decision to configuration, where the default silently\nbecomes the decision -- and the default is always the previous behaviour, so\nshipping is decoupled from taking effect. A bead can close, CI can be green,\nthe PR can merge, and nothing changes for the operator.\n\nThis directly contradicts the project's own automagic-invariants doctrine:\n'if Polylogue can maintain a condition fully automatically, it generally\nshould ... there is NO break-glass tier. Once the automatic path maintains an\ninvariant, the redundant manual surface is DELETED, not demoted.'\n\nNON-GOAL: removing genuinely necessary configuration (archive root, ports,\ncredentials, embedding model/dimension/cost ceiling -- the last gates real\nmoney). This is about flags whose only function is to keep landed code from\nrunning.","acceptance_criteria":"1. Every boolean feature flag is classified as: rollout-scaffolding for landed work (delete the flag, make the behaviour unconditional), genuine deployment choice (keep, document why config is the right home), or unshipped-work gate (keep until the work lands, with the bead that removes it named). 2. No flag gating already-merged work survives without a named reason. 3. For each flag deleted, the removal is unconditional -- not a default flip that leaves the knob in place. 4. m6tp's two flags are resolved first and their removal is the worked example. 5. A landed-but-dark capability is treated as not shipped: the closing bead's definition of done includes the behaviour being active.","notes":"Filed 2026-07-29 from the convergence audit. The trigger was discovering that the fix for a standing operator complaint ('why is import not within 1h') was built, merged, bead-closed, and switched off -- and that the daemon logs its own correct diagnosis hourly while doing the slow thing anyway.\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: LIVE. Filed 2026-07-29 same day as audit; explicitly describes current unaddressed state (18 flags, all defaulting False/off) with zero remediation notes.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-29T04:51:15Z","created_by":"Sinity","updated_at":"2026-07-31T05:52:12Z","labels":["area:substrate","lane:daemon-surface"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -144,7 +181,7 @@ {"_type":"issue","id":"polylogue-ihc8","title":"fold_duplicate_alias raw-authority strategy never reaches its terminal postcondition (recurring, non-converging)","description":"Live daemon observation (2026-07-27, polylogued.service, sinnix-prime): raw_reconciler.py's FOLD_DUPLICATE_ALIAS actuator (~line 1129) recurringly fails with RuntimeError('duplicate strategy did not reach its typed terminal postcondition') and never resolves across many retries.\n\nEvidence (90-minute journalctl window): plan raw-authority-frontier:058be945e0d8486eeacb4ede09f152d255261af33ba3c3ad38c096d2d00b2b1e failed 7 times; plan raw-authority-frontier:698b72b920313d23e96584441a1982b3b8a2a039711d83337260811fda1e82db failed once. Both logged as 'raw authority strategy failed plan=... actuator=fold_duplicate_alias' warnings (non-fatal, daemon keeps running, degrades gracefully per false_means_pending) but neither plan is making progress -- 'raw authority: 6/8 selected frontier plans remain retryable' confirms these sit in the non-retryable-but-still-selected remainder across cycles.\n\nRoot-cause locus (raw_reconciler.py:1108-1130, read directly): for FOLD_DUPLICATE_ALIAS, the code (1) inspects the duplicate raw identity via _inspect_duplicate_raw_identity, (2) if status=='eligible' applies the repair via _apply_duplicate_raw_identity_repair, (3) re-inspects via the SAME function and requires status=='already_repaired', else raises the observed error. So either: (a) _apply_duplicate_raw_identity_repair is not actually flipping whatever condition _inspect_duplicate_raw_identity checks for these 2 specific raws, or (b) _inspect_duplicate_raw_identity's classification for this raw pair has some property that legitimately can never satisfy 'already_repaired' (e.g. a member already quarantined by an unrelated process, or a duplicate-identity edge case the classifier doesn't model), making this a design gap rather than a transient failure.\n\nNeeds: reproduce read-only against the live archive (inspect_duplicate_raw_identity(conn, root, raw_id, canonical_id) for the two raw_ids/canonical pairs behind these 2 plan hashes -- correlate plan_id to its raw_id/canonical_ids via the frontier item dump or a fresh raw_authority_frontier_items() read-only scan), determine which of (a)/(b) applies, and fix accordingly -- either the repair application has a real bug, or the postcondition check/classifier needs to recognize a legitimate terminal state it currently doesn't. Do NOT apply a live repair without read-only reproduction first per this repo's raw-authority safety discipline.","notes":"ROOT CAUSE CONFIRMED (application-logic bug, not a design gap):\n\nCorrelated both failing plan hashes to live data read-only (mode=ro URI connections\nagainst /realm/db/polylogue, replaying _frontier_rows/_classify_frontier in a\nscratch script -- never wrote to the live archive).\n\nPlan 058be945e0d8486eeacb4ede09f152d255261af33ba3c3ad38c096d2d00b2b1e resolved to:\n- stale raw_id 08f40243e99738a804418d2259c504b8d334ebe45c811ac3736d6ecd8a1cce9e\n- canonical raw_id e869e6bf26b9df0e46c298ecd2f8fc63e489cd2c9e174f33f168ef0f1cd8d6f0\n- classified for session/logical_source_key claude-code-session:896c6b64-8e22-420e-bd57-6b27e510e9f5\n\nQuerying raw_revision_heads WHERE accepted_raw_id = '08f40243e9...' live returned\nFOUR rows -- one per logical_source_key/session (560a3328-..., 0f5e001c-...,\n850e32cf-..., 896c6b64-...). The exact same physical raw acquisition is\nlegitimately the accepted head of all four sessions simultaneously: forked/\nsubagent/resumed Claude Code sessions physically replay the identical parent\nJSONL evidence (source_path referenced a shared *.pre-enrich/\u003cuuid\u003e.jsonl), so\neach session's own materialization independently accepted that raw as its head.\n\n_inspect_duplicate_raw_identity (polylogue/storage/repair.py) looked this row up\nby `WHERE accepted_raw_id = ?` alone (no session/key scoping), so `fetchone()`\npicked an arbitrary one of the four. Direct proof from the live census: the\nfrontier item classified for session 896c6b64 carried a strategy_witness whose\nsession_id/logical_source_key were 560a3328's, not its own -- item.index_preconditions.logical_source_key\nwas 896c6b64-... while item.strategy_witness.logical_source_key was 560a3328-....\n\nAt apply time this meant _apply_duplicate_raw_identity_repair repointed the\nWRONG session's head/session-pointer inside the transaction. The re-inspect\ncall (same unscoped lookup) then found a different remaining row still\npointing at the stale raw, saw the canonical now claimed (canonical_head is not\nNone), and returned status=\"ineligible\" instead of \"already_repaired\" --\ntripping the typed terminal-postcondition check in raw_reconciler.py and\nrolling back the WHOLE transaction. Since nothing ever committed, the\nunderlying DB state never changed between retries, so the exact same plan hash\nand error recurred identically every cycle -- matching the observed 90-minute,\n7x-identical-failure log pattern exactly.\n\nFIX (PR #3326, branch feature/fix/raw-authority-fold-duplicate-alias-postcondition):\n- _inspect_duplicate_raw_identity gains a required logical_source_key parameter;\n the stale raw's accepted-head lookup and the already_repaired session/\n superseded-receipt checks are now scoped to it. The canonical raw's head/\n session checks stay unscoped (global \"not claimed by anyone yet\" fact, correct\n as-is).\n- All 3 call sites in raw_reconciler.py (_classify_frontier + both _apply_strategy\n inspect calls) now pass the frontier row's/item's own logical_source_key.\n- Added _seed_duplicate_raw_fanout fixture + 2 regression tests reproducing the\n exact fan-out shape (one stale raw shared by two sessions, one canonical\n twin). Both tests fail against the pre-fix code with the EXACT SAME \"did not\n reach its typed terminal postcondition\" RuntimeError observed live (verified\n by temporarily reverting the fix and re-running).\n\nVerification: devtools test on the direct + 6 adjacent raw-authority test files\n(150 total passed), mypy --strict clean, ruff clean, render all --check clean,\ndevtools verify --quick clean on push.\n\nDid NOT: touch the live archive in write mode (all reads mode=ro); attempt to\nsolve the deeper N:1 fan-out limitation (only ONE of the N sessions sharing a\nstale raw can ever be folded onto the single available canonical twin -- the\nother N-1 will gracefully fall through to a different actuator/state on the\nnext scan once the canonical is claimed; this is a separate, likely-legitimate\nfollow-up question, not part of this crash-loop fix). Did NOT close this bead --\nleaving for operator review/merge decision on PR #3326.\nFix merged: PR #3326 (fold_duplicate_alias non-convergence root-caused to unscoped accepted-head lookup across a legitimate multi-session raw fan-out; scoped by logical_source_key in repair.py/raw_reconciler.py, regression tests added). Bead left open per investigation-agent's own judgment pending live daemon confirmation of convergence on next deploy.\nSESSION 2 CONFIRMATION (re-dispatch of this bead's task): re-verified\neverything below from scratch this session, no duplicate work done.\n\n- PR #3326 (commit 41baf9935) is MERGED into master -- confirmed via\n `gh pr view 3326 --json state,mergedAt,mergeCommit` (state=MERGED,\n merged 2026-07-27T14:46:45Z) and `git log --oneline` showing the\n commit present on this checkout's master history.\n- Root cause is (a): an application-logic bug (unscoped\n accepted-head lookup in _inspect_duplicate_raw_identity), NOT a\n design gap in the classifier -- as already documented above. No new\n investigation needed; confirmed the prior session's evidence is\n accurate by re-reading the current repair.py/raw_reconciler.py source\n directly.\n- Regression tests present and GREEN on current master:\n `devtools test tests/unit/storage/test_duplicate_raw_identity_repair.py`\n -\u003e 9 passed, including\n test_duplicate_alias_witness_is_scoped_to_its_own_session_not_a_fanout_sibling\n and test_duplicate_alias_fold_reaches_terminal_postcondition_under_fanout.\n- Live archive status (read-only query against\n /realm/db/polylogue/index.db, mode=ro): the same 4 raw_revision_heads\n rows for stale raw_id 08f40243e9...ce9e0 (sessions 560a3328-,\n 0f5e001c-, 850e32cf-, 896c6b64-) are STILL present, and the canonical\n raw e869e6bf...8d6f0 STILL has zero heads (still dangling,\n unclaimed) -- i.e. the live archive has NOT yet converged.\n- Reason: the live polylogued.service runs from a pinned Nix store\n package (python3.14t-polylogue-0.3.0, confirmed via `ps aux` showing\n /nix/store/.../bin/.polylogued-wrapped run), not a live git checkout.\n Merging to polylogue's master does not update the running daemon --\n that requires a separate sinnix-side action (bump the polylogue flake\n input pin + `nix develop --command switch` in the sinnix repo) which\n will cause the daemon to ACTUALLY EXECUTE the fold repair against the\n live archive on its next raw-authority census cycle. Per this repo's\n own raw-authority safety discipline and this task's explicit\n instruction, did NOT trigger that deploy or any other live-mutating\n action this session -- it needs an explicit operator go/no-go, and it\n lives outside the polylogue repo (sinnix).\n- Opened polylogue-dmvo tracking the previously-undocumented N:1\n fan-out follow-up: only ONE of the four sessions sharing the stale\n raw can ever fold onto the single available canonical twin; the other\n three should classify to \"ineligible\" (canonical now claimed) and\n drop out of the retryable frontier on the next census, per current\n code reading of _classify_frontier's {\"eligible\",\"already_repaired\"}\n selection filter -- but this has never been observed live post-fix\n and needs confirmation once the sinnix-side deploy actually happens.\n\nNet: no code change needed this session (already shipped/merged/tested\nin #3326). Leaving open pending (1) the separate sinnix deploy decision\nand (2) live confirmation that convergence + the N:1 fallout both\nbehave as expected once deployed.\n2026-07-28 deploy confirmation: operator authorized live deploy + repair this\nsession. Sinnix flake input bumped to polylogue@798c31a41, `nix develop\n--command switch` applied successfully; daemon confirmed running new code\n(python3.14t-polylogue-0.3.0, PR #3326's fix included).\n\nDeploy surfaced a SEPARATE, pre-existing, unrelated bug: polylogued.service's\nshared resource-class MemoryMax=2G was too tight for this archive's\npost-restart catch-up backlog (36GB/5-tier, ~4.9M blocks) -- MemoryCurrent\npinned exactly at the cap, memory.events showed 306K+ max-limit hits within\n35 minutes, every ingest/status thread stalling in folio_wait_bit_common\n(page reclaim thrashing, confirmed via /proc/\u003cpid\u003e/task/\u003ctid\u003e/stack -- a\nkernel-level wait, not a Python deadlock). Ruled out today's merged PRs as\nthe cause first (direct read-only timing of aex0's new query +\nplan_revision_replay against the archive's largest real revision chain: both\nsub-millisecond). Fixed via sinnix commit be911e3 (MemoryHigh/MemoryMax -\u003e\n6G/8G for polylogued.service specifically, matching the order of magnitude\nalready used for polylogue-sqlite-backup); daemon recovered immediately\nafter restart under the new limit (MemoryCurrent dropped from pinned 2G to\n~900MB, catch-up chunks completing in seconds).\n\nPost-fix, the daemon drained its full catch-up backlog cleanly (idle,\nno stale/stuck ingest attempts) within ~25 minutes. However, as of this\nnote, the 4 sessions (560a3328-, 0f5e001c-, 850e32cf-, 896c6b64-) still\npoint at the stale raw 08f40243e9... in raw_revision_heads -- the\nfold_duplicate_alias convergence has NOT yet been observed to fire for this\nspecific plan. This is consistent with _converge_raw_authority_frontier's\nbounded per-pass limit (min(limit, 8) plans per raw-materialization cycle)\nworking through a large 20K+-file backlog scan first, not a sign the fix\nfailed. No manual repair-execute surface exists in this CLI (by the\nautomagic-invariants doctrine -- deleted, not break-glassed), so this\nsession did not force it; convergence remains dependent on the daemon's own\nperiodic reconciliation. Re-check `raw_revision_heads` for these raw_ids\n(read-only) in a future session to confirm.\n\n2026-07-28 LIVE CONFIRMATION COMPLETE, closing. This bead was deliberately\nleft open pending (1) the sinnix deploy and (2) live confirmation that\nfold_duplicate_alias's fix (PR #3326) actually converges in production.\nBoth are now definitively answered, via the subsequent ewfp/zaiz\ninvestigation chain this same session:\n\n(1) Deploy: confirmed earlier this session (sinnix flake bumped to\n polylogue@798c31a41, `nix develop --command switch` applied,\n daemon running the fix).\n\n(2) Live convergence: CONFIRMED. Session claude-code:896c6b64-8e22-420e-\n bd57-6b27e510e9f5 -- one of the 4-session fan-out sharing stale raw\n 08f40243e99738a804418d2259c504b8d334ebe45c811ac3736d6ecd8a1cce9e --\n successfully folded onto its canonical raw\n e869e6bf26b9df0e46c298ecd2f8fc63e489cd2c9e174f33f168ef0f1cd8d6f0 in\n production, verified directly via read-only SQL against\n raw_revision_heads multiple times across this session's ewfp/zaiz\n investigation. The fold_duplicate_alias actuator this bead tracks\n DOES reach its terminal postcondition correctly for a genuinely\n eligible session -- the original bug this bead reported (never\n converging) is fixed and proven working live, not just in tests.\n\nThe OTHER 3 sessions in this same fan-out (560a3328, 0f5e001c, 850e32cf)\nremain unconverged, but for reasons entirely SEPARATE from this bead's own\nscope, root-caused and closed out under polylogue-ewfp (postflight\ncrashes) and polylogue-zaiz (fan-out scoping bugs in the quarantine path,\n+ a genuine architectural boundary: they were accepted under semantic, not\nbyte, frontier authority, which no fold_duplicate_alias fix could ever\naddress -- see polylogue-sg80 for that separate follow-up). None of that\nremaining non-convergence reflects on THIS bead's own claim (does\nfold_duplicate_alias converge) -- it does, confirmed live.\n\nClosing as resolved and confirmed.\n","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-27T14:23:12Z","created_by":"Sinity","updated_at":"2026-07-28T12:16:50Z","closed_at":"2026-07-28T12:16:50Z","close_reason":"Fix (PR #3326) confirmed converging live: session 896c6b64 successfully folded onto its canonical raw in production, verified via direct read-only SQL across this session's ewfp/zaiz investigation. The bead's own stated closing criteria (live convergence confirmation) are met. Remaining fan-out non-convergence for 3 other sessions is out of this bead's scope -- tracked separately under ewfp (closed) and zaiz/sg80 (semantic-frontier architectural boundary, not a fold_duplicate_alias bug).","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-de2a","title":"Long-held writer lock starves periodic maintenance (FTS merge, WAL checkpoint) under backlog","description":"Discovered live 2026-07-27 while investigating why the daemon's raw-materialization stale-plan-blocker fix (polylogue-d7im, PR #3287) hadn't taken effect after deploy: the live watcher's catch-up.chunk actor held the sole-writer lock for 860 seconds (14+ minutes) parsing/writing a single modest (~7MB, 547-block session) append. During that entire hold, every other periodic daemon actor -- FTS merge, WAL checkpoint, raw-materialization convergence (and thus my new auto-resolve fix) -- was queued and blocked, since DaemonWriteCoordinator serializes every actor through one global asyncio.Lock with no priority/preemption.\n\nRoot cause chain, verified with live evidence (not speculation):\n1. messages_fts runs with automerge=0 (fts_automerge.py, #1851) -- segment consolidation depends entirely on periodic _periodic_fts_merge (was 300s interval, bounded 500-work-unit/2-4MiB per call by design).\n2. messages_fts_data (the FTS5 shadow table) had grown to 705,281 rows live -- consistent with merge being starved for an extended period, letting segment count balloon well past steady state.\n3. FTS5 insert cost degrades as unmerged segment count grows (well-documented FTS5 characteristic), so per-block insert triggers during ordinary appends get progressively slower.\n4. Slower per-block inserts -\u003e longer writer holds during ingest -\u003e less opportunity for the merge task to ever get a turn -\u003e more bloat. A genuine self-reinforcing spiral, not a one-off slow pass.\n\nPartial mitigation shipped in the same investigation (PR pending): reduced _periodic_fts_merge's interval 300s -\u003e 60s. This does NOT fix worst-case starvation (a single 14-minute hold still blocks every queued actor regardless of how often they ask) -- it only helps the task catch up faster once contention eases, and increases the chance it gets a turn between shorter holds.\n\nReal fix needs one of:\n- Writer-lock fairness/priority so maintenance actors (merge, checkpoint) can jump ahead of bulk ingest actors, or\n- Bound how long a single ingest/parse pass can hold the writer without yielding (chunk large appends internally so the lock is released and reacquired periodically), or\n- A bloat-triggered emergency larger merge budget (adaptive to segment count) rather than a fixed small per-call bound.\n\nAlso worth checking: whether the underlying 536s-of-850s \"append.index.blocks\" stage cost for a 547-block session is *itself* explained entirely by FTS insert-against-bloated-segments cost, or whether there's a second, independent per-block cost issue -- not fully isolated in this investigation.\n\nRef: PR #3287 (auto-resolve stale-plan blockers) deploy investigation, 2026-07-27.","acceptance_criteria":"1. Writer-hold and writer-wait are measured per actor and exported, so starvation is a number rather than a journal-reading exercise. 2. No maintenance actor waits longer than a declared bound while another holds the writer; the bound is stated and enforced, not aspirational. 3. Long-running convergence work yields the writer at declared checkpoints instead of holding it for the whole pass. 4. Live re-measure shows the queue depth and max wait below the declared bounds under an ingest backlog comparable to the 2026-07-28 baseline.","notes":"\n2026-07-27 deploy update: the partial mitigation (periodic FTS merge interval 300s-\u003e60s) shipped as PR #3288, merged and deployed live in the same sinnix switch as polylogue-d7im's fix. This does NOT close the bead - it only helps the merge task catch up faster between writer-lock windows, it does not fix worst-case single-actor lock-hold starvation (a long ingest/parse pass can still block every queued maintenance actor with no preemption). Real fix (writer-lock fairness/priority, or bounding a single ingest pass's lock hold via internal chunking) remains undone. Observed hold_s during this session's redeployed catch-up ranged ~0.02s-168s per chunk (down from an earlier observed 860s pathological case), but this variance looks driven by per-chunk file size/complexity, not confirmed to be caused by the 60s fix yet - avoid over-crediting it without isolated measurement.\n\n2026-07-27: real fix (writer-lock priority/fairness, not just the interval mitigation) merged as PR #3289 and deployed live (sinnix flake bump 4241316e0, nix develop --command switch). DaemonWriteCoordinator now admits queued maintenance.*/startup.*/daemon.lifecycle.* actors ahead of any queued watcher.* actor. This bounds worst-case maintenance starvation to \"current hold + at most one more already-queued ingest hold\" instead of unbounded backlog length - but does NOT fix the harder remaining problem (an already-admitted single ingest pass can still hold the gate for minutes with no preemption). That internal-chunking/preemption fix remains the real remaining scope; not attempted this session (too large/risky to rush). Post-deploy catch-up backlog is processing noticeably faster (chunk 33/441 within seconds each, vs earlier 860s pathological holds) though this is confounded with normal backlog-size variance - not yet isolated as solely attributable to this fix.\n2026-07-27: root-caused and fixed the dominant O(n^2) cost driver behind the\n860s/9297s pathological writer-gate holds via PR #3358 (not yet merged):\napply_raw_revision_replay's write loop was re-running\n_index_parsed_for_retained_raw (INSERT OR REPLACE into messages/blocks,\nre-firing messages_fts insert triggers) for EVERY historical raw_id in a\nsession's append chain on every single new live append, not just the new\ntail -- confirmed via direct SQL-level trace, not speculation. A\nlong-lived session accumulating N small live appends pays O(N) redundant\nhistorical writes on its Nth append and O(N^2) cumulatively, which is\nexactly the self-reinforcing FTS-segment-bloat spiral this bead's live\nevidence already pointed to (messages_fts_data at 705K rows).\n\nFix: apply_raw_revision_replay gained skip_already_applied=False (default,\nbyte-for-byte unchanged for existing callers); the live watcher's\nappend_ingest.py hot path opts in (skip_already_applied=True), skipping\nthe index WRITE (not the parse -- aggregate hash still needs every\nposition's parsed content merged) for every accepted_raw_ids position at\nor before the previously-recorded raw_revision_heads.accepted_raw_id.\nBackfill/restore/membership-classification callers are unchanged (keep\nfull self-healing re-apply).\n\nNOT closing yet: (1) PR #3358 needs merge; (2) this removes the dominant\ncost driver that produced the observed pathological holds, but does NOT\nadd a genuine preemption/yield mechanism for an already-admitted\nsingle-actor writer hold in general -- a mid-hold SQLite transaction can't\nsafely release the async gate without also releasing the real DB-level\nwrite lock. If a hold this long ever recurs from a genuinely different\nslow stage (not chain-replay-driven), that harder preemption design is\nstill needed and not attempted here (matches this bead's own earlier note\nthat it was judged \"too large/risky to rush\" this session).\n\nLIVE BASELINE 2026-07-28 21:39 (journalctl --user -u polylogued), recorded so the AC has a before-number:\n\n maintenance.raw_materialization hold_s=210.3 wait_s=42.2 queued=6\n maintenance.session_insights hold_s=1.9 wait_s=191.4 queued=6\n maintenance.convergence_debt hold_s=0.03 wait_s=193.3 queued=5\n maintenance.fts_merge hold_s=3.0 wait_s=152.3 queued=4\n maintenance.embedding_backlog hold_s=0.001 wait_s=155.3 queued=3\n\nShape is unambiguous: one actor holds the writer for ~3.5 minutes while four cheap actors (sub-3s of actual work between them) wait 2.5-3.2 minutes each behind it. This is a fairness/yielding problem, not a throughput problem.\nCONVERGENCE AUDIT 2026-07-29: this is arithmetic, not a tuning problem. The raw\nmaterialization pass holds the sole writer for ~188s (four consecutive passes\nmeasured: 188.7, 188.9, 187.1, 189.8). Three actors want a 60s cadence --\n_SESSION_INSIGHT_CONVERGENCE_INTERVAL_SECONDS, _FTS_MERGE_INTERVAL_SECONDS and\n_CONVERGENCE_DEBT_RETRY_INTERVAL_SECONDS are all 60. Starvation is guaranteed by\nconstruction. Observed wait_s reached 616.3 with queued=10.\n\nStructural note for whoever takes this: raw materialization is NOT a\nConvergenceStage. It is a hand-rolled loop in daemon/cli.py (2,836 lines) with\nits own burst pause, its own inferred mode (census_mode = censused\u003e0 and\nrepaired==0 and executed==0, which silently switches the batch limit between 64\nand 16), three exit conditions including a browser-spool check, and a separate\nwhale escalation tier. It therefore gets none of the framework's check/execute,\ncheck_many/execute_many, or debt handling. Moving it into the stage framework is\nthe structural fix; bounding hold time is the immediate one.\nVERDICT: LIVE — multiple real mitigations shipped and deployed (PR #3288 interval tuning, PR #3289 maintenance-actor priority admission, PR #3358 removing an O(n^2) redundant-reapply cost driver), each reducing but not eliminating the underlying problem. Bead's own 2026-07-29 CONVERGENCE AUDIT note shows raw-materialization still holds the sole writer for ~188s per pass with observed wait_s up to 616.3 and queued=10 — AC2 (bounded wait) and AC3 (yield checkpoints) are explicitly still open; author states the structural fix (moving raw materialization into the ConvergenceStage framework) is not attempted. Evidence: bead's own 2026-07-29 note; polylogue/daemon/write_coordinator.py has hold/wait measurement (AC1 satisfied) but no yield-checkpoint or bound-enforcement code found for the raw-materialization loop in daemon/cli.py.","status":"open","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-26T23:26:16Z","created_by":"Sinity","updated_at":"2026-07-31T05:51:56Z","dependencies":[{"issue_id":"polylogue-de2a","depends_on_id":"polylogue-m6tp","type":"parent-child","created_at":"2026-07-29T06:51:26Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-uhgm","title":"Enforce rebuild pass deadlines within replay work","description":"Live recovery evidence: operation 3f8fa7b0 configured pass_deadline_ms=300000, yet 100-row passes ran for roughly 8–9 minutes because rebuild_index_from_source checks elapsed time only after replay_source and planner-statistics refresh finish. A page can also expand into a much larger authority cohort. The advertised bounded-pass contract is therefore not enforced at the work boundary.","design":"Thread a monotonic deadline/cancellation budget through the replay/census and any post-page maintenance work. Check before beginning each independently recoverable cohort and before expensive post-processing; checkpoint only work whose source and index receipts are atomically committed. Preserve source-order cursor semantics: resumption must replay no skipped or duplicated raw/cohort. Report the concrete defer reason and elapsed budget in the receipt. Do not solve by weakening correctness checks or silently changing durable transaction budgets.","acceptance_criteria":"A transaction with a short pass deadline stops before starting work that would exceed its remaining budget, commits a valid cursor, and reports deadline deferral. Restarting resumes exactly at the next source-order raw/cohort with no duplicates or omissions. A deliberately slow/expanded cohort proves the deadline is checked inside production replay work rather than only after the outer call returns. Final terminal readiness checks remain exact and either have their own bounded receipt or are explicitly separately scheduled.","notes":"\n2026-07-27: confirmed still accurate and unfixed. Read rebuild_index_from_source (polylogue/maintenance/rebuild_index.py:305-460): the deadline_expired check at line ~447 runs only after `await replay_source(...)` (the whole page's replay) and _refresh_generation_planner_statistics complete for that page - exactly the gap the bead describes. A correct fix needs either (a) proactive page-sizing against remaining deadline before selecting the next page (needs a throughput estimate), or (b) threading interruption into replay_source's own per-raw loop so a page can stop mid-flight without corrupting the owned-inactive-generation transaction state. Both are real, scoped feature work against a critical rebuild-transaction state machine - not attempted this session; too large/risky to implement and verify properly at the effort level available, and the bug's actual damage (a bounded pass overrunning its SLA by minutes) is not correctness-threatening, just not as bounded as advertised.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-26T08:15:07Z","created_by":"Sinity","updated_at":"2026-07-27T01:30:30Z","labels":["area:maintenance","area:perf"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-m3p9","title":"sessions.created_at_ms NULL: 1,117 sessions remain after de-inflation (was 65,946 pre-fix)","description":"Found 2026-07-22 while fact-checking README examples: SELECT count(*), sum(sort_key_ms IS NULL) FROM sessions on the promoted v43 archive = 83,198 total, 65,946 NULL (79%). sort_key_ms = COALESCE(updated_at_ms, created_at_ms), both plain columns the writer only sets when the provider payload carries session-level timestamps. Result: find since:… matched exactly 17,252 (= the non-NULL population) — date filters, --by year/month histograms, and recency ordering silently exclude four-fifths of the archive, including most claude-code subagent sessions and hermes/observer material, even though their MESSAGES carry timestamps.","design":"Derive session timestamps from message evidence at write/materialize time: created_at_ms = min(message timestamp), updated_at_ms = max(message timestamp) when the provider gives none at session level (messages table already stores per-message timestamps for these origins). Classify: additive-derived (index tier) — either benign in-place backfill on same-version open (benign-DDL/backfill registry) or fold into next semantic bump; the insight/profile layer may already compute first/last message times (session_profiles) — prefer deriving the sessions columns from the same source rather than a second scan. Verify since:/analyze --by coverage jumps from 17,252 to ~all sessions with any timestamped message; regression test: session whose payload lacks session-level timestamps but has dated messages gets non-NULL sort_key_ms.","acceptance_criteria":"since:/until:/recency and --by year/month cover every session that has at least one timestamped message; NULL sort_key remains only for genuinely undatable sessions (count them in the receipt); regression test for the derive-from-messages path; live archive backfilled with receipt.","notes":"PR #3285 merged to master (fix-write-path derivation + session_timestamp_backfill maintenance target). Live-archive backfill run (polylogue ops maintenance run --target session_timestamp_backfill) + receipt still pending -- daemon must be stopped for offline maintenance or this needs a live-safe trigger; deferred, not run this session.\nRE-MEASURED 2026-07-28 against the live archive (index v43). The bead's headline was 12x stale and nobody re-measured it after two unrelated changes landed:\n\n SELECT created_at_ms IS NULL, count(*) FROM sessions GROUP BY 1;\n -\u003e non-NULL 17,754 | NULL 1,117 (5.9% of 18,871)\n\n by origin: claude-code-session 882 | antigravity-session 116 (100% of that origin)\n aistudio-drive 80 | chatgpt-export 17 | hermes-session 16 | grok-export 6\n codex-session 0 | claude-ai-export 0 | gemini-cli-session 0\n\nThe original '79% / 65,946 of 83,198' was measured before the hook-session de-inflation (83,286 -\u003e 18,391 sessions); the overwhelming majority of those NULLs were hook-event pseudo-sessions that no longer exist as sessions at all. PR #3285's write-path derivation fix accounts for the rest of the drop.\n\nResidual scope is therefore much smaller and differently shaped than the title claimed: 1,117 rows, of which antigravity-session is a total miss (116/116) worth its own look, and claude-code-session 882 is the only bulk population. The session_timestamp_backfill maintenance target is still unrun on the live archive; it now has ~1,117 rows to fix, not 65,946.\n\nMethod note for future readers: every number in this bead should be re-derived before acting on it. The de-inflation moved the denominator by 4.5x.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own 2026-07-28 re-measure said 1,117 NULL rows still need backfill. Live re-check today (sqlite3 index.db) shows 5,382 NULL created_at_ms rows now -- grew, not shrank. Write-path fix (PR #3285, merged) covers new writes only; backfill of existing rows never ran. Real unaddressed work, worse than last snapshot.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-21T22:59:34Z","created_by":"Sinity","updated_at":"2026-07-31T05:48:01Z","started_at":"2026-07-21T23:57:57Z","labels":["area:query","area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-m3p9","title":"sessions.created_at_ms NULL: 1,117 sessions remain after de-inflation (was 65,946 pre-fix)","description":"Found 2026-07-22 while fact-checking README examples: SELECT count(*), sum(sort_key_ms IS NULL) FROM sessions on the promoted v43 archive = 83,198 total, 65,946 NULL (79%). sort_key_ms = COALESCE(updated_at_ms, created_at_ms), both plain columns the writer only sets when the provider payload carries session-level timestamps. Result: find since:… matched exactly 17,252 (= the non-NULL population) — date filters, --by year/month histograms, and recency ordering silently exclude four-fifths of the archive, including most claude-code subagent sessions and hermes/observer material, even though their MESSAGES carry timestamps.","design":"Derive session timestamps from message evidence at write/materialize time: created_at_ms = min(message timestamp), updated_at_ms = max(message timestamp) when the provider gives none at session level (messages table already stores per-message timestamps for these origins). Classify: additive-derived (index tier) — either benign in-place backfill on same-version open (benign-DDL/backfill registry) or fold into next semantic bump; the insight/profile layer may already compute first/last message times (session_profiles) — prefer deriving the sessions columns from the same source rather than a second scan. Verify since:/analyze --by coverage jumps from 17,252 to ~all sessions with any timestamped message; regression test: session whose payload lacks session-level timestamps but has dated messages gets non-NULL sort_key_ms.","acceptance_criteria":"since:/until:/recency and --by year/month cover every session that has at least one timestamped message; NULL sort_key remains only for genuinely undatable sessions (count them in the receipt); regression test for the derive-from-messages path; live archive backfilled with receipt.","notes":"PR #3285 merged to master (fix-write-path derivation + session_timestamp_backfill maintenance target). Live-archive backfill run (polylogue ops maintenance run --target session_timestamp_backfill) + receipt still pending -- daemon must be stopped for offline maintenance or this needs a live-safe trigger; deferred, not run this session.\nRE-MEASURED 2026-07-28 against the live archive (index v43). The bead's headline was 12x stale and nobody re-measured it after two unrelated changes landed:\n\n SELECT created_at_ms IS NULL, count(*) FROM sessions GROUP BY 1;\n -\u003e non-NULL 17,754 | NULL 1,117 (5.9% of 18,871)\n\n by origin: claude-code-session 882 | antigravity-session 116 (100% of that origin)\n aistudio-drive 80 | chatgpt-export 17 | hermes-session 16 | grok-export 6\n codex-session 0 | claude-ai-export 0 | gemini-cli-session 0\n\nThe original '79% / 65,946 of 83,198' was measured before the hook-session de-inflation (83,286 -\u003e 18,391 sessions); the overwhelming majority of those NULLs were hook-event pseudo-sessions that no longer exist as sessions at all. PR #3285's write-path derivation fix accounts for the rest of the drop.\n\nResidual scope is therefore much smaller and differently shaped than the title claimed: 1,117 rows, of which antigravity-session is a total miss (116/116) worth its own look, and claude-code-session 882 is the only bulk population. The session_timestamp_backfill maintenance target is still unrun on the live archive; it now has ~1,117 rows to fix, not 65,946.\n\nMethod note for future readers: every number in this bead should be re-derived before acting on it. The de-inflation moved the denominator by 4.5x.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own 2026-07-28 re-measure said 1,117 NULL rows still need backfill. Live re-check today (sqlite3 index.db) shows 5,382 NULL created_at_ms rows now -- grew, not shrank. Write-path fix (PR #3285, merged) covers new writes only; backfill of existing rows never ran. Real unaddressed work, worse than last snapshot.\n2026-07-31 group3 sweep (agent-af085793b115e79d5): re-verified live, then traced the active-producer question to its root.\n\nLive measurement (read-only sqlite3 against index.db): sessions.created_at_ms NULL = 5,382 total (matches bead's 2026-07-30 note exactly). By origin: claude-code-session 5,263 | aistudio-drive 80 | chatgpt-export 17 | hermes-session 16 | grok-export 6.\n\nDrilled into the claude-code-session bulk (98.7% of the NULL population, 5,192/5,263): every one of a 30-row sample has ZERO messages. This is the exact shape PR #3428 (commit ab8a92c1a, \"fix(sources): require positive conversation evidence before session classification\", merged same day just before this investigation) fixed: non-conversational records (conversation_relationships.jsonl graph-edge indexes, agent-*.meta.json sidecars, workflow snapshots) were misclassified as claude-code-session with zero real messages, so write.py's own derive-from-messages fallback (_derive_session_timestamps_from_messages, correct and already landed via PR #3285) has no message evidence to derive from and correctly returns NULL rather than fabricating a timestamp.\n\nFor the remaining non-empty-message NULL rows (71/5,263, message counts 1-63), sampled all of them directly: every message in every one of those sessions also has occurred_at_ms IS NULL. So the storage-tier derivation is NOT the bug -- it is honoring its own documented contract (\"a genuinely undatable session stays NULL, it is not backdated to the ingest wall clock\"). The active producer is entirely upstream in sources/ classification, and PR #3428 already fixed the dominant case for new writes going forward.\n\nPR #3428's own body names one residual gap it did NOT fix: sources/live/append_ingest.py's _ingest_append_plans_archive calls dispatch.parse_payload directly with no classify_artifact consultation -- \"very likely safe... but not empirically proven,\" filed as polylogue-xwkh.\n\nConclusion for this bead's assigned scope (storage/daemon, sources/ off-limits per this session's task boundary): no additional code fix is available or needed here. The active producer was found and already stopped by #3428 (merged 2026-07-31, same day). Existing 5,382 NULL rows are old damage (or damage written in the narrow gap before #3428 landed) -- backfill is explicitly a separate live-archive-repair lane's job, not this bead's. The one still-open code gap (append_ingest.py) is sources/-scoped and already tracked as polylogue-xwkh; recommend closing this bead as superseded by #3428 + polylogue-xwkh once xwkh is resolved, or re-scoping it explicitly to depend on xwkh.","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-21T22:59:34Z","created_by":"Sinity","updated_at":"2026-07-31T09:06:57Z","started_at":"2026-07-21T23:57:57Z","labels":["area:query","area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-t93b","title":"Daemon must converge whale raw components: census permanently refuses \u003e64MiB, witness 6.33GB codex source unrecoverable automatically","description":"Operator ruling 2026-07-21: unacceptable that components exceeding _RAW_MATERIALIZATION_DAEMON_BLOB_LIMIT_BYTES (64MiB, daemon/cli.py:89) are resource-blocked FOREVER by the daemon census — the live witness codex:019f49d8 (788 raws, 6.33GB, 20495 messages at peak) plus 3 claude-code sources have zero index presence on the promoted v43 archive solely because every daemon pass logs \"resource-blocked ... exceed replay limit 67108864\" and moves on. Automagic-invariants doctrine: if the daemon owns raw-\u003eindex convergence it must converge whales too; a permanent manual/offline requirement is a policy bug. The refusal exists to bound writer-hold transaction length and parse memory — both concerns now have productized answers: streaming parsers for the dominant origins (codex parse_codex_stream, claude-code streaming JSONL; _raw_materialization_stream_safe at storage/repair.py:3972) and bounded commit batches (raw_authority_commit_batch_size config, PR #3248).","design":"Escalation tier, not a blanket limit raise: (1) keep the 64MiB fast-path limit for ordinary census passes; (2) when a component is resource-blocked AND the backlog is otherwise quiescent, schedule a dedicated whale pass for that single component: parse via the streaming path (require every member stream-safe, else remain typed-blocked with a distinct reason), bounded parse memory via the existing RawParsePrefetchCache inflight budget, replay with commit-batched transactions (raw_authority_commit_batch_size) so the writer hold stays bounded; (3) the resource-blocked durable fingerprint machinery (revision_backfill.py _resource_blocked_parser_fingerprint) already persists typed state — the whale pass consumes it; (4) emit daemon events for whale-pass start/receipt. Key anchors: daemon/cli.py:89 + _periodic_raw_materialization_convergence (:770) + _drain_raw_materialization_once (:958); storage/repair.py repair_raw_materialization (:5702), resource-blocked catch sites (:5833, :6245); revision_backfill.py:491 raise site. Verify against a synthetic multi-raw whale fixture exceeding the limit; the witness component on the live archive is the acceptance witness.","acceptance_criteria":"A component whose total raw bytes exceed the daemon limit but whose members are stream-safe converges to a resolved head through the DAEMON (no offline pass), with writer-hold time bounded (commit batches) and memory bounded (streaming parse + inflight budget); non-stream-safe oversized components get a distinct typed blocked reason; regression test with synthetic whale fixture; live witness codex:019f49d8 resolves after deploy; daemon event receipts recorded.","notes":"2026-07-22: implementation merged as PR #3256 (quiescence-gated single-component escalation pass, 8GiB default envelope via raw_authority_whale_payload_bytes, stream-safe-only, commit-batched, daemon events, default-on with daemon_whale_raw_materialization off-switch; coordinator review on the PR). Deployed to sinnix via flake bump 354be99 + switch. REMAINING for close: live witness codex:019f49d8 resolves to a head via the daemon whale pass — blocked until the operator resolves the durable stale-plan blocker (raw-authority-blocker:5406c7c3…, script staged) since ALL materialization passes fail-closed behind it.\n2026-07-22 recovery correction: PR #3267 supplies the dedicated census-reset mechanism now under review. It requires a verified source-tier backup manifest and an offline daemon before it clears only derived census bookkeeping; accepted raw authority remains intact. It also prunes only index revision seeds whose source raw no longer exists, through the active index pointer. Once merged and deployed, use its dry-run and verified backup receipt before applying, then confirm the fresh census removes the stale-plan blocker before retrying whale convergence.\n\n2026-07-27: confirmed the live archive still has an active stale_plan raw-authority-blocker (raw-authority-blocker:2a4fb67b97a896111abc4681d3cfc52d4e40f85e38b710d97f67a60143b69bfe - different id than this bead's previously-cited 5406c7c3..., which is gone/superseded by a later census, as expected) that fail-closes ALL materialization passes archive-wide, same failure mode described in this bead's notes for the codex:019f49d8 whale witness. polylogue-d7im's auto_resolve_stale_plan_blockers fix (PR #3287, merged+deployed) should clear this class of blocker automatically once the daemon's current watcher catch-up backlog finishes and _periodic_raw_materialization_convergence runs (gated behind catch_up_complete_gate). Re-check whale convergence status (codex:019f49d8 head materialization) after that clears - do not re-diagnose from scratch, this is very likely the same root cause already tracked in d7im.\n2026-07-27T06:11 update: whale-pass mechanism verified sound (directly invoked raw_authority.whale_pass_candidate() against the live archive read-only - correctly returns cc83e374b3... as an eligible candidate, confirming the earlier stream-safety exclusion bug for expanded members is indeed already fixed in master). NOT a bug that it hasn't run yet: the daemon log shows the ordinary trickle conveyor just discovered a fresh 4331-candidate/0.54GiB bulk-scale backlog (materialized.remaining_candidates=4288, made_progress=True) the moment the stale-plan blocker cleared and the watcher catch-up backlog drained (polylogue-d7im). _maybe_run_raw_materialization_whale_pass only runs when the ordinary conveyor is quiescent for that tick - correctly gated off while this fresh backlog is being worked. Daemon's own advisory log line suggests 'polylogue ops maintenance rebuild-index' (bulk blue-green rebuild) as faster than waiting on trickle for backlogs this size, but I did not trigger that myself (heavier/resource-intensive operation, deferring to operator). Will keep monitoring via periodic wakeup; expect whale pass to fire once this fresh backlog quiesces.\n2026-07-27T07:10 rate analysis: trickle conveyor discovered a fresh backlog after d7im's stale-plan fix cleared (4331 initial). Measured drain rate across 3 samples: 4272-\u003e4256 (08:32:59-\u003e08:40:16, -16/7.3min) and 4256-\u003e4224 (-\u003e09:05:48, -32/25.5min) = ~1.25 candidates/min average. At 4224 remaining, that's ~56 hours (~2.3 days) to reach quiescence via trickle alone -- the whale escalation pass (which needs a fully quiescent tick) will not fire on any session-scale timeframe at this rate. This matches the daemon's own advisory log line verbatim: 'the trickle conveyor is sized for steady-state drift and can take weeks on a backlog this size; run polylogue ops maintenance rebuild-index for a resumable blue-green bulk rebuild instead of waiting on this conveyor.' Did not trigger that myself (heavier/resource-intensive operation against the live personal archive, correctly deferred to operator per this session's risk posture). Recommend operator either (a) runs the suggested rebuild-index pass, or (b) accepts multi-day background convergence and lets it drain unattended. Not scheduling further short-interval check-ins on this specific number until either the rate changes materially or the operator acts.\n2026-07-27 ~16:50 UTC: whale pass's 'fail-closed behind 1 unresolved durable stale-plan blocker' (seen 22:47 and 00:41 attempts) is very likely the exact fold_duplicate_alias non-convergence bug just root-caused and fixed in polylogue-ihc8 (PR #3326, merged). Confirmed via journalctl the plan raw-authority-frontier:058be945e0d8... is still failing as of 16:46:58 because polylogued.service is running a pinned Nix build (polylogue-0.3.0), not the merged fix -- needs a sinnix pin bump + rebuild + service restart to take effect. Deploy deliberately not triggered without operator confirmation (bouncing the live daemon). Once deployed, expect this specific stale-plan blocker to clear and the whale pass to proceed past it.\n2026-07-27 ~17:35 UTC: post-redeploy check (daemon restarted 18:33 CEST with ihc8 fix live) — no raw-authority pass has fired yet in this daemon lifetime (1h9min uptime, still doing ordinary watcher catch-up: 18827 sessions/4.9M messages indexed per heartbeat). Consistent with the earlier finding that the whale/raw-authority pass needs a quiescent tick, which the trickle backlog (~56h ETA) won't produce on any short timeframe. Not holding a live monitor open for this; will check again on a longer horizon (next session or explicit request) rather than polling.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-21T21:17:41Z","created_by":"Sinity","updated_at":"2026-07-27T17:43:33Z","labels":["area:daemon","area:perf","area:storage"],"comments":[{"id":"019f8acc-2842-7288-a38b-5e51f6bbfd97","issue_id":"polylogue-t93b","author":"Sinity","text":"2026-07-22 census-state note (from hook de-inflation, polylogue-31r1): the live archive's raw-authority census is internally inconsistent and must be reconciled/rebuilt as part of this convergence work. Cause: hook de-inflation deleted 64,896 hook raw_sessions; those hook raws had ~64,895 frontier plans + blockers + census_plans/post_plans (hook noise flooding the authority machinery). A surgical orphan-plan deletion (PR #3266, now closed) removed the dangling plans but broke the carried-forward/retryable postflight invariant (raw_authority.py:1315). Daemon now defers census passes to convergence_debt (736+) instead of the prior stale-plan-blocker degradation; it survives (0 crashes), archive data correct. Recommended resolution: full raw-authority census rebuild over the current hook-free raw set (no prior-census carried-forward comparison), preserving accepted heads/revision_authority (byte_proven 15,465 / quarantined 20,986). No dedicated census-reset mechanism exists yet.","created_at":"2026-07-22T17:07:43Z"},{"id":"019f8b0b-295b-7b0b-9a1e-8187dac6711f","issue_id":"polylogue-t93b","author":"Sinity","text":"2026-07-22 precise convergence wall (after hook de-inflation + census reset + index-seed prune unblocked everything else): the daemon whale-pass candidate scan returns None because the eligible components are NOT stream-safe. Live: whale_pass_candidate=None; top materialization components are (1) 6.33GB / 803 members / stream_safe=FALSE — the codex 019f49d8 witness; (2) 1.28GB / 6695 members / stream_safe=FALSE; (3) 582MB / 9 / stream_safe=FALSE. raw_materialization_whale_pass_candidate (repair.py:4137) skips any component with a non-stream-safe member, so these stay typed-blocked exactly as designed. Ordinary candidates=1371; authority_quarantined=2085; byte_authority_quarantined=843. byte_proven=15465 / quarantined=20986 (most quarantined are members of the non-stream-safe whale components).\n\nSo full convergence is blocked on the ORIGINAL t93b design constraint: the whale members are not stream-record-safe, so the memory-bounded whale pass cannot parse them. Resolving needs a streaming parse path for the non-stream-safe codex members (or an offline bounded handling), plus authority refinement for the genuinely-ambiguous quarantined raws. NOT a hook-residue problem. Prerequisites now satisfied: stale-plan blocker cleared, census healthy (rebuilds fresh), hook residue gone, #3261 whale-budget deployed.","created_at":"2026-07-22T18:16:32Z"},{"id":"019f8b0d-de45-7ac4-812a-b11f5ad77276","issue_id":"polylogue-t93b","author":"Sinity","text":"2026-07-22 whale-pass stream-safety lead: raw_materialization_whale_pass_candidate returns None because _raw_materialization_component_stream_safe judges the whole 803-member whale component non-stream-safe. Root: _raw_materialization_stream_safe(candidates, raw_id) reads candidates.raw_origins/.raw_source_paths, but the ordered component includes ALREADY-MATERIALIZED (non-candidate) members not in the candidate maps -\u003e origin=None -\u003e is_stream_record_provider(None,None)=False. 783 of 803 whale members are non-candidate (real codex rows in raw_sessions, byte_proven). Memberships are clean (0 orphaned). So the whale is likely wrongly excluded: stream-safety should be resolved from raw_sessions for ALL component members, not just candidates. Candidate fix locus: repair.py:4016 _raw_materialization_stream_safe / 4130-4139 component scan. If confirmed, the whale (and the 1.28GB/582MB components) become eligible and the daemon whale pass can converge them.","created_at":"2026-07-22T18:19:30Z"}],"dependency_count":0,"dependent_count":0,"comment_count":3} {"_type":"issue","id":"polylogue-meoz","title":"ArchiveStore.delete_sessions detonates per-row derived-refresh triggers: 91-session delete ran 3h with 375GB reads and zero commit","description":"Live incident 2026-07-21 (yqeo retirement): ArchiveStore.delete_sessions on 91 hermes sessions sat 3h in one transaction: 375GB read (11 full scans of the 34GB index.db), 2MB written, WAL empty — killed and rolled back. py-spy: stuck in the per-session DELETE FROM sessions loop (archive.py:6725). Root cause: blocks_action_pairs_ad fires PER DELETED BLOCK ROW and each firing (a) deletes+rebuilds the whole session action_pairs with two window-function scans and (b) re-derives delegation_facts from delegation_facts_source. The production bulk write path suppresses this machinery via derived_refresh_guard rows (session-write, fts-bulk-session-write) but delete_sessions — the PRODUCT deletion API used by the CLI delete verb and SessionDeleteActuator — never sets them. Same pathology family as polylogue-crd8 (whale prefix-tail rewrite FTS/trigram detonation).","design":"Fix in delete_sessions itself (and any sibling bulk mutation entrypoints): wrap the delete in the derived_refresh_guard rows, do one-pass FTS maintenance explicitly (blocks_command_trigram delete commands with old text before block rows go away; contentless messages_fts DELETE by rowid), let indexed FK cascades remove the tree, clear guards, commit. Working reference implementation: /realm/tmp/worktrees/yqeo-v42/yqeo_retire_stale_v2.py (operator-run 2026-07-21). Regression test: seeded session with tool_use blocks, delete via product API, assert FTS docsize parity and action_pairs cleanup without trigger-driven rebuild (e.g. count trigger firings via guard-sensitive canary or measure statement count). Also audit epoch triggers (query_unit_frame_*_delete) cost under bulk cascade.","acceptance_criteria":"delete_sessions (and executor SessionDeleteActuator route) deletes a many-block session in seconds not hours; FTS/trigram stay coherent (docsize==indexable parity) after delete; regression test proves per-row action_pairs/delegation rebuild machinery does not fire during bulk delete; crd8 relation noted.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-21T19:15:22Z","created_by":"Sinity","updated_at":"2026-07-27T01:27:53Z","started_at":"2026-07-21T23:57:55Z","closed_at":"2026-07-27T01:27:53Z","close_reason":"Fixed and merged 2026-07-26 in PR #3263 (commit 096374983) — ArchiveStore.delete_sessions now wraps the whole batch in the same derived_refresh_guard rows the bulk session-write path uses (session-write + fts-bulk-session-write), does one explicit session-scoped FTS/trigram/action_pairs/delegation_facts pass instead of per-block trigger detonation, then removes physical rows via indexed FK cascade. Confirmed independently this session (2026-07-27) while investigating the same incident: attempted a narrower guard-only fix, found master already had a more complete version (also handles FTS/trigram, explicit belt-and-suspenders cleanup) already tested. Bead was stale (still in_progress with no completion note) - closing now.","labels":["area:perf","area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-zoc3","title":"ingest_record decode rejects binary provider payloads the rebuild parse path handles","description":"Found 2026-07-21 during the polylogue-yqeo targeted Hermes reprocess: parse_from_raw → process_ingest_batch → ingest_record fails all 3 hermes verification raws (source_path ~/.hermes/verification_evidence.db, SQLite database bytes) with \"decode: str is not valid UTF-8: surrogates not allowed: line 1 column 1\" — the worker decode step assumes text/JSON payloads before provider dispatch. The REBUILD path (revision_backfill._parse_retained_raw → sources/dispatch.parse_payload) parses these same raws fine (the v42 walk materialized verification sessions from them), so the two parse routes disagree on binary-payload providers. Consequence: targeted reprocess cannot re-materialize hermes verification sessions under the composed verification:\u003craw_id\u003e@profile-\u003ckey\u003e scheme (#3227); 4 stale old-pattern verification:2026* sessions remain in the index with no composed successors (retained deliberately — deleting them would lose read coverage).\n\nFix: route ingest_record payload decoding through the same provider-dispatch-aware envelope the rebuild path uses (binary-capable: detect_provider on bytes before any text decode), or teach build_raw_payload_envelope the binary lane. Add a contract test: any raw parseable by revision_backfill._parse_retained_raw must be parseable by ingest_record (parse-route parity for a representative binary fixture — the hermes verification fixture family exists under tests/fixtures/hermes/).\n\nAfter the fix: reprocess the 3 verification raws (coordinator, live archive), retire the 4 stale verification:2026* ids, and update the yqeo receipt.","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-21T15:08:01Z","created_by":"Sinity","updated_at":"2026-07-21T16:34:33Z","closed_at":"2026-07-21T16:34:33Z","close_reason":"Fixed in PR #3247 (merged): build_raw_payload_envelope now probes BOTH Hermes SQLite artifacts (state.db + verification_evidence.db) via the parsers own looks_like_*/marker_payload helpers BEFORE any text decode — ingest_record and the rebuild route now agree on binary payloads; marker classification extracted+shared so the decoded marker session classification is not shadowed by the .db path-only sidecar rule; profile_root threaded in backfill for identical composed ids on both routes. Parity contract test (243 lines) incl. exact live-error reproduction. Lane was 522-killed twice post-push; coordinator verified helpers + re-ran 51 tests on the branch and opened/merged the PR. Unblocks the yqeo verification-raw reprocess.","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -186,7 +223,7 @@ {"_type":"issue","id":"polylogue-9p8x","title":"Parallelize raw-authority replay census; fix spill-cache None sentinel","description":"Measured 2026-07-18 on the live 73,311-raw archive: polylogue ops maintenance rebuild-index ran at ~204 sessions/35min single-core (ETA 10-13h for the corpus) while the direct-ingest pipeline parses the same bytes with an 8-worker ProcessPool. Three causes, code-verified: (1) maintenance/replay.py:152 accepts ingest_workers and does `del ingest_workers` — the entire replay funnels into ONE asyncio.to_thread(backfill_historical_revision_evidence) call; census parses all payloads sequentially in-process. (2) _ParsedSessionSpill.add() returns WITHOUT caching when max_cached_payload_bytes is None, and the CLI path passes no envelope -\u003e None -\u003e zero caching -\u003e every raw parsed TWICE (census + replay) and cohort loops reparse per revision from blob; only the daemon path (max_payload_bytes=64MiB, daemon/cli.py:646) gets caching. (3) per-cohort transactions (minor). Combined ~14x slower than achievable. This machinery is also the hjpx.2 July-15-scale proof substrate, so its throughput gates Lane D.","design":"Fix 1 (one-line, ship first): maintenance/replay.py passes max_payload_bytes=64MiB (same envelope as daemon/cli.py) so the CLI rebuild caches parse output — eliminates the double/multi parse. Fix 2 (the real win): parallelize the CENSUS parse across a ProcessPoolExecutor (precedent: pipeline/services/archive_ingest.py _parse_source_path_worker) — parse is pure read-only blob-\u003eParsedSession work and authority-NEUTRAL; workers return spill entries; classification, cohort expansion, and apply_raw_revision_replay stay strictly sequential in the single writer, so authority ordering and the conservation ledger are untouched. Honor the existing ingest_workers parameter instead of deleting it; default min(8,cpus-1); POLYLOGUE_INGEST_PARSE_WORKERS override. Fix 3 (optional): batch cohort applies per commit window. Anti-vacuity: a test that pins spill-cache hit behavior under the CLI envelope (mutation: restore None -\u003e test fails) and a throughput smoke on the synthetic corpus proving parallel census output byte-identical to sequential (order-independence proof).","acceptance_criteria":"CLI rebuild-index on a synthetic multi-cohort corpus: (1) each raw parsed at most once (spill hits pinned by test); (2) census runs across N workers with results identical to sequential run (same generation content hash); (3) authority apply order remains sequential+deterministic; (4) measured wall-clock on the synthetic corpus improves \u003e=4x vs pre-fix baseline recorded in the bead.","notes":"2026-07-18 lane-D implementation: Fix 1 (honor ingest_workers instead of deleting it; maintenance/replay.py::rebuild_index_from_source now resolves None -\u003e shared resolve_parse_worker_count() default) and Fix 2 (decoupled spill-cache bound from the resource-envelope: backfill_historical_revision_evidence gained max_cached_payload_bytes, independent of max_payload_bytes so an unbounded selected_raw_ids=None rebuild can cache without also activating envelope blocking, which the literal \"max_payload_bytes=64MiB on the CLI path\" suggestion in this beads own design would have broken -- raw_membership_census_rows(None) returns the WHOLE archive in one census selection, so any finite envelope there raises RawRevisionReplayResourceBlockedError immediately) are implemented on branch feature/repair/raw-authority-closure. Census parse (_census_historical_revision_evidence) now spreads read-only blob-\u003eParsedSession decode across a ProcessPoolExecutor via a new _parse_retained_raws helper (polylogue/sources/revision_backfill.py); archive writes stay in fixed pending_rows order regardless of worker completion order, proven byte-identical to sequential by test_parallel_census_matches_sequential_archive_state. repair_raw_materialization (storage/repair.py) gained ingest_workers defaulting to the same resolver, so the daemon path and the hjpx.2 scale-proof harness (devtools/raw_authority_scale_proof.py, unmodified) get parallel census automatically. Anti-vacuity pair test_backfill_replay_reparses_when_spill_cache_absent (3 parse calls, pre-fix shape) vs test_backfill_replay_reuses_spill_cache_when_bound_explicitly (2 parse calls) pins the spill-cache fix. Focused: tests/unit/sources/test_revision_backfill.py 18 passed; -k raw_materialization 91 passed; -k raw_authority 57 passed.\n\nAC4 correction from measured evidence (evidence-driven investigation, not the original hypothesis): cProfile on a synthetic 60-raw/1.7MB-avg-payload corpus (backfill_historical_revision_evidence in isolation, real NVMe-backed /realm/tmp archive) shows sqlite3.Connection.__exit__ (per-write commit/fsync) at 17.265s of 40.517s total (42.6%) versus parse at 16.465s (40.6%) -- a near-even split, not parse-dominated. Since Fix1+2 only parallelize the parse share, Amdahls law caps the realistic ceiling near 1.7x, not 4x: a direct throughput benchmark measured 1.22x on 200 small (~50KB) payloads and 0.63x (WORSE) on 80 larger (~1.7MB) payloads, where cross-process pickling of large ParsedSession results exceeded the parse-time savings. AC4 as originally written is not met and is not achievable by this beads Fix1+2 scope alone. Filed polylogue-amg1 (commit-batching + size-aware parse dispatch, the \"Fix 3 (optional)\" this bead deliberately deferred, now promoted to required scope with the measured evidence) to pursue the remaining throughput lever without touching write/transaction boundaries in this authority-critical single-writer path inside an already-large change. Closing this bead on Fix1+2 (correct, tested, real modest speedup, eliminates the identified dead-code and double-parse bugs) with AC4 explicitly deferred to amg1, per acceptance-criteria-honesty discipline -- not closing silently or force-claiming 4x.\n2026-07-18 lane-D: PR #3122 opened (https://github.com/Sinity/polylogue/pull/3122) covering Fix 1+2 implementation plus rebase parity fix for #3113s Hermes SQLite-detection change. devtools verify --quick green on every commit.","status":"closed","priority":1,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-18T14:23:43Z","created_by":"Sinity","updated_at":"2026-07-18T17:26:32Z","started_at":"2026-07-18T14:35:30Z","closed_at":"2026-07-18T17:26:32Z","close_reason":"Merged PR #3122 (a53785b10): Fix1 (honor ingest_workers, don't delete it) and Fix2 (decouple spill-cache bound from resource envelope via new max_cached_payload_bytes) landed with parallel census parse across a ProcessPoolExecutor, proven byte-identical to sequential. AC4 (\u003e=4x measured speedup) corrected by cProfile evidence to ~1.2-1.7x (Amdahl-limited by comparable SQLite commit overhead, not parse-dominated); deferred to polylogue-amg1 rather than force a larger transaction-boundary change into this fix. Focused tests: revision_backfill 18/18, raw_materialization+raw_authority 148/148, devtools verify --quick green on every commit.","labels":["area:perf"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-z1c6","title":"Demo import path diverges from direct seeder (blocks README quickstart)","description":"External res-04 (README positioning, Wave 2) found polylogue import --demo --wait does NOT converge to the same archive as polylogue demo seed: daemon path yields 15 sessions/60 messages vs seeder 15/62; AI Studio identity differs (aistudio-drive:demo-00 vs demo-00-0); daemon path lacks provider-usage messages, capture-gap events, three browser-capture raw variants, source-outage interval events, synthetic embeddings + status rows; the success banner and tests/integration/test_demo_daemon_convergence.py still expect the OLD 3-session/19-message world. This blocks publishing the README quickstart (res-04 merge gate QA-01). Full repair checklist: .agent/handoffs/external-agent-campaigns/2026-07-17-gpt-pro-wave-2/results/res-04/r01/extracted/NEXT-ACTIONS.md","design":"Decision required, then implementation: either (1) move every intended construct into source-shaped fixtures so normal daemon convergence produces them, or (2) add an explicit idempotent post-ingest demo augmentation stage used by BOTH direct seed and daemon demo scheduling. Do not leave direct seed with a private sequence (insight rebuilds, usage injection, repo/embedding seeding, overlays) the public daemon path cannot execute. Owning areas: cli/commands/import_command.py, demo/{seed,verify,constructs}.py, scenarios/corpus.py, daemon ingest/convergence, test_demo_daemon_convergence.py.","acceptance_criteria":"Fresh temp archive: polylogue import --demo --wait and polylogue demo seed converge to the identical semantic contract (same session ids, message counts, all 37 declared constructs); success banner and integration test assert the CURRENT canonical world; polylogue demo verify passes against the daemon-produced archive.","notes":"Investigated and partially fixed via PR #3179 (feature/fix/demo-daemon-import-parity).\n\nUnderstanding of scope: root-caused THREE independent divergences between\n`polylogue import --demo --wait` and `polylogue demo seed` by reproducing\nboth against isolated scratch archive roots (real `polylogued run`\nsubprocess + fully isolated HOME/XDG/POLYLOGUE_* env, no operator config,\nno network):\n\n1. Identity bug (aistudio-drive:demo-00 vs demo-00-0) -- FIXED\n (polylogue/sources/dispatch.py: _lower_drive_like_payload's\n _looks_like_chunked_session_list branch always appended -{index}\n regardless of list length, unlike its sibling branch).\n2. Missing shared post-ingest augmentation (provider usage, embeddings,\n repo name, session-insight materialization never ran on the daemon\n path) -- FIXED via apply_demo_post_ingest_augmentation(), called from\n both seed_demo_archive() and import_command.py's --wait flow.\n3. Stale CLI banner (\"sessions=3 messages=19\") + stale integration test\n (3-session/19-message world) -- FIXED, banner now derives real counts,\n integration test rewritten against the current 16-session\n DEMO_SESSION_IDS world.\n\nNOT fixed (deferred to polylogue-52l2, filed with full root-cause detail):\none specific multi-material session (chatgpt-export:dc13ca54-..., a\ndirect ChatGPT export coalescing with paired browser-capture variants)\nnondeterministically loses 0-2 messages on the daemon path. Root cause:\nthe daemon's incremental raw-materialization census\n(classify_raw_revision_cohort) can isolate-accept one competing raw as an\n\"unambiguous singleton baseline\" before its true siblings are discovered\non a later tick; apply_raw_membership_classification's existing-head\nsafety guard then blocks a later, correct membership-classification\ndecision from overriding it. I DID wire up the (previously entirely dead)\nbrowser_snapshot_fidelity precedence machinery in\nsession_revision_membership.py + revision_backfill.py, and mirrored the\nsame \"direct export always outranks browser-capture\" rule in\ningest_precedence.py -- both are real, verified, necessary fixes -- but\nthey are not sufficient to fix this specific ordering race, which is a\ndeeper architectural issue in the revision-authority subsystem I judged\ntoo risky to fix in this same change (it's the core mechanism all real\narchives' raw materialization goes through, not demo-specific).\n\nAlso discovered (documented as an addendum on polylogue-52l2, NOT this\nPR's regression -- confirmed via direct comparison against unmodified\n`ingest_precedence.py`): the direct-seed path itself has pre-existing,\nunrelated flakiness (~40-60% failure rate) on the SAME\nsource_outage_interval_events/capture_gap_events construct checks, in\ntests/unit/demo/test_demo_seed_verify.py. Root cause not isolated.\n\nAcceptance criteria: satisfied for session/message identity convergence,\nbanner/test honesty. NOT satisfied for full 37-construct parity /\n`polylogue demo verify` passing unconditionally against the\ndaemon-produced archive -- one session's 3 constructs remain\nnondeterministic pending polylogue-52l2. Leaving this bead open per\ninstructions; PR #3179 is ready for review/merge as the honest, verified\npartial fix.\n\nVerification run: mypy clean (13 files), ruff clean, devtools verify\n--quick exit 0, devtools test (dispatch/session_revision_membership/\nrevision_backfill/demo_seed_verify) 64 passed + 3 pre-existing flaky\nfailures classified above, live-daemon integration test 1 passed.","status":"closed","priority":1,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-18T12:38:51Z","created_by":"Sinity","updated_at":"2026-07-20T00:07:06Z","closed_at":"2026-07-20T00:07:06Z","close_reason":"PR #3179 merged: daemon import --demo now converges with direct seeder — single-doc identity bug fixed (list-wrap -N suffix guard), shared post-ingest augmentation extracted + bounded self-heal vs insight-stage race, browser-capture precedence made order-independent incl. compact captures (review P1s). README quickstart unblocked. Deep residual (one multi-material session nondeterminism, 0-2 messages) tracked honestly on polylogue-52l2.","labels":["area:demo"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-8l8e","title":"Repair raw-authority convergence review gaps","description":"Resolve the eleven code-review findings across index rebuild membership replay, bounded repair scheduling, byte-envelope identity, crash-safe census and reconciler receipts, readiness, and raw-authority scale-proof fidelity.","design":"Treat durable source authority as replayable after derived-index loss, make repair plans immutable and fully postconditioned before execution receipts, carry active resource policy through every identity/decision, and fail proof evidence closed.","acceptance_criteria":"All eleven reported review findings have a production-code fix and a regression test; bounded repair receipts cannot falsely claim convergence; focused and affected-area verification pass.","notes":"PR #3046 squash-merged. All eleven review findings plus three follow-up review gaps were addressed. Verification: focused raw-authority suite 114 passed; ledger/scale follow-up 36 passed; legacy receipt regression passed; pre-push quick gate passed.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-17T16:31:01Z","created_by":"Sinity","updated_at":"2026-07-17T16:56:01Z","started_at":"2026-07-17T16:31:17Z","closed_at":"2026-07-17T16:56:01Z","close_reason":"Merged PR #3046 with review findings and regressions resolved.","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-hs3y","title":"Acquire linked agent materials as queryable work evidence","description":"Make arbitrary linked agent materials durable, queryable work evidence.\n\nAgents routinely emit links to files, pages, patches, exports, archives, logs,\nreports, artifacts, and other materials. Polylogue currently cannot acquire a\ngeneral linked material: a ZIP/PATCH/Markdown result may be only a download,\nand import may classify it as unknown without preserving a queryable record.\nThe archive must answer what material was referenced or acquired, by whom and\nwhen, what bytes were obtained, what it contained, what it supported, and what\nlater work it affected—without making any one provider UI, download sequence,\nclipboard, campaign, or chat workflow normative.","design":"Introduce a provider-neutral material acquisition boundary. Given a URL or\nattachment/reference admitted from any agent/session/surface, fetch or retain\nthe available bytes under explicit authority and privacy policy, record the\nimmutable content hash, retrieval time, source/referrer, media type, redirect\nand access outcome, extraction/index manifest, and any declared identity. A\nmaterial can be unavailable, expired, access-denied, malformed, duplicate,\npartial, or superseded and must remain an honest queryable object with the\nexact reason; it is never parse debt or silently discarded.\n\nAssociate acquired materials with zero, one, or many provider sessions,\nmessages, tool calls, workflow attempts, Beads, commits, PRs, and verification\nreceipts when direct evidence exists. Links and attachments are base material\nobservations; provider-native result packages, clipboard captures, browser\ndownloads, and manually supplied files are adapters on top, not competing\nofficial workflows. Reuse raw-artifact storage, work-evidence graph, OriginSpec\nadmission, ObjectRef, and privacy classification; do not make campaign-local\nJSON or a ChatGPT-specific protocol the authority.","acceptance_criteria":"1. Any admitted link or attachment from an agent/session/surface can become a durable material observation with referrer/source, acquisition attempt, immutable bytes when obtainable, content hash, media metadata, custody, and privacy classification.\n2. Redirected, expired, unavailable, access-denied, malformed, duplicate, partial, and stale materials remain queryable with truthful state, retry/supersession lineage, and exact diagnostic; no silent loss or false successful session.\n3. Safe type-aware extraction/indexing preserves an auditable manifest while arbitrary bytes stay retrievable; archive/session parsing is optional and never the only representation.\n4. Direct evidence links materials many-to-many with sessions, messages, actions, workflow run/task/attempts, Beads, commits/PRs, and verification effects; absence of a captured chat never prevents material retention.\n5. Query surfaces reconstruct material provenance and downstream effects with authority/confidence, distinguishing a claimed link from acquired bytes and from accepted repository effects.\n6. Browser downloads, pasted files, provider attachments, agent-emitted URLs, and the current GPT Pro packages are acceptance fixtures for the same general mechanism, not separate product workflows.\n7. Acquisition and indexing enforce privacy/access policy and prevent accidental schema/public/synthetic promotion of raw material.","notes":"2026-07-17 live GPT Pro intake evidence: campaign raw results were preserved under .agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/{analysis,beads,testdiet}/results. polylogue import --explain classifies every ZIP as unknown-export and produces zero sessions/messages/blocks (Markdown/PATCH/CSV entries unsupported); scheduling them would create parse debt, so no false archive ingest was attempted. Browser tabs establish external-chat continuity: cold-start agent implementation chat 6a59b873-f1c4-83eb-90b6-66a7dd6c9569 reports implementation but no valid ZIP; rebuild-equivalence chat 6a59b85f-4ffc-83eb-b955-cd4d32fe928c reports a broken link and ongoing rebuild. The recovered beads-02 PATCH.diff applies to f654480cad and must be linked as incomplete external result evidence rather than pretending it is a captured ChatGPT session.\n2026-07-17 scope correction: GPT Pro downloads, browser links, and ClipSe correlation were observed fixtures, not the product workflow. This Bead now owns general link/attachment material acquisition; any provider-specific adapter must consume that substrate.\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: LIVE. No landing note; 2026-07-17 notes record investigation/scope-correction only, describes current inability to acquire linked materials (import --explain classifies ZIPs as unknown-export).","status":"open","priority":1,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-17T10:57:46Z","created_by":"Sinity","updated_at":"2026-07-31T05:52:23Z","labels":["area:evidence","area:ingest","area:orchestration","area:storage","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-hs3y","depends_on_id":"polylogue-1vpm.6","type":"relates-to","created_at":"2026-07-17T12:58:08Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-hs3y","depends_on_id":"polylogue-2qx.1","type":"relates-to","created_at":"2026-07-17T12:58:08Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-hs3y","depends_on_id":"polylogue-t46.8","type":"relates-to","created_at":"2026-07-17T12:58:09Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-hs3y","title":"Acquire linked agent materials as queryable work evidence","description":"Make arbitrary linked agent materials durable, queryable work evidence.\n\nAgents routinely emit links to files, pages, patches, exports, archives, logs,\nreports, artifacts, and other materials. Polylogue currently cannot acquire a\ngeneral linked material: a ZIP/PATCH/Markdown result may be only a download,\nand import may classify it as unknown without preserving a queryable record.\nThe archive must answer what material was referenced or acquired, by whom and\nwhen, what bytes were obtained, what it contained, what it supported, and what\nlater work it affected—without making any one provider UI, download sequence,\nclipboard, campaign, or chat workflow normative.","design":"Introduce a provider-neutral material acquisition boundary. Given a URL or\nattachment/reference admitted from any agent/session/surface, fetch or retain\nthe available bytes under explicit authority and privacy policy, record the\nimmutable content hash, retrieval time, source/referrer, media type, redirect\nand access outcome, extraction/index manifest, and any declared identity. A\nmaterial can be unavailable, expired, access-denied, malformed, duplicate,\npartial, or superseded and must remain an honest queryable object with the\nexact reason; it is never parse debt or silently discarded.\n\nAssociate acquired materials with zero, one, or many provider sessions,\nmessages, tool calls, workflow attempts, Beads, commits, PRs, and verification\nreceipts when direct evidence exists. Links and attachments are base material\nobservations; provider-native result packages, clipboard captures, browser\ndownloads, and manually supplied files are adapters on top, not competing\nofficial workflows. Reuse raw-artifact storage, work-evidence graph, OriginSpec\nadmission, ObjectRef, and privacy classification; do not make campaign-local\nJSON or a ChatGPT-specific protocol the authority.","acceptance_criteria":"1. Any admitted link or attachment from an agent/session/surface can become a durable material observation with referrer/source, acquisition attempt, immutable bytes when obtainable, content hash, media metadata, custody, and privacy classification.\n2. Redirected, expired, unavailable, access-denied, malformed, duplicate, partial, and stale materials remain queryable with truthful state, retry/supersession lineage, and exact diagnostic; no silent loss or false successful session.\n3. Safe type-aware extraction/indexing preserves an auditable manifest while arbitrary bytes stay retrievable; archive/session parsing is optional and never the only representation.\n4. Direct evidence links materials many-to-many with sessions, messages, actions, workflow run/task/attempts, Beads, commits/PRs, and verification effects; absence of a captured chat never prevents material retention.\n5. Query surfaces reconstruct material provenance and downstream effects with authority/confidence, distinguishing a claimed link from acquired bytes and from accepted repository effects.\n6. Browser downloads, pasted files, provider attachments, agent-emitted URLs, and the current GPT Pro packages are acceptance fixtures for the same general mechanism, not separate product workflows.\n7. Acquisition and indexing enforce privacy/access policy and prevent accidental schema/public/synthetic promotion of raw material.","notes":"2026-07-17 live GPT Pro intake evidence: campaign raw results were preserved under .agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/{analysis,beads,testdiet}/results. polylogue import --explain classifies every ZIP as unknown-export and produces zero sessions/messages/blocks (Markdown/PATCH/CSV entries unsupported); scheduling them would create parse debt, so no false archive ingest was attempted. Browser tabs establish external-chat continuity: cold-start agent implementation chat 6a59b873-f1c4-83eb-90b6-66a7dd6c9569 reports implementation but no valid ZIP; rebuild-equivalence chat 6a59b85f-4ffc-83eb-b955-cd4d32fe928c reports a broken link and ongoing rebuild. The recovered beads-02 PATCH.diff applies to f654480cad and must be linked as incomplete external result evidence rather than pretending it is a captured ChatGPT session.\n2026-07-17 scope correction: GPT Pro downloads, browser links, and ClipSe correlation were observed fixtures, not the product workflow. This Bead now owns general link/attachment material acquisition; any provider-specific adapter must consume that substrate.\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: LIVE. No landing note; 2026-07-17 notes record investigation/scope-correction only, describes current inability to acquire linked materials (import --explain classifies ZIPs as unknown-export).\n2026-07-31 scoped GDPR-zip-classification fix landed (this session):\n\nRoot causes found (live archive, read-only):\n\n1. ZIP sidecar members default to unknown-export independently of their zip's\n real conversation-shaped sibling. Live evidence: 25 unknown-export\n raw_sessions rows, ALL of them non-conversation sidecars\n (user.json/message_feedback.json/shared_conversations.json/shopping.json/\n projects.json/memories.json/attachment file_*.json) sitting inside\n otherwise-correctly-detected chatgpt-export/claude-ai-export GDPR zips.\n `_extract_zip_member_records` (sources/live/batch.py) seeded every ZIP\n member's detection with a fresh Provider.UNKNOWN when the top-level\n fallback provider was itself unknown (generic inbox drop) - only the\n member whose own JSON shape detects cleanly (conversations.json) got\n tagged correctly; every low-signal sibling fell back independently.\n Fix: added `_sniff_zip_provider` - a one-time pre-scan of the zip's\n members (small prefix read, same detection budget as whole-file\n detection) that establishes the zip's dominant provider once, seeding\n every member's per-entry detection with it. Only activates when the\n top-level fallback is Provider.UNKNOWN; a source that already resolved a\n provider (per-provider watched directory) is untouched.\n\n2. 4 confirmed ~/.gemini path sessions tagged claude-code-session. Root\n cause: Gemini CLI's `.jsonl` chat-log checkpoint format opens with a\n session-metadata stub record (sessionId+projectHash+kind, NO \"messages\"\n key - turns arrive as later lines). That bare \"sessionId\" key alone\n satisfied Claude Code's `_STRONG_SESSION_KEYS` bare-presence rule\n (code_detection.py), and the existing Gemini CLI structural detector\n only ran for single-document payloads (len(payloads)==1), never for a\n genuine multi-line JSONL sequence. Fixed: widened\n `local_agent.looks_like_gemini_cli` to also recognize the messages-less\n stub shape (requires projectHash - unique to gemini-cli - alongside the\n kind enum), and widened dispatch.py's sequence-first-record check to\n trust that stub shape at any sequence length (kept the\n messages-embedded shape restricted to len==1, unchanged).\n Full turn-by-turn parsing of this JSONL event-log shape does not exist\n yet (no parser handles the multi-line-per-turn shape) - filed as\n polylogue-8u1p; these 4 sessions now correctly detect as\n Provider.GEMINI_CLI (raw_sessions.origin fixed) but do not yet\n materialize as sessions rows (0 messages, by design - no forced empty\n session; not a session shows nothing new was lost that the old\n misclassification didn't already lose).\n\nRead-only archive-wide audit of origin vs source_path shape (all 9 origins\npresent in the live archive: claude-code-session, codex-session,\nchatgpt-export, claude-ai-export, hermes-session, aistudio-drive,\nantigravity-session, gemini-cli-session, grok-export): only the gemini-cli\ncollision above was a genuine detection defect. One other bucket looked\nsuspicious at first (12 claude-code-session rows under\n~/.local/share/polylogue/drive-cache/gemini/*.jsonl.txt.json) but content\ninspection confirmed the bytes are genuinely Claude-Code-shaped\n(`{\"type\":\"summary\",\"summary\":\"Claude AI usage limit reached\",...}` -\nClaude Code's own summary record type) - a cache-location/content-provenance\nnaming coincidence, not a classification bug. Left untouched.\n\nDesign-constraint compliance: neither fix defaults anything to a session.\nThe ZIP fix only corrects which Provider a non-session sidecar is tagged\nwith (still routes through the existing raw_artifacts/classify_artifact\nnon-session path); the gemini-cli fix only corrects provider detection --\nit does not force parsing of the still-unsupported event-log shape into a\nfake session.\n\nFiles changed: polylogue/sources/live/batch.py, polylogue/sources/dispatch.py,\npolylogue/sources/parsers/local_agent.py. Tests: real live-archive-shaped\nfixtures added to tests/unit/sources/test_live_watcher.py (zip sniff,\nverified fails without the fix) and\ntests/unit/sources/parsers/test_origin_regression_pack.py (gemini-cli\nstub collision, documents the pre-fix false match).\n\nOut of scope / left alone: full parsing of the gemini-cli JSONL event-log\nformat (tracked polylogue-8u1p); no archive data repair (a separate lane\nowns that per the task brief) - this PR only fixes the producing code.\n\nPR opened: https://github.com/Sinity/polylogue/pull/3436 (fix(sources): stop GDPR export ZIP siblings and gemini-cli stubs misclassifying). Follow-up polylogue-8u1p filed for full gemini-cli JSONL event-log parsing.","status":"open","priority":1,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-17T10:57:46Z","created_by":"Sinity","updated_at":"2026-07-31T08:33:54Z","labels":["area:evidence","area:ingest","area:orchestration","area:storage","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-hs3y","depends_on_id":"polylogue-1vpm.6","type":"relates-to","created_at":"2026-07-17T12:58:08Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-hs3y","depends_on_id":"polylogue-2qx.1","type":"relates-to","created_at":"2026-07-17T12:58:08Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-hs3y","depends_on_id":"polylogue-t46.8","type":"relates-to","created_at":"2026-07-17T12:58:09Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-b054.1.1.9","title":"Diagnose zero-success live ingest under xdist","description":"The second fresh 8-worker seed on 2026-07-17, master b9431a05, completed cleanup but failed nine tests: all five non-nightly daemon convergence scale tiers, three large-session convergence probes, and demo construct coverage. Each convergence case reported succeeded_files=0 without a resource or timeout failure. The preceding fresh seed on 194a4597 passed, and b9431a05 changes browser-extension files only, so this is likely an order/isolation/shared-state pathology rather than a product regression caused by #2998.","design":"First reproduce the exact convergence and demo nodes isolated and under xdist on the same master, then capture per-file ingest errors/metrics through the live production path. Compare the seed worktree against the passing 194a4597 witness. Identify any shared config, archive-root, SQLite, process, or environment coupling. Repair only evidence-confirmed behavior; do not relax succeeded-file or demo construct assertions. Record why any suspected cause is refuted.","acceptance_criteria":"1. Exact nine-node cluster is classified as deterministic product defect, order/isolation defect, or environmental artifact using focused isolated and xdist witnesses. 2. Live-ingest evidence exposes why successful-file count is zero. 3. Any repair retains production-route scale-tier and demo construct assertions. 4. Focused cluster passes isolated and xdist, then a fresh 8-worker seed is green. 5. Receipt records cleanup, peak resource, and precise failure/passing evidence.","notes":"2026-07-17 evidence: the failed full seed had all five scale tiers and three convergence probes return succeeded_files=0, exactly matching LiveBatchProcessor's process-global is_degraded short-circuit. Exact cluster passed 10/10 under both 3 and 8 focused xdist on the same b9431a05 master, refuting a deterministic daemon/product or basic 8-worker defect. Global tests/conftest.py reset every other major singleton but not degraded state; only package-local sources/schema-preflight fixtures did. PR #3000 merged as 3826ecdef: global fixture clears degraded state before each test and at teardown, preserving within-test daemon semantics while eliminating suite-order leakage. Focused post-fix 8-worker cluster passed 10/10; final fresh full seed is running next.\n2026-07-17 closure evidence: full fresh 8-worker seed after #3000 passed on 3826ecdef (run 20260717T101835Z-seed-testmon-2104057-f1279475): 15,908 passed, 1 skipped, pytest 270.73s, peak PSS 5790.1 MiB, zero swap, no signals, quiescent RSS 0/no survivor. This confirms the process-global degraded-state reset repairs the full-suite order leak without weakening live-ingest assertions.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-17T10:09:35Z","created_by":"Sinity","updated_at":"2026-07-17T10:24:59Z","started_at":"2026-07-17T10:09:43Z","closed_at":"2026-07-17T10:24:59Z","close_reason":"Evidence confirmed a process-global degraded-state test leak; #3000 resets it per test. The exact cluster passed focused under 3 and 8 workers and the fresh full 8-worker seed passed on 3826ecdef.","labels":["agent-readiness","area:architecture","area:beads","area:daemon","area:test-harness","horizon:frontier","invariant","verification"],"dependencies":[{"issue_id":"polylogue-b054.1.1.9","depends_on_id":"polylogue-b054.1.1","type":"parent-child","created_at":"2026-07-17T12:09:35Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-b054.1.1.8","title":"Make named synthetic workload generation deterministic across processes","description":"Fresh 8-worker seed at master 193b722da completed its process scope but failed 14 CLI snapshot assertions as one coherent cluster: the nominally deterministic named chatgpt workload generated 15 messages and different session IDs/tokens where committed snapshots and the prior baseline expect 12. The first observed mismatch was test_analyze_facets_include_deferred_materializes_expensive_families (expected message_types {message: 12}, actual {message: 15}); all remaining failures are identities/counts derived from the same corpus. Do not regenerate snapshots until generation is shown deterministic across isolated and xdist processes.","design":"Trace every unordered iteration / process-sensitive state in schema-driven SyntheticCorpus and workload artifact construction, including schema field selection, structural variants, relation solving, corpus/build cache identity, and random state ownership. Make named workload output byte-identical for same spec/build/schema across fresh processes and xdist workers. Prove it through real workload-artifact construction rather than a toy RNG test, then reconcile snapshots only if a deliberate product corpus change remains.","acceptance_criteria":"1. Same named CorpusSpec produces byte-identical artifacts, identities, counts, and receipts across fresh isolated processes and xdist workers. 2. No unordered iteration or mutable cross-run state silently influences seeded output. 3. The 14 CLI snapshot failures are resolved by determinism repair or an explicitly audited intentional corpus change, not blind snapshot update. 4. Fresh 8-worker seed passes twice after repair.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-17T09:46:55Z","created_by":"Sinity","updated_at":"2026-07-17T09:48:46Z","started_at":"2026-07-17T09:46:57Z","closed_at":"2026-07-17T09:48:46Z","close_reason":"Misframed by fresh-process evidence: named cli-chatgpt generation at master 193b722da is byte-identical across two independent interpreters (SHA-256 3534d205eb169498463d65baf5107925f6d71f706b6b0f8537f4c6bb4838c99d). The 14 full-seed snapshot failures are deterministic stale expectations after intentional compact-default synthetic generation changed intra-session RNG consumption, not cross-process nondeterminism. Reconciliation remains in polylogue-b054.1.1.6.","labels":["agent-readiness","area:architecture","area:beads","horizon:frontier","invariant","verification"],"dependencies":[{"issue_id":"polylogue-b054.1.1.8","depends_on_id":"polylogue-b054.1.1","type":"parent-child","created_at":"2026-07-17T11:46:55Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-b054.1.1.7","title":"Bound Gemini property workload generation under xdist","description":"A clean 8-worker seed at master 5576d9d85 completed process cleanup but failed exactly one test: tests/unit/sources/test_source_laws.py::test_parse_payload_bundle_cardinality_contract[gemini-bundle]. Its Gemini synthetic provider payload strategy exceeded pytest-timeout 120s inside recursive schema generation, then Hypothesis reported inconsistent replay. The same node immediately passed isolated in 18.01s, so this is a load/shape-sensitive property workload pathology, not a deterministic product failure. It blocks the two green post-repair 8-worker seeds required by polylogue-b054.1.1.5.","design":"Measure the pathological generated schema/path and establish why its recursion/cardinality can explode under concurrent load. Repair the generator/strategy bound or cache policy so a property draw is deterministic and bounded while retaining coverage of representative Gemini nested payloads. Do not merely raise timeout or quarantine the test. Prove the exact node repeatedly isolated and under xdist, then repeat clean full 8-worker seeds.","acceptance_criteria":"1. Exact property node has a bounded, deterministic draw path under 8-worker load; no Hypothesis replay flake. 2. Representative Gemini nested/export shape remains covered. 3. Focused isolated and xdist repeats pass. 4. Two fresh full 8-worker seed-testmon runs pass after the repair.","notes":"2026-07-17: PR #2995 (193b722da) bounds default synthetic payload tails while preserving explicit unbounded tail workloads. Focused property/contract tests passed 21/21 under xdist; first fresh post-repair 8-worker seed passed on master 194a4597 (run 20260717T095554Z-seed-testmon-2043733-287df7bc; 278.71s; exit 0). Second independent full seed remains before closure under AC 4.\n2026-07-17 closure evidence: second independent fresh 8-worker seed passed on 3826ecdef (run 20260717T101835Z-seed-testmon-2104057-f1279475; 15,908 passed, 1 skipped; pytest 270.73s; no signals/process survivors). Together with the 194a4597 seed, this meets AC 4; PR #2995 plus focused 21/21 xdist proof meet AC 1-3.","status":"closed","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-17T09:20:59Z","created_by":"Sinity","updated_at":"2026-07-17T10:24:58Z","started_at":"2026-07-17T09:21:01Z","closed_at":"2026-07-17T10:24:58Z","close_reason":"Bounded default synthetic generation shipped in #2995; focused xdist proof passed 21/21 and two independent fresh 8-worker seeds passed at 194a4597 and 3826ecdef.","labels":["agent-readiness","area:architecture","area:beads","horizon:frontier","invariant","verification"],"dependencies":[{"issue_id":"polylogue-b054.1.1.7","depends_on_id":"polylogue-b054.1.1","type":"parent-child","created_at":"2026-07-17T11:20:58Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -243,7 +280,7 @@ {"_type":"issue","id":"polylogue-2qx.1","title":"Land the OriginSpec kernel and migrate the current origin vocabulary","description":"OriginSpec is the correct class-level source-admission mechanism, but one feature currently combines the declaration kernel, derivation/conformance machinery, migration of every current origin, and prerequisites for all future adapters. Preserve the full contract while separating the reusable admission kernel from current-origin adoption so a new origin does not wait for unrelated migration residuals.","design":"Slice 2qx.1.1 defines the typed OriginSpec/registry contract, derivations, deterministic detector ordering, fixture/conformance law, and proves it on representative executable and reserved origins. Slice 2qx.1.2 migrates every current Origin token and deletes/parity-checks parallel inventories without changing provider-specific parser behavior. Provider-specific semantic expansions such as Claude orchestration and Codex child calls follow current-origin migration. Future origin/export/federation adapters consume only the proven kernel plus their own fixture/authority requirements.","acceptance_criteria":"1. 2qx.1.1 provides one executable admission/conformance kernel with representative production proof and actionable missing-edge diagnostics. 2. 2qx.1.2 covers every current Origin token exactly once and derives or parity-checks dispatch, public vocabulary, coverage, docs, and fixtures. 3. Existing ambiguous-detector, identity, parsing, and public-filter behavior remains equivalent through migration. 4. Future origins depend only on the kernel; current Claude/Codex semantic extensions depend on completed current-origin adoption. 5. No second admission registry, detector-order list, or origin coverage vocabulary remains after the migration slice.","notes":"2026-07-16 GPT-Pro corpus adjudication: OriginSpec package cdc06754e7ce remains blocked on polylogue-o21.1. Its retained constraint is to consume the DeclarationSpec kernel rather than inventing a second origin registry; no stale patch was applied.","status":"closed","priority":1,"issue_type":"epic","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T17:43:10Z","created_by":"Sinity","updated_at":"2026-07-27T02:05:50Z","closed_at":"2026-07-27T02:05:50Z","close_reason":"Satisfied: both children closed - 2qx.1.1 (kernel, polylogue/sources/origin_specs.py, commit 04f5bd65c, PR #3246) and 2qx.1.2 (all 11 Origin tokens migrated, parallel _ORIGIN_DESCRIPTIONS inventory deleted, commits 34666259a/263c9a2ef, PR #3250/#3252). Epic itself had no close_reason recorded despite both dependencies being done. Re-verified 2026-07-27 via independent triage.","labels":["area:sources","area:substrate","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-2qx.1","depends_on_id":"polylogue-2qx","type":"parent-child","created_at":"2026-07-15T19:44:35Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-2qx.1","depends_on_id":"polylogue-hs3y","type":"relates-to","created_at":"2026-07-17T12:58:08Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-kwsb.2","title":"MutationTransaction: authorize and receipt every destructive operation","description":"Reset and excision now have compatible preview/--yes/MutationResultPayload behavior, but the contract is surface- and command-local. Other destructive CLI, MCP write/admin, HTTP, and Python operations can still invent target selection, authorization, idempotency, audit, partial-failure, and postflight semantics. This is a security boundary: a personal archive must not let one adapter bypass the same proof required by another. The missing abstraction is a shared transaction protocol, not a universal mutation executor.","design":"Define a typed MutationTransaction protocol with domain-owned PlanSpec and actuator. PREPARE resolves exact target refs and affected tiers/replicas against a snapshot vector, classifies reversibility and privacy impact, and returns a bounded plan plus plan hash without mutation. AUTHORIZE binds actor/role/capability, operation and target scope, plan hash, expiry, interactive or delegated confirmation, and policy version. APPLY uses an idempotency key, revalidates preconditions/plan hash, records per-target progress and domain receipts, and never upgrades partial/held/unknown to success. RECONCILE performs domain postflight and records residuals, rollback/undo availability, and replica status. Durable audit placement follows the affected authority tier; payloads redact secrets. CLI, MCP, HTTP, and Python are adapters over the protocol. Reset, excision, delete/retract/suppress, and future destructive maintenance retain separate actuators and plans; archive write effects and MaintenanceOutcome consume receipts but do not own authorization.","acceptance_criteria":"1. A census classifies every destructive public operation and adapter; each routes through MutationTransaction or has a reviewed typed exemption naming why it cannot mutate durable/user evidence. 2. Preview/prepare performs zero mutation and returns exact target refs, affected tiers/replicas, reversibility, privacy impact, snapshot/preconditions, plan hash, and expiry. 3. Apply requires a matching fresh authorization receipt, revalidates the plan, is idempotent, and records per-target applied/already-satisfied/blocked/failed/unknown plus domain receipt refs; TOCTOU or scope drift returns replan-required. 4. CLI, MCP, HTTP, and Python parity fixtures for reset and excision produce the same plan/authorization/outcome semantics and role denials; no surface bypasses confirmation/capability. 5. Crash/timeout and partial multi-target failure resume or reconcile without duplicate effects or false success; irreversible and replica-held states are explicit. 6. Audit records contain actor, authority, policy, targets, hashes, outcome/residual refs, and timestamps without storing excised secrets. 7. Mutation tests fail when preview writes, authorization is omitted/replayed out of scope, plan drift is ignored, or an adapter invokes an actuator directly.","notes":"Portfolio audit 2026-07-15: extracted from kwsb residual after jnj.5 and 27m independently landed compatible command-local mutation envelopes. This shares protocol and receipts only; it deliberately does not unify reset/excision/domain actuators, archive write effects, or maintenance result semantics.\nPriority correction 2026-07-15: promoted and admitted because cross-surface destructive authorization is a security boundary; command-local preview envelopes do not prevent MCP/HTTP/Python bypass.\n2026-07-16 GPT-Pro corpus adjudication: early destructive-operation package 542278830b90 is superseded by later MutationTransaction package 76a1279fe519. The later package is preserved as current design input, but no stale patch was merged: master needs a current route census proving MCP, HTTP, Python and every destructive actuator pass one domain-owned preview/authorize/apply/reconcile authority. Terminal package status is blocked_but_seeded here; do not claim completion from patch-level tests.\n2026-07-21 phase-1 receipt (PR #3249, merged b17bd4932): MutationTransaction protocol implemented as OperationExecutor lifecycle (one architecture with t46.9 — spec declares, transaction executes); AC1 census shipped as checked docs/plans/mutation-census.yaml (executor-routed / declared-not-routed / typed-exemption with reasons); preview-zero-mutation + plan-hash-refusal + confirmation-strength tests green incl. real seeded-archive staleness refusal. REMAINING: phase-2 route migration per census, bound_token cross-request flow, durable audit rows, crash/partial-failure semantics.\n2026-07-21 phase-2 receipt: see polylogue-t46.9 note of same date (PR #3253) — reversible tag/metadata/mark families now authorize+receipt through MutationTransaction; destructive file-tier resets and bound_token strength remain (phase 3).\n2026-07-27: phase 5 (learning-corrections family: record_correction/delete_correction/clear_corrections) migrated to executor-routed via PR #3294. Remaining declared-not-routed families per docs/plans/mutation-census.yaml: capture_assertion_candidate/blackboard_post, import_annotation_batch, maintenance_execute family, file-tier ops reset family (design question re: target-ref vocabulary, flagged not resolved).\n2026-07-28: same migration as t46.9 - phase 6 (blackboard_post family) landed via PR #3376 (open, not yet merged). See t46.9 notes 2026-07-28 for the full remaining-family breakdown (capture_assertion_candidate, import_annotation_batch, maintenance rebuild/update-index family, ops reset file-tier deletions, bound_token strength, durable audit rows, partial-failure resume) and the design-call flags on import_annotation_batch/maintenance/file-tier-reset (each may resolve to a typed-exemption rather than an executor route, not decided this session).\n[Verification sweep 2026-07-31, bead-landing-check group5] Verdict: LIVE. Ongoing phased migration (phase 1 PR #3249, phase 2 PR #3253, phase 5 PR #3294, phase 6 PR #3376 open-not-merged); 2026-07-28 note lists explicit remaining families (capture_assertion_candidate, import_annotation_batch, maintenance family, file-tier ops reset, bound_token strength, durable audit rows, partial-failure resume).","status":"open","priority":1,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T17:00:06Z","created_by":"Sinity","updated_at":"2026-07-31T05:52:28Z","metadata":{"frontier":"active","frontier_program_ref":"polylogue-kwsb"},"labels":["area:security","area:substrate","delivery:A-trust-floor","horizon:frontier","lane:security-privacy","spine"],"dependencies":[{"issue_id":"polylogue-kwsb.2","depends_on_id":"polylogue-kwsb","type":"parent-child","created_at":"2026-07-15T19:00:05Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-j9dt","title":"continue --format json removed by #2827 but still documented in 2 QueryActionWorkflow entries","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T13:22:28Z","created_by":"Sinity","updated_at":"2026-07-15T16:44:12Z","closed_at":"2026-07-15T16:44:12Z","close_reason":"Superseded by o21 declaration/consumer completeness. The exact removed continue --format json workflow examples are retained as an executable seeded regression; product resolution must derive from the live CLI declaration rather than a separate stale workflow vocabulary.","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-f2qv.6","title":"Reconcile profiles and costs to exact provider usage","description":"One live Codex session has three incompatible answers: exact model usage reports 64,561 uncached input, 723,456 cache read, and 7,776 output; session_profiles reports a 4,031-token estimate; cost insight reports zero and unavailable. Across all 2,856 Codex sessions with nonzero reported lanes, zero profiles matched. Profiles are built before provider usage and both are stamped current.\n\n## Steps to Reproduce\n1. Select a Codex session with a final provider cumulative usage event.\n2. Compare session_model_usage, session_profiles, and the per-session cost insight.\n3. Observe three incompatible lane sets with the same materialization freshness; repeat the model-versus-profile comparison across Codex sessions with nonzero exact lanes.","design":"Create one canonical per-session usage snapshot with disjoint token-lane authority separate from monetary price authority. Prefer exact provider events and model rollups; use estimates only as labeled fallback. Represent exact tokens with unknown USD, unavailable pricing, estimated money, and measured zero through the cuxz.2 EvidenceValue axes rather than numeric sentinels or a usage-local confidence vocabulary. Reconcile event, rollup, profile, cost, and public surfaces to this snapshot; record contradiction debt and materialize in dependency order.","acceptance_criteria":"Exact event through rollup, snapshot, profile, and cost agree on every lane; exact tokens with no price remain exact tokens plus unknown or estimated money; estimate-only providers stay explicit; rebuild and incremental convergence agree; live Codex census has zero unexplained profile contradictions with price unknowns separate; restoring profile-before-provider order fails; focused usage, profile, cost, and convergence tests pass.","notes":"2026-07-27 first slice: PR #3299 (feature/fix/session-usage-cost-reconciliation-slice) adds build_session_usage_reconciliation() / SessionUsageReconciliation to polylogue/storage/usage.py -- a pure reconciliation function over already-loaded session_model_usage rows, session_profiles token/cost columns, and the cost-insight fields, using two new session-grain FactFamilySpecs (SESSION_USAGE_RECONCILED_TOKENS_FAMILY, SESSION_USAGE_RECONCILED_COST_FAMILY) and the cuxz.2 refine_evidence_value primitive to pick the strongest-authority value on disagreement (provider-reported session_model_usage over a structural/model-derived session_profiles estimate; a fresh catalog reprice over a legacy persisted cost), while preserving every superseded input as a labeled contribution rather than discarding it. Test tests/unit/storage/test_session_usage_reconciliation.py reproduces the bead's exact reported numbers (64,561 uncached input + 723,456 cache read + 7,776 output vs a 4,031-token estimate vs zero/unavailable cost) and proves the reconciled snapshot picks the exact rollup, not an average, and surfaces the estimate as superseded.\n\nHonest scope: this is ONE case, not the full bead. Explicitly NOT done:\n- No storage/insight wiring -- nothing in storage/insights/session/rebuild.py, storage/sqlite/archive_tiers/archive.py (_session_cost_insight_from_archive_row still reads session_profiles directly), or insights/registry.py calls this function. session_model_usage, session_profiles, and the cost insight still disagree in the live archive today; this PR does not change any read path.\n- No daemon convergence integration or contradiction-debt recording.\n- No corpus-wide census proving \"zero unexplained profile contradictions\" (AC 5) -- that requires wiring plus a live-archive audit, deferred.\n- No \"restoring profile-before-provider order fails\" regression test -- that is a materialization-ordering test against the wired path, which doesn't exist yet.\n- Broader EvidenceValue family/surface migration remains polylogue-cuxz.3 scope, unaffected by this PR.\n\nRemaining work for this bead: wire build_session_usage_reconciliation (or its successor) into the actual session-insight rebuild/cost-insight read paths so live sessions produce the reconciled snapshot instead of three independent reads; add the corpus-wide census/contradiction-debt recording; add the profile-before-provider-order regression test; decide whether this becomes a materialized/insight-registry entry (per the bead's own design note) rather than a pure function callers must invoke manually.\n2026-07-27: first slice (per-session token/cost reconciliation for one disagreement case) merged via PR #3299. Self-review before merge (CodeRabbit rate-limited) found and fixed a real cost-mispricing bug: the reconciled token total collapsed input/output/cache_read/cache_write into one combined int, then priced the whole thing as pure input tokens - a ~4x cost overstatement on the bead's own repro case ($0.99 vs correct $0.25), since cache-read tokens (723K of 795K total) got priced at full input rate instead of their real discounted rate. Fixed by threading the winning source's real per-category breakdown through to estimate_cost. Remaining scope per the PR's own honest accounting (~15-20% of full AC): storage/insight wiring so live sessions actually surface reconciled values, daemon convergence/contradiction-debt integration, corpus-wide zero-unexplained-contradictions census, cuxz.3's broader family migration.\nMATERIALIZATION GAP FOUND 2026-07-29, upstream of any pricing-model work.\n\nsession_profiles, full scan of all 18,871 rows:\n cost_usd 100% NULL\n cost_credits 100% NULL\n priced_with 100% NULL\n priced_at_ms 100% NULL\n\nMeanwhile session_model_usage holds 18,618 rows WITH cost_usd populated. The\nprofile materializer never joins cost the archive already has, so cost-per-\nsession on the profile surface is structurally absent -- not wrong, empty.\n\nFix the join before reconciling the pricing model; reconciliation against an\nempty column proves nothing. Also 100% NULL on every profile row: duration_ms,\ntags_json, workflow_shape_method, terminal_state_method.\n\nRelated and already noted on this bead's cluster: the cost PROVENANCE vocabulary\n(api_billed, api_equivalent, subscription_equivalent, subscription_unconfigured,\nprovider_zero, tool_surcharge, configured_manual, tokenizer_estimated and 5\nmore) is fully declared in archive/semantic/pricing.py + cost_records.py and\nproduced by nothing. The design for honest cost attribution is already written;\nit is unwired at both ends.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own latest note (2026-07-29, 2 days before this check): 'MATERIALIZATION GAP FOUND... upstream of any pricing-model work' -- session_profiles cost columns 100% NULL across all 18,871 rows; profile materializer never joins cost data that already exists elsewhere. Explicitly unfixed.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T04:23:59Z","created_by":"Sinity","updated_at":"2026-07-31T05:47:33Z","labels":["area:analytics","area:insights","delivery:A-trust-floor","horizon:frontier","lane:security-privacy","spine"],"dependencies":[{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-cuxz","type":"relates-to","created_at":"2026-07-15T20:17:31Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-cuxz.2","type":"blocks","created_at":"2026-07-15T20:50:03Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-f2qv","type":"parent-child","created_at":"2026-07-15T06:23:58Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-f2qv.5","type":"relates-to","created_at":"2026-07-15T06:25:50Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} +{"_type":"issue","id":"polylogue-f2qv.6","title":"Reconcile profiles and costs to exact provider usage","description":"One live Codex session has three incompatible answers: exact model usage reports 64,561 uncached input, 723,456 cache read, and 7,776 output; session_profiles reports a 4,031-token estimate; cost insight reports zero and unavailable. Across all 2,856 Codex sessions with nonzero reported lanes, zero profiles matched. Profiles are built before provider usage and both are stamped current.\n\n## Steps to Reproduce\n1. Select a Codex session with a final provider cumulative usage event.\n2. Compare session_model_usage, session_profiles, and the per-session cost insight.\n3. Observe three incompatible lane sets with the same materialization freshness; repeat the model-versus-profile comparison across Codex sessions with nonzero exact lanes.","design":"Create one canonical per-session usage snapshot with disjoint token-lane authority separate from monetary price authority. Prefer exact provider events and model rollups; use estimates only as labeled fallback. Represent exact tokens with unknown USD, unavailable pricing, estimated money, and measured zero through the cuxz.2 EvidenceValue axes rather than numeric sentinels or a usage-local confidence vocabulary. Reconcile event, rollup, profile, cost, and public surfaces to this snapshot; record contradiction debt and materialize in dependency order.","acceptance_criteria":"Exact event through rollup, snapshot, profile, and cost agree on every lane; exact tokens with no price remain exact tokens plus unknown or estimated money; estimate-only providers stay explicit; rebuild and incremental convergence agree; live Codex census has zero unexplained profile contradictions with price unknowns separate; restoring profile-before-provider order fails; focused usage, profile, cost, and convergence tests pass.","notes":"2026-07-27 first slice: PR #3299 (feature/fix/session-usage-cost-reconciliation-slice) adds build_session_usage_reconciliation() / SessionUsageReconciliation to polylogue/storage/usage.py -- a pure reconciliation function over already-loaded session_model_usage rows, session_profiles token/cost columns, and the cost-insight fields, using two new session-grain FactFamilySpecs (SESSION_USAGE_RECONCILED_TOKENS_FAMILY, SESSION_USAGE_RECONCILED_COST_FAMILY) and the cuxz.2 refine_evidence_value primitive to pick the strongest-authority value on disagreement (provider-reported session_model_usage over a structural/model-derived session_profiles estimate; a fresh catalog reprice over a legacy persisted cost), while preserving every superseded input as a labeled contribution rather than discarding it. Test tests/unit/storage/test_session_usage_reconciliation.py reproduces the bead's exact reported numbers (64,561 uncached input + 723,456 cache read + 7,776 output vs a 4,031-token estimate vs zero/unavailable cost) and proves the reconciled snapshot picks the exact rollup, not an average, and surfaces the estimate as superseded.\n\nHonest scope: this is ONE case, not the full bead. Explicitly NOT done:\n- No storage/insight wiring -- nothing in storage/insights/session/rebuild.py, storage/sqlite/archive_tiers/archive.py (_session_cost_insight_from_archive_row still reads session_profiles directly), or insights/registry.py calls this function. session_model_usage, session_profiles, and the cost insight still disagree in the live archive today; this PR does not change any read path.\n- No daemon convergence integration or contradiction-debt recording.\n- No corpus-wide census proving \"zero unexplained profile contradictions\" (AC 5) -- that requires wiring plus a live-archive audit, deferred.\n- No \"restoring profile-before-provider order fails\" regression test -- that is a materialization-ordering test against the wired path, which doesn't exist yet.\n- Broader EvidenceValue family/surface migration remains polylogue-cuxz.3 scope, unaffected by this PR.\n\nRemaining work for this bead: wire build_session_usage_reconciliation (or its successor) into the actual session-insight rebuild/cost-insight read paths so live sessions produce the reconciled snapshot instead of three independent reads; add the corpus-wide census/contradiction-debt recording; add the profile-before-provider-order regression test; decide whether this becomes a materialized/insight-registry entry (per the bead's own design note) rather than a pure function callers must invoke manually.\n2026-07-27: first slice (per-session token/cost reconciliation for one disagreement case) merged via PR #3299. Self-review before merge (CodeRabbit rate-limited) found and fixed a real cost-mispricing bug: the reconciled token total collapsed input/output/cache_read/cache_write into one combined int, then priced the whole thing as pure input tokens - a ~4x cost overstatement on the bead's own repro case ($0.99 vs correct $0.25), since cache-read tokens (723K of 795K total) got priced at full input rate instead of their real discounted rate. Fixed by threading the winning source's real per-category breakdown through to estimate_cost. Remaining scope per the PR's own honest accounting (~15-20% of full AC): storage/insight wiring so live sessions actually surface reconciled values, daemon convergence/contradiction-debt integration, corpus-wide zero-unexplained-contradictions census, cuxz.3's broader family migration.\nMATERIALIZATION GAP FOUND 2026-07-29, upstream of any pricing-model work.\n\nsession_profiles, full scan of all 18,871 rows:\n cost_usd 100% NULL\n cost_credits 100% NULL\n priced_with 100% NULL\n priced_at_ms 100% NULL\n\nMeanwhile session_model_usage holds 18,618 rows WITH cost_usd populated. The\nprofile materializer never joins cost the archive already has, so cost-per-\nsession on the profile surface is structurally absent -- not wrong, empty.\n\nFix the join before reconciling the pricing model; reconciliation against an\nempty column proves nothing. Also 100% NULL on every profile row: duration_ms,\ntags_json, workflow_shape_method, terminal_state_method.\n\nRelated and already noted on this bead's cluster: the cost PROVENANCE vocabulary\n(api_billed, api_equivalent, subscription_equivalent, subscription_unconfigured,\nprovider_zero, tool_surcharge, configured_manual, tokenizer_estimated and 5\nmore) is fully declared in archive/semantic/pricing.py + cost_records.py and\nproduced by nothing. The design for honest cost attribution is already written;\nit is unwired at both ends.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own latest note (2026-07-29, 2 days before this check): 'MATERIALIZATION GAP FOUND... upstream of any pricing-model work' -- session_profiles cost columns 100% NULL across all 18,871 rows; profile materializer never joins cost data that already exists elsewhere. Explicitly unfixed.\n2026-07-31 group3 sweep (agent-af085793b115e79d5): root-caused and fixed the specific \"cost columns 100% NULL\" scope of this bead (session_profiles.cost_usd/cost_credits/priced_with/priced_at_ms), distinct from the broader profile/cost reconciliation program this bead's parent notes track.\n\nRoot cause: upsert_session_profile_costs (storage/sqlite/archive_tiers/write.py) is the only writer ever declared for these 4 columns and had ZERO production callers -- grepped the whole repo, confirmed. Not \"computed and dropped\": never computed for session_profiles at all. _SESSION_PROFILE_BASE_COLUMNS/session_profile_insert_values (storage/insights/session/storage.py), the actual INSERT the materializer uses, never referenced these 4 column names, so every row left them at their SQLite column default (NULL, no NOT NULL/DEFAULT clause).\n\nFix (PR pending, branch fix/cost-fts-null-bugs, commit 9914f28b8): wired the real materialization pipeline -- SessionProfile domain model gains nullable cost_usd/cost_credits/priced_with fields; build_session_profile (archive/session/runtime.py) computes them from the same cost_summary already used for total_cost_usd/total_credit_cost, gated on the model actually being in the PRICING catalog (mirrors write.py's session_model_usage \"no fabrication\" contract: NULL when no model was ever catalog-priced, not a fake $0.00); SessionProfileRecord gains the same fields + priced_at_ms; build_session_profile_record and the SQL column lists thread them through. upsert_session_profile_costs is left in place -- 4 test files use it as a seeding helper for unrelated tests, it's harmless dead weight now, not part of this fix.\n\nVerified with a new test (tests/unit/storage/test_session_profile_cost_columns.py, 3 tests) exercising the real production pipeline (write_parsed_session_to_archive + rebuild_session_insights_sync): catalog-priced model populates all 4 columns and cost_usd == total_cost_usd (same source); unpriced model leaves all 4 NULL; priced_at_ms advances on rebuild. mypy --strict clean on every touched file.\n\nScope note: this closes the \"cost columns 100% NULL\" symptom this specific bead names. It does NOT touch archive.py's _session_cost_insight_from_archive_row (out of this session's AVOID list) which reads sp.cost_usd/cost_provenance and checks `cost_provenance == \"exact\"` -- SessionCostSummary never actually produces that literal string (\"provider_reported\"/\"mixed\" instead), so the cost-insight status-labeling bug from this bead's ORIGINAL description (\"cost insight reports zero and unavailable\") may still need a read-path fix in archive.py by whichever lane owns it. That is now unblocked (cost_usd is no longer structurally NULL) but is a separate remaining slice.","status":"open","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T04:23:59Z","created_by":"Sinity","updated_at":"2026-07-31T09:08:32Z","labels":["area:analytics","area:insights","delivery:A-trust-floor","horizon:frontier","lane:security-privacy","spine"],"dependencies":[{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-cuxz","type":"relates-to","created_at":"2026-07-15T20:17:31Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-cuxz.2","type":"blocks","created_at":"2026-07-15T20:50:03Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-f2qv","type":"parent-child","created_at":"2026-07-15T06:23:58Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f2qv.6","depends_on_id":"polylogue-f2qv.5","type":"relates-to","created_at":"2026-07-15T06:25:50Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-37t.23","title":"Derive session resumability from open obligations, not termination text","description":"A live Codex session ended normally with an explicit unresolved deployment decision, yet its profile says clean_finish, blocker extraction is suppressed, and resume discovery excludes or zero-weights it. Among 500 recent sessions, 226 were clean finishes and at least two were manually confirmed clean-but-unfinished; keyword markers also yielded false positives. Process termination and objective posture are orthogonal: the archive needs one session-level resumability projection over authority-bearing open obligations, not a second completion truth inferred from the final message.","design":"Define ObjectivePosture as a derived projection, separate from terminal process state. Apply an explicit authority order: declared goal/question open-close-block events when available; provider/work-evidence graph claims, structured results, observed effects, and evaluated satisfaction; durable decision/blocker/handoff assertions; then bounded authored-request/structural inference; otherwise unknown. Preserve evidence refs, as-of frame, authority, contradictions, and multiple simultaneous obligations. Profiles, blocker extraction, resume ranking, and context compilation consume this one projection. The work-evidence graph and goal graph remain fact owners; this bead neither duplicates their storage nor equates a claim with completion. Keep routing in 37t.8 and descriptive proof in 212.6.","acceptance_criteria":"1. A normal final answer with an unresolved decision has terminal_state=clean_finish and objective_posture=awaiting_operator simultaneously; resume discovery includes it for the repository. 2. Completed, blocked, abandoned/inactive, awaiting_operator, awaiting_effect, and ambiguous/unknown cases preserve typed obligation/evidence refs, authority, as-of frame, and contradictions. 3. Explicit goal/work-effect evidence outranks weaker inference; a self-reported claim without observed/evaluated effect cannot become completed. 4. Protocol-only messages, final-assistant presence, and keyword matches cannot decide posture alone; removing the authority precedence recreates the known false completion/false positive. 5. Profiles, blocker extraction, ranking, and context all consume the same projection with no parallel terminal-state completion heuristic. 6. A labeled live sample records precision/coverage and the known anchor; focused profile/enrichment/ranking/context tests pass.","notes":"2026-07-15 invariant formulation: session posture is now explicitly a projection over 1vpm.6 work evidence and 7yk5 goal/question state when available, with assertions/inference as lower-authority fallbacks. Those graphs remain distinct fact lifecycles; this leaf owns the one resumability projection consumed by profiles, blockers, ranking, and context.\n2026-07-16 GPT-Pro corpus adjudication: objective-posture package 0d45bbbc8ddb remains blocked/seeded. Retained design: derive resumability from authoritative open obligations, not terminal prose; reconcile against current insight/storage authorities before any large patch.","status":"closed","priority":1,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T04:23:56Z","created_by":"Sinity","updated_at":"2026-07-20T20:04:07Z","closed_at":"2026-07-20T20:04:07Z","close_reason":"Delivered in PR #3226 (squash 4799d24e1): objective_posture projection with explicit authority order (goal_graph \u003e work_evidence \u003e assertion \u003e structural_inference \u003e none); structural tier baked into session_profiles materialization (index.db-only, never emits completed), assertion tier as read-time overlay (decision/blocker/handoff outrank structural inference; contradictions surfaced not collapsed); recomputed onto reconciled terminal_state at both read sites; consumers rewired (blocker extraction gates on shared mapping replacing the unknown-missing allowlist, resume ranking posture-weighted + dead clean_finish filter replaced post-#2960, resume_brief overlays assertion tier, context preamble surfaces posture). AC1 reframed honestly (clean_finish deleted by #2960). AC6 (labeled live-sample precision run) deferred — needs live archive; assertion overlay is per-physical-session, lineage composition and goal_graph/work_evidence tiers reserved for 7yk5/1vpm.6. Verification: 381 focused + 601 sweep tests green, 3 sweep failures proven pre-existing on pristine master.","labels":["area:context","area:insights","delivery:D-agent-context-coordination","horizon:frontier","lane:agent-coordination"],"dependencies":[{"issue_id":"polylogue-37t.23","depends_on_id":"polylogue-1vpm.6","type":"relates-to","created_at":"2026-07-15T20:29:53Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-37t.23","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-15T06:23:55Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-37t.23","depends_on_id":"polylogue-7yk5","type":"relates-to","created_at":"2026-07-15T20:29:53Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-j2zz","title":"Lower Codex orchestration child calls into typed actions","description":"Modern Codex embeds typed operations inside functions.exec JavaScript. In the newest 100-session sample, every session had nested tools calls, 14,004 envelopes held child operations, and 19,180 results yielded zero structured paths or outcomes although 1,444 texts contained exit_code. Polylogue retains only outer exec or shell semantics.\n\n## Steps to Reproduce\n1. Ingest a current Codex session containing functions.exec with nested exec_command and apply_patch calls.\n2. Query its actions and files through Polylogue.\n3. Compare with raw JSONL and observe only outer exec or shell actions, zero normalized file paths, and unknown structural outcomes.","design":"Lower functions.exec into provenance-linked child actions while retaining the outer call as transport. Use a typed registry for exec_command, apply_patch, write_stdin, update_plan, wait, web, image, MCP, and unknown shapes. Promote only structural result fields, preserve ordering and repeated calls, and feed the bounded relation owned by polylogue-z9gh.2.","acceptance_criteria":"Fixtures lower single and multiple children into ordered typed actions linked to transport; commands and patches expose normalized commands and paths; outcome fields are structural or unknown; malformed and unknown tools retain evidence; repeated calls and continuations pair deterministically without inventing recovery; live sample reports child/path/outcome coverage; removing lowering recreates zero-file outer-only results; parser/action tests and quick gate pass.","notes":"Portfolio placement 2026-07-15: execution slice and live canary of OriginSpec normalized-construct lowering and positive outcome/path provenance. The outer transport and child actions also feed 1vpm.6, but source authority stays with OriginSpec.","status":"closed","priority":1,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T04:23:52Z","created_by":"Sinity","updated_at":"2026-07-27T02:05:51Z","closed_at":"2026-07-27T02:05:51Z","close_reason":"Satisfied: Codex functions.exec child lowering into typed actions (exec_command, apply_patch, write_stdin, update_plan, wait, web, image, mcp, unknown registry with path/outcome promotion and ordering) landed via commit 46e478fb1, PR #3063. Regression coverage green: tests/unit/devtools/test_codex_exec_child_census.py + tests/unit/sources/test_codex_event_stream_contract.py (31 tests). No later commit reverted this logic - live on master unchanged since #3063. Bead was stale (open, no close_reason). Re-verified 2026-07-27 via independent triage.","labels":["area:query","area:sources","delivery:C-read-evidence-contract","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-j2zz","depends_on_id":"polylogue-2qx","type":"parent-child","created_at":"2026-07-15T18:38:54Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-j2zz","depends_on_id":"polylogue-2qx.1.2","type":"blocks","created_at":"2026-07-15T20:55:26Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-j2zz","depends_on_id":"polylogue-9l5.6","type":"relates-to","created_at":"2026-07-15T06:25:47Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-j2zz","depends_on_id":"polylogue-z9gh.2","type":"relates-to","created_at":"2026-07-15T06:25:43Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-ih67","title":"Enrich Codex titles from authored history in canonical ingest","description":"All 3,101 indexed Codex sessions in the live archive use native UUID as title. The canonical raw-record daemon worker bypasses provider assembly. Live Codex supplies history.jsonl, not the expected optional sidecar, and the current role=user fallback would select injected AGENTS context before the human_authored request.\n\n## Steps to Reproduce\n1. Count Codex sessions where title equals native_id in the live index.\n2. Inspect a modern Codex session whose first user-role row is runtime context and whose later row is human_authored.\n3. Follow canonical raw-record daemon ingest and observe that it calls parser entrypoints without provider assembly or history enrichment.","design":"Extend the Codex OriginSpec assembly declaration from 2qx.1.2 and make that assembly run in canonical raw-record ingest, not only direct path ingest. In polylogue/sources/assembly_codex.py, discover both session_index.jsonl thread names and the live Codex history.jsonl source with append-only newest-wins/dedup/freshness rules keyed by session/thread identity; represent sidecar identity and authority in typed assembly data rather than reading ambient files inside a parser. Resolve title in this order: non-empty provider thread name, matching authoritative history title/prompt, first message whose material_origin is human_authored, then native UUID/unknown. Never select role=user alone because runtime_context and operator protocol rows use that role. In polylogue/pipeline/services/ingest_worker.py and its parse-plan construction, pass acquired sidecar/assembly inputs through the subprocess-safe raw-record plan and call the same get_assembly_spec enrichment used by direct ingest before materialization/hash/write. Persist TitleSource plus a more specific provenance/ref/confidence field in the next appropriate batched index/source model change; title provenance must not alter session identity. Reprocess affected Codex raws through ordinary semantic reparse/rematerialization, preserving assertions and links, and emit before/after coverage counts. Keep generic display synthesis in polylogue-30h separate. Primary tests: sources assembly Codex, parsers Codex authoredness, pipeline ingest_worker/raw batch parity, storage title provenance, and a corpus-shaped UUID-title canary.","acceptance_criteria":"1. Direct source ingest and canonical raw-record daemon ingest invoke the same Codex assembly and produce identical title, TitleSource, specific provenance/ref/confidence, content hash consequences, and diagnostics for the same acquired inputs. Bypassing assembly makes the daemon parity test fail. 2. Resolution order is provider thread name, matched authoritative history entry, first human_authored message, UUID/unknown. A runtime_context or operator command in an earlier role=user row never becomes the title. 3. session_index.jsonl and history.jsonl duplicate, malformed, missing, stale, equal-timestamp, and conflicting rows have deterministic newest-wins or explicit ambiguous outcomes; ambient file changes cannot silently alter a previously acquired replay. 4. Sidecars are acquired/referenced as raw authority evidence and passed through subprocess-safe parse plans; parsers do not open live home-directory sidecars during replay. 5. Title provenance is persisted and queryable, while rematerialization preserves session_id, message/block identity where content is unchanged, lineage, assertions, and user state. Semantic hash/reparse behavior for an improved title is explicit and idempotent. 6. A live-scale privacy-safe census records UUID-title coverage before/after, improves all deterministically enrichable sessions, and leaves unresolved reasons classified rather than claiming 100 percent. 7. Focused assembly, Codex authoredness, direct-vs-daemon parity, raw replay, storage provenance, reprocess, and projection tests plus affected verification pass.","notes":"Portfolio placement 2026-07-15: execution slice and live canary of OriginSpec artifact inventory, canonical assembly, authoredness authority, title provenance, and semantic reparse. It is not an independent source-admission mechanism.\nPriority correction 2026-07-15: promoted and admitted because every indexed Codex session currently having a UUID title is a corpus-wide discovery failure tied to authoredness and source-admission authority.\nTerra-readiness correction 2026-07-15: named the current assembly and raw-worker bypass, fixed title precedence on material_origin rather than role, required acquired sidecar authority instead of ambient replay reads, and specified identity-preserving reprocessing plus a corpus canary.\n\n[2026-07-18] Named as a blocker (D6) in the ann-03-batch-runbook-r01 mass-annotation prioritization decision (full ranking recorded on polylogue-rxdo): title/topic quality is treated as an ingest/authority defect owned by this bead, not an annotation target -- labels cannot repair a route that never produced the intended title. Only a small post-fix canary annotation is recommended, and only after this bead lands. This bead therefore gates campaign D6 (title-source coverage, UUID residuals, generated-title acceptance, retrieval quality/lift) in the annotation launch order.\nWarroom It.18 (2026-07-18): first slice landed via PR #3071 -- canonical raw-record ingest now runs get_assembly_spec enrichment (keyed off recorded acquisition path; blob/foreign-machine replays degrade to parsed-content fallbacks); assembly_codex gains history.jsonl earliest-authored-entry titles with stat-fingerprint caching; resolution order = thread name -\u003e authored history -\u003e first HUMAN_AUTHORED message -\u003e native id; role=user alone never titles. Parity tests with named mutation (bypass fails 3/3). VERIFIED LOCAL DATA: ~/.codex/state_5.sqlite threads.title 2757/3040 non-empty; history.jsonl 17762 entries. REMAINING SCOPE on this bead: (a) sidecars acquired as raw authority evidence + subprocess-safe plans (AC#3/4 -- ambient reads still possible when source_path exists at reprocess time); (b) persisted TitleSource provenance/ref/confidence in a batched model change (AC#5 partial: title_source flows in parsed model only); (c) state_5.sqlite threads.title as an additional discovery source (richer than session_index.jsonl on live installs -- copy-first, it is live-locked); (d) reprocess affected Codex raws + before/after UUID-title census (AC#6).\n2026-07-26 portfolio-convergence audit: released stale in_progress claim after \u003e7 days with no recorded activity; scope remains open and must be re-claimed on real work start.\n2026-07-27: state_5.sqlite threads.title added as a discovery source, merged via PR #3292. Precedence: thread name -\u003e authored history -\u003e state_5.sqlite title -\u003e first human-authored message -\u003e native id. Remaining scope per prior notes: sidecar acquisition as raw authority evidence, persisted TitleSource/ref/confidence provenance columns, corpus-wide before/after census - not attempted this pass.\n2026-07-27 PR #3360: title_source persisted-but-unqueryable gap fixed. ArchiveStore.read_summary/list_summaries now SELECT s.title_source (ArchiveSessionSummary gained the field); archive/query/archive_execution.py + api/archive.py's duplicate _session_to_session/_summary_to_domain helpers now map title_source onto Session/SessionSummary domain models; SessionListRowPayload/SessionSummaryPayload expose it; SESSION_COLUMNS updated to match. Anti-vacuity-verified new tests in tests/unit/storage/test_title_source_queryable.py. This closes the \"queryable\" half of AC#5 for the value that already existed (TitleSource enum on the row), not a new ref/confidence field.\nREMAINING SCOPE (unchanged from 2026-07-27 prior note, not attempted this PR): (a) sidecar acquisition as raw authority evidence + subprocess-safe parse plans (AC#3/4 -- ambient reads still possible when source_path exists at reprocess time); (b) a dedicated ref/confidence provenance field beyond the existing TitleSource value; (c) corpus-wide before/after UUID-title census (AC#6). ih67 stays open.\n2026-07-28 PR #3378 (branch feature/sources/codex-title-provenance-ih67, 4 commits): landed all three items named as \"not attempted this pass\" in the 2026-07-27 note.\n(a) AC#3/#4 sidecar freeze: _resolve_codex_sidecar_snapshots (ingest_batch/_core.py) runs in the main process before dispatch, persists each Codex raw record's first-observed sidecar snapshot in history_sidecars (source.db, previously-unwired write_history_sidecar + new read_earliest_history_sidecar_for_path), and carries it across the process-pool boundary via RawSessionRecord.sidecar_snapshot (exclude=True). _enrich_parsed_sessions (subprocess) uses the frozen snapshot when present and never touches disk. Anti-vacuity: reverting the lookup reproduces the exact ambient-drift bug and fails the new test. Residual: state_5.sqlite is covered by the freeze (it's part of the persisted snapshot dict) but is still read live at first-acquisition time rather than separately blob-hashed beforehand -- not a correctness gap for AC#3 (frozen thereafter), just a smaller scope than a dedicated blob per sidecar file.\n(b) AC#5 ref/confidence: new nullable sessions.title_ref/title_confidence columns (index.db v44, additive derived-tier DDL), stamped per-lane in assembly_codex.py (thread-name=1.0, history=0.9, state-db=0.75, message-fallback=0.5), wired through the full write-\u003estorage-summary/envelope-\u003edomain-model-\u003eCLI/MCP-payload chain exactly like #3360 did for title_source. Regenerated schemas/openapi/webui client.\n(c) AC#6 census: polylogue/archive/codex_title_census.py + `polylogue ops diagnostics codex-title-census [--json|--save|--compare]`. Privacy-safe (sessions-table columns only, no message text, no paths). Classifies unresolved reason: no_messages_materialized / no_human_authored_message / not_yet_reprocessed_with_assembly / human_authored_present_synthesis_failed. Live read-only smoke test against the real archive (no mutation): 3201 total Codex sessions, 0 resolved, 2977 not_yet_reprocessed_with_assembly, 207 no_human_authored_message, 17 no_messages_materialized -- confirms the live corpus has not had a reprocess pass since #3071 landed; this is the honest \"before\" baseline.\nVerification: devtools verify --quick exit 0; mypy/ruff clean; 19 focused tests pass across all three pieces; anti-vacuity (revert/confirm-fail/restore) done for all three.\nREMAINING SCOPE (not this PR, explicit): (1) actually triggering a live reprocess of the 2977 eligible-but-stale sessions to move the corpus to an \"after\" baseline -- mutates production data, needs a separate operator-authorized step (polylogue ops reprocess / polylogued run), out of this PR's read-only scope. (2) state_5.sqlite as a dedicated content-hashed blob rather than read-then-frozen-in-snapshot (residual noted above). (3) SESSION_COLUMNS (search projection example list) intentionally not extended with title_ref/title_confidence -- separate optional surface decision.\nPR: https://github.com/Sinity/polylogue/pull/3378\n\n2026-07-28 CORRECTION to this session's earlier deploy-risk framing: previously stated the schema bump (43-\u003e44) would cause the live daemon to 'report a schema mismatch' -- that UNDERSTATES the real severity. Confirmed via polylogue/storage/sqlite/schema_bootstrap.py: decide_schema_bootstrap()'s version_mismatch branch means the runtime REFUSES TO OPEN index.db entirely (not degraded status, not a soft readout) until an operator runs `polylogue ops reset --index \u0026\u0026 polylogued run`. Since master's history is linear, this commit is now an ancestor of every later commit landed today (1vpm.6.1 #3375, t46.9/kwsb.2 phase 6 #3376, 20d.17 #3377, t46.8.2 verification, t46.8.3 #3379, ovme.2 #3380, ovme.3 #3381) -- deploying ANY of them live now necessarily deploys this schema bump too and would break the running daemon until the rebuild is performed. Currently HELD BACK: sinnix flake.lock remains pinned at 2725fc3e2 (the last commit before this one), so the live daemon is unaffected and still fully functional. All subsequent real fixes are merged to master but NOT yet deployed live, pending an explicit operator decision to deploy+immediately rebuild-index together as one coordinated action.\nMEASURE CORRECTION 2026-07-28 (live index v43): the description says '3,101 indexed Codex sessions use native UUID as title'. Actual:\n\n SELECT count(*) FROM sessions WHERE origin='codex-session' AND title=native_id; -\u003e 3201\n SELECT count(*) FROM sessions WHERE origin='codex-session'; -\u003e 3201\n\nIt is 3,201, and it is 100% of the Codex population -- not a large subset. The v44 fixes are merged but undeployed, so the live archive still shows the full pre-fix state; this is the correct before-baseline for AC#6's before/after UUID-title census.\n\nDeploy status: the v44 schema bump landed in PR #3378 without its lifecycle.py delta declaration, which is why the repo CLI could not read the live v43 archive at all ('no such column: s.title_ref'). The declaration now exists (SEMANTIC_REPARSE, truthful under the current vocabulary); polylogue-9rw0.1 owns making this delta class cheap enough that title_ref does not require a full-corpus replay to populate.\nCROSS-ORIGIN NOTE 2026-07-29: the Claude Code side of the title problem is\nlarger (10,157 UUID-titled vs Codex's 3,201) and has a simpler source. Claude\nCode emits {\"type\":\"ai-title\",\"aiTitle\":\"...\"} -- 18,422 records in the\nlive corpus -- and the parser drops it. Codex needed a resolution ladder because\nno provider title existed; Claude Code needs the skip removed. Keep the ladder\nas the shared abstraction but do not assume Claude Code requires synthesis.\nVERDICT: PARTIAL — Confirmed extensive real implementation on master: TitleSource/title_ref/title_confidence fields (polylogue/sources/assembly_codex.py), sidecar-snapshot freeze (_resolve_codex_sidecar_snapshots in ingest_batch/_core.py), and the census tool (polylogue/archive/codex_title_census.py) all exist and match the bead's own 2026-07-27/28 notes (PRs #3071/#3292/#3360/#3378). BUT AC#6's before/after corpus census explicitly shows 'before' only: live smoke test found 0/3201 Codex sessions resolved (2977 not_yet_reprocessed_with_assembly) — the actual live reprocess to move the corpus to an after-baseline is an explicit operator-authorized live action not yet run. Status remains in_progress; not closable. — evidence: bd show polylogue-ih67 --json notes; grep -n title_ref polylogue/sources/assembly_codex.py; grep -n _resolve_codex_sidecar_snapshots polylogue/pipeline/services/ingest_batch/_core.py (all present).","status":"in_progress","priority":1,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-15T04:23:49Z","created_by":"Sinity","updated_at":"2026-07-31T05:48:05Z","started_at":"2026-07-18T00:05:17Z","metadata":{"frontier":"active","frontier_program_ref":"polylogue-z9gh"},"labels":["area:insights","area:sources","area:surface","delivery:C-read-evidence-contract","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-ih67","depends_on_id":"polylogue-2qx","type":"parent-child","created_at":"2026-07-15T18:38:54Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-ih67","depends_on_id":"polylogue-2qx.1.2","type":"blocks","created_at":"2026-07-15T20:55:25Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-ih67","depends_on_id":"polylogue-30h","type":"relates-to","created_at":"2026-07-15T06:25:40Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} @@ -479,6 +516,41 @@ {"_type":"issue","id":"polylogue-sru.2","title":"Characterize ambiguous bucket: wordless continuation vs prose-without-markers","description":"Split next-turn-is-tool-call (wordless continuation) from prose-lacking-ack-markers; state counts for both. Opus-4-7 74% ambiguous vs deepseek 17% is likely turn-structure variance, not behavior — this split disambiguates.","design":"Implementation home: the claim-vs-evidence classifier in devtools (devtools/ module behind `devtools workspace claim-vs-evidence`; tests tests/unit/devtools/test_claim_vs_evidence.py). Wordless-continuation detection: for each failure's paired next assistant message, check whether its blocks contain tool_use and no text block with \u003eN chars before the first tool_use — that is 'wordless continuation'; prose without matched ack markers stays 'ambiguous-prose'. Emit both as classification_reason variants (field already exists) and add the two counts to the report summary + by_model/by_tool cuts. Regen: `devtools workspace claim-vs-evidence --limit 5000 --out-dir .agent/demos/claim-vs-evidence --json`. Acceptance: report shows ambiguous split into wordless_continuation vs prose_no_marker with counts; per-model ambiguous variance (opus-4-7 74% vs deepseek 17%) re-examined after the split.","notes":"2026-07-03 Codex WIP: unit implementation for ambiguous split passes focused tests, but live regeneration with --limit 5000 became too slow and had to be killed twice. First attempt used correlated subqueries for next-message block shape; second used set-based CTE; third used chunked second query after sampled rows, but the full command still exceeded 90s on active archive and ignored SIGINT while inside SQLite. Do not close or commit this slice until the live regeneration path is profiled/fixed. Dirty files currently show the WIP implementation: devtools/claim_vs_evidence.py and tests/unit/devtools/test_claim_vs_evidence.py. Last passing focused proof: python -m py_compile + ruff check + devtools test tests/unit/devtools/test_claim_vs_evidence.py -\u003e 3 passed.","status":"closed","priority":1,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:28Z","created_by":"Sinity","updated_at":"2026-07-03T07:45:10Z","started_at":"2026-07-03T07:09:21Z","closed_at":"2026-07-03T07:45:10Z","close_reason":"Completed: claim-vs-evidence now splits ambiguous follow-ups into wordless tool continuations and prose-without-marker buckets, reports the counts in JSON/README summaries, and regenerates the current demo on the active archive. Focused tests pass; live regen/check completed.","labels":["area:substrate","campaign"],"dependencies":[{"issue_id":"polylogue-sru.2","depends_on_id":"polylogue-sru","type":"parent-child","created_at":"2026-07-03T06:31:27Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-sru.3","title":"Benign-recovery vs consequential-silence split by handler kind","description":"Read failures are ~94% silent but 'tried another path' is usually benign; Bash/test failures are the consequential class. Scope the headline to consequential handler kinds or add an explicit split — credibility depends on not inflating with trivial recoveries.","design":"Handler kind is already available on the paired failure row (actions lane exposes handler/tool). Define the consequential set explicitly in code (Bash/test/build/write-class handlers) and the benign-recovery set (Read/Glob/Grep-class 'tried another path'), emit split headline rows: silent-proceed among consequential vs among all. Keep the mapping a named constant with a rationale comment so reviewers can argue with it. Report both; never let the headline mix classes silently. Same regen/tests as the other methodology children.","status":"closed","priority":1,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:28Z","created_by":"Sinity","updated_at":"2026-07-03T07:58:08Z","started_at":"2026-07-03T07:55:37Z","closed_at":"2026-07-03T07:58:08Z","close_reason":"Completed: claim-vs-evidence now reports a first-class handler-class split separating consequential shell/edit/write-class tool failures from benign read/search/path-discovery failures and other tools. The regenerated active-archive artifact shows consequential=4,177 failures with 921 silent-proceed (22.0% lower bound), benign_recovery=633 with 166 silent-proceed (26.2%), and other=190 with 92 silent-proceed (48.4%). Focused tests and demo shelf checks passed.","labels":["area:substrate","campaign"],"dependencies":[{"issue_id":"polylogue-sru.3","depends_on_id":"polylogue-sru","type":"parent-child","created_at":"2026-07-03T06:31:28Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-sru.1","title":"Expose action-unit outcome fields + followup_class as product capability","description":"Capabilities-may-not-be-silos gate for the campaign: the facts the report needs must become composable query capability. After this, the whole report is `actions where is_error:true | group by session.origin, followup_class | count` and every future cut (model/tool/repo/time) is free.","design":"1) is_error/exit_code are normalized at parse time (sources/parsers/base_models.py:74-75) but ActionQueryRowPayload (surfaces/payloads.py:~1298) carries neither — add as filterable/groupable action-unit fields. 2) Add derived followup_class (acknowledged|silent_proceed|wordless_continuation|ambiguous) + followup_message_ref computed in the source-derived lowering (no cache tables). 3) Reduce devtools workspace claim-vs-evidence to a render preset over these query strings, or retire it. Touchpoint chain: stage parser -\u003e AST to_payload -\u003e executor -\u003e metadata.py aggregate_group_fields -\u003e shell_completion_values.py -\u003e devtools render openapi + cli-output-schemas + cli-reference. Line refs pre-07-03; re-locate.","acceptance_criteria":"Fixture session with known unacknowledged failure fires via pure query strings; report README numbers reproducible from the printed queries.","notes":"Completed: action-unit outcome follow-up classification is now shared query capability. is_error/exit_code were already wired; this slice added source-derived followup_class and followup_message_ref over existing actions/messages/blocks, exposed followup_class as filterable/groupable action metadata, added action row payload fields, routed root CLI terminal-unit aggregate expressions before session-selector compilation, and moved the report classifier from scripts into polylogue.archive.actions.followup. Reproduction/query forms are now printed in .agent/demos/claim-vs-evidence/PUBLIC_REPRODUCTION.md: actions where is_error:true | group by followup_class | count; actions where followup_class:silent_proceed. Verification: focused DSL/report/CLI tests passed; active demo packet regenerated over archive root /home/sinity/.local/share/polylogue schema v23 with 41,886 structured failures and 5,000 inspected; devtools verify --quick passed run 20260703T092510Z-quick-718233-46e8b587.","status":"closed","priority":1,"issue_type":"feature","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:27Z","created_by":"Sinity","updated_at":"2026-07-03T09:25:36Z","started_at":"2026-07-03T09:05:37Z","closed_at":"2026-07-03T09:25:36Z","close_reason":"Completed","labels":["area:query","area:substrate","campaign"],"dependencies":[{"issue_id":"polylogue-sru.1","depends_on_id":"polylogue-sru","type":"parent-child","created_at":"2026-07-03T06:31:26Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-7r6u","title":"Attachment acquisition still ~80% incomplete: 7,376 rows with no bytes and no real hash","description":"MEASURED 2026-07-31 (conversation-fidelity audit). Re-confirms the backfill gap anticipated by #2468/#2469 with current numbers.\n\n select acquisition_status, count(*), sum(byte_count), sum(blob_hash is not null) from attachments group by 1;\n acquired : 1,913 rows · 571.8 MB · 1,913 with a real 32-byte SHA-256\n unfetched: 7,376 rows · 22.1 GB nominal (provider-reported byte_count, unverified) · 0 with a hash\n\nSo 79.5% of attachment rows by count -- 97.5% by claimed bytes -- have no content and no real hash. PR #2469 (2026-06-28) landed real _acquire_attachment_blob/_write_attachments and the 1,913 acquired rows all carry genuine hashes, confirming the fix works; the pre-fix rows were never backfilled.\n\nWORSE PER ORIGIN: chatgpt-export is 78 acquired / 6,144 unfetched = 1.25% acquired.\n\nCHANNEL CENSUS (attachment_refs.upload_origin): oauth 4,190 · drive 3,094 · paste 69 · url 63 · NULL 2,454.\n\nRELATED, SEPARATE, worth its own check before fixing this: ChatGPT inline images. blocks has 1,351 rows with block_type='image', every sampled one with text NULL and media_type NULL. The blocks table has no metadata column, so chatgpt.py:752-757 builds the IMAGE block with an in-process-only metadata={'asset_pointer': ...} and chatgpt.py:1008-1021 routes it to a chatgpt_block_metadata session_event instead. MEASURED: 8 of 500 sampled such events carry an asset_pointer, so the reference genuinely survives -- this is documented redirection, not silent loss. But INFERRED (code reading only, not a bytes-in-blob-store check): no code path resolves an asset_pointer into a ParsedAttachment or a blob fetch. ParsedAttachment construction at chatgpt.py:558-590 covers only msg_metadata.attachments (oauth) and assistant sandbox-file links. If confirmed, those 1,351 images have a reference and no route to ever acquire the bytes.\n\nSUGGESTED: (a) backfill acquisition for the 7,376 unfetched rows where the source is still reachable, and record a terminal status where it is not, so 'unfetched' stops meaning both 'not yet' and 'never'; (b) verify the asset_pointer acquisition gap and open a follow-up if it holds.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:27:24Z","created_by":"Sinity","updated_at":"2026-07-31T10:27:24Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-7dgf","title":"Codex tool_result blocks never record why an outcome is unknown: 411,200 NULL is_error with NULL reason","description":"MEASURED 2026-07-31 (conversation-fidelity audit).\n\nblocks.tool_result_outcome_unknown_reason exists specifically so a NULL is_error is not conflated between three causes (NOT_REPORTED / DISTRUSTED / NOT_READ -- see polylogue/core/enums.py:352-372, which states the intent: 'unknown must not silently mean known to be fine').\n\nMEASURED: for origin='codex-session', tool_result_outcome_unknown_reason is NULL for ALL 1,035,030 tool_result blocks -- including the 411,200 (39.7%) where tool_result_is_error is itself NULL.\n is_error count\n 0 579,015\n NULL 411,200 \u003c- unknown outcome, unknown reason\n 1 44,815\n\nCONTRAST (same audit): claude-code-session does classify. Ground session 53e64853 holds 54 blocks with outcome_unknown_reason='not_reported' (matching exactly the 54 raw tool_result segments that carried no is_error key) and 9 with 'distrusted'.\n\nCODE PATH: the shared Anthropic-protocol path already sets the default -- polylogue/sources/parsers/base_support.py:72 assigns ToolResultUnknownReason.NOT_REPORTED when the segment carries no boolean is_error. Codex does not use that path: its three tool_result construction sites (polylogue/sources/parsers/codex.py:1706-1714 function_call_output handler, :1807-1821 MCP handler) build ParsedContentBlock directly and never pass outcome_unknown_reason, so it silently defaults to None.\n\nNOT A CORRECTNESS BUG IN THE VALUES: codex outcome resolution itself is disciplined -- structural JSON fields first, then an anchored regex against Codex-CLI's own generated preamble ('Process exited with code N'), never a scan of arbitrary subprocess stdout (polylogue/sources/parsers/codex.py:1162-1260). The is_error values that ARE set look trustworthy. The gap is the missing reason classification on the ones that are not.\n\nFIX: pass outcome_unknown_reason at the two codex construction sites (NOT_REPORTED where the provider structurally emitted nothing). Index-tier change, needs a rebuild to backfill.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:22:23Z","created_by":"Sinity","updated_at":"2026-07-31T10:22:23Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-kcdg","title":"read --view summary is byte-identical to --view transcript: there is no summary view","description":"MEASURED 2026-07-31 (conversation-fidelity audit).\n\nRendered both views for five real sessions across two origins via the production CLI; md5 is identical in every case:\n 779fc8eb9a78a7e8a960b67e1e6a7e89 claude-code-session_38baa1de.../{summary,transcript}.md\n 877606d7c0b2e627f68363b3be433f09 claude-code-session_53e64853.../{summary,transcript}.md\n d1312d5250a6f96ce594a584077ec8df claude-code-session_conversation_relationships/{summary,transcript}.md\n dafab9f0342390a806e4276a06afff29 codex-session_019ce460.../{summary,transcript}.md\n f821a14dfed6dba49ff6f2d70b838ced codex-session_019f12b5.../{summary,transcript}.md\n(artifacts under /realm/inbox/polylogue_renders/)\n\nCODE PATH:\n - polylogue/cli/read_view_handlers.py:57-67 binds BOTH 'summary' and 'transcript' to the same handler run_read_summary_or_transcript.\n - polylogue/cli/read_views/standard.py:77-107 is that handler. Its only branch on invocation.view is the transcript-to-file fast path (stream_exact_session_markdown); every other route builds one identical request and calls execute_query_request.\n - polylogue/cli/query_verbs.py:2463 maps both 'summary' and 'transcript' tokens to 'messages'.\nThere is no summarization step anywhere on the path.\n\nCONSEQUENCE: an 828 KB full transcript is what a user gets when they ask for a summary. For the ground sessions that is a 1,493- and 1,861-message dump. The view is advertised in read_view_registry.py and documented, so this is a surface that promises a capability it does not have.\n\nDECISION NEEDED (this is why it is P2 not P1): either implement a real summary projection, or retire the view name. Do not leave an alias that reads as a feature. Note this repo's no-compat-pre-adoption stance favours a hard rename/removal over a deprecation shim.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:21:39Z","created_by":"Sinity","updated_at":"2026-07-31T10:21:39Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-awy5","title":"Acquisition failure has no durable representation: zero 'failed' rows ever, exceptions only in logger.warning, no source.db trace pre-acquire, batch path re-increments excluded cursors to failure_count=2018","description":"MECHANISM finding (enables every silent STAGE-1 leak; the operator requirement is that nothing is skipped without being accounted for). From the 2026-07-31 acquisition-completeness audit; all file:line verified on master.\n\nMeasured absences (all-time, ops.db mode=ro):\n- ingest_attempts: completed 2127 / interrupted 25 / failed 0 - the 'failed' status is never used. For failing batches, error_message holds the semicolon-joined source_path list, not the exception; the real exception (zipfile.BadZipFile etc.) goes only to logger.warning (polylogue/sources/live/batch.py:2571) and is lost.\n- daemon_stage_events: only 'running'/'completed' ever. daemon_events: zero error/fail kinds ever.\n- A file that fails BEFORE acquisition completes leaves no source.db row at all: raw_artifacts.raw_id is NOT NULL REFERENCES raw_sessions, and _insert_artifact (polylogue/storage/sqlite/archive_tiers/source_write.py:1046-1075) only runs post-acquire. Verified: the 5 crash-looped inbox ZIPs have 0 rows in both tables.\n- Give-up loop bug: _MAX_CURSOR_FAILURES_BEFORE_EXCLUDE=5 (sources/live/cursor.py:51); mark_failed (cursor.py:1110-1174) sets excluded, clears next_retry_at (:1162). The watcher gates on excluded (watcher.py:780), but the batch/full-ingest path calls mark_failed via _record_failed_cursor (batch.py:938-978; call sites :567,:702,:765) with no excluded pre-check - failure_count on the 5 ZIPs reached 689/864/975/1001/2018, i.e. the scan crash-loops on permanently-excluded sources, redoing work and capturing nothing.\n- No aggregate skip ledger: raw_artifacts.support_status has per-path lookup only (import_explain); ops status aggregates cursor exclusions (daemon/status.py:1170-1240) but not artifact statuses, and nothing can enumerate never-cursored classes (e.g. tool-results, antigravity .pb - see polylogue-rujy / polylogue-eo81).\n\nAC: (1) acquisition/parse failures produce a durable record carrying the actual exception (ingest_attempts status='failed' or equivalent event) - kill-based test; (2) files that fail pre-acquire leave a durable accounted-for row (artifact-level, not cursor-only); (3) batch path consults cursor.excluded before re-queueing (failure_count stops growing past threshold); (4) an aggregate accounting surface: counts by disposition for every observed-but-unarchived path class, so 'is everything accounted for?' is answerable with one query.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:11:34Z","created_by":"Sinity","updated_at":"2026-07-31T10:11:34Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-2qrx","title":"Stalled append-cursor backlog: 211 live files 414MB behind, 206 of them cold for days-to-weeks (top: 94.8MB lag on one codex rollout, 329h stale)","description":"STAGE-1 ACQUISITION LEAK (content still on disk, so recoverable - but only by acquisition catch-up; an index rebuild replays source.db and recovers none of it). From the 2026-07-31 acquisition-completeness audit.\n\n211 non-excluded ingest cursors have byte_offset \u003c current file size: codex-session 104 files / 252.1MB lag, claude-code-session 106 / 126.9MB, unknown-export 1 / 35.3MB (the live /realm/db/polylogue/inbox/claude-ai-data-2026-07-30 zip). Only 5 are hot (mtime\u003c1h); 206 are stalled - file cold for days-to-weeks yet the cursor never caught up. Top offenders: rollout-2026-07-15T20-11-43-019f66fa (94.8MB lag on 118MB, 329h stale), rollout-2026-06-29T11-29-04-019f12b5 (30.3MB lag on 428MB, 625h stale), rollout-2026-07-11T22-26-14-019f52db (22.5MB lag, 422h).\n\nThese are exactly the tails of large multi-hundred-MB sessions - the newest content of the biggest working sessions is what's missing. Distinct from the excluded/give-up class (those are 93-100% content-accounted via full-reacquire; see audit report) and from the interrupted-ingest bead (polylogue-61jg) though plausibly the same daemon-interruption incidents left both residues.\n\nRepro (mode=ro): sqlite3 \"file:/realm/db/polylogue/ops.db?mode=ro\" \"select origin,count(*),sum(stat_size-byte_offset) from ingest_cursor where excluded=0 and byte_offset\u003cstat_size group by origin\" then re-stat paths on disk for current sizes.\n\nRelated: polylogue-aex0 (cursor continuity anchored to source.db), polylogue-1xc.13 (expose freshness/excluded degradation).\n\nAC: (1) the 206 stalled files drained to byte_offset==size (or a recorded per-file disposition); (2) a freshness signal exists that would have flagged a 329h-stale 94MB lag (ties into polylogue-1xc.13); (3) whatever stalls append catch-up on multi-hundred-MB files is root-caused.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:11:33Z","created_by":"Sinity","updated_at":"2026-07-31T10:11:33Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-5iz4","title":"Codex 90.8MB session unindexable: 'membership replay cannot replace an unconvertible byte head' crash reproduces on every parse attempt","description":"STAGE-2 PARSE LEAK, rebuild does NOT fix (the parser crash reproduces on retry; bytes ARE complete in source.db). From the 2026-07-31 acquisition-completeness audit.\n\nCodex session native_id 019f49d8-0185-7c43-8793-db6e57db13e1 (rollout-2026-07-10T04-25-20-...jsonl) never entered the index. 804 raw_sessions rows share this source_path (incremental full-snapshot captures as the live file grew). The largest revision (90,822,451 bytes) has parsed_at_ms=NULL; the second largest (90,156,590 bytes) has parse_error='RuntimeError: membership replay cannot replace an unconvertible byte head'. index.db has zero sessions for this native_id. These 804 rows also account for 781 of codex's 'genuine gap' unparsed raw rows - one session, massive revision churn.\n\nRelated: polylogue-rgh2 (closed - accepted semantic head in membership replay authority) evidently did not cover this byte-head case; polylogue-1k9l tracks the broader 111-row parse_error ledger.\n\nRepro (mode=ro): sqlite3 \"file:/realm/db/polylogue/source.db?mode=ro\" \"select count(*), max(blob_size) from raw_sessions where source_path like '%019f49d8-0185-7c43-8793-db6e57db13e1%'\"\n\nAC: (1) root-cause the unconvertible-byte-head replay failure on this session's actual revision chain (fixture from the real blob shapes, content redacted); (2) the session parses and reaches the index with plausible message_count; (3) regression test for the replay path; (4) the 804-row churn compacts per normal revision authority rules.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:10:54Z","created_by":"Sinity","updated_at":"2026-07-31T10:10:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-p3b2","title":"Silent materialize drops: 97 chatgpt-export groups (211MB) parse OK but produce zero index sessions; ~3 of 47 claude-ai zero-message sessions hold real turns","description":"STAGE-2 PARSE LEAK, rebuild-fixes-it UNKNOWN until root-caused (if materialize has a deterministic drop it reproduces on rebuild). From the 2026-07-31 acquisition-completeness audit.\n\n1) 97 chatgpt-export union-find groups (211,881,173 bytes) have parsed_at_ms set, NO parse_error, yet no index.db session matches by raw_id or (origin,native_id). Concentrated under source paths containing 'inbox/\u003cuuid\u003e-\u003chash\u003e.json' (staging copies of browser captures). Parse claims success; materialization yields nothing; nothing records the drop.\n2) claude-ai-export zero-message sessions: of 47 total with index message_count=0, a 15-session sample found 14 genuinely-empty stubs (~233B raw, chat_messages:0) and 1 real drop: session claude-ai-export:44810a60-201b-4ea9-9db5-a46b21302bbc has chat_messages:4 in raw JSON but message_count=0 in index. Extrapolates to ~3/47; a full 47-session sweep is cheap and should be step one.\n\nRepro sketch (mode=ro): join raw_sessions latest revisions per (origin,native_id) against index sessions; for (2) decode the blob at /realm/db/polylogue/blob/\u003c2hex\u003e/\u003c62hex\u003e and count chat_messages vs sessions.message_count.\n\nAC: (1) the 97-group drop root-caused with the exact materialize decision named (and, if by-design e.g. duplicate-of-existing-session, that decision recorded durably per raw row rather than silent); (2) recoverable groups reach the index; (3) full sweep of the 47 zero-message claude-ai sessions, real-content ones re-materialized.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:10:51Z","created_by":"Sinity","updated_at":"2026-07-31T10:10:51Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-l1qg","title":"POLYLOGUE_ARCHIVE_ROOT silently redirects ops/maintenance CLI to a scratch archive during recovery flows","description":"Audit 2026-07-31, reproduced live: with POLYLOGUE_ARCHIVE_ROOT=/tmp/polylogue-archive inherited from the repo devshell env, 'polylogue ops maintenance raw-authority-frontier' reported census:1 accepted=0 plans=0 (an empty scratch archive) instead of the live archive's census 932 with 17,384 plans — no warning that the root came from an env override. An operator running break-glass maintenance in the wrong shell would conclude the frontier is clean. Matches the 2026-07-28 archive-root precedence scare (memory note). Needs: ops/maintenance commands should print the resolved archive root + its provenance (env/config/default) on every invocation, and arguably refuse env-derived roots for break-glass apply subcommands without an explicit flag.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:54Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-mia3","title":"Parse-pool result wait has no watchdog: zero-completion hang re-loops on 15s heartbeat forever (p0pw residual)","description":"Audit 2026-07-31. The forkserver deadlock cause behind polylogue-p0pw is already mitigated (process_pool.py:40 uses spawn), but the consumer loop remains unguarded: _iter_ingest_results_chunk (pipeline/services/ingest_batch/_core.py:965-974) does wait(futures, timeout=15, FIRST_COMPLETED) and on empty 'done' just heartbeats and continues — no worker-liveness probe, no escalation, no sequential fallback once submission succeeded, and terminate_process_pool (process_pool.py:117-132) is never wired as a watchdog. Any future worker hang (resource exhaustion, silent worker death without future resolution) stalls ingest indefinitely while heartbeats keep the attempt looking alive. Needs: bounded total-stall detection (N heartbeats with zero completions and zero running workers → terminate pool, fall back sequential, record attempt failure).","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:51Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:51Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-ix5r","title":"Excluded ingest cursors are permanent-unless-file-replaced; status mislabels them 'retry due' and hides age","description":"Audit 2026-07-31. 1,446 cursor rows excluded=1 (5-failure cap, cursor.py:51,1147-1164); revive_replaced_exclusion requires the FILE to change identity (size/dev/inode/mtime), so a parser fix never revives them — they stay dark until manual re-ingest. Pre-exclusion history shows the cost of the old regime: export zips reached failure_count 2,018/1,001/975/864/689 (thousands of full acquire+parse+crash cycles). polylogued status prints 'Live cursor: 1241 failed, 1446 excluded, 1241 retry due' — for excluded rows the retry never comes — and 'Failing files: 50 shown, 1397 omitted' with no ages. Needs: parser-fingerprint-aware revival (retry excluded files when the parser fingerprint changes), age/oldest surfacing, and honest labeling.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:47Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:47Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-ymqp","title":"Index generations leak: 34G superseded generation under retired archive-root identity + SIGKILL candidates never pruned","description":"Audit 2026-07-31. .index-generations/ holds: active gen-1785377665711 (38G); gen-1784807190100 (34G, superseded 07-30) whose generation.json still says state='active' with archive_root='/home/sinity/.local/share/polylogue' (the retired root identity), so root-keyed pruning won't claim it; gen-1785377192405 (900K) failed candidate retained 'for diagnosis'. prune_superseded_generations only prunes previously-PROMOTED generations (storage/index_generation.py:639-646); a SIGKILLed bulk-build candidate (synchronous=OFF, possibly corrupt) is left forever by design. Needs: prune path for (a) superseded generations regardless of recorded root identity after pointer verification, (b) aged failed/abandoned candidates; plus a status line for generation disk usage.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:45Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:45Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-gxig","title":"source.db is 84% freelist: 7.6GiB dead pages of 9.0GiB file; VACUUM never run after ledger purge","description":"Audit 2026-07-31. pragma freelist_count=2,000,618 of page_count=2,370,200 (4KiB pages) → ~7.6GiB free pages; dbstat live content = 1,443MiB. The '9.1GB archive' durable tier is actually ~1.4GiB of data. Cost: backups, page-cache pollution, IO, and misleading capacity/rebuild planning. VACUUM is operator-owned (docs/daemon.md maintenance table) and was never run after the wkc6 census-ledger purge. Action: schedule offline VACUUM of source.db during the next daemon stop (needs ~9GB free, /realm has 2.0T); note bead about ledger regrowth first or the space returns.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:43Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:43Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-gmw2","title":"Browser-capture spool-yield loop: undrainable spool files preempt raw materialization every pass","description":"Audit 2026-07-31. Live: 'raw materialization: yielding to pending browser-capture spool files' every ~60s (56x today since 05:13); 4 chatgpt spool files under browser-capture/chatgpt/, oldest since 05:03 local (6+h). They never drain because canonical-authority resolution fails: recurring 'browser canonical authority conflict competing-head diff unavailable' + 12 unresolved blockers 'byte-proven browser rekey requires no retained membership census' + 1 'no canonical authority an operator could retain'. Meanwhile browser_capture.invalid_payload logged 547x since 07-24 (client repeatedly posting a rejected payload). Spool status in polylogued status says 'ready'. Needs: terminal classification / quarantine for spool files that repeatedly fail authority resolution (so the yield stops), spool-age surfacing in status, and receiver-side dedup/backoff for repeating invalid payloads.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:09:39Z","created_by":"Sinity","updated_at":"2026-07-31T10:09:39Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-g16g","title":"Leak audit L10: audit reports embed real session ids and archive paths","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE.\n\nReports are the artifact class most likely to be shared onward. A marker scan across the six audit HTML reports in /realm/inbox/polylogue-audits-2026-07-31/ found the real archive path in five of them and three real session identifiers in one (dataset-forensics.html) - the identifiers were confirmed against the live index.db to be real sessions.\n\nNeither is conversation content, but both make a shared report say more about the operator's machine than intended.\n\nVerified alongside: /realm/inbox/polylogue_renders/ (two real rendered sessions) and the audits directory both sit OUTSIDE any git working tree - neither /realm/inbox nor /realm is a repo - and no symlink or configured output path connects them to the polylogue checkout. They are safe from accidental commit.\n\nConvention worth adopting: audit reports state a content policy in their metadata block and carry no identifiers. leak-surfaces.html does this; the other five predate it.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:57Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-ut3r","title":"Leak audit L16: read-role MCP and daemon API return unredacted raw content","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: EXPOSED BY DOCUMENTED DESIGN - filed for visibility, not as a defect.\n\n/api/raw_artifacts/:id returns unredacted raw payloads and /api/sources returns absolute filesystem paths, both by explicit decision in docs/security.md. The MCP base read surface includes an equivalent raw-payload path with required_capability=None.\n\nConsequence worth stating plainly: MCP 'read' is not metadata-only, it is full content. Combined with the default MCP profile being wired into the operator's ordinary claude/codex commands, every agent session on this machine has full-archive read by default. That is coherent for a single-user tool, and it is also the assumption that makes every other agent-facing surface in this audit a potential content path.\n\nIt also stops being consistent the moment the uid boundary in L6 is taken seriously, since the same-user argument is what justifies it.\n\nAudited SOUND on this surface: MCP capability gating is a hard block, not a listing filter - privileged tool closures are never defined when the capability is off, so the dispatcher has no entry to route to, and a registrar assertion fails startup if the registered set differs from the capability-filtered expected set. Every operation literal in the write and maintenance dispatchers stays within its own capability class, so there is no verb-table route from a read caller to a write verb. All three capability flags default false. Capability is process-wide with no per-caller identity - worth knowing before enabling any of them.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:53Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-ioz2","title":"Leak audit L19/L20: blob store dir modes and unswept residue","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: L19 REACHABLE (defence-in-depth), L20 CONTAINED (hygiene/observability).\n\nL19 - blob FILES are explicitly 0600 and publish is an os.replace rename that preserves the mode (verified on live samples). But the directories under the archive root are created with the process umask and are 0755. The entire boundary for directory structure rests on a single 0700 mode on the archive root, with no redundancy. If that regresses (bind mount, backup export, container misconfiguration) the structure and hash names become world-listable; file bytes stay protected. Fix: create the store root with an explicit mode=0o700.\n\nL20 - preparation temp files are DELIBERATELY excluded from the orphan walk, so a hard kill leaves residue that no maintenance or health surface can ever see. Publication reservations have no staleness expiry and clear only on an explicit confirmed operator action.\nMeasured live: 52 temp files / 63 MB dated 2026-07-11 to 2026-07-18, plus 2 stale reservations pinning 42.5 MB unresolved for ~19 days (the reservation figure matches the earlier audit exactly). All 0600 inside the 0700 root - not an exposure.\nNOTE: this measurement CORRECTS the previously cited '1.55 GB of orphan/temp residue'. The live figure is 63 MB.\nFix: age-based sweep for temp files; TTL-based auto-abandon for reservations.\n\nAudited SOUND alongside: blob paths are built only from a SHA-256 hash validated by a fullmatch hex regex, and there is no extract()/extractall() anywhere - zip members are streamed via open(), so zip-slip is impossible rather than merely unlikely.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:51Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:51Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-tztk","title":"Leak audit L13/L14/L15: three narrow diagnostic disclosure paths","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE (all three, all narrow).\n\nL13 - the Codex parser logs a Pydantic ValidationError at DEBUG. Pydantic v2's default __str__ embeds the offending input_value, i.e. raw payload content. It is the only such call site; every other ValidationError in the tree is discarded without logging. Requires an explicit 'polylogue --verbose'; the daemon never sets verbose. Fix: log exc.errors() filtered to type/loc, or the first line of str(exc).\n\nL14 - 'polylogue status' prints schema-drift examples whose identifiers are the SOURCE FILE PATHS of ingest files, revealing which local projects feed the archive. Paths, never content. Local terminal only, but it lands in scrollback and pasted issue text.\n\nL15 - ops.db otlp_telemetry.payload stores raw OTLP export bodies verbatim, unredacted, with no retention pruning (unlike schema_drift_samples, which prunes). Currently 0 rows and behind an opt-in observability flag. Hardening gap, not live exposure.\n\nAudited SOUND alongside these: the format-drift warning that prints on ordinary CLI runs emits only an origin name, a percentage, a count and a date - no titles, paths or payload. No show_locals, no rich-traceback install, no custom excepthook. No ops.db column holds message text, titles or query strings.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:47Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:47Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-kc26","title":"Leak audit L12: committed bead tracker leaks host paths and session ids","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE (metadata, ongoing).\n\n.beads/issues.jsonl is committed to the public repo and grows continuously. Measured over 1345 bead records: 125 reference private host paths (/home/sinity, /realm/db, /realm/data, /realm/inbox) and 22 reference real session identifiers. Bead descriptions run to 32 KB.\n\nContent at risk: filesystem layout, project names, session identifiers - metadata, not conversation text. Nothing to undo for what is already published; every future bead adds to it.\n\nFix options: a path-scrubbing convention for bead text, or a lint in the existing bead-graph policy check. Recording rather than prescribing.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:45Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:45Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-qut1","title":"Leak audit L8: MAIN-world capture bridge cannot distinguish itself from page JS","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE. Integrity, not confidentiality.\n\nThe postMessage handlers correctly check both event.source === window and event.origin === currentOrigin. No origin check can distinguish the extension's MAIN-world bridge from the page's own JavaScript, because both execute in the same realm on the allowed origins. A script running on claude.ai / chatgpt.com / grok.com can therefore forge a capture payload; the capture parser validates only that the URL contains the current conversation id and that the JSON has the expected array shape.\n\nConsequence: fabricated transcript content entering the operator's archive. Not a confidentiality leak, and no privilege escalation - the forging script already holds the authenticated fetch capability it is imitating. Preconditions: XSS or a compromised third-party script on an allowed origin.\n\nWorth a decision rather than a fix: the honest options are stronger provenance on native captures, or accepting that MAIN-world bridging carries this property.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:39Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:39Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-v73m","title":"Leak audit L7/L9: browser extension permission and origin breadth exceed the need","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE (both).\n\nL7 - the manifest grants http://127.0.0.1/* with NO port scoping. The extension only ever calls its own receiver on :8765, but the permission already covers the unauthenticated archive API on :8766 (see L6), and extension fetches are not subject to CORS. Nothing exploits this today; it removes a free layer.\n\nL9 - the receiver's origin allowlist accepts chrome-extension://\u003cany-id\u003e rather than pinning this extension's id, so any locally installed extension may attempt pairing redemption during an open window. Pairing-code entropy (8 chars / 32-symbol alphabet) plus a 5-attempt limit inside a 180s window makes brute force infeasible, so the mitigation is arithmetic; the fix is a string comparison.\n\nThe rest of the extension audited SOUND at the implementation level: loopback-only bind with a mandatory token for the remote case, auto-minted 0600 token, constant-time compare, positive-class regex path components (no traversal), TOCTOU-safe quota locking, stream-enforced byte caps, credential-dropping cross-origin asset fetches, a debug-log redaction list checked against what is actually logged, escaped innerHTML sinks in the privileged popup, and no externally_connectable / web-accessible resources / eval / remote script.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:37Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:37Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-5ka4","title":"Render/layout pipeline stage for terminal query-unit results","design":"Remaining scope of polylogue-fnm.2: a new render/layout pipeline stage (parallel structural shape to the 'agg' stage added for polylogue-fnm.1 in archive/query/expression.py -- QueryUnitPipelineStageKind, QueryUnitTerminalAction, a new QueryUnitRenderStage AST node, hand-parsed like the other pipeline stages, grammar file unchanged) that binds a read-package/render profile to a terminal query-unit result, picked up by explain via to_payload. Deferred out of the fnm.2 PR that landed the bracket-predicate/window half (with unit[field:value, last:N]) because a 'render/layout profile' concept does not exist yet as a first-class thing to bind to -- the nearest analogues (demo/read-package tooling, insight rendering) live in insights/ and other lanes that PR's task explicitly avoided touching, and fabricating a profile registry just to satisfy the AC would be exactly the kind of thin/misleading implementation the project's honesty rules reject. Needs its own scoped design: what a render/layout profile actually names (an existing CLI output format? a new named preset? something from docs/plans read-packages?), where its registry lives, and which surfaces (CLI/API at minimum; MCP/daemon out of scope per the sibling PR's lane boundaries) consume it.","acceptance_criteria":"- New pipeline stage (e.g. 'render' or 'layout') hand-parsed alongside sort/group/count/agg/limit/offset in archive/query/expression.py's terminal pipeline stage parser; grammar file (Lark) diff stays empty.\n- QueryUnitPipelineStageKind/QueryUnitTerminalAction widened; new AST node's to_payload() round-trips and appears in --explain --format json output.\n- Binds an existing or newly-registered read-package/render profile concept to the terminal result (define what that concept is as part of this bead's design work; do not stub it).\n- devtools test coverage for parse + explain-payload + at least one profile actually changing the emitted shape.\n- devtools render all --check passes (openapi/cli-output-schemas/cli-reference regen).","status":"open","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:58:11Z","created_by":"Sinity","updated_at":"2026-07-31T08:58:11Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-tf8p","title":"Docs drift cluster: cli-reference -h alias, mcp-reference resources/prompts, dead MCP surface contracts, CLAUDE.md contradictions","description":"Surface-coherence audit 2026-07-31, doc-vs-reality diffs (all verified against the live surface): (1) docs/cli-reference.md is stale vs live --help: root/judge/ops/ops doctor/ops auth/ops reset/ops insights/config/config completions/config paths/dashboard/tutorial now expose `-h, --help` but docs show `--help` only — while analyze/read/select/delete/mark/continue genuinely have no -h (context_settings only on root group and `find`, polylogue/cli/click_app.py:341,573): the alias itself is inconsistently applied across verbs. (2) docs/mcp-reference.md Resources section lists 8 URIs; the live server registers 9 static + 6 templates — missing from docs: polylogue://agent/{manual,reference,manifest}, polylogue://capabilities/{query,action-affordances}, raw-authority-census/detail templates; 12 registered prompts are undocumented entirely. (3) tests/infra/mcp.py EXPECTED_RESOURCE_URIS (5 entries) and EXPECTED_PROMPT_NAMES (6 entries) are referenced by NO test — dead constants, both stale vs the 15-resource/12-prompt live surface; the resource+prompt surfaces are unpinned (only EXPECTED_TOOL_NAMES is enforced, and via test_envelope_contracts.py/test_affordance_usage.py, not test_server_surfaces.py as CLAUDE.md claims). (4) CLAUDE.md contradicts docs/mcp-reference.md on the capability model: CLAUDE.md says '10 role-gated ... behind the write role, judge behind the review role, maintenance behind the admin role'; mcp-reference.md says 'There is no role ladder and no --role flag' (config opt-ins). (5) CLAUDE.md's CLI verb list (find/read/analyze/mark/select/delete/continue) omits live verbs facets/note/judge. (6) CLAUDE.md's Origin list omits beads-issue (present in core/enums.py, provider-origin-identity.md, and the sessions.origin CHECK). Fix: rerun devtools render cli-reference; extend render coverage (or the doc) to resources+prompts; wire or delete the dead contracts; align CLAUDE.md wording.\n","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:41:47Z","created_by":"Sinity","updated_at":"2026-07-31T08:41:47Z","labels":["docs","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-d0ew","title":"Tag vocabulary fragmented across 3 stores; all public tag surfaces return empty; dead broken storage list_tags","description":"Surface-coherence audit 2026-07-31: \"what tags exist?\" returns {} on every public surface while the index holds 817 session_tags rows (10 distinct auto tags: capture:browser-native-payload 436, degraded:brain-metadata-fragment 116, hermes:state-db 106, ...). polylogue://tags MCP resource -\u003e {} (routes to api list_tags -\u003e archive.list_user_tags, user.db assertions kind='tag' count=0); `polylogue facets --format json` tags family -\u003e {} as well. Meanwhile tag vocabulary is fragmented across ≥3 stores: user.db assertions (empty), index session_tags (817 auto rows), session_profiles auto_tags_json (e.g. origin:claude-code-session, degraded:large-session — not in session_tags either), plus session_tag_rollups (3629 rows). Also dead+broken code: polylogue/storage/sqlite/queries/sessions_identity.py:137 list_tags() JOINs a `tags` table that does not exist in the live index schema (session_tags has a `tag` TEXT column, no tag_id) and takes a `provider:` kwarg on an origin filter (vocabulary leak); it is exported via queries/sessions.py __all__ but has zero callers. Decide what the public 'tags' vocabulary means (user tags only? user+auto with source labels?), make facets/MCP/API answer it consistently, and delete the dead storage list_tags.\n","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:41:14Z","created_by":"Sinity","updated_at":"2026-07-31T08:41:14Z","labels":["surface-coherence","tags"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-umfp","title":"Per-session cost: profiles vs usage tables disagree and no public surface reads the authoritative number","description":"Surface-coherence audit 2026-07-31: \"what did this session cost?\" has different answers per read model and no public surface reads the authoritative one. Target claude-code-session:c1cf89f2-c4ff-48de-9459-599c2e8d04ff (3897 msgs): index.db session_model_usage says input=7,482,636 output=2,668,961 cost_usd=9.876981 (priced, deepseek-v4-pro row); session_profiles (and Python API get_session_profile / SessionProfile) says total_cost_usd=0.0, all token totals 0, cost_provenance='unknown' — because the profile is bounded_large_session (relates polylogue-wofr). Census: 10,311 profiles claim total_cost_usd\u003e0; 10,026 sessions have session_model_usage sum\u003e0; 3,395 profiles claim 0.0 with provenance unknown; codex example 019fb539... has profile cost 0.439496 with EMPTY usage rows (relates polylogue-shnc). Surface gap: MCP get(session:...) session-summary carries no cost; CLI `read --json` carries none; `analyze usage` has --origin but no per-session scope; `analyze --cost-outlook` is cycle-level. So the only way to answer the most basic cost question for one session is raw SQL. Wanted: one canonical per-session cost read (usage-table-backed, provenance-labeled) exposed on CLI read/summary, MCP get, and API — and profile cost fields that carry their bounded/unknown provenance loudly instead of a bare 0.0.\n","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:41:14Z","created_by":"Sinity","updated_at":"2026-07-31T08:41:14Z","labels":["cost","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-01fe","title":"Bad-input behavior diverges: CLI errors, daemon silent-empties, MCP ignores; unknown-export unfilterable","description":"Surface-coherence audit 2026-07-31: the same bad input gets three different behaviors. (1) Invalid origin: CLI `--origin bogus-origin` -\u003e UsageError \"Unknown origin(s)... Valid: chatgpt-export, claude-ai-export, claude-code-session, codex-session, aistudio-drive, gemini-cli-session, hermes-session, antigravity-session, grok-export\" (exit 2); daemon `GET /api/sessions?query=x\u0026origin=bogus-origin` -\u003e HTTP 200, total=0 silent-empty (same for origin=claude-code); MCP query -\u003e accepts it and returns the UNFILTERED aggregate (see polylogue-hnl7). (2) The CLI's valid-origin list also rejects `unknown-export`, which is a declared Origin enum member and a legal sessions.origin CHECK value (schema also allows `beads-issue`, absent from CLI vocabulary and from CLAUDE.md's origin list). If a session ever lands with those origins it is unfilterable from the CLI. (3) Missing session: CLI `-i nonexistent-xyz read` -\u003e exit 1 \"Error: Session not found\"; daemon `GET /api/session/nonexistent-xyz` -\u003e 404; MCP get/read -\u003e soft-miss payload (resolved:false, caveats:[\"session not found\"], no is_error envelope). Decide the contract per class (validate-and-error vs silent-empty vs soft-miss) and make all three surfaces implement the same one; today silent-empty on the daemon can mask a typo'd origin as \"no data\".\n","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:41:13Z","created_by":"Sinity","updated_at":"2026-07-31T08:41:13Z","labels":["errors","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-1c6j","title":"CLI and daemon search JSON both violate the published SearchEnvelope schema (only MCP conforms)","description":"Surface-coherence audit 2026-07-31. docs/cli-reference.md 'Published Machine Output Schemas' maps `polylogue --format json \u003cquery\u003e` to SearchEnvelope (docs/schemas/cli-output/search-envelope.schema.json, required: hits/total/limit/offset/query/retrieval_lane, additionalProperties: false). Live CLI output (`env -u POLYLOGUE_ARCHIVE_ROOT polylogue --no-daemon --limit 3 --json find 'frozen_clock'`) has top-level keys items/limit/mode/next_cursor/next_offset/offset/origin/query/retrieval_lane/total — jsonschema.validate FAILS: \"Additional properties are not allowed ('items', 'mode', 'origin' were unexpected)\" and required 'hits' missing. The daemon (`GET /api/sessions?query=frozen_clock\u0026limit=3`) emits the right envelope shape (hits/ranking_policy/route_state...) but ALSO fails validation: \"'message_count' is a required property\" inside the hit session payload. MCP query(projection='sessions') emits payload_type=SearchEnvelope with hits and matches the schema shape. So three surfaces claim one schema; only MCP conforms; CLI has a different envelope entirely (items/mode) and daemon's hit rows violate session-summary requirements. Also: CLI session read payload duplicates vocabulary — polylogue/cli/archive_query.py:2689 emits `\"source\": envelope.origin` alongside `origin` (same origin token under a 'source' key) on `read --json`. Either fix the emitters to match the published schemas or fix the schema table; today a consumer coding against the published schema breaks on 2 of 3 surfaces.\n","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:40:26Z","created_by":"Sinity","updated_at":"2026-07-31T08:40:26Z","labels":["schemas","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-nqx2","title":"classify_material_origin: the all-tool-result-blocks branch is defended by no test","description":"FALSE-GREEN AUDIT 2026-07-31 (finding F5). MUTATION-VERIFIED.\n\nclassify_material_origin (polylogue/archive/message/artifacts.py:157) is the authoredness axis\nCLAUDE.md calls load-bearing for honest cost/user-word accounting. It has 8 classification\nbranches. Exactly ONE test file names it -- tests/unit/core/test_message_types.py:43\ntest_plain_user_message_does_not_imply_human_authorship -- and it covers only the UNKNOWN\nfall-through. All other coverage is incidental, via parser tests.\n\nI mutated each branch and differenced against a measured baseline in an isolated worktree.\nGOOD NEWS -- 4 of 5 branches are genuinely well defended by real parser tests:\n\n MO1 operator-command detection deleted -\u003e CAUGHT, 4 tests red, incl.\n test_parsers_chatgpt.py::test_chatgpt_transport_rows_are_classified_as_protocol_material\n MO2 SUMMARY -\u003e GENERATED_CONTEXT_PACK -\u003e CAUGHT, 5 tests red, incl.\n test_parsers_claude_code_artifacts.py::test_parse_code_compaction_summary_is_generated_context\n MO3 CONTEXT -\u003e RUNTIME_CONTEXT -\u003e CAUGHT, 8 tests red, incl.\n test_parsers_codex.py::test_contextual_user_message_is_not_human_authored\n MO5 ASSISTANT_AUTHORED branch deleted -\u003e CAUGHT, 10 tests red\n\nTHE GAP:\n MO4 'if block_types and all(bt is BlockType.TOOL_RESULT for bt in block_types):\n return MaterialOrigin.TOOL_RESULT'\n replaced with 'if False:' -\u003e NOT CAUGHT.\n selection A: 14 files / 714 tests, 0 pre-existing failures -\u003e 0 new failures\n selection B: 12 tool-result-specific files / 218 tests (incl.\n test_tool_result_role_reclassification.py, test_tool_result_sidecars.py,\n test_archive_tiers_write.py) -\u003e 0 new failures\n 26 files, 932 tests total. Nothing goes red.\n\nSCOPE HONESTLY: this branch is a DEFENSIVE REDUNDANCY, which is why severity is P2 not P1.\nclassify_block_message_type (artifacts.py:146) already maps all-TOOL_RESULT blocks to\nMessageType.TOOL_RESULT, and classify_material_origin's FIRST branch catches\nnormalized_type is MessageType.TOOL_RESULT. So MO4 only fires when a message carries\nall-tool-result blocks while its message_type says otherwise -- i.e. exactly the\ninconsistent-metadata case a parser bug would produce. That is the case worth guarding, and\nnothing guards it.\n\nConsequence if it silently broke: such a message falls through to UNKNOWN instead of\nTOOL_RESULT, and UNKNOWN vs TOOL_RESULT is what separates authored-user counts from runtime\nmaterial in cost/user-word accounting.\n\nAC:\n- A test constructs a Message with all-TOOL_RESULT blocks and a NON-TOOL_RESULT message_type,\n and asserts material_origin is TOOL_RESULT.\n- Anti-vacuity: confirm the MO4 mutation above turns it red.\n- Decide whether the branch should instead be made unreachable-by-construction (normalize\n message_type from block_types at one chokepoint), which would be the surgical-renewal answer\n and would align with polylogue-aggz's 'make the case unrepresentable' framing.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:29:45Z","created_by":"Sinity","updated_at":"2026-07-31T08:29:45Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-8u1p","title":"Parse Gemini CLI JSONL chat-log checkpoint format (turn-per-line, no embedded messages)","description":"Gemini CLI has TWO on-disk checkpoint shapes for its \"chats\" feature:\n\n1. Single JSON document per session (`.json`): {\"sessionId\",\"projectHash\",\n \"startTime\",\"lastUpdated\",\"kind\",\"messages\":[...]} - the messages list is\n embedded. This shape has a working detector+parser (`local_agent.\n looks_like_gemini_cli` / `parse_gemini_cli`).\n\n2. A genuinely different multi-line `.jsonl` checkpoint log: a session-open\n stub record (same envelope fields, but NO \"messages\" key at all) followed\n by one JSON object per turn/event on subsequent lines, shaped\n {\"id\",\"timestamp\",\"type\":\"user\"|\"gemini\"|\"error\"|\"info\",...} interleaved\n with {\"$set\":{\"lastUpdated\":...}} patch lines. There is currently NO\n parser for this shape at all.\n\npolylogue-hs3y's fix (dispatch.py + local_agent.py) taught detect_provider\nto recognize the stub record so it no longer misclassifies as\nclaude-code-session (bare \"sessionId\" collided with Claude Code's\n_STRONG_SESSION_KEYS). But _lower_payload_specs's GEMINI_CLI branch only\nknows _single_document_record - a multi-line event-log stream still lowers\nto zero specs, so these sessions are correctly tagged gemini-cli-session in\nraw_sessions but never become a queryable sessions row (0 messages, by\ndesign - no forced empty session).\n\nConfirmed live in the archive: 4 raw_sessions rows under\n~/.gemini/tmp/*/chats/*.jsonl carry real turn content (user questions,\ngemini responses with thoughts/token usage, tool calls) that is currently\nunrecoverable from the archive.\n\nScope: write a stream-record parser for the event-log shape (turn-per-line,\n$set patches folded into session metadata, first-line stub as session\nidentity), wire it into GROUP_PROVIDERS/STREAM_RECORD_PROVIDERS or an\nequivalent per-line lowering path, add real-fixture-shaped tests.\n","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:22:44Z","created_by":"Sinity","updated_at":"2026-07-31T08:22:44Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-pfdf","title":"Attachment backlog: 7,376 of 9,289 attachments (79%) still acquisition_status='unfetched'","description":"Forensics 2026-07-31. attachments: 1,913 acquired vs 7,376 unfetched. The #2469 fix (real _acquire_attachment_blob) stores true blobs going forward; the historical backlog was never backfilled and is static. Sources may still have the bytes (exports re-acquired regularly).\nRepro: SELECT acquisition_status, count(*) FROM attachments GROUP BY 1;\nAC: backfill pass over unfetched attachments where the source payload still contains the bytes; unrecoverable ones marked distinctly from 'unfetched'.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:22:13Z","created_by":"Sinity","updated_at":"2026-07-31T08:22:13Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-bsi7","title":"test_web_reader agent-coordination test is order-dependent: passes alone, fails in a wide selection","description":"FALSE-GREEN AUDIT 2026-07-31 (finding F6). MEASURED.\n\ntests/unit/daemon/test_web_reader.py TestReaderSearchState::test_agent_coordination_endpoint_uses_shared_payload\nfails with KeyError 'root' at test_web_reader.py:894 when run as part of a 31-file selection,\nand PASSES when run alone.\n\n isolated: pytest tests/unit/daemon/test_web_reader.py -k agent_coordination\n -\u003e 3 passed, 174 deselected in 4.50s\n in a 31-file selection (all tests/unit files referencing MaterialOrigin)\n -\u003e FAILED with KeyError 'root'; reproduced 5 consecutive times\n\nThis is cross-test state leakage, not a flake: deterministic in both directions.\n\nWHY IT MATTERS: the default gate is devtools verify with pytest-testmon affected-selection,\nwhich rarely runs this file together with that set, so the pollution is invisible to the\nnormal pre-merge gate. It surfaces only in a broad run.\n\nHOW IT WAS FOUND: it produced a FALSE RED in my own mutation harness. v1 ran pytest with -x\nand read the exit code; this pre-existing failure tripped -x on every run, so all five planted\nmutations looked caught when the runs proved nothing. The harness auditing for false greens\ngenerated a false red.\n\nSTANDING RULE: a mutation-testing or bisect harness must difference against a measured\nbaseline set of failing node ids. An exit code is not evidence, and -x makes any pre-existing\nfailure masquerade as the signal.\n\nAC:\n- Identify the polluting module/fixture (bisect the 31-file selection).\n- Fix the leak at its source, not by reordering or by adding a fixture-reset to the victim.\n- Check whether the 'root' key comes from module-global or process-global state another test\n mutates.\n- Record whether other order-dependent failures exist in a broad run.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:21:55Z","created_by":"Sinity","updated_at":"2026-07-31T08:21:55Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-vid0","title":"1,413 unresolved subagent links; 58 of 85 distinct targets already acquired as raws but never parsed","description":"Forensics 2026-07-31. session_links: 9,333 total, 1,426 unresolved (1,413 subagent: 1,275 claude-code + 138 codex; 12 hermes branch; 1 continuation). The 1,413 subagent rows point at 85 distinct dst_native_ids; 58 of those exist in raw_sessions (acquired but never parsed into sessions) — recoverable by parsing; 27 are absent from capture entirely.\nRepro: SELECT count(*), count(DISTINCT dst_native_id) FROM session_links WHERE resolved_dst_session_id IS NULL AND link_type='subagent';\nAC: the 58 recoverable targets parse and resolve; the 27 unrecoverable are classified (deleted-before-capture vs still-pending) and the census documented.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:21:46Z","created_by":"Sinity","updated_at":"2026-07-31T08:21:46Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-9dtr","title":"test_web_reader agent-coordination test is order-dependent: passes alone, fails in a wide selection","description":"FALSE-GREEN AUDIT 2026-07-31 (finding F6). MEASURED.\n\ntests/unit/daemon/test_web_reader.py::TestReaderSearchState::test_agent_coordination_endpoint_uses_shared_payload\nfails with KeyError: 'root' at test_web_reader.py:894 when run as part of a 31-file selection,\nand PASSES when run alone.\n\n isolated: pytest tests/unit/daemon/test_web_reader.py -k agent_coordination\n -\u003e 3 passed, 174 deselected in 4.50s\n in a 31-file selection (all tests/unit files referencing MaterialOrigin)\n -\u003e FAILED ... KeyError: 'root'\n reproduced 5 consecutive times\n\nThis is cross-test state leakage, not a flake: it is deterministic in both directions.\n\nWHY IT MATTERS BEYOND THE ONE TEST: the default gate is 'devtools verify' with pytest-testmon\naffected-selection, which rarely runs this file together with that set, so the pollution is\ninvisible to the normal pre-merge gate. It surfaces only in a broad run.\n\nHOW IT WAS FOUND (worth recording): it produced a FALSE RED in my own mutation harness. v1 ran\npytest with -x and read the exit code; this pre-existing failure tripped -x on every run, so\nall five planted mutations looked 'caught' when the runs proved nothing. The harness auditing\nfor false greens generated a false red.\n\nSTANDING RULE that came out of it: a mutation-testing or bisect harness must difference against\na measured baseline set of failing node ids. An exit code is not evidence, and -x makes any\npre-existing failure masquerade as the signal.\n\nAC:\n- Identify the polluting module/fixture (bisect the 31-file selection).\n- Fix the leak at its source rather than by reordering or by adding a fixture-reset to the\n victim test.\n- Consider whether the 'root' key is being consumed from module-global or process-global state\n that another test mutates.\n- Record whether other order-dependent failures exist in a broad run (devtools verify --all is\n ~3min/12725 tests per project memory, so a full-order check is affordable).","status":"closed","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:21:15Z","created_by":"Sinity","updated_at":"2026-07-31T10:11:20Z","closed_at":"2026-07-31T10:11:20Z","close_reason":"Duplicate of polylogue-bsi7 (same order-dependent test_web_reader finding, filed minutes apart by two concurrent audit lanes). bsi7 is the survivor; 9dtr's description was merged in by reference.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-3a61","title":"Tautological assertions: three tests that cannot fail","description":"FALSE-GREEN AUDIT 2026-07-31 (findings F7, F8, F9). Read-verified, not mutation-checked.\n\n1) tests/unit/core/test_json.py:169 test_loads_malformed_json_never_silent\n Docstring: 'loads either raises or returns a non-None value; it never silently returns None\n for a non-null JSON input.'\n Body:\n try:\n result = core_json.loads(text)\n _ = result # No assertion needed - successful parse is fine\n except Exception:\n pass # Expected for malformed input\n There is NO assertion. The exact regression the docstring names -- loads() silently\n returning None -- passes. Note the contrast with the test immediately above it (:163),\n which uses pytest.raises and carries an explicit 'Anti-vacuity:' docstring, so the concept\n was understood in this very file.\n FIX: assert result is not None (the docstring's actual claim), keeping the documented\n carve-out that literal JSON 'null' legitimately returns None.\n\n2) tests/unit/core/test_filters_props.py:505 test_provider_filter_exclusion_disjoint\n Docstring: 'Provider inclusion and exclusion should be mutually exclusive.'\n Body computes result = included - excluded over two plain Python sets built from the\n Hypothesis inputs, then asserts members of the difference are not in excluded. That is a\n property of set.__sub__. No SessionFilter, no archive code, nothing from polylogue is\n invoked -- in a module whose subject is production filter properties.\n FIX: build a SessionFilter with those origins/exclusions and assert on .list() output, or\n delete the test.\n\n3) tests/unit/core/test_filters_props.py:791, :804, :816\n test_exclude_provider_and_exclude_tag / test_provider_with_exclude_tag /\n test_multiple_exclude_providers\n These DO call real SessionFilter(...).exclude_origin(...).list(), but every assertion sits\n inside 'for conv in result:' with no cardinality guard. Currently non-vacuous (the\n filter_repo_advanced fixture leaves 1-2 rows), so they are not silently passing today --\n but a regression that made the filter return [] (the total-failure mode) keeps all three\n green.\n FIX: add assert len(result) \u003e= 1 before each loop.\n\nCONTEXT -- suite-wide AST sweep over 12,513 test functions (upper bounds on CANDIDATES, not\ndefect counts; manual sampling found only ~15-20% of each bucket genuine, because this\ncodebase legitimately delegates assertions to shared helpers such as _assert_structured_error):\n 186 functions with zero bare-assert statements\n 137 with only weak asserts (is not None / isinstance / len\u003e=0)\n 238 with all asserts inside a possibly-empty loop \u003c- bucket (3) above\n 63 mock-assert only\n 17 with a swallowing try/except \u003c- bucket (1) above\n\nAC: the three tests above assert something that can fail; the loop-only cluster gets\ncardinality guards; consider whether a cardinality-guard convention belongs in TESTING.md.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:20:49Z","created_by":"Sinity","updated_at":"2026-07-31T08:20:49Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-zn1k","title":"234 stale workflow-artifact sessions (coordinator_session_stream 226 + workflow_run_snapshot 7 + journal counted separately) never reparsed after classification fix","description":"Forensics 2026-07-31. The previously known '172 workflow-artifact sessions' is actually 233 empty sessions today: 226 with artifact_kind=coordinator_session_stream + 7 workflow_run_snapshot (wf_*.json). Producers stopped (max acquired 2026-07-19 / 07-26 respectively; 455 newer coordinator raws stay correctly unparsed), but the classification fix shipped without a SEMANTIC_REPARSE / cleanup so the materialized empties persist.\nRepro: ATTACH source.db; SELECT count(*) FROM sessions s JOIN src.raw_artifacts a ON a.raw_id=s.raw_id WHERE s.message_count=0 AND a.artifact_kind IN ('coordinator_session_stream','workflow_run_snapshot');\nAC: these session rows removed or reparsed under current classification; policy lint that a reclassification shipping without reparse/purge of already-materialized rows fails.","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:19:27Z","created_by":"Sinity","updated_at":"2026-07-31T08:19:27Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-qs4b","title":"schema-versioning lint cannot catch an undeclared semantic classification change (only declaration-completeness)","description":"Investigation triggered by polylogue-lzh8 (PR landing the missing v48\nSEMANTIC_REPARSE declaration for #3088/1e0246d77). The bead asked: PR #3088\nshipped a semantic classification change (origin_specs.py artifact rules,\nchanging parse_as_session for four Claude Workflow artifact kinds) with no\nINDEX_SCHEMA_VERSION bump at all, and `devtools lab policy schema-versioning`\ndid not stop it. Why not, and can it be made to?\n\nFINDING: the lint (devtools/verify_schema_upgrade_lane.py) checks THREE\nthings: (1) no legacy upgrade-shaped helper functions exist under\nstorage/sqlite/, (2) `index_delta_declaration_report(INDEX_SCHEMA_VERSION)`\n-- every version from the compatibility floor up to the CURRENT\nINDEX_SCHEMA_VERSION constant has exactly one valid IndexDeltaDeclaration,\n(3) every INDEX_BENIGN_DDL_REGISTRY entry is an idempotent, non-mutating\nDDL shape. All three are structurally scoped to \"is the declaration table\ninternally consistent with the current version constant\" -- none of them\never inspect polylogue/sources/origin_specs.py, artifact_taxonomy/, or any\nother classification/parser source file, and none of them fire on a diff\nthat changes classification semantics without touching\nINDEX_SCHEMA_VERSION. A commit that changes what parse_as_session resolves\nto for a given artifact kind, without incrementing the version constant, is\ntherefore invisible to this lint by construction: index_delta_declaration_\nreport still reports \"ok\" because the (unchanged) current version still has\nits (already-declared) coverage. The lint can only catch an UNDECLARED\nBUMP, never a MISSING bump.\n\nWhy not fixed inline in polylogue-lzh8's PR: a real fix needs some notion of\n\"this source file changing without a version bump is itself a policy\nviolation\" -- e.g. a content-fingerprint of the classification decision\ntable (origin_specs.py's artifact_rules, artifact_taxonomy's classify_\nartifact) stored per INDEX_SCHEMA_VERSION and diffed at lint time, or a\ngit-diff-based check flagging commits that touch known classification-\nsemantic files without touching lifecycle.py/index.py in the same commit.\nThe former is a genuine architecture addition (a new declared invariant,\nnot a quick patch); the latter is close to the \"fossilized-diff\" check\nshape this repo's testing philosophy explicitly rejects (CLAUDE.md\nVerification section: don't gate on a changed file list). Neither is a\nsmall, local fix -- both need design work and a real value case, not a\nrushed addition riding on an unrelated bead.\n\nDoes NOT block polylogue-lzh8: that bead's job was to declare the missing\nv48 delta for the already-shipped classification fix, which is done\nregardless of whether the lint that should have caught the original miss\ngets strengthened.","acceptance_criteria":"1. Either (a) a designed, low-false-positive mechanism exists that would have caught #3088's undeclared classification change (e.g. a stored content-fingerprint of origin_specs.py/artifact_taxonomy classification tables, versioned and diffed by the lint), and is implemented + wired into devtools lab policy schema-versioning, or (b) the investigation concludes no low-false-positive mechanism is worth building at this time, with the reasoning recorded here and the gap documented in docs/internals.md's Schema Versioning Model section so a future contributor doesn't assume the lint already covers this case. 2. If implemented, devtools lab policy schema-versioning must still pass on the current archive state (post polylogue-lzh8) and a regression test proves it fails when a classification-table change lands without a version bump.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:17:48Z","created_by":"Sinity","updated_at":"2026-07-31T08:17:48Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-ezaq","title":"shipped-but-dead: repair.py stale_supersession_receipts capability is built but unregistered — silently unreachable","description":"Audit 2026-07-31 (shipped-but-dead census). MEASURED, handler dicts opened and read.\n\npolylogue/storage/repair.py builds a complete stale-supersession-receipts repair\ncapability:\n :5777 count_stale_supersession_receipts_sync\n :5786 repair_stale_supersession_receipts (constructs RepairResult(\"stale_supersession_receipts\", ...))\n :5851 preview_stale_supersession_receipts\n\nNone of the three is a key in either dispatch table. REPAIR_HANDLERS and\nPREVIEW_HANDLERS each enumerate exactly these eight targets:\n empty_sessions, message_type_backfill, orphaned_attachments, orphaned_blobs,\n orphaned_messages, session_insights, session_timestamp_backfill,\n superseded_raw_snapshots\n\nrun_safe_repairs dispatches only through REPAIR_HANDLERS, so no CLI or daemon path\ncan reach the capability. It has zero test coverage as well -- fully unexercised.\n\nThe underlying primitive it wraps, raw_retention.py:815 reissue_stale_supersession_receipts,\nIS tested (tests/unit/storage/test_raw_retention.py:2019,2047,2155) and has other\ncallers -- so this is specifically the orchestration/registration layer that was\nnever connected.\n\nThis differs from the other findings in consequence: it is not wasted writes, it\nis a repair the operator believes exists and cannot run.\n\nRelated dead single functions found in the same sweep (lower value, fold in or\nsplit):\n polylogue/storage/repair.py:5165 has_orphaned_messages_sync (zero callers;\n live sibling count_orphaned_messages_sync:5140 has 6+)\n polylogue/storage/sqlite/archive_tiers/ops_write.py:1383 read_mcp_call (zero callers;\n sibling list_mcp_calls:1352 is wired to cli/commands/diagnostics.py:850,866)","acceptance_criteria":"stale_supersession_receipts is registered in REPAIR_HANDLERS and PREVIEW_HANDLERS with a test that reaches it through run_safe_repairs (not by direct import), or the three functions are deleted. has_orphaned_messages_sync and read_mcp_call are deleted or given a caller.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:05:42Z","created_by":"Sinity","updated_at":"2026-07-31T08:05:42Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-y93u","title":"shipped-but-dead: cli/shared/formatting.py run-progress renderer is 10/12 dead, kept alive only by its own tests","description":"Audit 2026-07-31 (shipped-but-dead census). MEASURED, all call sites read.\n\npolylogue/cli/shared/formatting.py defines 12 top-level functions. Only two have\na production caller:\n should_use_plain -\u003e cli/click_app.py:499\n format_sources_summary -\u003e cli/shared/helpers.py:14\n\nThe other ten have zero production callers anywhere in polylogue/ or devtools/:\n :18 plain_forced_by_env :23 no_color_requested\n :34 announce_plain_mode :38 format_cursors\n :71 format_counts :111 format_run_details\n :166 format_plan_counts :185 format_plan_details\n :202 format_index_status :210 format_source_label\n\nTheir only consumers are tests/unit/cli/test_deterministic_output.py and\ntests/unit/cli/test_color_and_layout.py, which import each function directly and\nassert on its string output -- so the suite is green while the renderer reaches\nno CLI output path.\n\nFalse-positive checked and excluded: the one apparent hit for format_counts,\npolylogue/schemas/generation/field_annotations.py:96, is a dict key named\n\"format_counts\", not a call.\n\nTogether this is an entire Acquire/Validate/Sessions/Materialize/Schemas\nrun-progress text renderer that was never wired (or was unwired when output went\nJSON-first) and whose tests now memorialize a dead surface.","acceptance_criteria":"The ten unwired renderers are deleted along with the tests that only exercise them, or the verbose run-progress output is wired to a real CLI path and the tests assert through that path instead of by direct import.","status":"open","priority":2,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:04:19Z","created_by":"Sinity","updated_at":"2026-07-31T08:04:19Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-resk","title":"shipped-but-dead: v2mg kept price_catalogs on a justification that is false in all three named particulars","description":"Audit 2026-07-31 (shipped-but-dead census). CORRECTION to a closed bead.\n\npolylogue-v2mg (CLOSED) dropped model_prices and session_reported_costs as\nzero-consumer tables, and kept price_catalogs. Its justification is quoted\nverbatim in production source at\npolylogue/storage/sqlite/archive_tiers/index_convergence.py:74-77:\n\n \"The sibling price_catalogs table genuinely is read (session_model_usage.\n priced_with FK, active_price_catalog_id) and is kept.\"\n\nMEASURED -- all three named particulars are false:\n\n1. session_model_usage.priced_with -- zero production SELECTs. Every reference is\n an INSERT/UPDATE/NULL-clear in write.py (946,955,962,971,3711,3745,3783,\n 3911,3929,3938,3960), the DDL FK line index.py:1033, or prose. The only\n SELECTs in the entire repo are in tests/unit/storage/test_pricing_chain_roundtrip.py\n (162,283,305).\n2. session_model_usage.priced_at_ms -- same shape; write-only.\n (session_profiles.priced_with / priced_at_ms are write-only too.)\n3. active_price_catalog_id -- pricing_seed.py:105, exported at :155. Its only\n caller in the whole repo is tests/unit/storage/test_pricing_chain_roundtrip.py:146.\n\nA FOREIGN KEY declaration is not a read. price_catalogs itself is read only by\npricing_seed.py, the module that writes it (a seeded-already check).\n\nLive data confirms the column carries no information: of 18,655\nsession_model_usage rows, 10,222 have priced_with set and there is exactly\n1 distinct value.\n\nActual pricing resolution is in-process via\npolylogue.archive.semantic.pricing.PRICING -- exactly the reason v2mg gave for\ndropping model_prices. price_catalogs is the same defect the bead was closing.","acceptance_criteria":"Either price_catalogs + session_model_usage.priced_with/priced_at_ms + session_profiles.priced_with/priced_at_ms gain a real consumer (a cost surface that reports which catalog version priced a row), or they are retired the same way model_prices was, via INDEX_BENIGN_DDL_REGISTRY. The false justification text at index_convergence.py:74-77 is corrected either way, so the next audit does not re-trust it.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:03:56Z","created_by":"Sinity","updated_at":"2026-07-31T08:03:56Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -491,8 +563,8 @@ {"_type":"issue","id":"polylogue-8ifs","title":"Insight-panel HTTP handlers make 'surface errored' indistinguishable from 'genuinely empty'","description":"Silent-degradation audit 2026-07-31. daemon/http.py:3889-3916: timeline/phases/threads panels catch ArchiveInsightUnavailableError → events=[]/phases=[]/all_threads=[] with NO logging; _work_event_panel_payload et al (http.py:989-1046) then compute readiness_tag from bool(events), producing the identical materialized:false/count:0 payload whether the session truly has zero rows or the insight surface errored. (Contrast: the profile branch at :3862 documents why its except is defensive-only.) Fix: logger.warning in each except; add a third readiness state ('unavailable'/'q-error') distinct from 'materialized zero rows'. Verdict: SHOULD-RECORD (log part is trivial MUST). Same theme: daemon/status.py:2793-2805 _archive_debt_status_summary swallows Exception with zero logging → available:False indistinguishable from feature-off (sibling assertion_candidate_queue_status_summary at :2783 logs correctly).","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:49:55Z","created_by":"Sinity","updated_at":"2026-07-31T07:49:55Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-f9kk","title":"Hybrid search silently degrades to 2 lanes when vector provider absent/failing; lane provenance discarded","description":"Silent-degradation audit 2026-07-31. archive/query/retrieval_search.py:~163: vector search failure inside hybrid → logger.warning + vector_results=[]; RRF fusion runs over text+action only while search_hit_surface (archive/query/search_hits.py:103-110) still labels every hit 'hybrid' (label derives from REQUESTED lane, not executed lanes). retrieval_candidates.py:170-176 discards lane_ranks ('results, _lane_ranks = ...'), throwing away the only per-lane provenance computed. api/archive.py:4221-4228,4242-4249: 'with suppress(ValueError, ImportError): create_vector_provider(...)' — no log line at this callsite; inconsistent with pure near: queries which fail loud via RepositoryVectorMixin.search_similar ValueError. Fix: thread lane_ranks/vector_lane_used into SearchEnvelope/MCP payload as a degraded_lanes/advisories field (pattern exists: mcp/server_cutover.py:853-856 archive_evidence_degraded); log the suppressed provider-resolution failure. Verdict: MUST-FAIL-LOUD (response-level signal), SHOULD-RECORD components.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:49:24Z","created_by":"Sinity","updated_at":"2026-07-31T07:49:24Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-d70d","title":"Daemon startup repair failures (FTS trigger restoration, lineage) leave no debt row — warning log only","description":"Silent-degradation audit 2026-07-31. (a) daemon/fts_startup.py:423-462 ensure_fts_startup_readiness_sync: the SIGKILL-recovery/trigger-restoration path (the exact silent-FTS-bypass scenario its own docstring cites, #1242) catches Exception, logs warning, returns; caller discards result; no ops.db row, no health flag. (b) daemon/lineage_startup.py:23-38: repair failure returns 0, indistinguishable from '0 needed, healthy'; caller (daemon/cli.py _run_startup_lineage_readiness) discards the int. Fix: on failure write a convergence-debt/health row (mirror _record_fts_surface_debt already in fts_startup.py) and make the lineage return type distinguish failed from clean. Verdict: SHOULD-RECORD (borderline MUST for the FTS branch). Related: converged-state eviction in daemon/convergence.py erases per-file error_count history once a file converges — emit a daemon event on _mark_barrier_failure so transient failure bursts stay queryable.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:48:50Z","created_by":"Sinity","updated_at":"2026-07-31T07:48:50Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-xwkh","title":"Verify append_ingest.py live path honors the classify_artifact session gate","description":"Follow-up to polylogue-9ykn. While tracing every code path that can turn a raw record into a\nParsedSession destined for write_parsed_session_to_archive (the sole INSERT INTO sessions\nchokepoint), found THREE distinct upstream decision points instead of one:\n\n1. pipeline/services/ingest_worker.py (live daemon ingest, default validation_mode=advisory) --\n already gated by archive.artifact_taxonomy.classify_artifact before calling parse_payload /\n parse_stream_payload.\n2. sources/revision_backfill.py (`_parse_one` / `_parse_stream`, used by\n `polylogue ops reset --index` rebuild replay and historical backfill) -- previously gated ONLY\n by the narrower path-pattern-only artifact_rule_for_path (OriginSpec), NOT the richer content\n classifier; polylogue-9ykn's fix unified this with (1) by sampling the first ~64 records and\n running them through classify_artifact too, so a rebuild can no longer resurrect a phantom the\n live path now refuses.\n3. sources/live/append_ingest.py (`_ingest_append_plans_archive`, live incremental append for a\n growing/watched file, source_index=-1) -- calls dispatch.parse_payload directly with NO\n classify_artifact / artifact_rule_for_path consultation at all.\n\n(3) was NOT touched by polylogue-9ykn's fix, for lack of time to verify it safely. It is very\nlikely safe-by-construction: append plans should only ever be created for a path the watcher's\ndiscovery phase (sources/live/batch.py) already classified as a session stream when it was first\nregistered for incremental-append tracking, so by the time _ingest_append_plans_archive runs, the\nprovider/path pair has already passed the gate once. But this was not empirically verified --\ntrace batch.py's registration path for _AppendPlan and confirm a record that classify_artifact\nwould refuse (or that fails artifact_rule_for_path's session policy) can never reach\n_ingest_append_plans_archive's parse_payload call. If it CAN reach it (e.g. a directory that starts\nproducing a new artifact shape mid-watch, after the file was already registered), wire the same\nclassify_artifact(sample=...) gate used in revision_backfill.py's _is_declared_non_session_artifact\ninto this path too, so all three chokepoints agree.\n\nAdd a regression test proving append-only records that would fail classify_artifact never produce\na session through this path, whichever the finding turns out to be (already-safe -\u003e pin it;\nneeds-a-gate -\u003e add and pin it).","status":"in_progress","priority":2,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T06:28:02Z","created_by":"Sinity","updated_at":"2026-07-31T07:51:20Z","started_at":"2026-07-31T07:51:20Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-uh9l","title":"Wire Claude Workflow artifact coverage into a readiness/repair surface; delete dead SidecarData branch","description":"Follow-up from the 2026-07-31 closure-accuracy audit of polylogue-z9gh.6\n(see that bead's corrective note for full evidence).\n\npolylogue-z9gh.6 claimed \"readiness and repair commands no longer report\nhealthy solely because subagents/workflows is classified as a known\nsidecar\" (its AC5). That is not true today. Two separate coverage\ncomputations exist for Claude Workflow artifacts and neither is consulted\nby any readiness/repair command:\n\n1. assembly_claude_code.py:discover_sidecars's `orchestration_coverage`/\n `orchestration_parse_gaps` (ClaudeOrchestrationCoverage) -- computed into\n SidecarData every ingest pass, never read by anything except its own\n definition site and a struct-level unit test. Dead code.\n2. claude_workflow_materializer.py's ClaudeWorkflowMaterializationSummary.gaps\n -- genuinely computed and logged every daemon convergence pass\n (daemon/convergence_stages.py), but only as an internal log line, not a\n surface an operator or automation can query.\n\nThis bead is scoped narrowly:\n- Either wire branch 1's coverage into something real (a `polylogue check`\n subcommand, an insight, or fold it into branch 2 if redundant) or delete\n it if branch 2 already supersedes it -- decide which, don't keep both.\n- Expose branch 2's gap count through an actual readiness/status surface\n (CLI `polylogue check` output, daemon health endpoint, or equivalent) so\n \"subagents/workflows is a known sidecar\" cannot read as healthy while\n gaps \u003e 0.\n- Add a fixture proving a corrupted/missing journal or attempt\n materialization produces a visible, actionable gap through that surface\n (this was z9gh.6's AC2/AC4, worth re-verifying end-to-end while here).","acceptance_criteria":"1. Exactly one live coverage/gap computation remains for Claude Workflow artifacts (the dead SidecarData branch is either wired up or deleted, not left as parallel dead code). 2. A readiness/repair surface (CLI or daemon status) reports the current gap count, not just a log line. 3. Corrupting/deleting an expected journal or attempt sidecar in a fixture produces a visible, actionable gap through that surface. 4. Focused test coverage for the surface, not just the underlying struct.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T06:00:44Z","created_by":"Sinity","updated_at":"2026-07-31T06:00:44Z","dependencies":[{"issue_id":"polylogue-uh9l","depends_on_id":"polylogue-z9gh.6","type":"related","created_at":"2026-07-31T08:00:56Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-xwkh","title":"Verify append_ingest.py live path honors the classify_artifact session gate","description":"Follow-up to polylogue-9ykn. While tracing every code path that can turn a raw record into a\nParsedSession destined for write_parsed_session_to_archive (the sole INSERT INTO sessions\nchokepoint), found THREE distinct upstream decision points instead of one:\n\n1. pipeline/services/ingest_worker.py (live daemon ingest, default validation_mode=advisory) --\n already gated by archive.artifact_taxonomy.classify_artifact before calling parse_payload /\n parse_stream_payload.\n2. sources/revision_backfill.py (`_parse_one` / `_parse_stream`, used by\n `polylogue ops reset --index` rebuild replay and historical backfill) -- previously gated ONLY\n by the narrower path-pattern-only artifact_rule_for_path (OriginSpec), NOT the richer content\n classifier; polylogue-9ykn's fix unified this with (1) by sampling the first ~64 records and\n running them through classify_artifact too, so a rebuild can no longer resurrect a phantom the\n live path now refuses.\n3. sources/live/append_ingest.py (`_ingest_append_plans_archive`, live incremental append for a\n growing/watched file, source_index=-1) -- calls dispatch.parse_payload directly with NO\n classify_artifact / artifact_rule_for_path consultation at all.\n\n(3) was NOT touched by polylogue-9ykn's fix, for lack of time to verify it safely. It is very\nlikely safe-by-construction: append plans should only ever be created for a path the watcher's\ndiscovery phase (sources/live/batch.py) already classified as a session stream when it was first\nregistered for incremental-append tracking, so by the time _ingest_append_plans_archive runs, the\nprovider/path pair has already passed the gate once. But this was not empirically verified --\ntrace batch.py's registration path for _AppendPlan and confirm a record that classify_artifact\nwould refuse (or that fails artifact_rule_for_path's session policy) can never reach\n_ingest_append_plans_archive's parse_payload call. If it CAN reach it (e.g. a directory that starts\nproducing a new artifact shape mid-watch, after the file was already registered), wire the same\nclassify_artifact(sample=...) gate used in revision_backfill.py's _is_declared_non_session_artifact\ninto this path too, so all three chokepoints agree.\n\nAdd a regression test proving append-only records that would fail classify_artifact never produce\na session through this path, whichever the finding turns out to be (already-safe -\u003e pin it;\nneeds-a-gate -\u003e add and pin it).","status":"closed","priority":2,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T06:28:02Z","created_by":"Sinity","updated_at":"2026-07-31T08:18:09Z","started_at":"2026-07-31T07:51:20Z","closed_at":"2026-07-31T08:18:09Z","close_reason":"Closed the third chokepoint: append_ingest.py now applies revision_backfill._is_declared_non_session_artifact (same classify_artifact/artifact_rule_for_path gate the other two chokepoints use) to the decoded record sample before calling parse_payload. Empirically verified before the fix: a declared non-session artifact (workflow_journal.jsonl) reaching append tracking did NOT leak a phantom session (parse_retained_raw_sessions' existing gate during replay accidentally protected it via a 'did not replay to exactly one session' RuntimeError), but wasted a raw write + parse + crash-shaped failure log on every observation forever since a failed append never advances its cursor. Now refuses cleanly up front. Regression test test_live_append_refuses_declared_non_session_artifact added (tests/unit/storage/test_raw_revision_authority.py), plus the two pre-existing live-append tests confirm real Codex session appends are unaffected. devtools test tests/unit/storage/test_raw_revision_authority.py -k test_live_append: 3 passed.","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-uh9l","title":"Wire Claude Workflow artifact coverage into a readiness/repair surface; delete dead SidecarData branch","description":"Follow-up from the 2026-07-31 closure-accuracy audit of polylogue-z9gh.6\n(see that bead's corrective note for full evidence).\n\npolylogue-z9gh.6 claimed \"readiness and repair commands no longer report\nhealthy solely because subagents/workflows is classified as a known\nsidecar\" (its AC5). That is not true today. Two separate coverage\ncomputations exist for Claude Workflow artifacts and neither is consulted\nby any readiness/repair command:\n\n1. assembly_claude_code.py:discover_sidecars's `orchestration_coverage`/\n `orchestration_parse_gaps` (ClaudeOrchestrationCoverage) -- computed into\n SidecarData every ingest pass, never read by anything except its own\n definition site and a struct-level unit test. Dead code.\n2. claude_workflow_materializer.py's ClaudeWorkflowMaterializationSummary.gaps\n -- genuinely computed and logged every daemon convergence pass\n (daemon/convergence_stages.py), but only as an internal log line, not a\n surface an operator or automation can query.\n\nThis bead is scoped narrowly:\n- Either wire branch 1's coverage into something real (a `polylogue check`\n subcommand, an insight, or fold it into branch 2 if redundant) or delete\n it if branch 2 already supersedes it -- decide which, don't keep both.\n- Expose branch 2's gap count through an actual readiness/status surface\n (CLI `polylogue check` output, daemon health endpoint, or equivalent) so\n \"subagents/workflows is a known sidecar\" cannot read as healthy while\n gaps \u003e 0.\n- Add a fixture proving a corrupted/missing journal or attempt\n materialization produces a visible, actionable gap through that surface\n (this was z9gh.6's AC2/AC4, worth re-verifying end-to-end while here).","acceptance_criteria":"1. Exactly one live coverage/gap computation remains for Claude Workflow artifacts (the dead SidecarData branch is either wired up or deleted, not left as parallel dead code). 2. A readiness/repair surface (CLI or daemon status) reports the current gap count, not just a log line. 3. Corrupting/deleting an expected journal or attempt sidecar in a fixture produces a visible, actionable gap through that surface. 4. Focused test coverage for the surface, not just the underlying struct.","status":"closed","priority":2,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T06:00:44Z","created_by":"Sinity","updated_at":"2026-07-31T09:01:23Z","started_at":"2026-07-31T09:00:56Z","closed_at":"2026-07-31T09:01:23Z","close_reason":"Both branches resolved. AC1 (exactly one live coverage/gap computation):\ndeleted the dead branch (assembly_claude_code.py:discover_sidecars's\norchestration_artifacts/orchestration_coverage/orchestration_parse_gaps and\ninventory_claude_orchestration_artifacts/ClaudeOrchestrationCoverage in\nparsers/claude/orchestration.py) -- confirmed by grep it was consumed by\nnothing except its own definition and a struct-level unit test; the whole\ndiscover_sidecars orchestration sub-block was unused (not just coverage --\nscope note: the bead named coverage/parse_gaps specifically, but\norchestration_artifacts turned out equally dead on inspection, same\ndisease, deleted alongside). materialize_claude_workflow_archive's gap\ntracking (branch 2, already running every convergence pass) is now the\nsole computation.\n\nAC2 (readiness/repair surface reports the gap count): daemon/\nconvergence_stages.py's claude_workflow stage now persists each\nmaterialization summary into ops.db's existing daemon_stage_events table\n(no schema change -- record_daemon_stage_event already existed and is used\nby other stages) via a new\n_record_claude_workflow_stage_event() call in execute(). readiness/\n__init__.py's run_archive_readiness() reads it back through a new\nclaude_workflow_materialization_status() helper (storage/archive_readiness.py)\nand registers a \"claude_workflow_materialization\" ReadinessCheck --\nthe exact function `polylogue doctor` already calls via get_readiness(), so\nno CLI/renderer changes were needed for it to surface.\n\nAC3 (corruption produces a visible, actionable gap through the surface):\nnew integration test\ntest_claude_workflow_convergence_stage_surfaces_gap_through_readiness\ndrives the actual production callers end-to-end against the\nwf_54d4fb2e-841 fixture -- ConvergenceStage.execute() (what the daemon\ninvokes every pass) then get_readiness() (what doctor calls). Deleting one\nretained metadata sidecar flips the check OK-\u003eWARNING with the specific gap\ntext in check.details. Not just the materializer's own summary struct in\nisolation.\n\nAC4 (focused test coverage for the surface): the above integration test\nplus two new unit tests in tests/unit/storage/test_archive_readiness.py\ncovering claude_workflow_materialization_status's missing-ops.db and\nread-back paths.\n\nVerification: devtools test tests/integration/test_claude_workflow_admission.py\ntests/unit/storage/test_archive_readiness.py tests/unit/daemon/test_convergence_stages.py\ntests/unit/cli/test_convergence_surface_contract.py tests/unit/cli/test_check.py -\u003e\n191 passed (combined with xyel's changed files). mypy --strict (dmypy) clean.\ndevtools verify --quick -\u003e 20/20 steps green. devtools render all --check -\u003e OK.\nLanding on branch feature/cleanup/dead-coverage-and-session-refs.","dependencies":[{"issue_id":"polylogue-uh9l","depends_on_id":"polylogue-z9gh.6","type":"related","created_at":"2026-07-31T08:00:56Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-2vor","title":"session_commit.py typed-evidence gaps: PR #0 coercion, cross-repo number collision, foreign-trailer false-disagreement","description":"Follow-up from CodeRabbit review on PR #3425 (fix/insights/session-commit-typed-evidence). Three P2 findings left unaddressed at merge time, filed here rather than blocking the merge of otherwise-complete, tested typed-evidence wiring:\n\n1. polylogue/insights/session_commit.py (typed_refs_from_session_refs, around L785) - a session_refs row with a valid url/repo but no ref_number (observed for Codex Cloud's chatgpt_codex_sidecar._pull_request_ref(), which stores external_pull_request_id in url and leaves repo/number unset) coerces to PR #0 instead of being skipped or parsed from the URL. Since typed refs are authoritative over the regex fallback, this can suppress a correctly-parsed regex result with a bogus PR #0.\n2. polylogue/insights/session_commit.py (disagreement detection, around L739) - PR/issue identity comparison uses only the bare number, not (owner, repo, number). acme/product#42 vs other/repo#42 compare equal, so a real disagreement across differently-named repos is not surfaced.\n3. polylogue/insights/session_commit.py (foreign-trailer classification, around L500) - when the current session has no bridge_session_ids (own_trailer_tokens is empty), every commit carrying any Claude-Session trailer is labeled as naming a foreign session, producing a disagreement even though there is no typed identity to actually compare against.\n\nAcceptance: (1) a session_refs row lacking ref_number is skipped or its number is parsed from url rather than defaulting to 0; (2) disagreement comparison uses full (owner,repo,number) identity, not bare number, when repo-qualified; (3) foreign-trailer disagreement classification is gated on having at least one own bridge/trailer token to compare against. Regression test per fix.","notes":"Implemented in PR #3434 (feature/test/mock-scaffolding-extract). Fix 1: typed_refs_from_session_refs() now parses a real number from a genuine github.com PR/issue URL when the row's number is absent, else skips the row (no more PR #0 coercion). Fix 2: new _refs_match() compares full (owner, repo, number) identity when both refs are repo-qualified, falling back to number-only equality otherwise. Fix 3: foreign_trailer now additionally requires own_trailer_tokens non-empty (no disagreement fabricated when the session has no bridge identity of its own). Regression test added per finding in tests/unit/insights/test_session_commit.py. Verification: devtools test tests/unit/insights/test_session_commit.py -\u003e 46 passed; also ran consumers tests/unit/cli/test_correlate_view.py tests/unit/storage/test_archive_tiers_write.py -\u003e 80 passed; mypy --strict + devtools verify --quick clean.","status":"closed","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T05:57:08Z","created_by":"Sinity","updated_at":"2026-07-31T08:26:53Z","closed_at":"2026-07-31T08:26:53Z","close_reason":"Merged in PR #3434: all three findings fixed with regression tests (PR#0 coercion, cross-repo identity comparison, foreign-trailer false disagreement).","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-upbv","title":"Temporary-chat tabs never show accurate archive-state (always 'missing')","description":"browser-extension: background.js's conversationIdForUrl returns TEMPORARY_CHAT_SENTINEL for a ChatGPT temporary-chat URL (fixed in PR #3411 to unblock automatic capture at all). Multiple call sites (refreshActiveTabArchiveState, captureTab's pageSessionId derivation) query /v1/archive-state and log ledger/UI state keyed by that sentinel rather than the conversation's real ephemeral provider_session_id (only known after a successful capture's envelope). Net effect: a temporary chat's popup/badge 'captured' indicator never turns accurate, and refreshActiveTabArchiveState's auto_capture_missing branch re-fires every ~30s (throttled) treating an already-captured temporary chat as missing. Not a data-loss bug (content-hash dedup makes the redundant re-captures cheap/idempotent), but real UI inaccuracy and wasted background work. Fix requires giving background.js a per-tab 'last known real captured id' to prefer over the sentinel at every archive-state query site, not just the ones fixed in #3411 (freshness-hint mismatch, captureTab's own pageSessionId). Found during PR #3411 Codex review (P1 finding), partially fixed there (freshness-hint rejection, which WAS a real data-loss bug, and captureTab's own log/state precedence); this bead tracks the remaining archive-state-query-site work.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T05:24:44Z","created_by":"Sinity","updated_at":"2026-07-31T05:24:44Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-qqi1","title":"read --view summary silently falls through to transcript","description":"MEASURED 2026-07-31 while rendering sessions to /realm/inbox/polylogue_renders/.\n\nFor every session rendered, summary.md is BYTE-IDENTICAL to transcript.md:\n conversation_relationships summary 967,558 B == transcript 967,558 B\n 019f12b5-1a85 (135k msgs) summary 190,075,729 B == transcript 190,075,729 B\n 019ce460-6914 (175 msgs) summary 406,924 B == transcript 406,924 B\n\nread --views documents summary as: 'Compact human browse view for matched\nsessions', projection=sessions, body=full. A 190 MB 'compact browse view' is\nnot compact -- the view is silently falling through to the transcript renderer\nrather than producing a session-level summary.\n\nReproduce:\n env -u POLYLOGUE_ARCHIVE_ROOT polylogue --id \u003csession_id\u003e read --view summary --format markdown --to stdout\n\nNote this is the same defect FAMILY as the rest of tonight's findings: a\ndeclared behaviour silently degrading to a different one with no error. The\ncaller cannot tell the summary view did not run.\n\nAC: summary renders a session-level summary distinct from transcript, or the\nview is removed; a test pins that summary output is materially smaller than\ntranscript for a multi-message session.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T05:19:52Z","created_by":"Sinity","updated_at":"2026-07-31T05:19:52Z","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -710,7 +782,7 @@ {"_type":"issue","id":"polylogue-5q2u","title":"Order rebuild replay by lineage to avoid deferred-tail amplification","description":"polylogue-3wb's graph_resolve tail latency (260s for codex-session:019d4e in one rebuild batch) is caused by the #2467 deferred-tail-extraction path: when a session's children (resumes/forks) are replayed before their parent during a rebuild, each child is stored WHOLE (a full duplicate of the eventual shared prefix). When the parent finally arrives, _resolve_session_graph must walk every orphaned child and normalize it (delete the duplicate prefix rows, remap session_events refs, delete prefix-scoped dependents) -- O(orphaned_children x shared_prefix_size) real row-mutation work, confirmed linear (not quadratic) via tests/benchmarks/test_graph_resolve_deferred_tail.py.","design":"Root cause pinpointed to polylogue/sources/revision_backfill.py:136 (approximate, verify current line): 'for logical_key in sorted(logical_keys):' -- a lexicographic string sort with zero relationship to parent/child lineage. During a full/cold rebuild this guarantees children are processed before parents roughly as often as not, maximizing how often the expensive deferred-tail path triggers. The census phase (same function, lines ~77-129) already parses and spills every session via _parse_retained_raw before the replay loop runs, so ParsedSession.parent_session_provider_id is available cheaply at that point without re-parsing. Proposed fix: after computing logical_keys, build a lineage-aware processing order -- roots (no parent_session_provider_id, or parent not present in this rebuild's logical_keys set) first, then children whose parent's logical_key has already been replayed, falling back to the current lexicographic order for any remaining/cyclic/unresolvable cases so nothing is ever skipped. This is a scheduling-only change (must not alter what gets adopted/replayed, only the order), so it needs careful test coverage proving replay outcome parity (accepted_raw_ids, adopted sessions, quarantine/defer decisions) is identical to the current lexicographic order for a representative fixture, with only wall-clock/call-count differing. Investigated and ruled out as NOT worth pursuing: batching multiple children's SQL into fewer statements, and range-query vs IN-list restructuring inside _reextract_prefix_tail_db -- both measured within 10% of current cost, confirming the expense is real B-tree mutation work bound by row count, not query-shape overhead.","acceptance_criteria":"1. A fixture/benchmark proves lineage-aware ordering reduces (or eliminates) the number of _resolve_session_graph calls that hit the deferred-tail/orphaned-child path for a representative parent-with-many-resumes archive, without changing which raw revisions get adopted. 2. Replay outcome parity: accepted_raw_ids/adoption/quarantine decisions are byte-identical to the current lexicographic-order baseline for the same input on a differential test. 3. No change weakens canonical rebuild correctness -- cycles, missing/external parents, and cross-batch parents (not in this rebuild's logical_keys) degrade gracefully to the current behavior, never skip a session. 4. Focused tests plus devtools verify --quick land together.","notes":"Split out of polylogue-3wb after evidence-gathering (tests/benchmarks/test_graph_resolve_deferred_tail.py) confirmed the graph_resolve cost is linear in orphaned-child count (5 children=0.76s, 40 children=6.19s, ratio 8.1x for 8x children) and is genuine per-child row-mutation work, not an accidental quadratic bug or a missing-index gap (every SQL statement in the path already uses an index per EXPLAIN QUERY PLAN, confirmed against the live archive, except web_content_constructs which polylogue-rgbj fixed -- though Codex sessions like 019d4e don't populate that table, so rgbj's fix doesn't explain the original evidence). This bead owns the actual latency-reduction lever: cutting how often the expensive path triggers by scheduling rebuild replay in lineage order instead of lexicographic order.","status":"open","priority":2,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-12T02:43:59Z","created_by":"Sinity","updated_at":"2026-07-12T02:43:59Z","labels":["area:storage","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-5q2u","depends_on_id":"polylogue-3wb","type":"relates-to","created_at":"2026-07-12T04:43:59Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-5q2u","depends_on_id":"polylogue-b5l","type":"parent-child","created_at":"2026-07-15T01:23:12Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-yla8.8","title":"Bound complete-prefix verification cost","description":"The yla8.6 correctness repair authenticates every previously accepted byte before an append route, because bounded tails and ordinary file stat fields cannot prove an arbitrary earlier prefix unchanged. This changes append planning from bounded-tail I/O to O(accepted-prefix bytes). On 2026-07-11 production evidence, the largest cursor is 442,201,540 bytes and sha256sum over that file took 2.90 s wall / 1.10 s user on sinnix-prime; the actively growing root session was 68-77 MB. The correctness invariant must not be weakened, but scheduler latency and cumulative read amplification now require a measured budget.","design":"Instrument accepted-prefix verification bytes and duration per path (the byte counter already exists), then measure real daemon batches and bound scheduling impact. Evaluate only designs that preserve arbitrary-prefix authority: kernel/filesystem change evidence with explicit portability fallback, authenticated chunk/checkpoint structures whose dirty-region discovery is itself authoritative, or coalescing/quiet-window policy that reduces how often proof runs. Sampling, bounded tails, mtime/ctime, or self-authorized test registries are not acceptable substitutes. Keep the current sequential proof as the fail-safe fallback.","acceptance_criteria":"A production-like corpus including 77 MB and 442 MB JSONL paths reports verification bytes, duration, read amplification, and batch latency; a documented budget is enforced or surfaced by daemon telemetry; the chosen optimization preserves the rewrite-before-tail-plus-growth mutation proof and falls back to exact sequential verification when stronger change evidence is unavailable; removing arbitrary-prefix verification makes the adversarial test fail; no polling loop repeatedly hashes unchanged files.","notes":"Baseline measurement: 442,201,540-byte Codex JSONL, sha256sum elapsed=2.90s user=1.10s sys=0.17s maxrss=3072KiB. Census receipt /realm/tmp/polylogue-yla8-6-premerge-census.json.","status":"open","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-11T20:33:03Z","created_by":"Sinity","updated_at":"2026-07-11T20:33:03Z","labels":["area:daemon","area:performance","area:sources","area:storage","area:test","delivery:A-trust-floor","horizon:frontier","lane:operational-resilience","spine"],"dependencies":[{"issue_id":"polylogue-yla8.8","depends_on_id":"polylogue-yla8","type":"parent-child","created_at":"2026-07-11T22:33:02Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-yla8.8","depends_on_id":"polylogue-yla8.6","type":"discovered-from","created_at":"2026-07-11T22:33:04Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-c3qh","title":"Lint pytest timeout overrides against the bounded exception policy","description":"The managed runner establishes a repository-wide 300-second pytest-timeout default, but Polylogue has no quick/static gate over explicit @pytest.mark.timeout(...) or devtools --timeout overrides. Add a narrow AST/static policy verifier rather than making the containment supervisor own source-policy scanning.","design":"Register a normal devtools verify command and quick-gate step. Parse test decorators and managed pytest command literals structurally; reject zero, negative, dynamic, or malformed overrides. Inventory values above the repository default behind a small rationale-bearing manifest so exceptional budgets remain reviewable. Do not infer timeouts from prose or grep generated files.","acceptance_criteria":"1. devtools verify --quick runs the timeout-override policy gate. 2. The gate rejects unbounded, non-positive, dynamic, and malformed pytest timeout overrides. 3. Overrides above the repository default require a path/value/rationale manifest entry, and stale entries fail. 4. Focused tests mutate each production rule and prove the gate fails non-vacuously.","notes":"2026-07-12 Terra lane: isolated worktree /realm/worktrees/polylogue-c3qh, branch feature/test/timeout-override-policy. Own timeout override policy verifier, command registration/manifest, focused mutation tests; avoid provider parsers and storage authority. Coordinator reviews/merges.","status":"closed","priority":2,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-11T18:52:10Z","created_by":"Sinity","updated_at":"2026-07-12T00:02:00Z","started_at":"2026-07-11T23:10:02Z","closed_at":"2026-07-12T00:02:00Z","close_reason":"Merged PR #2721 (50378f24c): bounded AST policy for explicit pytest timeout overrides, 50 focused production-command tests and 14/14 quick gate; adversarial review converged.","labels":["area:devtools","area:test","delivery:A-trust-floor","lane:test-infrastructure"],"dependencies":[{"issue_id":"polylogue-c3qh","depends_on_id":"polylogue-lxyt","type":"discovered-from","created_at":"2026-07-11T20:52:10Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-37t.22","title":"Expose durable context-delivery receipts through authenticated surfaces","description":"PR #2703 adds the durable user-tier v5 context-delivery ledger, but no current API/MCP/CLI surface records or retrieves those receipts. Compilation is now distinct from storage; the product still needs an authenticated delivery boundary that persists the exact image and lets operators resolve it.","design":"CURRENT SUBSTRATE (verified 2026-07-11): PR #2703 owns the durable user-v5 context_deliveries table and polylogue/storage/sqlite/archive_tiers/context_delivery_write.py. That implementation is stronger than the recovered Branch 20 copy: recipient_ref is required, stored JSON fails closed, delivered_by_ref is a validated agent/user ref, record/image refs are cross-checked, and exact retry compares the complete immutable delivery identity. Preserve that schema and storage behavior; this bead adds product and surface adapters, not another migration or ledger.\n\nIMPLEMENTATION:\n1. Add current-schema API adapters in polylogue/api/archive.py: internal write/read/list helpers plus record_context_delivery(), compile_and_record_context(), get_context_delivery(), and list_context_deliveries(). Adapt recovered session_ref call sites to the canonical required recipient_ref. A public delivery method must return the exact image it records so the call itself is the named delivery boundary; compilation alone remains non-evidence.\n2. Add shared surface contracts in polylogue/surfaces/payloads.py. Exact get returns ContextDeliveryPayload with image, digest, recipient, actor, run, boundary, inheritance, segment/evidence/assertion refs, omissions, caveats, metadata, timestamp, and recorded|idempotent outcome. List returns a bounded summary payload WITHOUT full context_image/text; an authorized exact get is required for content disclosure.\n3. MCP: add deliver_context to authenticated write capability; add get_context_delivery and list_context_deliveries under the explicit read/disclosure policy. Bind delivered_by_ref from the authenticated server principal/capability context. A caller parameter is audit input at most and can never select or elevate authority. Candidate judgment remains separately gated by 37t.12 review authority.\n4. CLI: extend the existing query-first context-image/read path in polylogue/cli/query_verbs.py rather than adding a new root command. An explicit delivery form (for example read --view context-image --deliver-to \u003csession-ref\u003e --delivery-boundary \u003cname\u003e with optional run ref) compiles, records, and renders the same image. Exact receipt get/list are read views over the shared payloads and obey the same summary/full disclosure split.\n5. Reuse current context_snapshot_record_from_image() and the v5 storage helpers. Do not copy the recovered migration or recovered context_delivery_write.py: it allowed optional session_ref, tolerated corrupt stored JSON as empty containers, and had weaker identity validation.\n6. Register every MCP tool in tests/infra/mcp.py::EXPECTED_TOOL_NAMES and tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT; update role discovery, routing inventory, OpenAPI/CLI output schemas, MCP reference, and topology/generated surfaces required by the actual file additions.\n7. Keep a single transaction per receipt write and preserve exact-drift refusal across every adapter. API/MCP/CLI errors must distinguish unauthorized, not found, disclosure denied, invalid ref, and immutable drift rather than returning empty success.\n\nPRIMARY FILES: polylogue/api/archive.py; polylogue/surfaces/payloads.py; polylogue/cli/query_verbs.py; polylogue/cli/commands/status.py; polylogue/mcp/{server_tools.py,server_mutation_tools.py,server_support.py}; tests/infra/mcp.py; tests/unit/{api,cli,mcp,storage}/ plus generated contract surfaces.","acceptance_criteria":"1. CURRENT-SCHEMA ADAPTATION: no durable migration or context-delivery table change is introduced. All adapters use required recipient_ref and the current strict v5 write/read/list helpers. A stored malformed JSON field fails closed rather than degrading to an empty list/object.\n2. REAL DELIVERY: an authenticated API, MCP, and CLI context-delivery call compiles one bounded ContextImage, returns that exact image, and persists a receipt with matching canonical bytes/digest, recipient, authenticated actor, run, boundary, inheritance, refs, omissions, caveats, metadata, and timestamp. Removing the record call makes the real-route test fail.\n3. IDEMPOTENCY/DRIFT: replaying the identical surface request returns idempotent and leaves one row. Changing image bytes or any immutable identity field is rejected through API, MCP, and CLI before a second row or mutation occurs.\n4. AUTHORITY: ordinary read cannot record; caller-supplied delivered_by_ref/actor text cannot acquire write or review capability and cannot override the authenticated actor recorded in the receipt. Candidate review authority remains independent per 37t.12. Role-specific MCP discovery proves the boundary.\n5. DISCLOSURE: list_context_deliveries is bounded and returns summaries without context_image/text. Exact get returns full content only when the requester satisfies the disclosure policy for that receipt/recipient. Unauthorized and unrelated-ref probes return typed refusal, not empty success or leaked text.\n6. FILTER/PARITY: exact get plus list filters for recipient, run, and assertion ref agree across API/MCP/CLI on ordering, counts, and refs. Missing snapshot and invalid-ref behavior is contract-tested.\n7. CONTRACT REGISTRIES: EXPECTED_TOOL_NAMES, TOOL_CONTRACT, routing inventory, generated schemas/references, topology projection, and role-specific tool snapshots include the new surfaces with no unclassified tool.\n8. VERIFICATION: devtools test tests/unit/storage/test_context_delivery_write.py tests/unit/api/test_facade_contracts.py tests/unit/cli/test_query_verbs_runtime.py tests/unit/mcp/test_tool_contracts.py tests/unit/mcp/test_tool_discovery.py tests/unit/mcp/test_envelope_contracts.py; add and run focused context-delivery API/CLI/MCP files; devtools verify --quick. Record exact pass counts and a scratch user-v5 end-to-end receipt round trip in notes.","notes":"[Branch 20 source assimilation, 2026-07-11] Portable candidate code exists for API write/read/list helpers, ContextDeliveryPayload/ListPayload, compile_and_record_context(), and MCP deliver_context. It is useful as a call-shape reference only. No matching surface tests, MCP expected-name rows, TOOL_CONTRACT rows, generated-schema updates, CLI delivery surface, or MCP receipt get/list tools were recovered. Its storage/migration copy is rejected in favor of current #2703: it used optional session_ref, forgiving corrupt-JSON reads, a default self-asserted actor, and weaker field validation. Its list payload also exposed every full context image, contrary to this bead's disclosure AC. Do not treat the recovered deterministic proof report as proof of authenticated surface wiring.\nVERIFICATION (group3 sweep): LIVE. Checked: storage substrate real (write_context_delivery/read_context_delivery in archive_tiers/context_delivery_write.py, get_context_delivery in api/archive.py, MCPContextDeliveryPayload in mcp/payloads.py) but rg confirms get_context_delivery/write_context_delivery are called ONLY from tests/unit/api/test_facade_contracts.py -- no MCP tool and no CLI command actually invokes compile-and-record or the read path in production. AC2 (authenticated API+MCP+CLI delivery call) is not satisfied; only the API-facade plumbing exists. Matches own 2026-07-11 note that recovered branch code lacked matching surface tests/MCP rows/CLI wiring. Not stale.","status":"open","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-11T11:58:51Z","created_by":"Sinity","updated_at":"2026-07-31T05:51:13Z","labels":["area:api","area:cli","area:context","area:mcp","delivery:D-agent-context-coordination","horizon:frontier","lane:agent-coordination","lane:context-memory"],"dependencies":[{"issue_id":"polylogue-37t.22","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-11T13:58:50Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-37t.22","title":"Expose durable context-delivery receipts through authenticated surfaces","description":"PR #2703 adds the durable user-tier v5 context-delivery ledger, but no current API/MCP/CLI surface records or retrieves those receipts. Compilation is now distinct from storage; the product still needs an authenticated delivery boundary that persists the exact image and lets operators resolve it.","design":"CURRENT SUBSTRATE (verified 2026-07-11): PR #2703 owns the durable user-v5 context_deliveries table and polylogue/storage/sqlite/archive_tiers/context_delivery_write.py. That implementation is stronger than the recovered Branch 20 copy: recipient_ref is required, stored JSON fails closed, delivered_by_ref is a validated agent/user ref, record/image refs are cross-checked, and exact retry compares the complete immutable delivery identity. Preserve that schema and storage behavior; this bead adds product and surface adapters, not another migration or ledger.\n\nIMPLEMENTATION:\n1. Add current-schema API adapters in polylogue/api/archive.py: internal write/read/list helpers plus record_context_delivery(), compile_and_record_context(), get_context_delivery(), and list_context_deliveries(). Adapt recovered session_ref call sites to the canonical required recipient_ref. A public delivery method must return the exact image it records so the call itself is the named delivery boundary; compilation alone remains non-evidence.\n2. Add shared surface contracts in polylogue/surfaces/payloads.py. Exact get returns ContextDeliveryPayload with image, digest, recipient, actor, run, boundary, inheritance, segment/evidence/assertion refs, omissions, caveats, metadata, timestamp, and recorded|idempotent outcome. List returns a bounded summary payload WITHOUT full context_image/text; an authorized exact get is required for content disclosure.\n3. MCP: add deliver_context to authenticated write capability; add get_context_delivery and list_context_deliveries under the explicit read/disclosure policy. Bind delivered_by_ref from the authenticated server principal/capability context. A caller parameter is audit input at most and can never select or elevate authority. Candidate judgment remains separately gated by 37t.12 review authority.\n4. CLI: extend the existing query-first context-image/read path in polylogue/cli/query_verbs.py rather than adding a new root command. An explicit delivery form (for example read --view context-image --deliver-to \u003csession-ref\u003e --delivery-boundary \u003cname\u003e with optional run ref) compiles, records, and renders the same image. Exact receipt get/list are read views over the shared payloads and obey the same summary/full disclosure split.\n5. Reuse current context_snapshot_record_from_image() and the v5 storage helpers. Do not copy the recovered migration or recovered context_delivery_write.py: it allowed optional session_ref, tolerated corrupt stored JSON as empty containers, and had weaker identity validation.\n6. Register every MCP tool in tests/infra/mcp.py::EXPECTED_TOOL_NAMES and tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT; update role discovery, routing inventory, OpenAPI/CLI output schemas, MCP reference, and topology/generated surfaces required by the actual file additions.\n7. Keep a single transaction per receipt write and preserve exact-drift refusal across every adapter. API/MCP/CLI errors must distinguish unauthorized, not found, disclosure denied, invalid ref, and immutable drift rather than returning empty success.\n\nPRIMARY FILES: polylogue/api/archive.py; polylogue/surfaces/payloads.py; polylogue/cli/query_verbs.py; polylogue/cli/commands/status.py; polylogue/mcp/{server_tools.py,server_mutation_tools.py,server_support.py}; tests/infra/mcp.py; tests/unit/{api,cli,mcp,storage}/ plus generated contract surfaces.","acceptance_criteria":"1. CURRENT-SCHEMA ADAPTATION: no durable migration or context-delivery table change is introduced. All adapters use required recipient_ref and the current strict v5 write/read/list helpers. A stored malformed JSON field fails closed rather than degrading to an empty list/object.\n2. REAL DELIVERY: an authenticated API, MCP, and CLI context-delivery call compiles one bounded ContextImage, returns that exact image, and persists a receipt with matching canonical bytes/digest, recipient, authenticated actor, run, boundary, inheritance, refs, omissions, caveats, metadata, and timestamp. Removing the record call makes the real-route test fail.\n3. IDEMPOTENCY/DRIFT: replaying the identical surface request returns idempotent and leaves one row. Changing image bytes or any immutable identity field is rejected through API, MCP, and CLI before a second row or mutation occurs.\n4. AUTHORITY: ordinary read cannot record; caller-supplied delivered_by_ref/actor text cannot acquire write or review capability and cannot override the authenticated actor recorded in the receipt. Candidate review authority remains independent per 37t.12. Role-specific MCP discovery proves the boundary.\n5. DISCLOSURE: list_context_deliveries is bounded and returns summaries without context_image/text. Exact get returns full content only when the requester satisfies the disclosure policy for that receipt/recipient. Unauthorized and unrelated-ref probes return typed refusal, not empty success or leaked text.\n6. FILTER/PARITY: exact get plus list filters for recipient, run, and assertion ref agree across API/MCP/CLI on ordering, counts, and refs. Missing snapshot and invalid-ref behavior is contract-tested.\n7. CONTRACT REGISTRIES: EXPECTED_TOOL_NAMES, TOOL_CONTRACT, routing inventory, generated schemas/references, topology projection, and role-specific tool snapshots include the new surfaces with no unclassified tool.\n8. VERIFICATION: devtools test tests/unit/storage/test_context_delivery_write.py tests/unit/api/test_facade_contracts.py tests/unit/cli/test_query_verbs_runtime.py tests/unit/mcp/test_tool_contracts.py tests/unit/mcp/test_tool_discovery.py tests/unit/mcp/test_envelope_contracts.py; add and run focused context-delivery API/CLI/MCP files; devtools verify --quick. Record exact pass counts and a scratch user-v5 end-to-end receipt round trip in notes.","notes":"[Branch 20 source assimilation, 2026-07-11] Portable candidate code exists for API write/read/list helpers, ContextDeliveryPayload/ListPayload, compile_and_record_context(), and MCP deliver_context. It is useful as a call-shape reference only. No matching surface tests, MCP expected-name rows, TOOL_CONTRACT rows, generated-schema updates, CLI delivery surface, or MCP receipt get/list tools were recovered. Its storage/migration copy is rejected in favor of current #2703: it used optional session_ref, forgiving corrupt-JSON reads, a default self-asserted actor, and weaker field validation. Its list payload also exposed every full context image, contrary to this bead's disclosure AC. Do not treat the recovered deterministic proof report as proof of authenticated surface wiring.\nVERIFICATION (group3 sweep): LIVE. Checked: storage substrate real (write_context_delivery/read_context_delivery in archive_tiers/context_delivery_write.py, get_context_delivery in api/archive.py, MCPContextDeliveryPayload in mcp/payloads.py) but rg confirms get_context_delivery/write_context_delivery are called ONLY from tests/unit/api/test_facade_contracts.py -- no MCP tool and no CLI command actually invokes compile-and-record or the read path in production. AC2 (authenticated API+MCP+CLI delivery call) is not satisfied; only the API-facade plumbing exists. Matches own 2026-07-11 note that recovered branch code lacked matching surface tests/MCP rows/CLI wiring. Not stale.\n[Group3-followup sweep, worktree agent-a564975670ee09dee, 2026-07-31] Wired MCP surface for the durable receipt ledger via PR #3435 (branch feature/mcp/context-delivery-read-access-surface):\n- API: Polylogue.record_context_delivery / compile_and_record_context / list_context_deliveries (polylogue/api/archive.py), routed through the existing user.db write_context_delivery/list_context_deliveries storage functions from PR #2703 -- idempotency/drift refusal enforced there, not reimplemented.\n- MCP: write(operation=\"deliver_context\") records a receipt; context(result_ref=..., recipient_ref=...) resolves one receipt (recipient-scoped disclosure); context(recipient_ref=...) alone lists bounded summaries (no context_image).\n- New payloads MCPContextDeliverySummaryPayload / MCPContextDeliveryListPayload.\n- Verified end-to-end against a real archive: compile+record, idempotent replay, drift refusal, recipient-scoped disclosure, bounded list-without-content, capability gating.\n\nNOT satisfied (explicitly deferred, not silently dropped):\n- AC2/AC4's \"authenticated API+MCP+CLI\" requirement is now 2/3: API+MCP done, CLI intentionally left unwired -- design item 4 (a `read --deliver-to` CLI form) is a CLI-verb/flag product decision this task was told not to make unilaterally (CLI strict command floor #1842). Needs an explicit operator call on whether/how to extend cli/query_verbs.py.\n- \"Authenticated actor\" binding for delivered_by_ref is the same caller-supplied-field convention every other write operation in this dispatcher already uses (author_ref etc.) -- there is no richer per-caller identity system in this codebase to bind against. If the bead wants something stronger than that existing convention, that's a new cross-cutting authority mechanism, not scoped to this bead alone.\n- AC6 (filter/parity contract tests across API/MCP/CLI) only covers API+MCP now, per the CLI gap above.\n- AC7 (routing inventory, tool declarations) done for the surfaces that exist; nothing to add for the CLI gap yet.\n\nRecommend: keep open, narrow remaining scope to \"CLI wiring, pending operator decision on whether cli/query_verbs.py should grow a delivery form\" -- everything else in the original AC list is now real and tested.\n","status":"open","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-11T11:58:51Z","created_by":"Sinity","updated_at":"2026-07-31T08:27:29Z","labels":["area:api","area:cli","area:context","area:mcp","delivery:D-agent-context-coordination","horizon:frontier","lane:agent-coordination","lane:context-memory"],"dependencies":[{"issue_id":"polylogue-37t.22","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-11T13:58:50Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-bby.17","title":"Deepen cockpit API with privacy-safe overview and evidence aggregates","description":"The four-verb cockpit shipped in PR #2675, but its landing and evidence strip still stitch multiple broad payloads client-side. Source-backed audit of the interrupted Lane E plan found two public HTTP DTOs (ProviderUsageReport and ArchiveDebtListPayload) serialize the local absolute archive_root; the landing combines /api/status plus /api/sessions; and the session evidence strip derives tool outcome totals from the full insights event payload. This is residual API work, not part of the already-merged UI lane.","design":"Keep substrate and operations models rich enough for CLI diagnostics, but introduce explicit public HTTP projections that omit local filesystem identity by default. Add one bounded overview aggregate for landing totals, readiness, and recent activity and one session evidence-summary aggregate sourced from structural tool-use and action outcome evidence. Reuse existing operations, read models, route-contract, and OpenAPI machinery; do not create web-only semantics or duplicate counts. Any privileged diagnostic path exposure must be separately authorized and explicitly named, never ambient in normal cockpit responses.","acceptance_criteria":"1. Normal /api/provider-usage and /api/archive-debt responses contain no absolute archive path; sentinel tests cover configured paths, symlink targets, and serialized error or caveat text without removing needed CLI/operator diagnostics. 2. A bounded overview contract returns session, message, and origin totals, readiness, and recent activity from shared projections in one request, with explicit unknown/degraded fields and no archive-wide hydration. 3. A bounded per-session evidence summary returns structural tool-call and ok, failed, and unknown outcome counts plus cost and lineage refs used by the evidence strip; parity tests compare it to the underlying actions and tool-use relations. 4. The cockpit consumes the typed aggregates, handles 401, 409, and 503 plus stale data truthfully, and no longer downloads full insight events solely to compute header chips. 5. Route catalog, OpenAPI, generated witnesses, focused HTTP/security/UI tests, a real Playwright journey, and devtools verify --quick pass.","notes":"Recovered 2026-07-11 from the archived Fable session claude-code-session:fa4df7c3-7fc7-449c-bbd0-b42aec839c40 and original 3347cf34-ca12-45ae-918f-781c7f96a704. The empty /realm/worktrees/lane-api checkout had zero commits and zero diff and was removed; this bead is the durable residual rather than pretending implementation existed.","status":"closed","priority":2,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-11T07:33:35Z","created_by":"Sinity","updated_at":"2026-07-13T00:57:28Z","closed_at":"2026-07-13T00:57:28Z","close_reason":"PR #2793 merged: privacy-safe overview + evidence aggregates API shipped — provider-usage/archive-debt HTTP projections redact archive_root/symlink paths, /api/overview bounded totals, /api/sessions/:id/evidence-summary canonical structural outcomes+cost+capped lineage, live shell consumes it with truthful stale/failure rendering, route catalog/OpenAPI generated, real Playwright cockpit journey passed","labels":["area:api","area:privacy","area:web","delivery:H-web-cockpit","lane:web-evidence-cockpit"],"dependencies":[{"issue_id":"polylogue-bby.17","depends_on_id":"polylogue-bby","type":"parent-child","created_at":"2026-07-11T09:33:35Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-5k5l","title":"Browser-capture asset acquisition: fetch sandbox + file-service bytes at capture time","description":"Assistant-produced files are captured as links only: sandbox:/mnt/data deliverables (now recorded as unfetchable sandbox_file attachment rows since PR #2666) and file-service:// asset pointers (image/audio blocks keep the pointer in metadata). The bytes are never acquired, and sandbox links EXPIRE with the container. Concrete loss 2026-07-10: ten GPT-Pro fork conversations each delivering a kit ZIP (proof-obligation compiler, DSL expansion, web cockpit v2, test-vacuity audit, context/memory package, beads surgery...) reachable only via expiring links; none downloaded before capture; text captured, bytes gone unless operator re-downloads manually. This is the INBOUND capture pipeline — distinct from polylogue-ptx (outbound posting actuator); do not merge scopes.","design":"Extension side (browser-extension/): at capture time, for each conversation being captured, (1) collect sandbox:/mnt/data links from assistant messages and file-service:// asset pointers from parts; (2) fetch bytes via the PAGE-AUTHENTICATED context — sandbox files via the backend interpreter download endpoint (conversation id + message id + sandbox path -\u003e signed URL -\u003e bytes), file-service assets via the files download endpoint; (3) POST alongside the capture payload as attachment parts (multipart or follow-up POSTs keyed by capture_id + provider_attachment_id). Respect size caps (configurable, default e.g. 50MB/file) and report per-file acquisition outcome in the capture envelope.\nReceiver/daemon side: store fetched bytes through the existing attachment blob path (#2468/#2469 plumbing: content-addressed blob + true SHA-256 + acquisition_status=acquired); match rows by provider_attachment_id (sandbox rows use the sandbox:\u003cmsg\u003e:\u003cpath\u003e ids from PR #2666; asset pointers need equivalent rows added for image/audio blocks). Unfetched/failed stay unfetched/unavailable with the failure reason in metadata — never fabricate.\nConstraints: expired links are NORMAL (capture may happen after container death) — per-file failure must not fail the capture; no fetching outside the captured conversation scope; agent-private browser posture per ambient control model. Re-capture of an already-archived conversation should backfill missing bytes (idempotent by content hash).\nRelated: polylogue-ptx (outbound channel, keep separate); PR #2666 (sandbox rows), PR #2668 (context/citation fidelity).\n","acceptance_criteria":"1. Capturing a live conversation containing a sandbox deliverable stores its bytes as a content-addressed blob with true SHA-256 and acquisition_status=acquired, linked to the sandbox_file attachment row.\n2. file-service image/audio pointers gain attachment rows and are acquired the same way.\n3. Expired/failed fetches leave rows unfetched/unavailable with a recorded reason; capture itself still succeeds (negative test with a dead link).\n4. Re-capture of an archived conversation backfills missing bytes idempotently (content-hash: no duplicate blobs, no session re-import churn).\n5. Size cap enforced and disclosed in the capture envelope.","notes":"[2026-07-10 fable] Implementation landed via PR #2669: extension page-bridge asset fetch (sandbox interpreter/download + files download, signed-URL two-step, 25MB/75MB budgets, outcome disclosure), envelope session attachments with inline_base64, and the critical parser fix — envelope attachments now merge into native-payload-delegated sessions (were silently dropped). Citation fidelity deepened in the same PR (nested metadata surfaced, inline markers preserved as anchored constructs). REMAINING for AC: live end-to-end proof — reload the unpacked extension in the agent browser, capture a conversation with a live sandbox deliverable, verify blob acquired with true SHA-256 (AC#1), and the dead-link negative path (AC#3 — code path exists, needs live evidence). Extension must also be repointed at the production receiver (dialogue [15]) or captures keep landing in the temp spool.\n[2026-07-10 fable, LIVE EVIDENCE] AC#3 proven live: operator re-captured 10 fork conversations with the new extension code; asset acquisition ran end-to-end (68-161 assets attempted per capture), every fetch returned asset_bytes_status_403 (files genuinely expired server-side — ChatGPT own UI also fails on them), failures disclosed per-file in provider_meta.asset_acquisition, captures themselves succeeded and ingested. The 15s-message-timeout stall this exposed was fixed in PR #2672 (10s total budget + circuit breaker). AC#1 (acquired blob with true SHA-256) still needs one live capture of a conversation with ALIVE sandbox files — easiest path: ask any GPT fork to regenerate its zip, refresh tab, capture.\n[2026-07-11 authenticated recovery correction] Prior 403 evidence was a false global expiry conclusion: authenticated direct conversation API recovered most Branch Project packages. 45 files / 34.8MB are checksummed at /realm/inbox/gpt-pro-sol/recovered-branch-project-explanation-2026-07-11/. New child polylogue-5k5l.1 owns the missing bearer/signed-download contract. AC#1 remains open until the extension itself acquires a live artifact.\nPR #2785 merged: retains and exercises the existing parser/CAS path. DEFERRED (not closing, all 5 ACs): does not claim the controlled live sandbox/file-service acquisition, idempotent re-capture, or size-cap closure this bead requires. Note: the authenticated interpreter child is already merged separately as PR #2712 (8c23ba218).\n2026-07-16 live q32 closure evidence: conversation 6a5830bc-0d94-83ed-8d4f-6136a748bc19 completed with a provider-native sandbox output pointer. An authenticated native conversation read exposed the exact asset name, size 81240, and SHA-256 7fa320242b2c6aa6a92e3eada4299e8355a8628eefc41cb4846327f1c6205080; the manually downloaded ZIP matched byte-for-byte, while the extension had captured no output asset. Root cause is architectural: ordinary backfill compacted away output descriptors/terminal state and launch monitoring depended on a conversation tab/DOM. Active implementation unifies closed-tab, backfill, user-created, and receiver-launched ChatGPT capture through one exact content-script envelope with authenticated ChatGPT-Account-Id reads and output-byte acquisition. Exact-capture failure remains retryable instead of accepting an asset-less compact fallback.\n2026-07-16 live ordinary-capture proof: the reloaded canonical extension recaptured q32 without its conversation tab open and acquired all three provider assets. The assistant ZIP was 81,240 bytes with SHA-256 7fa320242b2c6aa6a92e3eada4299e8355a8628eefc41cb4846327f1c6205080, byte-identical to the operator download; the receiver validated 19 contained files and linked the canonical artifact chatgpt/6a5830bc-0d94-83ed-8d4f-6136a748bc19-76bfadd9563a.json. Collision-renamed display name `(14).zip` exposed and now tests stable sandbox-path/provider-id matching. This satisfies the live sandbox acquisition/idempotent canonical correlation evidence; retain the bead until the separate file-service image/audio and remaining stated ACs are audited honestly.\n2026-07-26 portfolio-convergence audit: released stale in_progress claim after \u003e7 days with no recorded activity; scope remains open and must be re-claimed on real work start.\nVERIFICATION (group4 stale-sweep, 2026-07-31): PARTIAL. Bead's own 2026-07-16 note proves AC1 (live sandbox-blob acquisition, true SHA-256 matching independently-recovered bytes) and AC3 (dead-link 403 disclosure) with concrete live evidence, but explicitly says to retain the bead until file-service image/audio and remaining stated ACs are audited honestly. AC2 (file-service image/audio attachment rows) has no cited implementation evidence anywhere in the notes; a 2026-07-26 sweep released a stale in_progress claim, leaving status open with real remaining scope. Evidence: bd show polylogue-5k5l --json.","status":"open","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-10T18:43:50Z","created_by":"Sinity","updated_at":"2026-07-31T05:53:51Z","started_at":"2026-07-16T03:13:20Z","labels":["area:browser","area:sources","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-5k5l","depends_on_id":"polylogue-83u","type":"parent-child","created_at":"2026-07-15T18:54:38Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-nhjs","title":"Bound web reader shapes for long sessions and aggregates","description":"The current session-detail route materializes every message, while attachments, paste, overlays, and stack/compare views lack a shared bounded web-read contract. Large-session responsiveness therefore depends on client rendering and ad hoc endpoints rather than keyset pages and declared aggregate shapes.","design":"Define keyset message windows with stable cursors, bounded aggregate attachment/paste reads, bounded overlay/assertion pages, and stack/compare projections. Route declarations expose limits/exactness/cursors through the typed registry/generated client. The reader virtualizes rendered nodes and preserves anchor/scroll semantics across page fetches. Avoid duplicating domain queries in the web adapter.","acceptance_criteria":"A large deterministic session opens to first useful content within a measured budget without loading the full transcript; DOM node count stays bounded while deep anchor navigation, back/forward, copy refs, attachment/paste summaries, overlays, and compare views remain correct. Cursor growth does not duplicate/skip rows. Removing server bounds or client virtualization fails request-count/DOM-budget journeys. Focused route/query/Playwright tests and verify --quick pass.","notes":"PR #2793 merged (this slice satisfied): HTTP detail responses capped, limits clamped, continuation appends pages, prefix-sharing display metadata reconciled. DEFERRED (not closing): server-side non-hydrating/keyset windows, client virtualization/DOM budgets, deep-anchor page seeking, bounded stack/compare/overlay projections remain open.","status":"closed","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-10T17:06:10Z","created_by":"Sinity","updated_at":"2026-07-14T23:43:24Z","closed_at":"2026-07-14T23:43:24Z","close_reason":"Superseded without scope reduction: 4p1 now owns stable keyset/non-hydrating/deep-anchor/bounded projection semantics; bby.8 owns virtualization, cancellation, DOM/request budgets, cache revalidation, and navigation behavior. PR #2793 remains landed partial evidence.","labels":["area:perf","area:web","delivery:H-web-cockpit","horizon:frontier","lane:web-evidence-cockpit"],"dependencies":[{"issue_id":"polylogue-nhjs","depends_on_id":"polylogue-37km","type":"relates-to","created_at":"2026-07-10T19:06:18Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-nhjs","depends_on_id":"polylogue-bby","type":"parent-child","created_at":"2026-07-10T19:06:12Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-nhjs","depends_on_id":"polylogue-bby.8","type":"relates-to","created_at":"2026-07-10T19:06:17Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -878,6 +950,15 @@ {"_type":"issue","id":"polylogue-rii.1","title":"Agent work-event write-leg -\u003e session_events -\u003e materialized read-models","description":"record_work_event/emit_decision write surface routed through the existing idempotent ingest seam (no parallel writer); flows into the run-projection read models. Today agents can only record_correction/blackboard_post/tag — there is no 'I ran this tool / spawned this subagent / decided X' write. GH issue thread (body + comments) is input, not authority; this bead's scope statement wins where they conflict.","design":"Route through the existing idempotent ingest seam (write_raw_and_parsed / the daemon ingest path) — no parallel writer (gh#2459 body is code-grounded here). Surface: MCP tools record_work_event/emit_decision (mutation role) accepting typed events (tool run, subagent spawn, decision, artifact change) with evidence/session refs; land in session_events; run-projection read models pick them up through the normal materializer. MCP registration trap: EXPECTED_TOOL_NAMES + TOOL_CONTRACT + role gating + render openapi/cli-output-schemas regen (see bd memories). Acceptance: an agent posts a work event mid-session; it is queryable via observed-events within one convergence cycle; re-posting is idempotent.","acceptance_criteria":"- MCP tools record_work_event / emit_decision are registered with the mutation role: EXPECTED_TOOL_NAMES + TOOL_CONTRACT updated, role gating enforced, and `devtools render openapi \u0026\u0026 devtools render cli-output-schemas` regenerated with `devtools render all --check` clean.\n- Typed events (tool run, subagent spawn, decision, artifact change) with evidence/session refs route through the existing idempotent ingest seam (write_raw_and_parsed / the daemon ingest path) into session_events — no parallel writer (grep confirms reuse).\n- Behavior test: an agent posts a work event mid-session and it is queryable via observed-events (session_work_events / DSL) within one convergence cycle; re-posting the same event is idempotent (no duplicate row). `devtools test \u003cmcp work-event test\u003e` green.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=D-agent-context-coordination; lane=agent-coordination; readiness=A-implementation-ready; proof=two-agent separate-worktree proof with before/after coordination envelopes. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/071_polylogue_rii_1.md (depth: bead-localized-from-export; urgency: T2-foundation-before-feature-proof). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\nRECONCILED 2026-07-13 with 37t.2 inline protocol: the agent work-event write-leg and the marker channel are ONE channel with two encodings (structured MCP writes; prose markers extracted at enrichment). Unify vocabularies — work-event kinds and marker kinds must share the registry (a ::phase marker IS a work event). Do not build parallel event taxonomies.","status":"open","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:43Z","created_by":"Sinity","updated_at":"2026-07-13T04:00:08Z","external_ref":"gh-2459","labels":["area:substrate","delivery:D-agent-context-coordination","horizon:frontier","lane:agent-coordination"],"dependencies":[{"issue_id":"polylogue-rii.1","depends_on_id":"polylogue-rii","type":"parent-child","created_at":"2026-07-03T06:31:43Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-fs1.3","title":"Per-source coverage/fidelity declaration for Hermes imports","description":"Every Hermes acquisition tier and schema version needs a machine-readable fidelity declaration that distinguishes what is exact, absent, redacted, degraded, or inferred. The declaration is the guard against a parser test going green while silently dropping forensic history or cost/addressing provenance.","design":"Extend the OriginSpec/fidelity surface with: producer/schema version; installation/profile namespace; acquisition method (sqlite_backup, stable export, JSON fallback, runtime spans); exact retained-blob-to-normalized reproducibility verdict; counts and coverage for active, rewound, compacted, and observed messages; addressing/material-origin semantics; actual/estimated cost with status/source/pricing/billing provenance; lifecycle/relationship coverage; runtime-span coverage and explicit missingness. The snapshot and span lanes may enrich one logical session revision only with per-field provenance; they may not double-count or silently prefer a lower-fidelity tier.","acceptance_criteria":"explain-import on Hermes v16, a later schema, JSON fallback, and a spans-plus-snapshot merge names every capability as exact, absent, redacted, degraded, or inferred; exact-blob reproducibility is stated and verified; the same logical session from two tiers remains one revision with field-level provenance; message-state/addressing and cost-provenance counts reconcile to fixtures; deliberately dropping observed mapping, cost provenance, snapshot proof, or an unpaired span changes the declared fidelity and surfaces a downstream forensics caveat. OriginSpec fixtures and mutation-style negative tests pass.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=K-interop-origin-export; lane=origin-interop-export; readiness=D-horizon-ready; proof=OriginSpec detector/parser/fixture/fidelity suite and content-hash export/import roundtrip. Original readiness=E-spec-needed.\n2026-07-12 fanout lane finding: blocked as scoped — explain-import cannot inspect SQLite Hermes state DBs and its payload lacks a fidelity-declaration field; both surfaces (import_explain.py + payload schema) must be in scope to implement. Evidence: 37bdfa04c; import_explain.py decodes JSON/JSONL only.","status":"closed","priority":2,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:40Z","created_by":"Sinity","updated_at":"2026-07-12T23:15:18Z","closed_at":"2026-07-12T23:15:18Z","close_reason":"PR #2789 merged: Hermes per-source coverage/fidelity declaration shipped (import_explain.py, hermes_state.py, generated CLI-output schema regenerated)","labels":["area:ingest","area:substrate","delivery:K-interop-origin-export","delivery:ac-patched","horizon:frontier","lane:origin-interop-export"],"dependencies":[{"issue_id":"polylogue-fs1.3","depends_on_id":"polylogue-fs1","type":"parent-child","created_at":"2026-07-03T06:31:40Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":3,"comment_count":0} {"_type":"issue","id":"polylogue-tf2.2","title":"Fold agent_forensics.py into polylogue analyze","description":"~70% already materialized (cost_rollups, archive_coverage, total_credit_cost, portfolio, cost_outlook). Real gaps: reasoning-token lane on SessionProfile; usage_timeline archive insight (tokens/cost per month per model) registered in insights/registry.py; optional markdown forensics renderer. Drop the script's hand-rolled _CREDIT_RATES; delete the script. Sequenced AFTER the campaign regen (the campaign uses the script one last time). GH issue thread (body + comments) is input, not authority; this bead's scope statement wins where they conflict.","status":"closed","priority":2,"issue_type":"feature","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:34Z","created_by":"Sinity","updated_at":"2026-07-03T11:54:39Z","started_at":"2026-07-03T11:31:18Z","closed_at":"2026-07-03T11:54:39Z","close_reason":"Completed: usage forensics is no longer a standalone script surface. Added registered usage_timeline archive insight with CLI/API/MCP registry coverage, reused the shared subscription-pricing catalog for credit estimates, deleted scripts/agent_forensics.py and its private-helper tests, and rewrote README/docs around polylogue analyze insights coverage/cost-rollups/usage-timeline plus devtools workspace claim-vs-evidence. Verification: focused claim-vs-evidence/insights tests passed, render all --check passed, devtools verify --quick passed, and live active-archive usage-timeline smoke returned valid JSON. Follow-up polylogue-5nn tracks the observed 18s whole-archive aggregation latency for unfiltered month-origin-model usage-timeline.","external_ref":"gh-2480","labels":["area:usage","campaign"],"dependencies":[{"issue_id":"polylogue-tf2.2","depends_on_id":"polylogue-tf2","type":"parent-child","created_at":"2026-07-03T06:31:34Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-tf2.2","depends_on_id":"polylogue-tf2.1","type":"blocks","created_at":"2026-07-03T06:31:34Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-0nvk","title":"Leak audit L17: origin-token validation diverges between CLI, DSL and HTTP","description":"AUDIT 2026-07-31 (leak-surfaces). VERDICT: REACHABLE - coherence gap, not a leak.\n\nThree places validate an origin token and they disagree:\n 1. The CLI --origin flag validates in a Click parameter callback and raises before any query is built.\n 2. The query DSL validates origin: independently inside the expression parser.\n 3. The shared substrate does neither - the enum's string constructor is deliberately lenient and maps anything unrecognised to unknown-export, because its job is normalising untrusted wire tokens from provider exports, not gating user input.\n\nThe HTTP ?origin= parameter goes straight into the query spec with no validation call, lands on path 3, matches nothing, and returns HTTP 200 with total:0. A caller who mistypes an origin gets a false 'no results' instead of an error, inconsistent with the CLI and DSL on the same conceptual filter.\n\nFix: validate at the HTTP boundary so the three surfaces agree. No content exposure.\n\nReport: /realm/inbox/polylogue-audits-2026-07-31/leak-surfaces.html","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T10:08:55Z","created_by":"Sinity","updated_at":"2026-07-31T10:08:55Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-nt5f","title":"D1 receipts: build the public seed-corpus variant (session_refs pr-link fixture)","description":"polylogue-xyel shipped .agent/demos/d1-receipts/ as the live-archive\noperator variant only (mode=private): a real merged PR (Sinity/polylogue#3282)\nresolved to its authoring/dispatch session via session_refs, with 4\nindividually-checked claim-vs-evidence rows.\n\nThe epic's own design (polylogue-212) calls for two variants per demo: a\npublic seeded-corpus reproduction (seed 1843) and a live-archive operator\nvariant. session_refs kind='pull_request' rows are populated from Claude\nCode's own provider-native pr-link sidecar record type; the deterministic\ndemo seed fixture (polylogue demo seed) does not currently synthesize any\nsuch record, so there is nothing for a public D1 receipts variant to\nresolve against today.\n\nScope: either (a) extend the demo seed fixture generator to synthesize a\nrealistic pr-link sidecar record + matching PR body fixture so the existing\nd1-receipts packet's method can run against the public corpus, or (b)\ndecide the live-archive variant is sufficient for D1 specifically (provider\ntelemetry demos may not all need a public arm) and update polylogue-212's\ndesign note to say so explicitly rather than leaving it silently unbuilt.\nDo not leave it as an unstated gap either way.","acceptance_criteria":"1. Either the demo seed fixture generator synthesizes a pr-link sidecar record plus matching PR body so d1-receipts's method runs on the public seed corpus, or 212's design doc is updated to explicitly say D1 has no public variant. 2. Whichever is chosen is reflected in .agent/demos/d1-receipts (new public variant, or an updated NON-CLAIMS/report.md limits note) and validates via devtools lab policy demo-packet-registry.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T09:01:41Z","created_by":"Sinity","updated_at":"2026-07-31T09:04:17Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-zumd","title":"analyze tools: no session scope, root -i unbounded scan (\u003e60s) while MCP answers identically in seconds","description":"Surface-coherence audit 2026-07-31: `analyze tools` cannot answer \"what tools ran in session X\" and is interactively unusable on the live archive, while MCP/daemon answer the same question in seconds. Evidence: `polylogue -i c1cf89f2-c4ff-48de-9459-599c2e8d04ff analyze tools --json` ran \u003e60s (timeout, 12% CPU) and \u003e110s on a second attempt; `analyze --by tool` similarly. analyze tools has --origin/--tool/--days/--basis but no session scope, and the root `-i` filter does not bound its scan. Same question via MCP query 'actions where session.id:\u003cfull sid\u003e | group by tool | count' -\u003e 12 groups (Bash 453, Agent 261, Read 136, Edit 82, Write 59...) in ~4s, identical to daemon /api/query-units and to SQL over the actions view. Also observed (transient, twice): plain `find '\u003cterm\u003e'` stalled \u003e100s at ~3% CPU (both daemon-backed and --no-daemon) then completed in 4-5s on retry minutes later — likely writer-lock contention during ingest; worth a look while touching read-path performance. Fix options: teach analyze tools to push the root -i/session scope into the actions projection (fast path exists — MCP proves it), or point users at the query pipeline and bound the full-archive scan.\n","status":"closed","priority":3,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:41:49Z","created_by":"Sinity","updated_at":"2026-07-31T10:45:51Z","started_at":"2026-07-31T10:45:49Z","closed_at":"2026-07-31T10:45:51Z","close_reason":"Fixed on branch worktree-agent-a1277ae4859b61089 (commit b48805b48, not yet merged): analyze tools now accepts the root --id/--latest filter (resolve_session_id_from_root_params, same pattern as turns) and pushes session_id as a SQL predicate into list_tool_call_count_rows/list_tool_observed_event_count_rows/list_tool_action_evidence_count_rows (archive.py). EQP before: idx_blocks_type_tool(block_type) full-archive scan + per-row LEFT JOIN nested loop. EQP after: idx_blocks_session_position(session_id) direct search. Live-archive verification on a 1861-message session: was QueryTimeoutError \u003e120s, now ~3s. Also fixed the same silent-archive-wide-scan defect in analyze pace and analyze usage (usage additionally got a new session-scoped fast path via session_usage_reconciliation_for_connection, reusing the previously-dead build_session_usage_reconciliation from PR #3299). analyze latency intentionally left unscoped (route telemetry, not session data). Also fixed the unrelated but same-audit read --view summary == --view transcript alias bug found in the same session. devtools verify --quick green; devtools test on the affected files green (one pre-existing unrelated frozen_clock failure confirmed via git stash against unmodified master).","labels":["cli","surface-coherence"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-59qy","title":"Schema-generation chatgpt phase-receipt test skips because the seeded fixture has no samples","description":"FALSE-GREEN AUDIT 2026-07-31 (finding F12). MEASURED.\n\ntests/unit/core/test_schema_generation.py:80 test_generation_records_aggregate_phase_receipt\nskips with 'seeded archive has no chatgpt samples' because generate_provider_schema('chatgpt', ...)\nreturns sample_count == 0 against the shared seeded_archive_writable fixture.\n\n devtools test tests/unit/core/test_schema_generation.py -v -rs -\u003e 32 passed, 1 SKIPPED\n\nThis is the ONLY one of six audited skip-suspects that actually fires. The others are dormant\nin this environment and were verified individually with -rs:\n tests/unit/storage/test_insight_materialization_laws.py 6 passed, 0 skipped\n tests/unit/insights/test_temporal_source_taxonomy.py 64 passed, 0 skipped\n tests/integration/test_workflows.py 18 passed, 0 skipped\n tests/unit/sources/test_parser_crashlessness.py 10 passed, 0 skipped\n tests/unit/sources/test_parsers_props.py 43 passed, 0 skipped\nsqlite_vec is importable and FTS5 is compiled in, so that whole skip class is dormant too.\n\nWHY IT STILL MATTERS: a data-availability skip is a silent permanent exemption when the data\nis a FIXTURE THE REPO CONTROLS. 'The seeded archive has no chatgpt samples' is not an\nenvironment fact like 'no systemd on this host' -- it is a gap in our own fixture, and the\nskip converts it into a green check forever. Nobody is told the chatgpt schema-generation\nphase-receipt path is unverified.\n\nAC:\n- Either seed chatgpt samples into the shared fixture so the test runs, or\n- assert the precondition (fail loudly if the fixture lacks chatgpt samples) rather than\n skipping, so a fixture regression is visible.\n- General principle worth recording in TESTING.md: skip on ENVIRONMENT facts; assert on\n FIXTURE facts. A skip whose condition the repo controls is an exemption, not a guard.\n\nBroader xfail/skip audit result for the record: the entire suite contains ONE xfail\n(tests/unit/cost/test_contract_suite.py:486). It is strict=True, declares raises=KeyError, and\ncites live bead polylogue-hg97. That is a correctly-formed exemption -- no xfail drift exists\nin this repo.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:30:12Z","created_by":"Sinity","updated_at":"2026-07-31T08:30:12Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-rxfo","title":"Over-mocking: two suites where the mock supplies the asserted value","description":"FALSE-GREEN AUDIT 2026-07-31 (findings F10, F11). Read-verified. LOW-MEDIUM severity -- filed for completeness, both have mitigating sibling coverage.\n\nContext: the repo's mocking discipline is generally strong. Of ~2551 patch sites, the core\nsubstrate (tests/unit/core/test_hashing.py, tests/unit/pipeline/test_pipeline_ids.py,\ntests/unit/storage/test_lineage_normalization.py, all of tests/unit/cost/, the daemon\nconvergence suite) uses real SQLite and real computation. Several tests carry explicit\nanti-vacuity docstrings, e.g. test_daemon_cli.py:1401 replaces a mock coordinator with a real\none because 'a mock coordinator would trivially report False for both, proving nothing'.\nThese two are the exceptions found.\n\n1) tests/unit/pipeline/test_parsing_service.py:132 test_ingest_calls_acquire_then_parse\n Patches ParsingService.parse_from_raw -- a method on the instance under test -- with\n AsyncMock(return_value=parse_result). The assertions result.counts['sessions'] == 2 and\n result.processed_ids == {'conv-1','conv-2'} are the mock's own canned ParseResult flowing\n through. parse_sources/ingest_sources (polylogue/pipeline/services/parsing.py:58-90,\n parsing_workflow.py:163) is a pass-through of that return value, so the test proves nothing\n about parsing.\n MITIGATION: real parse correctness is covered by test_parse_from_raw_parses_stored_sessions\n (:403) and test_ingest_with_real_database (:377), both against a real DB. Only this\n individual test is vacuous on the counts-propagate axis.\n\n2) tests/unit/daemon/test_convergence_stages.py:703-712 (repeats at :989-1001, :1213-1223)\n Patches polylogue.storage.insights.session.rebuild.rebuild_session_insights_sync with a\n fake whose body echoes a hard-coded SessionInsightCounts(profiles=1, work_events=2, ...).\n The test then asserts rebuilt is True and stage.execute(...) returns True.\n The stage's DISPATCH decision (session-id resolution, hot-session gating) is genuinely\n exercised and is arguably the subject; the 'insights were rebuilt correctly' half rests\n entirely on the fake's own numbers.\n\nREJECTED as legitimate during the same pass, recorded so they are not re-audited:\n- ArchiveStore.* patches in test_duplicate_raw_identity_repair.py / test_revision_backfill.py /\n test_live_batch_support.py: every one wraps 'original = ArchiveStore.method' and calls\n through before injecting the fault, then verifies real SQLite state. Transactional-integrity\n testing, not tautology.\n- test_lineage_normalization.py:788,1736,1776 _resolve_session_graph/_prefix_sharing_edge_sync\n patches: call real_resolve(...) then interleave, to prove snapshot isolation under concurrent\n writes. Sophisticated race tests.\n- subprocess/git/clock/filesystem-root/Voyage-API patches: external boundaries, correct.\n\nAC: make the two tests above assert something the mock does not supply, or retitle them to\nwhat they actually pin (wiring/forwarding) so the name stops overclaiming.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:22:23Z","created_by":"Sinity","updated_at":"2026-07-31T08:22:23Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-1k9l","title":"111 raws stuck with parse_error (59 truncated-JSONL claude-code, 25 no-session unknown-export, 19 CAS-frontier, 6 decode, 2 hermes)","description":"Forensics 2026-07-31. raw_sessions.parse_error non-null on 111 rows: 59x 'captured JSONL payload ends before a complete record boundary' (claude-code), 25x 'parsed raw payload produced no sessions' (unknown-export), 14x codex + 4x claude-code + 1x codex-membership 'raw revision CAS rejected an older accepted frontier', 5x+1x JSONDecodeError, 2x hermes 'no materializable sessions'. None appear in convergence_debt (0 rows) — they will not retry.\nRepro: SELECT origin, substr(parse_error,1,80), count(*) FROM raw_sessions WHERE parse_error IS NOT NULL GROUP BY 1,2;\nAC: each error family triaged: retryable ones re-queued, permanent ones classified with a terminal status distinct from silent parse_error, truncated-capture family root-caused.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:22:13Z","created_by":"Sinity","updated_at":"2026-07-31T08:22:13Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-mnds","title":"Blob store residue: 1,590 orphan blobs (1.49GB) + 52 stale .blob.* temp files (66MB), all pre-2026-07-19","description":"Forensics 2026-07-31. Blob store has 104,877 hash-named files; blob_refs references 103,235 distinct hashes (0 missing on disk). 1,590 hash-named files have no blob_refs row (1.49GB, latest mtime 2026-07-18) plus 52 .blob.* temp spool files at the store root (66MB, mtimes 07-11..07-18) leaked by interrupted acquisitions. 93 gc_generations logged; GC has not collected these. No new orphans since 07-18 — historical residue from the de-inflation / index-generation era.\nRepro: compare find /realm/db/polylogue/blob -type f (shard+basename = hash) against SELECT DISTINCT lower(hex(blob_hash)) FROM blob_refs.\nAC: GC (or a one-shot sweep) collects unreferenced blobs under the existing two-invariant safety model; temp-file leak has a cleanup path.","status":"open","priority":3,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:22:12Z","created_by":"Sinity","updated_at":"2026-07-31T08:22:12Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-5yig","title":"19 prefix-sharing children whose earliest message predates the branch-point timestamp","description":"Forensics 2026-07-31. Of 537 prefix-sharing session_links, 19 children have min(occurred_at_ms) earlier than the branch-point message's occurred_at_ms. Two shapes: claude-code agent-acompact-* auto-compaction copies (replayed head keeps original timestamps), and hermes observer branches starting 1-30s before the recorded branch point. Consumers must not assume 'child tail starts after branch point'. Positive result recorded alongside: 0 of 537 children store parent-prefix blocks (block-level content_hash check) — tail-only storage holds.\nRepro: SELECT count(*) FROM session_links l JOIN messages bpm ON bpm.message_id=l.branch_point_message_id WHERE l.inheritance='prefix-sharing' AND (SELECT min(occurred_at_ms) FROM messages c WHERE c.session_id=l.src_session_id AND occurred_at_ms IS NOT NULL) \u003c bpm.occurred_at_ms;\nAC: decide whether branch_point selection should be timestamp-consistent for these shapes or the invariant documented as non-guaranteed; fix or document.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:21:47Z","created_by":"Sinity","updated_at":"2026-07-31T08:21:47Z","comments":[{"id":"019fb76a-e7ee-7b08-a53a-a69746fcacd3","issue_id":"polylogue-5yig","author":"Sinity","text":"Correction (same audit, better instrument): the earlier '0 of 537 children store parent-prefix blocks' readout used messages.content_hash, which is identity-unique by construction and therefore vacuous. Re-measured with blocks.content_hash (content-only anchor): 8,840 of 229,073 child block rows (3.9%) across 382/537 children match parent-prefix content — consistent with incidental boilerplate/tool-output repetition, NOT wholesale prefix replay (which would dominate the ratio). Tail-only storage HOLDS. The 19 timestamp-predating children remain the open item.","created_at":"2026-07-31T09:04:25Z"}],"dependency_count":0,"dependent_count":0,"comment_count":1} +{"_type":"issue","id":"polylogue-b4n2","title":"3 durable judgment assertions target chatgpt sessions that no longer exist in index.db","description":"Forensics 2026-07-31. user.db (durable, irreplaceable tier): 3 of 101 assertions (kind=judgment) have target_ref session:chatgpt-export:6a50b7cc-0b24-83eb-bd15-2edadd846f2b (x2) and session:chatgpt-export:69d5383e-69d0-8327-a899-94a89ff35ea4 — neither session exists in index.db. index is rebuildable, so either these sessions vanished in a rebuild/reclassification (recoverable) or their raws were superseded. Durable-tier anchors must not silently dangle.\nRepro: ATTACH user.db; SELECT a.assertion_id, a.target_ref FROM usr.assertions a WHERE a.target_ref LIKE 'session:%' AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.session_id=substr(a.target_ref,9));\nAC: root-cause the disappearance; re-anchor or tombstone; add a maintenance check for dangling durable ObjectRefs.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:21:47Z","created_by":"Sinity","updated_at":"2026-07-31T08:21:47Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-1bkl","title":"shipped-but-dead: three insight modules and two ops drift readers are exercised only by their own tests","description":"Audit 2026-07-31 (shipped-but-dead census). Lower-consequence tail, grouped so it\ndoes not get re-discovered piecemeal.\n\nA. Insight modules with zero production callers (only their own test file, plus\n docs/plans/topology-target.yaml which lists every module and proves nothing):\n polylogue/insights/archive_summaries.py (day/week session aggregation)\n polylogue/insights/improvement_loops.py active_loops(), horizon_loops()\n polylogue/insights/delegation_work_evidence.py materialize_delegation_work_evidence_graph\n These are never invoked in production at all -- not registered in\n INSIGHT_REGISTRY, no CLI verb, no MCP tool. No bead names them (checked:\n polylogue-ic5i covered three DIFFERENT modules, all since removed).\n\nB. Populated ops tables whose only reader function is called only from tests:\n schema_drift_samples 313 rows -\u003e list_schema_drift_samples\n (ops_write.py:373; callers only in\n tests/unit/schemas/test_drift_sentinel_sampling.py,\n tests/unit/storage/test_schema_drift_samples.py)\n fts_drift_samples 8 rows -\u003e list_fts_drift_samples\n (ops_write.py:241; callers only in\n tests/unit/storage/test_fts_identity_ledger.py,\n tests/unit/daemon/test_fts_identity_convergence.py)\n Contrast with the sibling that IS wired: list_route_observations\n (ops_write.py:1487) reaches cli/commands/diagnostics.py:850,866. The drift\n samplers write real signal every pass and no operator can see it.\n\nC. Dead legacy parser models: polylogue/sources/providers/claude_ai.py\n (ClaudeAISession:99, ClaudeAIChatMessage:23). The live path for\n Provider.CLAUDE_AI is dispatch.py:1137 -\u003e parsers/claude/ai_parser.py.\n Only tests/unit/sources/test_models.py imports the old classes.\n\nD. polylogue/context/selection.py -- an orphaned parallel implementation\n (archive_context_image_active:188, query_archive_context_image:200,\n archive_context_image_filters:243, archive_context_image_summary:257,\n dedupe_archive_context_image_rows:271). They call each other in a closed loop.\n The file's real entry point, select_context_image_sessions:121, is imported by\n api/archive.py:2893 and does not touch any of them.","acceptance_criteria":"Each item gets one of two dispositions, recorded: wired to a real surface, or deleted with its by-direct-import tests. For B specifically, either the drift samples become visible through diagnostics alongside route observations, or the sampling stops.","status":"open","priority":3,"issue_type":"chore","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T08:06:31Z","created_by":"Sinity","updated_at":"2026-07-31T08:06:31Z","labels":["shipped-but-dead"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-d0kj","title":"benign-DDL allowlist regex admits CREATE TABLE IF NOT EXISTS ... AS SELECT, which transforms data on every archive open","description":"AUDIT FINDING (claimed-vs-enforced invariants sweep, 2026-07-31). Verdict: latent\ngap in a regex-based allowlist. Currently unreachable; filed before it is used.\n\nCLAIM (docs/internals.md, index-tier benign-DDL convergence, polylogue-jc1b): the\nregistry is restricted to \"idempotent, data-non-transforming DDL statements\n(CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS / DROP TABLE IF EXISTS\nonly)\", and \"devtools lab policy schema-versioning validates every registry entry\nagainst the allowed idempotent-DDL shapes and rejects anything else\".\n\nWHAT THE VALIDATOR IS. _invalid_benign_ddl_entries\n(devtools/verify_schema_upgrade_lane.py:144-171) is regex matching, not SQL\nparsing:\n _ALLOWED_BENIGN_DDL_PATTERNS (:120-135) e.g. ^\\s*CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s\n _FORBIDDEN_BENIGN_DDL_PATTERNS (:120-135) ALTER TABLE / INSERT INTO / UPDATE / DELETE FROM\nIt does correctly block multi-statement smuggling: a ';' scan at :152-156 after\nstripping one trailing semicolon.\n\nTHE GAP. `CREATE TABLE IF NOT EXISTS x AS SELECT ...` is idempotent-LOOKING and\ngenuinely data-transforming. It matches the allowed CREATE TABLE IF NOT EXISTS\nprefix, contains none of the forbidden tokens, and carries no second statement --\nso it passes. The allowlist has no rule against `... AS SELECT`, because a regex\non the statement prefix cannot see the statement's shape.\n\nThis matters more than a normal lint gap because of where these statements run:\napply_index_benign_ddl_convergence executes on EVERY same-version index.db open\n(bootstrap.py:174-192), on fresh and existing archives alike, with no version\nbump and no reparse. A data-transforming statement placed there would rewrite\nderived content on every open, silently.\n\nCURRENTLY UNREACHABLE: the live registry\n(storage/sqlite/archive_tiers/index_convergence.py:63-80) contains only DROP\nTABLE IF EXISTS entries. Nothing is wrong today.\n\nAC:\n- The validator rejects `AS SELECT` (and any other data-producing tail) on a\n CREATE TABLE IF NOT EXISTS entry -- either an added forbidden pattern or a real\n statement parse.\n- A test adds a `CREATE TABLE IF NOT EXISTS t AS SELECT 1` registry entry and\n asserts the lint fails, so the guard is proven rather than assumed.\n","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:54:04Z","created_by":"Sinity","updated_at":"2026-07-31T07:54:04Z","labels":["area:devtools"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-pkst","title":"session_links over-claims: 4-value enum vs 2-value CHECK, unconstrained inheritance pairing, cycle-budget false positives","description":"AUDIT FINDING (claimed-vs-enforced invariants sweep, 2026-07-31). Three small,\nrelated over-claims on the session_links surface. All dormant on live data; filed\nso they are tracked debt rather than anonymous debt.\n\n--- 1. TopologyEdgeStatus advertises four values; the column permits two.\nCLAUDE.md: \"TopologyEdgeStatus = unresolved/resolved/repaired/quarantined\n(cycle-break)\". core/enums.py:323-329 does define four members. But the DDL,\nstorage/sqlite/archive_tiers/index.py:763:\n status TEXT CHECK(status IN ('repaired','quarantined') OR status IS NULL)\n`resolved` and `unresolved` are never literal column values -- they are inferred\nstructurally from resolved_dst_session_id being NULL or not\n(storage/sqlite/queries/session_links.py:26-29 only ever serializes QUARANTINED\nand REPAIRED). MEASURED live: 9,333 session_links rows, 0 quarantined,\n0 repaired, 1,426 unresolved-by-structure. Not a bug; a hand-maintained subset of\nan enum with no check that the subset stays valid if the enum is renamed or\nextended. Related to the literal_check bead (the generation mechanism CLAUDE.md\ncites does not run).\n\n--- 2. The inheritance \u003c-\u003e branch_point pairing is convention, not constraint.\nThe design requires inheritance='prefix-sharing' to carry a branch point and\n'spawned-fresh' not to. MEASURED live -- it holds perfectly:\n inheritance NULL, branch_point NULL 1,436\n inheritance 'prefix-sharing', branch_point NOT NULL 537\n inheritance 'spawned-fresh', branch_point NULL 7,360\n contradictory rows 0\nBut nothing enforces it. index.py:762-763 constrains `inheritance` and `status`\nindependently; there is no cross-column CHECK. The consistency is a property of\none write path (write.py:5074-5096, where branch_point_message_id is computed\nonly alongside the 'prefix-sharing' assignment). A second writer, or a repair\nthat nulls one field without the other, produces a row the schema accepts and the\ncomposition logic cannot interpret.\n\n--- 3. Cycle-walk budget exhaustion is reported as a cycle.\n_would_create_cycle (storage/sqlite/queries/session_links.py:93-128) walks\nsessions.parent_session_id upward for at most _CYCLE_WALK_BUDGET = 1024 steps. On\nexhaustion it appends \"...budget-exceeded\" to the path and returns it as a TRUTHY\ncycle result (:109-111), so _quarantine_link (:131-173) records\nevidence_json reason \"cycle_rejected\". A legitimate chain deeper than 1024 hops\nis therefore quarantined as if it were a cycle -- a false positive that\npermanently drops a real lineage edge and mislabels why.\nTrue cycles are detected correctly (genuine parent-pointer traversal to a repeat).\nThe read-composition path has its own independent limit,\nLINEAGE_ITERATIVE_DEPTH_LIMIT = 1024 (store_constants.py:16), which on exhaustion\nsets LINEAGE_TRUNCATION_DEPTH_LIMIT instead of quarantining -- and that signal is\nsubject to the discard bug filed separately.\nMEASURED: deepest live prefix-sharing chain is 60 hops. Dormant.\n\nAC:\n- The status CHECK either lists what the enum lists, or a comment at the DDL\n records that the column is deliberately a two-value subset and why.\n- The inheritance/branch_point pairing is a CHECK constraint, or the invariant is\n stated at the DDL so a future writer sees it.\n- Budget exhaustion is distinguishable from a detected cycle in the quarantine\n evidence, so an operator can tell a false positive from a real one.\n","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-31T07:54:02Z","created_by":"Sinity","updated_at":"2026-07-31T07:54:02Z","labels":["area:storage"],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -930,7 +1011,7 @@ {"_type":"issue","id":"polylogue-xnws","title":"Audit silent exception swallowing against the loud-degradation doctrine","description":"WHY: the evidence-honesty doctrine says degraded modes are loud and unknown never renders as zero/blank, but no census exists of except-and-continue sites in production code. Dogfood passes repeatedly found this exact shape as real bugs: transient capture failures permanently excluding 22 cursors (3v1 audit), refresh.py silently skipping the heavy-session safety valve (61zb). Every broad except that logs-and-continues is a candidate for invisible evidence loss.","design":"AST census over polylogue/ production packages for except handlers that (a) pass, (b) log below warning and continue, (c) catch Exception/BaseException broadly. Classify by consequence: evidence-dropping (raw/parse/materialize/convergence path continues with data loss), state-dropping (debt/receipt/degradation marker not written), benign (cleanup, best-effort presentation). For each evidence-dropping site verify whether a durable debt row, receipt, or degradation marker is written on that path; absence is a defect. Pitfalls: asyncio CancelledError re-raise conventions; legitimate contextlib.suppress; do not demand receipts from genuinely best-effort presentation code. The census half is mechanical (delegable); the consequence classification requires reading the route.","acceptance_criteria":"Census recorded with counts per class; every evidence-dropping site lacking a durable degradation record filed as a defect bead naming the exact route and observable consequence; explicit negative result recorded if none found. Method documented on the bead so future sweeps rerun it comparably.","notes":"CENSUS COMPLETE 2026-07-16 (Fable, inline AST scan of polylogue/ production packages; method: ast.ExceptHandler walk classifying bare/broad type x body shape): 14 broad except+pass, 104 broad-catch with NO log and NO re-raise, 148 log-and-continue = 266 candidate sites. High-signal clusters for the consequence-classification pass, in priority order: (1) daemon/backup.py:296,660,671 - silent broad catches in BACKUP code; potentially connected to the yla8 preflight finding that the last verified full backup was not current - if a backup failure path swallows, staleness is invisible by construction; classify FIRST. (2) cli/commands/status.py - 8 broad except+pass plus at least 4 silent broad catches; the status surface silently degrading is the most direct loud-degradation-doctrine violation possible (status exists to report degradation). (3) daemon/health.py:141, daemon/convergence_debt_alert.py:174, daemon/fts_startup.py:134 - health/alert/startup paths that swallow defeat their own purpose. (4) api/sync/bridge.py:47 catches BaseException without re-raise - can eat KeyboardInterrupt/CancelledError; check asyncio cancellation correctness. Remaining work per AC: route-level consequence classification (evidence-dropping vs state-dropping vs benign) for the 118 no-log sites, then defect beads for evidence-droppers lacking durable degradation records. Census script is reproducible from this note's method description.\nPILOT SWEEP COMPLETE 2026-07-16 (Fable, 9 priority sites + method calibration; traces at .agent/reports/narration/2026-07-16-xnws-pilot.jsonl): 5 sites benign-correct (backup.py x3, health.py, sync/bridge.py - four are 'error-captured-into-result', loud by DATA FLOW not logging; bridge is deliberate cross-thread BaseException marshalling re-raised after join), 1 benign-by-contract (convergence_debt_alert 'unknown' family; optional ImportError narrowing), 1 HIGH evidence-dropping cluster (status.py component assembly, 6 except-pass sites -\u003e defect bead polylogue-feqr with fix fragment), 2 judgment candidates (fts_startup.py:134 silent skip of startup FTS maintenance on schema-probe failure; status.py:225 active-pointer unreadable vs absent conflation). CRITICAL METHOD CALIBRATION for the remaining ~109 sites: the census needs two new benign classes - error-captured-into-result (handler assigns exc into returned/mutated object) and reraise-after-capture (stored exception re-raised outside the handler, e.g. thread joins) - or it overestimates ~5x. The backup.py cluster is NOT the yla8 stale-backup cause; verification failures are fully loud there.\nCALIBRATED CENSUS 2026-07-16 (Fable, implements trace xnws-m01): with the two new benign classes the 266 raw sites reduce to: 104 captured-into-result + 8 reraise-after-capture + 10 except-pass = auto-classified; 160 log-and-continue (lower tier, revisit only after candidates); 35 GENUINE EVIDENCE CANDIDATES = the walkable worklist. Candidates by cluster: context/preamble.py x3 (56/80/102, except-pass in the context-injection path - HIGH interest: silent preamble degradation is invisible context loss); mcp/server_context_tools.py:184 x3 (except-pass, rewrite-boundary t46.8 - note-only); status.py 225/1099/2393/2408 (p09 already traced); tutorial.py 91/152; tree_sitter.py 71/106 (code-detection degradation); daemon http.py:2100, metrics.py:955, status.py:2283, fts_startup.py:134 (p07), convergence_debt_alert.py:174 (p06); browser_capture/receiver.py:800; sources/token_store.py:119 + drive/source_support.py:108 (auth/token paths - silent failure = silent capture stop); storage/repository/raw/repository_raw.py:110; api/archive.py:5109; cli click_app.py 250/313, archive_query.py:1225, shell_completion_values.py:101, convergence_feedback.py:28, paths.py:282; api/contracts/tui_surface.py:75; insights/correlation_view.py:93; schemas/generation/schema_builder.py:38; ui/theme.py:298; ui/tui/screens/search.py:51. Census method (AST, handler-name dataflow + function-level reraise detection) documented in this note's implementing script; next pass walks the 35 with per-site consequence classification, prioritizing preamble/token-store/receiver/raw-repository (evidence-plane paths).\nSWEEP COMPLETE 2026-07-17 (Fable, PR #2963): all 35 calibrated candidates walked with per-site consequence classification; traces at .agent/reports/narration/2026-07-17-xnws-sweep.jsonl (committed). 14 genuine violations FIXED in the PR: preamble x3 (new ContextPreamble.component_failures field + warnings), fts_startup:134 (p07 resolved - warning on probe failure), status.py 225 (p09 resolved - absent vs unreadable distinguished) /1099/2393/2408, receiver backfill-checkpoint corruption warning, repository_raw stat-fast-path warning, daemon http health log, correlation_view honest failed-query message, schema_builder _load_pins_safe wrapper DELETED (double-wrapped already-safe load_pins), paths.py debt_classifier_error marker. 21 benign/by-contract/note-only with rationale in traces. server_context_tools.py:184 deliberately deferred to t46.8 rewrite (component_failures is the landing spot). Bonus root-cause fix: authored scenario catalog now pinned to in-repo SCHEMA_DIR - operator-local inferred schemas were leaking into render quality-reference and turning the pre-push quick gate red per-machine. Remaining lower tier: 160 log-and-continue sites, deprioritized by construction (they already log). AC status: census=done, consequence classification=done, defect-beads-or-fixes for evidence-droppers=done (all fixed directly). Close after #2963 merges.\nQUANTIFIED 2026-07-29. polylogue/ contains 0 bare 'except:' (good) but:\n except Exception 391 sites\n except ...: return None 232\n except ...: continue 68\n with suppress(...) 35\n except ...: pass 30\n\nConcentration is the finding: the four convergence/daemon modules are the top\nswallowers -- daemon/convergence_stages.py 31, daemon/cli.py 23,\ndaemon/health.py 20, daemon/convergence.py 14. That is the subsystem the\noperator most often cannot get a straight answer about ('something is\nconstantly broken and nobody can say why'), and it is also where failures are\nmost likely to be absorbed into a warning and a retry.\n\nPrioritise the convergence four over a tree-wide sweep; a swallowed exception\ninside a bounded convergence pass is indistinguishable from 'no work to do'.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own 2026-07-29 quantified re-scan: except Exception 391 sites, except: pass 30, with suppress 35, with daemon/convergence_stages.py(31)/daemon/cli.py(23)/daemon/health.py(20)/daemon/convergence.py(14) named as top unaudited concentration and explicitly prioritized as remaining work -- dated one day before this sweep, still current. Census + first sweep (35 candidates, 14 fixed via PR #2963) done; much larger remaining population never swept.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T18:51:56Z","created_by":"Sinity","updated_at":"2026-07-31T05:48:03Z","labels":["area:audit","area:daemon","area:substrate","horizon:frontier"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-896y","title":"Audit production time authority: naive datetimes and wall-clock reads outside a clock seam","description":"WHY: test-side clock hygiene is enforced (tests/infra/frozen_clock.py, verify-test-clock-hygiene lint, docs/plans/test-clock-allowlist.yaml) but there is no equivalent audit of PRODUCTION paths, while timestamp semantics are load-bearing (sort keys, freshness stages, revision authority, cost windows). The external testdiet-15 job (equivalent-instant temporal behavior) has no owning bead; this audit is its local evidence base. Suspected shapes: datetime.now() without tz, naive/aware comparisons, time.time() drift between tiers, module-level now captured at import.","design":"Static census first: rg for datetime.now/utcnow/time.time/date.today across polylogue/ (tests excluded), then classify each site: (a) behind an injectable clock/seam, (b) operational logging only, (c) semantic - affects stored values, comparisons, or query results. For every class-(c) site trace the failure under tz change/DST/clock skew and check naive/aware mixing. Deliverable is evidence, not a blanket rewrite: per-site classification, defect beads for real failures, and a clock-seam proposal ONLY if the class-(c) population justifies one (no spelling-ban lints per the fossilized-diff rule). Pitfalls: provider-observed timestamps are data, not clock reads - keep them out of scope; ops.db timestamps are disposable-tier, lower stakes; dateparser internals out of scope.","acceptance_criteria":"Every production datetime.now/utcnow/time.time/date.today site classified (count per class a/b/c) and recorded on this bead or a linked packet; every class-(c) site either proven safe with a one-line reason or filed as its own defect bead with a concrete failure scenario; an explicit yes/no on whether a production clock seam is warranted with rejected alternatives. testdiet owning-beads.json updated to reference this bead for testdiet-15.","notes":"2026-07-17: PR #3044 / 1d3145afa admitted Test Diet 15: canonical UTC public instants, normalized temporal bounds, and inferred-event-gap semantics with 238 affected-route tests. This advances but does not close the broader production time-authority audit.\nVERDICT: LIVE — no production-time-authority census/classification artifact found anywhere in the repo (checked docs/, .agent/, testdiet owning-beads.json); PR #3044 (Test Diet 15) only touched test-side route timing per the bead's own note ('advances but does not close the broader production time-authority audit'). No class a/b/c site classification exists. Evidence: find for *clock-audit*/*production-clock* found nothing; bead's own 2026-07-17 note.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T18:51:55Z","created_by":"Sinity","updated_at":"2026-07-31T05:51:53Z","labels":["area:audit","area:substrate","horizon:frontier"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-cinh","title":"Terminal-grid rendering laws: width-degradation snapshots over the PTY harness","description":"WHY: tests/infra/pty_cli.py provides a full pyte-based PTY harness (grid rendering, ANSI capture, normalization) but no test asserts CLI output layout at controlled widths. The aesthetics program (closed tjx1 direction; 9xuk/bkzv/dbiv children) makes CLI presentation a product surface - density with rhythm, tabular alignment, unknown-as-dash - and none of it is executable for the terminal today. Live surfaces: read --view transcript, status, analyze tables render at whatever width the host gives with no law preventing mid-cell truncation, ANSI bleed, or interleaved wrap garbage at narrow widths.","design":"Extend tests/infra/pty_cli.py with a width-matrix helper: run the same command against the demo archive at PTY winsize widths 80/120/200 (set winsize, not just COLUMNS env - Rich reads the terminal), render the pyte grid, and assert structural laws on the GRID, never raw bytes: (1) no row exceeds the width; (2) table column separators align across rows; (3) no ANSI escape fragments survive in cell text; (4) narrower width degrades content (record count monotone, no two logical rows interleaved into one). Add a tiny syrupy snapshot tier (\u003c=6 normalized grids) for change detection; the laws are the real gate. Pitfalls: strip timestamps/paths with the existing normalizers; keep snapshots minimal to avoid churn. Coordination: dbiv owns styling/theme routing (blocked on 9xuk) - this bead tests geometry only, no color/style assertions, so it lands independently and dbiv inherits the harness.","acceptance_criteria":"A width-matrix helper plus one test module exercising at least read --view transcript, a status/dashboard view, and one analyze table at widths 80/120/200 against the demo archive (no live archive). Laws 1-4 asserted semantically. Anti-vacuity: forcing an over-width row into the renderer (or lying about width without re-rendering) fails the law tests. Runs via devtools test \u003cmodule\u003e; snapshot set \u003c=6 grids.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T18:51:53Z","created_by":"Sinity","updated_at":"2026-07-16T18:51:53Z","labels":["area:cli","area:test","horizon:frontier"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-5vbs","title":"FTS convergence-debt session-scoped retry has no live feeder for the fts stage","description":"dogfood-2 round-3 investigation (investigations/fts-convergence-divergence.md): in archive/split-file mode (the sole live runtime), make_fts_stage (daemon/convergence_stages.py:80) has path-scoped check_many/execute_many (_archive_fts_check_many/_archive_fts_execute_many, convergence_stages.py:1022-1031) hardcoded as no-op stubs that never touch the database -- deliberate, since session/message writes already run repair_message_fts_index_sync in-transaction as a WriteEffect with failure_policy=abort (archive/write_effects.py:99-130), so a repair failure poisons the whole write rather than landing silently. However this means DaemonConverger.converge_batch (convergence.py:298-413) marks the fts stage DONE for every write unconditionally (check_many always returns an empty needs-work set), so no ConvergenceDebt(stage=\"fts\", subject_type=\"session_id\") row is EVER produced by the live write path -- confirmed by tracing sources/live/batch.py -\u003e convergence.py -\u003e sources/live/convergence_debt.py -\u003e daemon/cli.py:_drain_convergence_debt_once end to end, and by grepping every direct writer of stage=\"fts\" debt (exactly two, both using subject_type=\"fts_surface\" through a separate repair_fts_surface branch that bypasses make_fts_stage entirely). Consequence: check_sessions/execute_sessions (convergence_stages.py:1034-1069) -- real, correct, well-tested implementations that exist specifically \"for retrying convergence_debt without re-resolving source paths\" per the module docstring -- are currently dead code on the live retry path, because nothing ever populates a subject for them to retry. Comparative check: embed and insights convergence stages do NOT have this gap -- both share one real predicate and one real execution worker across their _many/_sessions pairs, with working end-to-end debt-retry tests; this is FTS-specific, not a systemic four-callable-shape problem.","design":"The narrow, in-transaction write path is correctly covered and does not need this route. The gap is the missing safety net for FTS staleness introduced OUTSIDE that write path -- partial migration, external DB surgery, a future write path that skips the write_effects.py registry, or a bug in a currently-unlisted writer. Fix options: (a) wire a real path-scoped check_many/execute_many implementation (mirroring embed/insights shape) so any drift is caught the same way other stages catch it, accepting the small per-write cost the current stub avoids; or (b) if the write-time guarantee is judged sufficient, add an explicit periodic global audit (distinct from the existing fts_surface route, which is driven only by post-raw-replay failure) that can produce stage=\"fts\", subject_type=\"session_id\" debt rows when it finds drift, so the already-built check_sessions/execute_sessions retry path has a real feeder.","acceptance_criteria":"Either FTS staleness introduced outside the in-transaction write path is detected and produces a stage=fts/subject_type=session_id convergence_debt row that check_sessions/execute_sessions can retry, or the design decision to rely solely on the write-time abort-policy guarantee is explicitly documented as sufficient with the residual gap (external DB surgery, partial migration, hypothetical future writer bugs) named and accepted.","notes":"\n2026-07-17 GPT-Pro testdiet-02 admission: campaign artifact `testdiet/results/testdiet-02/r01` was reconciled on current master and accepted as PR #3014 (`feature/test/convergence-restart-laws`, c261f8eb4). It adds a production restart/retry/quiescence law covering partial insight and FTS convergence debt across fresh Python processes through `_drain_convergence_debt_once`, not a synthetic mock. Verification: handoff law passed; 54 daemon convergence/stage/final-state/restart tests passed; ruff, strict mypy, and quick verification passed. This proves the existing retry path when debt exists. It does NOT by itself satisfy this bead’s missing live feeder question: whether FTS staleness outside the write-time abort path creates a `stage=fts, subject_type=session_id` debt row remains the implementation decision/open requirement.\n2026-07-17 Test Diet 02 r02 was acquired and CRC-readable but its own RELEASE-STATUS is INCOMPLETE/FAIL, with unknown base, no changed files, no command results, and no PATCH.diff. It is retained in campaign custody as failed-delivery evidence only; it changes neither the verified PR #3014 slice nor the remaining live FTS-debt-feeder scope.\n2026-07-19 coordinator: the convergence redesign (m6tp program, esp. polylogue-gd6v bulk routing) will restructure stage feeding — re-evaluate this FTS debt-feeder gap against the gd6v design before implementing standalone; it may be subsumed.\n2026-07-31 empirical evidence (H9, adversarial dataset investigation, live archive): confirmed non-zero FTS gap despite the fts_freshness_state bookkeeping only ever showing a single recently-touched session as 'stale'. Direct count: 10,837 blocks with populated search_text are absent from messages_fts (4,956,019 populated blocks vs 4,945,241 rows in messages_fts_docsize, verified by anti-join not just the row-count delta). Spot-checked 10 of the highest-rowid (most recent) gap blocks directly -- all are real substantive text content (claude-ai-export text blocks with legible prose), not empty/degenerate rows, so this is real un-searchable content, not a false positive. This is smaller than the prior session's 36,757/13,235 figures (measurement methodology differs and may not be apples-to-apples), but non-zero after what was expected to be a clean post-rebuild state, consistent with this bead's thesis that nothing currently produces a stage=fts/subject_type=session_id convergence_debt row to catch drift introduced outside the in-transaction write path.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own 2026-07-31 (today) note: live anti-join query still finds 10,837 blocks with populated search_text absent from messages_fts (spot-checked 10, all real content). No feeder for stage=fts,subject_type=session_id convergence_debt confirmed still missing same day. Real unaddressed work.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T11:18:05Z","created_by":"Sinity","updated_at":"2026-07-31T05:46:25Z","labels":["area:daemon","area:search","discovered-from:dogfood-2"],"dependencies":[{"issue_id":"polylogue-5vbs","depends_on_id":"polylogue-a7xr","type":"parent-child","created_at":"2026-07-16T13:25:48Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-5vbs","title":"FTS convergence-debt session-scoped retry has no live feeder for the fts stage","description":"dogfood-2 round-3 investigation (investigations/fts-convergence-divergence.md): in archive/split-file mode (the sole live runtime), make_fts_stage (daemon/convergence_stages.py:80) has path-scoped check_many/execute_many (_archive_fts_check_many/_archive_fts_execute_many, convergence_stages.py:1022-1031) hardcoded as no-op stubs that never touch the database -- deliberate, since session/message writes already run repair_message_fts_index_sync in-transaction as a WriteEffect with failure_policy=abort (archive/write_effects.py:99-130), so a repair failure poisons the whole write rather than landing silently. However this means DaemonConverger.converge_batch (convergence.py:298-413) marks the fts stage DONE for every write unconditionally (check_many always returns an empty needs-work set), so no ConvergenceDebt(stage=\"fts\", subject_type=\"session_id\") row is EVER produced by the live write path -- confirmed by tracing sources/live/batch.py -\u003e convergence.py -\u003e sources/live/convergence_debt.py -\u003e daemon/cli.py:_drain_convergence_debt_once end to end, and by grepping every direct writer of stage=\"fts\" debt (exactly two, both using subject_type=\"fts_surface\" through a separate repair_fts_surface branch that bypasses make_fts_stage entirely). Consequence: check_sessions/execute_sessions (convergence_stages.py:1034-1069) -- real, correct, well-tested implementations that exist specifically \"for retrying convergence_debt without re-resolving source paths\" per the module docstring -- are currently dead code on the live retry path, because nothing ever populates a subject for them to retry. Comparative check: embed and insights convergence stages do NOT have this gap -- both share one real predicate and one real execution worker across their _many/_sessions pairs, with working end-to-end debt-retry tests; this is FTS-specific, not a systemic four-callable-shape problem.","design":"The narrow, in-transaction write path is correctly covered and does not need this route. The gap is the missing safety net for FTS staleness introduced OUTSIDE that write path -- partial migration, external DB surgery, a future write path that skips the write_effects.py registry, or a bug in a currently-unlisted writer. Fix options: (a) wire a real path-scoped check_many/execute_many implementation (mirroring embed/insights shape) so any drift is caught the same way other stages catch it, accepting the small per-write cost the current stub avoids; or (b) if the write-time guarantee is judged sufficient, add an explicit periodic global audit (distinct from the existing fts_surface route, which is driven only by post-raw-replay failure) that can produce stage=\"fts\", subject_type=\"session_id\" debt rows when it finds drift, so the already-built check_sessions/execute_sessions retry path has a real feeder.","acceptance_criteria":"Either FTS staleness introduced outside the in-transaction write path is detected and produces a stage=fts/subject_type=session_id convergence_debt row that check_sessions/execute_sessions can retry, or the design decision to rely solely on the write-time abort-policy guarantee is explicitly documented as sufficient with the residual gap (external DB surgery, partial migration, hypothetical future writer bugs) named and accepted.","notes":"\n2026-07-17 GPT-Pro testdiet-02 admission: campaign artifact `testdiet/results/testdiet-02/r01` was reconciled on current master and accepted as PR #3014 (`feature/test/convergence-restart-laws`, c261f8eb4). It adds a production restart/retry/quiescence law covering partial insight and FTS convergence debt across fresh Python processes through `_drain_convergence_debt_once`, not a synthetic mock. Verification: handoff law passed; 54 daemon convergence/stage/final-state/restart tests passed; ruff, strict mypy, and quick verification passed. This proves the existing retry path when debt exists. It does NOT by itself satisfy this bead’s missing live feeder question: whether FTS staleness outside the write-time abort path creates a `stage=fts, subject_type=session_id` debt row remains the implementation decision/open requirement.\n2026-07-17 Test Diet 02 r02 was acquired and CRC-readable but its own RELEASE-STATUS is INCOMPLETE/FAIL, with unknown base, no changed files, no command results, and no PATCH.diff. It is retained in campaign custody as failed-delivery evidence only; it changes neither the verified PR #3014 slice nor the remaining live FTS-debt-feeder scope.\n2026-07-19 coordinator: the convergence redesign (m6tp program, esp. polylogue-gd6v bulk routing) will restructure stage feeding — re-evaluate this FTS debt-feeder gap against the gd6v design before implementing standalone; it may be subsumed.\n2026-07-31 empirical evidence (H9, adversarial dataset investigation, live archive): confirmed non-zero FTS gap despite the fts_freshness_state bookkeeping only ever showing a single recently-touched session as 'stale'. Direct count: 10,837 blocks with populated search_text are absent from messages_fts (4,956,019 populated blocks vs 4,945,241 rows in messages_fts_docsize, verified by anti-join not just the row-count delta). Spot-checked 10 of the highest-rowid (most recent) gap blocks directly -- all are real substantive text content (claude-ai-export text blocks with legible prose), not empty/degenerate rows, so this is real un-searchable content, not a false positive. This is smaller than the prior session's 36,757/13,235 figures (measurement methodology differs and may not be apples-to-apples), but non-zero after what was expected to be a clean post-rebuild state, consistent with this bead's thesis that nothing currently produces a stage=fts/subject_type=session_id convergence_debt row to catch drift introduced outside the in-transaction write path.\nVerification (group2 sweep, 2026-07-30): LIVE. Bead's own 2026-07-31 (today) note: live anti-join query still finds 10,837 blocks with populated search_text absent from messages_fts (spot-checked 10, all real content). No feeder for stage=fts,subject_type=session_id convergence_debt confirmed still missing same day. Real unaddressed work.\n2026-07-31 group3 sweep (agent-af085793b115e79d5): re-measured live (anti-join blocks vs messages_fts_docsize) -- found 0 orphans / 0 convergence_debt rows at measurement time, down from the bead's own same-day 10,837 figure. The archive is under heavy concurrent multi-agent write/investigation load tonight; this population is a moving target, not a stable one, and the daemon's periodic convergence-debt drain (every 60s) appears to have caught up between the bead's last note and this measurement.\n\nTraced the \"no live feeder\" claim and found it is now PARTIALLY STALE: git blame shows sources/live/batch.py:2146-2159 and :2478-2483 (commit 4120c40c2b, 2026-07-26 -- 5 days before this bead's most recent \"confirmed still missing\" note) DO record stage=\"fts\", subject_type=\"session_id\" convergence debt for their own two deferred-FTS write branches (full ingest, membership replay), which daemon/cli.py's _drain_convergence_debt_once correctly dispatches to make_fts_stage's check_sessions/execute_sessions (confirmed these are real, not stubs, by reading them directly). So the \"exactly two writers, both subject_type=fts_surface\" claim in this bead's design section is no longer accurate for those two call sites specifically.\n\nWhat's still genuinely true and unaddressed: this only covers drift the write path ITSELF introduces via those two specific deferred branches. There is still no feeder for drift introduced OUTSIDE any write path at all -- external DB surgery, a partial migration, a future writer bug -- exactly the residual gap this bead's design section (option b) already named as the acceptance-worthy fix: \"add an explicit periodic global audit... that can produce stage=fts, subject_type=session_id debt rows when it finds drift.\"\n\nImplemented that option (b). New module polylogue/daemon/fts_orphan_audit.py: find_orphaned_fts_sessions_sync (bounded anti-join over the existing idx_blocks_search_text_populated partial index, 200 sessions/call) + run_fts_orphan_audit_once_sync (records the found sessions as retryable stage=fts/subject_type=session_id debt via CursorStore) + periodic_fts_orphan_audit (hourly asyncio loop, wired into daemon/cli.py's periodic_loops list alongside periodic_fts_identity_drift_recompute -- same \"standalone loop, not a ConvergenceStage\" shape convergence_stages.py's own 1498-cascade retro already prescribes for new FTS maintenance). PR pending, branch fix/cost-fts-null-bugs, commit dd38f4747.\n\nVerified with tests/unit/daemon/test_fts_orphan_audit.py (7 tests): hand-orphans a block's messages_fts row directly (bypassing the write path entirely, simulating the exact external-drift shape this bead's design names) and proves (a) the audit finds it, (b) records real convergence debt for it, and (c) the ALREADY-EXISTING make_fts_stage.check_sessions/execute_sessions genuinely repairs it end to end -- not just recording debt that nothing drains. mypy --strict clean.\n\nThis closes the design gap (option b) but does not resolve the live 10,837-vs-0 measurement discrepancy, which is unexplained and worth a fresh independent re-measurement once the archive is quiet -- the swing is large enough (10,837 -\u003e 0 in under an hour of concurrent activity) that either the existing session-scoped retry path was already working better than the bead's last note credited, or something else is repairing orphans that this investigation didn't identify. Recommend a follow-up quiet-window re-measurement before declaring the underlying data-divergence question fully closed.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T11:18:05Z","created_by":"Sinity","updated_at":"2026-07-31T09:09:26Z","labels":["area:daemon","area:search","discovered-from:dogfood-2"],"dependencies":[{"issue_id":"polylogue-5vbs","depends_on_id":"polylogue-a7xr","type":"parent-child","created_at":"2026-07-16T13:25:48Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-lyv4","title":"rebuild_session_insights_async targeted branch does an unscoped archive-wide wipe instead of matching its sync twins scoped refresh","description":"dogfood-2 insights-rebuild investigation (investigations/insights-rebuild-correctness.md): rebuild_session_insights_sync, when called with an explicit session_ids (the targeted/incremental case), branches at rebuild.py:1535 into a properly scoped refresh -- only affected thread roots, only touched provider-day groups, no table-wide DELETE. rebuild_session_insights_async has no equivalent branch: after its per-chunk session loop (which is correctly scoped), it unconditionally runs \"DELETE FROM threads\" (rebuild.py:1714) then rebuilds ALL roots archive-wide, and \"DELETE FROM session_tag_rollups\" (rebuild.py:1724) then rebuilds ALL provider-day groups archive-wide, regardless of whether session_ids was None or an explicit subset -- confirmed both iter_root_id_pages_async/_sync and list_async_provider_day_groups take no session-id-scoping parameter at all. Currently non-corrupting: the sole production caller (pipeline/run_stages.py:187) always passes session_ids=None (where the two behaviors coincide since a full rebuild legitimately wants an archive-wide wipe), but rebuild_session_insights_async is public API (exported in __all__) and IS directly exercised with a non-None session_ids by tests/unit/storage/test_session_insight_refresh.py:992 -- that test only uses a single-session fixture so the archive-wide-wipe cost is not visible in its assertions, which is why this has not been caught. Compounding: unlike the sync twin, which commits internally on every return path, rebuild_session_insights_async never calls conn.commit() after its post-loop threads/tag-rollup/aggregate section -- it currently only works because the sole caller happens to call commit() immediately afterward; any caller assuming the async function commits internally (as its own comment block \"Bounded-WAL parity with rebuild_session_insights_sync\" invites) would silently lose the entire refresh on connection close with no exception.","design":"Port the sync twins scoped-refresh branch (thread_root_ids_sync-equivalent root/group scoping) into the async targeted path, and add the missing internal commit so the async function actually matches the parity its own comment claims.","acceptance_criteria":"rebuild_session_insights_async(conn, session_ids=[subset]) only touches threads/session_tag_rollups rows for roots/groups reachable from that subset, matching the sync twins behavior, and commits its own work internally without relying on caller cleanup. The existing single-session test at test_session_insight_refresh.py:992 is extended (or a sibling test added) with a multi-session, multi-thread fixture that would fail under the current unscoped-wipe behavior.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T11:03:10Z","created_by":"Sinity","updated_at":"2026-07-16T11:03:10Z","labels":["area:insights","discovered-from:dogfood-2"],"dependencies":[{"issue_id":"polylogue-lyv4","depends_on_id":"polylogue-a7xr","type":"parent-child","created_at":"2026-07-16T13:25:46Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-60v8","title":"Archive .agent/scratch/ directories once their open-bead references clear","description":"A 2026-07-16 audit found most .agent/scratch/ directories are not dead notes but live packet infrastructure: corpus-gpt-pro-2026-07-07 backs 122 open beads prework-packet notes, new backs 181, research backs 32, legibility-kit-2026-07-10 backs 8, corpus-gpt-pro-2026-07-06 backs 6, readme-positioning-2026-07-14 backs 4, legibility-kit-v2-2026-07-10 backs 3, new-gpt-pro and fanout-prompts back 2 each (open-bead reference counts, via grep over .beads/issues.jsonl). Moved the only 3 directories with zero open-bead dependency (2026-07-04-beads-swarm, gpt-fork-deliveries-2026-07-10, swarm2) to .agent/archive/scratch-2026-07/. The rest cannot be archived without either (a) waiting for their referencing beads to close, or (b) bulk-updating every referencing bead notes field to a new path first - not attempted here since (b) is a much larger, riskier task than the cleanup ask that prompted this audit.","design":"Periodically re-run: for each .agent/scratch/\u003cdir\u003e, grep .beads/issues.jsonl for the directory name and count open vs closed bead matches. Once a directory hits zero open references, move it to .agent/archive/scratch-2026-07/ (or a fresh dated bucket) with a short README noting why it was safe (mirrors .agent/archive/scratch-2026-07/README.md). Also separately resolve legibility-kit-2026-07-10 vs legibility-kit-v2-2026-07-10 - a literal versioned duplicate, both still open-bead-referenced (8 and 3 respectively) - by checking whether v1s referencing beads are actually satisfied by v2s content before considering v1 redundant.","acceptance_criteria":"Not closable until the referenced-directory backlog clears naturally or a deliberate reference-migration is done; treat as a recurring low-priority housekeeping check, not a one-shot close.","notes":"CORRECTION 2026-07-16: the original open-reference counts for new (181 open) and research were computed with an unanchored substring grep that matched incidental occurrences of the word \"new\" elsewhere in bead text, not real .agent/handoffs/polylogue-session-snapshot-2026-07-08/ path references. Redone with path-anchored matching (needle = \"scratch/\u003cdirname\u003e\"): new=3 open/4 closed (not 181/167), research=12 open/22 closed, corpus-gpt-pro-2026-07-06=4 open/1 closed, new-gpt-pro=2 open/4 closed, readme-positioning-2026-07-14=4 open/1 closed, legibility-kit-2026-07-10=8 open/1 closed, legibility-kit-v2-2026-07-10=3 open/0 closed. corpus-gpt-pro-2026-07-07 (122 open/66 closed) was already accurate - it matches exactly the count of beads carrying the real structured \"[Prework packet 2026-07-07]\" notes-field marker, confirming that corpus is the ONLY one using a consistent, grep-recoverable tag; the others (research, legibility-kit, readme-positioning, corpus-gpt-pro-2026-07-06) are referenced via ad hoc prose, not a reusable marker - there is no queryable bd label for \"has a prework packet\" or \"packet consumed\", only free text. legibility-kit v1-vs-v2 resolved: NOT a simple duplicate. v2 self-describes as a second edition of v1 but is explicitly, provably incomplete per its own MISSING-FROM-DOWNLOAD.txt (missing 01-ITERATION-AUDIT.md, 02-PUBLIC-STORY-V2.md, 09-VALIDATION-REPORT.md, the entire fork-prompts/*.md corpus, incident-1432/materials+parser, and more) - v1 is the complete package with real generated evidence (demo-tour archive containing actual source.db/index.db/embeddings.db/user.db/ops.db, a full rendered previews/polylogue-site/ site). polylogue-3tl.18 is explicitly the bead tasked with adjudicating/retiring the whole legibility-kit \"parallel control plane\" pattern - do not archive either v1 or v2 until 3tl.18 closes and whatever is still load-bearing is absorbed into beads proper.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T10:54:40Z","created_by":"Sinity","updated_at":"2026-07-16T11:01:56Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-ykhy","title":"Extract a shared read-only SQLite open helper for devtools/","description":"At least 18 devtools/ files hand-roll their own sqlite3.connect() call for opening the archive read-only, with no shared helper anywhere in devtools/ (confirmed: no open_archive/_open_readonly/ArchiveHandle/DevtoolsContext symbol exists). Sampled 4 concrete variants that disagree with each other: deployment_smoke.py and cost_reconciliation_probe.py use file:{path}?mode=ro; index_fast_forward.py adds timeout=30.0; archive_schema_fast_forward.py adds immutable=1. The divergence is a real risk, not just duplication - a devtools command missing immutable=1 can behave differently under concurrent daemon writes than one that has it. Surfaced during a 2026-07-16 refactoring-opportunity survey.","design":"Add one devtools/_sqlite.py (or similar) with open_readonly(path) and open_immutable_readonly(path) helpers encoding the correct, single URI construction (decide the right default including immutable=1 and timeout by consulting the storage/ tier docs on WAL/locking behavior for read-only access during live daemon writes). Migrate the 18 call sites to use it. Do not change behavior beyond making the URI construction consistent unless a site is found to be using a genuinely wrong mode for its use case, in which case fix that specific bug separately and note it in the bead.","acceptance_criteria":"A single shared open helper exists and is used by all 18 identified call sites; devtools test on affected files passes; any behavior change found necessary during migration (e.g. a site missing immutable=1 that needed it) is called out explicitly, not silently folded in.","notes":"PR #3316 opened: https://github.com/Sinity/polylogue/pull/3316 (branch feature/refactor/devtools-sqlite-open-helper). Migrated all 18 identified mode=ro call sites across 14 devtools files (degraded_archive_proof.py, index_v37_fast_forward.py x2, index_fast_forward.py x3, archive_schema_fast_forward.py, cost_reconciliation_probe.py, scale_regression_probe.py, deployment_smoke.py, self_verify.py, render_demo_corpus_datasheet.py, read_package.py, dev_loop.py x2, schema_generate.py, failure_context.py, test_economics_report.py) onto the pre-existing polylogue.storage.sqlite.connection_profile.open_readonly_connection helper (already used correctly by 6 other devtools files before this change). Extended that helper with immutable: bool=False, kept True only at the two sites (index_v37_fast_forward.py, archive_schema_fast_forward.py) that already prove zero WAL/SHM/journal sidecars before opening -- verified genuinely load-bearing, not drift. Preserved index_fast_forward.py's 30s/120s timeout overrides (live-archive lock contention); dropped their now-redundant manual PRAGMA query_only=ON since the helper sets it. Unified read_package.py's timeout=5.0 (was byte-identical to the canonical default). Flagged one real behavior change in the PR body: archive_schema_fast_forward.py previously built its immutable URI via Path.as_uri() (percent-encoded); the canonical helper uses the same plain f-string URI construction as its other 30+ existing callers, so that one site loses percent-encoding for paths with reserved URI chars -- an existing risk shared by all other callers, not newly introduced. Did not touch: row_factory assignments (query ergonomics, not connection semantics), the many read-write sqlite3.connect() calls in the same files, turso_probe.py (different library), or ATTACH DATABASE statements in pipeline_probe/result.py and daemon_workload_probe.py (attach to an already-open connection, not a new connect()). Verification: ruff+mypy strict clean, devtools render all --check exit 0, devtools test green on all touched files' test modules (149+360 passed); pre-existing unrelated failures (14 in test_index_v37_fast_forward.py/test_index_fast_forward_lifecycle.py, 1 in test_status.py) confirmed identical on origin/master via git stash A/B. Not closing -- leaving for operator review/merge.","status":"closed","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-16T10:43:09Z","created_by":"Sinity","updated_at":"2026-07-27T10:12:02Z","closed_at":"2026-07-27T10:12:02Z","close_reason":"Fixed and merged via PR #3316. Unified 18 divergent read-only sqlite3.connect() call sites across 14 devtools/ files onto the already-existing canonical helper open_readonly_connection (polylogue/storage/sqlite/connection_profile.py), which 6 other devtools files already used correctly. Investigated each sampled divergence honestly: both immutable=1 sites (index_v37_fast_forward.py, archive_schema_fast_forward.py) genuinely check for zero WAL/SHM/journal sidecars first - load-bearing, so extended the helper with an immutable: bool=False parameter rather than erasing the distinction. index_fast_forward.py's 30s/120s timeout overrides read a potentially-live archive under daemon lock contention - kept as deliberate per-caller overrides; dropped now-redundant manual PRAGMA query_only=ON since the helper already sets it. read_package.py's timeout=5.0 was byte-identical to the canonical default - collapsed as accidental drift. One honest behavior note flagged in the PR: archive_schema_fast_forward.py's Path.as_uri() percent-encoding is replaced by the helper's plain f-string URI construction (same as 30+ other existing callers already do) - an existing risk shared repo-wide, not newly introduced by this change. mypy --strict clean (1250 files), ruff clean, devtools render all --check clean, devtools test green on all touched files (149+360 passed), pre-existing unrelated failures confirmed identical via git stash A/B against master. Personally reviewed the full diff (CodeRabbit rate-limited) before merging.","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -1054,7 +1135,7 @@ {"_type":"issue","id":"polylogue-f3kd","title":"Model delegation chains, retries, and evidence-backed parent follow-up","description":"After the canonical delegation-attempt relation and ObjectRefs land, add richer sequence semantics: retries, corrections, redelegations, escalation, and bounded parent follow-up observations. The prior target_kind and provider-fixture scope moves to the foundational ObjectRef bead. The prior lexical-overlap PARENT-USE heuristic is rejected: text overlap is not evidence that a child result was used.","design":"Build relations over stable delegation refs and transcript order. Parent follow-up is a typed observation with evidence categories such as explicit citation, quote, structured result reference, synthesis judgment, ignored, or unknown. Only structural refs or accepted annotations can support utility/used claims; lexical similarity may be exposed as a low-tier candidate signal but never promoted automatically. Include provider-native retry/redelegation and auto-compaction exclusion fixtures.","acceptance_criteria":"Fixtures cover retry, correction, redelegation, escalation, ignored result, explicit structured use, ambiguous follow-up, and auto-compaction exclusion. Every follow-up category carries an evidence tier and refs; unknown is excluded from use/utility denominators. Removing the lexical similarity signal does not erase structurally supported observations. Sequence rows and cards resolve through stable delegation refs.","notes":"Priority calibration 2026-07-15: P2 to P3. This is an important downstream integration, richer semantic layer, or adoption step whose prerequisites and core contracts must land first. It stays explicitly tracked at its existing horizon; P3 marks sequencing, not reduced ambition.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-09T04:19:02Z","created_by":"Sinity","updated_at":"2026-07-15T20:07:29Z","labels":["area:analytics","area:delegations","area:lineage","delivery:I-analytics-experiments","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-f3kd","depends_on_id":"polylogue-1vpm","type":"parent-child","created_at":"2026-07-15T01:19:27Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f3kd","depends_on_id":"polylogue-1vpm.1","type":"discovered-from","created_at":"2026-07-09T06:19:02Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f3kd","depends_on_id":"polylogue-lph4","type":"blocks","created_at":"2026-07-10T10:10:39Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-f3kd","depends_on_id":"polylogue-y964","type":"blocks","created_at":"2026-07-10T10:10:38Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-57bg","title":"Extend cfk uplift re-run to n=12-20 using the production pack-generation pipeline","description":"polylogue-cfks n=5 pilot (directional positive, 4/5 pairs favor handoff-pack, mean 30.2/40 vs 22.8/40) used hand-written context summaries as the \"pack\" arm input, not the actual production pack-generation pipeline (qt3s fast regeneration + yps freshness metadata), and drew all 5 checkpoints from one sessions own consecutive devloop history rather than genuinely independent subjects. Both are real limitations the n=5 report documents explicitly. A publishable uplift claim needs n=12-20 per the original protocol.","design":"Use the actual production pack-generation command (compose_context_preamble / devtools workspace read-package or whatever the qt3-shipped fast-regeneration path is) to generate each pack arms input, verifying yps freshness metadata (generated_at ~= consumption time, freshness state fresh, zero successor warnings) before dispatching that arm -- this directly tests the root-cause fix the original jxe campaign attributed its negative result to (packet staleness), which the n=5 pilot did not test. Draw subjects from genuinely independent devloop sessions/checkpoints (not all from one continuous session) to avoid the correlated-subject-and-rater limitation the n=5 report flags. Reuse the n=5 pilots mechanism otherwise: isolated Agent-tool subagents per arm, ground truth written before dispatch, blinded judge subagents, cold-reader gate on the final artifact. Commit under a NEW .agent/demos/uplift-two-arm/ run (retire the n=5 current/ to a dated subfolder per the shelfs own \"current, not append-only\" convention).","acceptance_criteria":"n=12-20 paired runs completed using the production pack-generation pipeline with verified freshness metadata per pack; genuinely independent subjects (not one sessions consecutive checkpoints); per-pair scores + paired analysis (sign test, means) committed; cold-reader gate PASS; result recorded as the programs first potentially-publishable uplift finding (positive, negative, or still-ambiguous).","notes":"[2026-07-09] Added a required measurement per user challenge to the n=5 pilots \"synthesis effort\" framing: the n=5 pilot did not impose or measure any effort/budget difference between the raw-ref and handoff-pack arms (both got the same nominal single unbounded dispatch), so it cannot actually show whether raw-ref lost because it explored less or because synthesis quality is independent of exploration volume. This re-run must log tool-call count and token usage per arm per pair, and explicitly check whether raw-ref arms that matched or exceeded the pack arms measured effort still lost -- that is much stronger evidence for (or against) the synthesis-effort hypothesis than the current pilots untested assumption.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-09T04:05:38Z","created_by":"Sinity","updated_at":"2026-07-09T04:50:45Z","labels":["area:analytics","area:experiments"],"dependencies":[{"issue_id":"polylogue-57bg","depends_on_id":"polylogue-cfk","type":"discovered-from","created_at":"2026-07-09T06:05:38Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-57bg","depends_on_id":"polylogue-e5b5","type":"blocks","created_at":"2026-07-09T12:31:02Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-57bg","depends_on_id":"polylogue-rxdo","type":"parent-child","created_at":"2026-07-15T19:13:27Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-57bg","depends_on_id":"polylogue-x35k","type":"blocks","created_at":"2026-07-09T12:31:03Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-vv2b","title":"Wire lineage-completeness signal into CLI/API session payloads","description":"polylogue-4ts.6 added lineage_complete/lineage_truncation_reason to ArchiveSessionEnvelope and wired it through the two MCP-facing payloads (MCPMessagesListPayload via archive_messages_payload, MCPArchiveSessionPayload.from_session) -- CodeRabbit correctly flagged (PR #2603) that two more read surfaces still silently drop it: _session_payload (polylogue/cli/archive_query.py:2198, the CLI reader payload) and _archive_session_to_session (polylogue/api/archive.py:1162, the Python API Session domain model). Also relevant: the async batch/paginated wrappers (get_messages_batch, get_messages_paginated, get_message_edge_windows in message_query_reads.py) currently discard the signal by calling plain get_messages internally rather than get_messages_with_lineage_completeness -- their callers cannot observe truncation either.","design":"Same additive pattern as the two already-wired payloads: add lineage_complete: bool = True / lineage_truncation_reason: str | None = None (or the LineageTruncationReason Literal from polylogue.storage.runtime) to whatever dict/model _session_payload and Session (api/archive.py) already return, and pass session.lineage_complete/lineage_truncation_reason through at the two construction sites. For the async batch/paginated wrappers, switch their internal get_messages(...) calls to get_messages_with_lineage_completeness(...) and thread the signal through their own return shapes (may need new tuple/dataclass wrapping, same trade-off already made for get_messages itself).","acceptance_criteria":"polylogue read (CLI) and the Python API Session model both expose lineage_complete/lineage_truncation_reason for a truncated session, proven by a fixture (dangling branch point or depth-limit case) asserting the field on the CLI JSON output and the API Session object. get_messages_batch/get_messages_paginated/get_message_edge_windows either surface the signal or explicitly document why they intentionally do not (e.g. if paginated views are inherently partial by design and completeness is a session-level, not a page-level, concern).","status":"closed","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-09T03:14:24Z","created_by":"Sinity","updated_at":"2026-07-15T19:40:22Z","closed_at":"2026-07-15T19:40:22Z","close_reason":"Superseded by polylogue-4p1, whose sole read algebra and generated field-parity contract now explicitly own lineage completeness across CLI, Python, batch, and paginated readers.","labels":["area:lineage","area:mcp"],"dependencies":[{"issue_id":"polylogue-vv2b","depends_on_id":"polylogue-4ts","type":"parent-child","created_at":"2026-07-15T19:13:10Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-vv2b","depends_on_id":"polylogue-4ts.6","type":"discovered-from","created_at":"2026-07-09T05:14:24Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-vv2b","depends_on_id":"polylogue-4ts.9","type":"relates-to","created_at":"2026-07-15T06:25:54Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-xyel","title":"Real PF-D1-receipts demo (212.2) re-emitted through demo-packet contract","description":"polylogue-212.7 built the Demo Finding Packet contract (devtools/demo_packet.py: validate_packet, lint_demo_registry, devtools lab policy demo-packet-registry) and proved it end-to-end with a deliberately trivial stub fixture (.agent/demos/_packet-contract-stub/, counts sessions in the seeded corpus). The bead AC literally asked for \"one existing demo (PF-D1 receipts) re-emitted through the runner\" -- 212.2 (PF-D1 receipts: claim-vs-evidence on a real PR) does not exist as an implemented demo yet, so 212.7 shipped the mechanism proven against a stub instead of the real thing. This bead is the follow-up: implement 212.2 for real and register it in .agent/demos/registry.json as a conforming packet, retiring (or keeping alongside, if useful as a contract-only fixture) the stub.","design":"Implement 212.2 per its own description: pick a merged agent-authored PR, resolve PR -\u003e authoring session via session_commits/session_repos, get_postmortem_bundle, render two columns (claimed PR-body sentences vs observed actions rows with exit_code/duration, drillable to the raw tool_result block). Package the output as a packet directory under .agent/demos/d1-receipts/ conforming to devtools/demo_packet.py PACKET_FILENAMES + PROVENANCE_STANZA_FIELDS + REPORT_SECTION_ORDER (reuse the stub as a structural template). Register it in .agent/demos/registry.json. Run devtools lab policy demo-packet-registry to prove it validates.","acceptance_criteria":".agent/demos/d1-receipts/ (or similar slug) exists with all 7 required packet files, a real claim-vs-evidence finding on an actual merged PR from this repo, and validates cleanly via devtools lab policy demo-packet-registry. Registered in .agent/demos/registry.json. Verify: devtools lab policy demo-packet-registry passes with the new entry included.","notes":"[2026-07-10 fable] polylogue demo receipts (PR #2662) is the deterministic contract-proof baseline this bead re-emits through the packet contract; receipts.json/summary.json shapes in the v2 escrow (polylogue-demo-receipts/) are a draft packet layout.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. AC requires .agent/demos/d1-receipts/ (or similar) implementing a real PF-D1 receipts demo, registered in .agent/demos/registry.json. No such directory/entry exists on master. Bead's own dependency chain (cijx.1) confirms the underlying PR\u003c-\u003esession correlation producer (session_refs) exists but has no consumer wired on any surface, so 212.2/xyel remain explicitly un-unblocked per cijx.1's 2026-07-31 note. Evidence: git ls-tree -r origin/master --name-only -- .agent/demos/ | grep -i d1 -\u003e empty; git show origin/master:.agent/demos/registry.json | grep -i d1-receipts -\u003e empty.\nUNBLOCKED 2026-07-31 (polylogue-pbuh/cijx.1 residual pass, worktree agent-aaffe89902b670d4b): the session-\u003ePR producer+reader chain this bead depends on is now real. session_refs carries typed pull_request evidence (18,949 rows live), and PR #3425 (merged 5525446a2) wired `read --view correlation` / Polylogue.session_correlation_payload to consume it as authoritative over the old regex/time-window heuristics, with disagreements surfaced rather than silently guessed. Verified live against /realm/db/polylogue/index.db (read-only) that the CLI path resolves real typed PR refs end-to-end (also fixed a pre-existing NameError in that path's GitHub-enrichment branch that had never been exercised with real refs before this pass). Full detail: polylogue-cijx.1 and polylogue-pbuh notes, 2026-07-31.\n\nNOT closed by this alone: this bead's own AC still needs its specific deliverable (see this bead's own description) beyond \"the correlation data is now readable\" -- that implementation work was not attempted in this pass (out of its declared scope: read-surface residual verification for pbuh/cijx.1 only). Re-triage this bead's own AC against the now-working session_commit.py/correlation_view.py surface when picked up next.\n","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-09T00:12:05Z","created_by":"Sinity","updated_at":"2026-07-31T06:07:17Z","labels":["area:demos","delivery:L-external-legibility","lane:docs-demos-launch"],"dependencies":[{"issue_id":"polylogue-xyel","depends_on_id":"polylogue-212","type":"parent-child","created_at":"2026-07-15T19:13:34Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-xyel","depends_on_id":"polylogue-212.7","type":"discovered-from","created_at":"2026-07-09T02:12:05Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-xyel","depends_on_id":"polylogue-cijx.1","type":"blocks","created_at":"2026-07-29T06:51:59Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-xyel","title":"Real PF-D1-receipts demo (212.2) re-emitted through demo-packet contract","description":"polylogue-212.7 built the Demo Finding Packet contract (devtools/demo_packet.py: validate_packet, lint_demo_registry, devtools lab policy demo-packet-registry) and proved it end-to-end with a deliberately trivial stub fixture (.agent/demos/_packet-contract-stub/, counts sessions in the seeded corpus). The bead AC literally asked for \"one existing demo (PF-D1 receipts) re-emitted through the runner\" -- 212.2 (PF-D1 receipts: claim-vs-evidence on a real PR) does not exist as an implemented demo yet, so 212.7 shipped the mechanism proven against a stub instead of the real thing. This bead is the follow-up: implement 212.2 for real and register it in .agent/demos/registry.json as a conforming packet, retiring (or keeping alongside, if useful as a contract-only fixture) the stub.","design":"Implement 212.2 per its own description: pick a merged agent-authored PR, resolve PR -\u003e authoring session via session_commits/session_repos, get_postmortem_bundle, render two columns (claimed PR-body sentences vs observed actions rows with exit_code/duration, drillable to the raw tool_result block). Package the output as a packet directory under .agent/demos/d1-receipts/ conforming to devtools/demo_packet.py PACKET_FILENAMES + PROVENANCE_STANZA_FIELDS + REPORT_SECTION_ORDER (reuse the stub as a structural template). Register it in .agent/demos/registry.json. Run devtools lab policy demo-packet-registry to prove it validates.","acceptance_criteria":".agent/demos/d1-receipts/ (or similar slug) exists with all 7 required packet files, a real claim-vs-evidence finding on an actual merged PR from this repo, and validates cleanly via devtools lab policy demo-packet-registry. Registered in .agent/demos/registry.json. Verify: devtools lab policy demo-packet-registry passes with the new entry included.","notes":"[2026-07-10 fable] polylogue demo receipts (PR #2662) is the deterministic contract-proof baseline this bead re-emits through the packet contract; receipts.json/summary.json shapes in the v2 escrow (polylogue-demo-receipts/) are a draft packet layout.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. AC requires .agent/demos/d1-receipts/ (or similar) implementing a real PF-D1 receipts demo, registered in .agent/demos/registry.json. No such directory/entry exists on master. Bead's own dependency chain (cijx.1) confirms the underlying PR\u003c-\u003esession correlation producer (session_refs) exists but has no consumer wired on any surface, so 212.2/xyel remain explicitly un-unblocked per cijx.1's 2026-07-31 note. Evidence: git ls-tree -r origin/master --name-only -- .agent/demos/ | grep -i d1 -\u003e empty; git show origin/master:.agent/demos/registry.json | grep -i d1-receipts -\u003e empty.\nUNBLOCKED 2026-07-31 (polylogue-pbuh/cijx.1 residual pass, worktree agent-aaffe89902b670d4b): the session-\u003ePR producer+reader chain this bead depends on is now real. session_refs carries typed pull_request evidence (18,949 rows live), and PR #3425 (merged 5525446a2) wired `read --view correlation` / Polylogue.session_correlation_payload to consume it as authoritative over the old regex/time-window heuristics, with disagreements surfaced rather than silently guessed. Verified live against /realm/db/polylogue/index.db (read-only) that the CLI path resolves real typed PR refs end-to-end (also fixed a pre-existing NameError in that path's GitHub-enrichment branch that had never been exercised with real refs before this pass). Full detail: polylogue-cijx.1 and polylogue-pbuh notes, 2026-07-31.\n\nNOT closed by this alone: this bead's own AC still needs its specific deliverable (see this bead's own description) beyond \"the correlation data is now readable\" -- that implementation work was not attempted in this pass (out of its declared scope: read-surface residual verification for pbuh/cijx.1 only). Re-triage this bead's own AC against the now-working session_commit.py/correlation_view.py surface when picked up next.\n","status":"closed","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-09T00:12:05Z","created_by":"Sinity","updated_at":"2026-07-31T09:03:24Z","closed_at":"2026-07-31T09:03:24Z","close_reason":"Re-verified the bead's original framing against current master before doing\nanything: \"session_refs has no consumer\" is FALSE today. PR #3425 wired\ntyped session_refs pull_request/issue evidence into\ninsights/session_commit.py:build_correlation_result, and PR #3431 fixed a\npre-existing NameError in insights/correlation_view.py's GitHub-enrichment\npath that had made the default `read --view correlation --github-api`\ninvocation crash on every session carrying a ref -- confirmed live (this\nsession) by running it against /realm/db/polylogue (read-only): it resolves\na typed PR ref (source=typed_session_ref) plus a disagreements entry naming\nnon-corroborated regex-heuristic matches. The bead's own dependency\npolylogue-cijx.1 documents the same finding. So the consumer-wiring half of\nthis bead's title was already satisfied by tonight's merges -- accurately\nreported here rather than re-claimed as new work.\n\nWhat remained was this bead's own literal AC: build and register a real D1\nreceipts demo (212.2), not the packet-contract stub 212.7 shipped. Built\n.agent/demos/d1-receipts/ -- 9 packet files (current PACKET_FILENAMES\ncontract; AC's \"7\" is a stale pre-v2-schema count), a real claim-vs-evidence\nfinding on an actual merged PR (Sinity/polylogue#3282), registered in\n.agent/demos/registry.json, validating cleanly via\n`devtools lab policy demo-packet-registry` (\"all 4 entries conform\").\n\nThe finding itself: resolved PR #3282 to its authoring/dispatch session\nstructurally via session_refs, then checked 4 individually falsifiable\nPR-body sentences against that session's own tool_use/tool_result blocks.\n3 of 4 are structurally supported; the 4th (a 7-file devtools test\ninvocation named in the PR's Verification section) is correctly scored\nnot_supported -- that exact string appears only inside the gh-pr-create\n--body text itself, never as an executed command in this session. Also\nsurfaced a genuine, undocumented-until-now finding: the resolved session is\na merge-conductor (53 Bash + 3 Read tool_use, 0 Edit/Write) that dispatches\nfile edits to separate worker worktrees rather than editing files directly\n-- session_refs correctly answers \"which session opened this PR\", not\n\"which session edited file X\".\n\nHonest scope disposition: only the live-archive operator variant is built\n(mode=private). 212's own two-variant design (public seed-corpus + live\noperator) is not fully satisfied -- session_refs pull_request rows are a\nprovider-native capability the deterministic seed fixture doesn't populate,\nso the public D1 variant is out of scope here. Filed polylogue-nt5f for\nthat named remainder rather than silently leaving it unstated.\n\n--force disposition: closed over the open blocker polylogue-cijx.1. cijx.1's\nown notes explicitly state the specific concern it raised for this bead's\ndependents (the session_refs producer/reader chain \"does not work\") is\nresolved, and that concluding this bead's own concrete deliverable was left\nto whoever picks it up next -- done here. cijx.1 itself remains legitimately\nopen for its own, unrelated titled AC (106 repo_ids for one polylogue\nrepository across worktrees/URL spellings); that scope has no bearing on\nthis bead's demo-packet deliverable, so the dependency edge no longer\nreflects a real blocker for this specific bead.\n\nVerification: devtools lab policy demo-packet-registry -\u003e all 4 entries\nconform. devtools test tests/unit/devtools/test_demo_packet.py\ntests/unit/demo/test_tour_packet_contract.py -\u003e 32 passed. devtools verify\n--quick -\u003e 20/20 steps green. devtools render all --check -\u003e OK. Landing on\nbranch feature/cleanup/dead-coverage-and-session-refs alongside polylogue-uh9l.","labels":["area:demos","delivery:L-external-legibility","lane:docs-demos-launch"],"dependencies":[{"issue_id":"polylogue-xyel","depends_on_id":"polylogue-212","type":"parent-child","created_at":"2026-07-15T19:13:34Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-xyel","depends_on_id":"polylogue-212.7","type":"discovered-from","created_at":"2026-07-09T02:12:05Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-xyel","depends_on_id":"polylogue-cijx.1","type":"blocks","created_at":"2026-07-29T06:51:59Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-8e1b","title":"Reconcile bead priority field with delivery-gate order","description":"priority (1-4) is currently uncorrelated with the delivery:* gate letter (A-trust-floor..N-horizon) that actually encodes intended sequencing. Sample: E-variants-preferences carries 5 P1 items vs A-trust-floor 2, D-agent-context-coordination 9 P1s. Sorting ready work by priority alone (as bd ready does by default) surfaces late-gate items ahead of earlier-gate ones, misleading anyone not cross-checking the gate board. Discovered 2026-07-08 while walking the top-P1 ready list with the operator.","design":"Re-derive priority from (gate letter, ready-vs-blocked, epic-vs-leaf) rather than hand-set values: earlier gates should dominate later gates at the same nominal urgency; a blocked items priority should not compete with a ready items in an earlier gate. Candidate mechanical rule: priority = f(gate_index, blocked_flag), leaving room for genuine P0 (security/data-loss) overrides. Use .agent/tools/delivery-gate-status.py as the source of gate ordering/state. Batch as one mechanical bd update sweep + bd-graph-lint, not per-bead edits.","acceptance_criteria":"Mechanical priority rule derived from delivery-gate order is documented in this bead's notes before execution; a single scripted bd update sweep reassigns priority (no other field touched) on every open/in_progress bead carrying a delivery:*-gate label; bd-graph-lint passes after the sweep; before/after priority-by-gate distribution is reported in the shipping PR.","notes":"MECHANICAL RULE (2026-07-08, executed as one scripted bd update sweep):\n\nScope: every OPEN or IN_PROGRESS bead carrying a delivery:\u003cgate\u003e label\n(gate != delivery:ac-patched, which is an overlay marker not a gate).\nOut of scope (left untouched): closed beads; beads with no delivery:*-gate\nlabel (24 at sweep time - counted, not reassigned); any bead whose CURRENT\npriority is 0 (explicit P0 override signal - none existed among open,\ngate-labeled beads at sweep time, but the rule preserves them if they\nappear later).\n\nGate groups (source: .agent/tools/delivery-gate-status.py GATES order),\nmapped to base priority tiers 1-4:\n tier1 = {A-trust-floor} (the active frontier)\n tier2 = {B-storage-rebuild-bytes, C-read-evidence-contract,\n D-agent-context-coordination} (near-term)\n tier3 = {E-variants-preferences, F-lineage-compaction,\n G-live-performance, H-web-cockpit} (mid-term)\n tier4 = {I-analytics-experiments, J-embeddings-retrieval,\n K-interop-origin-export, L-external-legibility,\n M-substrate-consolidation, N-horizon} (far horizon)\n\nnew_priority = min(4, base_tier\n + (1 if blocked else 0)\n + (1 if issue_type == 'epic' else 0))\n\nblocked := status == 'open' AND has an unresolved (non-closed) dependency\nof type 'blocks' (same definition delivery-gate-status.py uses for its\nready/blocked split). in_progress beads are treated as unblocked (already\nactively claimed). Epics are demoted one tier below their gate's leaf tier\nso P1 signals \"grab this leaf task now\", not \"here is a rollup tracker\".\nDemotions stack (blocked epic in gate A -\u003e tier 1+1+1 = 3), capped at 4.\n\nEffect: this directly fixes the motivating case (gate A-trust-floor ready\nleaf work now dominates gate E-variants-preferences ready leaf work at\nevery tier), and makes `bd ready` sorted by priority track delivery-gate\norder by construction instead of by an independently hand-set field.\n\nScript: computed by a one-off Python pass over `bd export`'d issues.jsonl\n(scratch, not committed) producing an id -\u003e new_priority map, applied via\ngrouped `bd update \u003cids...\u003e --priority N` calls (one call per target\npriority value, not per-bead) so the change lands as a single mechanical\nsweep. 288 of 387 open/gate-labeled beads changed priority; 99 already\nmatched the rule's output.","status":"closed","priority":3,"issue_type":"task","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-08T19:53:15Z","created_by":"Sinity","updated_at":"2026-07-09T20:17:19Z","started_at":"2026-07-08T20:08:32Z","closed_at":"2026-07-09T20:17:19Z","close_reason":"Work was actually completed and merged via PR #2584 (merged 2026-07-08T20:22:23Z) -- the mechanical priority/delivery-gate reconciliation sweep described in this beads own notes. Bead was left in_progress, never closed, likely the known beads-checkout-hook-reverts-live-updates pattern (close silently reverted by a branch switch before the close commit landed on master). Found stale while doing final dangling-item sweep at the end of an unrelated session; not connected to this sessions own work.","labels":["area:beads-hygiene"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-3utv","title":"Typed route registry: declare-once RouteSpec table generates Starlette router, OpenAPI, and the TS client","description":"Consequence of the ratified dx1 decision (ASGI via Starlette, presumption to proceed): the daemon route table must become a DECLARE-ONCE REGISTRY before the first family migrates, so 20d.1 fast-path endpoints and the webui v2 API land ON the registry instead of beside it, and the bby.7 class (untyped params, list-vs-detail drift) becomes structurally impossible. Today ~45 routes live as hand-matched paths in a 3,870-line handler; OpenAPI is rendered separately; nothing forces them to agree.\n","design":"RouteSpec registry, one entry per route: RouteSpec(name, method, path template with typed params, request model | None, response model, auth tier CHECK(open|read|write|admin), streaming: none|sse, preset_ref /* the (Q,P,R) preset this route serves, 4p1 — read routes MUST name one */, operation_ref /* OperationSpec for mutating routes — reuses the existing contract-test machinery */, rate_class, owner_module). GENERATION, not duplication: (a) Starlette router built FROM the registry at startup (routes = [r.to_starlette() for r in REGISTRY]); (b) devtools render openapi consumes the registry as its source of truth (today it renders from code inspection — flip the arrow); (c) the typed TS client (bby.11 lib/api.ts) generates from that OpenAPI — end-to-end type chain registry-\u003eserver-\u003eclient with no hand sync. CONTRACT TESTS inherit the OperationSpec pattern: every registry entry with auth!=open must reject unauthenticated in a parametrized test; every read route must name a preset; every SSE route must declare its event model; a route in code but not registry (or vice versa) fails a census test — same census discipline as EXPECTED_TOOL_NAMES. MIGRATION FIT: hand-rolled families move one-per-PR by re-declaring their routes as RouteSpecs (contracts byte-stable: /metrics, /healthz pinned by snapshot tests); the registry is ALSO what makes yeq lane 3 (ref-walks) and stzx (schemathesis) generation-driven instead of hand-listed. NON-GOALS: no middleware framework beyond auth/gzip/CORS; no versioned API namespaces yet (loopback daemon, single client set).\n","acceptance_criteria":"Registry exists with every migrated route declared; Starlette router and rendered OpenAPI both derive from it (census test fails on drift in either direction); read routes name their (Q,P,R) preset; auth-tier rejection tests parametrized over the registry; lib/api.ts regenerates from the registry-derived OpenAPI. VERIFY: devtools test tests/unit/daemon -k \"registry or route_census\"; render openapi diff shows registry provenance.","notes":"SEQUENCE 2026-07-13: land the RouteSpec registry WITH the dx1 ASGI migration and BEFORE webui-v2 route work — hot-daemon's new UDS/query endpoints (in flight) are exactly the family that should migrate onto it first; 20d.13 SSE (three buyers: fleet observatory fcyf, standing-query notifications rxdo.5, live UIs) lands natively on ASGI in the same move.\nPriority calibration 2026-07-15: P2 to P3. This is retained architectural renewal, hygiene, documentation, decision, proof packaging, or operator convenience work, but it does not presently outrank concrete archive-truth and core-query failures. Existing evidence, acceptance criteria, and horizon remain authoritative.\n2026-07-19 investigation (lane-e followup, Claude Sonnet): scoped this bead for the \"registry core + ONE family migrated\" slice per the followup packet, but found the literal AC (\"generates a Starlette router\") requires actually starting the dx1 ASGI migration for real, not just filling in an implementation detail. Verified: dx1 is RATIFIED but fully unimplemented -- daemon/http.py is 100% stdlib BaseHTTPRequestHandler (3870 lines), starlette/uvicorn/sse-starlette are in uv.lock only as TRANSITIVE deps of the mcp SDK package (its SSE transport), zero usage anywhere in polylogue/. dx1 itself carries explicit abort criteria (latency/RSS regression under live benchmarking) never evaluated. Asked the operator how to proceed given this mismatch: (a) reinterpret narrowly -- registry generates OpenAPI + TS client + the daemon current stdlib dispatch table, deferring literal Starlette-router generation until dx1 lands for real; (b) do the real ASGI migration now; (c) skip this session. Operator chose (c) skip. No code written for this bead this session. Recommendation for whoever picks this up next: either resolve dx1 first (run its one-route-family benchmark prototype, decide go/no-go for real) or explicitly re-scope 3utv to the \"narrow reinterpretation\" path (a) above and drop the Starlette-router AC until dx1 has landed -- attempting 3utv literally-as-written before dx1 is implemented is scope-inverted (a P3 hygiene bead cannot be the vehicle that first stands up a P-unranked, benchmark-gated architecture migration).","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-08T18:50:16Z","created_by":"Sinity","updated_at":"2026-07-18T22:30:44Z","labels":["area:daemon","area:web","horizon:frontier","lane:daemon-surface"],"dependencies":[{"issue_id":"polylogue-3utv","depends_on_id":"polylogue-4p1","type":"related","created_at":"2026-07-08T20:50:16Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-3utv","depends_on_id":"polylogue-bby.11","type":"related","created_at":"2026-07-08T20:50:16Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-3utv","depends_on_id":"polylogue-dx1","type":"related","created_at":"2026-07-08T20:50:16Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-3utv","depends_on_id":"polylogue-o21","type":"parent-child","created_at":"2026-07-15T18:54:36Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-occ5","title":"CLI post-query interaction design: per-verb follow-through, next-action affordances, result-set handles","description":"Operator directive 2026-07-08: the query side is well figured out, but what happens AFTER a query is not designed. Today every verb ends at stdout; there is no designed follow-through. Coverage today: 4p1 records the Query x Projection x Render ALGEBRA (what a render is), jnj.1 collapses view flags, and three point-moments exist (jnj.11 fzf at ambiguous results, jnj.12 empty-result guidance, jnj.13 bare-invocation triage) - but nothing designs the interaction LANGUAGE: per-verb next-action affordances, how a result becomes the operand of the next command, and how workflows chain. rxdo changes the ground under this: once query runs and result-sets are first-class objects (rxdo.2/.3) and referenceable in the DSL (rxdo.6), the CLI can hand the user/agent a durable handle instead of scrollback. This bead is the interaction-design sibling of tjx1 (aesthetics = visual language; this = interaction language), CLI-first but the affordance vocabulary should project onto MCP (rsad) and web.\n","design":"Direction-doc deliverable (like tjx1): map the post-result moment for EACH verb - find (narrow/widen, open Nth, mark, save-as-named-query, pipe to compact), read (jump to next/prev in result order, open lineage parent/children, extract refs), analyze (drill from aggregate row to member sessions - the group-by row is a cohort handle), mark/select (confirm what changed, undo affordance), continue (handoff into harness). Design decisions to settle: (1) result-set handle surfacing - every query output footer carries its result-set/query-run ref (rxdo.3) and a \"last result\" shorthand so follow-ups are polylogue \u003cverb\u003e @last or from result-set:\u003cid\u003e (rxdo.6 syntax); (2) affordance presentation - printed next-action lines (copy-pasteable, agent-friendly) vs interactive picker (jnj.11 fzf pattern) vs both by TTY detection, respecting FORCE_PLAIN; (3) per-verb affordance table lives in the declare-once surface machinery (product/workflows or surfaces/ action affordances - action_affordances MCP tool already exists, reuse its registry rather than a new one); (4) chaining grammar - whether \"then\" extends beyond find QUERY then ACTION into result-set-carrying pipelines. Output: docs/ or .agent/reports direction doc + implementation beads dep-linked here, enriching jnj.11/.12/.13 rather than duplicating them. HARD dep: none (design can proceed); rxdo.3/.6 gate only the handle-surfacing implementation.\n","acceptance_criteria":"A written interaction-flow direction exists and is committed: per-verb post-result affordance map, result-set handle surfacing decision, presentation-mode decision (printed vs picker vs both), chaining-grammar decision; implementation beads filed and dep-linked (enriching jnj.11/.12/.13 where they overlap); operator sign-off note on this bead. VERIFY: doc path + child bead ids in notes.","notes":"[RATIFIED 2026-07-08, decision brief] Design questions RESOLVED: (1) result-set handles always — one-line footer (result-set \u003cshort-id\u003e · N sessions · query \u003chash-short\u003e); @last resolves to most recent result-set of current workspace; durable form is from result-set:\u003cid\u003e (rxdo.6); until rxdo.3 lands, footer prints canonical query hash only. (2) Presentation BOTH by TTY: printed next-action lines always (the agent affordance, copy-pasteable); fzf picker additionally on interactive TTY; FORCE_PLAIN suppresses picker never printed affordances. (3) Affordance source = existing action_affordances registry (CLI footers, MCP post-rsad opt-in payloads, web chips render the same entries). (4) Chaining: then stays as-is; result-set pipelines arrive exclusively via DSL from operand — one grammar owns composition. (5) Per-verb map as in the brief (find narrow/open/mark/save/compact; read next-prev/lineage/refs; analyze rows are drillable cohort handles; mark echo+undo; continue composes harness invocation). Remaining deliverable: the direction doc + implementation children.\nREWRITE 2026-07-13: rxdo landed the missing substrate — @last (per workspace+surface), query_run_ref/result-set refs on every envelope (#2813 lineage). The interaction language becomes: every verb's output IS a ref; next verb takes refs. Re-scope this bead from designing handles to WIRING existing refs into per-verb affordances + the judgment-inbox micro-moments (rxdo.9.16).\nPriority calibration 2026-07-15: P2 to P3. This is a valuable query-language extension, interaction refinement, presentation improvement, or convenience surface, but the truthful bounded core and model-facing discovery contract precede it. The capability remains in scope and at the same horizon.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. Design ratified 2026-07-08 (decision brief) but per the bead's own 2026-07-13 rewrite, 'Remaining deliverable: the direction doc + implementation children' was never produced -- no direction doc found under docs/ or .agent/reports/, and no per-verb next-action-footer wiring (result-set handle in CLI footers) exists in polylogue/cli/*.py. Evidence: find /realm/project/polylogue -iname '*occ5*' -\u003e no results; grep -rn action_affordances polylogue/cli/*.py filtered for footer/next-action wiring -\u003e no matches.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-08T18:22:06Z","created_by":"Sinity","updated_at":"2026-07-31T05:54:10Z","labels":["area:cli","area:surface","horizon:frontier"],"dependencies":[{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-4p1","type":"related","created_at":"2026-07-08T20:22:06Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-jnj","type":"parent-child","created_at":"2026-07-15T18:54:39Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-jnj.11","type":"related","created_at":"2026-07-08T20:22:06Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-rsad","type":"related","created_at":"2026-07-08T20:22:06Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-rxdo.3","type":"related","created_at":"2026-07-08T20:22:06Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-rxdo.6","type":"related","created_at":"2026-07-08T20:22:06Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-occ5","depends_on_id":"polylogue-tjx1","type":"related","created_at":"2026-07-08T20:22:07Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} @@ -1075,7 +1156,7 @@ {"_type":"issue","id":"polylogue-37t.19","title":"Semantic notification policy: route CONTENT signals through the existing fan-out, fatigue-controlled","description":"Wire-what-exists: the daemon already has a 5-backend notification fan-out carrying only OPS alerts. Add a Notice severity + content family so CONTENT signals (standing-query deltas, \"you are repeating a past mistake\" nudges via embed-live-tail vs pathology/lesson sessions that ended badly) route through the SAME pipe — zero new channel. Three-cadence policy (on-event/daily/weekly) + per-family token-bucket fatigue control + SUPPRESSION-assertion snooze + now-quiet deferral reusing hot-file logic. RECURSIVE-SAFETY: never alert/mine on generated_context_pack/runtime material; no self-alert on notice.* Ship LEDGER-FIRST — fatigue that defeats adoption is the failure mode (the very thing it exists to prevent). polylogue brief --since 24h = a query over the event ledger (deterministic oracle habit). Verbatim spec: bundles/rnd-bundle-4-of-6.md L1787.","design":"Declare NoticePolicy entries over durable signal refs: family, severity, eligibility/material-origin filter, owner/scope, cadence, token-bucket budget, quiet-window behavior, suppression key/expiry, renderer, and destination fan-out. A notification evaluator turns committed standing-query or memory-risk deltas into idempotent notice events after recursive-safety and authority checks; existing notification backends only render/deliver them. The event ledger is authority for dedupe, suppression, delivery, and brief queries. Content can suggest or cite but never alter context policy or execute instructions.","acceptance_criteria":"A standing-query delta emits one Notice through the existing fan-out; token bucket suppresses a storm; snooze works; zero alerts on generated material; brief --since reads the ledger. Verify: notification fixture tests.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=D-agent-context-coordination; lane=context-memory; readiness=D-horizon-ready; proof=context scheduler ledger fixture and candidate judgment queue proof. Original readiness=D-horizon-ready.\nPriority calibration 2026-07-15: P2 to P3. This remains part of the full project ambition, but it is a sequenced demo, experiment, governed analytic extension, or evaluation layer rather than a present failure of archive truth, bounded queryability, durability, or source fidelity. Priority is urgency, not deletion or scope reduction; horizon is unchanged.","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:53:34Z","created_by":"Sinity","updated_at":"2026-07-15T20:07:24Z","labels":["area:context","area:daemon","delivery:D-agent-context-coordination","horizon:mid","lane:context-memory","tech-tree"],"dependencies":[{"issue_id":"polylogue-37t.19","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-06T01:53:34Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-37t.19","depends_on_id":"polylogue-rxdo.5","type":"related","created_at":"2026-07-06T01:53:37Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-37t.18","title":"Second-brain entity graph: structural-vs-candidate mention split, backlinks, topic co-occurrence","description":"Navigable knowledge graph over the archive: entities/entity_mentions/entity_topics + an entity_backlinks VIEW. The load-bearing split is STRUCTURAL vs CANDIDATE mentions — structural (bare #N repo-scoped, explicit refs) are trusted; prose-mined candidate mentions are recursive-safety-gated (an ungated prose-miner creates a fabrication feedback loop because the archive self-ingests). Topic co-occurrence clustering builds the graph edges. This is the aggregate of the entity-mention unit + a graph read surface; belongs under 37t (memory/second-brain) with a related link to the missing-units epic.","design":"Storage (derived, index-tier — rebuild regime): entities(entity_id, kind, canonical_name), entity_mentions(entity_id, block_id, mention_kind: structural|candidate, extractor_version, confidence), entity_topics join, entity_backlinks VIEW over mentions. Extractors: STRUCTURAL = deterministic (bare #N with repo scope, explicit bead/session/file refs from 37t.2 notation, URLs, git SHAs) — trusted, no gate; CANDIDATE = prose-mined names/concepts — enters via the 37t.15 chokepoint as candidate assertions, promoted only by judgment (the recovery-digest fabrication incident is the standing regression fixture). Topic clustering rides mhx.5 (semantic analytics), not its own pipeline.","acceptance_criteria":"Structural mentions resolve without gating; candidate mentions enter as recursive-safety candidates; backlinks VIEW works; a prose-fabrication fixture does NOT self-promote. Verify: extraction + gating tests.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=D-agent-context-coordination; lane=context-memory; readiness=D-horizon-ready; proof=context scheduler ledger fixture and candidate judgment queue proof. Original readiness=D-horizon-ready.\nPriority calibration 2026-07-15: P2 to P3. This remains part of the full project ambition, but it is a sequenced demo, experiment, governed analytic extension, or evaluation layer rather than a present failure of archive truth, bounded queryability, durability, or source fidelity. Priority is urgency, not deletion or scope reduction; horizon is unchanged.","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:53:33Z","created_by":"Sinity","updated_at":"2026-07-15T20:07:24Z","labels":["area:context","area:substrate","delivery:D-agent-context-coordination","horizon:mid","lane:context-memory","tech-tree"],"dependencies":[{"issue_id":"polylogue-37t.18","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-06T01:53:33Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-37t.18","depends_on_id":"polylogue-9l5.18","type":"related","created_at":"2026-07-06T01:53:37Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-9l5.18","title":"Infer cross-origin threads without confusing similarity with lineage","description":"A useful conversational/work thread may cross Claude, Codex, ChatGPT, Gemini, or other origins without a provider-native parent edge. The archive needs a derived candidate relation for these joins, but similarity, shared files, and temporal proximity cannot become asserted lineage. The former six-unit epic mixed this with entity mentions, topic clustering, world effects, verification runs, and project identity; those now belong to stronger graph and verification contracts.","design":"Define cross_origin_threads as a versioned derived candidate relation over sessions/segments from different origins. Require a declared combination of hard signals (explicit refs, common work-evidence objects/artifacts, shared repo/project identity) plus calibrated semantic/temporal features; exclude provider-native lineage already represented in session_links/work graph. Preserve component scores, evidence refs, extractor/model version, corpus frame, ambiguity, and candidate/accepted/rejected judgment. Hub-merge guards prevent one popular repo/topic from collapsing unrelated work. Entity/topic signals come from polylogue-37t.18; direct work/artifact/effect edges come from polylogue-1vpm.6.","acceptance_criteria":"1. cross_origin_threads is queryable with member sessions/segments, component scores, evidence refs, extractor/model version, corpus frame, and candidate/accepted/rejected state. 2. Provider-native lineage is excluded rather than relabeled cross-origin. 3. Shared repo/topic or temporal overlap alone cannot create a thread; a hub-merge fixture remains separated. 4. A known cross-origin continuation with direct refs or shared work-evidence objects is proposed and can be judged without mutating source topology. 5. Entity/topic and work/artifact signals are consumed from their owning contracts; no duplicate entity, world-effect, verification-run, project, or artifact tables are introduced. 6. Precision/coverage on a labeled fixture and mutation tests for similarity-as-lineage and hub collapse pass.","notes":"ALSO IN SCOPE (units-D, bundle-5 L466): phase-segment is a DSL PROJECTION over existing session_work_events, NOT a new table and NOT a kind column on session_phases (re-adding kind reverts a construct decision — work_events already carry intent labels); goal and decision-object are CONSTRUCT-GATED CANDIDATES via the existing candidate-\u003ejudge state machine (never active-by-extraction; the recovery-digest incident is the shared regression test); mined decisions need cycle-safe supersession (DAG check on supersedes insertion).\n2026-07-06 decomposition contract: this epic decomposes on claim — each of the six units (entity-mention, world-effect, verification-run, project, topic-cluster, cross-origin-thread) becomes a child bead inheriting its TABLE-vs-VIEW decision + extraction gating from this description; claiming agent creates the child, lifts the relevant desc slice into it, and executes per the enrich-on-claim convention. Do not implement units directly against this epic.\n[Delivery upgrade 2026-07-07T00:05:00Z] Release=I-analytics-experiments; lane=analytics-experiments; readiness=D-horizon-ready; proof=measure registry, sample-frame/uncertainty/confound rendering tests, experiment analysis fixture. Original readiness=D-horizon-ready.\nOntology consolidation 2026-07-15: the old six-unit epic was decomposed by ownership. Entity mention/topic graph belongs to 37t.18; world effects, artifacts, and repository-scoped work identity belong to 1vpm.6; verification runs/failures belong to d45p plus work-evidence receipts. This bead retains only the independent cross-origin-thread candidate relation.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:53:32Z","created_by":"Sinity","updated_at":"2026-07-15T19:49:55Z","labels":["area:analytics","area:substrate","delivery:I-analytics-experiments","horizon:mid","lane:analytics-experiments","tech-tree"],"dependencies":[{"issue_id":"polylogue-9l5.18","depends_on_id":"polylogue-1vpm","type":"related","created_at":"2026-07-06T01:53:37Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-9l5.18","depends_on_id":"polylogue-1vpm.6","type":"relates-to","created_at":"2026-07-15T21:49:55Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-9l5.18","depends_on_id":"polylogue-37t.18","type":"relates-to","created_at":"2026-07-15T21:49:56Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-9l5.18","depends_on_id":"polylogue-9l5","type":"parent-child","created_at":"2026-07-06T01:53:31Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-9l5.18","depends_on_id":"polylogue-9l5.7","type":"relates-to","created_at":"2026-07-07T15:02:06Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-9l5.18","depends_on_id":"polylogue-d45p","type":"relates-to","created_at":"2026-07-15T21:49:57Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-37t.17","title":"Read-access log + memory-utility analytics: which injected memories earn their tokens","description":"The signal the context scheduler (37t.11) needs and cannot get today: a read-access log (ops.db — already multi-writer via daemon events) recording which assertions/memories/packs were injected, read, expanded, or ignored, with in-process debounce + decayed counters. Enables memory-utility analytics: injected-but-never-used memories, warnings that preceded avoided mistakes, saved queries returning nothing, recall packs never opened (dead-memory detection) -\u003e delete/supersede recommendations surfaced as candidate assertions. CRITICAL SAFETY EXCLUSION (wave finding): context_inject events are EXCLUDED from the attention signal the scheduler consumes, or the scheduler reinforces its own injections (feedback loop). Verbatim wave spec: .agent/handoffs/polylogue-gpt-pro-2026-07-06/bundles/rnd-bundle-2-of-6.md L2038.\n\n## Authoritative corrective scope (2026-07-13)\n\nThis bead owns the implementation and evidence stream for improvement-loop pilot L1 recall\nrelevance. rxdo.11 registers and schedules it; it must not build a parallel read-access system.","design":"Emit one privacy-bounded AccessReceipt at context compilation, delivery, explicit expansion/open, and downstream citation/use when observable. It binds actor/workspace/session, assertion/pack/evidence refs, action kind, timestamp source, presentation position, token budget, policy version, and observability limits; context_inject is a delivery event and is excluded from independent-use signals. Store disposable raw access events in ops.db and materialize versioned utility measures with denominators/unknowns. The scheduler consumes only declared measures, while deletion/supersession remains a judged candidate action.","acceptance_criteria":"Injection + read events land in ops.db with debounce; a memory-utility report ranks injected-vs-used; scheduler ranking consumes attention WITHOUT context_inject events (test proves the exclusion); dead-memory candidates emitted, never auto-deleted. Verify: focused daemon/event tests.\n\n## Corrective acceptance criteria (2026-07-13)\n\nL1's watch/measure/propose/judge/bump receipts point to this bead's read-access and memory-utility\ndata. Running the pilot creates no duplicate analytics table or alternate memory-utility definition.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=D-agent-context-coordination; lane=context-memory; readiness=D-horizon-ready; proof=context scheduler ledger fixture and candidate judgment queue proof. Original readiness=D-horizon-ready.\nRECONCILED 2026-07-13: this bead IS rxdo.11 loop L1 (recall relevance) — implementation home here. Signal: delivery receipts (#2792 landed) x read-access log; usage detection = injected refs cited/quoted/re-read downstream (text+embedding match); output feeds retrieval ranker reweighting (ranker:\u003chash\u003e bump through the judge gate). Also feeds h4 rediscovery-miss detection (closed-loops doc Part C).\nPriority calibration 2026-07-15: P2 to P3. This remains part of the full project ambition, but it is a sequenced demo, experiment, governed analytic extension, or evaluation layer rather than a present failure of archive truth, bounded queryability, durability, or source fidelity. Priority is urgency, not deletion or scope reduction; horizon is unchanged.\nVERIFICATION (group3 sweep): LIVE. Checked for implementation: rg -i 'read_access_log|memory_utility_report|dead_memory_candidate' across polylogue/ and tests/ -- zero matches. sqlite3 ops.db .tables has no read-access-log table. Nothing implemented; this is a genuine open feature, not stale.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:39:26Z","created_by":"Sinity","updated_at":"2026-07-31T05:50:49Z","labels":["area:context","area:daemon","delivery:D-agent-context-coordination","horizon:mid","lane:context-memory","tech-tree"],"dependencies":[{"issue_id":"polylogue-37t.17","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-06T01:39:25Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-37t.17","title":"Read-access log + memory-utility analytics: which injected memories earn their tokens","description":"The signal the context scheduler (37t.11) needs and cannot get today: a read-access log (ops.db — already multi-writer via daemon events) recording which assertions/memories/packs were injected, read, expanded, or ignored, with in-process debounce + decayed counters. Enables memory-utility analytics: injected-but-never-used memories, warnings that preceded avoided mistakes, saved queries returning nothing, recall packs never opened (dead-memory detection) -\u003e delete/supersede recommendations surfaced as candidate assertions. CRITICAL SAFETY EXCLUSION (wave finding): context_inject events are EXCLUDED from the attention signal the scheduler consumes, or the scheduler reinforces its own injections (feedback loop). Verbatim wave spec: .agent/handoffs/polylogue-gpt-pro-2026-07-06/bundles/rnd-bundle-2-of-6.md L2038.\n\n## Authoritative corrective scope (2026-07-13)\n\nThis bead owns the implementation and evidence stream for improvement-loop pilot L1 recall\nrelevance. rxdo.11 registers and schedules it; it must not build a parallel read-access system.","design":"Emit one privacy-bounded AccessReceipt at context compilation, delivery, explicit expansion/open, and downstream citation/use when observable. It binds actor/workspace/session, assertion/pack/evidence refs, action kind, timestamp source, presentation position, token budget, policy version, and observability limits; context_inject is a delivery event and is excluded from independent-use signals. Store disposable raw access events in ops.db and materialize versioned utility measures with denominators/unknowns. The scheduler consumes only declared measures, while deletion/supersession remains a judged candidate action.","acceptance_criteria":"Injection + read events land in ops.db with debounce; a memory-utility report ranks injected-vs-used; scheduler ranking consumes attention WITHOUT context_inject events (test proves the exclusion); dead-memory candidates emitted, never auto-deleted. Verify: focused daemon/event tests.\n\n## Corrective acceptance criteria (2026-07-13)\n\nL1's watch/measure/propose/judge/bump receipts point to this bead's read-access and memory-utility\ndata. Running the pilot creates no duplicate analytics table or alternate memory-utility definition.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=D-agent-context-coordination; lane=context-memory; readiness=D-horizon-ready; proof=context scheduler ledger fixture and candidate judgment queue proof. Original readiness=D-horizon-ready.\nRECONCILED 2026-07-13: this bead IS rxdo.11 loop L1 (recall relevance) — implementation home here. Signal: delivery receipts (#2792 landed) x read-access log; usage detection = injected refs cited/quoted/re-read downstream (text+embedding match); output feeds retrieval ranker reweighting (ranker:\u003chash\u003e bump through the judge gate). Also feeds h4 rediscovery-miss detection (closed-loops doc Part C).\nPriority calibration 2026-07-15: P2 to P3. This remains part of the full project ambition, but it is a sequenced demo, experiment, governed analytic extension, or evaluation layer rather than a present failure of archive truth, bounded queryability, durability, or source fidelity. Priority is urgency, not deletion or scope reduction; horizon is unchanged.\nVERIFICATION (group3 sweep): LIVE. Checked for implementation: rg -i 'read_access_log|memory_utility_report|dead_memory_candidate' across polylogue/ and tests/ -- zero matches. sqlite3 ops.db .tables has no read-access-log table. Nothing implemented; this is a genuine open feature, not stale.\n[Group3-followup sweep, worktree agent-a564975670ee09dee, 2026-07-31] Re-verified zero implementation:\nrg -i 'read_access_log|memory_utility_report|dead_memory_candidate|AccessReceipt' polylogue tests -\u003e no matches.\nAlso landed real MCP/API surface wiring for the sibling bead this session (polylogue-37t.22: write(operation=\"deliver_context\") + context(result_ref=..., recipient_ref=...) get/list), which is this bead's own stated L1 signal source (delivery receipts).\n\nDECISION: leaving 37t.17 open, not implementing a speculative read-access-log module this session. Evidence:\n1. The design's dependency chain is real, not just prose: this bead is explicitly rxdo.11's pilot L1 (recall relevance) implementation home. rxdo.11's own corrective AC (verified PARTIAL, see its notes) requires L1 to \"register and execute through one shared scheduler/state machine\" that does not exist yet -- building a bespoke ops.db table here with no caller wired to that scheduler would reproduce the exact \"substrate exists, zero surface wiring\" anti-pattern this sweep exists to fix, just one bead over.\n2. The AC's \"usage detection\" leg (injected refs cited/quoted/re-read downstream via text+embedding match) has no concretized algorithm anywhere in the design/notes -- it's a research problem, not an implementation task, and inventing one now would be exactly the \"inventing a design to close a bead\" anti-pattern the task brief warns against.\n3. Storage tier choice in the design (ops.db, disposable, multi-writer) is sound and durability-correct if/when this is built -- that part of the design is NOT the blocker.\n4. What WOULD unblock a minimal first slice: an operator decision on which concrete touchpoints count as a loggable \"read\"/\"expand\"/\"cite\" event (e.g. \"log every MCP context tool invocation\" vs \"log every delivered receipt's segment_refs on read\"), scoped independently of the full scheduler. Recommend that as the next actionable slice rather than the full design.\n\nNo code changes made for this bead. Priority/status unchanged (P3, open).\n","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:39:26Z","created_by":"Sinity","updated_at":"2026-07-31T08:17:06Z","labels":["area:context","area:daemon","delivery:D-agent-context-coordination","horizon:mid","lane:context-memory","tech-tree"],"dependencies":[{"issue_id":"polylogue-37t.17","depends_on_id":"polylogue-37t","type":"parent-child","created_at":"2026-07-06T01:39:25Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-bby.15","title":"Verified cold-reader evidence export over findings and selected relations","description":"This is the named cold-reader artifact for the external audit wedge. The interactive basket is a\nmutable workspace pointer to versioned selection/result snapshots plus annotations; it is not a\nparallel evidence store. Export produces a minimal self-contained report profile over findings,\nevidence ancestry, claims view, and evaluation/frame/privacy state.","design":"INTERACTION. Select refs -\u003e workspace basket pointer -\u003e draft -\u003e verify -\u003e export. Basket items refer\nto promoted query/result/finding/assertion/block anchors and carry notes/order; evidence bytes and\nprovenance remain in their owning stores. No evidence_basket domain table is introduced.\n\nVERIFIED COLD-READER PROFILE. Emit Markdown and HTML plus a machine-readable citation/evaluation\nmanifest containing claim/finding refs, resolved citations and content hashes, query/result and\nevaluation-world refs, enumeration/frame/measurement-authority labels, coverage/degradation,\nprivacy/redaction/excision policy, archive/runtime versions, and a reproducer command. The gate\nre-resolves every ref: drift is annotated, ambiguous/missing/quarantined/hash-mismatch states block\nor require explicit stale/forensic policy. The profile is an export shape, not a universal portable-\nbundle object/compiler. General federation waits until this one profile proves closure, redaction,\nand excision.","acceptance_criteria":"1. A no-context reader receives one directory/artifact set and can trace every rendered claim to\n verified evidence and reproduce the public-safe query without archive UI knowledge.\n2. Exact/frame/authority/privacy/degradation labels survive Markdown, HTML, and manifest rendering.\n3. Re-ingest drift, deleted evidence, ambiguity, quarantine, hash mismatch, stale evaluation, and\n held-private content each trigger the declared export behavior.\n4. Excision/redaction updates or invalidates the export manifest without leaving copied private\n evidence in a parallel basket store.\n5. The external audit flow uses this profile with 3tl.16's claims view and rxdo.4 findings.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=H-web-cockpit; lane=web-evidence-cockpit; readiness=A-implementation-ready; proof=web visual smoke, slow-route state fixture, basket-to-citable-export proof. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/059_polylogue_bby_15.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\nCONSUMER DECLARATION 2026-07-13: evidence basket -\u003e citable report IS the rxdo pipeline (findings + result_sets + ancestry checks rxdo.9.9) rendered in the web. The web owns presentation/interaction; the OBJECTS are rxdo's. Building a parallel basket model would fork provenance — do not.\n\n[LEGACY FIELDS PRESERVED BY CORRECTIVE PASS 2026-07-13]\nORIGINAL DESCRIPTION:\nThe missing \"report\" end of the web workbench: select blocks/spans in the reader -\u003e basket (content-hash anchors + quote + note + provenance of the query that surfaced it) -\u003e live Markdown report draft with footnotes -\u003e EXPORT GATE re-resolves every citation and blocks/flags by state (ok + drifted_position export with verified note; drifted_message/relocated need explicit promotion; ambiguous/missing block by default; quarantined blocks unless the report is explicitly forensic; hash_mismatch hard-fails). Storage v1 rides recall-pack machinery with an evidence_basket payload schema (items resolve/degrade counts already exist) — UI names it basket, storage adapter is an implementation detail; dedicated AssertionKinds (evidence_basket, report_draft) deliberately deferred until the shape settles (each new kind costs openapi/cli-schema regen + user_audit entry). Report exports emit Markdown/HTML + a citation manifest JSON.\n\nORIGINAL DESIGN:\nThree-pane cockpit flow (results | reader+graph | basket+draft); daemon API basket/report/verify routes collapse into service verbs when the t46/B8 contract lands. Depends on the block content-hash anchor substrate. Batch overlay endpoint (assertions/marks for a set of refs) serves the reader badges.\n[FULL VIEW SPEC 2026-07-08, post-bby.11 ratification]\nLOOP: select -\u003e basket -\u003e draft -\u003e verify -\u003e export; every stage durable. SELECT: in reader, any block/span selection offers \"add to basket\" (occ5 affordance registry entry); basket item = {block content-hash anchor (svfj), quote text, optional note, provenance = query-run ref that surfaced it (rxdo.3) + result-set id + workspace}. BASKET: right pane, reorderable, grouped by session; each item shows resolution state chip (bkzv vocabulary: resolved=solid, drifted=warn+diff affordance, missing=err) re-checked lazily on focus. Basket persists as ze5 WORKSPACE-class record (survives reload, addressable ref). DRAFT: live Markdown editor pane; inserting a basket item creates a footnote citation [^n] whose target is the content-hash ref, not prose — the draft stores refs, rendering resolves them. Agent leg: the draft is editable by agents via MCP (basket + draft are refs agents can read/extend — the 212.7 packet contract composes here). VERIFY (the export gate, the honesty differentiator): re-resolve every citation against the live archive; each resolves exact / drifted (content at anchor changed — show both, require re-pin or annotate) / missing (source deleted/re-ingested away — block export unless marked stale-accepted). Gate output = verification manifest embedded in the export (per-citation status + archive epoch + content hashes). EXPORT: Markdown with footnotes + manifest appendix; HTML via canonical renderer; both carry the polylogue:// deep links (gqx handler makes them desktop-live). FINDINGS BRIDGE: \"promote to finding\" turns a verified draft claim + its citations into an AssertionKind.FINDING (rxdo.4) — the basket is the finding-authoring UX. NON-GOALS: no WYSIWYG, no collaborative editing, no export formats beyond md/html until asked. TESTS: seeded drift fixture (re-ingest changes a cited block -\u003e gate flags exactly that citation); vitest basket state; playwright full-loop journey (select-\u003ebasket-\u003edraft-\u003everify-\u003eexport) as the flagship 1ilk e2e.\n\n\nORIGINAL ACCEPTANCE_CRITERIA:\nFull loop on the seeded demo corpus: query -\u003e basket 5 items -\u003e draft renders footnotes -\u003e re-ingest the corpus -\u003e verify flags the drifted item and export annotates it; a deleted block blocks export with a typed reason. Verify: integration-flavored test over the loop.\nPriority calibration 2026-07-15: P2 to P3. This is retained architectural renewal, hygiene, documentation, decision, proof packaging, or operator convenience work, but it does not presently outrank concrete archive-truth and core-query failures. Existing evidence, acceptance criteria, and horizon remain authoritative.","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:35:30Z","created_by":"Sinity","updated_at":"2026-07-15T20:07:36Z","metadata":{"consumer_proof":"external-audit"},"labels":["area:web","delivery:H-web-cockpit","horizon:frontier","lane:web-evidence-cockpit","tech-tree"],"dependencies":[{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-4p1","type":"blocks","created_at":"2026-07-07T14:52:39Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-bby","type":"parent-child","created_at":"2026-07-06T01:35:29Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-fnm.11","type":"blocks","created_at":"2026-07-07T14:52:40Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-rxdo.1","type":"blocks","created_at":"2026-07-07T14:52:35Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-rxdo.2","type":"blocks","created_at":"2026-07-07T14:52:36Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-rxdo.3","type":"blocks","created_at":"2026-07-07T14:52:37Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-rxdo.4","type":"blocks","created_at":"2026-07-07T14:52:38Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-rxdo.9.9","type":"blocks","created_at":"2026-07-13T07:55:11Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-bby.15","depends_on_id":"polylogue-svfj","type":"blocks","created_at":"2026-07-06T01:36:15Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":8,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-at44","title":"user_settings table is dead: DDL + migration 004 exist, zero runtime read/write helpers","description":"Verified live 2026-07-06: rg over polylogue/ finds user_settings only in the DDL (user.py), migration 004, and an unrelated filename string in artifact_taxonomy/runtime.py — no reader, no writer, table is empty and unwired. Two designs need it: cost-correctness (subscription_tier drives the $/credit parametrization instead of the hardcoded Pro-tier constant) and the config doctrine db layer (w8db: scope x actor x override resolver). DECISION encoded here after weighing the synthesis proposal to fold settings into assertions: KEEP the separate table — the user.py comment is right that settings are state, not epistemic claims, and the corpus recipe review independently reaffirms that separation. Wire it instead of unifying it.","design":"Add get/set/list helpers in user_write.py + async twin (STORAGE TWINS trap: apply to both sync archive_tiers and async mixins or daemon/CLI diverge), a settings surface on the api facade, and first consumer: subscription_tier read by cost_compute (kills the hardcoded /21_700_000*20.0 Pro assumption). w8db epic owns the full resolver; this bead is just liveness + first consumer.","acceptance_criteria":"Set+get subscription_tier via CLI/API; cost compute reads it with a sane default; both storage paths tested. Verify: focused tests on settings helpers + cost path.","notes":"2026-07-06 guardrail (gpt-pro feedback, accepted): even the liveness slice must not create a free-form global KV — define a typed registry of allowed setting keys from day one (subscription_tier first), partition deployment secrets OUT (they stay env/agenix, never user.db), and leave scope layering (global/repo/origin/surface) + winning-layer resolver explain to the w8db epic as designed. The failure mode to avoid: user_settings reborn as an untyped junk drawer, recreating the dead-table problem one level up.\n[Delivery upgrade 2026-07-07T00:05:00Z] Release=E-variants-preferences; lane=variants-preferences; readiness=D-horizon-ready; proof=mixed-language variant fixture with source/variant/alignment rendering in CLI/web/MCP. Original readiness=D-horizon-ready.\nFOLD DECISION 2026-07-13: treat at44 as the liveness and first-consumer slice of the y4c configuration implementation, not as an independent lane. Preserve its typed-setting-key and sync/async wiring acceptance criteria, but claim and execute it in the same branch/lane as y4c; y4c owns the broader resolver and doctrine.","status":"open","priority":3,"issue_type":"bug","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:26:59Z","created_by":"Sinity","updated_at":"2026-07-13T04:57:59Z","labels":["area:substrate","delivery:E-variants-preferences","horizon:frontier","lane:variants-preferences","tech-tree"],"dependencies":[{"issue_id":"polylogue-at44","depends_on_id":"polylogue-f2qv","type":"related","created_at":"2026-07-06T01:27:28Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-at44","depends_on_id":"polylogue-w8db","type":"parent-child","created_at":"2026-07-15T19:14:17Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-at44","depends_on_id":"polylogue-y4c","type":"related","created_at":"2026-07-13T07:04:39Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-rxdo.8","title":"Analysis recipes as DB-native runtime objects; YAML as import/export serialization only","description":"Corpus-reviewed decision (defended against two runner-ups): recipes/runs must be DB objects because complex analyses are interactive DAGs, not static phase lists — the durable truth is what actually ran (which queries, which batches, which model, what got superseded), which YAML cannot record and assertions must not become (assertions are claims; recipes are procedure; runs are execution state — the user.py settings-vs-assertions comment already encodes this distinction). YAML remains the portable/reviewable serialization: import pins a hash, composer can save-as-recipe, render-back-out supported. Distinct from prompt templates, which stay git-YAML per the distillery lane (code under review) — recipes reference prompt files by ref, they do not absorb them.","design":"user.db tables (batch with v5): analysis_recipes (definition_json, source_artifact_ref, version), analysis_runs (recipe ref, status, actor, archive_epoch, query_run_refs, annotation_batch_refs, artifact_refs, degraded). analysis:\u003cid\u003e and analysis-run refs from the ObjectRef bead. Runs launched via recipe run are durable by default; casual CLI queries stay ops-only. Surfaces ride the act/query/read contract (t46), not a sidecar runner.","acceptance_criteria":"recipe import -\u003e run -\u003e the run record cites its query runs and batches; re-run against a later epoch produces a diffable second run; YAML round-trips. Verify: focused tests over recipe lifecycle.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=D-horizon-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=D-horizon-ready.\n[2026-07-14 rxdo-cluster pass] Deferred, not attempted. This bead's own design requires a new user-tier schema slot for analysis_recipes/analysis_runs (\"batch with v5\"), but polylogue-60i5's authoritative corrective contract (2026-07-13) requires: (1) durable schema promotion needs a stabilized typed protocol AND at least two materially different consumers unless an urgent trust-floor exception is recorded; (2) a declared tier window with a durable ready-rider set in Beads before any migration lands; (3) exactly one contiguous migration step per declared window with the conductor refusing a second writer. No user-v6 window is currently declared (60i5's latest note only reconciles state after the v5 collision on PR #2813/#2794; it does not declare v6 riders). Adding analysis_recipes/analysis_runs tables now would be an undeclared, uncoordinated second writer against a window 60i5 hasn't opened -- exactly the failure class 60i5 exists to prevent.\n\nNot implemented as a workaround either: the design explicitly rejects a YAML-only or assertion-payload-only substitute (\"recipes/runs must be DB objects because... YAML cannot record [interactive DAGs]... assertions are claims; recipes are procedure; runs are execution state\").\n\nRecommended next step: this bead should stay blocked until a rider claims the next declared user-tier window through polylogue-60i5, per that bead's own coordination contract. Not closing or reprioritizing here -- flagging status quo accurately.\nVERDICT: LIVE — nothing landed; the bead's own 2026-07-14 note says it was 'deferred, not attempted' pending a declared user-tier v6 window via polylogue-60i5. Confirmed zero code exists: rg for analysis_recipes/analysis_runs across polylogue/ returns no hits (no schema, no runtime). — evidence: rg -ln 'analysis_recipes|analysis_runs' polylogue/ (0 results).","status":"open","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-05T23:26:56Z","created_by":"Sinity","updated_at":"2026-07-31T05:47:17Z","labels":["area:substrate","delivery:C-read-evidence-contract","horizon:mid","lane:read-contracts","tech-tree"],"dependencies":[{"issue_id":"polylogue-rxdo.8","depends_on_id":"polylogue-60i5","type":"related","created_at":"2026-07-06T01:27:28Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-rxdo.8","depends_on_id":"polylogue-rxdo","type":"parent-child","created_at":"2026-07-06T01:26:56Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-rxdo.8","depends_on_id":"polylogue-rxdo.3","type":"blocks","created_at":"2026-07-06T01:27:26Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-rxdo.8","depends_on_id":"polylogue-rxdo.7","type":"blocks","created_at":"2026-07-06T01:27:25Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":2,"dependent_count":0,"comment_count":0} @@ -1153,8 +1234,8 @@ {"_type":"issue","id":"polylogue-20d.5","title":"Finish streaming reads: composed transcripts, messages --full writer, origin-filtered pagination SQL","description":"Residue of the streaming-export slice: lineage-composed transcript streaming falls back to the eager path; read --view messages --full --to file lacks a true writer/iterator renderer; material-origin-filtered pagination is eager until SQL owns the predicate.","design":"Three eager fallbacks to close (prior-audit evidence, re-locate): (1) lineage-composed transcript streaming falls back to the eager path — extend the streaming writer landed in a9dc3f274 to composed (parent-prefix + tail) reads; (2) read --view messages --full --to file lacks a true writer/iterator renderer — same pattern; (3) material-origin-filtered message pagination hydrates eagerly until SQL owns the predicate — push material_origin into the repository pagination SQL (pattern: a17e3af95 routed ordinary paginated reads through repository pagination). Verify each with a live-archive file export timing + RSS bound, plus focused unit tests on the streaming/pagination modules.","acceptance_criteria":"- Lineage-composed transcript streaming uses the streaming writer (extend the a9dc3f274 pattern) for composed (parent-prefix + tail) reads — no eager full-materialization fallback remains (grep the composed read path).\n- `read --view messages --full --to \u003cfile\u003e` uses a true iterator/writer renderer rather than eager buffering.\n- Material-origin-filtered message pagination pushes `material_origin` into the repository pagination SQL (pattern a17e3af95); hydration no longer filters in Python.\n- Each of the three is verified with a live-archive file export showing bounded peak RSS (flat vs message count) with export timing recorded, plus focused unit tests on the streaming/pagination modules (`devtools test \u003cstreaming/pagination modules\u003e` green).","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=G-live-performance; lane=interactive-performance; readiness=A-implementation-ready; proof=named SLO report, daemon hot-path benchmark, push/cache invalidation tests. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/095_polylogue_20d_5.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.","status":"closed","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:32:02Z","created_by":"Sinity","updated_at":"2026-07-15T19:34:36Z","closed_at":"2026-07-15T19:34:36Z","close_reason":"Superseded by polylogue-z9gh.9.1, whose shared bounded query transaction now explicitly owns all three eager streaming/pagination residues.","labels":["area:perf","area:storage","delivery:G-live-performance","lane:interactive-performance"],"dependencies":[{"issue_id":"polylogue-20d.5","depends_on_id":"polylogue-20d","type":"parent-child","created_at":"2026-07-03T06:32:02Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-20d.2","title":"Defer heavy imports off the CLI startup path","description":"~2s import tax per invocation; also the residual cold cost when the daemon path is absent. Candidates: surfaces/payloads (~2,915 lines of Pydantic model construction), api/archive. Measure first: python -X importtime -c 'from polylogue.cli.click_app import main'. Covers the old help-latency and find-select-cold items; add the help-latency devtools budget check as the regression gate.","design":"Measure first: python -X importtime -c 'from polylogue.cli.click_app import main' 2\u003e\u00261 | sort -t'|' -k2 -rn | head -30. Known heavy candidates: surfaces/payloads (~2,915 lines of Pydantic model construction), api/archive, storage imports pulled at command-module import time. Mechanics: the repo already uses lazy Click commands (see bd memory: lazy cmds hide flags — use cmd.get_params(ctx) in tests); push heavy imports inside command bodies / module __getattr__; keep a leaf path-resolution module import-light for the daemon fast-path handshake. Regression gate: a devtools help-latency budget check (targeted `polylogue \u003ccmd\u003e --help` under a fixed budget) so drift fails loudly. Prior evidence: nested help 5-9s (import/reset/maintenance archive-read/analyze tools); warm find-select ~1.7s vs cold spikes.","acceptance_criteria":"- `python -X importtime -c 'from polylogue.cli.click_app import main'` shows surfaces/payloads and api/archive no longer imported on the `polylogue \u003ccmd\u003e --help` path. Verify: importtime diff before/after.\n- A new devtools help-latency budget check runs targeted `polylogue \u003ccmd\u003e --help` invocations under a fixed budget (e.g. \u003c700ms cold, citing the 20d.14 cold-CLI budget) and fails loudly on drift.\n- Nested helps (import / reset / maintenance archive-read / analyze tools) drop from the observed 5-9s to under the budget. Verify: measured before/after under the new budget check.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=G-live-performance; lane=interactive-performance; readiness=A-implementation-ready; proof=named SLO report, daemon hot-path benchmark, push/cache invalidation tests. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/096_polylogue_20d_2.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\nPR #2809 (live-performance-2) merged: additional partial progress — reset help import deferral measured, warm nested help ~1.18s -\u003e ~0.30s. DEFERRED (not closing): importtime diff artifact, fixed help-latency gate, and maintenance/archive-read nested-help work remain incomplete.\nPR #2816 merged: coordination archive-state probe groundwork landed. Remaining AC gaps still open per lane report: importtime diff artifact, fixed help-latency gate, maintenance/archive-read nested-help sweep.\n[2026-07-14] PR #2874 (branch feature/perf/interactive-slo-fast-path): re-measured current state — most nested helps already fast (~0.28-0.35s) thanks to prior PR #2809/#2827 work; found and fixed one remaining outlier, `polylogue config --help` (1.16s -\u003e 0.29s), caused by config.py eagerly importing completions.py (pulls insights/storage stack, ~650ms) just to register 3 subcommands — fixed via _LazyCommand proxies. Added `devtools bench help-latency` regression gate (11 required targets, all green). Remaining known outlier: `ops maintenance` command group (~1.6-1.9s, 2789-line module, ~30 heavy top-level imports) — kept informational in the gate, tracked as polylogue-sod7 rather than risking a rushed refactor. Bead stays open pending that follow-up + merge.","status":"closed","priority":3,"issue_type":"task","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:32:00Z","created_by":"Sinity","updated_at":"2026-07-14T17:04:57Z","closed_at":"2026-07-14T17:04:57Z","close_reason":"AC fully satisfied as of PR #2902 (merged dfe52af4f): all 13 required devtools bench help-latency targets green, including ops-maintenance and ops-maintenance-archive-read (the AC's own named nested-help targets), both now ~288ms (down from the original 5-9s evidence this bead cited). importtime diff artifact exists and is reproducible (python -X importtime -m polylogue.cli ops maintenance archive-read --help shows zero occurrences of the heavy storage/insights stack). The devtools bench help-latency regression gate (added in PR #2874) is fixed and enforced. One documented exception outside this AC's named scope: ops maintenance migrate-tier stays informational/over-budget for a separate, deeper architectural reason (archive_tiers package __init__.py eager DDL imports) -- tracked separately, not part of this bead's closure.","labels":["area:cli","area:perf","delivery:G-live-performance","lane:interactive-performance","wave:2"],"dependencies":[{"issue_id":"polylogue-20d.2","depends_on_id":"polylogue-20d","type":"parent-child","created_at":"2026-07-03T06:32:00Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-fnm.4","title":"Shell completion + fuzzy selection as read-only projections of the grammar registries","description":"Completion/query-builder metadata built on the same grammar+registries used by CLI/MCP/daemon/web — not a second parser. Substantial substrate exists (query_completions tool, projection-unit completions landed 07-03); remaining scope per issue. GH issue thread (body + comments) is input, not authority; this bead's scope statement wins where they conflict.","design":"Scope per gh#1844 minus what landed: query_completions MCP tool, projection-unit completions, and dynamic shell completions (polylogue config completions --shell) exist. Remaining: completion coverage for pipeline stages/operators/read-view names sourced from the SAME registries (metadata.py descriptors, read_view_registry, action contracts — no second vocabulary); bounded archive-backed value providers (origins, repos, tags) with latency caps; fzf-style fuzzy selection beyond the select verb. Acceptance: a completion snapshot test asserts every grammar-reachable field/unit/stage/view appears in the completion payload (registry-diff test, so new DSL work cannot silently miss completions).","acceptance_criteria":"- A registry-diff snapshot test asserts that every grammar-reachable field/unit/pipeline-stage/read-view name (enumerated from metadata.py descriptors, read_view_registry, and operations.action_contracts.ACTION_CONTRACTS) appears in the completion payload, so new DSL work cannot silently miss completions. Verify: pytest registry-diff test.\n- Completions for pipeline stages, operators, and read-view names are sourced from those same registries (no second vocabulary — completions.py already imports query_unit_descriptor/terminal_query_pipeline_stage_infos/ACTION_CONTRACTS at completions.py:14-32).\n- Archive-backed value providers (origins, repos, tags) return under a stated latency cap. Verify: test measures provider latency against the cap.\n- Both `polylogue config completions --shell` and the query_completions MCP tool resolve pipeline-stage and read-view completions. Verify: a test asserts parity across the two surfaces.","notes":"Perf tie-in (2026-07-03): completions are keystroke-path — they inherit the interactive SLO tier budget (\u003c50ms round trip, 20d.14). That effectively requires daemon-served completion via the fast path (20d.1) with the registry payload precomputed in the daemon cache (20d.12); a cold-CLI completion that spins the full import+archive-open path can never meet the budget. Shell completion scripts should call the daemon endpoint and degrade to static grammar-only completions (no archive values) when the daemon is down.\n[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=A-implementation-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/155_polylogue_fnm_4.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\n2026-07-15 wiring-closure audit (polylogue-9e5.31): cwd_prefix filtering is production-wired and declares a completion_source, but complete_cwd_prefix_values is a registered handler that always returns [] because no cwd aggregate/read model exists. This is an exact instance of the remaining archive-backed value-provider AC; do not file a separate completion bead. The closure proof should include cwd_prefix alongside origin/repo/tag and fail if its handler reverts to an unconditional empty list.\nPriority calibration 2026-07-15: P2 to P3. This is a valuable query-language extension, interaction refinement, presentation improvement, or convenience surface, but the truthful bounded core and model-facing discovery contract precede it. The capability remains in scope and at the same horizon.\nAC candidates from the 2026-07-16 frontier sweep (Fable): the grammar-projection framing makes two laws checkable that should ride this bead's AC when claimed: (1) round-trip validity - any completion offered by any completer, spliced into its command position, parses without UsageError against the demo archive (property over the completer matrix; tests/unit/cli/test_completion_matrix.py proves shape, not validity); (2) latency budget - every dynamic completer answers within an interactive budget against a seeded corpus (completions run in the shell hot path). Both are laws about the projection contract itself, not new features.","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:57Z","created_by":"Sinity","updated_at":"2026-07-16T18:51:58Z","external_ref":"gh-1844","labels":["area:cli","area:query","delivery:C-read-evidence-contract","horizon:frontier","lane:read-contracts"],"dependencies":[{"issue_id":"polylogue-fnm.4","depends_on_id":"polylogue-fnm","type":"parent-child","created_at":"2026-07-03T06:31:57Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-fnm.2","title":"Projection predicates/windows + render/layout stages on attached units","description":"Declared predicates/windows on attached units (e.g. with messages[role:user, last:20]) and render/layout stages so read packages and demos are declarable in the query rather than per-view flags. Direction: .agent/includes/fables-poly-findings.md.","design":"Two layers: (1) predicates/windows on attached units — extend the with-stage parse (hand-parsed pipeline region, expression.py ~:1484-1601 where WITH_PROJECTION_SUPPORTED_UNITS is enforced) to accept per-unit bracket args (messages[role:user, last:20]); lower onto the existing exact-session-id fetch in attached_units.py (caps exist: _MAX_ROWS_PER_SESSION=200; field selection landed 867b1d048 — extend that payload, don't fork it). (2) render/layout stages — new pipeline stage kind (same touchpoint chain as aggregates: stage parser -\u003e AST/to_payload -\u003e executor -\u003e registry -\u003e completions -\u003e render regen) that binds a read-package/render profile to the query result. Keep grammar untouched (stages are hand-parsed); regenerate openapi/cli-output-schemas/cli-reference; explain payloads pick stages up via to_payload.","acceptance_criteria":"- `... with messages[role:user, last:20]` parses per-unit bracket predicates/windows in the hand-parsed with-stage region and lowers onto the existing exact-session-id fetch in attached_units.py, respecting _MAX_ROWS_PER_SESSION and extending the landed field-selection payload rather than forking it. Verify: pytest asserts filtered/windowed attached rows and cap enforcement.\n- A new render/layout pipeline stage binds a read-package/render profile to the result and is picked up by explain via to_payload.\n- The Lark grammar file is unchanged (stages stay hand-parsed). Verify: grammar-file diff is empty.\n- openapi/cli-output-schemas/cli-reference regens pass `devtools render all --check`.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=A-implementation-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/154_polylogue_fnm_2.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\nPriority calibration 2026-07-15: P2 to P3. This is a valuable query-language extension, interaction refinement, presentation improvement, or convenience surface, but the truthful bounded core and model-facing discovery contract precede it. The capability remains in scope and at the same horizon.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. No trace of bracket-predicate/window syntax on attached units (messages[role:user, last:20]) or a render/layout pipeline stage anywhere in polylogue/archive/query/expression.py or elsewhere in the tree. Evidence: grep -rn 'role:user, last:20|bracket_predicate|render_stage|layout_stage' polylogue/archive/query/expression.py -\u003e no matches.","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:56Z","created_by":"Sinity","updated_at":"2026-07-31T05:54:05Z","labels":["area:query","delivery:C-read-evidence-contract","horizon:frontier","lane:read-contracts"],"dependencies":[{"issue_id":"polylogue-fnm.2","depends_on_id":"polylogue-fnm","type":"parent-child","created_at":"2026-07-03T06:31:55Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"polylogue-fnm.1","title":"Aggregates beyond count (sum/avg/min/max/percentiles)","description":"`group by X | count` is the only aggregate; cost/duration/token questions need sum/avg/percentiles to compose instead of spawning bespoke analyze modes.","design":"Full target shape (fables ladder item 4): `| group by tool, session.origin | agg count, avg:duration_ms, p90:duration_ms, sum:tokens` — multi-field group by AND named aggregate list AND time bucketing `group by bucket:day(time)` (temporal-bucket machinery already exists in the temporal read view; reuse its bucket functions in the lowering). SQLite computes sum/avg/min/max natively; percentiles via nearest-rank in Python over grouped rows (pattern insights/portfolio.py:107-128). Pipeline stages are hand-parsed OUTSIDE the Lark grammar (~expression.py:1574/:2777) — no grammar change for the stage itself. Chain: stage parser -\u003e AST dataclass + to_payload (pattern :312-478) -\u003e QueryUnitPipelineStage union + assembly (:511-540; aggregate is currently Literal['count']) -\u003e executor (unit_results.py/plan_execution.py) -\u003e SQL SELECT-list on per-unit sql_query_method -\u003e metadata.py aggregate_metrics + multi-field aggregate_group_fields -\u003e shell_completion_values.py -\u003e render openapi/cli-output-schemas/cli-reference. This is what converts the DSL from counting console to the analytics engine the web aggregate view and saved-view defaults sit on. Line refs pre-07-03; re-locate.","acceptance_criteria":"- On the live archive `messages where ... | group by tool | agg count, avg:duration_ms, p90:duration_ms` returns per-group rows with each named metric column; sum/avg/min/max lower to native SQLite aggregates and percentiles compute via nearest-rank in Python over grouped rows. Verify: pytest over a seeded corpus asserts column presence and computed values.\n- Multi-field group-by (`group by tool, session.origin`) and time bucketing (`group by bucket:day(time)`) reuse the temporal read-view bucket functions.\n- Unsupported agg names/fields error naming the unit, the metric, and the supported set (the fnm.11 group-by error pattern).\n- The QueryUnitPipelineStageSpec aggregate union is widened from Literal['count'] and round-trips through to_payload; explain_query_expression shows the new aggregate. Verify: `devtools render openapi \u0026\u0026 devtools render cli-output-schemas \u0026\u0026 devtools render cli-reference` regen and `devtools render all --check` pass.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=A-implementation-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/050_polylogue_fnm_1.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\n2026-07-10 Fable campaign integration: the first useful slice must support multi-field grouping plus count/proportion with an explicit denominator, n, unknown/missing counts, and unsupported-field errors. This is sufficient for delegation-discourse tables; percentiles and time buckets may remain later in the same bead if needed, but the denominator contract may not be deferred.\nPR #2775 (merged) delivered the narrowed first slice: multi-field group-by + count/proportion aggregates with explicit denominator/n and distinct [missing]/unknown buckets, envelope-pagination aware. Remaining in-bead scope: percentiles + time buckets (see fnm.11). Do not re-deliver the slice.\nPriority calibration 2026-07-15: P2 to P3. This is a valuable query-language extension, interaction refinement, presentation improvement, or convenience surface, but the truthful bounded core and model-facing discovery contract precede it. The capability remains in scope and at the same horizon.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. Only the narrowed slice (count/proportion aggregate, PR #2775) merged, per the bead's own notes. Live source still has aggregate: Literal['count'] | None in polylogue/archive/query/expression.py (lines 587, 667) -- no avg/sum/min/max/percentile support, no multi-field group-by extension, no bucket:day(time) grouping. Evidence: grep -n \"aggregate: Literal\" polylogue/archive/query/expression.py -\u003e only Literal['count']; git log origin/master --oneline --grep=aggregate -i shows no landing PR for widened aggregates.","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:55Z","created_by":"Sinity","updated_at":"2026-07-31T05:54:03Z","labels":["area:query","delivery:C-read-evidence-contract","horizon:frontier","lane:read-contracts"],"dependencies":[{"issue_id":"polylogue-fnm.1","depends_on_id":"polylogue-fnm","type":"parent-child","created_at":"2026-07-03T06:31:54Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fnm.1","depends_on_id":"polylogue-fnm.11","type":"blocks","created_at":"2026-07-04T21:31:21Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} +{"_type":"issue","id":"polylogue-fnm.2","title":"Projection predicates/windows + render/layout stages on attached units","description":"Declared predicates/windows on attached units (e.g. with messages[role:user, last:20]) and render/layout stages so read packages and demos are declarable in the query rather than per-view flags. Direction: .agent/includes/fables-poly-findings.md.","design":"Two layers: (1) predicates/windows on attached units — extend the with-stage parse (hand-parsed pipeline region, expression.py ~:1484-1601 where WITH_PROJECTION_SUPPORTED_UNITS is enforced) to accept per-unit bracket args (messages[role:user, last:20]); lower onto the existing exact-session-id fetch in attached_units.py (caps exist: _MAX_ROWS_PER_SESSION=200; field selection landed 867b1d048 — extend that payload, don't fork it). (2) render/layout stages — new pipeline stage kind (same touchpoint chain as aggregates: stage parser -\u003e AST/to_payload -\u003e executor -\u003e registry -\u003e completions -\u003e render regen) that binds a read-package/render profile to the query result. Keep grammar untouched (stages are hand-parsed); regenerate openapi/cli-output-schemas/cli-reference; explain payloads pick stages up via to_payload.","acceptance_criteria":"- `... with messages[role:user, last:20]` parses per-unit bracket predicates/windows in the hand-parsed with-stage region and lowers onto the existing exact-session-id fetch in attached_units.py, respecting _MAX_ROWS_PER_SESSION and extending the landed field-selection payload rather than forking it. Verify: pytest asserts filtered/windowed attached rows and cap enforcement.\n- A new render/layout pipeline stage binds a read-package/render profile to the result and is picked up by explain via to_payload.\n- The Lark grammar file is unchanged (stages stay hand-parsed). Verify: grammar-file diff is empty.\n- openapi/cli-output-schemas/cli-reference regens pass `devtools render all --check`.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=A-implementation-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/154_polylogue_fnm_2.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\nPriority calibration 2026-07-15: P2 to P3. This is a valuable query-language extension, interaction refinement, presentation improvement, or convenience surface, but the truthful bounded core and model-facing discovery contract precede it. The capability remains in scope and at the same horizon.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. No trace of bracket-predicate/window syntax on attached units (messages[role:user, last:20]) or a render/layout pipeline stage anywhere in polylogue/archive/query/expression.py or elsewhere in the tree. Evidence: grep -rn 'role:user, last:20|bracket_predicate|render_stage|layout_stage' polylogue/archive/query/expression.py -\u003e no matches.\n[Implementation 2026-07-31] Landed the bracket-predicate/window half on feature/query-dsl/aggregates-and-attached-unit-windows: `with unit[field:value, ...]` bracket clauses on the `with \u003cunits\u003e` session-query projection clause, combinable with the existing `unit(field, field)` payload-field selector. WithUnitWindow (predicates + optional first:N/last:N) threaded end-to-end: expression.py -\u003e SessionQuerySpec.with_unit_windows -\u003e SessionFilter -\u003e archive_execution.py -\u003e attached_units.py (fetch_attached_units applies predicates then window trim) -\u003e cli/archive_query.py. Grammar file untouched (hand-parsed like the existing with-clause splitting).\n\nAC status (predicates/windows half):\n- bracket predicates/windows on attached units: SATISFIED. Verified against the live archive (hermes-session with 36 user messages, 1124 total) and via the real CLI module end-to-end against a seeded demo archive.\n- caps (_MAX_ROWS_PER_SESSION) respected: SATISFIED, and a real bug was found+fixed during verification -- last:N naively fetching ascending-from-start and slicing [-n:] silently returns the WRONG rows once a session exceeds the fetch cap (returns the tail of the *capped head*, not the session's true tail). Fixed by fetching descending-time specifically for last:N, then restoring ascending order.\n- extends the landed field-selection payload rather than forking it: SATISFIED (same unit(field,field) parse path, bracket is an added optional group in the same regex).\n\nAC status (render/layout stage half): NOT ATTEMPTED. Filed as polylogue-5ka4 (P2) -- needs a \"read-package/render profile\" concept that doesn't exist as a first-class thing to bind to yet; the nearest analogues live in insights/ and other lanes this task's scope excluded. Fabricating a profile registry just to close the AC would have been a thin/misleading implementation.\n\nPR not yet opened at time of this note.\nPR opened: https://github.com/Sinity/polylogue/pull/3440 (branch feature/query-dsl/aggregates-and-attached-unit-windows, predicates/windows half only; render/layout stage split out as polylogue-5ka4).","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:56Z","created_by":"Sinity","updated_at":"2026-07-31T09:30:35Z","labels":["area:query","delivery:C-read-evidence-contract","horizon:frontier","lane:read-contracts"],"dependencies":[{"issue_id":"polylogue-fnm.2","depends_on_id":"polylogue-fnm","type":"parent-child","created_at":"2026-07-03T06:31:55Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"polylogue-fnm.1","title":"Aggregates beyond count (sum/avg/min/max/percentiles)","description":"`group by X | count` is the only aggregate; cost/duration/token questions need sum/avg/percentiles to compose instead of spawning bespoke analyze modes.","design":"Full target shape (fables ladder item 4): `| group by tool, session.origin | agg count, avg:duration_ms, p90:duration_ms, sum:tokens` — multi-field group by AND named aggregate list AND time bucketing `group by bucket:day(time)` (temporal-bucket machinery already exists in the temporal read view; reuse its bucket functions in the lowering). SQLite computes sum/avg/min/max natively; percentiles via nearest-rank in Python over grouped rows (pattern insights/portfolio.py:107-128). Pipeline stages are hand-parsed OUTSIDE the Lark grammar (~expression.py:1574/:2777) — no grammar change for the stage itself. Chain: stage parser -\u003e AST dataclass + to_payload (pattern :312-478) -\u003e QueryUnitPipelineStage union + assembly (:511-540; aggregate is currently Literal['count']) -\u003e executor (unit_results.py/plan_execution.py) -\u003e SQL SELECT-list on per-unit sql_query_method -\u003e metadata.py aggregate_metrics + multi-field aggregate_group_fields -\u003e shell_completion_values.py -\u003e render openapi/cli-output-schemas/cli-reference. This is what converts the DSL from counting console to the analytics engine the web aggregate view and saved-view defaults sit on. Line refs pre-07-03; re-locate.","acceptance_criteria":"- On the live archive `messages where ... | group by tool | agg count, avg:duration_ms, p90:duration_ms` returns per-group rows with each named metric column; sum/avg/min/max lower to native SQLite aggregates and percentiles compute via nearest-rank in Python over grouped rows. Verify: pytest over a seeded corpus asserts column presence and computed values.\n- Multi-field group-by (`group by tool, session.origin`) and time bucketing (`group by bucket:day(time)`) reuse the temporal read-view bucket functions.\n- Unsupported agg names/fields error naming the unit, the metric, and the supported set (the fnm.11 group-by error pattern).\n- The QueryUnitPipelineStageSpec aggregate union is widened from Literal['count'] and round-trips through to_payload; explain_query_expression shows the new aggregate. Verify: `devtools render openapi \u0026\u0026 devtools render cli-output-schemas \u0026\u0026 devtools render cli-reference` regen and `devtools render all --check` pass.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=A-implementation-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/050_polylogue_fnm_1.md (depth: bead-localized-from-export; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\n2026-07-10 Fable campaign integration: the first useful slice must support multi-field grouping plus count/proportion with an explicit denominator, n, unknown/missing counts, and unsupported-field errors. This is sufficient for delegation-discourse tables; percentiles and time buckets may remain later in the same bead if needed, but the denominator contract may not be deferred.\nPR #2775 (merged) delivered the narrowed first slice: multi-field group-by + count/proportion aggregates with explicit denominator/n and distinct [missing]/unknown buckets, envelope-pagination aware. Remaining in-bead scope: percentiles + time buckets (see fnm.11). Do not re-deliver the slice.\nPriority calibration 2026-07-15: P2 to P3. This is a valuable query-language extension, interaction refinement, presentation improvement, or convenience surface, but the truthful bounded core and model-facing discovery contract precede it. The capability remains in scope and at the same horizon.\nVERIFICATION (group4 stale-sweep, 2026-07-31): LIVE. Only the narrowed slice (count/proportion aggregate, PR #2775) merged, per the bead's own notes. Live source still has aggregate: Literal['count'] | None in polylogue/archive/query/expression.py (lines 587, 667) -- no avg/sum/min/max/percentile support, no multi-field group-by extension, no bucket:day(time) grouping. Evidence: grep -n \"aggregate: Literal\" polylogue/archive/query/expression.py -\u003e only Literal['count']; git log origin/master --oneline --grep=aggregate -i shows no landing PR for widened aggregates.\n[Implementation 2026-07-31] Landed on feature/query-dsl/aggregates-and-attached-unit-windows: new `| agg count, sum:FIELD, avg:FIELD, min:FIELD, max:FIELD, pNN:FIELD` pipeline stage (QueryUnitAggMetric/QueryUnitAggStage, new \"agg\" terminal action) alongside the existing count-only aggregate. Numeric metric fields declared per unit via QueryUnitDescriptor.aggregate_metric_fields: message=word_count, action=is_error/exit_code (error count/rate). Verified against the live archive: exact match vs hand-written SQL for a 116-row group (sum/avg/min/max), correct exact=false/sampled_rows reporting for a 2.8M-row group.\n\nAC status:\n- count/sum/avg/min/max/percentile with named metric columns: SATISFIED (word_count, is_error, exit_code only -- see deferred).\n- multi-field group-by: SATISFIED (reused existing group-by machinery, works with agg).\n- time bucketing (bucket:day(time)): DEFERRED -- needs the temporal read-view's bucket functions, which live in insights/ (out of this PR's lane).\n- unsupported names/fields error naming unit/metric/supported set: SATISFIED.\n- aggregate union widened + to_payload round-trip + explain visibility: PARTIALLY SATISFIED -- implemented as an additive `agg_metrics` field/`agg` stage alongside the existing `aggregate: Literal[\"count\"]` field rather than widening that field in place, to avoid destabilizing the count/group/sort-by-count paths other lanes (mcp/, insights/) depend on. New agg_metrics field round-trips through to_payload and is explain-visible.\n- SQL-pushdown vs post-filter honesty: the count-only aggregate lowerer (ArchiveStore.query_unit_counts, storage/sqlite -- another lane's file) stays exact SQL. Named-metric reduction is NOT SQL-pushed: it fetches up to 50,000 predicate-matching rows through the existing row query and reduces in Python, reporting result.exact/result.sampled_rows explicitly rather than silently sampling.\n\nNot attempted: duration_ms/token metric fields (not present in the current row payload projections -- ArchiveMessageQueryRow/ArchiveActionQueryRow don't carry them; adding them needs a storage/sqlite change, another lane's scope).\n\nPR not yet opened at time of this note.\nPR opened: https://github.com/Sinity/polylogue/pull/3440 (branch feature/query-dsl/aggregates-and-attached-unit-windows, bundled with fnm.2's predicate/window half).","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:55Z","created_by":"Sinity","updated_at":"2026-07-31T09:30:24Z","labels":["area:query","delivery:C-read-evidence-contract","horizon:frontier","lane:read-contracts"],"dependencies":[{"issue_id":"polylogue-fnm.1","depends_on_id":"polylogue-fnm","type":"parent-child","created_at":"2026-07-03T06:31:54Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-fnm.1","depends_on_id":"polylogue-fnm.11","type":"blocks","created_at":"2026-07-04T21:31:21Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-fnm","title":"Query DSL: one grammar owns query semantics; compose instead of multiplying verbs","description":"The Lark grammar in polylogue/archive/query/expression.py is THE query language; extend in place. Landed since the GH issue: with-projection for all units, field selection for attached units, projection-unit completions. GH issue thread (body + comments) is input, not authority; this bead's scope statement wins where they conflict.","design":"The Lark grammar in polylogue/archive/query/expression.py IS the query language — extend it in place, never as a parallel verb/flag path. Baseline already landed: with-projection for all units, field selection for attached units, projection-unit completions. Treat the GH issue thread as input, not authority; this bead's scope statement wins where they conflict. Coordinates with t46 (the DSL becomes the sole owner of query semantics).","acceptance_criteria":"- New query semantics are added to the Lark grammar in polylogue/archive/query/expression.py (grep shows the grammar rule) rather than as a parallel verb or flag.\n- The landed-since baseline (with-projection all units, field selection for attached units, projection-unit completions) stays green; `explain_query_expression` / `query_units` reflect the grammar.\n- `devtools verify` is green on DSL tests; `devtools render all --check` is clean for any generated query-surface docs/schemas.\n- Individual grammar extensions are tracked as child beads; the epic closes when the DSL is the sole owner of query semantics (t46 coordination).","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=C-read-evidence-contract; lane=read-contracts; readiness=A-implementation-ready; proof=CLI/daemon/MCP/Python/web query parity suite and content-hash citation drift fixture. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/150_polylogue_fnm.md (depth: epic-checklist; urgency: T1-critical-path-correctness). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.\n[GPT-Pro branch assimilation 2026-07-11] Branch 16 (`6a5112f9`; mission 05 Query DSL) archive endpoints were reached with authenticated browser control but return `ace_pod_expired`; prose remains. Accepted: missing-time != zero, preserve unconstrained arrow, explicit nearest-rank, physical grouping != lineage dedup, independent oracle/mutations, typed Sinex handoff. Existing fnm.3/fnm.1 own implementation; no monolithic patch/prompt-pack reconstruction. Matrix: `.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md`.\nHorizon classification 2026-07-15: the grammar remains the sole query-language authority, while current mandate execution lands first through 4p1/z9gh and selected concrete DSL children.","status":"open","priority":3,"issue_type":"epic","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:54Z","created_by":"Sinity","updated_at":"2026-07-15T19:38:13Z","external_ref":"gh-2006","labels":["area:query","delivery:C-read-evidence-contract","horizon:mid","lane:read-contracts"],"dependencies":[{"issue_id":"polylogue-fnm","depends_on_id":"polylogue-38x","type":"relates-to","created_at":"2026-07-04T02:59:21Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"polylogue-4ts.4","title":"Wrap lineage composition reads in a single read transaction","description":"Composition uses multiple autocommit SELECTs; a concurrent parent re-ingest between reads yields a torn transcript. Hold one deferred read transaction across the recursion (pattern: fts_invariant_snapshot_sync). GH issue thread (body + comments) is input, not authority; this bead's scope statement wins where they conflict.","design":"Code-confirmed (gh#2476): get_messages / read_archive_session_envelope / _composed_db_signatures compose via multiple autocommit SELECTs (edge read -\u003e recursive parent read -\u003e own read); a parent re-ingest between reads yields a torn transcript. Fix: hold one deferred read transaction across the whole composition recursion — pattern to copy: fts_invariant_snapshot_sync. Apply to BOTH sync and async paths (twin-path trap, see bd memories). Test: interleave a parent full-replace between edge-read and parent-read via a hook/monkeypatch; assert composed transcript is either old-consistent or new-consistent, never mixed.","acceptance_criteria":"1. Both the sync path (read_archive_session_envelope, _composed_db_signatures) and the async path (get_messages, plus batch/paginated composition) hold ONE deferred read transaction across the full inheritance recursion (edge read -\u003e recursive parent read -\u003e own read), following the fts_invariant_snapshot_sync pattern. 2. A regression test interleaves a parent full-replace (DELETE + re-INSERT) between the edge-read and the parent-read via hook/monkeypatch and asserts the composed transcript is wholly old-consistent or wholly new-consistent, never torn — asserted on BOTH the sync and async paths (the twin-path trap is an explicit checkable item, not incidental). Verify: `devtools test tests/unit/storage/` selection covering the composition paths passes; the interleaving test fails on current main if the snapshot is missing and passes after the fix.","notes":"[Delivery upgrade 2026-07-07T00:05:00Z] Release=F-lineage-compaction; lane=lineage-compaction; readiness=A-implementation-ready; proof=branch/shared-prefix/compaction/truncation fixture matrix and regrounding proof. Original readiness=A-implementation-ready.\n[Prework packet 2026-07-07] Static execution packet (anchors, mechanism, plan, tests, verification): .agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/081_polylogue_4ts_4.md (depth: anchored-contract-prework; urgency: T2-foundation-before-feature-proof). Generated from master @ 8a975a40 2026-07-06 — verify source anchors before coding; line numbers are snapshot-relative.","status":"closed","priority":3,"issue_type":"bug","assignee":"Sinity","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:53Z","created_by":"Sinity","updated_at":"2026-07-09T01:18:42Z","started_at":"2026-07-09T01:02:53Z","closed_at":"2026-07-09T01:18:42Z","close_reason":"Fixed the torn-transcript race in polylogue/storage/sqlite/archive_tiers/write.py (read_archive_session_envelope, _composed_db_signatures) and polylogue/storage/sqlite/queries/message_query_reads.py (get_messages): each now checks conn.in_transaction and, if not already inside one, opens a deferred read transaction (BEGIN DEFERRED / ROLLBACK in finally) around the whole recursive/iterative composition, so a concurrent parent re-ingest between the child-own-read and the recursive parent-read cannot produce a torn transcript. Recursive/inner calls see in_transaction already true and skip re-wrapping (no nested BEGIN). get_messages_paginated/get_messages_batch/get_message_edge_windows all delegate their lineage-composition case to get_messages already, so they inherit the fix without separate changes.\n\nBoth sync and async paths covered (the twin-path trap AC item satisfied explicitly, not incidentally). Regression tests (test_sync_composition_holds_one_snapshot_across_concurrent_parent_write, test_async_composition_holds_one_snapshot_across_concurrent_parent_write in tests/unit/storage/test_lineage_normalization.py) interleave a real concurrent parent-block edit via a second WAL-mode connection, using a monkeypatch hook on the prefix-sharing edge lookup -- CONFIRMED via git stash to FAIL on pre-fix code (assert conn.in_transaction) and PASS after the fix, satisfying the AC verify clause literally (\"fails on current main if the snapshot is missing and passes after the fix\"). devtools test on test_lineage_normalization.py + test_archive_tiers_write.py: 76 passed. mypy --strict clean on all 3 changed files. devtools render all --check clean. Shipped as PR #2594, merged 086171701.\n\nNote: the design note referenced \"pattern to copy: fts_invariant_snapshot_sync\" but that function turned out to be an unrelated state-recording helper, not a transaction-snapshot pattern -- no existing idiom for this technique existed in the codebase; implemented the conn.in_transaction guard + BEGIN DEFERRED/ROLLBACK wrapper as original, minimal-footprint design instead.","external_ref":"gh-2476","labels":["area:lineage","area:storage","delivery:F-lineage-compaction","lane:lineage-compaction","wave:1"],"dependencies":[{"issue_id":"polylogue-4ts.4","depends_on_id":"polylogue-4ts","type":"parent-child","created_at":"2026-07-03T06:31:52Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0} {"_type":"issue","id":"polylogue-83u.5","title":"Blob store zstd compression (36GB -\u003e est 5-8GB)","description":"Content-addressed blobs are uncompressed JSON; zstd frames are self-identifying so no schema change is needed.","design":"Address stays SHA-256 of UNCOMPRESSED bytes. No marker column: zstd magic \\x28\\xB5\\x2F\\xFD — read path sniffs 4 bytes, falls back to raw. Touchpoints: blob_store.py (write: compress if len\u003e512 and not already zstd; read: sniff+decompress), blob_integrity.py (verify = decompress-then-hash — silently breaks if missed), size accounting (logical size + stored_bytes where cheap). Migration: ops maintenance blob-compact [--limit N] — iterate shards, skip magic-prefixed, temp+rename atomic, verify hash before replace, honor pending_blob_refs leases. Level 9 one-shot, level 3 ingest-time. Dependency: zstandard wheel (pure-wheel exists). Synergy: the recompression pass and the GC sweep walk the same shard tree — one `ops maintenance blob-compact` job can do verify-hash -\u003e recompress -\u003e GC in a single walk. Expected 5-10x on raw provider JSON (36GB -\u003e ~4-7GB); write-once/read-rarely is the ideal compression profile; also shrinks the backup surface (blob store is backup_required). Lazy migration alternative: recompress opportunistically during GC passes instead of one big job.","acceptance_criteria":"`polylogue-83u.5` preserves byte integrity: before/after byte counts, SHA-256 roundtrip verification, missing-reference handling, and degraded-state rendering are recorded. The feature is blocked until missing blob debt is classified and no cleanup path can delete leased in-flight blobs. Verification artifact: leased-blob race fixture, blob-reference resolver report, SHA-256 restore/compression proof.","notes":"REVIEW CORRECTION (2026-07-06, bundle-3): the no-break claim is too optimistic — address stays SHA(uncompressed) but EVERY reader/verifier/backup-checker/evidence-resolver/GC path changes behaviorally (codec sniffing, dictionaries, frozen ranges, tombstones, logical-vs-stored bytes). MANDATORY PHASING: (1) read/verify beachhead FIRST — codec sniffing + decompress-then-hash verification while writes still emit raw; block compression writes until verify_all/backup/resolver/GC pass mixed raw+zstd fixtures; (2) source-v3 placement metadata (blob_placement, blob_dicts, blob_tombstones, frozen_segments — batch via 60i5); dictionary registry becomes BACKUP-CRITICAL; (3) compression writes; (4) cold/frozen/drop with citable tombstones, each blob-compact phase resumable + lease-aware; dropped reacquirable blobs resolve to a typed BlobDropped payload, never 500/silent absence. Access-temperature signals live in ops (lossy ok). Verbatim spec: bundles/rnd-bundle-3-of-6.md L742.\n[Delivery upgrade 2026-07-07T00:05:00Z] Release=B-storage-rebuild-bytes; lane=blob-integrity; readiness=D-horizon-ready; proof=leased-blob race fixture, blob-reference resolver report, SHA-256 restore/compression proof. Original readiness=E-spec-needed.\nPAIRING 2026-07-13: decide with fie's scaling doctrine — zstd on the blob store (36GB -\u003e est 5-8GB) is the biggest single lever for the keep-everything-forever policy's storage curve, and D7 (redundancy atlas) quantifies the semantic-duplicate mass on top. Measure both before fie's ceiling decisions.","status":"open","priority":3,"issue_type":"feature","owner":"ezo.dev@gmail.com","created_at":"2026-07-03T04:31:49Z","created_by":"Sinity","updated_at":"2026-07-13T04:13:27Z","labels":["area:attachments","area:perf","area:storage","delivery:B-storage-rebuild-bytes","delivery:ac-patched","lane:blob-integrity"],"dependencies":[{"issue_id":"polylogue-83u.5","depends_on_id":"polylogue-83u","type":"parent-child","created_at":"2026-07-03T06:31:48Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-83u.5","depends_on_id":"polylogue-83u.2","type":"blocks","created_at":"2026-07-07T14:52:32Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-83u.5","depends_on_id":"polylogue-83u.3","type":"blocks","created_at":"2026-07-07T14:52:33Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-83u.5","depends_on_id":"polylogue-83u.4","type":"blocks","created_at":"2026-07-07T14:52:31Z","created_by":"Sinity","metadata":"{}"},{"issue_id":"polylogue-83u.5","depends_on_id":"polylogue-83u.6","type":"blocks","created_at":"2026-07-07T14:52:34Z","created_by":"Sinity","metadata":"{}"}],"dependency_count":4,"dependent_count":0,"comment_count":0}