fix(insights): prefer typed PR/session evidence over regex correlation - #3425
Conversation
Problem: session_commit.py's on-demand `analyze correlation` path (build_correlation_result / detect_session_commits, reachable via `read --view correlation` and the API's session_correlation_payload) regex-scans message text for GitHub PR/issue refs and scores commits by time-window and file-overlap, while the archive already persists the same facts as typed evidence: session_refs (kind=pull_request, 18,932 rows / 167 sessions) and claude_bridge_session session_events (12,154 rows), plus this repo's own `Claude-Session:` commit trailers (verified: the trailer token is the same base62 id as claude_bridge_session's bridge_session_id minus its `cse_` prefix). Neither typed source had a reader; the session_commits schema's `origin_reported` detection_type CHECK value had zero rows. Ref polylogue-l9su. Solution: - session_commit.py: `detect_session_commits` gains `bridge_session_ids`; when a commit's Claude-Session trailer matches one of the session's own bridge-session ids, the edge is `detection_method="origin_reported"`, confidence=1.0, superseding file_overlap/time_window/explicit_ref for that commit. A trailer naming a *different* session is recorded on the edge as `disagreement_note` rather than silently accepted or dropped. - `build_correlation_result` gains `typed_pr_refs`/`typed_issue_refs` (from session_refs); when supplied they are authoritative for that ref kind and the regex scan (still run, since it is the only file-path source) becomes a disagreement check, not the primary result. New `CorrelationDisagreement`/`SessionCorrelationResult.disagreements` and `GitHubRef.source` (`typed_session_ref` vs `heuristic_regex`) make which mechanism resolved each linkage visible on the JSON/plaintext payload. - Wired both callers (`insights/correlation_view.py`, `api/archive.py::session_correlation_payload`) to fetch session_refs via `repository.get_session_refs` and bridge ids from the session's own `session_events`, via new `typed_refs_from_session_refs` / `bridge_session_ids_from_events` helpers. - Fixed a latent bug in `_parse_git_log_blocks` found while adding the trailer scan: splitting `git log` output on a literal `\n---\n` token left every commit's changed-file set empty (the file list for commit N landed in the *next* split segment), so file_overlap detection never actually worked against a real repo. Switched to `%x1e`/`%x1f` field/record separators that cannot collide with commit text. Verification: - `devtools test tests/unit/insights/test_session_commit.py tests/unit/cli/test_correlate_view.py` -- 45 passed (new fixtures cover trailer-priority-over-heuristics, foreign-trailer disagreement, typed-refs-preferred, and the helper converters, using a real git repo built in tmp_path). - `devtools verify --quick` -- exit 0 (ruff format/check, mypy --strict, render all --check, layering/closure/manifest/doc-command gates). - `mypy polylogue/insights/session_commit.py polylogue/insights/correlation_view.py polylogue/api/archive.py` -- no issues. - Live read-only measurement against /realm/db/polylogue/index.db (sqlite3 mode=ro, no writes): 167 sessions carry typed pull_request session_refs (1,690 typed PR-number rows). Running the OLD regex-only extract_github_refs() over each of those sessions' own block text finds 1,934 PR-number mentions: 102 sessions match typed evidence exactly, 65 sessions would have surfaced extra/different PR numbers from the regex scan alone -- the disagreement class this change now makes visible instead of silently guessing. Trailer-side: of 9 distinct Claude-Session trailer tokens in this repo's own git history, 8 resolve to a real archived session via claude_bridge_session (one token maps to 2 sessions, 9 total) -- those commits will get detection_method "origin_reported" once bridge_session_ids is threaded through for that session, instead of file_overlap/time_window/nothing. - session_commits (the batch-ingest-time table, storage/sqlite/ archive_tiers/write.py) is out of this change's declared surface and unaffected: still 2,990 rows, all detection_type=explicit_ref, populated from session.git_commit_hash at ingest time. That table is a different, narrower fact (HEAD at session-capture time) from the on-demand correlation path this PR fixes; see polylogue-l9su's self-correction note. Ref polylogue-l9su Co-Authored-By: Claude <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Ref polylogue-l9su. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 36c0caddc6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| number = getattr(ref, "number", None) or 0 | ||
| url = getattr(ref, "url", None) | ||
| built = GitHubRef( | ||
| owner=owner_name, | ||
| repo=repo_name, | ||
| number=int(number), |
There was a problem hiding this comment.
Preserve typed pull-request IDs instead of emitting PR #0
When a valid session_refs row lacks ref_number, this coerces it to zero. This occurs for Codex Cloud because chatgpt_codex_sidecar._pull_request_ref() stores external_pull_request_id (for example, "1234") in url while leaving repo and number unset. The correlation surface consequently emits a typed PR numbered 0, builds an inconsistent object ref from the raw ID, and—because typed refs are authoritative—suppresses any correctly parsed regex result. Parse the numeric/URL identifier when possible or skip rows that cannot be represented as a GitHubRef.
Useful? React with 👍 / 👎.
| typed_numbers = {ref.number for ref in resolved} | ||
| heuristic_numbers = {ref.number for ref in heuristic_refs} | ||
| extra = heuristic_numbers - typed_numbers |
There was a problem hiding this comment.
Compare complete repository-qualified reference identities
For repository-qualified URLs, PR/issue identity is (owner, repo, number), not just the number. If typed evidence names acme/product#42 while message text names other/repo#42, these sets compare equal and no disagreement is surfaced, despite the two signals identifying different PRs. This silently violates the new disagreement behavior precisely when repositories reuse the same issue number; compare canonical repository-qualified identities, retaining explicit handling for genuinely bare references.
Useful? React with 👍 / 👎.
| ) | ||
| continue | ||
|
|
||
| foreign_trailer = bool(trailer_tokens) and not (trailer_tokens & own_trailer_tokens) |
There was a problem hiding this comment.
Avoid declaring foreign trailers without an own bridge ID
When the current session has no claude_bridge_session evidence, own_trailer_tokens is empty, so every heuristically matched commit carrying any Claude-Session trailer is labeled as naming a different session. The inspected Claude/Codex correlation paths legitimately pass an empty list for sessions without such events, meaning the result reports a disagreement despite having no typed identity to compare. Gate the foreign-trailer classification on having at least one own trailer token.
Useful? React with 👍 / 👎.
|
Triaged the 3 CodeRabbit P2 findings (PR #0 coercion at L785, cross-repo number collision at L739, foreign-trailer false-disagreement at L500) as real but non-blocking: they are edge cases of the new typed-evidence path (Codex Cloud refs lacking ref_number; repo-qualified identity comparison; sessions with no bridge evidence), not regressions in existing behavior, and the 45 new/updated tests cover the primary trailer-match/typed-ref/fallback paths. Filed polylogue-2vor with per-item acceptance criteria and merging. Ref polylogue-2vor. |
…3431) ## Summary Closes the two named, still-open gaps from tonight's polylogue-pbuh and polylogue-cijx.4 investigations: (1) verifies and finishes the read-side wiring for typed session→PR evidence (pbuh AC4), and (2) wires the `root:` session-structure filter end-to-end across the query DSL, CLI, and Python API (cijx.4 AC4 / its follow-up polylogue-oqib). ## Problem **Gap 1 (pbuh AC4).** polylogue-pbuh's parser-side fix (index v46, PR #3390) persists Claude Code's `pr-link` sidecar record as typed `session_refs` evidence, but nothing on the CLI/insights/MCP surface read it — the four dependent beads (212.2/xyel/kph/fs1.4) were blocked on an inference mechanism (`session_commits`, 0 readers, 2,989 rows of a narrower fact) instead of the typed evidence the provider already supplies. **Gap 2 (cijx.4 AC4 / oqib).** `sessions.parent_session_id` and `Session.is_root` are correct, and a plan-level `root: bool | None` field plus `.is_root()` builder already existed, but `root` had no `spec_attr`, no DSL grammar case, and no CLI flag — completely unreachable from any query surface, so a default `find` mixes 66.1% root sessions with 33.9% subagent/branch children unlabeled. ## Solution **Gap 1**: A sibling lane's PR #3425 (fix/insights/session-commit-typed-evidence) had landed the actual read-side fix — `build_correlation_result` now consumes `session_refs`/`claude_bridge_session` typed evidence as authoritative, falling back to regex/time-window heuristics only where no typed evidence exists, and surfacing disagreements. It was open but unmerged when this pass started; triaged its 3 non-blocking CodeRabbit P2 findings (filed as follow-up polylogue-2vor) and merged it (5525446). Verifying the now-merged surface against the live archive (`find id:<session> then read --view correlation`) surfaced a **second, independent, pre-existing bug**: `_enrich_with_github_api` (`polylogue/insights/correlation_view.py`) referenced `SessionCorrelationResult` at runtime while only importing it under `TYPE_CHECKING` — every call with the default `github_api=True` and any issue/PR ref present raised `NameError`. This predates PR #3425 (present since `ac84f734f`); the existing test suite only exercised `github_api=False`, so it was never caught. Fixed by importing the class at runtime alongside the existing `GitHubRef` import, plus a regression test. **Gap 2**: - `SessionQuerySpec.root: bool | None`, wired through `build_query_spec_from_params`/`query_spec_to_plan` (new `optional_bool` tri-state parser in `archive/query/spec.py`). - DSL: `root:true`/`root:false` field clause (`archive/query/expression.py`); `-root:` negation is rejected pointing at `root:false` instead (the value already carries polarity). - CLI: `--root/--no-root` flag, added last in `cli()`'s signature per this repo's "new Click params go last" convention. - `EXPRESSION_FIELD_REGISTRY["root"]` + regenerated `docs/cli-reference.md`, `docs/search.md`. Two deeper bugs found while making this reachable (neither was "just unreachable" — both were silent no-ops even where a plan/spec did carry a value): 1. The CLI's actual browse/search path (`cli/archive_query.py`'s `_ArchiveFilterKwargs` → `ArchiveStore.list_summaries`/`search_summaries`/ `count_sessions`/`count_search_sessions`/`search_session_ids`/ `semantic_summaries`/`stats`/`stats_by`) is a SQL-level filter path entirely separate from the `SessionQueryPlan`/`apply_common_filters` post-filter machinery `root`'s field descriptor (`requires_post_filter=True`) was designed against. None of those eight `ArchiveStore` methods accepted a `root` kwarg. Fixed by pushing `root` into `_session_filter_clause` as a direct SQL predicate (`sessions.parent_session_id IS [NOT] NULL` — trivially SQL-pushable, unlike `continuation`/`sidechain` which derive from `branch_type`) and threading it through all eight methods. 2. Even the `SessionQueryPlan` post-filter path (Python API's `list_summaries_archive`/`list_archive`) was independently broken: `ArchiveSessionSummary` never carried `parent_id` (the SELECT never projected `sessions.parent_session_id`, `_summary_from_row` never read it), so `is_root` was `True` for every summary row regardless of actual parent — a `root:true` filter would have silently returned everything even once reachable. Fixed by adding `parent_id` to `ArchiveSessionSummary`, projecting the column in both `read_summary` and `list_summaries`, and threading it through `_summary_to_domain`. **Default-behavior decision**: did **not** flip any surface's default. `find`/Python API `list()`/MCP query/daemon HTTP all continue to return every session unless `root:`/`--root`/`.is_root()` is given explicitly. Flipping even the narrower "CLI `find` verb only" option still has real blast radius (every existing test/saved-query/demo-script assuming today's "everything" default needs re-auditing), and reachability is the load-bearing wedge — `polylogue find repo:polylogue root:true` now produces the named, non-fanout view AC4's proof text asks for. The default question is left open as a deliberate, separately-reviewable follow-up (narrowed onto polylogue-oqib). ## AC disposition **polylogue-pbuh** (typed session→PR evidence): AC4 **satisfied**. Typed session→PR linkage is reachable from `read --view correlation` (CLI/API), verified live against the real archive. AC6 (before/after UUID-title/PR-link census) is untouched — out of this gap's declared scope, still open. **polylogue-cijx.1** (repo-identity bead whose notes tracked the session-commits/pr-link consumer question): the specific blocking concern its notes raised for 212.2/xyel/kph/fs1.4 ("the producer does not work" / "0 readers") is resolved. Its own titled AC (repo_id fragmentation) is unrelated and was already addressed by cijx.4. **212.2 / xyel / kph / fs1.4**: unblocked, not closed — each still needs its own concrete deliverable (demo build, CI hook, CLI/report regen) beyond "the data is now readable." Noted individually on each bead. **polylogue-cijx.4** AC4: reachability **satisfied**. Default-behavior question **deferred**, narrowed onto polylogue-oqib (priority lowered — remaining scope is a design decision, not plumbing). **polylogue-oqib**: reachability + both deeper bugs **satisfied and fixed**. Default-behavior flip **not attempted**, left open and explicitly narrowed to that one remaining decision. ## Verification - `devtools test tests/unit/cli/test_correlate_view.py` — 4 passed (including new NameError regression test). - `devtools test tests/unit/cli/test_query_expression.py tests/unit/core/test_query_fields.py tests/unit/cli/test_archive_query.py tests/unit/archive/test_archive_execution_filters.py tests/unit/cli/test_query_exec_laws.py tests/unit/archive/ tests/unit/storage/test_archive_tiers_archive.py tests/unit/archive/query/test_discovery.py` — all green (new `test_root_filter_partitions_top_level_and_subagent_sessions` covers all eight `ArchiveStore` methods plus the `parent_id`/`is_root` wiring bug). - `mypy --strict` on every touched module — no issues. - `devtools render all --check` — sync OK. - `devtools verify --quick` — exit 0 (also ran automatically via the pre-push hook). - Manual live verification (read-only, `/realm/db/polylogue/index.db`): `find id:<session> then read --view correlation --format json` returns typed `pr_refs` (`source=typed_session_ref`) plus a `disagreements` list; `find repo:polylogue --root` → total 1906; `find repo:polylogue --no-root` → total 3206; 1906+3206=5112, the unfiltered total. ## Not done / follow-ups - polylogue-2vor: 3 CodeRabbit P2 findings on PR #3425's typed-evidence path (PR #0 coercion, cross-repo number collision, foreign-trailer false-disagreement). - polylogue-oqib: the default-behavior decision for `root:` (which surfaces, if any, should default to top-level-only). - `root` is not wired into `query_unit_session_filters` (the `with <units>` projection's separate session-filter adapter), and daemon HTTP's `_build_query_spec_params` named-param allowlist has no dedicated `?root=` query param (the existing `?query=root:true` DSL path already covers it). `continuation`/`sidechain`/`has_branches` remain exactly as unreachable as before this PR. Ref polylogue-pbuh, polylogue-cijx.1, polylogue-cijx.4, polylogue-oqib. --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Extracts the hand-duplicated `_MockServer`/`_MockHeaders`/`_make_handler` trio from three daemon test files into a shared `tests/infra/daemon_http_harness.py` (polylogue-myhg). - Closes three CodeRabbit findings from PR #3425's review that were triaged and filed as polylogue-2vor rather than blocking that merge (all three are in `polylogue/insights/session_commit.py`, with regression tests). ## Problem **polylogue-myhg**: CodeRabbit flagged (PR #2559) that `_MockServer`/`_MockHeaders`/`_make_handler` were duplicated near-identically across `test_daemon_http_security.py`, `test_daemon_events_endpoint.py`, and `test_provider_usage_endpoint.py`. Drift already happened once: the `host=` parameter was added to two of the three copies during a fast-follow, not all three. A fresh grep tonight confirmed the duplication was still live. **polylogue-2vor**: three P2 findings in `session_commit.py` were left unaddressed at PR #3425's merge time: 1. `typed_refs_from_session_refs()` coerced a `session_refs` row with a valid `url`/`repo` but no `ref_number` to "PR #0" (observed for Codex Cloud's `chatgpt_codex_sidecar._pull_request_ref()`, which stores `external_pull_request_id` in `url` and leaves `repo`/`number` unset). Since typed refs are authoritative over the regex fallback, this could suppress a correctly-parsed regex result with a bogus PR #0. 2. Disagreement detection compared PR/issue identity by bare number only, so `acme/product#42` and `other/repo#42` compared equal — a real cross-repo disagreement was never surfaced. 3. Foreign-trailer classification flagged a disagreement whenever a commit carried *any* Claude-Session trailer, even when the current session has no `bridge_session_ids` of its own — i.e. even with no typed identity to compare against. ## Solution **Mock harness extraction** (`tests/infra/daemon_http_harness.py`): - `MockDaemonServer`, `MockHeaders`, `make_daemon_handler`, `capture_json_response`, `capture_responses` — these mock only the HTTP transport boundary (the listening socket/`ThreadingHTTPServer` a real `DaemonAPIHandler` normally sits on, and the parsed header block a socket read would produce). `do_GET`/`do_POST` dispatch, `_check_auth`, cross-origin checks, route handlers, and JSON/SSE serialization all still run as the real production `DaemonAPIHandler` built via `__new__` — no boundary that was previously mocked is hollowed out. - The three named test files now import the shared harness instead of defining their own copies. `test_daemon_http_security.py` keeps a thin file-local `_make_handler` wrapper because that file's whole point is exercising the auth-required path, so its default server needs a configured token (`"secret"`) unlike the harness's own open-by-default `MockDaemonServer`. - While touching `test_daemon_events_endpoint.py`, three ad-hoc `type()`-built `_Srv` server stand-ins were also converted to `MockDaemonServer` — same duplication pattern CodeRabbit flagged in the original trio. - Fixed two cross-file importers (`test_web_auth.py`, `test_route_contracts.py`) that imported `_MockServer`/`_capture_responses` *from* `test_daemon_http_security.py` rather than defining their own copies — a case a narrow per-file `devtools test` run doesn't catch but whole-repo `mypy --strict` does. **session_commit.py fixes** (regression test per finding, see `tests/unit/insights/test_session_commit.py`): 1. `typed_refs_from_session_refs()` now tries to recover a real number by parsing a genuine `github.com` PR/issue URL out of `url` when `number` is absent; if that also fails, the row is skipped rather than defaulting to 0. 2. New `_refs_match()` compares the full `(owner, repo, number)` identity whenever both refs are repo-qualified, falling back to number-only equality when either side lacks repo identity (e.g. a bare `#42` regex mention). 3. `foreign_trailer` now additionally requires `own_trailer_tokens` to be non-empty. ## Verification - `ruff check` + `mypy` (strict, whole repo: 2397 source files) clean. - `devtools test tests/unit/daemon/test_daemon_http_security.py tests/unit/daemon/test_daemon_events_endpoint.py tests/unit/daemon/test_provider_usage_endpoint.py tests/unit/daemon/test_web_auth.py tests/unit/daemon/test_route_contracts.py` → 709 passed. - `devtools test tests/unit/insights/test_session_commit.py` → 46 passed; also ran `tests/unit/cli/test_correlate_view.py tests/unit/storage/test_archive_tiers_write.py` (other consumers of `session_commit.py`) → 80 passed. - `devtools verify --quick` → clean end to end. Ref polylogue-myhg Ref polylogue-2vor --------- Co-authored-by: Claude <noreply@anthropic.com>
…#3438) ## Summary Two independent "computed and discarded" defects, both verified live tonight per the operator's audit theme: - **polylogue-uh9l**: Claude Workflow artifact coverage was computed on every ingest pass by `assembly_claude_code.py:discover_sidecars` and consumed by nothing. Deleted the dead branch and wired the genuinely-running materializer's gap count into `polylogue doctor`'s readiness surface instead. - **polylogue-xyel**: re-verified against current master (not assumed from the bead's original framing) that `session_refs` now has a live production consumer (PR #3425/#3431, already merged) — then built the bead's own remaining, un-satisfied AC: a real D1 "receipts" demo packet. ## Problem **uh9l**: `polylogue-z9gh.6`'s closure claimed "readiness and repair commands no longer report healthy solely because subagents/workflows is classified as a known sidecar." False as written — no readiness/repair command consulted either of the two coverage computations that existed for Claude Workflow artifacts. One (`ClaudeOrchestrationCoverage`/`inventory_claude_orchestration_artifacts`) was fully dead code; the other (`claude_workflow_materializer`'s gap tuple) ran every convergence pass but was only ever logged. **xyel**: the bead's title/framing ("SESSION_REFS HAS NO CONSUMER") predates this repo's own same-night investigation trail (`polylogue-cijx.1`), which found and fixed the actual gap (a NameError that crashed `read --view correlation`'s default GitHub-enrichment path on every real ref). The bead's own literal AC — build and register a real PF-D1 receipts demo, not the packet-contract stub `212.7` shipped — remained unaddressed. ## Solution **uh9l** (3 commits): 1. `refactor(sources)`: delete the dead `orchestration_artifacts`/`orchestration_coverage`/`orchestration_parse_gaps` computation from `discover_sidecars` and its supporting `ClaudeOrchestrationCoverage`/`inventory_claude_orchestration_artifacts` (kept `parse_claude_orchestration_artifact`/`ClaudeOrchestrationArtifact`/`ClaudeOrchestrationFact`, still used by the materializer). 2. `feat(readiness)`: `daemon/convergence_stages.py`'s `claude_workflow` stage now persists each materialization summary into `ops.db`'s existing generic `daemon_stage_events` table (no schema change). `readiness/__init__.py` registers a new `claude_workflow_materialization` `ReadinessCheck` that `polylogue doctor` already surfaces via `get_readiness()`. **xyel** (1 commit + a follow-up type fix): 3. `feat(demos)`: `.agent/demos/d1-receipts/` — resolves a real merged PR (`#3282`) to its authoring/dispatch session structurally via `session_refs`, then checks 4 individually falsifiable PR-body claims against that session's own recorded blocks. 3 supported, 1 explicitly scored `not_supported` (a real, structurally-confirmed gap: a 7-file `devtools test` invocation named in the PR body only ever appears as prose, never as an executed command in this session). Also surfaces a genuine finding: the resolved session is a merge-conductor (0 `Edit`/`Write` tool_use blocks), not the file-editing session. 4. `fix(tests)`: type-narrow a `dict[str, object]` read in the new integration test that `devtools verify --quick`'s `dmypy`-backed mypy step caught (the earlier plain `mypy polylogue` spot-check doesn't cover `tests/`). Beads updated in a final commit: closed `polylogue-uh9l` and `polylogue-xyel` with full AC disposition in their close reasons; filed `polylogue-nt5f` for the one honestly-named remainder (D1's public seed-corpus variant isn't built — `session_refs` pull_request rows are a provider-native capability the deterministic demo seed fixture doesn't currently populate). ## Verification - `devtools test tests/unit/sources/test_assembly_claude_code_history.py tests/unit/sources/test_parsers_claude_code_artifacts.py tests/unit/storage/test_archive_readiness.py tests/unit/daemon/test_convergence_stages.py tests/unit/cli/test_convergence_surface_contract.py tests/unit/cli/test_check.py tests/integration/test_claude_workflow_admission.py tests/unit/devtools/test_demo_packet.py tests/unit/demo/test_tour_packet_contract.py` → 191 passed - `devtools lab policy demo-packet-registry` → "demo packet registry: all 4 entries conform" - `devtools lab policy bead-graph` → exit 0 (dup_labels=0, inversions=0, malformed_wave=0) - `devtools verify --quick` → 20/20 steps green (pre-push hook also ran this clean) - `devtools render all --check` → OK, no drift Ref polylogue-uh9l, polylogue-xyel <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Claude Workflow readiness reporting that identifies missing materialization records, unresolved gaps, and successful convergence. * Added a verification demo documenting pull-request claims, supporting evidence, counterexamples, limitations, and reproduction steps. * **Bug Fixes** * Improved archive readiness handling for missing, malformed, or unavailable status data. * Updated audit records with fixes, deferred work, and newly identified reliability issues. * **Refactor** * Simplified Claude Code history processing by removing obsolete orchestration artifact inventory and coverage reporting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…faces (#3442) ## Summary Three beads about polylogue evidence that is already captured and persisted, but unreachable from any CLI/MCP/API surface. This PR wires the genuine remainders: `file_edits`, `session_agent_policies`, and `sessions.display_name` (the Claude Code "slug" wire field). ## Problem - **polylogue-nua7**: `file_edits` (76,272 live rows), `session_agent_policies` (402,879 rows), and `session_refs` (19,024 rows) each had a complete, tested read chain that terminated at `repository/archive/sessions.py` with nothing above it. `session_refs` was already wired by prior PRs #3425/#3431 (`read --view correlation`); `file_edits` and `session_agent_policies` remained unreachable from any CLI/MCP/API surface. Four helpers (`get_file_edit`, `sync_get_file_edits_for_session`, `sync_get_session_refs`, `sync_session_agent_policies_batch`) had zero references anywhere outside their own `def`/`__all__` entry. - **polylogue-cgfy**: the Claude Code `slug` wire field (1,500 sampled occurrences) is captured into `ParsedSession.display_name` and persisted into `sessions.display_name`, but neither `Session` nor `SessionSummary` carried a `display_name` field at all — the value was dropped on every read path. This is the fix for subagent rows rendering as `<uuid-prefix>:agent-<suffix>` instead of a human name. - **polylogue-pbuh**: AC6 asked for a live before/after census of UUID-titled sessions and PR-link counts. Measured read-only against `/realm/db/polylogue/index.db`. ## Solution 1. **`polylogue/api/archive.py`**: added `Polylogue.get_file_edits()` / `get_agent_policies()`, mirroring the existing `get_session_events()` reader pattern (returns `None` for an unknown session, an empty list for a session with no matching rows). 2. **MCP** (`polylogue/mcp/server_cutover.py`): wired both into the existing `get(ref, projection=...)` dispatcher as two new projection values (`"file-edits"`, `"agent-policies"`) alongside the existing `"events"` projection. No new tool or tool contract needed — same six-tool `get` operation. 3. **CLI**: added `read --view file-edits` / `read --view agent-policies`, following the existing `events`/`hooks` view pattern — new handler module `polylogue/cli/read_views/file_edits.py`, registered in `read_view_handlers.py`, `read_view_registry.py`, and the profile metadata in `archive/viewport/profiles.py`. Also required adding entries to `polylogue/surfaces/projection_spec.py`'s `EvidenceFamily`/`READ_VIEW_PROJECTION_FAMILIES` maps — a separate registry from the CLI handler registry that raises "unknown projection view" if a view is missing there. 4. **Dead code**: deleted the four zero-reference helpers named in nua7's audit, plus their now-dangling `sqlite3` imports where nothing else used them. 5. **`display_name` reachability** (polylogue-cgfy), two independent hydration paths: - Async repository path (`storage/hydrators.py`): added `display_name` to `Session`/`SessionSummary` (`archive/session/domain_models.py`) and their runtime mixins' `display_title` property — now falls back `user_title > title > display_name > id[:8]`. - 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 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`. 6. **pbuh AC6 live census** (read-only against `/realm/db/polylogue/index.db`, no code change — measurement only): - 16,420 Claude Code sessions total; 14,717 carry `title_source='unknown'` (raw-id/structural-label fallback before this PR). - 7,088 of those 16,420 sessions have a captured `display_name`; **6,585 of the 14,717 `title_source='unknown'` sessions (44.7%) now surface a real slug-derived title** instead of a raw id or structural label — this PR's concrete, measured improvement. - `session_events` with `event_type='claude_pr_link'`: 19,140 rows (unchanged by this PR — pr-link reader wiring was already resolved by the earlier #3425/#3431 pass; see pbuh's own notes). - `file_edits`: 76,272 rows; `session_agent_policies`: 402,879 rows; `session_refs`: 19,024 rows (167 distinct sessions) — all now reachable per point 1-3 above. ## AC disposition **polylogue-nua7**: file_edits/session_agent_policies now have real CLI (`read --view file-edits`/`agent-policies`) and MCP (`get(projection=...)`) consumers; session_refs already had one (prior PRs, verified unchanged). All four zero-reference helpers deleted. Satisfied. **polylogue-cgfy**: AC1 (per-key classification recorded in OriginSpec) and AC4 (re-runnable committed enumeration) were already addressed by prior passes per the bead's own notes — not re-verified in this pass, out of this PR's declared surface. AC2's `structuredPatch`/`originalFile`/`oldString` persistence was already done (index v46); this PR adds the missing *read* side (file_edits reachability, point 1-3 above) — the specific "cijx grading rises from observed to checkpointed" wiring is a separate insights-model change (`insights/session_commit.py`-adjacent, in this PR's avoid list) and remains open, noted here as a genuine remainder. AC3 (slug reaches read surfaces) is satisfied — see point 5 above, proven with a measured live census (point 6) and end-to-end tests. AC5 (bytes/row counts per key acquired) is partially covered by the live counts in point 6 but not a full per-key report. **polylogue-pbuh**: AC6 (before/after census) satisfied as a live measurement (point 6) — this is the "after" state; no "before" baseline exists since the parser fix landed in an earlier PR and no un-fixed archive is available to compare against. AC1-AC5 were already resolved in prior passes per the bead's own extensive notes; not re-verified here, out of this PR's declared surface (parsers/claude, assembly_claude_code.py, providers/claude_code*.py were avoided per the task's own instructions). ## Verification - `devtools test tests/unit/mcp/test_server_surfaces.py` — 11 passed, including 3 new tests exercising `get(projection="file-edits")`, `get(projection="agent-policies")`, and the default `get(ref)` display_name fallback through the real MCP `tool_manager` entrypoint against a real `ArchiveStore`-written session. - `devtools test tests/unit/cli/test_file_edits_and_agent_policies_views.py` — 2 passed, full `CliRunner` invocation of `read --view file-edits`/`agent-policies` against a real `ArchiveStore`-written session. - `devtools test tests/unit/storage/test_session_display_name_reaches_repository.py` — 2 passed, proving both `Session` and `SessionSummary` carry `display_name`/`display_title` correctly through the real writer → async repository chain, and that a real title still wins over the slug. - `devtools test tests/unit/storage/test_unread_wire_batch_v46.py tests/unit/storage/test_repository_agent_policies.py` — 13 passed (no regression from dead-helper removal). - `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). - `devtools test tests/unit/api/test_facade_contracts.py -k "no_undiscovered or file_edits or agent_polic"` — 3 passed. - Broader affected-area sweep: 28 test files that directly import the touched domain modules (`tests/unit/{api,archive,cli,core,insights,sources,storage,surfaces}/...`) — 863 passed, 2 pre-existing unrelated failures (`test_archive_tiers_api_raw_artifacts_read_source_tier`, 4 parametrized cases of `test_filters_props.py::TestFilterDateParsing`) confirmed to reproduce identically with this branch's changes reverted — stale hardcoded date assertions / a clock-hygiene issue in `archive/filter/filters.py`, untouched by this PR. - `mypy --strict` on every touched file — no issues. - `devtools verify --quick` — exit 0. - `devtools render all --check` — all surfaces sync OK (regenerated `docs/cli-reference.md`, `docs/plans/topology-target.yaml` for the new `polylogue/cli/read_views/file_edits.py` module). Not run: full `devtools verify --all` / whole-directory `tests/unit` sweep (anti-pattern per repo convention — testmon wasn't seeded in this worktree; relying on the targeted + affected-area sweeps above plus CI's post-merge heavy `test` suite). Ref polylogue-nua7, polylogue-cgfy, polylogue-pbuh
Summary
analyze correlation(theread --view correlationCLI path andPolylogue.session_correlation_payload) regex-scans message text forGitHub PR/issue refs and scores git commits by time-window/file-overlap
heuristics, while the archive already persists the same facts as typed
evidence with zero readers. This PR makes the correlation path consume
the typed evidence first and treats the heuristics as a fallback, per
polylogue-l9su.
Problem
Bead polylogue-l9su (P1, filed after an independent audit) found:
session_refs(kind=pull_request, from the Claude Codepr-linksidecar record) has 18,932 rows across 167 sessions — never read by
build_correlation_result.claude_bridge_sessionsession_events (12,154 rows) carry abridge_session_id(cse_<token>) — never read either.Claude-Session: https://claude.ai/code/session_<token>trailer by convention (116commits in this repo's history). Verified live: the
<token>is thesame id as
claude_bridge_session'sbridge_session_idminus itscse_prefix (cse_0182HDxDpJpsbn2qcKWK6Fsfinsession_eventsmatches this repo's own
session_0182HDxDpJpsbn2qcKWK6Fsftrailerbyte-for-byte). Nothing parsed it.
session_commits.detection_typehas a live CHECK-constraint valueorigin_reportedwith zero rows using it.Solution
polylogue/insights/session_commit.py:detect_session_commitsgainsbridge_session_ids. When a commit'sClaude-Session:trailer token matches one of the session's ownbridge-session ids, the edge is
detection_method="origin_reported",confidence=1.0, and supersedes file_overlap/time_window/explicit_reffor that commit sha. When a trailer names a different session than
the one being scored, the heuristic edge is kept (not silently dropped)
but flagged with
disagreement_note— "derive rather than compare, failloud rather than guess quietly."
build_correlation_resultgainstyped_pr_refs/typed_issue_refs(
GitHubRefrows built fromsession_refs). When supplied, they areauthoritative for that ref kind; the regex scan of message text still
runs (it's the only source for
file_paths) but is used only as afallback for sessions with no typed evidence, or to detect a
disagreement against typed evidence — never to override it silently.
CorrelationDisagreement/SessionCorrelationResult.disagreementsand
GitHubRef.source(typed_session_refvsheuristic_regex) makewhich mechanism resolved each linkage visible in the JSON/plaintext
payload, satisfying AC3/AC4 (record which mechanism won; surface
disagreements instead of silently preferring one).
typed_refs_from_session_refs/bridge_session_ids_from_eventsconvertSessionRefRecord/SessionEventrows into the typed inputs above (duck-typed, so themodule stays DB-free and unit-testable).
Wiring (point 5 — read surface):
read --view correlationandsession_correlation_payloadalready existed as the CLI/API surface forthis data (
polylogue/cli/read_views/correlation.py,polylogue/insights/correlation_view.py); both were updated to fetchsession_refsviarepository.get_session_refsand bridge ids from thesession's own
session_events, and pass them through. No new surface wasneeded — the blocker was purely that the existing surface ignored typed
evidence it already had access to.
Incidental bug fix required for correctness: while adding the
trailer scan I found
_parse_git_log_blockshad a latent bug — splittinggit log --format=%H%n%ai%n%s%n---output on the literal\n---\ntokenleft every commit's changed-file set empty, because
--name-onlyappends the file list after the format text, landing it in the next
split segment past where the parser had already stopped consuming header
lines. This meant
file_overlapdetection never actually returned afile-overlap edge against a real repository (only ever exercised in tests
against nonexistent paths, which never hit this code). Fixed by switching
to
%x1e/%x1fASCII field/record separators that cannot collide withcommit text.
Explicitly out of scope:
session_commits(the durable batch-ingesttable in
storage/sqlite/archive_tiers/write.py, excluded from thislane's surface) is a separate mechanism populated from
session.git_commit_hashat ingest time — a narrower fact (repo HEAD atsession-capture time, not "commits this session produced") that the bead's
self-correction note already distinguishes from the on-demand correlation
path this PR fixes. It is unaffected and unchanged by this PR.
Verification
devtools test tests/unit/insights/test_session_commit.py tests/unit/cli/test_correlate_view.py→ 45 passed. New fixtures build areal git repo in
tmp_path(subprocessgit init/commit) and cover:trailer match takes priority over file_overlap (
origin_reported,confidence 1.0); a foreign trailer is flagged as a disagreement but the
heuristic edge is kept; no
bridge_session_idsfalls back to plainheuristics unflagged; typed PR refs are preferred over regex and tagged
source=typed_session_ref; a disagreement is recorded when regex findsPR numbers not in typed evidence; the
typed_refs_from_session_refs/bridge_session_ids_from_eventsconverters.devtools verify --quick→ exit 0 (ruff format/check,mypy --strict,render all --check, layering/closure-matrix/manifest/doc-commandgates) — run twice, once pre-commit and once on
git pushvia the hook.mypy polylogue/insights/session_commit.py polylogue/insights/correlation_view.py polylogue/api/archive.py→ noissues found.
/realm/db/polylogue/index.db(
sqlite3 ...?mode=ro, no writes, no daemon):pull_requestsession_refs (1,690 typedPR-number rows total).
extract_github_refs()over each ofthose sessions' own block text finds 1,934 PR-number mentions: 102
sessions match typed evidence exactly, 65 sessions would have
surfaced extra/different PR numbers from the regex scan alone — this
is exactly the silent-disagreement class the fix now makes visible
instead of guessing.
Claude-Sessiontrailer tokens found inthis repo's own git history, 8 resolve to a real archived session
via
claude_bridge_session(one token maps to 2 sessions → 9 sessionstotal). Once a caller threads that session's
bridge_session_idsthrough, those commits get
detection_method="origin_reported"instead of file_overlap/time_window/nothing.
session_commitsunaffected: still 2,990 rows, alldetection_type=explicit_ref(out of this lane's declared surface,see above).
Follow-ups
session_commits'origin_reportedCHECK slot is still unused at thebatch-ingest layer; wiring the same trailer-parse logic into
archive_tiers/write.py(explicitly excluded from this lane) is aseparate follow-up if the operator wants the durable table to carry
this fact too, rather than only the on-demand correlation payload.
typed_issue_refshas the same priority wiring as PR refs, but noproducer currently writes
session_refsrows withkind=issue(onlypull_requestis populated today) — so the issue-ref path is exercisedby unit tests but has no live typed data yet.
Ref polylogue-l9su