Hmbown/CodeWhale · error · Error

Duplicate producer incarnation/sequence in evidence bundle…

Error message

Duplicate producer incarnation/sequence in evidence bundle. Import cancelled; resolve identity before import.

What it means

validateBundle enforces that every observation has a unique producer incarnation/sequence key (observationKey). If two records share that key the import is cancelled entirely rather than silently deduped, per the message in pet/src/core/evidence.ts:118.

Solutions

  1. Deduplicate records by observationKey before import
  2. Fix the producer to use a fresh incarnation id on restart
  3. Split into separate bundles if the records are genuinely distinct streams

Example fix

// before
importTrace({..., records: [...batch1, ...batch1Retry]})
// after
const seen = new Set(); const records = all.filter(r => { const k = key(r); if (seen.has(k)) return false; seen.add(k); return true; });
Defensive patterns

Strategy: validation

Validate before calling

const keys=records.map(observationKey); if(new Set(keys).size!==keys.length) throw new Error('Duplicate observation keys before import');

Type guard

const deduped = <T>(records:T[], key:(r:T)=>string): T[] => [...new Map(records.map(r=>[key(r),r])).values()];

Try / catch

try { importTrace(raw); } catch (e) { if (/Duplicate producer incarnation/.test(e.message)) { /* dedupe by observationKey and retry */ } else throw e; }

Prevention

When it happens

Trigger: Calling validateBundle/importTrace where two records in `records` produce the same observationKey (same producer incarnation and sequence number).

Common situations: Concatenating two exports from the same producer run; retry logic re-emitting already-queued observations; a producer restarting with a reused incarnation id and replaying sequence numbers.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/3eae19892a7d12d2. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/evidence.ts:118

  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');
  if(o.version!==1||!Array.isArray(o.sources)||o.sources.length>256||!Array.isArray(o.grants)||o.grants.length>2048)throw new Error('Invalid policy version or limits.');
  const sources=o.sources.map(x=>{const a=object(x,'source');onlyKeys(a,['id','stages','surfaces','runIds','sandboxIds','isolationGroups','heartbeatMs','description'],'source');return {id:text(a.id,'source.id'),stages:strings(a.stages,'stages').map(v=>enumValue(v,STAGES,'stage')),surfaces:strings(a.surfaces,'surfaces').map(v=>enumValue(v,SURFACES,'surface')),runIds:strings(a.runIds,'runIds'),sandboxIds:a.sandboxIds===undefined?undefined:strings(a.sandboxIds,'sandboxIds'),isolationGroups:a.isolationGroups===undefined?undefined:strings(a.isolationGroups,'isolationGroups'),heartbeatMs:num(a.heartbeatMs,'heartbeatMs',1),description:optionalText(a,'description')};});
  const grants=o.grants.map(x=>{const a=object(x,'grant');onlyKeys(a,['id','runIds','sandboxIds','targetIds','actions','effects','notBefore','expiresAt','actionDigest'],'grant');const g={id:text(a.id,'grant.id'),runIds:strings(a.runIds,'runIds'),sandboxIds:strings(a.sandboxIds,'sandboxIds'),targetIds:strings(a.targetIds,'targetIds'),actions:strings(a.actions,'actions'),effects:strings(a.effects,'effects').map(v=>enumValue(v,EFFECTS,'effect')),notBefore:num(a.notBefore,'notBefore'),expiresAt:num(a.expiresAt,'expiresAt'),actionDigest:optionalText(a,'actionDigest')};if(g.expiresAt<=g.notBefore)throw new Error('Grant expiry must follow its start.');return g;});
  if(new Set(sources.map(s=>s.id)).size!==sources.length||new Set(grants.map(g=>g.id)).size!==grants.length)throw new Error('Duplicate policy identity.');
  return {version:1,id:text(o.id,'policy.id'),sources,grants,expectedSurfaces:strings(o.expectedSurfaces,'expectedSurfaces',true).map(v=>enumValue(v,SURFACES,'surface')),forbiddenCrossGroup:boolean(o.forbiddenCrossGroup,'forbiddenCrossGroup')};
}
export function validateBundle(input:unknown,maxRecords=100_000):EvidenceBundle {
  const o=object(input,'bundle');onlyKeys(o,['format','name','records','policy','asOf'],'bundle');
  if(o.format!=='whalesong.evidence/v1'||!Array.isArray(o.records)||o.records.length>maxRecords)throw new Error('Invalid evidence bundle or record limit exceeded.');
  const records=o.records.map(validateObservation),seen=new Set<string>();
  for(const record of records){const key=observationKey(record);if(seen.has(key))throw new Error('Duplicate producer incarnation/sequence in evidence bundle. Import cancelled; resolve identity before import.');seen.add(key);}
  return {format:o.format,name:text(o.name,'name'),records,policy:validatePolicy(o.policy),asOf:num(o.asOf,'asOf')};
}
export function sourceAccepts(source:EvidenceSource,o:Observation):boolean {
  return source.id===o.sourceId&&source.stages.includes(o.stage)&&source.surfaces.includes(o.surface)&&source.runIds.includes(o.runId)&&(!source.sandboxIds||source.sandboxIds.includes(o.subject.sandboxId??''))&&(!source.isolationGroups||source.isolationGroups.includes(o.subject.isolationGroup??''));
}
export type Authorization = { decision:'permitted'|'denied'|'unknown'; reason:string };
/** Exact allowlists; no prefix matching, text approval, or ambient default allow. Not enforcement. */
export function authorize(o:Observation,policy:BoundaryPolicy):Authorization {
  const source=policy.sources.find(s=>s.id===o.sourceId);
  if(!source||!sourceAccepts(source,o))return {decision:'unknown',reason:'Source is not bound to this scope.'};
  if(o.effect==='unknown')return {decision:'unknown',reason:'Actual effect not established.'};
  const grant=policy.grants.find(g=>g.id===o.authority?.grantId);
  if(!grant)return {decision:'denied',reason:'No matching operator-configured grant.'};
  const u=o.time.uncertaintyMs;
  if(u===undefined)return {decision:'unknown',reason:'Clock uncertainty absent; grant validity cannot be established.'};
  if(o.time.wallMs-u<grant.notBefore||o.time.wallMs+u>=grant.expiresAt)return {decision:'denied',reason:'Outside grant validity interval (including clock uncertainty).'};
  if(!grant.runIds.includes(o.runId)||!grant.sandboxIds.includes(o.subject.sandboxId??'')||!grant.targetIds.includes(o.target?.id??'')||!grant.actions.includes(o.action)||!grant.effects.includes(o.effect))return {decision:'denied',reason:'Action, effect, target, run, or sandbox is outside the grant.'};
  if(grant.actionDigest&&o.actionDigest!==grant.actionDigest)return {decision:'denied',reason:'Approved action digest does not match.'};

View on GitHub (pinned to 433685b202)