1*523fa7a6SAndroid Build Coastguard Worker# Copyright (c) Meta Platforms, Inc. and affiliates. 2*523fa7a6SAndroid Build Coastguard Worker# All rights reserved. 3*523fa7a6SAndroid Build Coastguard Worker# 4*523fa7a6SAndroid Build Coastguard Worker# This source code is licensed under the BSD-style license found in the 5*523fa7a6SAndroid Build Coastguard Worker# LICENSE file in the root directory of this source tree. 6*523fa7a6SAndroid Build Coastguard Worker 7*523fa7a6SAndroid Build Coastguard Workerimport dataclasses 8*523fa7a6SAndroid Build Coastguard Workerimport hashlib 9*523fa7a6SAndroid Build Coastguard Workerimport re 10*523fa7a6SAndroid Build Coastguard Workerimport typing 11*523fa7a6SAndroid Build Coastguard Workerfrom enum import IntEnum 12*523fa7a6SAndroid Build Coastguard Workerfrom typing import Any, Dict, Optional, Union 13*523fa7a6SAndroid Build Coastguard Worker 14*523fa7a6SAndroid Build Coastguard Workerfrom torch._export.serde import schema 15*523fa7a6SAndroid Build Coastguard Workerfrom torch._export.serde.union import _Union 16*523fa7a6SAndroid Build Coastguard Worker 17*523fa7a6SAndroid Build Coastguard Worker 18*523fa7a6SAndroid Build Coastguard Workerclass SchemaUpdateError(Exception): 19*523fa7a6SAndroid Build Coastguard Worker pass 20*523fa7a6SAndroid Build Coastguard Worker 21*523fa7a6SAndroid Build Coastguard Worker 22*523fa7a6SAndroid Build Coastguard Workerdef _check(x, msg): 23*523fa7a6SAndroid Build Coastguard Worker if not x: 24*523fa7a6SAndroid Build Coastguard Worker raise SchemaUpdateError(msg) 25*523fa7a6SAndroid Build Coastguard Worker 26*523fa7a6SAndroid Build Coastguard Worker 27*523fa7a6SAndroid Build Coastguard Workerdef _staged_schema(): 28*523fa7a6SAndroid Build Coastguard Worker ret: Dict[str, Any] = {} 29*523fa7a6SAndroid Build Coastguard Worker defs = {} 30*523fa7a6SAndroid Build Coastguard Worker 31*523fa7a6SAndroid Build Coastguard Worker def _handle_aggregate(ty): 32*523fa7a6SAndroid Build Coastguard Worker def dump_type(t): 33*523fa7a6SAndroid Build Coastguard Worker if isinstance(t, type): 34*523fa7a6SAndroid Build Coastguard Worker return t.__name__ 35*523fa7a6SAndroid Build Coastguard Worker elif isinstance(t, str): 36*523fa7a6SAndroid Build Coastguard Worker assert t in defs 37*523fa7a6SAndroid Build Coastguard Worker return t 38*523fa7a6SAndroid Build Coastguard Worker elif o := typing.get_origin(t): 39*523fa7a6SAndroid Build Coastguard Worker # Lemme know if there's a better way to do this. 40*523fa7a6SAndroid Build Coastguard Worker if o == list: 41*523fa7a6SAndroid Build Coastguard Worker head = "List" 42*523fa7a6SAndroid Build Coastguard Worker elif o == dict: 43*523fa7a6SAndroid Build Coastguard Worker head = "Dict" 44*523fa7a6SAndroid Build Coastguard Worker elif o == tuple: 45*523fa7a6SAndroid Build Coastguard Worker if typing.get_args(t) == (): 46*523fa7a6SAndroid Build Coastguard Worker return "Tuple[()]" 47*523fa7a6SAndroid Build Coastguard Worker head = "Tuple" 48*523fa7a6SAndroid Build Coastguard Worker elif o == Union: 49*523fa7a6SAndroid Build Coastguard Worker args = typing.get_args(t) 50*523fa7a6SAndroid Build Coastguard Worker assert len(args) == 2 and args[1] == type(None) 51*523fa7a6SAndroid Build Coastguard Worker return f"Optional[{dump_type(args[0])}]" 52*523fa7a6SAndroid Build Coastguard Worker else: 53*523fa7a6SAndroid Build Coastguard Worker raise AssertionError(f"Type {t} is not supported in export schema.") 54*523fa7a6SAndroid Build Coastguard Worker return ( 55*523fa7a6SAndroid Build Coastguard Worker f"{head}[{', '.join([dump_type(x) for x in typing.get_args(t)])}]" 56*523fa7a6SAndroid Build Coastguard Worker ) 57*523fa7a6SAndroid Build Coastguard Worker elif t == (): 58*523fa7a6SAndroid Build Coastguard Worker return "()" 59*523fa7a6SAndroid Build Coastguard Worker else: 60*523fa7a6SAndroid Build Coastguard Worker raise AssertionError(f"Type {t} is not supported in export schema.") 61*523fa7a6SAndroid Build Coastguard Worker 62*523fa7a6SAndroid Build Coastguard Worker def dump_field(f): 63*523fa7a6SAndroid Build Coastguard Worker t = dump_type(f.type) 64*523fa7a6SAndroid Build Coastguard Worker ret = {"type": t} 65*523fa7a6SAndroid Build Coastguard Worker 66*523fa7a6SAndroid Build Coastguard Worker value = dataclasses.MISSING 67*523fa7a6SAndroid Build Coastguard Worker if f.default is not dataclasses.MISSING: 68*523fa7a6SAndroid Build Coastguard Worker value = f.default 69*523fa7a6SAndroid Build Coastguard Worker elif f.default_factory is not dataclasses.MISSING: 70*523fa7a6SAndroid Build Coastguard Worker value = f.default_factory() 71*523fa7a6SAndroid Build Coastguard Worker 72*523fa7a6SAndroid Build Coastguard Worker if t.startswith("Optional[") and value is not None: 73*523fa7a6SAndroid Build Coastguard Worker raise AssertionError( 74*523fa7a6SAndroid Build Coastguard Worker f"Optional field {ty.__name__}.{f.name} must have default value to be None." 75*523fa7a6SAndroid Build Coastguard Worker ) 76*523fa7a6SAndroid Build Coastguard Worker 77*523fa7a6SAndroid Build Coastguard Worker if value is not dataclasses.MISSING: 78*523fa7a6SAndroid Build Coastguard Worker default = str(value) 79*523fa7a6SAndroid Build Coastguard Worker ret["default"] = default 80*523fa7a6SAndroid Build Coastguard Worker return ret 81*523fa7a6SAndroid Build Coastguard Worker 82*523fa7a6SAndroid Build Coastguard Worker return {f.name: dump_field(f) for f in dataclasses.fields(ty)} 83*523fa7a6SAndroid Build Coastguard Worker 84*523fa7a6SAndroid Build Coastguard Worker def _handle_int_enum(name, ty): 85*523fa7a6SAndroid Build Coastguard Worker ret[name] = {"kind": "enum", "fields": {x.name: x.value for x in ty}} 86*523fa7a6SAndroid Build Coastguard Worker 87*523fa7a6SAndroid Build Coastguard Worker def _handle_struct(name, ty): 88*523fa7a6SAndroid Build Coastguard Worker ret[name] = {"kind": "struct", "fields": _handle_aggregate(ty)} 89*523fa7a6SAndroid Build Coastguard Worker 90*523fa7a6SAndroid Build Coastguard Worker def _handle_union(name, ty): 91*523fa7a6SAndroid Build Coastguard Worker ret[name] = {"kind": "union", "fields": _handle_aggregate(ty)} 92*523fa7a6SAndroid Build Coastguard Worker 93*523fa7a6SAndroid Build Coastguard Worker for name in dir(schema): 94*523fa7a6SAndroid Build Coastguard Worker if name.startswith("_"): 95*523fa7a6SAndroid Build Coastguard Worker continue 96*523fa7a6SAndroid Build Coastguard Worker 97*523fa7a6SAndroid Build Coastguard Worker value = getattr(schema, name) 98*523fa7a6SAndroid Build Coastguard Worker 99*523fa7a6SAndroid Build Coastguard Worker if hasattr(value, "__module__") and value.__module__ != schema.__name__: 100*523fa7a6SAndroid Build Coastguard Worker continue 101*523fa7a6SAndroid Build Coastguard Worker 102*523fa7a6SAndroid Build Coastguard Worker defs[name] = value 103*523fa7a6SAndroid Build Coastguard Worker 104*523fa7a6SAndroid Build Coastguard Worker for name, value in defs.items(): 105*523fa7a6SAndroid Build Coastguard Worker if isinstance(value, type): 106*523fa7a6SAndroid Build Coastguard Worker if issubclass(value, IntEnum): 107*523fa7a6SAndroid Build Coastguard Worker _handle_int_enum(name, value) 108*523fa7a6SAndroid Build Coastguard Worker elif dataclasses.is_dataclass(value): 109*523fa7a6SAndroid Build Coastguard Worker if issubclass(value, _Union): 110*523fa7a6SAndroid Build Coastguard Worker _handle_union(name, value) 111*523fa7a6SAndroid Build Coastguard Worker else: 112*523fa7a6SAndroid Build Coastguard Worker _handle_struct(name, value) 113*523fa7a6SAndroid Build Coastguard Worker else: 114*523fa7a6SAndroid Build Coastguard Worker raise AssertionError(f"Unknown schema type {name}: {value}") 115*523fa7a6SAndroid Build Coastguard Worker elif isinstance(value, (int, tuple)): 116*523fa7a6SAndroid Build Coastguard Worker assert name in ("SCHEMA_VERSION", "TREESPEC_VERSION") 117*523fa7a6SAndroid Build Coastguard Worker else: 118*523fa7a6SAndroid Build Coastguard Worker raise AssertionError(f"Unknown variable {name}: {value}") 119*523fa7a6SAndroid Build Coastguard Worker 120*523fa7a6SAndroid Build Coastguard Worker ret["SCHEMA_VERSION"] = list(defs["SCHEMA_VERSION"]) 121*523fa7a6SAndroid Build Coastguard Worker assert all(x > 0 for x in ret["SCHEMA_VERSION"]) 122*523fa7a6SAndroid Build Coastguard Worker ret["TREESPEC_VERSION"] = defs["TREESPEC_VERSION"] 123*523fa7a6SAndroid Build Coastguard Worker assert ret["TREESPEC_VERSION"] > 0 124*523fa7a6SAndroid Build Coastguard Worker return ret 125*523fa7a6SAndroid Build Coastguard Worker 126*523fa7a6SAndroid Build Coastguard Worker 127*523fa7a6SAndroid Build Coastguard Workerdef _diff_schema(dst, src): 128*523fa7a6SAndroid Build Coastguard Worker additions = {key: src[key] for key in src.keys() - dst.keys()} 129*523fa7a6SAndroid Build Coastguard Worker subtractions = {key: dst[key] for key in dst.keys() - src.keys()} 130*523fa7a6SAndroid Build Coastguard Worker 131*523fa7a6SAndroid Build Coastguard Worker common_keys = src.keys() & dst.keys() 132*523fa7a6SAndroid Build Coastguard Worker 133*523fa7a6SAndroid Build Coastguard Worker versions = {"SCHEMA_VERSION", "TREESPEC_VERSION"} 134*523fa7a6SAndroid Build Coastguard Worker common_keys -= versions 135*523fa7a6SAndroid Build Coastguard Worker 136*523fa7a6SAndroid Build Coastguard Worker for key in common_keys: 137*523fa7a6SAndroid Build Coastguard Worker src_kind = src[key]["kind"] 138*523fa7a6SAndroid Build Coastguard Worker src_fields = src[key]["fields"] 139*523fa7a6SAndroid Build Coastguard Worker dst_kind = dst[key]["kind"] 140*523fa7a6SAndroid Build Coastguard Worker dst_fields = dst[key]["fields"] 141*523fa7a6SAndroid Build Coastguard Worker _check( 142*523fa7a6SAndroid Build Coastguard Worker src_kind == dst_kind, 143*523fa7a6SAndroid Build Coastguard Worker f"Type {key} changed kind from {dst_kind} to {src_kind}", 144*523fa7a6SAndroid Build Coastguard Worker ) 145*523fa7a6SAndroid Build Coastguard Worker assert isinstance(src_fields, dict) and isinstance(dst_fields, dict) 146*523fa7a6SAndroid Build Coastguard Worker added_fields = { 147*523fa7a6SAndroid Build Coastguard Worker key: src_fields[key] for key in src_fields.keys() - dst_fields.keys() 148*523fa7a6SAndroid Build Coastguard Worker } 149*523fa7a6SAndroid Build Coastguard Worker subtracted_fields = { 150*523fa7a6SAndroid Build Coastguard Worker key: dst_fields[key] for key in dst_fields.keys() - src_fields.keys() 151*523fa7a6SAndroid Build Coastguard Worker } 152*523fa7a6SAndroid Build Coastguard Worker common_fields = src_fields.keys() & dst_fields.keys() 153*523fa7a6SAndroid Build Coastguard Worker 154*523fa7a6SAndroid Build Coastguard Worker for field in common_fields: 155*523fa7a6SAndroid Build Coastguard Worker src_field = src_fields[field] 156*523fa7a6SAndroid Build Coastguard Worker dst_field = dst_fields[field] 157*523fa7a6SAndroid Build Coastguard Worker if src_kind == "struct": 158*523fa7a6SAndroid Build Coastguard Worker _check( 159*523fa7a6SAndroid Build Coastguard Worker src_field["type"] == dst_field["type"], 160*523fa7a6SAndroid Build Coastguard Worker f"Type of the field {key}.{field} changed from {dst_field['type']} to {src_field['type']}", 161*523fa7a6SAndroid Build Coastguard Worker ) 162*523fa7a6SAndroid Build Coastguard Worker if "default" in src_field and "default" not in dst_field: 163*523fa7a6SAndroid Build Coastguard Worker added_fields[field] = {} 164*523fa7a6SAndroid Build Coastguard Worker added_fields[field]["default"] = src_field["default"] 165*523fa7a6SAndroid Build Coastguard Worker if "default" not in src_field and "default" in dst_field: 166*523fa7a6SAndroid Build Coastguard Worker subtracted_fields[field] = {} 167*523fa7a6SAndroid Build Coastguard Worker subtracted_fields[field]["default"] = dst_field["default"] 168*523fa7a6SAndroid Build Coastguard Worker elif src_kind == "enum": 169*523fa7a6SAndroid Build Coastguard Worker _check( 170*523fa7a6SAndroid Build Coastguard Worker src_field == dst_field, 171*523fa7a6SAndroid Build Coastguard Worker f"Value of the enum field {key}.{field} changed from {dst_field} to {src_field}", 172*523fa7a6SAndroid Build Coastguard Worker ) 173*523fa7a6SAndroid Build Coastguard Worker elif src_kind == "union": 174*523fa7a6SAndroid Build Coastguard Worker _check( 175*523fa7a6SAndroid Build Coastguard Worker src_field["type"] == dst_field["type"], 176*523fa7a6SAndroid Build Coastguard Worker f"Type of the field {key}.{field} changed from {dst_field['type']} to {src_field['type']}", 177*523fa7a6SAndroid Build Coastguard Worker ) 178*523fa7a6SAndroid Build Coastguard Worker else: 179*523fa7a6SAndroid Build Coastguard Worker raise AssertionError(f"Unknown kind {src_kind}: {key}") 180*523fa7a6SAndroid Build Coastguard Worker if len(added_fields) > 0: 181*523fa7a6SAndroid Build Coastguard Worker assert key not in additions 182*523fa7a6SAndroid Build Coastguard Worker additions[key] = {} 183*523fa7a6SAndroid Build Coastguard Worker additions[key]["fields"] = added_fields 184*523fa7a6SAndroid Build Coastguard Worker if len(subtracted_fields) > 0: 185*523fa7a6SAndroid Build Coastguard Worker assert key not in subtractions 186*523fa7a6SAndroid Build Coastguard Worker subtractions[key] = {} 187*523fa7a6SAndroid Build Coastguard Worker subtractions[key]["fields"] = subtracted_fields 188*523fa7a6SAndroid Build Coastguard Worker 189*523fa7a6SAndroid Build Coastguard Worker return additions, subtractions 190*523fa7a6SAndroid Build Coastguard Worker 191*523fa7a6SAndroid Build Coastguard Worker 192*523fa7a6SAndroid Build Coastguard Workerdef _hash_schema(s): 193*523fa7a6SAndroid Build Coastguard Worker return hashlib.sha256(repr(s).encode("utf-8")).hexdigest() 194*523fa7a6SAndroid Build Coastguard Worker 195*523fa7a6SAndroid Build Coastguard Worker 196*523fa7a6SAndroid Build Coastguard Worker@dataclasses.dataclass 197*523fa7a6SAndroid Build Coastguard Workerclass _Commit: 198*523fa7a6SAndroid Build Coastguard Worker result: Dict[str, Any] 199*523fa7a6SAndroid Build Coastguard Worker checksum_result: str 200*523fa7a6SAndroid Build Coastguard Worker path: str 201*523fa7a6SAndroid Build Coastguard Worker additions: Dict[str, Any] 202*523fa7a6SAndroid Build Coastguard Worker subtractions: Dict[str, Any] 203*523fa7a6SAndroid Build Coastguard Worker base: Dict[str, Any] 204*523fa7a6SAndroid Build Coastguard Worker checksum_base: Optional[str] 205*523fa7a6SAndroid Build Coastguard Worker 206*523fa7a6SAndroid Build Coastguard Worker 207*523fa7a6SAndroid Build Coastguard Workerdef update_schema(): 208*523fa7a6SAndroid Build Coastguard Worker import importlib.resources 209*523fa7a6SAndroid Build Coastguard Worker 210*523fa7a6SAndroid Build Coastguard Worker if importlib.resources.is_resource(__package__, "schema.yaml"): 211*523fa7a6SAndroid Build Coastguard Worker content = importlib.resources.read_text(__package__, "schema.yaml") 212*523fa7a6SAndroid Build Coastguard Worker match = re.search("checksum<<([A-Fa-f0-9]{64})>>", content) 213*523fa7a6SAndroid Build Coastguard Worker _check(match is not None, "checksum not found in schema.yaml") 214*523fa7a6SAndroid Build Coastguard Worker assert match is not None 215*523fa7a6SAndroid Build Coastguard Worker checksum_base = match.group(1) 216*523fa7a6SAndroid Build Coastguard Worker from yaml import load, Loader 217*523fa7a6SAndroid Build Coastguard Worker 218*523fa7a6SAndroid Build Coastguard Worker dst = load(content, Loader=Loader) 219*523fa7a6SAndroid Build Coastguard Worker assert isinstance(dst, dict) 220*523fa7a6SAndroid Build Coastguard Worker else: 221*523fa7a6SAndroid Build Coastguard Worker checksum_base = None 222*523fa7a6SAndroid Build Coastguard Worker dst = {"SCHEMA_VERSION": None, "TREESPEC_VERSION": None} 223*523fa7a6SAndroid Build Coastguard Worker 224*523fa7a6SAndroid Build Coastguard Worker src = _staged_schema() 225*523fa7a6SAndroid Build Coastguard Worker additions, subtractions = _diff_schema(dst, src) 226*523fa7a6SAndroid Build Coastguard Worker return _Commit( 227*523fa7a6SAndroid Build Coastguard Worker result=src, 228*523fa7a6SAndroid Build Coastguard Worker checksum_result=_hash_schema(src), 229*523fa7a6SAndroid Build Coastguard Worker path=__package__.replace(".", "/") + "/schema.yaml", 230*523fa7a6SAndroid Build Coastguard Worker additions=additions, 231*523fa7a6SAndroid Build Coastguard Worker subtractions=subtractions, 232*523fa7a6SAndroid Build Coastguard Worker base=dst, 233*523fa7a6SAndroid Build Coastguard Worker checksum_base=checksum_base, 234*523fa7a6SAndroid Build Coastguard Worker ) 235*523fa7a6SAndroid Build Coastguard Worker 236*523fa7a6SAndroid Build Coastguard Worker 237*523fa7a6SAndroid Build Coastguard Workerdef check(commit: _Commit, force_unsafe: bool = False): 238*523fa7a6SAndroid Build Coastguard Worker next_version = None 239*523fa7a6SAndroid Build Coastguard Worker reason = "" 240*523fa7a6SAndroid Build Coastguard Worker # Step 1: Detect major schema updates. 241*523fa7a6SAndroid Build Coastguard Worker if len(commit.additions) > 0: 242*523fa7a6SAndroid Build Coastguard Worker for k, v in commit.additions.items(): 243*523fa7a6SAndroid Build Coastguard Worker if k not in commit.base: 244*523fa7a6SAndroid Build Coastguard Worker continue 245*523fa7a6SAndroid Build Coastguard Worker kind = commit.result[k]["kind"] 246*523fa7a6SAndroid Build Coastguard Worker fields = v["fields"] 247*523fa7a6SAndroid Build Coastguard Worker for f, d in fields.items(): 248*523fa7a6SAndroid Build Coastguard Worker if "default" not in d and kind == "struct": 249*523fa7a6SAndroid Build Coastguard Worker reason += ( 250*523fa7a6SAndroid Build Coastguard Worker f"Field {k}.{f} is added to schema.py without a default value as an incomparible change " 251*523fa7a6SAndroid Build Coastguard Worker + "which requires major version bump.\n" 252*523fa7a6SAndroid Build Coastguard Worker ) 253*523fa7a6SAndroid Build Coastguard Worker next_version = [commit.base["SCHEMA_VERSION"][0] + 1, 1] 254*523fa7a6SAndroid Build Coastguard Worker 255*523fa7a6SAndroid Build Coastguard Worker if len(commit.subtractions) > 0: 256*523fa7a6SAndroid Build Coastguard Worker for k, v in commit.subtractions.items(): 257*523fa7a6SAndroid Build Coastguard Worker if k not in commit.result: 258*523fa7a6SAndroid Build Coastguard Worker continue 259*523fa7a6SAndroid Build Coastguard Worker for f in v["fields"]: 260*523fa7a6SAndroid Build Coastguard Worker reason = f"Field {k}.{f} is removed from schema.py as an incompatible change which requires major version bump.\n" 261*523fa7a6SAndroid Build Coastguard Worker next_version = [commit.base["SCHEMA_VERSION"][0] + 1, 1] 262*523fa7a6SAndroid Build Coastguard Worker 263*523fa7a6SAndroid Build Coastguard Worker if force_unsafe: 264*523fa7a6SAndroid Build Coastguard Worker reason += "--force-unsafe is used." 265*523fa7a6SAndroid Build Coastguard Worker next_version = commit.result["SCHEMA_VERSION"] 266*523fa7a6SAndroid Build Coastguard Worker else: 267*523fa7a6SAndroid Build Coastguard Worker # Step 2: Detect minor schema updates. 268*523fa7a6SAndroid Build Coastguard Worker if next_version is None and len(commit.additions) > 0: 269*523fa7a6SAndroid Build Coastguard Worker for k, v in commit.additions.items(): 270*523fa7a6SAndroid Build Coastguard Worker for f in v["fields"]: 271*523fa7a6SAndroid Build Coastguard Worker reason += ( 272*523fa7a6SAndroid Build Coastguard Worker f"Field {k}.{f} is added to schema.py as an compatible change " 273*523fa7a6SAndroid Build Coastguard Worker + "which still requires minor version bump.\n" 274*523fa7a6SAndroid Build Coastguard Worker ) 275*523fa7a6SAndroid Build Coastguard Worker next_version = [ 276*523fa7a6SAndroid Build Coastguard Worker commit.base["SCHEMA_VERSION"][0], 277*523fa7a6SAndroid Build Coastguard Worker commit.base["SCHEMA_VERSION"][1] + 1, 278*523fa7a6SAndroid Build Coastguard Worker ] 279*523fa7a6SAndroid Build Coastguard Worker if next_version is None and len(commit.subtractions) > 0: 280*523fa7a6SAndroid Build Coastguard Worker for k, v in commit.subtractions.items(): 281*523fa7a6SAndroid Build Coastguard Worker for f in v["fields"]: 282*523fa7a6SAndroid Build Coastguard Worker reason += ( 283*523fa7a6SAndroid Build Coastguard Worker f"Field {k}.{f} is removed from schema.py as an compatible change " 284*523fa7a6SAndroid Build Coastguard Worker + "which still requires minor version bump.\n" 285*523fa7a6SAndroid Build Coastguard Worker ) 286*523fa7a6SAndroid Build Coastguard Worker next_version = [ 287*523fa7a6SAndroid Build Coastguard Worker commit.base["SCHEMA_VERSION"][0], 288*523fa7a6SAndroid Build Coastguard Worker commit.base["SCHEMA_VERSION"][1] + 1, 289*523fa7a6SAndroid Build Coastguard Worker ] 290*523fa7a6SAndroid Build Coastguard Worker 291*523fa7a6SAndroid Build Coastguard Worker return next_version, reason 292