fix(sources): reconcile sidecar coverage with attachment evidence events - #3437
Merged
Merged
Conversation
Problem: master went red after #3419 (per-type sidecar coverage) merged alongside #3423 (attachment sidecar split). Seven attachment tests assert exact equality on parsed.session_events, and #3419 unconditionally appends a claude_parse_coverage event, so every list gained an unexpected member. Neither PR was wrong alone; their tests were never reconciled because the conflict resolution was verified with a narrower selection than the change warranted. The failure also exposed a real defect: the attachment branch appends a session_event but never set persisted_this_record, so coverage reported sidecar_seen={attachment: 1} with sidecar_persisted={} -- understating persistence for every attachment record. What changed: - attachment branch marks persisted_this_record when it emits an event - the seven exact-equality assertions exclude claude_parse_coverage, which is a parse-level report orthogonal to the attachment evidence each test is about Verification: devtools test -k 'sidecar or code_parser' -> 145 passed (was 7 failed / 138 passed on master). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sinity
added a commit
that referenced
this pull request
Aug 2, 2026
…ad model (#3543) ## Summary Ingests Claude Code's live plan-snapshot directory (`~/.claude/todos/*.json`) into the archive end-to-end: raw admission with provenance, a session-linked read model over every retained revision, and a registered plan-completion metric. `~/.claude/file-history/` is deferred as a scoped follow-up (see below). ## Problem polylogue-t0p: Claude Code writes several artifacts beyond the session JSONL transcript that answer questions the transcript alone cannot, and the harness prunes some of them on its own schedule -- unread state is eventually lost, not merely delayed. `~/.claude/todos/<session-id>[-agent-<agent-id>].json` holds the agent's current TODO/plan list (task content/status/priority/id), overwritten wholesale on every `TodoWrite` call. Nothing in the archive captured this before. ## Solution - **Admission**: a new `todo_snapshot` `OriginArtifactRule` in `polylogue/sources/origin_specs.py` classifies `todos/*.json` as a fact-tier Claude Code artifact, the same admission shape already used for `workflow_run_snapshot`/`agent_sidecar_meta` (PR #3419/#3437/#3448 sidecar-join precedent this bead pointed at). Fidelity is declared explicitly: preserved -- every item's content/status/priority/id and the agent's own list order; lost -- intermediate status transitions between two watcher-observed snapshots (each write is a full overwrite, no in-file timestamp), and anything pruned before observation. - **Parser**: `polylogue/sources/parsers/claude/todos.py` parses the bare JSON array and recovers `session_id`/`agent_id` from the filename (`<uuid>[-agent-<uuid>].json`, verified against the real on-disk shape under `~/.claude/todos/`). - **Watched root**: `~/.claude/todos` is a new configured/watched source (`polylogue/config.py`, `polylogue/paths/_roots.py`, `polylogue/sources/live/watcher.py` -- a second `WatchSource` sibling to `claude-code`, same pattern as the existing `codex-state` root), aliased to the `claude-code` provider (`polylogue/core/provider_identity.py`) so the existing `_admit_non_session_origin_artifacts` admission path and `classify_artifact_path` pick it up with zero bespoke wiring -- confirmed by tracing that gate's `Provider.from_string(source.name) is Provider.CLAUDE_CODE` filter. - **Read model**: `polylogue/insights/claude_todo_projection.py` is a storage-free, session-linked projection (same justification as `insights/run_projection.py`) over every retained `todo_snapshot` raw revision in `raw_sessions` (not just the current-pointer `raw_artifacts` row, which would collapse history to the latest snapshot) -- per-session plan state, latest completion rate, and per-item status-transition history across observed snapshots. - **Measure registration**: `plan_completion_rate` registered in `insights/measurement/registered_metrics.py`'s `DEFAULT_METRIC_REGISTRY` (rxdo.9.1 identity layer), the same bounded slice `session_cost_usd` already uses -- reachable via MCP `get(ref="metric:plan_completion_rate")`. polylogue-9l5.7's full statistics/composition registry (checked: still open, `MeasureSpec` unimplemented) is *not* duplicated here; this registers identity only, same limitation `session_cost_usd` already documents. ## Deferred: `~/.claude/file-history/` Investigated the real on-disk shape to save the follow-up lane research time: - Each session has a directory `~/.claude/file-history/<session-id>/<hash>@v<N>` holding **raw pre-edit file bytes with no wrapper** (no path, no timestamp inside the file itself). - The mapping from `<hash>@v<N>` back to a real file path lives **inside the session's own JSONL transcript**, in a `type: "file-history-snapshot"` record's `snapshot.trackedFileBackups: {<path>: {backupFileName, version, backupTime}}` map (plus `type: "file-history-delta"` records for incremental updates). - This is **already parsed today** as an evidence-only session event (`code_parser.py`'s `_sidecar_evidence_payload` for `file-history-snapshot`/`file-history-delta`, `#2qx.4`) -- it records the file list and backup pointers, but never acquires the actual snapshot bytes at `~/.claude/file-history/`. - Follow-up scope: join the already-parsed `trackedFileBackups` pointers to the sibling `~/.claude/file-history/<session-id>/` directory, content-address-acquire each snapshot as a blob keyed to (session, path, version), and apply the same privacy/redaction discipline `_acquire_attachment_blob` uses for attachments (these are literal pre-edit file contents from real projects). This is a distinct acquisition shape from `todo_snapshot` (cross-referencing two directories, not a single self-contained file) and was intentionally left out of this PR per the bead's own note that if full scope is too large for one PR, todos should land alone. Lower-priority artifacts the bead also names (`history.jsonl`, `history-summaries/`, `debug/`, `mcp-logs/`, `ide/` locks, `jobs/`, `ccusage`/stats caches) were not investigated this session; still open. ## Verification - `devtools test tests/unit/sources/test_origin_specs.py tests/unit/sources/test_parsers_claude_todos.py tests/unit/insights/test_claude_todo_projection.py tests/unit/core/test_config.py tests/unit/sources/test_live_watcher.py` -> 219 passed. (One pre-existing failure, `test_live_batch_processor_records_durable_attempt`, reproduces identically with this diff stashed on this same checkout -- unrelated codex-path assertion, not touched by this change.) - `mypy polylogue` -> `Success: no issues found in 1113 source files`. - `ruff check` / `ruff format --check` -> clean. - `devtools render all --check` -> OK (topology projection regenerated for the two new modules, `devtools render topology-projection`). - `devtools verify --quick` -> exit 0 (includes a cherry-picked, already-vetted-elsewhere ack commit for an unrelated pre-existing `classifier-fingerprints` gate failure from PR #3537, tracked separately at issue #3540 -- confirmed via `git stash` that this failure predates and is independent of this branch's changes). Anti-vacuity: removing the `todo_snapshot` `OriginArtifactRule` (or narrowing its path pattern) makes `artifact_rule_for_path`/`classify_artifact_path` return `None` for a real todos path, which `test_origin_spec_admits_todos_directory_artifact_as_fact_tier` and the end-to-end `test_todos_admitted_and_materialized_into_session_linked_plan_states` (via `parse_sources_archive`) both catch. Removing the `claude-code-todos` -> `claude-code` provider alias makes `_admit_non_session_origin_artifacts`'s `Provider.from_string(source.name) is Provider.CLAUDE_CODE` gate skip the source entirely, caught by `test_todos_source_name_resolves_to_claude_code_provider` and the same end-to-end test (would return zero plan states). Ref polylogue-t0p Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restores master to green. Two independently-correct PRs — #3419 (per-type sidecar coverage) and #3423 (attachment sidecar split) — produced a broken combination, and the merge that joined them was verified against a narrower test selection than the change warranted.
Problem
devtools test -k "sidecar or code_parser"on master: 7 failed, 138 passed.All seven are
test_attachment_*intests/unit/sources/test_claude_code_sidecar_evidence.py. They assert exact equality onparsed.session_events; #3419 unconditionally appends aclaude_parse_coverageevent, so every expected list gained an unexpected member:That payload also exposes a real defect rather than only a test-reconciliation problem:
sidecar_persistedis empty whilesidecar_seencounts the record. The attachment branch appends asession_eventbut never setspersisted_this_record, so per-type coverage understated persistence for every attachment record — the exact metric #3419 exists to report.Solution
polylogue/sources/parsers/claude/code_parser.py— the attachment branch setspersisted_this_record = Truewhen it emits an event, matching every sibling branch in the same dispatch.tests/unit/sources/test_claude_code_sidecar_evidence.py— the seven exact-equality assertions excludeclaude_parse_coverage. It is a parse-level report orthogonal to the attachment evidence each test is about; filtering keeps each test pinned to its own subject rather than loosening the assertion.Verification
devtools test tests/unit/sources/test_claude_code_sidecar_evidence.py→ 30 passed.Note
The conflict resolution that produced this was verified with
devtools test tests/unit/sources/test_assembly_claude_code_history.py(14 passed) — a file that does not exercise the attachment path. A cross-feature merge deserves the affected-area selection, not the file the conflict happened to sit in.