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

  1. Regenerate the calibrator through the current calibration pipeline and re-serialize with toJSON() (which always writes {v:1, buckets})
  2. Inspect the file: node -e "console.log(require('./cal.json').v)" — anything not 1 cannot be loaded by this version
  3. Keep calibrator artifacts version-pinned next to the package version that produced them; store the writer version alongside
  4. 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

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


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)