{"record":{"id":"07f8320e2e6b98a0","repo":"headroomlabs-ai/headroom","slug":"rollout-worker-registry-digest-mismatch","errorCode":null,"errorMessage":"rollout worker registry digest mismatch","messagePattern":"rollout worker registry digest mismatch","errorType":"exception","errorClass":"RolloutConfigurationError","httpStatus":null,"severity":"error","filePath":"headroom/rollout.py","lineNumber":317,"sourceCode":"\n            def names(field: str) -> set[str]:\n                raw = value[field]\n                if not isinstance(raw, list) or not all(isinstance(item, str) for item in raw):\n                    raise RolloutConfigurationError(f\"invalid rollout worker field {field!r}\")\n                return set(_validate_names(set(raw), source=field, strict=True))\n\n            snapshot = _resolve_snapshot(\n                channel=channel,\n                explicit_requested=names(\"explicit_requested\"),\n                explicit_disabled=names(\"explicit_disabled\"),\n                legacy_requested=names(\"legacy_requested\"),\n                legacy_disabled=names(\"legacy_disabled\"),\n                unsafe=unsafe,\n            )\n        except (KeyError, TypeError) as exc:\n            raise RolloutConfigurationError(\"invalid rollout worker snapshot\") from exc\n        if value.get(\"registry_digest\") != snapshot.registry_digest:\n            raise RolloutConfigurationError(\"rollout worker registry digest mismatch\")\n        if value.get(\"snapshot_digest\") != snapshot.snapshot_digest:\n            raise RolloutConfigurationError(\"rollout worker snapshot digest mismatch\")\n        return snapshot\n\n    def with_legacy_env(self, environ: Mapping[str, str]) -> RolloutSnapshot:\n        \"\"\"Return a new snapshot after applying supplied legacy alias values.\n\n        This intentionally supports existing hot-reloadable aliases without\n        re-reading ambient process state or weakening named disable precedence.\n        Both the old and new snapshots remain immutable, so requests observe a\n        complete policy rather than partially updated fields.\n        \"\"\"\n\n        legacy_requested = set(self.config.legacy_requested)\n        legacy_disabled = set(self.config.legacy_disabled)\n        for spec in FEATURES.values():\n            for alias in spec.legacy_env:\n                if alias not in environ:","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/rollout.py#L299-L335","documentation":"Raised by from_internal_dict() when the restored dict's 'registry_digest' does not match the digest recomputed from the restored snapshot's feature registry. The digest ties the handoff payload to the exact registry state, so tampering or partial updates to feature names are detected even when all fields individually validate.","triggerScenarios":"Restoring a snapshot whose feature-registry fields were edited (a feature name added/removed/renamed) without recomputing 'registry_digest', or a payload truncated/corrupted in transit through the queue.","commonSituations":"Middleware or ad-hoc scripts mutating handoff dicts; encoding corruption in queues; fixtures edited by hand to add a feature for testing.","solutions":["Never edit serialized snapshots; rebuild them via to_internal_dict() from a live snapshot.","If you must change feature sets, apply changes through the snapshot/config API and re-serialize.","Check transport integrity if payloads are edited by neither side (queue encoding, truncation)."],"exampleFix":"# before\npayload = snapshot.to_internal_dict()\npayload['explicit_requested'].append('new-feature')  # digest now stale\n\n# after\nnew_snapshot = snapshot.with_feature_requested('new-feature')\npayload = new_snapshot.to_internal_dict()","handlingStrategy":"validation","validationCode":"# There is no safe pre-check for digest equality except not mutating payloads.\n# Verify provenance instead:\nassert payload_source == 'RolloutSnapshot.to_internal_dict', 'handcrafted payload cannot match digests'","typeGuard":null,"tryCatchPattern":"try:\n    snapshot = RolloutSnapshot.from_internal_dict(payload)\nexcept RolloutConfigurationError as e:\n    if 'registry digest mismatch' in str(e):\n        logger.error('payload tampered or corrupted; requesting fresh handoff')\n        payload = request_fresh_handoff()\n        snapshot = RolloutSnapshot.from_internal_dict(payload)\n    else:\n        raise","preventionTips":["Treat handoff payloads as opaque — modify snapshots through the API and re-serialize.","Log digests at send/receive time to detect in-flight corruption.","Reject and re-request on any digest error instead of hand-fixing fields."],"tags":["integrity","rollout","digest","worker-handoff"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}