{"record":{"id":"5a1dbf42782b3139","repo":"alibaba/nacos","slug":"runtime-version-range-must-be-canonical","errorCode":null,"errorMessage":"Runtime Version range must be canonical","messagePattern":"Runtime Version range must be canonical","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java","lineNumber":584,"sourceCode":"        }\n        Set<String> bindingKeys = new HashSet<String>();\n        for (RuntimeVersionBinding binding : bindings) {\n            validateRuntimeVersionBinding(binding, selectedVersion, bindingKeys);\n        }\n        requireNonNull(item.getState(), \"runtime Endpoint state\");\n        requireNonNull(item.getEnabled(), \"runtime Endpoint enabled\");\n        requireNonNull(item.getHealthy(), \"runtime Endpoint healthy\");\n        validateRuntimeEndpointState(item);\n        validateEpochMillis(item.getLastUpdatedTime(), \"lastUpdatedTime\");\n    }\n    \n    private static void validateRuntimeVersionBinding(RuntimeVersionBinding binding,\n        AgentVersion selectedVersion, Set<String> bindingKeys) {\n        requireNonNull(binding, \"runtime Version binding\");\n        AgentVersion runtimeVersion = AgentVersion.parse(binding.getRuntimeVersion());\n        AgentVersionRange versionRange = AgentVersionRange.parse(binding.getVersionRange());\n        if (!versionRange.getValue().equals(binding.getVersionRange())) {\n            throw new IllegalArgumentException(\"Runtime Version range must be canonical\");\n        }\n        if (!versionRange.contains(runtimeVersion)) {\n            throw new IllegalArgumentException(\n                \"versionRange must contain runtimeVersion: \" + runtimeVersion);\n        }\n        if (selectedVersion != null && !versionRange.contains(selectedVersion)) {\n            throw new IllegalArgumentException(\n                \"Snapshot binding does not match selected Version: \" + selectedVersion);\n        }\n        String bindingKey = runtimeVersion + \"\\u0000\" + versionRange.getValue();\n        if (!bindingKeys.add(bindingKey)) {\n            throw new IllegalArgumentException(\"Duplicate Runtime Version binding\");\n        }\n    }\n    \n    private static void validateRuntimeEndpointState(RuntimeEndpointSnapshotItem item) {\n        RuntimeEndpointState expected;\n        if (!item.getEnabled()) {","sourceCodeStart":566,"sourceCodeEnd":602,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/api/src/main/java/com/alibaba/nacos/api/ai/utils/AgentModelValidator.java#L566-L602","documentation":"Thrown by validateRuntimeVersionBinding when the versionRange string, after parsing and canonicalization via AgentVersionRange.parse, does not equal the raw input. This enforces canonical form so that range identity and duplicate detection are stable. AgentVersionRange canonicalizes by reformatting versions (via AgentVersion.toString) and by collapsing a single-point closed interval '[a,a]' or '[x,x]' into the exact form '[x]'. Any non-canonical spelling — extra whitespace, non-canonical version text, or a redundant closed range around identical bounds — is rejected.","triggerScenarios":"A RuntimeVersionBinding.versionRange that parses but is not in canonical form. Examples that trigger: '[ 1.0.0,2.0.0 ]' (whitespace), '[1.0.0.0,2.0.0)' where AgentVersion normalizes '1.0.0.0', '[1.0.0,1.0.0]' (collapses to '[1.0.0]'). Reached during validateRuntimeEndpointSnapshotItem -> validateRuntimeVersionBinding in the runtime snapshot push path.","commonSituations":"Hand-writing version ranges with spaces around bounds; pasting SemVer-style pre-release text that AgentVersion rewrites; using a closed range '[v,v]' for a single version instead of the canonical exact form '[v]'; building range strings from non-canonical version sources.","solutions":["Run AgentVersionRange.parse(range).getValue() (or AgentValidationUtils.validateVersionRange after fixing) and store the returned canonical string as versionRange.","For a single version use the exact form '[1.0.0]' (closed brackets, no comma).","For an interval use '[lo,hi]', '[lo,hi)', '(lo,hi]', or '(lo,hi)' with canonical version spellings and no surrounding whitespace.","Leave a bound empty only with the open delimiter on that side, e.g. '[1.0.0,)'.","If you only need one version, prefer setting both runtimeVersion and versionRange to the canonical exact form."],"exampleFix":"// before\nb.setVersionRange(\"[ 1.0.0, 2.0.0 )\");  // whitespace -> rejected\nb.setVersionRange(\"[1.0.0,1.0.0]\");      // collapses -> rejected\n// after\nb.setVersionRange(\"[1.0.0,2.0.0)\");\n// single version:\nb.setVersionRange(\"[1.0.0]\");","handlingStrategy":"validation","validationCode":"import com.alibaba.nacos.api.ai.utils.AgentValidationUtils;\n// AgentVersionRange is package-private; use the public validator to confirm parse,\n// then canonicalize via the same parse path exposed indirectly:\nAgentValidationUtils.validateVersionRange(range); // throws if unparseable\n// To get canonical form, mirror the rule: trim, no internal whitespace,\n// single-point '[v,v]' -> '[v]'.","typeGuard":"// canonical check without package-private access: round-trip equality\nstatic boolean isCanonicalRange(String range) {\n    String trimmed = range.trim();\n    if (!trimmed.equals(range)) return false;\n    // no whitespace inside brackets\n    if (range.matches(\".*\\\\s.*\")) return false;\n    return true;\n}","tryCatchPattern":"try {\n    AgentModelValidator.validateRuntimeEndpointSnapshot(snapshot);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().equals(\"Runtime Version range must be canonical\")) {\n        // re-emit versionRange from AgentVersionRange.parse(range).getValue()\n    } else throw e;\n}","preventionTips":["For a single version use '[v]' (closed, no comma).","For intervals use '[lo,hi)' style with canonical version spellings, no spaces.","Generate ranges programmatically rather than hand-editing."],"tags":["ai-agent","validation","version-range","canonical","runtime-snapshot"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}