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:433 — event_epoch() or chain drops epoch 0.
forensics/analysis.py:720 — message_counts[event_epoch(event)] mis-buckets/loses epoch-0 message events.
forensics/analysis.py:795 — timeline item "epoch": event_epoch(event).
forensics/analysis.py:399 — selected_tip_epoch or '-' renders epoch 0 as -.
forensics/analysis.py:410 — event.epoch or '-'.
forensics/analysis.py:467 — f"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.
Impact
Epoch
0is a legitimate, common value (MLS groups start at the genesis epoch 0), andvalue_if_int()explicitly accepts0as valid. But the analysis layer collapses falsy integers withX or Ychains, so a real epoch of0is treated as "absent". This produces incorrect forensic output: genesis-epoch events are mis-bucketed in the timeline, and0renders 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:If
event.epoch == 0, theorchain skips it and returns the next truthy field (orNone), so the event is attributed to the wrong epoch — or dropped frommessage_event_countentirely.The same falsy-zero bug appears in several display strings, and in the template via the
defaultfilter (which also treats0as empty).Code pointers
forensics/analysis.py:433—event_epoch()orchain drops epoch0.forensics/analysis.py:720—message_counts[event_epoch(event)]mis-buckets/loses epoch-0 message events.forensics/analysis.py:795— timeline item"epoch": event_epoch(event).forensics/analysis.py:399—selected_tip_epoch or '-'renders epoch 0 as-.forensics/analysis.py:410—event.epoch or '-'.forensics/analysis.py:467—f"epoch {event.from_epoch or '-'} -> {event.to_epoch or '-'}".forensics/templates/forensics/audit_file_detail.html:102—{{ event.seq|default:"–" }}rendersseq == 0as–(should bedefault_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, and0should display as0, not–.Suggested fix
orchains used to pick integer values with explicitNonechecks, e.g. a small helperfirst_not_none(*values)forevent_epoch().is not None(not truthiness) wherever a fallback for an integer field is rendered.default_if_noneinstead ofdefaultfor integer fields likeseq/wall_time_ms/epochs.epoch_confirmed/message event at epoch0asserting it buckets under epoch 0 and renders as0.