Summary
event_tone has an explicit "error" branch for publish_failure, but no branch for the sibling failure event types send_error and create_group_error. Both are real, ingested event types, and both fall through to the generic startswith("send_") catch-all — so a failed send is painted with the neutral/positive "send" tone (visually identical to a successful send_entry/send_outcome) and a failed group creation is painted "receive". Neither failure is highlighted, even though the analogous publish_failure is.
Code
forensics/analysis.py:860-882:
def event_tone(event: AuditEvent) -> str:
if event.event_type == "human_action":
return "send" if event.human_action_origin == "local_user" else "receive"
if event.event_type == "publish_failure":
return "error"
if event.event_type == "publish_outcome" and event.failed_relays:
return "error" if not event.met_required_acks else "send"
tone = "send" if event.event_type.startswith("send_") else "receive" # catch-all
...
return tone
send_error and create_group_error are ingested event types (forensics/ingest.py:661 case "send_error":, :670 case "create_group_error":, which populate outcome_kind/detail):
send_error → startswith("send_") is True → tone "send"
create_group_error → tone "receive"
Concrete failure scenario
An audit log contains a send_error (a send that failed — relay rejected, encryption error, etc.). On the timeline (timeline_item_for_event → event_tone, analysis.py:1465) it is rendered with the "send" tone, indistinguishable from a successful send. The analyst gets no error indication for a failed operation. Failed sends and failed group-creations silently blend into successful traffic — a correctness gap for a forensic triage surface.
Related supporting defect (same root cause)
event_summary (analysis.py:823-857) likewise has no send_error/create_group_error case, so it returns the bare event.event_type (line 857) and never surfaces the ingested outcome_kind/detail — whereas publish_failure summaries include the reason (line 835-836). The failure reason is dropped from the summary line.
Why not a duplicate
The two existing event_tone issues are #191 (successful/attempted publish events classified inbound "receive") and #258 (create_group_outcome classified "receive"). Neither concerns the *_error event types being denied an "error" tone. This is a distinct misclassification of failure events.
Suggested fix
Add explicit "error" branches for send_error and create_group_error in event_tone, and matching cases in event_summary that surface outcome_kind/detail, mirroring the existing publish_failure handling.
Severity: MEDIUM — consistent with #191/#258; a failed forensic operation is not visually distinguishable from a successful one.
Summary
event_tonehas an explicit"error"branch forpublish_failure, but no branch for the sibling failure event typessend_errorandcreate_group_error. Both are real, ingested event types, and both fall through to the genericstartswith("send_")catch-all — so a failed send is painted with the neutral/positive"send"tone (visually identical to a successfulsend_entry/send_outcome) and a failed group creation is painted"receive". Neither failure is highlighted, even though the analogouspublish_failureis.Code
forensics/analysis.py:860-882:send_errorandcreate_group_errorare ingested event types (forensics/ingest.py:661case "send_error":,:670case "create_group_error":, which populateoutcome_kind/detail):send_error→startswith("send_")isTrue→ tone"send"create_group_error→ tone"receive"Concrete failure scenario
An audit log contains a
send_error(a send that failed — relay rejected, encryption error, etc.). On the timeline (timeline_item_for_event→event_tone,analysis.py:1465) it is rendered with the"send"tone, indistinguishable from a successful send. The analyst gets no error indication for a failed operation. Failed sends and failed group-creations silently blend into successful traffic — a correctness gap for a forensic triage surface.Related supporting defect (same root cause)
event_summary(analysis.py:823-857) likewise has nosend_error/create_group_errorcase, so it returns the bareevent.event_type(line 857) and never surfaces the ingestedoutcome_kind/detail— whereaspublish_failuresummaries include the reason (line 835-836). The failure reason is dropped from the summary line.Why not a duplicate
The two existing
event_toneissues are #191 (successful/attempted publish events classified inbound"receive") and #258 (create_group_outcomeclassified"receive"). Neither concerns the*_errorevent types being denied an"error"tone. This is a distinct misclassification of failure events.Suggested fix
Add explicit
"error"branches forsend_errorandcreate_group_errorinevent_tone, and matching cases inevent_summarythat surfaceoutcome_kind/detail, mirroring the existingpublish_failurehandling.Severity: MEDIUM — consistent with #191/#258; a failed forensic operation is not visually distinguishable from a successful one.