{"record":{"id":"b6c32c2a7b586db5","repo":"paperclipai/paperclip","slug":"invalid-migrator-lockfile-root","errorCode":null,"errorMessage":"Invalid migrator lockfile root.","messagePattern":"Invalid migrator lockfile root\\.","errorType":"console","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/cloud-migrator-artifacts.mjs","lineNumber":42,"sourceCode":"function assertDescriptor(pin, extension) {\n  if (!pin || typeof pin.integrity !== \"string\" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(pin.integrity) ||\n      !Number.isSafeInteger(pin.size) || pin.size <= 0 || pin.size > maximumBytes) throw new Error(\"Invalid artifact integrity or size.\");\n  const digest = Buffer.from(pin.integrity.slice(7), \"base64\");\n  if (digest.toString(\"base64\") !== pin.integrity.slice(7) || pin.url !== `${artifactBase}/blobs/${digest.toString(\"hex\")}.${extension}`) {\n    throw new Error(\"Artifact URL does not match its content hash and trusted origin.\");\n  }\n}\n\nexport function assertManifest(manifest, sha) {\n  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.\");","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/cloud-migrator-artifacts.mjs#L24-L60","documentation":"The cloud migrator artifacts script throws this from assertLockfile when the lockfile root is structurally wrong: lockfileVersion is not 3, packages is missing or is an array, or the root package \"\" does not have dependencies exactly { \"@paperclipai/db\": <version> }. It guarantees the lockfile being installed is the generated migrator lockfile and nothing else.","triggerScenarios":"assertLockfile receives a lock object that is null, was produced by a different package manager/lockfileVersion (not 3), has packages as an array, or whose root package dependencies differ from the single expected @paperclipai/db dependency pinned to manifest.packageVersion.","commonSituations":"Pointing the migrator at a project's own package-lock.json instead of the generated migrator lockfile; a regenerated lockfile from a different manifest version; npm rewriting the root dependencies section.","solutions":["Use the generated migrator lockfile (lockfileVersion 3) from the artifact set, not an arbitrary package-lock.json.","Regenerate the lockfile artifacts so the root depends exactly on @paperclipai/db at manifest.packageVersion.","Check that the lockfile was not modified after download (npm install can rewrite it).","Confirm manifest and lockfile come from the same artifact release."],"exampleFix":"// before (rewritten root)\n\"packages\": { \"\": { \"dependencies\": { \"@paperclipai/db\": \"0.0.1\", \"extra\": \"...\" } } }\n// after\n\"packages\": { \"\": { \"dependencies\": { \"@paperclipai/db\": \"<manifest.packageVersion>\" } } }","handlingStrategy":"validation","validationCode":"const root = lock?.packages?.['']?.dependencies;\nconst isMigratorLock = lock?.lockfileVersion === 3 && lock.packages && !Array.isArray(lock.packages) &&\n  JSON.stringify(root) === JSON.stringify({ '@paperclipai/db': manifest.packageVersion });\nif (!isMigratorLock) throw new Error('Not the generated migrator lockfile');","typeGuard":"const isMigratorLockfile = (lock) => lock?.lockfileVersion === 3 && typeof lock.packages === 'object' && !Array.isArray(lock.packages);","tryCatchPattern":"try { assertLockfile(lock, manifest); } catch (e) {\n  if (e.message === 'Invalid migrator lockfile root.') {\n    console.error('Use the lockfile shipped in the artifact set, not a project package-lock.json');\n  } else throw e;\n}","preventionTips":["Only install from the lockfile downloaded as part of the verified artifact set.","Run installs with frozen lockfile settings so tooling cannot rewrite the root dependencies.","Verify manifest and lockfile come from the same release before asserting."],"tags":["lockfile","validation","migration","npm"],"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"}