Hmbown/CodeWhale · error · Error

Duplicate .

Error message

Duplicate ${field}.

What it means

The `strings` validator in evidence.ts parses an unknown value into a bounded, non-empty string array and rejects arrays containing repeated elements. This library treats duplicate entries in evidence fields (stages, surfaces, runIds, grant lists, etc.) as malformed policy/evidence input rather than silently de-duplicating, because duplicates in authorization data can change semantics. The thrown message includes the field name, e.g. `Duplicate runIds.`

Solutions

  1. De-duplicate the array before validation, e.g. `[...new Set(arr)]`
  2. Check the field name in the message and remove the repeated entry from the policy/bundle JSON
  3. If the duplicates come from merging configs, merge with a Set/union instead of concatenation

Example fix

// before
{"stages":["plan","plan","act"]}
// after
{"stages":["plan","act"]}
Defensive patterns

Strategy: validation

Validate before calling

function assertNoDuplicates(arr, name) { if (new Set(arr).size !== arr.length) throw new Error(`Duplicate ${name}.`); }

Type guard

const hasNoDuplicates = (a: unknown[]): a is string[] => new Set(a).size === a.length;

Try / catch

try { validatePolicy(input); } catch (e) { if (/^Duplicate /.test(e.message)) console.error('De-duplicate field named in message'); throw e; }

Prevention

When it happens

Trigger: Calling validatePolicy or validateBundle with a policy whose source/grant arrays (stages, surfaces, runIds, sandboxIds, isolationGroups, targetIds, actions, effects) contain the same string twice; also validateObservation via sources/g when building arrays with accidental repeats.

Common situations: Hand-authored policy JSON where a run ID is copy-pasted twice; generated policies that append to an existing list without dedup; merging two configs where both contributed the same sandboxId.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

  unknownEffects: number; authority: { permitted: number; denied: number; unknown: number };
  provenance: string;
}
const enumValue = <T extends string>(v:unknown, values: readonly T[], field:string): T => {
  if(typeof v!=='string'||!values.includes(v as T))throw new Error(`Invalid ${field}.`);return v as T;
};
const object = (v:unknown, field:string): Record<string,unknown> => {
  if(!v||typeof v!=='object'||Array.isArray(v))throw new Error(`Invalid ${field}: object required.`);return v as Record<string,unknown>;
};
const text = (v:unknown, field:string, max=256):string => {
  if(typeof v!=='string'||!v.length||v.length>max||/[\u0000-\u001f\u007f]/.test(v))throw new Error(`Invalid ${field}: nonempty bounded text required.`);return v;
};
const num = (v:unknown,field:string,min=0):number => {
  if(typeof v!=='number'||!Number.isFinite(v)||v<min||Math.abs(v)>Number.MAX_SAFE_INTEGER)throw new Error(`Invalid ${field}.`);return v;
};
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.');

View on GitHub (pinned to 433685b202)