Problem
buildFlushDeltas in src/LatestValuesAccumulator.ts regroups accumulated path values by context + $source, merging them into a single Update and assigning that update the max timestamp across the merged items (lines 113-119). Per the Signal K spec, an update's timestamp applies to every path value in that update (there is no per-pathvalue timestamp field), so this emits incorrect timestamps for any merged path value whose original frame timestamp differs from the newest one in the group.
Concrete case
Two frames arrive during backpressure from $source: gps:
T=10.000: { timestamp: '10.000', values: [{ path: 'navigation.speedOverGround', value: 5.0 }] }
T=10.005: { timestamp: '10.005', values: [{ path: 'navigation.headingTrue', value: 1.5 }] }
accumulateLatestValue keys per context:path:$source, so each lands in its own accumulator entry with its original timestamp. buildFlushDeltas then merges both into one update keyed by context + $source and assigns timestamp 10.005:
$source: 'gps',
timestamp: '10.005',
values: [
{ path: 'navigation.speedOverGround', value: 5.0 }, // actually sampled at 10.000
{ path: 'navigation.headingTrue', value: 1.5 } // actually sampled at 10.005
]
A downstream consumer is told speedOverGround=5.0 @ 10.005, which is wrong.
Pre-existing
The merge-and-max-timestamp logic predates #2661. #2661 only restructured the loop.
Proposed fix
Group by context + $source + timestamp so path values that originally shared a timestamp ride in one update and the rest go in their own. Preserves spec semantics exactly. The N2K-PGN-demuxed-into-multiple-paths case (the main concern for extra updates per flush) usually shares a timestamp at source, so it'll still coalesce in practice. The extra-updates cost only shows up when timestamps genuinely differ, which is when keeping them separate is the right thing.
Alternative: group by context + $source and drop timestamp (or set it to flush time). Honest "summarized snapshot" semantics but loses original-sample freshness.
References
Problem
buildFlushDeltasinsrc/LatestValuesAccumulator.tsregroups accumulated path values bycontext + $source, merging them into a singleUpdateand assigning that update the max timestamp across the merged items (lines 113-119). Per the Signal K spec, an update'stimestampapplies to every path value in that update (there is no per-pathvalue timestamp field), so this emits incorrect timestamps for any merged path value whose original frame timestamp differs from the newest one in the group.Concrete case
Two frames arrive during backpressure from
$source: gps:T=10.000:{ timestamp: '10.000', values: [{ path: 'navigation.speedOverGround', value: 5.0 }] }T=10.005:{ timestamp: '10.005', values: [{ path: 'navigation.headingTrue', value: 1.5 }] }accumulateLatestValuekeys percontext:path:$source, so each lands in its own accumulator entry with its original timestamp.buildFlushDeltasthen merges both into one update keyed bycontext + $sourceand assigns timestamp10.005:A downstream consumer is told
speedOverGround=5.0 @ 10.005, which is wrong.Pre-existing
The merge-and-max-timestamp logic predates #2661. #2661 only restructured the loop.
Proposed fix
Group by
context + $source + timestampso path values that originally shared a timestamp ride in one update and the rest go in their own. Preserves spec semantics exactly. The N2K-PGN-demuxed-into-multiple-paths case (the main concern for extra updates per flush) usually shares a timestamp at source, so it'll still coalesce in practice. The extra-updates cost only shows up when timestamps genuinely differ, which is when keeping them separate is the right thing.Alternative: group by
context + $sourceand droptimestamp(or set it to flush time). Honest "summarized snapshot" semantics but loses original-sample freshness.References