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

  1. 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.
  2. Remove or relocate content-bearing fields out of the segment; content belongs in the body store referenced by bodyHandle, never on the segment object.
  3. Ensure bodies are registered through the framework's lowering API so bodyHandle/provenanceDigest are generated, not hand-authored.
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/c00ea57f45422206. Report an issue: GitHub.