Skip to content

convergence_payload_matches_filters has a dead evidence-ref branch: it reads a message_id key that evidence_ref_payload never emits, so message-scoped convergence filtering silently ignores evidence-event message ids #302

Description

@erskingardner

Summary

convergence_payload_matches_filters is the post-serialization payload_filter used to apply a ?message_id= filter to convergence runs (there is no SQL-level message_id filter in filtered_convergence_runs). One of its match branches reads a key that the payload it inspects never contains, so the branch is dead — it can never return True.

Location

# forensics/views.py:1914-1916  (convergence_payload_matches_filters)
for ref in payload.get("evidence_refs", []):
    if message_id == ref.get("message_id"):
        return True

Every element of payload["evidence_refs"] is produced by evidence_ref_payload:

# forensics/views.py:2285-2296  (evidence_ref_payload)
def evidence_ref_payload(event: AuditEvent) -> dict:
    return {
        "event_id": event.id,
        "audit_file_id": event.audit_file_id,
        "line_number": event.line_number,
        "line_hash": event.line_hash,
        "schema_version": event.schema_version,
        "audit_data_mode": event.audit_data_mode,
        "event_type": event.event_type,
        "wall_time_ms": event.wall_time_ms,
        "api_path": reverse("api-event-evidence", kwargs={"event_id": event.id}),
    }

There is no message_id key. convergence_run_payload sets "evidence_refs": evidence_refs from evidence_refs_payloadevidence_ref_payload (views.py:2776, 2796, 2299-2300), so ref.get("message_id") is unconditionally None. The function only runs with a non-empty message_id (it early-returns True at views.py:1903-1904 when blank), so message_id == None is always False. The loop can never match.

Failure scenario / impact (LOW)

filtered_convergence_runs (views.py:1870-1898) has no SQL message_id filter, so message-scoped convergence filtering relies entirely on this predicate (wired as payload_filter at views.py:989, 1063, 1337 whenever message_id is set). A convergence run is therefore matched for ?message_id=<m> only when <m> appears as selected_branch_id, a losing_branch_id, a candidate branch_id, or a candidate commit_id — the evidence-ref branch contributes nothing.

The practical impact is small because branch/commit ids usually carry the same identifiers, so results rarely change. But this is a real latent gap: the branch was evidently intended to also match a run through its evidence events' message ids, and it silently does nothing.

Suggested fix

Decide which of these the branch was meant to be, and do it explicitly:

  • If evidence-ref message matching was intended: add a message_id to evidence_ref_payload (or match against the convergence evidence events' normalized msg_id/outbound_msg_id), and ideally push it into a SQL filter in filtered_convergence_runs for consistency with the other projection sections.
  • If not: delete the dead loop (views.py:1914-1916) so the matching contract is clear.

Why this is not a duplicate

The filed message_id-filter issues are #239 (Evidence surface matches only msg_id/outbound_msg_id), #250 (mode-changes ignore message_id/epoch), and #247 (action message_id applied after truncation). None concern convergence runs or the evidence_ref_payload schema mismatch; #200/#212/#156 are about scan cost / integer overflow, unrelated to this matching logic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LOWSeverity: minor correctness, polish, or maintainability issuebugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions