{"record":{"id":"91ca78c24e137435","repo":"Hmbown/CodeWhale","slug":"unknown-label-field-k","errorCode":null,"errorMessage":"Unknown ${label} field: ${k}.","messagePattern":"Unknown (.+?) field: (.+?)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/evidence.ts","lineNumber":80,"sourceCode":"};\nconst object = (v:unknown, field:string): Record<string,unknown> => {\n  if(!v||typeof v!=='object'||Array.isArray(v))throw new Error(`Invalid ${field}: object required.`);return v as Record<string,unknown>;\n};\nconst text = (v:unknown, field:string, max=256):string => {\n  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;\n};\nconst num = (v:unknown,field:string,min=0):number => {\n  if(typeof v!=='number'||!Number.isFinite(v)||v<min||Math.abs(v)>Number.MAX_SAFE_INTEGER)throw new Error(`Invalid ${field}.`);return v;\n};\nconst optionalText = (o:Record<string,unknown>,key:string):string|undefined => o[key]===undefined?undefined:text(o[key],key);\nconst strings = (v:unknown,field:string,allowEmpty=false):string[]=>{\n  if(!Array.isArray(v)||(!allowEmpty&&!v.length)||v.length>256)throw new Error(`Invalid ${field}.`);\n  const out=v.map(x=>text(x,field));if(new Set(out).size!==out.length)throw new Error(`Duplicate ${field}.`);return out;\n};\nconst boolean = (v:unknown,field:string):boolean=>{if(typeof v!=='boolean')throw new Error(`Invalid ${field}.`);return v;};\nconst optionalNumber=(o:Record<string,unknown>,key:string)=>o[key]===undefined?undefined:num(o[key],key);\nfunction onlyKeys(o:Record<string,unknown>,keys:string[],label:string):void{\n  for(const k of Object.keys(o))if(!keys.includes(k))throw new Error(`Unknown ${label} field: ${k}.`);\n}\n/** All unrecognized fields are rejected, never secretly retained in metadata-only evidence. */\nexport function validateObservation(input:unknown):Observation {\n  const o=object(input,'observation');\n  onlyKeys(o,['version','id','runId','operationId','sourceId','epoch','sequence','time','receivedAt','subject','stage','surface','action','effect','status','target','actionDigest','authority','correlation','facts'],'observation');\n  if(o.version!==1)throw new Error('Unsupported observation version.');\n  const t=object(o.time,'time'),s=object(o.subject,'subject');\n  onlyKeys(t,['wallMs','clockId','monotonicMs','taskMs','uncertaintyMs'],'time');onlyKeys(s,['agentId','sandboxId','isolationGroup'],'subject');\n  const seq=num(o.sequence,'sequence',1);if(!Number.isSafeInteger(seq))throw new Error('sequence must be an integer.');\n  const facts=object(o.facts??{},'facts'),safeFacts:Observation['facts']={};\n  if(Object.keys(facts).length>48)throw new Error('Too many observation facts.');\n  for(const [k,v] of Object.entries(facts)){\n    text(k,'fact key',80);if(['__proto__','prototype','constructor'].includes(k))throw new Error('Unsafe fact key.');\n    if(typeof v==='string')safeFacts[k]=text(v,'fact value',512);\n    else if(typeof v==='number')safeFacts[k]=num(v,'fact value',-Number.MAX_SAFE_INTEGER);\n    else if(v===null||typeof v==='boolean')safeFacts[k]=v;\n    else throw new Error('Facts must be scalar metadata, not content objects.');\n  }","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/evidence.ts#L62-L98","documentation":"`onlyKeys` enforces a closed schema: any key on the input object not in the allowed list throws `Unknown ${label} field: ${k}.`. This library rejects unrecognized fields outright (never stores them silently), so typo'd or extra keys fail fast. `label` tells you which object (observation, time, subject, source, grant, target, ...) rejected it.","triggerScenarios":"validateObservation/validatePolicy/validateBundle given an object containing an extra or misspelled key, e.g. `runid` instead of `runId`, or a leftover key like `notes` on a source/grant; nested objects (time, subject, target, authority, correlation, facts containers) are checked with their own key lists.","commonSituations":"Older bundle format field removed in v1; typo in a hand-edited policy; adding custom metadata keys that the schema does not allow (use `facts` for that in observations); a producer upgraded ahead of the schema.","solutions":["Remove the unknown key from the input object","Fix the key's spelling/casing to match the allowed list shown in the error","Move free-form metadata into `facts` (observations) or drop it; check the library's current schema for the renamed field"],"exampleFix":"// before\n{\"id\":\"s1\",\"stage\":\"plan\",\"descripton\":\"oops\"}\n// after\n{\"id\":\"s1\",\"stage\":\"plan\",\"description\":\"fixed typo\"}","handlingStrategy":"validation","validationCode":"const allowed = ['version','id','sources','grants','expectedSurfaces','forbiddenCrossGroup']; for (const k of Object.keys(policy)) if (!allowed.includes(k)) console.warn(`Unknown policy field: ${k}`);","typeGuard":"const onlyKnownKeys = (o: object, keys: string[]) => Object.keys(o).every(k => keys.includes(k));","tryCatchPattern":"try { validateObservation(rec); } catch (e) { const m = e.match?.(/^Unknown (\\w+) field: (\\w+)/) ?? /^Unknown (\\w+) field: (\\w+)/.exec(e.message); if (m) console.warn(`Strip or rename '${m[2]}' on ${m[1]}`); throw e; }","preventionTips":["Keep client types in sync with the library's schema after upgrades","Use TypeScript types derived from the library instead of hand-written ones","Move custom metadata into the designated extension field (facts)","Read the allowed-keys list in the error message — it names the offending key"],"tags":["validation","schema","strict-fields","typescript"],"backgroundTag":"schema-validation-failed","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}