Hmbown/CodeWhale · error · Error
Invalid policy version or limits.
Error message
Invalid policy version or limits.
What it means
validatePolicy requires the policy object to have `version === 1` with at most 256 sources and at most 2048 grants (both arrays present). Failing any of these throws `Invalid policy version or limits.` The single message covers both a wrong version and exceeded size limits, so check all three conditions.
Solutions
- Set `version: 1` and ensure sources/grants are arrays
- Consolidate or expire grants to get under the 2048 limit
- Split oversized policies into multiple bundles
- Log sources.length/grants.length when debugging to see which limit was hit
Example fix
// before
{"version":2,"sources":[...300 sources],"grants":[...]}
// after
{"version":1,"sources":[...deduped under 256],"grants":[...under 2048]} Defensive patterns
Strategy: validation
Validate before calling
if (p.version !== 1 || !Array.isArray(p.sources) || p.sources.length > 256 || !Array.isArray(p.grants) || p.grants.length > 2048) throw new Error('policy version/limits invalid'); Type guard
const policyShapeOk = (p: any): p is {version: 1, sources: unknown[], grants: unknown[]} => p.version === 1 && Array.isArray(p.sources) && p.sources.length <= 256 && Array.isArray(p.grants) && p.grants.length <= 2048; Try / catch
try { validatePolicy(raw); } catch (e) { if (e.message === 'Invalid policy version or limits.') console.error(`check version===1, sources<=256 (got ${raw?.sources?.length}), grants<=2048 (got ${raw?.grants?.length})`); throw e; } Prevention
- Prune expired grants routinely so counts stay under limits
- Validate policies in CI before deployment
- Always set version: 1 explicitly in generators
When it happens
Trigger: Calling validatePolicy (directly or via validateBundle) with `version` ≠ 1, `sources` missing/not an array, more than 256 sources, `grants` missing/not an array, or more than 2048 grants.
Common situations: Machine-generated policies accumulating thousands of grants over time; hand-written policy missing the version field; merging policies from multiple environments without dedup.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Duplicate .
- Invalid evidence bundle or record limit exceeded.
- Invalid grant: object required.
- Codewhale terminal receipt contained a non-scalar field
- Codewhale terminal receipt contained an invalid count
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/b04def6edcbb70b3.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/evidence.ts:108
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');
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 {View on GitHub (pinned to 433685b202)