{"record":{"id":"c00ea57f45422206","repo":"JuliusBrussee/caveman","slug":"cave-privacy-conformance-failed","errorCode":null,"errorMessage":"cave_privacy_conformance_failed","messagePattern":"cave_privacy_conformance_failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/cli.ts","lineNumber":750,"sourceCode":"    evalDynamicKinds(approved),\n    {\n      ...(loaded.config.allowedModels === undefined ? {} : { allowedModels: loaded.config.allowedModels }),\n      ...(loaded.config.deniedModels === undefined ? {} : { deniedModels: loaded.config.deniedModels }),\n      ...(loaded.config.forbiddenSafetyClasses === undefined\n        ? {}\n        : { forbiddenSafetyClasses: loaded.config.forbiddenSafetyClasses }),\n    },\n  );\n  const entitled = await engineEntitled();\n  const plannedRuns = candidates.filter((candidate) => !candidate.static_rejection).length * approved.length * 5;\n  const estimatedCeiling = candidates\n    .filter((candidate) => !candidate.static_rejection)\n    .reduce((sum, candidate) => sum + candidate.estimated_cost_usd_per_run * approved.length * 5, 0);\n  process.stdout.write(`search ceiling: $${estimatedCeiling.toFixed(4)} public-catalog estimate · ${plannedRuns} runs\\n`);\n  const sandboxConformance = await verifySandboxConformance();\n  if (!sandboxConformance) throw new Error(\"cave_sandbox_conformance_failed\");\n  const privacyConformance = contextIRIsContentBlind(lowered.ir);\n  if (!privacyConformance) throw new Error(\"cave_privacy_conformance_failed\");\n  const conversations = new Map<string, ConversationState>();\n  const result = await compileAndWrite({\n    agent: loaded.agent,\n    contextIR: lowered.ir,\n    evals: loaded.evals,\n    candidates,\n    baselinePlan: baseline,\n    seeds: [1, 2, 3, 4, 5],\n    config: loaded.config,\n    entitled,\n    sourceSha256: loaded.sourceSha256,\n    catalogSha256: CATALOG_SHA256,\n    transformRegistrySha256: transformRegistry.sha256,\n    runtimeVersion: FRAMEWORK_VERSION,\n    adapterVersion: PI_ADAPTER_VERSION,\n    upstreamVersion: PI_UPSTREAM_VERSION,\n    runner: async ({ plan, eval: fixture, seed, signal }) => runFixture(\n      root,","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/cli.ts#L732-L768","documentation":"Thrown by the CLI build path after lowering the agent context to Context IR. `contextIRIsContentBlind(lowered.ir)` verifies every segment carries only metadata keys from a fixed allowlist (id, kind, stability, safety, priority, recovery, cacheRegion, privacy, opaque, ttlTurns, provenanceDigest, tokenCount, bodyHandle), that `bodyHandle` matches `cave_local_sha256:<64 hex>`, and `provenanceDigest` is 64 hex. The build refuses to continue while the IR could leak content into the immutable lock digest.","triggerScenarios":"Calling `caveman build` (or the compile pipeline in `buildAgent`) when a lowered context segment has an extra key outside the allowlist, a `bodyHandle` not shaped `cave_local_sha256:<64-hex>`, or a `provenanceDigest` that is not a bare 64-hex sha256. Any single failing segment makes `ir.segments.every(...)` false and the CLI throws before `compileAndWrite`.","commonSituations":"Custom context sources or hand-built segment objects added to an agent definition; a new segment field introduced by an upstream framework version that the allowlist was not updated for; a body handle pointing at a remote/inline body instead of a locally hashed one; tests that construct fixture IRs with placeholder digests like \"test\".","solutions":["Log each segment's keys and digests to find the first offender: any key not in the allowlist, a bodyHandle missing the `cave_local_sha256:` prefix, or a non-hex provenanceDigest.","Remove or relocate content-bearing fields out of the segment; content belongs in the body store referenced by bodyHandle, never on the segment object.","Ensure bodies are registered through the framework's lowering API so bodyHandle/provenanceDigest are generated, not hand-authored.","If you extended the segment type in a fork, mirror the extension in the allowlist in `contextIRIsContentBlind` (cli.ts:1260) only after confirming the new field is content-blind."],"exampleFix":"// before: hand-built segment leaks content\nconst segment = { id: \"instructions\", kind: \"static\", bodyHandle: \"inline:text\", provenanceDigest: \"\", text: \"You are...\" };\n\n// after: register the body so lowering emits metadata only\nconst bodyHandle = await store.put(\"instructions\", encode(\"You are...\"));\nconst segment = { id: \"instructions\", kind: \"static\", bodyHandle, provenanceDigest: sha256(bytes) };","handlingStrategy":"validation","validationCode":"import { lowerAgentContext } from \"@caveman-ai/agent\";\n\nconst ALLOWED = new Set([\"id\",\"kind\",\"stability\",\"safety\",\"priority\",\"recovery\",\"cacheRegion\",\"privacy\",\"opaque\",\"ttlTurns\",\"provenanceDigest\",\"tokenCount\",\"bodyHandle\"]);\n\nfunction irIsContentBlind(ir: { segments: Array<Record<string, unknown>> }): boolean {\n  return ir.segments.every((s) =>\n    Object.keys(s).every((k) => ALLOWED.has(k)) &&\n    /^cave_local_sha256:[0-9a-f]{64}$/.test(String(s.bodyHandle)) &&\n    /^[0-9a-f]{64}$/.test(String(s.provenanceDigest)));\n}\n\n// before building:\nconst lowered = await lowerAgentContext(definition, { rootDir: root });\nif (!irIsContentBlind(lowered.ir)) throw new Error(\"refusing to build: IR not content-blind\");","typeGuard":"function isContentBlindSegment(segment: Record<string, unknown>): boolean {\n  return (\n    Object.keys(segment).every((k) => ALLOWED.has(k)) &&\n    typeof segment.bodyHandle === \"string\" &&\n    /^cave_local_sha256:[0-9a-f]{64}$/.test(segment.bodyHandle) &&\n    typeof segment.provenanceDigest === \"string\" &&\n    /^[0-9a-f]{64}$/.test(segment.provenanceDigest)\n  );\n}","tryCatchPattern":"try {\n  await build(args);\n} catch (error) {\n  if (error instanceof Error && error.message === \"cave_privacy_conformance_failed\") {\n    // dump segment keys to find the offender, then fix the context source\n  } else throw error;\n}","preventionTips":["Always create segments through the framework lowering API; never hand-author bodyHandle or provenanceDigest.","Keep content bytes in the body store referenced by bodyHandle, never as fields on the segment.","Add a unit test asserting your agent's lowered IR passes the same allowlist check before integrating."],"tags":["privacy","context-ir","build","validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}