JuliusBrussee/caveman · error · Error
cave_privacy_conformance_failed
Error message
cave_privacy_conformance_failed
What it means
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.
Source
Thrown at packages/agent/src/cli.ts:750
evalDynamicKinds(approved),
{
...(loaded.config.allowedModels === undefined ? {} : { allowedModels: loaded.config.allowedModels }),
...(loaded.config.deniedModels === undefined ? {} : { deniedModels: loaded.config.deniedModels }),
...(loaded.config.forbiddenSafetyClasses === undefined
? {}
: { forbiddenSafetyClasses: loaded.config.forbiddenSafetyClasses }),
},
);
const entitled = await engineEntitled();
const plannedRuns = candidates.filter((candidate) => !candidate.static_rejection).length * approved.length * 5;
const estimatedCeiling = candidates
.filter((candidate) => !candidate.static_rejection)
.reduce((sum, candidate) => sum + candidate.estimated_cost_usd_per_run * approved.length * 5, 0);
process.stdout.write(`search ceiling: $${estimatedCeiling.toFixed(4)} public-catalog estimate · ${plannedRuns} runs\n`);
const sandboxConformance = await verifySandboxConformance();
if (!sandboxConformance) throw new Error("cave_sandbox_conformance_failed");
const privacyConformance = contextIRIsContentBlind(lowered.ir);
if (!privacyConformance) throw new Error("cave_privacy_conformance_failed");
const conversations = new Map<string, ConversationState>();
const result = await compileAndWrite({
agent: loaded.agent,
contextIR: lowered.ir,
evals: loaded.evals,
candidates,
baselinePlan: baseline,
seeds: [1, 2, 3, 4, 5],
config: loaded.config,
entitled,
sourceSha256: loaded.sourceSha256,
catalogSha256: CATALOG_SHA256,
transformRegistrySha256: transformRegistry.sha256,
runtimeVersion: FRAMEWORK_VERSION,
adapterVersion: PI_ADAPTER_VERSION,
upstreamVersion: PI_UPSTREAM_VERSION,
runner: async ({ plan, eval: fixture, seed, signal }) => runFixture(
root,View on GitHub (pinned to 27d5a3981a)
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.
Example fix
// before: hand-built segment leaks content
const segment = { id: "instructions", kind: "static", bodyHandle: "inline:text", provenanceDigest: "", text: "You are..." };
// after: register the body so lowering emits metadata only
const bodyHandle = await store.put("instructions", encode("You are..."));
const segment = { id: "instructions", kind: "static", bodyHandle, provenanceDigest: sha256(bytes) }; Defensive patterns
Strategy: validation
Validate before calling
import { lowerAgentContext } from "@caveman-ai/agent";
const ALLOWED = new Set(["id","kind","stability","safety","priority","recovery","cacheRegion","privacy","opaque","ttlTurns","provenanceDigest","tokenCount","bodyHandle"]);
function irIsContentBlind(ir: { segments: Array<Record<string, unknown>> }): boolean {
return ir.segments.every((s) =>
Object.keys(s).every((k) => ALLOWED.has(k)) &&
/^cave_local_sha256:[0-9a-f]{64}$/.test(String(s.bodyHandle)) &&
/^[0-9a-f]{64}$/.test(String(s.provenanceDigest)));
}
// before building:
const lowered = await lowerAgentContext(definition, { rootDir: root });
if (!irIsContentBlind(lowered.ir)) throw new Error("refusing to build: IR not content-blind"); Type guard
function isContentBlindSegment(segment: Record<string, unknown>): boolean {
return (
Object.keys(segment).every((k) => ALLOWED.has(k)) &&
typeof segment.bodyHandle === "string" &&
/^cave_local_sha256:[0-9a-f]{64}$/.test(segment.bodyHandle) &&
typeof segment.provenanceDigest === "string" &&
/^[0-9a-f]{64}$/.test(segment.provenanceDigest)
);
} Try / catch
try {
await build(args);
} catch (error) {
if (error instanceof Error && error.message === "cave_privacy_conformance_failed") {
// dump segment keys to find the offender, then fix the context source
} else throw error;
} Prevention
- 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.
When it happens
Trigger: 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`.
Common situations: 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".
Related errors
- caveman build: requiredFixturePassRate must be in (0,1]
- caveman build: qualityRetention must be in (0,1]
- cave_context_body_missing:${segment.id}
- cave_live_eval_sandbox_profile_missing
- invalid scoped package
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/c00ea57f45422206.
Report an issue: GitHub.