{"record":{"id":"795c6be5d5b5d1bf","repo":"Hmbown/CodeWhale","slug":"invalid-engine-pet-clock","errorCode":null,"errorMessage":"Invalid Engine pet clock.","messagePattern":"Invalid Engine pet clock\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/pet-native.js","lineNumber":1729,"sourceCode":"    }\n    /** Transactional batch copy; failed validation cannot accept half a packet. */\n    clone() {\n        const next = new PetEngineTelemetry();\n        const copy = (value) => JSON.parse(JSON.stringify(value));\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) => 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;\n        next.lastTime = this.lastTime;\n        return next;\n    }\n    observe(value, at) {\n        if (!Number.isFinite(at) || at < this.lastTime || at > pet_sim_js_1.PET_MAX_SECONDS * 1000)\n            throw new Error('Invalid Engine pet clock.');\n        if (!value || typeof value !== 'object' || Array.isArray(value))\n            throw new Error('Invalid Engine pet metadata.');\n        const e = value;\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)\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 < 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) => { const s = e[field]; if (typeof s !== 'string' || !s)\n            throw new Error(`Missing Engine ${field}.`); return s; };\n        const index = () => { if (!Number.isSafeInteger(e.index))\n            throw new Error('Missing Engine index.'); return String(e.index); };\n        const start = (key, name, category, agentId) => {","sourceCodeStart":1711,"sourceCodeEnd":1747,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/pet-native.js#L1711-L1747","documentation":"PetEngineTelemetry.observe validates the observation timestamp before accepting any event. It throws 'Invalid Engine pet clock.' when at is not finite, is earlier than the last accepted timestamp (monotonic clock), or exceeds PET_MAX_SECONDS * 1000 (the simulation's maximum horizon in milliseconds). The pet clock must move forward and stay inside the sim horizon so bucketing stays deterministic.","triggerScenarios":"Passing a timestamp that went backwards (e.g. events delivered out of order, or performance.now() mixed with Date.now()); passing NaN/Infinity; passing a time beyond the pet sim's PET_MAX_SECONDS horizon; attaching a stale replay tape whose timestamps predate the instance's lastTime.","commonSituations":"Mixing wall-clock and monotonic time sources in the same telemetry stream; reusing a telemetry instance across session restores where the old lastTime exceeds the new stream's start; a replay with unsorted events; overflowing past the fixed simulation horizon in a very long session.","solutions":["Sort events by timestamp before feeding them to observe()","Use a single monotonic time source (performance.now()-style ms) for all observations","Create a fresh PetEngineTelemetry instance for a new/replayed session instead of reusing one with a large lastTime","Guard callers: skip or clamp timestamps that are not finite or fall outside [lastTime, PET_MAX_SECONDS*1000]"],"exampleFix":"// before\nevents.forEach(e => telemetry.observe(e.meta, e.atMs)); // unsorted, mixed clocks\n// after\nevents.sort((a, b) => a.atMs - b.atMs).forEach(e => {\n  const at = Math.round(e.atMs);\n  if (Number.isFinite(at) && at >= telemetry.lastTime) telemetry.observe(e.meta, at);\n});","handlingStrategy":"validation","validationCode":"function canObserve(telemetry, at) {\n  return Number.isFinite(at) && at >= telemetry.lastTime && at <= PET_MAX_SECONDS * 1000;\n}\nif (canObserve(telemetry, atMs)) telemetry.observe(meta, atMs);","typeGuard":null,"tryCatchPattern":"try {\n  telemetry.observe(meta, atMs);\n} catch (err) {\n  if (err.message === 'Invalid Engine pet clock.') {\n    // drop or buffer the out-of-order/stale event; do not retry with a rewritten clock silently\n    droppedStaleEvents.push({ meta, atMs });\n  } else throw err;\n}","preventionTips":["Use one monotonic millisecond time source everywhere","Sort events by timestamp before observing","Instantiate a fresh PetEngineTelemetry per session/replay","Clamp timestamps to the sim horizon before observing"],"tags":["telemetry","clock","validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}