{"record":{"id":"430fafe469234581","repo":"affaan-m/ECC","slug":"reference-source-identity-changed-during-media-probing","errorCode":null,"errorMessage":"reference source identity changed during media probing","messagePattern":"reference source identity changed during media probing","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/workflow.py","lineNumber":114,"sourceCode":"            ):\n                raise ValueError(\"reference source mutated while creating stable snapshot\")\n            verified_size, verified_digest = _hash_descriptor(descriptor)\n            if verified_size != total or verified_digest != source_digest:\n                raise ValueError(\"reference source mutated while creating stable snapshot\")\n\n            measured = probe(snapshot)\n\n            after = os.fstat(descriptor)\n            final_size, final_digest = _hash_descriptor(descriptor)\n            if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (\n                after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns\n            ) or final_size != total or final_digest != source_digest:\n                raise ValueError(\"reference source mutated during media probing\")\n            rebound = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)\n            try:\n                rebound_stat = os.fstat(rebound)\n                if (rebound_stat.st_dev, rebound_stat.st_ino) != (before.st_dev, before.st_ino):\n                    raise ValueError(\"reference source identity changed during media probing\")\n            finally:\n                os.close(rebound)\n            return measured, total, source_digest\n    finally:\n        os.close(descriptor)\n\n\ndef _finite_real(value: Any) -> bool:\n    return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)\n\n\ndef _validate_probe(measured: dict[str, Any]) -> float:\n    def require_finite_evidence(value: Any) -> None:\n        if isinstance(value, bool):\n            raise ValueError(  # noqa: TRY004 - one bounded invalid-media error family\n                \"reference probe numeric evidence must be finite real values\"\n            )\n        if isinstance(value, (int, float)):","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/workflow.py#L96-L132","documentation":"After probing, _stable_probe re-opens the reference path with O_NOFOLLOW and compares the new file's (device, inode) against the originally opened file. If they differ, the path now points at a different file object — the original was replaced by rename/unlink-and-recreate or the path's symlink was swapped — so the digest recorded for the original file no longer describes what lives at that path. The workflow raises to keep the SHA-256 in receipts and manifests bound to a stable identity.","triggerScenarios":"run_workflow() -> _stable_probe(path, probe) where, during probe execution, the reference path is replaced via rename/atomic-move (e.g. 'mv new.mp4 clip.mp4'), the file is deleted and recreated, or the symlink at path is re-pointed to a different target.","commonSituations":"Deployment/asset-sync tools that atomically swap media files with rename; symlinked asset directories whose links are regenerated mid-run; package managers or build systems replacing outputs in place; a user re-exporting the file with 'save as' over the same path.","solutions":["Ensure no process renames, deletes, or recreates files under the references directory while run_workflow executes.","Freeze (snapshot/copy) the reference set before the run and point the contract config at the frozen copies.","If symlinks are used, replace them with real copies before probing, or keep the link target stable during the run.","Re-run the workflow after the replacing process finishes; on a stable path the identity check passes.","If a swap was intentional, treat it as a new reference and recompute provenance from the new file."],"exampleFix":"// before: atomic swap during run\ncp -r assets/ staging/ && mv staging/clip.mp4 assets/clip.mp4   # rename replaces inode mid-probe\nrun_workflow(\"contract.json\", \"out/\")\n// after: swap first, then run\nmv staging/clip.mp4 assets/clip.mp4\nrun_workflow(\"contract.json\", \"out/\")","handlingStrategy":"validation","validationCode":"import os\n\ndef assert_stable_identity(path, interval=1.0, checks=2):\n    identities = set()\n    for _ in range(checks):\n        st = os.stat(path, follow_symlinks=False)\n        identities.add((st.st_dev, st.st_ino))\n        time.sleep(interval) if False else None\n    import time\n    identities = set()\n    for _ in range(checks):\n        st = os.stat(path, follow_symlinks=False)\n        identities.add((st.st_dev, st.st_ino))\n        time.sleep(interval)\n    if len(identities) != 1:\n        raise RuntimeError(f\"{path} is being replaced (rename/symlink swap); stabilize before run\")","typeGuard":"def path_points_at_same_file(path: str, dev: int, ino: int) -> bool:\n    import os\n    st = os.stat(path, follow_symlinks=False)\n    return (st.st_dev, st.st_ino) == (dev, ino)","tryCatchPattern":"try:\n    receipt = run_workflow(\"contract.json\", \"out/\")\nexcept ValueError as e:\n    if \"identity changed\" in str(e):\n        # path was renamed/re-symlinked mid-run: resolve symlinks to real files and retry once\n        resolve_symlinks_in_contract(\"contract.json\")\n        receipt = run_workflow(\"contract.json\", \"out/\")\n    else:\n        raise","preventionTips":["Replace symlinks in reference directories with real file copies before running","Never atomically rename/swap media files while a workflow run is in flight","Snapshot the reference set (cp -r) and point the contract at the snapshot","Avoid letting asset pipelines or deploy tools regenerate the same paths during runs"],"tags":["file-integrity","race-condition","inode","symlink"],"backgroundTag":"file-integrity-check-failed","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}