{"record":{"id":"93b72b7bede0ba7a","repo":"ruvnet/RuView","slug":"invalid-privacy-class-s-r-expected-one-of-list","errorCode":null,"errorMessage":"invalid privacy class {s!r}; expected one of {list(m.keys())}","messagePattern":"invalid privacy class (.+?); expected one of (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/c6-presence-watcher.py","lineNumber":82,"sourceCode":"    \"\"\"\n    RAW = 0\n    DERIVED = 1\n    ANONYMOUS = 2\n    RESTRICTED = 3\n\n    _names = {RAW: \"Raw\", DERIVED: \"Derived\", ANONYMOUS: \"Anonymous\",\n              RESTRICTED: \"Restricted\"}\n\n    @classmethod\n    def name(cls, value: int) -> str:\n        return cls._names.get(value, f\"Unknown({value})\")\n\n    @classmethod\n    def from_str(cls, s: str) -> int:\n        m = {\"raw\": cls.RAW, \"derived\": cls.DERIVED,\n             \"anonymous\": cls.ANONYMOUS, \"restricted\": cls.RESTRICTED}\n        if s.lower() not in m:\n            raise ValueError(f\"invalid privacy class {s!r}; \"\n                             f\"expected one of {list(m.keys())}\")\n        return m[s.lower()]\n\n    @classmethod\n    def allows_hap(cls, value: int) -> bool:\n        \"\"\"ADR-125 §2.1.d gate: only class-2/3 cross the HomeKit boundary.\"\"\"\n        return value in (cls.ANONYMOUS, cls.RESTRICTED)\n\n\n# Semantic-event naming per ADR-125 §2.1.d. The HAP bridge keeps\n# advertising a generic MotionSensor; this is the operator-facing\n# *label* for the event, written into the watcher log + summary line\n# so the operator never sees \"intruder detected\" framing.\nSEMANTIC_EVENT_UNKNOWN_PRESENCE = \"Unknown Presence\"\n\n# Hysteresis — entry / exit thresholds keep the HomeKit characteristic\n# from flapping when presence_score sits near the boundary.\nPRESENCE_ON_THRESHOLD = 0.40","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/scripts/c6-presence-watcher.py#L64-L100","documentation":"PrivacyClass.from_str parses the --privacy-class CLI/config value into the integer privacy classes used by the ADR-125 §2.1.d HomeKit/HAP gate. Only raw, derived, anonymous, restricted (case-insensitive) are accepted; any other string raises ValueError listing the valid set. Only anonymous (2) and restricted (3) may cross the HAP boundary (allows_hap).","triggerScenarios":"`python scripts/c6-presence-watcher.py --privacy-class public` (or 'anon', 'PII', any non-listed token); an automation or config file feeding an old/renamed class name into from_str at line 213.","commonSituations":"Renamed classes after an ADR update; typos in wrapper scripts; runbooks with stale example commands.","solutions":["Use one of raw, derived, anonymous, restricted (the CLI default is anonymous)","For HomeKit exposure choose anonymous or restricted — raw/derived fail the allows_hap gate and the watcher refuses to start","Fix the calling script/config that produced the invalid token"],"exampleFix":"# before\npython scripts/c6-presence-watcher.py --privacy-class public  # ValueError\n\n# after\npython scripts/c6-presence-watcher.py --privacy-class anonymous","handlingStrategy":"validation","validationCode":"VALID_PRIVACY = {\"raw\", \"derived\", \"anonymous\", \"restricted\"}\n\ndef validate_privacy_class(value: str) -> str:\n    if value.lower() not in VALID_PRIVACY:\n        raise SystemExit(\n            f\"invalid privacy class {value!r}; \"\n            f\"expected one of {sorted(VALID_PRIVACY)}\"\n        )\n    return value.lower()","typeGuard":"def is_valid_privacy_class(s: str) -> bool:\n    return isinstance(s, str) and s.lower() in {\n        \"raw\", \"derived\", \"anonymous\", \"restricted\"\n    }","tryCatchPattern":"try:\n    privacy_class = PrivacyClass.from_str(raw_value)\nexcept ValueError as e:\n    raise SystemExit(f\"bad --privacy-class value: {e}\") from e","preventionTips":["Validate config values at load time, before the watcher starts","Keep valid class names in one constants module shared by wrappers","Remember the HAP gate: only anonymous/restricted are HomeKit-eligible"],"tags":["python","cli","config","validation","privacy","adr-125"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}