{"record":{"id":"be9b8b10ac53f120","repo":"Hmbown/CodeWhale","slug":"invalid-engine-pet-clock-be9b8b","errorCode":null,"errorMessage":"Invalid Engine pet clock.","messagePattern":"Invalid Engine pet clock\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-engine.ts","lineNumber":73,"sourceCode":"  }\n\n  /** Transactional batch copy; failed validation cannot accept half a packet. */\n  clone(): PetEngineTelemetry {\n    const next = new PetEngineTelemetry();\n    const copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;\n    next.events = copy(this.events);\n    const spans = new Map(next.events.map(event => [event.id, event]));\n    // Active and waiting spans must still reference their journal entry so a\n    // later heartbeat extends the coverage consumed by bucket().\n    const span = (event: WhaleEvent) => spans.get(event.id) ?? copy(event);\n    next.active = new Map(Array.from(this.active, ([key, event]) => [key, span(event)]));\n    next.waiting = this.waiting ? span(this.waiting) : undefined;\n    next.sequence = this.sequence; next.lastTime = this.lastTime;\n    return next;\n  }\n\n  observe(value: unknown, at: number): void {\n    if (!Number.isFinite(at) || at < this.lastTime || at > PET_MAX_SECONDS * 1000) throw new Error('Invalid Engine pet clock.');\n    if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid Engine pet metadata.');\n    const e = value as Record<string, unknown>;\n    const allowed = ['event', 'index', 'channel', 'tool_call_id', 'tool_name', 'id', 'worker_status', 'failed'];\n    if (Object.keys(e).some(k => !allowed.includes(k)) || typeof e.event !== 'string'\n      || Object.values(e).some(v => typeof v === 'string' && v.length > 4096)\n      || e.channel !== undefined && !['text', 'reasoning'].includes(e.channel as string)\n      || ['tool_call_id', 'tool_name', 'id', 'worker_status'].some(k => e[k] !== undefined && typeof e[k] !== 'string')\n      || e.failed !== undefined && typeof e.failed !== 'boolean'\n      || e.index !== undefined && (!Number.isSafeInteger(e.index) || (e.index as number) < 0))\n      throw new Error('Invalid Engine pet metadata fields.');\n    this.lastTime = at;\n    this.events = this.events.filter(span => span.endTime >= at - 12_800);\n    const id = (field: string) => { const s = e[field]; if (typeof s !== 'string' || !s) throw new Error(`Missing Engine ${field}.`); return s; };\n    const index = () => { if (!Number.isSafeInteger(e.index)) throw new Error('Missing Engine index.'); return String(e.index); };\n    const start = (key: string, name: string, category: Category, agentId?: string) => {\n      if (this.active.size >= 256 && !this.active.has(key)) throw new Error('Too many active Engine pet spans.');\n      this.active.set(key, this.add(name, category, at, agentId));\n    };","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-engine.ts#L55-L91","documentation":"observe() validates the wall-clock timestamp before accepting an observation: at must be a finite number that is monotonically non-decreasing (>= this.lastTime) and within the session horizon (<= PET_MAX_SECONDS*1000, i.e. PET_MAX_SECONDS in milliseconds). Non-monotonic or out-of-horizon clocks are rejected so event ordering and window math stay consistent.","triggerScenarios":"Calling engine.observe(value, at) with: a non-finite at (NaN, Infinity from Date arithmetic on an invalid date), a timestamp earlier than a previously observed one (clock rollback, out-of-order event delivery, replay of old events), or a timestamp beyond PET_MAX_SECONDS*1000 (e.g. passing Date.now() instead of session-elapsed ms).","commonSituations":"Passing Date.now() (epoch ms, huge) instead of elapsed session milliseconds; feeding events from two sources whose timestamps interleave backwards; system clock adjustment/NTP correction mid-session; replaying a recorded event stream without re-basing timestamps; computing at from a performance.now()/Date.now() mix.","solutions":["Log the offending at value and compare with engine.lastTime (from checkpoint/snapshot); ensure at >= lastTime and at <= PET_MAX_SECONDS*1000.","Convert absolute time to session-elapsed milliseconds: at = Date.now() - sessionStart, clamped to PET_MAX_SECONDS*1000.","Before calling, clamp: at = Math.min(Math.max(at, lastKnownTime), PET_MAX_SECONDS * 1000) and skip or drop events that arrive out of order.","If events can arrive out of order, buffer and sort by timestamp, or drop ones older than lastTime, before observing.","Guard Date arithmetic: replace NaN-producing Date.now() paths (invalid Date objects) with performance.now()-based elapsed time."],"exampleFix":"// before\nengine.observe(evt, Date.now());\n\n// after\nconst elapsed = Math.min(Math.round(performance.now()), PET_MAX_SECONDS * 1000);\nif (Number.isFinite(elapsed) && elapsed >= lastObserved) {\n  engine.observe(evt, elapsed);\n  lastObserved = elapsed;\n}","handlingStrategy":"validation","validationCode":"function isClockValid(at: number, lastTime: number): boolean {\n  return Number.isFinite(at) && at >= lastTime && at <= PET_MAX_SECONDS * 1000;\n}\nif (isClockValid(at, lastObserved)) engine.observe(evt, at);","typeGuard":"null","tryCatchPattern":"try {\n  engine.observe(evt, at);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid Engine pet clock.') {\n    console.warn('dropping out-of-order/overflow clock', at);\n    // or: re-base and clamp\n    engine.observe(evt, Math.min(Math.max(at, lastObserved), PET_MAX_SECONDS * 1000));\n  } else throw err;\n}","preventionTips":["Pass session-elapsed milliseconds, never Date.now() epoch values.","Compute time from performance.now() to avoid wall-clock jumps and invalid-Date NaN.","Track lastObserved yourself and drop/skip events older than it (monotonic delivery).","Clamp every timestamp to [lastTime, PET_MAX_SECONDS * 1000] before calling observe().","When replaying recorded streams, re-base timestamps to a fresh session clock first."],"tags":["validation","clock","monotonic-time"],"backgroundTag":"value-out-of-range","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}