{"record":{"id":"69850524a09a1731","repo":"Hmbown/CodeWhale","slug":"field-must-be-nonnegative","errorCode":null,"errorMessage":"${field} must be nonnegative.","messagePattern":"(.+?) must be nonnegative\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/ingest.ts","lineNumber":72,"sourceCode":"    const { payload: _payload, raw: _raw, ...rest } = e;\n    return { ...rest, links: e.links?.map(link=>({traceId:link.traceId,spanId:link.spanId})), attributes: Object.fromEntries(Object.entries(e.attributes).filter(([k, v]) =>\n      SAFE_META.test(k) && (typeof v !== 'object' || v === null)\n      || ['whalesong.container', 'codewhale.container', 'whalesong.waiting'].includes(k) && typeof v === 'boolean'\n      // Relative timestamps can be negative before the importer rebases them.\n      || k === 'whalesong.error_onset_ms' && typeof v === 'number' && Number.isFinite(v))) };\n  }\n  return e;\n}\nfunction number(v: unknown, field: string, optional = true): number | undefined {\n  if (v === undefined || v === null) {\n    if (optional) return undefined;\n    throw new Error(`Missing ${field}. Times must be numeric milliseconds.`);\n  }\n  if (typeof v !== 'number' || !Number.isFinite(v) || Math.abs(v) > Number.MAX_SAFE_INTEGER) throw new Error(`Invalid ${field}: expected a finite, safely representable number.`);\n  return v;\n}\nfunction nonnegative(v: unknown, field: string): number | undefined {\n  const n = number(v, field); if (n !== undefined && n < 0) throw new Error(`${field} must be nonnegative.`); return n;\n}\nfunction numericAttr(v: unknown): number | undefined {\n  if (v === undefined || v === null || v === '') return undefined;\n  const n = Number(v); return Number.isFinite(n) && n >= 0 ? n : undefined;\n}\nexport function categoryFor(name: string, a: Obj): Category {\n  const explicit = a['whalesong.category'] ?? a.category;\n  if (CATEGORIES.includes(explicit)) return explicit;\n  const n = name.toLowerCase(), op = String(a['gen_ai.operation.name'] ?? '').toLowerCase();\n  if (/exception|^error\\b/.test(n)) return 'error';\n  if (/spawn|fork|subagent/.test(n) || op === 'invoke_agent') return 'agent';\n  if (/message\\.send|handoff|agent\\.message/.test(n)) return 'communication';\n  if (/retrieve|retrieval|context|embedding|vector|memory|rag/.test(n)) return 'memory';\n  if (/browser|navigate|screenshot|click|playwright/.test(n)) return 'browser';\n  if (/read_file|write_file|list_dir|file\\.|filesystem|fs\\.|patch/.test(n)) return 'filesystem';\n  if (/exec|shell|run_test|cargo|pytest|compile/.test(n)) return 'code';\n  if (a['gen_ai.request.model'] || a['llm.model_name'] || /reason|completion|generate|chat|llm/.test(n) || ['chat', 'generate_content', 'text_completion'].includes(op)) return 'reasoning';\n  if (a['gen_ai.tool.name'] || a['tool.name'] || /tool|search|function/.test(n)) return 'tool';","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/ingest.ts#L54-L90","documentation":"Numeric field guard in the pet ingest normalizer: the field named by ${field} (e.g. startTime/endTime or a duration) is negative where the normalized event schema requires a nonnegative value. Times may only be negative transiently before the importer rebases relative timestamps against the trace origin; once normalized, they must be >= 0.","triggerScenarios":"Calling the ingest normalizer with a negative value for a nonnegative-validated field (e.g. duration, elapsed, sequence offset) via normalizedEvent.","commonSituations":"Clock skew producing negative durations; subtracting timestamps in the wrong order; test fixtures with -1 sentinels.","solutions":["Clamp or reject negative values at the producer before ingest","Recompute the value as Math.max(0, end-start)","Confirm field ordering — endTime must not precede startTime"],"exampleFix":"// before\nduration: endTime - startTime // can be negative\n// after\nduration: Math.max(0, endTime - startTime)","handlingStrategy":"validation","validationCode":"if (typeof rec.duration==='number' && rec.duration<0) throw new Error('duration must be nonnegative');","typeGuard":"const isNonnegative=(v:unknown): v is number => typeof v==='number'&&Number.isFinite(v)&&v>=0;","tryCatchPattern":"try { ingest(records); } catch (e) { if (/must be nonnegative/.test(e.message)) { /* clamp with Math.max(0,n) or fix the producer */ } else throw e; }","preventionTips":["Clamp derived durations to >= 0 at computation time","Fix clock-skew sources (NTP) so end>=start","Avoid -1 sentinels in numeric fields"],"tags":["validation","times","negative-value","ingest"],"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"}