{"record":{"id":"1a79007b1ab7def4","repo":"phacility/phabricator","slug":"blueprint-s-of-type-s-is-not-properly-impl-1a7900","errorCode":null,"errorMessage":"Blueprint \"%s\" (of type \"%s\") is not properly implemented: %s must actually allocate the resource it returns.","messagePattern":"Blueprint \"(.+?)\" \\(of type \"(.+?)\"\\) is not properly implemented: (.+?) must actually allocate the resource it returns\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/drydock/worker/DrydockLeaseUpdateWorker.php","lineNumber":841,"sourceCode":"   */\n  private function validateAllocatedResource(\n    DrydockBlueprint $blueprint,\n    $resource,\n    DrydockLease $lease) {\n\n    if (!($resource instanceof DrydockResource)) {\n      throw new Exception(\n        pht(\n          'Blueprint \"%s\" (of type \"%s\") is not properly implemented: %s must '.\n          'return an object of type %s or throw, but returned something else.',\n          $blueprint->getBlueprintName(),\n          $blueprint->getClassName(),\n          'allocateResource()',\n          'DrydockResource'));\n    }\n\n    if (!$resource->isAllocatedResource()) {\n      throw new Exception(\n        pht(\n          'Blueprint \"%s\" (of type \"%s\") is not properly implemented: %s '.\n          'must actually allocate the resource it returns.',\n          $blueprint->getBlueprintName(),\n          $blueprint->getClassName(),\n          'allocateResource()'));\n    }\n\n    $resource_type = $resource->getType();\n    $lease_type = $lease->getResourceType();\n\n    if ($resource_type !== $lease_type) {\n      throw new Exception(\n        pht(\n          'Blueprint \"%s\" (of type \"%s\") is not properly implemented: it '.\n          'built a resource of type \"%s\" to satisfy a lease requesting a '.\n          'resource of type \"%s\".',\n          $blueprint->getBlueprintName(),","sourceCodeStart":823,"sourceCodeEnd":859,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php#L823-L859","documentation":"validateAllocatedResource() also checks the returned resource was actually allocated: DrydockResource::allocateResource() sets the in-process isAllocated flag after acquiring slot locks and saving. If the flag is false, the blueprint returned a resource object without ever calling allocateResource() on it, so it was never persisted or locked. The object the blueprint hands back is not a real, usable resource.","triggerScenarios":"Custom allocateResource() that creates a template with newResourceTemplate(), sets some attributes, and returns it without calling $resource->allocateResource(); calling allocateResource() conditionally and returning on the skipped branch.","commonSituations":"Blueprint author assumes the worker does the allocation; refactor moves the allocateResource() call behind an if that is false in the tested path; porting an old blueprint written before allocation became explicit.","solutions":["Call $resource->allocateResource() before returning it from allocateResource(); that call persists the row and acquires its slot locks.","Make sure the call is unconditional on the success path; on failure, throw instead of returning an unallocated template.","Follow the pattern in DrydockWorkingCopyBlueprintImplementation::allocateResource()."],"exampleFix":"// before\n$resource = $this->newResourceTemplate($blueprint);\n$resource->setAttribute('host', $host);\nreturn $resource; // never allocated\n\n// after\n$resource = $this->newResourceTemplate($blueprint);\n$resource->setAttribute('host', $host);\nreturn $resource->allocateResource();","handlingStrategy":"validation","validationCode":"if (!$resource->isAllocatedResource()) {\n  throw new Exception(\n    pht('allocateResource() returned before allocating; call $resource->allocateResource().'));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["End every success path of blueprint allocateResource() with $resource->allocateResource().","Return $resource->allocateResource() directly so the flow cannot skip the call."],"tags":["drydock","php","blueprint","contract","resource-lifecycle","state-machine"],"backgroundTag":"invalid-state-transition","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}