Category: spec-conformance Severity: minor
Location: lib/arcp/runtime/session_actor.rb:297-300 (secondary: lib/arcp/runtime/event_log.rb:61-69)
Spec: ARCP v1.1 §7.6
What
Spec §7.6: with history: true, "the runtime replays buffered events with seq > from_event_seq." handle_subscribe calls replay_job(sub.job_id, from_event_seq: sub.from_event_seq) with no +1, and EventLog#replay_job skips only env.event_seq < from_event_seq, thereby including the event whose seq == from_event_seq. The subscriber receives one event it already has. Contrast the resume path (session_actor.rb:143), which correctly compensates with from_event_seq: last_processed_seq + 1. The off-by-one is benign only because callers like the stream_resume recipe dedup by chunk_seq.
Evidence
# lib/arcp/runtime/session_actor.rb:297-300
if sub.history
replay = @runtime.event_log.replay_job(sub.job_id, from_event_seq: sub.from_event_seq) # no +1
replay.each { |e| send_envelope(e) }
end
# lib/arcp/runtime/event_log.rb:63-65 — keeps seq == from_event_seq
@jobs[job_id].each_with_object([]) do |(env, _t), out|
next if env.event_seq && from_event_seq && env.event_seq < from_event_seq
out << env
end
Proposed fix
Replay strictly greater than from_event_seq: either pass from_event_seq: sub.from_event_seq + 1 (guarding nil) in handle_subscribe, or change the replay_job predicate to env.event_seq <= from_event_seq. Keep the resume path consistent. Add a test subscribing with from_event_seq: N, history: true and asserting the first replayed event has event_seq > N.
Acceptance criteria
Category: spec-conformance Severity: minor
Location:
lib/arcp/runtime/session_actor.rb:297-300(secondary:lib/arcp/runtime/event_log.rb:61-69)Spec: ARCP v1.1 §7.6
What
Spec §7.6: with
history: true, "the runtime replays buffered events withseq > from_event_seq."handle_subscribecallsreplay_job(sub.job_id, from_event_seq: sub.from_event_seq)with no+1, andEventLog#replay_jobskips onlyenv.event_seq < from_event_seq, thereby including the event whoseseq == from_event_seq. The subscriber receives one event it already has. Contrast the resume path (session_actor.rb:143), which correctly compensates withfrom_event_seq: last_processed_seq + 1. The off-by-one is benign only because callers like thestream_resumerecipe dedup bychunk_seq.Evidence
Proposed fix
Replay strictly greater than
from_event_seq: either passfrom_event_seq: sub.from_event_seq + 1(guarding nil) inhandle_subscribe, or change thereplay_jobpredicate toenv.event_seq <= from_event_seq. Keep the resume path consistent. Add a test subscribing withfrom_event_seq: N, history: trueand asserting the first replayed event hasevent_seq > N.Acceptance criteria
job.subscribehistory replay excludesevent_seq == from_event_seq.