{"record":{"id":"4a3ea7597346bd69","repo":"abhigyanpatwari/GitNexus","slug":"candidate-overlay-digest-no-longer-matches-promoti","errorCode":null,"errorMessage":"candidate overlay digest no longer matches promotion evidence","messagePattern":"candidate overlay digest no longer matches promotion evidence","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/promotion_apply.py","lineNumber":626,"sourceCode":"            handle.flush()\n            os.fsync(handle.fileno())\n    finally:\n        os.close(descriptor)\n    os.fsync(root_descriptor)\n    return root_path / recovery_name\n\n\ndef apply_promoted_overlay(\n    overlay: Path,\n    repo_root: Path = REPO_ROOT,\n    *,\n    expected_digest: str | None = None,\n    expected_target_bases: dict[str, str] | None = None,\n) -> list[str]:\n    \"\"\"Compare-and-swap one evidence-bound overlay across every mirror.\"\"\"\n    digest, payload = candidate_overlay_payload(overlay)\n    if expected_digest is not None and digest != expected_digest:\n        raise ValueError(\"candidate overlay digest no longer matches promotion evidence\")\n\n    repo_root, root_descriptor, prepared = _prepare_targets(payload, repo_root)\n    current_bases = {item[\"target\"].as_posix(): item[\"base_digest\"] for item in prepared}\n    if expected_target_bases is not None and expected_target_bases != current_bases:\n        expected_paths = set(expected_target_bases)\n        current_paths = set(current_bases)\n        missing = sorted(current_paths - expected_paths)\n        unexpected = sorted(expected_paths - current_paths)\n        drifted = sorted(\n            path for path in current_paths & expected_paths if current_bases[path] != expected_target_bases[path]\n        )\n        details = []\n        if missing:\n            details.append(\"missing=\" + \",\".join(missing))\n        if unexpected:\n            details.append(\"unexpected=\" + \",\".join(unexpected))\n        if drifted:\n            details.append(\"drifted=\" + \",\".join(drifted))","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/promotion_apply.py#L608-L644","documentation":"Thrown at the top of `apply_promoted_overlay` when the caller passed `expected_digest` and the recomputed SHA-256 of the candidate overlay payload no longer matches it. The overlay file (its canonical bytes) changed between evidence capture and apply, so the promotion evidence is stale.","triggerScenarios":"Calling `apply_promoted_overlay(overlay, expected_digest=<d>)` after the overlay file was edited, regenerated, or re-serialized, so `candidate_overlay_payload(overlay)[0]` returns a different hash than the `expected_digest` recorded earlier.","commonSituations":"A build step rewrote the overlay between evidence capture and apply; two overlays share a path and the wrong one was passed; line-ending normalization changed the bytes; a formatter touched the file; the digest was captured from a different branch.","solutions":["Re-capture the evidence digest immediately before apply: `expected_digest = candidate_overlay_payload(overlay)[0]`, then call apply.","Confirm the overlay path is the exact file the evidence was recorded from (same inode, no regeneration in between).","Pin line endings and serialization (write the overlay once, treat it as immutable, hash it, and reference it by hash).","If the overlay legitimately changed, regenerate `expected_digest` and `expected_target_bases` together — never partially."],"exampleFix":"# before: stale digest from an earlier build\napply_promoted_overlay(overlay, expected_digest='abc123...')  # -> ValueError\n# after: capture the digest of the exact file you are about to apply\nfrom workflow_bench.promotion_apply import candidate_overlay_payload\ndigest, _ = candidate_overlay_payload(overlay)\napply_promoted_overlay(overlay, expected_digest=digest)","handlingStrategy":"validation","validationCode":"from workflow_bench.promotion_apply import candidate_overlay_payload\n\ndef capture_fresh_digest(overlay) -> str:\n    \"\"\"Capture the digest of the exact bytes you are about to apply.\"\"\"\n    return candidate_overlay_payload(overlay)[0]\n\n# use it immediately\ndigest = capture_fresh_digest(overlay)\napply_promoted_overlay(overlay, expected_digest=digest)","typeGuard":"null","tryCatchPattern":"try:\n    apply_promoted_overlay(overlay, expected_digest=expected)\nexcept ValueError as exc:\n    if \"digest no longer matches\" in str(exc):\n        # overlay changed: re-capture digest AND target bases together, then retry\n        from workflow_bench.promotion_apply import destination_base_digests\n        expected = candidate_overlay_payload(overlay)[0]\n        bases = destination_base_digests(overlay)\n    raise","preventionTips":["Treat the overlay file as immutable from digest capture through apply.","Generate the overlay, hash it, and pass the file path + hash together — never a hash captured at a different time.","Disable any formatter/normalizer that may rewrite the overlay between capture and apply."],"tags":["integrity","digest","cas","overlay"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}