{"record":{"id":"67a3f6d2894e443a","repo":"JuliusBrussee/caveman","slug":"schema-version-must-be-1-and-tools-must-be-an-ar","errorCode":null,"errorMessage":"schema_version must be \"1\" and tools must be an array","messagePattern":"schema_version must be \"1\" and tools must be an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"skills/verbs-gate.mjs","lineNumber":73,"sourceCode":"    topLevel = new Set(doc.verbs);\n  } catch (error) {\n    errors.push(`reserved-verbs.json: ${error.message}`);\n  }\n\n  // `caveman tools <verb>` / `caveman cloud <verb>` namespaces.\n  const indexSrc = readOptional(join(cliDir, \"src\", \"index.ts\"));\n  const toolVerbs = discoveryVerbs(indexSrc, \"const TOOL_DISCOVERY\");\n  const cloudVerbs = discoveryVerbs(indexSrc, \"const CLOUD_DISCOVERY\");\n  if (toolVerbs.size === 0) errors.push(\"could not parse TOOL_DISCOVERY verbs from CLI index.ts\");\n  if (cloudVerbs.size === 0) errors.push(\"could not parse CLOUD_DISCOVERY verbs from CLI index.ts\");\n\n  // MCP tool names: engine server (Go) + agent-native server (TS).\n  const mcpTools = new Set();\n  let declaredEngineTools = [];\n  try {\n    const manifest = JSON.parse(readFileSync(join(skillsDir, \"engine-mcp-tools.json\"), \"utf8\"));\n    if (manifest.schema_version !== \"1\" || !Array.isArray(manifest.tools)) {\n      throw new Error('schema_version must be \"1\" and tools must be an array');\n    }\n    declaredEngineTools = [...manifest.tools];\n    if (\n      declaredEngineTools.length === 0\n      || new Set(declaredEngineTools).size !== declaredEngineTools.length\n      || declaredEngineTools.some((name) => typeof name !== \"string\" || !/^caveman_[a-z_]+$/.test(name))\n    ) {\n      throw new Error(\"tools must be unique caveman_* names\");\n    }\n    for (const name of declaredEngineTools) mcpTools.add(name);\n  } catch (error) {\n    errors.push(`engine-mcp-tools.json: ${error.message}`);\n  }\n  const engineSrc = readOptional(join(skillsDir, \"..\", \"mcp\", \"engine_tools.go\"));\n  const sourceEngineTools = [...engineSrc.matchAll(/Tool\\w+\\s*=\\s*\"(caveman_[a-z_]+)\"/g)].map((match) => match[1]);\n  if (sourceEngineTools.length > 0) {\n    const declared = [...declaredEngineTools].sort();\n    const source = [...new Set(sourceEngineTools)].sort();","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/skills/verbs-gate.mjs#L55-L91","documentation":"Thrown inside loadSurfaces() in skills/verbs-gate.mjs while reading skills/engine-mcp-tools.json, the manifest of MCP tool names exposed by the Go engine. The gate pins the manifest format: schema_version must be exactly the string \"1\" and tools must be an array. Like the reserved-verbs error, it is caught and reported as `engine-mcp-tools.json: <message>` in the gate's error list.","triggerScenarios":"engine-mcp-tools.json has schema_version 2, a numeric 1 instead of \"1\", or missing/renamed schema_version; or `tools` is an object/absent instead of an array.","commonSituations":"Bumping the manifest version without updating the gate; regenerating the manifest with a script that emits different casing or shape; hand-merging a conflict that drops a field.","solutions":["Set `\"schema_version\": \"1\"` (string) and make `tools` an array of name strings in engine-mcp-tools.json.","If the schema genuinely evolved, update skills/verbs-gate.mjs to accept the new version — do not silence the error.","Re-run the verbs gate; also expect the engine_tools.go drift check to validate the names."],"exampleFix":"// before: engine-mcp-tools.json\n{ \"schema_version\": 1, \"tools\": { \"a\": \"caveman_a\" } }\n// after\n{ \"schema_version\": \"1\", \"tools\": [\"caveman_compress\", \"caveman_expand\"] }","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isEngineToolsManifest(doc) {\n  return doc !== null && typeof doc === \"object\"\n    && doc.schema_version === \"1\" && Array.isArray(doc.tools);\n}","tryCatchPattern":null,"preventionTips":["Keep schema_version as the string \"1\" — a numeric 1 fails the strict check.","Generate the manifest with a script rather than by hand so the shape stays stable."],"tags":["json","validation","mcp","gate"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}