{"record":{"id":"0ed0fd747b192197","repo":"paperclipai/paperclip","slug":"app-slug-unknown-invalid-appdefinition","errorCode":null,"errorMessage":"${app.slug || \"unknown\"}: invalid AppDefinition","messagePattern":"(.+?): invalid AppDefinition","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ingest-app-definitions.mjs","lineNumber":1579,"sourceCode":"};\n// Runtime credentials share the provider catalog, but never expose tool actions.\nfor (const [slug, name, subscription, envKey] of [[\"anthropic\", \"Claude\", true, \"ANTHROPIC_API_KEY\"], [\"openai\", \"OpenAI\", true, \"OPENAI_API_KEY\"], [\"openrouter\", \"OpenRouter\", false, \"OPENROUTER_API_KEY\"], [\"xai\", \"Grok\", true, \"XAI_API_KEY\"]]) {\n let app=apps.find(a=>a.slug===slug);\n if(!app){app={schemaVersion:1,slug,name,description:`Connect ${name} accounts for your agents.`,categories:[\"ai\"],branding:brandingFor(slug),urlPatterns:[{\"openai\":\"https://api.openai.com/*\",\"openrouter\":\"https://openrouter.ai/api/*\",\"xai\":\"https://api.x.ai/*\"}[slug]],methods:[]};apps.push(app);}\n const methods=(subscription?[\"subscription\",\"api_key\"]:[\"api_key\"]).map(authMethod=>({key:`ai-${authMethod}`,label:authMethod===\"subscription\"?`${name} subscription`:`${name} API key`,purpose:\"ai\",transport:\"runtime_auth\",auth:authMethod===\"subscription\"?\"oauth\":\"api_key\",ai:{provider:slug,method:authMethod},grantKinds:[\"user\",\"organization\"],ownershipModes:[\"customer\"],whenToUse:\"Authenticate an agent with this account.\",guidanceMd:\"Use your personal account or an explicitly shared company account.\",riskTier:\"S3\",...(authMethod===\"api_key\"?{credentialFields:[field(\"apiKey\",\"API key\",\"Enter API key\")],keyPlacement:{location:\"env\",name:envKey}}:{})}));\n // Legacy REST entries have no tool execution adapter. Only offer the supported\n // AI account flow; saved REST connections remain removable through Connections.\n app.methods = [...methods, ...app.methods.filter(method => method.transport !== \"rest_api\")];\n}\nconst validateApp = (app) => {\n  if (\n    app.schemaVersion !== 1 ||\n    !app.slug ||\n    !app.name ||\n    !Array.isArray(app.methods) ||\n    app.methods.length === 0\n  )\n    throw new Error(`${app.slug || \"unknown\"}: invalid AppDefinition`);\n  for (const connectionMethod of app.methods) {\n    if (\n      connectionMethod.auth === \"api_key\" &&\n      !connectionMethod.keyPlacement &&\n      (connectionMethod.purpose ?? \"tool\") !== \"channel\"\n    )\n      throw new Error(\n        `${app.slug}/${connectionMethod.key}: tool api_key requires keyPlacement`,\n      );\n    if (\n      connectionMethod.auth === \"oauth\" &&\n      connectionMethod.ownershipModes.length === 0\n    )\n      throw new Error(\n        `${app.slug}/${connectionMethod.key}: oauth requires ownershipModes`,\n      );\n    for (const connectionField of [\n      ...(connectionMethod.tenantFields ?? []),","sourceCodeStart":1561,"sourceCodeEnd":1597,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/ingest-app-definitions.mjs#L1561-L1597","documentation":"validateApp() enforces the minimal AppDefinition contract before writing app JSON into packages/shared/src/app-definitions. An app must have schemaVersion 1, a non-empty slug and name, and at least one connection method; anything else is structurally invalid and would break consumers of the generated definitions.","triggerScenarios":"validateApp(app) is called for each generated app when: app.schemaVersion !== 1, app.slug is falsy, app.name is falsy, app.methods is not an Array, or app.methods.length === 0. The label falls back to \"unknown\" when app.slug itself is missing.","commonSituations":"A hand-edited or partially generated definition JSON where methods were stripped; bumping or forgetting schemaVersion after a schema change; a factory/helper change that returns an object without methods; an upstream template that produces an empty methods array for a provider with no supported connections.","solutions":["Set schemaVersion to exactly 1 on the app definition.","Ensure slug and name are non-empty strings (slug must be kebab, matching the branding manifest).","Populate methods with at least one connection method built via the method() helper (transport, auth, defaults, riskTier, guidanceMd).","If the provider genuinely supports nothing, remove the app from generation rather than emitting an empty methods array."],"exampleFix":"// before\n{ name: \"Acme\" }\n// after\n{ schemaVersion: 1, slug: \"acme\", name: \"Acme\", methods: [method(\"acme-mcp\", \"mcp_remote\", \"oauth\", ...)] }","handlingStrategy":"validation","validationCode":"const valid = app && app.schemaVersion === 1 && !!app.slug && !!app.name && Array.isArray(app.methods) && app.methods.length > 0;\nif (!valid) throw new Error('AppDefinition fails minimal contract');","typeGuard":"function isValidAppDefinition(app) {\n  return typeof app === 'object' && app !== null &&\n    app.schemaVersion === 1 && typeof app.slug === 'string' && app.slug.length > 0 &&\n    typeof app.name === 'string' && app.name.length > 0 &&\n    Array.isArray(app.methods) && app.methods.length > 0;\n}","tryCatchPattern":"try { validateApp(app); writeOutput(app); } catch (e) { if (e.message.includes('invalid AppDefinition')) { console.error(`Definition for ${app?.slug ?? 'unknown'} is incomplete: check schemaVersion/slug/name/methods.`); } else throw e; }","preventionTips":["Always construct definitions via the method()/field() helpers rather than raw object literals.","Never hand-edit generated definition JSON; edit the generator.","Run the ingest script locally before committing generator changes."],"tags":["validation","schema","app-definition","ingest"],"backgroundTag":"schema-validation-failed","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}