{"record":{"id":"c711c5f15c40889f","repo":"phacility/phabricator","slug":"blueprint-s-of-type-s-is-not-properly-impl-c711c5","errorCode":null,"errorMessage":"Blueprint \"%s\" (of type \"%s\") is not properly implemented: it returned from \"%s\" without activating a lease.","messagePattern":"Blueprint \"(.+?)\" \\(of type \"(.+?)\"\\) is not properly implemented: it returned from \"(.+?)\" without activating a lease\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/drydock/worker/DrydockLeaseUpdateWorker.php","lineNumber":1021,"sourceCode":"    // be distinguishable from a resource dying right after we activate a lease\n    // on it. We end up with an active lease on a dead resource either way, and\n    // can not prevent resources dying from lightning strikes.\n\n    $blueprint = $resource->getBlueprint();\n    $blueprint->activateLease($resource, $lease);\n    $this->validateActivatedLease($blueprint, $resource, $lease);\n  }\n\n  /**\n   * @task activate\n   */\n  private function validateActivatedLease(\n    DrydockBlueprint $blueprint,\n    DrydockResource $resource,\n    DrydockLease $lease) {\n\n    if (!$lease->isActivatedLease()) {\n      throw new Exception(\n        pht(\n          'Blueprint \"%s\" (of type \"%s\") is not properly implemented: it '.\n          'returned from \"%s\" without activating a lease.',\n          $blueprint->getBlueprintName(),\n          $blueprint->getClassName(),\n          'acquireLease()'));\n    }\n\n  }\n\n\n/* -(  Releasing Leases  )--------------------------------------------------- */\n\n\n  /**\n   * @task release\n   */\n  private function releaseLease(DrydockLease $lease) {","sourceCodeStart":1003,"sourceCodeEnd":1039,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php#L1003-L1039","documentation":"After the blueprint activates a lease, validateActivatedLease() checks isActivatedLease(), which only becomes true when DrydockLease::activateLease() runs. A normal return without that call violates the blueprint contract: activateLease() must activate the lease or throw. Note the message text in this version says 'acquireLease()' but the check guards the activation step.","triggerScenarios":"Custom blueprint's activateLease() performs its setup (bootstrapping a working directory, writing credentials) but never calls $lease->activateLease(); an early return on a branch where activation was skipped.","commonSituations":"Writing the activation half of a custom blueprint; refactoring that moves the activate call into an exception path only.","solutions":["Call $lease->activateLease() on every success path of the blueprint's activateLease().","Throw instead of returning when activation cannot complete.","Follow DrydockWorkingCopyBlueprintImplementation's activation flow as a template."],"exampleFix":"// before\npublic function activateLease(DrydockResource $resource, DrydockLease $lease) {\n  $this->prepareDirectory($resource);\n  // missing $lease->activateLease()\n}\n\n// after\npublic function activateLease(DrydockResource $resource, DrydockLease $lease) {\n  $this->prepareDirectory($resource);\n  $lease->activateLease();\n}","handlingStrategy":"validation","validationCode":"if (!$lease->isActivatedLease()) {\n  throw new Exception(\n    pht('activateLease() returned without activating; call $lease->activateLease().'));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["End blueprint activateLease() with $lease->activateLease() on every success path.","Throw PhabricatorWorkerYieldException for transient problems so the worker retries instead of failing validation."],"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"}