{"record":{"id":"38609a908a2e3f7d","repo":"ruvnet/ruflo","slug":"unsupported-calibrator-schema-v-j-v","errorCode":null,"errorMessage":"unsupported calibrator schema v=${j?.v}","messagePattern":"unsupported calibrator schema v=(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/ruvector/router-calibrator.ts","lineNumber":128,"sourceCode":"\n    // Linear interpolation between two adjacent bucket midpoints. Could be\n    // binary-searched, but n is typically <30 after PAV so linear is fine.\n    for (let i = 0; i < n - 1; i++) {\n      if (x >= mids[i] && x <= mids[i + 1]) {\n        const t = (x - mids[i]) / (mids[i + 1] - mids[i]);\n        return this.buckets[i].calibrated * (1 - t) + this.buckets[i + 1].calibrated * t;\n      }\n    }\n    return x; // unreachable given the bounds above\n  }\n\n  /** Pure-JSON serialization — calibrator JSON is small (typically <2kB). */\n  toJSON(): CalibratorJSON {\n    return { v: 1, buckets: this.buckets };\n  }\n\n  static fromJSON(j: CalibratorJSON): IsotonicCalibrator {\n    if (!j || j.v !== 1) throw new Error(`unsupported calibrator schema v=${j?.v}`);\n    return new IsotonicCalibrator(j.buckets);\n  }\n\n  /** Diagnostic — number of distinct calibration points after PAV. */\n  get bucketCount(): number {\n    return this.buckets.length;\n  }\n\n  /** Diagnostic — return a copy of the bucket array (read-only view). */\n  inspect(): CalibratorBucket[] {\n    return this.buckets.map(b => ({ ...b }));\n  }\n}\n","sourceCodeStart":110,"sourceCodeEnd":142,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/ruvector/router-calibrator.ts#L110-L142","documentation":"Thrown by IsotonicCalibrator.fromJSON(j) when j is null/undefined OR j.v !== 1. The calibrator file format is versioned with a single schema version (v:1, holding a buckets array); any other version, or a payload missing the v field, is rejected. This guards against silently applying a calibrator fit for a different schema — which would produce garbage predictedQuality values in the router.","triggerScenarios":"Loading a calibrator JSON written by a future version (v:2); loading a partial/empty file ({}, null); loading a file that's actually a different JSON (e.g. the KRR artifact) by mistake; the file was hand-edited and the v field was removed; JSON.parse on an empty file returns null and that's passed in.","commonSituations":"Cross-version install: calibrator was generated by a newer @claude-flow/cli and loaded by an older one (or vice versa); a path misconfiguration points calibratorPath at the wrong JSON; CI copied a stale calibrator from an old release; the calibrator file got truncated to empty during a write.","solutions":["Inspect the file at cfg.calibratorPath — it must contain {\"v\":1,\"buckets\":[...]}.","Regenerate the calibrator with IsotonicCalibrator.fit(...).toJSON() on the current version and overwrite the file.","If the file is from an incompatible version, delete it and disable calibration (CLAUDE_FLOW_ROUTER_CALIBRATE=0) until a matching one is produced.","Guard fromJSON calls so a bad calibrator disables calibration rather than crashing router init — loadCal in neural-router already does this (catch returns null)."],"exampleFix":"// before — direct fromJSON on untrusted payload\nconst cal = IsotonicCalibrator.fromJSON(JSON.parse(raw));\n\n// after — schema-check first, fall back to no-calibration\nlet cal = null;\ntry {\n  const j = JSON.parse(raw);\n  if (j && j.v === 1) cal = IsotonicCalibrator.fromJSON(j);\n} catch { /* leave cal null */ }\nconst router = cal ? wrapWithCalibrator(r, cal) : r;","handlingStrategy":"validation","validationCode":"import { IsotonicCalibrator } from './router-calibrator';\n\nfunction loadCalibratorSafe(path: string) {\n  try {\n    const j = JSON.parse(readFileSync(path, 'utf8'));\n    if (!j || j.v !== 1) return null;       // wrong schema → disable calibration\n    return IsotonicCalibrator.fromJSON(j);\n  } catch {\n    return null;\n  }\n}","typeGuard":"function isCalibratorJSONv1(j: unknown): j is { v: 1; buckets: unknown[] } {\n  return !!j && typeof j === 'object' && (j as any).v === 1 && Array.isArray((j as any).buckets);\n}","tryCatchPattern":"let calibrator = null;\ntry {\n  calibrator = IsotonicCalibrator.fromJSON(parsed);\n} catch (e) {\n  if (/unsupported calibrator schema/.test(String(e))) {\n    // Disable calibration rather than crashing router init.\n    console.warn('calibrator schema mismatch — running uncalibrated');\n  } else throw e;\n}","preventionTips":["Always set { v: 1 } via IsotonicCalibrator.toJSON() — don't hand-write calibrator JSON.","Validate the schema (v === 1 && Array.isArray(buckets)) before calling fromJSON.","When upgrading @claude-flow/cli, regenerate calibrator artifacts to match the runtime version.","Load calibrators through the neural-router's loadCal helper, which already swallows errors to null."],"tags":["calibrator","schema","versioning","configuration","neural-router"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}