Skip to content

fix(sources): reconcile sidecar coverage with attachment evidence events - #3437

Merged
Sinity merged 1 commit into
masterfrom
fix/sidecar-coverage-attachment-integration
Jul 31, 2026
Merged

fix(sources): reconcile sidecar coverage with attachment evidence events#3437
Sinity merged 1 commit into
masterfrom
fix/sidecar-coverage-attachment-integration

Conversation

@Sinity

@Sinity Sinity commented Jul 31, 2026

Copy link
Copy Markdown
Owner

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_* in tests/unit/sources/test_claude_code_sidecar_evidence.py. They assert exact equality on parsed.session_events; #3419 unconditionally appends a claude_parse_coverage event, so every expected list gained an unexpected member:

Left contains one more item: ('claude_parse_coverage',
  {'sidecar_seen': {'attachment': 1}, 'sidecar_persisted': {}, ...})

That payload also exposes a real defect rather than only a test-reconciliation problem: sidecar_persisted is empty while sidecar_seen counts the record. The attachment branch appends a session_event but never sets persisted_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 sets persisted_this_record = True when 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 exclude claude_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 -k "sidecar or code_parser"
  before:  7 failed, 138 passed
  after:       145 passed

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.

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
Sinity merged commit ad069ae into master Jul 31, 2026
1 of 2 checks passed
@Sinity
Sinity deleted the fix/sidecar-coverage-attachment-integration branch July 31, 2026 08:53
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant