fix(worklog): persist event timestamps through settlement + reject invalid epochs

Two gate findings on the worklog-event-timestamp feature (#5700/#5739):

1. Thinking-row timestamps disappeared after settlement. Reasoning source events
   carry no server timestamp; the live DOM showed a fallback time but the settled
   scene row rebuilt with created_at:null. Now stamp created_at on the source event
   the first time it's seen (messages.js apply path) and carry row.created_at
   through snapshot hydration, so the timestamp survives live->settled.

2. A garbage huge numeric timestamp (e.g. 1e20) passed the finite/>0 checks and
   rendered literal 'Invalid Date'. _timestampSeconds now rejects epochs outside
   JavaScript's valid Date range. Added a regression test.

Co-authored-by: rodboev. Gate findings from adversarial Codex re-gate.
This commit is contained in:
nesquena-hermes
2026-07-11 00:23:48 +00:00
parent 815d06f10f
commit a86324985e
3 changed files with 51 additions and 3 deletions
+11
View File
@@ -2601,6 +2601,13 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
const sourceEvent={
...raw,
source_event_type:sourceEventType,
// Persist a creation timestamp the FIRST time we see this source event, so
// the worklog event timestamp (#5700/#5739) survives settlement. Reasoning
// events carry no server timestamp; without this, the live DOM shows a
// fallback time but the settled scene row rebuilds with created_at:null and
// the timestamp disappears. Prefer any real server-supplied stamp; fall back
// to now only when none exists. (#5739 gate finding.)
created_at:raw.created_at??raw.timestamp??raw.ts??(Date.now()/1000),
activitySegmentSeq:raw.activitySegmentSeq??raw.activity_segment_seq??_assistantSegmentSeq,
activityBurstId:raw.activityBurstId??raw.activity_burst_id??_currentActivityBurstId,
};
@@ -3890,6 +3897,10 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
status:row.status||undefined,
stream_id:row.stream_id||streamId,
run_id:row.run_id||streamId,
// Carry the row's persisted creation timestamp through hydration so the
// worklog event timestamp (#5700/#5739) survives a settled-snapshot rebuild
// (payload may not carry created_at even when the row does). (#5739 gate.)
created_at:payload.created_at??row.created_at??undefined,
};
try{
_anchorApi.applyAssistantTurnAnchorSourceEvent(_anchorRegistry,sourceEvent,{session_id:activeSid,stream_id:streamId,run_id:streamId});
+9 -3
View File
@@ -5572,18 +5572,24 @@ function _timestampSeconds(value){
if(value===undefined||value===null||value==='') return null;
if(value instanceof Date){
const stamp=value.getTime()/1000;
return Number.isFinite(stamp)&&stamp>0?stamp:null;
return (Number.isFinite(stamp)&&stamp>0&&Math.abs(stamp)<=8.64e12)?stamp:null;
}
const numeric=Number(value);
if(Number.isFinite(numeric)&&numeric>0){
const stamp=numeric>1e12?numeric/1000:numeric;
return Number.isFinite(stamp)&&stamp>0?stamp:null;
// Reject epochs outside JavaScript's valid Date range (±8.64e15 ms = ±8.64e12 s);
// otherwise new Date(stamp*1000) yields "Invalid Date" and renders literally
// (e.g. a garbage numeric timestamp like 1e20 passes finite/>0). (#5739 gate.)
return (Number.isFinite(stamp)&&stamp>0&&stamp<=8.64e12)?stamp:null;
}
if(typeof value==='string'){
const text=value.trim();
if(!text||/^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(text)) return null;
const parsed=Date.parse(text);
if(Number.isFinite(parsed)&&parsed>0) return parsed/1000;
if(Number.isFinite(parsed)&&parsed>0){
const stamp=parsed/1000;
return stamp<=8.64e12?stamp:null;
}
}
return null;
}
@@ -427,6 +427,37 @@ process.stdout.write(JSON.stringify({
assert data["nil"] is None
def test_timestamp_seconds_rejects_out_of_range_epochs_so_invalid_date_never_renders():
# A garbage huge numeric timestamp (e.g. 1e20) passes the finite/>0 checks but
# is far past JavaScript's max representable Date (±8.64e15 ms), so without a
# range guard new Date(stamp*1000) yields "Invalid Date" and would render
# literally in the worklog time label. (#5739 gate finding.)
data = _run_node(
"""
process.stdout.write(JSON.stringify({
huge: _timestampSeconds(1e20),
hugeMs: _timestampSeconds(1e20 * 1000),
normal: _timestampSeconds(1700000901),
normalMs: _timestampSeconds(1700000901000),
hugeLabel: _activityClockLabel(_timestampSeconds(1e20) || undefined),
normalLabel: _activityClockLabel(_timestampSeconds(1700000901)),
}));
"""
)
# garbage huge epochs (whether given as seconds or ms) resolve to null
assert data["huge"] is None
assert data["hugeMs"] is None
# a normal recent epoch still resolves, in both seconds and ms forms
assert data["normal"] == 1700000901
assert data["normalMs"] == 1700000901
# the clock label for a rejected timestamp must never be the literal "Invalid Date";
# a valid one produces a real time string.
assert "Invalid Date" not in (data["hugeLabel"] or "")
assert "Invalid Date" not in (data["normalLabel"] or "")
assert data["normalLabel"]
def test_settled_anchor_scene_rows_use_row_created_at_timestamps():
data = _run_node(
"""