{"id":"543eaf4b2b6b8e8b","repo":"prisma/prisma","slug":"psl-orphaned-base-543eaf","errorCode":"PSL_ORPHANED_BASE","errorMessage":"Model \"${variantName}\" declares @@base(${baseDecl.baseName}, ...) but \"${baseDecl.baseName}\" has no @@discriminator","messagePattern":"Model \"(.+?)\" declares @@base\\((.+?), \\.\\.\\.\\) but \"(.+?)\" has no @@discriminator","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/2-sql/2-authoring/contract-psl/src/interpreter.ts","lineNumber":1744,"sourceCode":"      ...patched,\n      [coordinateFor(modelName)]: { ...model, discriminator: { field: decl.fieldName }, variants },\n    };\n  }\n\n  for (const [variantName, baseDecl] of baseDeclarations) {\n    if (!modelNames.has(baseDecl.baseName)) {\n      diagnostics.push({\n        code: 'PSL_BASE_TARGET_NOT_FOUND',\n        message: `Model \"${variantName}\" @@base references non-existent model \"${baseDecl.baseName}\"`,\n        sourceId,\n        span: baseDecl.span,\n      });\n      continue;\n    }\n\n    if (!discriminatorDeclarations.has(baseDecl.baseName)) {\n      diagnostics.push({\n        code: 'PSL_ORPHANED_BASE',\n        message: `Model \"${variantName}\" declares @@base(${baseDecl.baseName}, ...) but \"${baseDecl.baseName}\" has no @@discriminator`,\n        sourceId,\n        span: baseDecl.span,\n      });\n      continue;\n    }\n\n    if (discriminatorDeclarations.has(variantName)) {\n      continue;\n    }\n\n    const variantModel = patched[coordinateFor(variantName)];\n    if (!variantModel) continue;\n\n    const baseMapping = modelMappings.get(baseDecl.baseName);\n    const variantMapping = modelMappings.get(variantName);\n    const hasExplicitMap =\n      variantMapping?.model.attributes.some((attr) => attr.name === 'map') ?? false;","sourceCodeStart":1726,"sourceCodeEnd":1762,"githubUrl":"https://github.com/prisma/prisma/blob/1243cdffbec0506c595ac8fd5865fe258c336fa0/packages/2-sql/2-authoring/contract-psl/src/interpreter.ts#L1726-L1762","documentation":"A variant points its `@@base(baseName, value)` at a model that exists but has no `@@discriminator`. `resolvePolymorphism` checks that the base is registered as a discriminator base (interpreter.ts:1742); a variant without a discriminator base is meaningless, so it is rejected. Note this is distinct from PSL_BASE_TARGET_NOT_FOUND, which fires when the base name does not exist at all.","triggerScenarios":"Declaring `@@base(Animal, \"dog\")` where `model Animal` exists but lacks `@@discriminator`; forgetting to add the discriminator attribute to the intended base model.","commonSituations":"Setting up inheritance and forgetting the base-side attribute; removing a discriminator while leaving variants in place; confusing the base/variant attribute direction.","solutions":["Add `@@discriminator(field)` to the referenced base model (with a String field).","If the base should not be polymorphic, remove the `@@base(...)` attribute from the variant.","Double-check you are pointing `@@base(...)` at the model that owns the discriminator, not another variant."],"exampleFix":"// before\nmodel Animal { id Int @id }\nmodel Dog { @@base(Animal, \"dog\") }\n// after\nmodel Animal { id Int @id; kind String; @@discriminator(kind) }\nmodel Dog { @@base(Animal, \"dog\") }","handlingStrategy":"validation","validationCode":"// Pre-flight: every @@base(baseName, ...) must point at a model that itself\n// declares @@discriminator.\nimport type { ParsedModel } from './schema-types';\n\nexport function findOrphanedBases(\n  models: readonly ParsedModel[],\n): { variant: string; baseName: string }[] {\n  const discriminating = new Set(\n    models\n      .filter((m) => (m.attributes ?? []).some((a) => a.name === 'discriminator'))\n      .map((m) => m.name),\n  );\n  const problems: { variant: string; baseName: string }[] = [];\n  for (const model of models) {\n    for (const attr of model.attributes ?? []) {\n      if (attr.name !== 'base') continue;\n      const baseName = attr.args['base'] as string;\n      if (!discriminating.has(baseName)) problems.push({ variant: model.name, baseName });\n    }\n  }\n  return problems;\n}","typeGuard":"import type { Result } from '@prisma-next/utils/result';\nimport type { Contract, ContractSourceDiagnostics, ContractSourceDiagnostic } from '@prisma-next/config/config-types';\n\nexport function isOrphanedBaseError(\n  result: Result<Contract, ContractSourceDiagnostics>,\n): result is { ok: false; failure: ContractSourceDiagnostics } {\n  return !result.ok && result.failure.diagnostics.some(\n    (d: ContractSourceDiagnostic) => d.code === 'PSL_ORPHANED_BASE',\n  );\n}","tryCatchPattern":null,"preventionTips":["Always pair a variant's @@base(Base) with a @@discriminator on Base — they are two halves of one STI/MTI declaration.","Add @@discriminator to the base first, then the variant's @@base, so the schema is never in a half-wired state.","If you remove @@discriminator from a base, remove all @@base references to it in the same change."],"tags":["psl","schema","polymorphism","discriminator","validation"],"analyzedSha":"1243cdffbec0506c595ac8fd5865fe258c336fa0","analyzedAt":"2026-08-01T19:42:02.087Z","schemaVersion":2}