Hmbown/CodeWhale · error · Error
sequence must be an integer.
Error message
sequence must be an integer.
What it means
Observation `sequence` must be a finite number ≥ 1 that is a safe integer; the `num` validator catches non-numbers/out-of-range values and a follow-up `Number.isSafeInteger` check throws `sequence must be an integer.` This guarantees per-source ordering fields are exact integers usable as monotonic sequence numbers.
Solutions
- Ensure sequence is `Math.trunc`-ed and ≥ 1 before validation
- Use an integer counter (BigInt on the producer side, converted safely) instead of derived floats
- Check the emitter: it should serialize sequence as a JSON integer
Example fix
// before sequence: totalEvents / batchSize // after sequence: Math.max(1, Math.trunc(totalEvents / batchSize))
Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isSafeInteger(seq) || seq < 1) throw new Error('sequence must be a safe integer >= 1'); Type guard
const isValidSequence = (v: unknown): v is number => Number.isSafeInteger(v) && v >= 1;
Try / catch
try { validateObservation(rec); } catch (e) { if (e.message === 'sequence must be an integer.') rec.sequence = Math.trunc(Number(rec.sequence)) || 1; throw e; } Prevention
- Use an integer counter, not timestamp-derived floats
- Serialize sequence with an integer JSON type
- Avoid arithmetic that can produce fractions on counters
When it happens
Trigger: validateObservation given `sequence` of 1.5, 0, -3, 1e21, NaN, or a numeric string "12".
Common situations: Sequence computed with floating-point math (averaging, division); JSON parsed from a source that emitted decimals; sequence generated from a timestamp in ms with fractional part; very large counters beyond Number.MAX_SAFE_INTEGER.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Duplicate .
- Invalid Engine pet metadata.
- Invalid Engine pet metadata fields.
- Invalid fact key: nonempty bounded text required.
- Invalid first pet bucket.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/6448bbbd681be021.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/evidence.ts:89
};
const optionalText = (o:Record<string,unknown>,key:string):string|undefined => o[key]===undefined?undefined:text(o[key],key);
const strings = (v:unknown,field:string,allowEmpty=false):string[]=>{
if(!Array.isArray(v)||(!allowEmpty&&!v.length)||v.length>256)throw new Error(`Invalid ${field}.`);
const out=v.map(x=>text(x,field));if(new Set(out).size!==out.length)throw new Error(`Duplicate ${field}.`);return out;
};
const boolean = (v:unknown,field:string):boolean=>{if(typeof v!=='boolean')throw new Error(`Invalid ${field}.`);return v;};
const optionalNumber=(o:Record<string,unknown>,key:string)=>o[key]===undefined?undefined:num(o[key],key);
function onlyKeys(o:Record<string,unknown>,keys:string[],label:string):void{
for(const k of Object.keys(o))if(!keys.includes(k))throw new Error(`Unknown ${label} field: ${k}.`);
}
/** All unrecognized fields are rejected, never secretly retained in metadata-only evidence. */
export function validateObservation(input:unknown):Observation {
const o=object(input,'observation');
onlyKeys(o,['version','id','runId','operationId','sourceId','epoch','sequence','time','receivedAt','subject','stage','surface','action','effect','status','target','actionDigest','authority','correlation','facts'],'observation');
if(o.version!==1)throw new Error('Unsupported observation version.');
const t=object(o.time,'time'),s=object(o.subject,'subject');
onlyKeys(t,['wallMs','clockId','monotonicMs','taskMs','uncertaintyMs'],'time');onlyKeys(s,['agentId','sandboxId','isolationGroup'],'subject');
const seq=num(o.sequence,'sequence',1);if(!Number.isSafeInteger(seq))throw new Error('sequence must be an integer.');
const facts=object(o.facts??{},'facts'),safeFacts:Observation['facts']={};
if(Object.keys(facts).length>48)throw new Error('Too many observation facts.');
for(const [k,v] of Object.entries(facts)){
text(k,'fact key',80);if(['__proto__','prototype','constructor'].includes(k))throw new Error('Unsafe fact key.');
if(typeof v==='string')safeFacts[k]=text(v,'fact value',512);
else if(typeof v==='number')safeFacts[k]=num(v,'fact value',-Number.MAX_SAFE_INTEGER);
else if(v===null||typeof v==='boolean')safeFacts[k]=v;
else throw new Error('Facts must be scalar metadata, not content objects.');
}
let target:Observation['target'];if(o.target!==undefined){const a=object(o.target,'target');onlyKeys(a,['id','kind','version','boundary'],'target');target={id:text(a.id,'target.id'),kind:text(a.kind,'target.kind',64),version:optionalText(a,'version'),boundary:a.boundary===undefined?undefined:enumValue(a.boundary,['local','external','unknown'] as const,'boundary')};}
let authority:Observation['authority'];if(o.authority!==undefined){const a=object(o.authority,'authority');onlyKeys(a,['grantId','claimed'],'authority');authority={grantId:optionalText(a,'grantId'),claimed:a.claimed===undefined?undefined:boolean(a.claimed,'claimed')};}
let correlation:Observation['correlation'];if(o.correlation!==undefined){const a=object(o.correlation,'correlation');onlyKeys(a,['sessionId','requestId','parentOperationId','messageId'],'correlation');correlation={sessionId:optionalText(a,'sessionId'),requestId:optionalText(a,'requestId'),parentOperationId:optionalText(a,'parentOperationId'),messageId:optionalText(a,'messageId')};}
return {version:1,id:text(o.id,'id'),runId:text(o.runId,'runId'),operationId:optionalText(o,'operationId'),sourceId:text(o.sourceId,'sourceId'),epoch:text(o.epoch,'epoch'),sequence:seq,
time:{wallMs:num(t.wallMs,'wallMs'),clockId:optionalText(t,'clockId'),monotonicMs:optionalNumber(t,'monotonicMs'),taskMs:optionalNumber(t,'taskMs'),uncertaintyMs:optionalNumber(t,'uncertaintyMs')},receivedAt:optionalNumber(o,'receivedAt'),
subject:{agentId:text(s.agentId,'agentId'),sandboxId:optionalText(s,'sandboxId'),isolationGroup:optionalText(s,'isolationGroup')},stage:enumValue(o.stage,STAGES,'stage'),surface:enumValue(o.surface,SURFACES,'surface'),action:text(o.action,'action',128),effect:enumValue(o.effect,EFFECTS,'effect'),status:enumValue(o.status,['started','completed','error','unknown'],'status'),target,actionDigest:optionalText(o,'actionDigest'),authority,correlation,facts:safeFacts};
}
export function validatePolicy(input:unknown):BoundaryPolicy {
const o=object(input,'policy');onlyKeys(o,['version','id','sources','grants','expectedSurfaces','forbiddenCrossGroup'],'policy');View on GitHub (pinned to 433685b202)