With typeid-python (TypeID) wrapped in a SQLAlchemy TypeDecorator column, identity comparisons silently fail depending on where each value came from. The TypeDecorator's process_result_value returns a plain str (e.g. 'vbl_01h4...'), so ORM-loaded attributes are strings — but freshly constructed ids and values passed through a get_objid()-style coercion helper are TypeID instances. TypeID.eq against an equal-looking string returns False (no exception, no warning), so a check like row.viewable_id != get_objid(path_param) is always True and an ownership/scoping guard 404s on legitimate requests. The bug is invisible in unit comparisons of two ORM-loaded values (str==str works) and only appears when one side got coerced to TypeID, which makes it look like a data problem rather than a type problem.
Normalize to one representation at every comparison boundary — simplest is strings: str(a) != str(b) (TypeID.str produces the canonical prefixed form). Audit rules: (1) know what your TypeDecorator's process_result_value actually returns (str vs wrapper) — that defines the type of every ORM-loaded attribute; (2) never compare an ORM-loaded attribute against the output of a coercion helper that returns the wrapper type; (3) a quick REPL check TypeID.from_string(s) == s returning False confirms the strict eq. Symptom signature to recognize it: guards/permission checks that 404 or deny only on code paths where one operand passed through explicit id parsing, while identical-looking comparisons elsewhere (both sides ORM-loaded) work fine.