{"record":{"id":"41d53824af90babf","repo":"paperclipai/paperclip","slug":"slug-missing-local-branding-provenance","errorCode":null,"errorMessage":"${slug}: missing local branding provenance","messagePattern":"(.+?): missing local branding provenance","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ingest-app-definitions.mjs","lineNumber":32,"sourceCode":"const brandingManifest = JSON.parse(\n  fs.readFileSync(\n    path.join(root, \"ui/public/brands/apps/manifest.json\"),\n    \"utf8\",\n  ),\n);\nconst brandingBySlug = new Map(\n  brandingManifest.providers.map((entry) => [entry.slug, entry]),\n);\nconst brandingFor = (slug) => {\n  const entry = brandingBySlug.get(slug);\n  if (entry)\n    return {\n      logoUrl: entry.localAsset,\n      ...(entry.darkAsset ? { darkLogoUrl: entry.darkAsset } : {}),\n    };\n  if (slug === \"oauth-generic\" || slug === \"api-key-generic\")\n    return { logoUrl: `/brands/apps/${slug}.svg` };\n  throw new Error(`${slug}: missing local branding provenance`);\n};\nconst field = (key, label, placeholder) => ({\n  key,\n  label,\n  type: \"password\",\n  required: true,\n  placeholder,\n  secret: true,\n});\nconst method = (\n  key,\n  transport,\n  auth,\n  defaults,\n  riskTier,\n  guidanceMd,\n  extra = {},\n) => ({","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/ingest-app-definitions.mjs#L14-L50","documentation":"brandingFor() resolves each app slug to a locally-hosted brand asset via ui/public/brands/apps/manifest.json. If the slug is not in the manifest and is not one of the two generic fallbacks (oauth-generic/api-key-generic), ingestion refuses to emit an AppDefinition without provenance, because a published app must ship a verifiable local logo rather than a remote or missing one.","triggerScenarios":"Calling brandingFor(slug) (directly or via app-definition generation in scripts/ingest-app-definitions.mjs) with a slug that (a) has no entry in brandingBySlug (built from ui/public/brands/apps/manifest.json 'providers' keyed by 'slug'), and (b) is not exactly \"oauth-generic\" or \"api-key-generic\".","commonSituations":"Adding a new app definition without first exporting its brand asset and regenerating the branding manifest; a slug typo or rename in the app definition that no longer matches the manifest slug; manually editing manifest.json and dropping a provider entry; running the ingest script in a checkout where ui/public/brands/apps/manifest.json is stale or truncated.","solutions":["Add (or fix the slug of) the provider entry in ui/public/brands/apps/manifest.json, including localAsset pointing at the bundled SVG under /brands/apps/.","Verify the app slug in scripts/ingest-app-definitions.mjs matches the manifest slug exactly (case-sensitive), including darkAsset handling.","Regenerate the branding manifest via the asset-export pipeline if a new provider asset was added but not ingested.","If the app is intentionally generic (no real provider brand), use slug \"oauth-generic\" or \"api-key-generic\", which have hardcoded fallbacks."],"exampleFix":"// before\nslug: \"slackapp\"\n// after (manifest.json providers entry must exist)\nslug: \"slack\" // entry: { \"slug\": \"slack\", \"localAsset\": \"/brands/apps/slack.svg\" }","handlingStrategy":"validation","validationCode":"const manifest = JSON.parse(fs.readFileSync('ui/public/brands/apps/manifest.json','utf8'));\nconst slugs = new Set(manifest.providers.map(p => p.slug));\nif (!slugs.has(slug) && !['oauth-generic','api-key-generic'].includes(slug)) {\n  throw new Error(`${slug}: add branding manifest entry before ingesting`);\n}","typeGuard":"const hasBranding = (slug) => typeof slug === 'string' && (genericSlugs.includes(slug) || brandingBySlug.has(slug));","tryCatchPattern":null,"preventionTips":["Always add the manifest entry + SVG in the same PR as a new app definition.","Add a CI check that diffs app slugs against manifest slugs.","Keep slug names consistent via a single shared constant list."],"tags":["branding","manifest","ingest","missing-asset"],"backgroundTag":"entity-not-found","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"}