{"record":{"id":"b69a72c9f0fd4984","repo":"JuliusBrussee/caveman","slug":"cave-harness-upstream-version-invalid","errorCode":"cave_harness_upstream_version_invalid","errorMessage":"cave_harness_upstream_version_invalid","messagePattern":"cave_harness_upstream_version_invalid","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/adapters.ts","lineNumber":93,"sourceCode":"}\n\nexport interface HarnessAdapterManifest extends HarnessAdapterIdentity {\n  schemaVersion: 1;\n  harness: HarnessID;\n  wireContract: Readonly<Record<string, unknown>>;\n}\n\nexport function createHarnessAdapter(\n  id: HarnessID,\n  identity: HarnessAdapterIdentity,\n  wireContract: Readonly<Record<string, unknown>>,\n  invoke: HarnessInvoke,\n): HarnessAdapter {\n  if (!/^[0-9A-Za-z][0-9A-Za-z._-]{0,63}$/.test(identity.adapterVersion)) {\n    throw new Error(\"cave_harness_adapter_version_invalid\");\n  }\n  if (!/^[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$/.test(identity.upstreamVersion)) {\n    throw new Error(\"cave_harness_upstream_version_invalid\");\n  }\n  if (!/^[0-9a-f]{64}$/.test(identity.bundleSHA256) ||\n      !/^[0-9a-f]{64}$/.test(identity.dependencyLockSHA256)) {\n    throw new Error(\"cave_harness_artifact_digest_invalid\");\n  }\n  const manifest = deepFreeze({\n    schemaVersion: 1 as const,\n    harness: id,\n    adapterVersion: identity.adapterVersion,\n    upstreamVersion: identity.upstreamVersion,\n    bundleSHA256: identity.bundleSHA256,\n    dependencyLockSHA256: identity.dependencyLockSHA256,\n    wireContract: canonicalRecord(wireContract),\n  });\n  const contractSHA256 = sha256(stableStringify(manifest));\n  return Object.freeze({\n    id,\n    version: identity.adapterVersion,","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/adapters.ts#L75-L111","documentation":"`createHarnessAdapter` validates `identity.upstreamVersion` against `^[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$`: alphanumeric first character, then up to 127 more of letters, digits, `.`, `_`, `+`, `-`. Unlike adapterVersion it permits `+` and doubles the length budget, but it still rejects empty strings, whitespace, parentheses, and other punctuation. The value is compared against the actually-installed upstream package version later, so a malformed pin fails fast here.","triggerScenarios":"`upstreamVersion: \"7.0.43 (npm latest)\"` (spaces/parens from a registry banner); `upstreamVersion: \"\"` when the value was never set; a 130+ character composite version string; a leading `.` or `-`.","commonSituations":"Scraping a version string from a gateway or CLI banner that appends qualifiers; concatenating version+digest into one long string; forgetting to set the field at all in a custom adapter.","solutions":["Pass the plain semver/build string without qualifiers (e.g. `\"7.0.43\"`)","Keep it under 128 characters and start it with a letter or digit","Sanitize at config load: trim and strip anything after the first space or parenthesis"],"exampleFix":"// before\nupstreamVersion: `7.0.43 (resolved from npm)`\n// after\nupstreamVersion: \"7.0.43\"","handlingStrategy":"type-guard","validationCode":"const UPSTREAM_VERSION_RE = /^[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$/;\nconst upstreamVersion = rawUpstream.trim().split(' ')[0]; // drop banner qualifiers\nif (!UPSTREAM_VERSION_RE.test(upstreamVersion)) {\n  throw new Error(`upstreamVersion fails identity contract: ${JSON.stringify(rawUpstream)}`);\n}","typeGuard":"const UPSTREAM_VERSION_RE = /^[0-9A-Za-z][0-9A-Za-z._+-]{0,127}$/;\nfunction isValidUpstreamVersion(v: unknown): v is string {\n  return typeof v === 'string' && UPSTREAM_VERSION_RE.test(v);\n}","tryCatchPattern":"if (!isValidUpstreamVersion(identity.upstreamVersion)) {\n  // sanitize or refuse before the factory call; note the installed-package check\n  // (assertSupportedUpstream) will separately demand the exact pinned version\n}\nreturn createHarnessAdapter(id, { ...identity, upstreamVersion: clean(identity.upstreamVersion) }, contract, invoke);","preventionTips":["Pass the plain version string (e.g. '7.0.43'), never scraped banners with qualifiers","Trim and cut at the first whitespace when versions come from external tools","Keep version strings under 128 characters — don't concatenate digests into them"],"tags":["typescript","validation","versioning","harness-adapter"],"backgroundTag":"invalid-version-string","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}