{"record":{"id":"dcccb5570af90ce0","repo":"Hmbown/CodeWhale","slug":"invalid-engine-pet-metadata","errorCode":null,"errorMessage":"Invalid Engine pet metadata.","messagePattern":"Invalid Engine pet metadata\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/pet-native.js","lineNumber":1731,"sourceCode":"    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) => {\n            if (this.active.size >= 256 && !this.active.has(key))\n                throw new Error('Too many active Engine pet spans.');","sourceCodeStart":1713,"sourceCodeEnd":1749,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/pet-native.js#L1713-L1749","documentation":"PetEngineTelemetry.observe expects a plain object carrying Engine event metadata (an adapter view of codewhale_protocol::EventMsg). It throws 'Invalid Engine pet metadata.' when value is null, not an object, or an Array. This guards the transactional boundary: a malformed packet must be rejected whole rather than half-applied.","triggerScenarios":"Calling observe(undefined/null), passing an array of events instead of one event object, or passing a primitive (string/number) where an event metadata object is expected.","commonSituations":"A caller batching events loops over the array but passes the array itself to observe(); a deserialize step returning undefined for a missing record; a Rust->JS bridge mapping an Option<EventMsg> to null.","solutions":["Unwrap arrays and call observe once per element","Check the value is a non-null, non-array object before calling observe","Fix the bridge/deserializer so missing events are skipped, not passed as null/undefined"],"exampleFix":"// before\ntelemetry.observe(eventBatch, at); // array passed\n// after\nfor (const e of eventBatch) if (e && typeof e === 'object' && !Array.isArray(e)) telemetry.observe(e, at);","handlingStrategy":"type-guard","validationCode":"function isEngineEventMeta(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}\nif (isEngineEventMeta(msg)) telemetry.observe(msg, at);","typeGuard":"const isEventMeta = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try {\n  telemetry.observe(value, at);\n} catch (err) {\n  if (err.message === 'Invalid Engine pet metadata.') return; // skip null/non-object packet\n  throw err;\n}","preventionTips":["Unwrap arrays at the batching layer before calling observe","Handle null/undefined from Option-style bridges explicitly","Type-check deserialized payloads before feeding telemetry"],"tags":["telemetry","type-mismatch","validation"],"backgroundTag":"type-mismatch","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"}