ruvnet/ruflo · error
unsupported calibrator schema v=
Error message
unsupported calibrator schema v=${j?.v} What it means
IsotonicCalibrator.fromJSON() deserializes only schema v1: the parsed object must satisfy j.v === 1 (with j.buckets passed to the constructor). Any other value — including undefined when the field is missing, the file is empty (parses to null), or a future writer bumped the version — throws this error, interpolating the offending version (missing prints as 'undefined').
Solutions
- Regenerate the calibrator through the current calibration pipeline and re-serialize with toJSON() (which always writes {v:1, buckets})
- Inspect the file: node -e "console.log(require('./cal.json').v)" — anything not 1 cannot be loaded by this version
- Keep calibrator artifacts version-pinned next to the package version that produced them; store the writer version alongside
- If the buckets layout is unchanged and only v is missing, wrap the file as {v:1, buckets:<old buckets>} after verifying the shape
Example fix
// before
const cal = IsotonicCalibrator.fromJSON(JSON.parse(readFileSync(path, 'utf8'))); // throws on v!==1
// after
const parsed = JSON.parse(readFileSync(path, 'utf8'));
if (parsed?.v !== 1 || !Array.isArray(parsed.buckets)) {
throw new Error(`calibrator artifact stale (v=${parsed?.v}); recalibrating...`);
}
const cal = IsotonicCalibrator.fromJSON(parsed); Defensive patterns
Strategy: type-guard
Validate before calling
const parsed = JSON.parse(raw);
if (parsed?.v !== 1) {
throw new Error(`stale calibrator artifact (v=${parsed?.v}); recalibrate before loading`);
} Type guard
interface CalibratorV1 { v: 1; buckets: unknown[] }
function isCalibratorV1(j: unknown): j is CalibratorV1 {
return !!j && typeof j === 'object' && (j as any).v === 1 && Array.isArray((j as any).buckets);
} Try / catch
try {
cal = IsotonicCalibrator.fromJSON(parsed);
} catch (e) {
if (e instanceof Error && e.message.startsWith('unsupported calibrator schema')) {
cal = await recalibrate(); // regenerate with current pipeline, then persist via toJSON()
} else throw e;
} Prevention
- Store the writer's package version next to calibrator artifacts and validate on load
- Always round-trip through toJSON()/fromJSON() of the same version pair
- Run isCalibratorV1 on any artifact restored from backup or another environment
When it happens
Trigger: Loading calibrator JSON persisted by a newer/older package version that changed the schema; hand-edited or truncated JSON losing the v field; passing the wrong file entirely (e.g., a KRR router JSON) whose top-level has no v; JSON.parse of an empty file yielding null, then null?.v !== 1.
Common situations: Upgrading the library and reusing calibrated state saved before the upgrade; restoring calibration files from backups across versions; pipelines where the calibrator file is regenerated by a different tool version than the one reading it.
Related errors
- Cannot calibrate with empty samples
- canonical JSON does not support cycles
- canonical JSON does not support lone UTF-16 surrogates
- canonical JSON does not support
- canonical JSON does not support undefined
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/38609a908a2e3f7d.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/ruvector/router-calibrator.ts:128
// Linear interpolation between two adjacent bucket midpoints. Could be
// binary-searched, but n is typically <30 after PAV so linear is fine.
for (let i = 0; i < n - 1; i++) {
if (x >= mids[i] && x <= mids[i + 1]) {
const t = (x - mids[i]) / (mids[i + 1] - mids[i]);
return this.buckets[i].calibrated * (1 - t) + this.buckets[i + 1].calibrated * t;
}
}
return x; // unreachable given the bounds above
}
/** Pure-JSON serialization — calibrator JSON is small (typically <2kB). */
toJSON(): CalibratorJSON {
return { v: 1, buckets: this.buckets };
}
static fromJSON(j: CalibratorJSON): IsotonicCalibrator {
if (!j || j.v !== 1) throw new Error(`unsupported calibrator schema v=${j?.v}`);
return new IsotonicCalibrator(j.buckets);
}
/** Diagnostic — number of distinct calibration points after PAV. */
get bucketCount(): number {
return this.buckets.length;
}
/** Diagnostic — return a copy of the bucket array (read-only view). */
inspect(): CalibratorBucket[] {
return this.buckets.map(b => ({ ...b }));
}
}
View on GitHub (pinned to fa13ee4ad6)