{"record":{"id":"2177ffc21a2703bd","repo":"Hmbown/CodeWhale","slug":"invalid-failure-observation-time-model","errorCode":null,"errorMessage":"Invalid failure observation time.","messagePattern":"Invalid failure observation time\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/model.ts","lineNumber":88,"sourceCode":"  if (f.model && e.model !== f.model) return false;\n  if (f.tool && e.tool !== f.tool) return false;\n  if (f.status && e.status !== f.status) return false;\n  if (f.query) {\n    const q = f.query.toLowerCase();\n    // Search is deliberately content-aware but runs only over locally retained fields.\n    if (![e.name, e.id, e.agentId, e.model, e.tool, e.provider, e.category,\n      JSON.stringify(e.attributes), JSON.stringify(e.payload), JSON.stringify(e.raw)].filter(Boolean).join(' ').toLowerCase().includes(q)) return false;\n  }\n  return true;\n}\nexport const durationOf = (e: WhaleEvent): number => Math.max(0, e.endTime - e.startTime);\n/** An explicit failure receipt can arrive after a span began or ended. Keep\n * its timestamp distinct from the operation onset in every signal view. */\nexport function errorOnsetOf(e: WhaleEvent): number {\n  const time = e.attributes['whalesong.error_onset_ms'];\n  if (time === undefined) return e.startTime;\n  if (typeof time !== 'number' || !Number.isFinite(time) || time < e.startTime)\n    throw new Error('Invalid failure observation time.');\n  return time;\n}\nexport function stableHash(text: string, seed = 2166136261): number {\n  let h = seed;\n  for (let i = 0; i < text.length; i++) { h ^= text.charCodeAt(i); h = Math.imul(h, 16777619); }\n  return h >>> 0;\n}\nexport function quantile(a: number[], q: number): number {\n  if (!a.length) return 0;\n  const s = [...a].sort((a, b) => a - b), x = Math.min(1, Math.max(0, q)) * (s.length - 1);\n  return s[Math.floor(x)] + (s[Math.ceil(x)] - s[Math.floor(x)]) * (x % 1);\n}\nexport function clamp(x: number, lo: number, hi: number): number { return Math.max(lo, Math.min(hi, x)); }\nexport function formatTime(ms: number, precise = false): string {\n  const m = Math.floor(Math.max(0, ms) / 60000), s = Math.floor(Math.max(0, ms) / 1000) % 60;\n  return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}${precise ? '.' + String(Math.floor(ms % 1000)).padStart(3, '0') : ''}`;\n}\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/model.ts#L70-L106","documentation":"errorOnsetOf reads the whalesong.error_onset_ms attribute recording when a failure was observed on an event. The attribute is valid only if it is a finite number greater than or equal to the event's startTime; anything else (missing type, NaN/Infinity, or a value before the span began) throws, keeping failure timing trustworthy across all signal views.","triggerScenarios":"An event carries whalesong.error_onset_ms that is a string, NaN, Infinity, or a number smaller than e.startTime, and errorOnsetOf is invoked by prune, normalized, snapshot, importTrace, events, or buildPyramid.","commonSituations":"A producer emitting the attribute before computing the span start; serializing the attribute to a string; clock skew between the failure recorder and span starter; hand-edited bundles setting negative or bogus onsets.","solutions":["Fix the producer to emit a finite number >= the event's startTime, or omit the attribute entirely (it then defaults to startTime).","Clamp or drop bad attribute values before ingestion: if onset < startTime, set it equal to startTime or delete the key.","Verify the attribute is not stringified during export/import (JSON numbers must stay numbers).","Check for clock skew between the component writing onsets and the one starting spans; normalize clocks before stamping."],"exampleFix":"// before\n{ \"attributes\": { \"whalesong.error_onset_ms\": \"1700000000000\" } } // string\n// after\n{ \"attributes\": { \"whalesong.error_onset_ms\": 1700000000000 } }\n","handlingStrategy":"type-guard","validationCode":"const onset = e.attributes['whalesong.error_onset_ms'];\nif (onset !== undefined && (typeof onset !== 'number' || !Number.isFinite(onset) || onset < e.startTime)) {\n  delete e.attributes['whalesong.error_onset_ms']; // or clamp to e.startTime\n}","typeGuard":"function hasValidOnset(e: WhaleEvent): boolean {\n  const t = e.attributes['whalesong.error_onset_ms'];\n  return t === undefined || (typeof t === 'number' && Number.isFinite(t) && t >= e.startTime);\n}","tryCatchPattern":"try {\n  const onset = errorOnsetOf(event);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid failure observation time.') {\n    console.warn('Bad error_onset_ms; falling back to event startTime.');\n    const onset = event.startTime;\n  }\n}","preventionTips":["Only emit whalesong.error_onset_ms as a JSON number >= the span's startTime.","Omit the attribute when the onset is unknown — it then defaults to startTime.","Normalize clock skew between the failure recorder and the span producer.","Never stringify the attribute during serialization; guard against schema drift in producers."],"tags":["timestamps","validation","attributes"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}