JuliusBrussee/caveman · error · Error

${where} change set or eval pack is bound to another case

Error message

${where} change set or eval pack is bound to another case

What it means

Thrown when change_set.case_id or eval_pack.case_id differs from the enclosing case's id. Every change set and eval pack is private to exactly one case; a mismatched binding means the artifacts were copied from another case and would attribute one case's proposal/evaluation to another.

Source

Thrown at packages/shared/contracts/scripts/validate-continuous-improvement.mjs:222

      }
    }
    const upstream = new Set(item.diagnosis.root_cause_hypothesis.upstream_node_ids);
    for (const nodeID of upstream) {
      if (!slice.node_chain.includes(nodeID) || nodeID === slice.manifestation_node_id) {
        throw new Error(`${where} diagnosis upstream node ${nodeID} is not an upstream node of its causal slice`);
      }
    }

    // A case that proposes nothing must carry nothing: no operator, no eval
    // pack, no replay. Anything else would present an abstention as a proposal.
    if (item.status === "diagnosis_only") {
      if (item.change_set.operator_id !== "" || item.eval_pack.id !== "" || item.replay.status !== "not_run") {
        throw new Error(`${where} is diagnosis-only but carries a change set, eval pack or replay`);
      }
      continue;
    }
    if (item.change_set.case_id !== item.id || item.eval_pack.case_id !== item.id) {
      throw new Error(`${where} change set or eval pack is bound to another case`);
    }

    // The case must investigate the pair its own opportunity named. A cohort
    // built from a differently-derived pair would describe one comparison and
    // measure another.
    const source = report.opportunities.find((opportunity) => opportunity.id === item.opportunity_id);
    if (!source) throw new Error(`${where} names an opportunity that is not in this report`);
    if (armVariants.get("baseline").id !== source.current_variant_id || armVariants.get("alternative").id !== source.alternative_variant_id) {
      throw new Error(`${where} cohort arms (${armVariants.get("baseline").id}/${armVariants.get("alternative").id}) are not the variants opportunity ${source.id} named`);
    }

    // Every required grader is one the recorded interpreter actually computes.
    // `json_schema` is deliberately absent: over a typed interpreter result it
    // could only assert that a hash is non-empty, which is a grader that cannot
    // fail and therefore inflates how much a replay was checked.
    const knownGraders = new Set(["exact_match", "tool_order", "tool_count", "guard_respected"]);
    for (const grader of item.eval_pack.graders) {
      if (!knownGraders.has(grader)) throw new Error(`${where} requires grader ${grader}, which the recorded interpreter does not compute`);

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Set item.change_set.case_id and item.eval_pack.case_id to the enclosing case's item.id.
  2. Prefer generating new cases programmatically over copy-paste so nested ids are always rewritten.
  3. Re-run the validator to catch any other nested ids that still reference the old case.

Example fix

// before
case id: "case-9"
change_set: {case_id: "case-8", ...}
eval_pack: {case_id: "case-9", ...}

// after
change_set: {case_id: "case-9", ...}
eval_pack: {case_id: "case-9", ...}
Defensive patterns

Strategy: validation

Validate before calling

const bound = item.change_set.case_id === item.id && item.eval_pack.case_id === item.id;

Prevention

When it happens

Trigger: Cloning a case (including its change_set and eval_pack) to create a new case with a new id without updating the nested case_id fields.

Common situations: Duplicating cases in the report fixture to grow the corpus; splitting one case into two; renaming case ids during a refactor.

Related errors


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