Skip to content

event_epoch() and X or Y fallbacks treat epoch 0 (and other zero integers) as missing #16

@erskingardner

Description

@erskingardner

Impact

Epoch 0 is a legitimate, common value (MLS groups start at the genesis epoch 0), and value_if_int() explicitly accepts 0 as valid. But the analysis layer collapses falsy integers with X or Y chains, so a real epoch of 0 is treated as "absent". This produces incorrect forensic output: genesis-epoch events are mis-bucketed in the timeline, and 0 renders as in summaries.

event_epoch() is the worst case because its result is used to bucket message events per epoch and to set each timeline item's epoch:

def event_epoch(event):
    return (
        event.epoch
        or event.source_epoch
        or event.to_epoch
        or event.pending_epoch
        or event.current_tip_epoch
        or event.selected_tip_epoch
    )

If event.epoch == 0, the or chain skips it and returns the next truthy field (or None), so the event is attributed to the wrong epoch — or dropped from message_event_count entirely.

The same falsy-zero bug appears in several display strings, and in the template via the default filter (which also treats 0 as empty).

Code pointers

  • forensics/analysis.py:433event_epoch() or chain drops epoch 0.
  • forensics/analysis.py:720message_counts[event_epoch(event)] mis-buckets/loses epoch-0 message events.
  • forensics/analysis.py:795 — timeline item "epoch": event_epoch(event).
  • forensics/analysis.py:399selected_tip_epoch or '-' renders epoch 0 as -.
  • forensics/analysis.py:410event.epoch or '-'.
  • forensics/analysis.py:467f"epoch {event.from_epoch or '-'} -> {event.to_epoch or '-'}".
  • forensics/templates/forensics/audit_file_detail.html:102{{ event.seq|default:"–" }} renders seq == 0 as (should be default_if_none).

Expected behavior

A stored integer of 0 (epoch, seq, etc.) should be treated as a real value: epoch-0 events should bucket under epoch 0, and 0 should display as 0, not .

Suggested fix

  • Replace the or chains used to pick integer values with explicit None checks, e.g. a small helper first_not_none(*values) for event_epoch().
  • Use is not None (not truthiness) wherever a fallback for an integer field is rendered.
  • In templates, use default_if_none instead of default for integer fields like seq/wall_time_ms/epochs.
  • Add a regression test with an epoch_confirmed/message event at epoch 0 asserting it buckets under epoch 0 and renders as 0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    MEDIUMSeverity: important bug or performance issue with bounded impactbugSomething isn't working

    Type

    No type
    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