{"record":{"id":"0f7d8bdae05f321b","repo":"phacility/phabricator","slug":"blueprint-s-of-type-s-is-not-properly-impl-0f7d8b","errorCode":null,"errorMessage":"Blueprint \"%s\" (of type \"%s\") is not properly implemented: it returned from \"%s\" without acquiring a lease.","messagePattern":"Blueprint \"(.+?)\" \\(of type \"(.+?)\"\\) is not properly implemented: it returned from \"(.+?)\" without acquiring a lease\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/drydock/worker/DrydockLeaseUpdateWorker.php","lineNumber":946,"sourceCode":"  }\n\n\n  /**\n   * Make sure that a lease was really acquired properly.\n   *\n   * @param DrydockBlueprint Blueprint which created the resource.\n   * @param DrydockResource Resource which was acquired.\n   * @param DrydockLease The lease which was supposedly acquired.\n   * @return void\n   * @task acquire\n   */\n  private function validateAcquiredLease(\n    DrydockBlueprint $blueprint,\n    DrydockResource $resource,\n    DrydockLease $lease) {\n\n    if (!$lease->isAcquiredLease()) {\n      throw new Exception(\n        pht(\n          'Blueprint \"%s\" (of type \"%s\") is not properly implemented: it '.\n          'returned from \"%s\" without acquiring a lease.',\n          $blueprint->getBlueprintName(),\n          $blueprint->getClassName(),\n          'acquireLease()'));\n    }\n\n    $lease_phid = $lease->getResourcePHID();\n    $resource_phid = $resource->getPHID();\n\n    if ($lease_phid !== $resource_phid) {\n      throw new Exception(\n        pht(\n          'Blueprint \"%s\" (of type \"%s\") is not properly implemented: it '.\n          'returned from \"%s\" with a lease acquired on the wrong resource.',\n          $blueprint->getBlueprintName(),\n          $blueprint->getClassName(),","sourceCodeStart":928,"sourceCodeEnd":964,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php#L928-L964","documentation":"After DrydockLeaseUpdateWorker calls $blueprint->acquireLease($resource, $lease), validateAcquiredLease() verifies the lease actually transitioned to acquired (isAcquiredLease() is set by DrydockLease::acquireLease()). If the blueprint returns normally without acquiring, it violated the contract: acquireLease() must either acquire the lease or throw. The lease would otherwise sit forever in an inconsistent state.","triggerScenarios":"Custom acquireLease() that does setup work (writes credentials, reserves a slot) but forgets to call $lease->acquireLease(); an early return on a path that decides not to lease instead of throwing.","commonSituations":"First implementation of a custom blueprint's acquireLease(); refactoring that extracts the acquire call into a helper that is bypassed on one branch.","solutions":["Call $lease->acquireLease() (and save) inside the blueprint's acquireLease() before returning.","Throw on any path where the lease cannot be acquired rather than returning silently.","Model the implementation on DrydockWorkingCopyBlueprintImplementation::acquireLease()."],"exampleFix":"// before\npublic function acquireLease(DrydockResource $resource, DrydockLease $lease) {\n  $this->reserveSlot($resource);\n  // no $lease->acquireLease() -> validator throws\n}\n\n// after\npublic function acquireLease(DrydockResource $resource, DrydockLease $lease) {\n  $this->reserveSlot($resource);\n  $lease->acquireLease();\n}","handlingStrategy":"validation","validationCode":"if (!$lease->isAcquiredLease()) {\n  throw new Exception(\n    pht('acquireLease() returned without acquiring; call $lease->acquireLease().'));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call $lease->acquireLease() inside blueprint acquireLease() on every success path.","Throw when acquisition is impossible; never return silently."],"tags":["drydock","php","blueprint","contract","lease","state-machine"],"backgroundTag":"invalid-state-transition","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}