JuliusBrussee/caveman · error · MiddlewareError

unsupported_version

unsupported_version

Error message

unsupported_version

What it means

MiddlewareError('unsupported_version') is thrown by validateCapabilities() when the runtime's /capabilities response fails structural validation: schema_version !== 1, missing/invalid policy_revision or runtime_build, an unrecognized mode, malformed transforms array (>128 entries), invalid limits or retention_seconds, or wrong-typed recovery/persistent flags. It indicates the server's capability advertisement is not compatible with this SDK version.

Solutions

  1. Align SDK and runtime versions so schema_version is 1 on both sides
  2. Verify the endpoint actually serves the Caveman capabilities document (curl the capabilities path)
  3. Clear any proxy/CDN cache serving stale capabilities
  4. Catch MiddlewareError code 'unsupported_version' and surface an upgrade message to operators

Example fix

// before
createMiddlewareRuntime({ endpoint: 'http://127.0.0.1:3000' }) // wrong service, old schema
// after
createMiddlewareRuntime({ endpoint: 'http://127.0.0.1:8787' }) // matching runtime, schema_version 1
await runtime.ready();
Defensive patterns

Strategy: try-catch

Validate before calling

const caps = await fetch(capsUrl).then(r => r.json()).catch(() => null);
if (caps?.schema_version !== 1) throw new Error('runtime/SDK schema mismatch — align versions');

Try / catch

try { await runtime.ready(); } catch (e) { if (e instanceof MiddlewareError && e.code === 'unsupported_version') { alertOperator('upgrade runtime to match SDK schema_version 1'); } throw e; }

Prevention

When it happens

Trigger: Connecting to a runtime built for a different schema_version (not 1); an old SDK against a newer runtime or vice versa; a proxy or mock returning a non-capabilities JSON document; a runtime bug producing a malformed capabilities payload.

Common situations: Upgrading the SDK without upgrading the bundled runtime (or the reverse); pointing the middleware at an arbitrary HTTP server that is not the Caveman runtime; an intermediary caching a stale capabilities document.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/a0a0fe84ae40b3ab. Report an issue: GitHub.

Appendix: source

Thrown at packages/sdk/typescript/src/middleware/validate.ts:23

export async function sha256(text: string): Promise<string> {
  return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', encoder.encode(text))), b => b.toString(16).padStart(2, '0')).join('');
}
export const isHash = (s: unknown): s is string => typeof s === 'string' && /^[a-f0-9]{64}$/.test(s);
export const isToken = (s: unknown): s is string => typeof s === 'string' && /^[a-zA-Z0-9._:/-]{1,256}$/.test(s);
const integer = (n: unknown): n is number => Number.isSafeInteger(n) && (n as number) >= 0;
export function scopeKey(scope: Scope): string {
  if (![scope.namespace, scope.session_id, scope.branch_id, scope.cache_epoch].every(isToken)) throw new MiddlewareError('invalid_scope');
  return JSON.stringify([scope.namespace, scope.session_id, scope.branch_id, scope.cache_epoch]);
}
export class MiddlewareError extends Error {
  constructor(readonly code: string) { super(`Caveman middleware: ${code}`); this.name = 'MiddlewareError'; }
}
export function validateCapabilities(value: unknown): Capabilities {
  const c = value as Capabilities;
  if (!c || c.schema_version !== 1 || !isToken(c.policy_revision) || typeof c.runtime_build !== 'string' ||
    !['record', 'compress'].includes(c.mode) || !Array.isArray(c.transforms) || c.transforms.length > 128 ||
    !c.limits || !Object.values(c.limits).every(n => integer(n) && n > 0) ||
    !integer(c.retention_seconds) || typeof c.recovery !== 'boolean' || typeof c.persistent !== 'boolean') throw new MiddlewareError('unsupported_version');
  const ids = new Set<string>();
  for (const t of c.transforms) {
    if (!isToken(t.transform_id) || ids.has(t.transform_id) || !isToken(t.implementation_version) || !Array.isArray(t.eligible_segment_kinds) ||
      !['exact_ccr', 'none'].includes(t.recovery) || t.deterministic !== true) throw new MiddlewareError('unknown_capability');
    ids.add(t.transform_id);
  }
  return c;
}

/** Validate every leaf before an adapter is allowed to apply any of them. */
export async function validatePlan(value: unknown, request: OptimizeRequest, inputDigest: string, caps: Capabilities): Promise<Plan> {
  const p = value as Plan;
  const invalid = () => { throw new MiddlewareError('invalid_plan'); };
  if (!p || p.schema_version !== 1 || p.request_id !== request.request_id || p.input_digest !== inputDigest ||
    p.policy_revision !== request.policy.revision || !isHash(p.replacement_set_id) || !['optimized','bypassed','record'].includes(p.status) ||
    !isToken(p.reason) || !Array.isArray(p.replacements) || !Array.isArray(p.skipped) || !p.measurement || !p.stability || !p.recovery) invalid();
  const m = p.measurement;
  if (m.basis !== 'inferred' || m.scope !== 'segment' || m.verified_saved_usd !== 0 || typeof m.tokenizer !== 'string' ||

View on GitHub (pinned to 3ee70a1026)