{"record":{"id":"a3818ca0d6945e4b","repo":"paperclipai/paperclip","slug":"unexpected-internal-migrator-dependency","errorCode":null,"errorMessage":"Unexpected internal migrator dependency.","messagePattern":"Unexpected internal migrator dependency\\.","errorType":"console","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/cloud-migrator-artifacts.mjs","lineNumber":52,"sourceCode":"  if (manifest?.version !== 1 || manifest.sourceSha !== sha || manifest.packageVersion !== versionFor(sha)) throw new Error(\"Artifact source identity mismatch.\");\n  for (const name of names) assertDescriptor(manifest.packages?.[name], \"tgz\");\n  assertDescriptor(manifest.lockfile, \"json\");\n}\n\nexport function assertLockfile(lock, manifest) {\n  const version = manifest.packageVersion;\n  if (lock?.lockfileVersion !== 3 || !lock.packages || Array.isArray(lock.packages) ||\n      JSON.stringify(lock.packages[\"\"]?.dependencies) !== JSON.stringify({ \"@paperclipai/db\": version })) throw new Error(\"Invalid migrator lockfile root.\");\n  for (const name of names) {\n    const pin = lock.packages[`node_modules/@paperclipai/${name}`];\n    const expected = manifest.packages[name];\n    if (pin?.version !== version || pin.integrity !== expected.integrity || pin.resolved !== expected.url || pin.link || pin.inBundle) throw new Error(\"Migrator lockfile package pin mismatch.\");\n  }\n  if (lock.packages[\"node_modules/@paperclipai/db\"].dependencies?.[\"@paperclipai/shared\"] !== version) throw new Error(\"Migrator shared dependency mismatch.\");\n  for (const [key, entry] of Object.entries(lock.packages)) {\n    if (key === \"\") continue;\n    if (!entry || typeof entry !== \"object\" || entry.link) throw new Error(\"Invalid migrator lockfile entry.\");\n    if (/(?:^|\\/)node_modules\\/@paperclipai\\/[^/]+$/.test(key) && !names.some((name) => key === `node_modules/@paperclipai/${name}`)) throw new Error(\"Unexpected internal migrator dependency.\");\n    if (entry.inBundle === true) {\n      if (!key.startsWith(\"node_modules/@paperclipai/db/node_modules/\")) throw new Error(\"Unexpected bundled dependency.\");\n      continue;\n    }\n    if (!/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? \"\")) throw new Error(\"Migrator dependency has no strong integrity pin.\");\n    if (names.some((name) => key === `node_modules/@paperclipai/${name}`)) continue;\n    const url = new URL(entry.resolved);\n    if (url.origin !== \"https://registry.npmjs.org\" || url.username || url.password || url.search || url.hash) throw new Error(\"Migrator dependency must resolve to npm.\");\n  }\n}\n\nexport function buildBundle(directory, sha, { exec = execFileSync } = {}) {\n  versionFor(sha);\n  directory = path.resolve(directory);\n  const packages = {};\n  for (const name of names) {\n    const bytes = readFileSync(path.join(directory, `${name}.tgz`));\n    assertMetadata(tarManifest(bytes), `@paperclipai/${name}`, sha);","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/cloud-migrator-artifacts.mjs#L34-L70","documentation":"The migrator bundle may contain exactly the internal packages @paperclipai/db and @paperclipai/shared at node_modules/@paperclipai/<name>. This error is thrown when the lockfile contains any other @paperclipai/* package entry — meaning the dependency graph pulled in an unexpected internal package (e.g. a transitive dep on another workspace package).","triggerScenarios":"assertLockfile finds a key matching /node_modules\\/@paperclipai\\/[^/]+$/ that is not node_modules/@paperclipai/db or node_modules/@paperclipai/shared — for example @paperclipai/adapters appearing transitively.","commonSituations":"db or shared gained a dependency on another internal @paperclipai package; the install root package.json accidentally lists extra internal deps; lockfile was generated in the monorepo where workspaces resolved extra internal packages.","solutions":["Remove the dependency on the extra @paperclipai/* package from db/shared, or vendor what it needed, then rebuild the bundle","Check the generated lockfile before publishing: only db and shared entries may exist under node_modules/@paperclipai/","If the dependency is intentional, extend the names list and manifest handling in scripts/cloud-migrator-artifacts.mjs deliberately, not ad hoc"],"exampleFix":"// before (db package.json)\n\"dependencies\": { \"@paperclipai/shared\": \"0.4.2\", \"@paperclipai/adapters\": \"0.4.2\" }\n// after (only allowlisted internal deps)\n\"dependencies\": { \"@paperclipai/shared\": \"0.4.2\" }","handlingStrategy":"validation","validationCode":"const allowed = new Set([\"node_modules/@paperclipai/db\", \"node_modules/@paperclipai/shared\"]);\nfor (const key of Object.keys(lock.packages ?? {})) {\n  if (/(?:^|\\/)node_modules\\/@paperclipai\\/[^/]+$/.test(key) && !allowed.has(key))\n    throw new Error(`unexpected internal dep in lockfile: ${key}`);\n}","typeGuard":"const onlyAllowedInternal = (lock, names = [\"db\", \"shared\"]) =>\n  Object.keys(lock?.packages ?? {}).every((key) =>\n    !/(?:^|\\/)node_modules\\/@paperclipai\\/[^/]+$/.test(key) || names.some((n) => key === `node_modules/@paperclipai/${n}`));","tryCatchPattern":"try {\n  assertLockfile(lock, manifest);\n} catch (err) {\n  if (err.message === \"Unexpected internal migrator dependency.\") throw new Error(\"db/shared gained a dependency on another @paperclipai package; remove it or extend the bundle allowlist\");\n  throw err;\n}","preventionTips":["Keep db and shared free of dependencies on other internal @paperclipai packages","Generate the lockfile outside the monorepo workspace resolution (buildBundle uses a temp scratch dir for this reason)","Grep the generated lockfile for @paperclipai/ before publishing","If a new internal package is truly needed, update the names allowlist deliberately"],"tags":["supply-chain","lockfile","dependency-graph","validation"],"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"}