{"record":{"id":"7770ae2df2a2d408","repo":"puppetlabs/puppet","slug":"invalid-instance-type-klass-expected-model-t","errorCode":null,"errorMessage":"Invalid instance type %{klass}, expected %{model_type}","messagePattern":"Invalid instance type %(.+?), expected %(.+?)","errorType":"validation","errorClass":"Puppet::Indirector::ValidationError","httpStatus":null,"severity":"error","filePath":"lib/puppet/indirector/terminus.rb","lineNumber":177,"sourceCode":"    self.class.terminus_type\n  end\n\n  def validate(request)\n    if request.instance\n      validate_model(request)\n      validate_key(request)\n    end\n  end\n\n  def validate_key(request)\n    unless request.key == request.instance.name\n      raise Puppet::Indirector::ValidationError, _(\"Instance name %{name} does not match requested key %{key}\") % { name: request.instance.name.inspect, key: request.key.inspect }\n    end\n  end\n\n  def validate_model(request)\n    unless model === request.instance\n      raise Puppet::Indirector::ValidationError, _(\"Invalid instance type %{klass}, expected %{model_type}\") % { klass: request.instance.class.inspect, model_type: model.inspect }\n    end\n  end\nend\n","sourceCodeStart":159,"sourceCodeEnd":181,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/indirector/terminus.rb#L159-L181","documentation":"Terminus#validate_model uses `model === instance` to check that the object attached to a request is an instance of (or duck-compatible with) the indirection's model class. Saving a foreign object raises Puppet::Indirector::ValidationError naming the actual class and the expected model. This protects termini such as the YAML store from serializing an object of the wrong kind under an indirection (e.g., a Puppet::Node handed to the facts terminus).","triggerScenarios":"Puppet::Node::Facts.indirection.save(node_instance, key) where node_instance is a Puppet::Node; passing a String or Hash where a model instance is required; stub/mock objects in tests that do not inherit from the model class.","commonSituations":"Refactorings that change which class is constructed but not which indirection saves it; copy-pasted code between facts and node handling; RSpec doubles substituted for real instances without verifying the model contract.","solutions":["Construct the correct model for the indirection (facts -> Puppet::Node::Facts, node -> Puppet::Node, catalog -> Puppet::Resource::Catalog)","Check the error message: %{klass} tells you what you actually passed; convert it before saving","In tests, build real (or properly subclassed) instances instead of pure doubles when exercising save paths","Use each indirection's own factory methods (Puppet::Node::Facts.new(...)) rather than generic serializers"],"exampleFix":"# before\nnode = Puppet::Node.new('web01')\nPuppet::Node::Facts.indirection.save(node, 'web01')\n# => Invalid instance type Puppet::Node, expected Puppet::Node::Facts\n\n# after\nfacts = Puppet::Node::Facts.new('web01', values)\nPuppet::Node::Facts.indirection.save(facts, 'web01')","handlingStrategy":"type-guard","validationCode":"expected = Puppet::Node::Facts  # the indirection's model\nunless expected === instance\n  raise Puppet::Indirector::ValidationError, \"expected #{expected}, got #{instance.class}\"\nend","typeGuard":"def valid_model_instance?(instance, model_class)\n  model_class === instance\nend","tryCatchPattern":null,"preventionTips":["Construct objects with the model class named in the indirection","In RSpec, use instance_double(Puppet::Node::Facts) so contract violations fail at test time","Centralize save calls in one helper per indirection so the model pairing is checked once"],"tags":["puppet","indirector","validation","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}