Index: resources/sql/patches/20131020.harbormaster.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131020.harbormaster.sql
@@ -0,0 +1,74 @@
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  containerPHID VARCHAR(64) COLLATE utf8_bin,
+  buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+  buildableStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+  KEY `key_buildable` (buildablePHID),
+  KEY `key_container` (containerPHID),
+  UNIQUE KEY `key_phid` (phid)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  artifactType VARCHAR(32) NOT NULL COLLATE utf8_bin,
+  artifactIndex VARCHAR(12) NOT NULL COLLATE utf8_bin,
+  artifactKey VARCHAR(255) NOT NULL COLLATE utf8_bin,
+  artifactData LONGTEXT NOT NULL COLLATE utf8_bin,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+  UNIQUE KEY `key_artifact` (buildablePHID, artifactType, artifactIndex),
+  UNIQUE KEY `key_artifact_type` (artifactType, artifactIndex),
+  KEY `key_garbagecollect` (artifactType, dateCreated)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  name VARCHAR(255) NOT NULL,
+  planStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+  UNIQUE KEY `key_phid` (phid),
+  KEY `key_status` (planStatus)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplantransaction (
+  id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  commentPHID VARCHAR(64) COLLATE utf8_bin,
+  commentVersion INT UNSIGNED NOT NULL,
+  transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
+  oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
+  newValue LONGTEXT NOT NULL COLLATE utf8_bin,
+  contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
+  metadata LONGTEXT NOT NULL COLLATE utf8_bin,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+
+  UNIQUE KEY `key_phid` (phid),
+  KEY `key_object` (objectPHID)
+
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_build (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+  buildablePHID varchar(64) NOT NULL COLLATE utf8_bin,
+  buildPlanPHID varchar(64) NOT NULL COLLATE utf8_bin,
+  buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+  UNIQUE KEY `key_phid` (phid),
+  KEY `key_buildable` (buildablePHID),
+  KEY `key_plan` (buildPlanPHID),
+  KEY `key_status` (buildStatus)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -619,8 +619,46 @@
     'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
     'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
     'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
+    'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
+    'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
+    'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
+    'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php',
+    'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php',
+    'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
+    'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
+    'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
+    'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
+    'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
+    'HarbormasterBuildPlanTransactionComment' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php',
+    'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
+    'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
+    'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
+    'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
+    'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
+    'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
+    'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
+    'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php',
+    'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php',
+    'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
+    'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php',
+    'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php',
+    'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php',
+    'HarbormasterCapabilityManagePlans' => 'applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php',
+    'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
     'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
     'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
+    'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php',
+    'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php',
+    'HarbormasterPHIDTypeBuildPlan' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php',
+    'HarbormasterPHIDTypeBuildStep' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php',
+    'HarbormasterPHIDTypeBuildTarget' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php',
+    'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php',
+    'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
+    'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
+    'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php',
+    'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
+    'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
+    'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
     'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php',
     'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
     'HeraldAction' => 'applications/herald/storage/HeraldAction.php',
@@ -846,6 +884,7 @@
     'PhabricatorApplicationFeed' => 'applications/feed/application/PhabricatorApplicationFeed.php',
     'PhabricatorApplicationFiles' => 'applications/files/application/PhabricatorApplicationFiles.php',
     'PhabricatorApplicationFlags' => 'applications/flag/application/PhabricatorApplicationFlags.php',
+    'PhabricatorApplicationHarbormaster' => 'applications/harbormaster/application/PhabricatorApplicationHarbormaster.php',
     'PhabricatorApplicationHerald' => 'applications/herald/application/PhabricatorApplicationHerald.php',
     'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
     'PhabricatorApplicationLegalpad' => 'applications/legalpad/application/PhabricatorApplicationLegalpad.php',
@@ -2740,8 +2779,71 @@
     'FileCreateMailReceiver' => 'PhabricatorMailReceiver',
     'FileMailReceiver' => 'PhabricatorObjectMailReceiver',
     'FileReplyHandler' => 'PhabricatorMailReplyHandler',
+    'HarbormasterBuild' =>
+    array(
+      0 => 'HarbormasterDAO',
+      1 => 'PhabricatorPolicyInterface',
+    ),
+    'HarbormasterBuildArtifact' =>
+    array(
+      0 => 'HarbormasterDAO',
+      1 => 'PhabricatorPolicyInterface',
+    ),
+    'HarbormasterBuildItem' => 'HarbormasterDAO',
+    'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildLog' => 'HarbormasterDAO',
+    'HarbormasterBuildPlan' =>
+    array(
+      0 => 'HarbormasterDAO',
+      1 => 'PhabricatorPolicyInterface',
+      2 => 'PhabricatorSubscribableInterface',
+    ),
+    'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
+    'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
+    'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction',
+    'HarbormasterBuildPlanTransactionComment' => 'PhabricatorApplicationTransactionComment',
+    'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+    'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildStep' => 'HarbormasterDAO',
+    'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildTarget' => 'HarbormasterDAO',
+    'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildable' =>
+    array(
+      0 => 'HarbormasterDAO',
+      1 => 'PhabricatorPolicyInterface',
+    ),
+    'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildableEditController' => 'HarbormasterController',
+    'HarbormasterBuildableListController' =>
+    array(
+      0 => 'HarbormasterController',
+      1 => 'PhabricatorApplicationSearchResultsControllerInterface',
+    ),
+    'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine',
+    'HarbormasterBuildableViewController' => 'HarbormasterController',
+    'HarbormasterCapabilityManagePlans' => 'PhabricatorPolicyCapability',
+    'HarbormasterController' => 'PhabricatorController',
     'HarbormasterDAO' => 'PhabricatorLiskDAO',
     'HarbormasterObject' => 'HarbormasterDAO',
+    'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType',
+    'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType',
+    'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType',
+    'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType',
+    'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType',
+    'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType',
+    'HarbormasterPlanController' => 'PhabricatorController',
+    'HarbormasterPlanEditController' => 'HarbormasterPlanController',
+    'HarbormasterPlanExecuteController' => 'HarbormasterPlanController',
+    'HarbormasterPlanListController' =>
+    array(
+      0 => 'HarbormasterPlanController',
+      1 => 'PhabricatorApplicationSearchResultsControllerInterface',
+    ),
+    'HarbormasterPlanViewController' => 'HarbormasterPlanController',
+    'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
     'HarbormasterRunnerWorker' => 'PhabricatorWorker',
     'HarbormasterScratchTable' => 'HarbormasterDAO',
     'HeraldAction' => 'HeraldDAO',
@@ -2992,6 +3094,7 @@
     'PhabricatorApplicationFeed' => 'PhabricatorApplication',
     'PhabricatorApplicationFiles' => 'PhabricatorApplication',
     'PhabricatorApplicationFlags' => 'PhabricatorApplication',
+    'PhabricatorApplicationHarbormaster' => 'PhabricatorApplication',
     'PhabricatorApplicationHerald' => 'PhabricatorApplication',
     'PhabricatorApplicationLaunchView' => 'AphrontView',
     'PhabricatorApplicationLegalpad' => 'PhabricatorApplication',
Index: src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
@@ -0,0 +1,68 @@
+<?php
+
+final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
+
+  public function getBaseURI() {
+    return '/harbormaster/';
+  }
+
+  public function getShortDescription() {
+    return pht('Continuous Build');
+  }
+
+  public function getIconName() {
+    return 'harbormaster';
+  }
+
+  public function getTitleGlyph() {
+    return "\xE2\x99\xBB";
+  }
+
+  public function getFlavorText() {
+    return pht('Ship Some Freight');
+  }
+
+  public function getApplicationGroup() {
+    return self::GROUP_UTILITIES;
+  }
+
+  public function isBeta() {
+    return true;
+  }
+
+  public function getRemarkupRules() {
+    return array(
+      new HarbormasterRemarkupRule(),
+    );
+  }
+
+  public function getRoutes() {
+    return array(
+      '/B(?P<id>[1-9]\d*)' => 'HarbormasterBuildableViewController',
+      '/harbormaster/' => array(
+        '(?:query/(?P<queryKey>[^/]+)/)?'
+          => 'HarbormasterBuildableListController',
+        'buildable/' => array(
+          'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
+        ),
+        'plan/' => array(
+          '(?:query/(?P<queryKey>[^/]+)/)?'
+            => 'HarbormasterPlanListController',
+          'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
+          '(?P<id>\d+)/' => 'HarbormasterPlanViewController',
+          'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController',
+        ),
+      ),
+    );
+  }
+
+  public function getCustomCapabilities() {
+    return array(
+      HarbormasterCapabilityManagePlans::CAPABILITY => array(
+        'caption' => pht('Can create and manage build plans.'),
+        'default' => PhabricatorPolicies::POLICY_ADMIN,
+      ),
+    );
+  }
+
+}
Index: src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php
@@ -0,0 +1,21 @@
+<?php
+
+final class HarbormasterCapabilityManagePlans
+  extends PhabricatorPolicyCapability {
+
+  const CAPABILITY = 'harbormaster.plans';
+
+  public function getCapabilityKey() {
+    return self::CAPABILITY;
+  }
+
+  public function getCapabilityName() {
+    return pht('Can Manage Build Plans');
+  }
+
+  public function describeCapabilityRejection() {
+    return pht(
+      'You do not have permission to manage Harbormaster build plans.');
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterBuildableEditController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterBuildableEditController.php
@@ -0,0 +1,144 @@
+<?php
+
+final class HarbormasterBuildableEditController
+  extends HarbormasterController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = idx($data, 'id');
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $this->requireApplicationCapability(
+      HarbormasterCapabilityManagePlans::CAPABILITY);
+
+    if ($this->id) {
+      $buildable = id(new HarbormasterBuildableQuery())
+        ->setViewer($viewer)
+        ->withIDs(array($this->id))
+        ->executeOne();
+      if (!$buildable) {
+        return new Aphront404Response();
+      }
+    } else {
+      $buildable = HarbormasterBuildable::initializeNewBuildable($viewer);
+    }
+
+    $e_name = true;
+    $v_name = null;
+
+    $errors = array();
+    if ($request->isFormPost()) {
+      $v_name = $request->getStr('buildablePHID');
+
+      if ($v_name) {
+        $object = id(new PhabricatorObjectQuery())
+          ->setViewer($viewer)
+          ->withNames(array($v_name))
+          ->executeOne();
+
+        if ($object instanceof DifferentialRevision) {
+          throw new Exception(
+            "TODO: We need to assign PHIDs to diffs before this will work.");
+        } else if ($object instanceof PhabricatorRepositoryCommit) {
+          $buildable
+            ->setBuildablePHID($object->getPHID())
+            ->setContainerPHID($object->getRepository()->getPHID());
+        } else {
+          $e_name = pht('Invalid');
+          $errors[] = pht('Enter the name of a revision or commit.');
+        }
+      } else {
+        $e_name = pht('Required');
+        $errors[] = pht('You must choose a revision or commit to build.');
+      }
+
+      if (!$errors) {
+        $buildable->save();
+
+        $buildable_uri = '/B'.$buildable->getID();
+        return id(new AphrontRedirectResponse())->setURI($buildable_uri);
+      }
+    }
+
+    if ($errors) {
+      $errors = id(new AphrontErrorView())->setErrors($errors);
+    }
+
+    $is_new = (!$buildable->getID());
+    if ($is_new) {
+      $title = pht('New Buildable');
+      $cancel_uri = $this->getApplicationURI();
+      $save_button = pht('Create Buildable');
+    } else {
+      $id = $buildable->getID();
+
+      $title = pht('Edit Buildable');
+      $cancel_uri = "/B{$id}";
+      $save_button = pht('Save Buildable');
+    }
+
+    $form = id(new AphrontFormView())
+      ->setUser($viewer);
+
+    if ($is_new) {
+      $form
+        ->appendRemarkupInstructions(
+          pht(
+            'Enter the name of a commit or revision, like `rX123456789` '.
+            'or `D123`.'))
+        ->appendChild(
+          id(new AphrontFormTextControl())
+            ->setLabel('Buildable Name')
+            ->setName('buildablePHID')
+            ->setError($e_name)
+            ->setValue($v_name));
+    } else {
+      $form->appendChild(
+        id(new AphrontFormMarkupControl())
+          ->setLabel(pht('Buildable'))
+          ->setValue($buildable->getBuildableHandle()->renderLink()));
+    }
+
+    $form->appendChild(
+      id(new AphrontFormSubmitControl())
+        ->setValue($save_button)
+        ->addCancelButton($cancel_uri));
+
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText($title)
+      ->setFormError($errors)
+      ->setForm($form);
+
+    $crumbs = $this->buildApplicationCrumbs();
+    if ($is_new) {
+      $crumbs->addCrumb(
+        id(new PhabricatorCrumbView())
+          ->setName(pht('New Buildable')));
+    } else {
+      $id = $buildable->getID();
+      $crumbs->addCrumb(
+        id(new PhabricatorCrumbView())
+          ->setName("B{$id}")
+          ->setHref("/B{$id}"));
+      $crumbs->addCrumb(
+        id(new PhabricatorCrumbView())
+          ->setName(pht('Edit')));
+    }
+
+    return $this->buildApplicationPage(
+      array(
+        $crumbs,
+        $box,
+      ),
+      array(
+        'title' => $title,
+        'device' => true,
+      ));
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterBuildableListController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterBuildableListController.php
@@ -0,0 +1,78 @@
+<?php
+
+final class HarbormasterBuildableListController
+  extends HarbormasterController
+  implements PhabricatorApplicationSearchResultsControllerInterface {
+
+  private $queryKey;
+
+  public function shouldAllowPublic() {
+    return true;
+  }
+
+  public function willProcessRequest(array $data) {
+    $this->queryKey = idx($data, 'queryKey');
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $controller = id(new PhabricatorApplicationSearchController($request))
+      ->setQueryKey($this->queryKey)
+      ->setSearchEngine(new HarbormasterBuildableSearchEngine())
+      ->setNavigation($this->buildSideNavView());
+
+    return $this->delegateToController($controller);
+  }
+
+  public function renderResultsList(
+    array $buildables,
+    PhabricatorSavedQuery $query) {
+    assert_instances_of($buildables, 'HarbormasterBuildable');
+
+    $viewer = $this->getRequest()->getUser();
+
+    $list = new PHUIObjectItemListView();
+    foreach ($buildables as $buildable) {
+      $id = $buildable->getID();
+
+      $item = id(new PHUIObjectItemView())
+        ->setHeader(pht('Build %d', $buildable->getID()));
+
+      if ($id) {
+        $item->setHref("/B{$id}");
+      }
+
+      $list->addItem($item);
+    }
+
+    return $list;
+  }
+
+  public function buildSideNavView($for_app = false) {
+    $user = $this->getRequest()->getUser();
+
+    $nav = new AphrontSideNavFilterView();
+    $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+    id(new HarbormasterBuildableSearchEngine())
+      ->setViewer($user)
+      ->addNavigationItems($nav->getMenu());
+
+    if ($for_app) {
+      $nav->addFilter('new/', pht('New Build Plan'));
+    }
+
+    $nav->addLabel('Utilities');
+    $nav->addFilter('buildable/edit/', pht('New Manual Build'));
+    $nav->addFilter('plan/', pht('Manage Build Plans'));
+
+    $nav->selectFilter(null);
+
+    return $nav;
+  }
+
+  public function buildApplicationMenu() {
+    return $this->buildSideNavView(true)->getMenu();
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -0,0 +1,105 @@
+<?php
+
+final class HarbormasterBuildableViewController
+  extends HarbormasterController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = $data['id'];
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $id = $this->id;
+
+    $buildable = id(new HarbormasterBuildableQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($id))
+      ->needBuildableHandles(true)
+      ->needContainerObjects(true)
+      ->executeOne();
+    if (!$buildable) {
+      return new Aphront404Response();
+    }
+
+    $builds = id(new HarbormasterBuildQuery())
+      ->setViewer($viewer)
+      ->withBuildablePHIDs(array($buildable->getPHID()))
+      ->needBuildPlans(true)
+      ->execute();
+
+    $build_list = id(new PHUIObjectItemListView())
+      ->setUser($viewer);
+    foreach ($builds as $build) {
+      $item = id(new PHUIObjectItemView())
+        ->setObjectName(pht('Build %d', $build->getID()))
+        ->setHeader($build->getName());
+      $build_list->addItem($item);
+    }
+
+    $title = pht("Buildable %d", $id);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setUser($viewer)
+      ->setPolicyObject($buildable);
+
+    $box = id(new PHUIObjectBoxView())
+      ->setHeader($header);
+
+    $actions = $this->buildActionList($buildable);
+    $this->buildPropertyLists($box, $buildable, $actions);
+
+    $crumbs = $this->buildApplicationCrumbs();
+    $crumbs->addCrumb(
+      id(new PhabricatorCrumbView())
+        ->setName("B{$id}"));
+
+    return $this->buildApplicationPage(
+      array(
+        $crumbs,
+        $box,
+        $build_list,
+      ),
+      array(
+        'title' => $title,
+        'device' => true,
+      ));
+  }
+
+  private function buildActionList(HarbormasterBuildable $buildable) {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+    $id = $buildable->getID();
+
+    $list = id(new PhabricatorActionListView())
+      ->setUser($viewer)
+      ->setObject($buildable)
+      ->setObjectURI("/B{$id}");
+
+    return $list;
+  }
+
+  private function buildPropertyLists(
+    PHUIObjectBoxView $box,
+    HarbormasterBuildable $buildable,
+    PhabricatorActionListView $actions) {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $properties = id(new PHUIPropertyListView())
+      ->setUser($viewer)
+      ->setObject($buildable)
+      ->setActionList($actions);
+    $box->addPropertyList($properties);
+
+    $properties->addProperty(
+      pht('Buildable'),
+      $buildable->getBuildableHandle()->renderLink());
+
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterController.php
@@ -0,0 +1,17 @@
+<?php
+
+abstract class HarbormasterController extends PhabricatorController {
+
+  public function buildApplicationCrumbs() {
+    $crumbs = parent::buildApplicationCrumbs();
+
+    $crumbs->addAction(
+      id(new PHUIListItemView())
+        ->setName(pht('New Build Plan'))
+        ->setHref($this->getApplicationURI('plan/edit/'))
+        ->setIcon('create'));
+
+    return $crumbs;
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterPlanController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterPlanController.php
@@ -0,0 +1,16 @@
+<?php
+
+abstract class HarbormasterPlanController extends PhabricatorController {
+
+  public function buildApplicationCrumbs() {
+    $crumbs = parent::buildApplicationCrumbs();
+
+    $crumbs->addCrumb(
+      id(new PhabricatorCrumbView())
+        ->setName(pht('Build Plans'))
+        ->setHref($this->getApplicationURI('plan/')));
+
+    return $crumbs;
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterPlanEditController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterPlanEditController.php
@@ -0,0 +1,121 @@
+<?php
+
+final class HarbormasterPlanEditController
+  extends HarbormasterPlanController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = idx($data, 'id');
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $this->requireApplicationCapability(
+      HarbormasterCapabilityManagePlans::CAPABILITY);
+
+    if ($this->id) {
+      $plan = id(new HarbormasterBuildPlanQuery())
+        ->setViewer($viewer)
+        ->withIDs(array($this->id))
+        ->executeOne();
+      if (!$plan) {
+        return new Aphront404Response();
+      }
+    } else {
+      $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
+    }
+
+    $e_name = true;
+    $v_name = $plan->getName();
+    $validation_exception = null;
+    if ($request->isFormPost()) {
+      $xactions = array();
+
+      $v_name = $request->getStr('name');
+      $e_name = null;
+      $type_name = HarbormasterBuildPlanTransaction::TYPE_NAME;
+
+      $xactions[] = id(new HarbormasterBuildPlanTransaction())
+        ->setTransactionType($type_name)
+        ->setNewValue($v_name);
+
+      $editor = id(new HarbormasterBuildPlanEditor())
+        ->setActor($viewer)
+        ->setContentSourceFromRequest($request);
+
+      try {
+        $editor->applyTransactions($plan, $xactions);
+        return id(new AphrontRedirectResponse())
+          ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
+      } catch (PhabricatorApplicationTransactionValidationException $ex) {
+        $validation_exception = $ex;
+
+        $e_name = $validation_exception->getShortMessage(
+          HarbormasterBuildPlanTransaction::TYPE_NAME);
+      }
+
+    }
+
+    $is_new = (!$plan->getID());
+    if ($is_new) {
+      $title = pht('New Build Plan');
+      $cancel_uri = $this->getApplicationURI();
+      $save_button = pht('Create Build Plan');
+    } else {
+      $id = $plan->getID();
+
+      $title = pht('Edit Build Plan');
+      $cancel_uri = "/B{$id}";
+      $save_button = pht('Save Build Plan');
+    }
+
+    $form = id(new AphrontFormView())
+      ->setUser($viewer)
+      ->appendChild(
+        id(new AphrontFormTextControl())
+          ->setLabel('Plan Name')
+          ->setName('name')
+          ->setError($e_name)
+          ->setValue($v_name));
+
+    $form->appendChild(
+      id(new AphrontFormSubmitControl())
+        ->setValue($save_button)
+        ->addCancelButton($cancel_uri));
+
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText($title)
+      ->setValidationException($validation_exception)
+      ->setForm($form);
+
+    $crumbs = $this->buildApplicationCrumbs();
+    if ($is_new) {
+      $crumbs->addCrumb(
+        id(new PhabricatorCrumbView())
+          ->setName(pht('New Build Plan')));
+    } else {
+      $id = $plan->getID();
+      $crumbs->addCrumb(
+        id(new PhabricatorCrumbView())
+          ->setName(pht("Plan %d", $id))
+          ->setHref($this->getApplicationURI("plan/{$id}/")));
+      $crumbs->addCrumb(
+        id(new PhabricatorCrumbView())
+          ->setName(pht('Edit')));
+    }
+
+    return $this->buildApplicationPage(
+      array(
+        $crumbs,
+        $box,
+      ),
+      array(
+        'title' => $title,
+        'device' => true,
+      ));
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
@@ -0,0 +1,88 @@
+<?php
+
+final class HarbormasterPlanExecuteController
+  extends HarbormasterPlanController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = $data['id'];
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $this->requireApplicationCapability(
+      HarbormasterCapabilityManagePlans::CAPABILITY);
+
+    $id = $this->id;
+
+    $plan = id(new HarbormasterBuildPlanQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($id))
+      ->executeOne();
+    if (!$plan) {
+      return new Aphront404Response();
+    }
+
+    $cancel_uri = $this->getApplicationURI("plan/{$id}/");
+
+    $v_buildable = null;
+    $e_buildable = null;
+
+    $errors = array();
+    if ($request->isFormPost()) {
+      $v_buildable = $request->getStr('buildable');
+
+      if ($v_buildable) {
+        $buildable = id(new HarbormasterBuildableQuery())
+          ->setViewer($viewer)
+          ->withIDs(array(trim($v_buildable, 'B')))
+          ->executeOne();
+        if (!$buildable) {
+          $e_buildable = pht('Invalid');
+        }
+      } else {
+        $e_buildable = pht('Required');
+        $errors[] = pht('You must provide a buildable.');
+      }
+
+      if (!$errors) {
+        $build_plan = HarbormasterBuild::initializeNewBuild($viewer)
+          ->setBuildablePHID($buildable->getPHID())
+          ->setBuildPlanPHID($plan->getPHID())
+          ->save();
+
+        $buildable_id = $buildable->getID();
+
+        return id(new AphrontRedirectResponse())
+          ->setURI("/B{$buildable_id}");
+      }
+    }
+
+    if ($errors) {
+      $errors = id(new AphrontErrorView())->setErrors($errors);
+    }
+
+    $form = id(new PHUIFormLayoutView())
+      ->appendChild(
+        id(new AphrontFormTextControl())
+          ->setLabel(pht('Buildable'))
+          ->setName('buildable')
+          ->setValue($v_buildable)
+          ->setError($e_buildable));
+
+    $dialog = id(new AphrontDialogView())
+      ->setUser($viewer)
+      ->setTitle(pht('Execute Build Plan'))
+      ->setWidth(AphrontDialogView::WIDTH_FORM)
+      ->appendChild($errors)
+      ->appendChild($form)
+      ->addSubmitButton(pht('Execute Build Plan'))
+      ->addCancelButton($cancel_uri);
+
+    return id(new AphrontDialogResponse())->setDialog($dialog);
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterPlanListController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterPlanListController.php
@@ -0,0 +1,69 @@
+<?php
+
+final class HarbormasterPlanListController
+  extends HarbormasterPlanController
+  implements PhabricatorApplicationSearchResultsControllerInterface {
+
+  private $queryKey;
+
+  public function shouldAllowPublic() {
+    return true;
+  }
+
+  public function willProcessRequest(array $data) {
+    $this->queryKey = idx($data, 'queryKey');
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $controller = id(new PhabricatorApplicationSearchController($request))
+      ->setQueryKey($this->queryKey)
+      ->setSearchEngine(new HarbormasterBuildPlanSearchEngine())
+      ->setNavigation($this->buildSideNavView());
+
+    return $this->delegateToController($controller);
+  }
+
+  public function renderResultsList(
+    array $plans,
+    PhabricatorSavedQuery $query) {
+    assert_instances_of($plans, 'HarbormasterBuildPlan');
+
+    $viewer = $this->getRequest()->getUser();
+
+    $list = new PHUIObjectItemListView();
+    foreach ($plans as $plan) {
+      $id = $plan->getID();
+
+      $item = id(new PHUIObjectItemView())
+        ->setObjectName(pht('Plan %d', $plan->getID()))
+        ->setHeader($plan->getName());
+
+      $item->setHref($this->getApplicationURI("plan/{$id}/"));
+
+      $list->addItem($item);
+    }
+
+    return $list;
+  }
+
+  public function buildSideNavView($for_app = false) {
+    $user = $this->getRequest()->getUser();
+
+    $nav = new AphrontSideNavFilterView();
+    $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+    id(new HarbormasterBuildPlanSearchEngine())
+      ->setViewer($user)
+      ->addNavigationItems($nav->getMenu());
+
+    $nav->selectFilter(null);
+
+    return $nav;
+  }
+
+  public function buildApplicationMenu() {
+    return $this->buildSideNavView(true)->getMenu();
+  }
+
+}
Index: src/applications/harbormaster/controller/HarbormasterPlanViewController.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -0,0 +1,121 @@
+<?php
+
+final class HarbormasterPlanViewController
+  extends HarbormasterPlanController {
+
+  private $id;
+
+  public function willProcessRequest(array $data) {
+    $this->id = $data['id'];
+  }
+
+  public function processRequest() {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $id = $this->id;
+
+    $plan = id(new HarbormasterBuildPlanQuery())
+      ->setViewer($viewer)
+      ->withIDs(array($id))
+      ->executeOne();
+    if (!$plan) {
+      return new Aphront404Response();
+    }
+
+    $xactions = id(new HarbormasterBuildPlanTransactionQuery())
+      ->setViewer($viewer)
+      ->withObjectPHIDs(array($plan->getPHID()))
+      ->execute();
+
+    $engine = id(new PhabricatorMarkupEngine())
+      ->setViewer($viewer);
+
+    $xaction_view = id(new PhabricatorApplicationTransactionView())
+      ->setUser($viewer)
+      ->setObjectPHID($plan->getPHID())
+      ->setTransactions($xactions)
+      ->setMarkupEngine($engine);
+
+    $title = pht("Plan %d", $id);
+
+    $header = id(new PHUIHeaderView())
+      ->setHeader($title)
+      ->setUser($viewer)
+      ->setPolicyObject($plan);
+
+    $box = id(new PHUIObjectBoxView())
+      ->setHeader($header);
+
+    $actions = $this->buildActionList($plan);
+    $this->buildPropertyLists($box, $plan, $actions);
+
+    $crumbs = $this->buildApplicationCrumbs();
+    $crumbs->addCrumb(
+      id(new PhabricatorCrumbView())
+        ->setName(pht("Plan %d", $id)));
+
+    return $this->buildApplicationPage(
+      array(
+        $crumbs,
+        $box,
+        $xaction_view,
+      ),
+      array(
+        'title' => $title,
+        'device' => true,
+      ));
+  }
+
+  private function buildActionList(HarbormasterBuildPlan $plan) {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+    $id = $plan->getID();
+
+    $list = id(new PhabricatorActionListView())
+      ->setUser($viewer)
+      ->setObject($plan)
+      ->setObjectURI($this->getApplicationURI("plan/{$id}/"));
+
+    $can_edit = $this->hasApplicationCapability(
+      HarbormasterCapabilityManagePlans::CAPABILITY);
+
+    $list->addAction(
+      id(new PhabricatorActionView())
+        ->setName(pht('Edit Plan'))
+        ->setHref($this->getApplicationURI("plan/edit/{$id}/"))
+        ->setWorkflow(!$can_edit)
+        ->setDisabled(!$can_edit)
+        ->setIcon('edit'));
+
+    $list->addAction(
+      id(new PhabricatorActionView())
+        ->setName(pht('Manually Execute Plan'))
+        ->setHref($this->getApplicationURI("plan/execute/{$id}/"))
+        ->setWorkflow(true)
+        ->setDisabled(!$can_edit)
+        ->setIcon('arrow_right'));
+
+    return $list;
+  }
+
+  private function buildPropertyLists(
+    PHUIObjectBoxView $box,
+    HarbormasterBuildPlan $plan,
+    PhabricatorActionListView $actions) {
+    $request = $this->getRequest();
+    $viewer = $request->getUser();
+
+    $properties = id(new PHUIPropertyListView())
+      ->setUser($viewer)
+      ->setObject($plan)
+      ->setActionList($actions);
+    $box->addPropertyList($properties);
+
+    $properties->addProperty(
+      pht('Created'),
+      phabricator_datetime($plan->getDateCreated(), $viewer));
+
+  }
+
+}
Index: src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
@@ -0,0 +1,91 @@
+<?php
+
+final class HarbormasterBuildPlanEditor
+  extends PhabricatorApplicationTransactionEditor {
+
+  public function getTransactionTypes() {
+    $types = parent::getTransactionTypes();
+    $types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
+    $types[] = PhabricatorTransactions::TYPE_COMMENT;
+    return $types;
+  }
+
+  protected function getCustomTransactionOldValue(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+    switch ($xaction->getTransactionType()) {
+      case HarbormasterBuildPlanTransaction::TYPE_NAME:
+        if ($this->getIsNewObject()) {
+          return null;
+        }
+        return $object->getName();
+    }
+
+    return parent::getCustomTransactionOldValue($object, $xaction);
+  }
+
+  protected function getCustomTransactionNewValue(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+    switch ($xaction->getTransactionType()) {
+      case HarbormasterBuildPlanTransaction::TYPE_NAME:
+        return $xaction->getNewValue();
+    }
+    return parent::getCustomTransactionNewValue($object, $xaction);
+  }
+
+  protected function applyCustomInternalTransaction(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+    switch ($xaction->getTransactionType()) {
+      case HarbormasterBuildPlanTransaction::TYPE_NAME:
+        $object->setName($xaction->getNewValue());
+        return;
+    }
+    return parent::applyCustomInternalTransaction($object, $xaction);
+  }
+
+  protected function applyCustomExternalTransaction(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+    switch ($xaction->getTransactionType()) {
+      case HarbormasterBuildPlanTransaction::TYPE_NAME:
+        return;
+    }
+    return parent::applyCustomExternalTransaction($object, $xaction);
+  }
+
+  protected function validateTransaction(
+    PhabricatorLiskDAO $object,
+    $type,
+    array $xactions) {
+
+    $errors = parent::validateTransaction($object, $type, $xactions);
+
+    switch ($type) {
+      case HarbormasterBuildPlanTransaction::TYPE_NAME:
+        $missing_name = true;
+        if (strlen($object->getName()) && empty($xactions)) {
+          $missing_name = false;
+        } else if (strlen(last($xactions)->getNewValue())) {
+          $missing_name = false;
+        }
+
+        if ($missing_name) {
+          $error = new PhabricatorApplicationTransactionValidationError(
+            $type,
+            pht('Required'),
+            pht('Plan name is required.'),
+            last($xactions));
+
+          $error->setIsMissingFieldError(true);
+          $errors[] = $error;
+        }
+        break;
+    }
+
+    return $errors;
+  }
+
+
+}
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuild extends PhabricatorPHIDType {
+
+  const TYPECONST = 'HMBD';
+
+  public function getTypeConstant() {
+    return self::TYPECONST;
+  }
+
+  public function getTypeName() {
+    return pht('Build');
+  }
+
+  public function newObject() {
+    return new HarbormasterBuild();
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new HarbormasterBuildQuery())
+      ->withPHIDs($phids);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $build_plan = $objects[$phid];
+    }
+  }
+
+}
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildItem extends PhabricatorPHIDType {
+
+  const TYPECONST = 'HMBI';
+
+  public function getTypeConstant() {
+    return self::TYPECONST;
+  }
+
+  public function getTypeName() {
+    return pht('Build Item');
+  }
+
+  public function newObject() {
+    return new HarbormasterBuildItem();
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new HarbormasterBuildItemQuery())
+      ->withPHIDs($phids);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $build_item = $objects[$phid];
+    }
+  }
+
+}
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildPlan extends PhabricatorPHIDType {
+
+  const TYPECONST = 'HMCP';
+
+  public function getTypeConstant() {
+    return self::TYPECONST;
+  }
+
+  public function getTypeName() {
+    return pht('Build Plan');
+  }
+
+  public function newObject() {
+    return new HarbormasterBuildPlan();
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new HarbormasterBuildPlanQuery())
+      ->withPHIDs($phids);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $build_plan = $objects[$phid];
+    }
+  }
+
+}
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildStep extends PhabricatorPHIDType {
+
+  const TYPECONST = 'HMCS';
+
+  public function getTypeConstant() {
+    return self::TYPECONST;
+  }
+
+  public function getTypeName() {
+    return pht('Build Step');
+  }
+
+  public function newObject() {
+    return new HarbormasterBuildStep();
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new HarbormasterBuildStepQuery())
+      ->withPHIDs($phids);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $build_step = $objects[$phid];
+    }
+  }
+
+}
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildTarget extends PhabricatorPHIDType {
+
+  const TYPECONST = 'HMBT';
+
+  public function getTypeConstant() {
+    return self::TYPECONST;
+  }
+
+  public function getTypeName() {
+    return pht('Build Target');
+  }
+
+  public function newObject() {
+    return new HarbormasterBuildTarget();
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new HarbormasterBuildTargetQuery())
+      ->withPHIDs($phids);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $build_target = $objects[$phid];
+    }
+  }
+
+}
Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php
@@ -0,0 +1,74 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildable extends PhabricatorPHIDType {
+
+  const TYPECONST = 'HMBB';
+
+  public function getTypeConstant() {
+    return self::TYPECONST;
+  }
+
+  public function getTypeName() {
+    return pht('Buildable');
+  }
+
+  public function newObject() {
+    return new HarbormasterBuildable();
+  }
+
+  protected function buildQueryForObjects(
+    PhabricatorObjectQuery $query,
+    array $phids) {
+
+    return id(new HarbormasterBuildableQuery())
+      ->withPHIDs($phids)
+      ->needBuildableHandles(true);
+  }
+
+  public function loadHandles(
+    PhabricatorHandleQuery $query,
+    array $handles,
+    array $objects) {
+
+    foreach ($handles as $phid => $handle) {
+      $buildable = $objects[$phid];
+
+      $id = $buildable->getID();
+      $target = $buildable->getBuildableHandle()->getFullName();
+
+      $handle->setURI("/B{$id}");
+      $handle->setName("B{$id}");
+      $handle->setFullName("B{$id}: ".$target);
+    }
+  }
+
+  public function canLoadNamedObject($name) {
+    return preg_match('/^B\d*[1-9]\d*$/i', $name);
+  }
+
+  public function loadNamedObjects(
+    PhabricatorObjectQuery $query,
+    array $names) {
+
+    $id_map = array();
+    foreach ($names as $name) {
+      $id = (int)substr($name, 1);
+      $id_map[$id][] = $name;
+    }
+
+    $objects = id(new HarbormasterBuildableQuery())
+      ->setViewer($query->getViewer())
+      ->withIDs(array_keys($id_map))
+      ->execute();
+
+    $results = array();
+    foreach ($objects as $id => $object) {
+      foreach (idx($id_map, $id, array()) as $name) {
+        $results[$name] = $object;
+      }
+    }
+
+    return $results;
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildItemQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuildItem();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'phid in (%Ls)',
+        $this->phids);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
@@ -0,0 +1,61 @@
+<?php
+
+final class HarbormasterBuildPlanQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuildPlan();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'phid IN (%Ls)',
+        $this->phids);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
@@ -0,0 +1,49 @@
+<?php
+
+final class HarbormasterBuildPlanSearchEngine
+  extends PhabricatorApplicationSearchEngine {
+
+  public function buildSavedQueryFromRequest(AphrontRequest $request) {
+    $saved = new PhabricatorSavedQuery();
+
+    return $saved;
+  }
+
+  public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+    $query = id(new HarbormasterBuildPlanQuery());
+
+    return $query;
+  }
+
+  public function buildSearchForm(
+    AphrontFormView $form,
+    PhabricatorSavedQuery $saved_query) {
+
+  }
+
+  protected function getURI($path) {
+    return '/harbormaster/plan/'.$path;
+  }
+
+  public function getBuiltinQueryNames() {
+    $names = array(
+      'all' => pht('All Plans'),
+    );
+
+    return $names;
+  }
+
+  public function buildSavedQueryFromBuiltin($query_key) {
+
+    $query = $this->newSavedQuery();
+    $query->setQueryKey($query_key);
+
+    switch ($query_key) {
+      case 'all':
+        return $query;
+    }
+
+    return parent::buildSavedQueryFromBuiltin($query_key);
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class HarbormasterBuildPlanTransactionQuery
+  extends PhabricatorApplicationTransactionQuery {
+
+  public function getTemplateApplicationTransaction() {
+    return new HarbormasterBuildPlanTransaction();
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildQuery.php
@@ -0,0 +1,141 @@
+<?php
+
+final class HarbormasterBuildQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+  private $buildablePHIDs;
+  private $buildPlanPHIDs;
+
+  private $needBuildPlans;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  public function withBuildablePHIDs(array $buildable_phids) {
+    $this->buildablePHIDs = $buildable_phids;
+    return $this;
+  }
+
+  public function withBuildPlanPHIDs(array $build_plan_phids) {
+    $this->buildPlanPHIDs = $build_plan_phids;
+    return $this;
+  }
+
+  public function needBuildPlans($need_plans) {
+    $this->needBuildPlans = $need_plans;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuild();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+  protected function willFilterPage(array $page) {
+    $buildables = array();
+
+    $buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
+    if ($buildable_phids) {
+      $buildables = id(new PhabricatorObjectQuery())
+        ->setViewer($this->getViewer())
+        ->withPHIDs($buildable_phids)
+        ->setParentQuery($this)
+        ->execute();
+      $buildables = mpull($buildables, null, 'getPHID');
+    }
+
+    foreach ($page as $key => $build) {
+      $buildable_phid = $build->getBuildablePHID();
+      if (empty($buildables[$buildable_phid])) {
+        unset($page[$key]);
+        continue;
+      }
+      $build->attachBuildable($buildables[$buildable_phid]);
+    }
+
+    return $page;
+  }
+
+  protected function didFilterPage(array $page) {
+    if ($this->needBuildPlans) {
+      $plans = array();
+
+      $plan_phids = array_filter(mpull($page, 'getBuildPlanPHID'));
+      if ($plan_phids) {
+        $plans = id(new PhabricatorObjectQuery())
+          ->setViewer($this->getViewer())
+          ->withPHIDs($plan_phids)
+          ->setParentQuery($this)
+          ->execute();
+        $plans = mpull($plans, null, 'getPHID');
+      }
+
+      foreach ($page as $key => $build) {
+        $plan_phid = $build->getBuildPlanPHID();
+        $build->attachBuildPlan(idx($plans, $plan_phid));
+      }
+    }
+
+    return $page;
+  }
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'phid in (%Ls)',
+        $this->phids);
+    }
+
+    if ($this->buildablePHIDs) {
+      $where[] = qsprintf(
+        $conn_r,
+        'buildablePHID IN (%Ls)',
+        $this->buildablePHIDs);
+    }
+
+    if ($this->buildPlanPHIDs) {
+      $where[] = qsprintf(
+        $conn_r,
+        'buildPlanPHID IN (%Ls)',
+        $this->buildPlanPHIDs);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildStepQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuildStep();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'phid in (%Ls)',
+        $this->phids);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildTargetQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuildTarget();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'phid in (%Ls)',
+        $this->phids);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php
@@ -0,0 +1,116 @@
+<?php
+
+final class HarbormasterBuildableArtifactQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $buildablePHIDs;
+  private $artifactTypes;
+  private $artifactKeys;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withBuildablePHIDs(array $buildable_phids) {
+    $this->buildablePHIDs = $buildable_phids;
+    return $this;
+  }
+
+  public function withArtifactTypes(array $artifact_types) {
+    $this->artifactTypes = $artifact_types;
+    return $this;
+  }
+
+  public function withArtifactKeys(array $artifact_keys) {
+    $this->artifactKeys = $artifact_keys;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuildArtifact();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+  protected function willFilterPage(array $page) {
+    $buildables = array();
+
+    $buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
+    if ($buildable_phids) {
+      $buildables = id(new PhabricatorObjectQuery())
+        ->setViewer($this->getViewer())
+        ->withPHIDs($buildable_phids)
+        ->setParentQuery($this)
+        ->execute();
+      $buildables = mpull($buildables, null, 'getPHID');
+    }
+
+    foreach ($page as $key => $artifact) {
+      $buildable_phid = $artifact->getBuildablePHID();
+      if (empty($buildables[$buildable_phid])) {
+        unset($page[$key]);
+        continue;
+      }
+      $artifact->attachBuildable($buildables[$buildable_phid]);
+    }
+
+    return $page;
+  }
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->buildablePHIDs) {
+      $where[] = qsprintf(
+        $conn_r,
+        'buildablePHID IN (%Ls)',
+        $this->buildablePHIDs);
+    }
+
+    if ($this->artifactTypes) {
+      $where[] = qsprintf(
+        $conn_r,
+        'artifactType in (%Ls)',
+        $this->artifactTypes);
+    }
+
+    if ($this->artifactKeys) {
+      $indexes = array();
+      foreach ($this->artifactKeys as $key) {
+        $indexes[] = PhabricatorHash::digestForIndex($key);
+      }
+
+      $where[] = qsprintf(
+        $conn_r,
+        'artifactIndex IN (%Ls)',
+        $indexes);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildableQuery.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildableQuery.php
@@ -0,0 +1,165 @@
+<?php
+
+final class HarbormasterBuildableQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $phids;
+  private $buildablePHIDs;
+  private $containerPHIDs;
+
+  private $needContainerObjects;
+  private $needBuildableHandles;
+
+  public function withIDs(array $ids) {
+    $this->ids = $ids;
+    return $this;
+  }
+
+  public function withPHIDs(array $phids) {
+    $this->phids = $phids;
+    return $this;
+  }
+
+  public function withBuildablePHIDs(array $buildable_phids) {
+    $this->buildablePHIDs = $buildable_phids;
+    return $this;
+  }
+
+  public function withContainerPHIDs(array $container_phids) {
+    $this->containerPHIDs = $container_phids;
+    return $this;
+  }
+
+  public function needContainerObjects($need) {
+    $this->needContainerObjects = $need;
+    return $this;
+  }
+
+  public function needBuildableHandles($need) {
+    $this->needBuildableHandles = $need;
+    return $this;
+  }
+
+  protected function loadPage() {
+    $table = new HarbormasterBuildable();
+    $conn_r = $table->establishConnection('r');
+
+    $data = queryfx_all(
+      $conn_r,
+      'SELECT * FROM %T %Q %Q %Q',
+      $table->getTableName(),
+      $this->buildWhereClause($conn_r),
+      $this->buildOrderClause($conn_r),
+      $this->buildLimitClause($conn_r));
+
+    return $table->loadAllFromArray($data);
+  }
+
+  protected function willFilterPage(array $page) {
+    $buildables = array();
+
+    $buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
+    if ($buildable_phids) {
+      $buildables = id(new PhabricatorObjectQuery())
+        ->setViewer($this->getViewer())
+        ->withPHIDs($buildable_phids)
+        ->setParentQuery($this)
+        ->execute();
+      $buildables = mpull($buildables, null, 'getPHID');
+    }
+
+    foreach ($page as $key => $buildable) {
+      $buildable_phid = $buildable->getBuildablePHID();
+      if (empty($buildables[$buildable_phid])) {
+        unset($page[$key]);
+        continue;
+      }
+      $buildable->attachBuildableObject($buildables[$buildable_phid]);
+    }
+
+    return $page;
+  }
+
+  protected function didFilterPage(array $page) {
+    if ($this->needContainerObjects) {
+      $containers = array();
+
+      $container_phids = array_filter(mpull($page, 'getContainerPHID'));
+      if ($container_phids) {
+        $containers = id(new PhabricatorObjectQuery())
+          ->setViewer($this->getViewer())
+          ->withPHIDs($container_phids)
+          ->setParentQuery($this)
+          ->execute();
+        $containers = mpull($containers, null, 'getPHID');
+      }
+
+      foreach ($page as $key => $buildable) {
+        $container_phid = $buildable->getContainerPHID();
+        $buildable->attachContainerObject(idx($containers, $container_phid));
+      }
+    }
+
+    if ($this->needBuildableHandles) {
+      $handles = array();
+
+      $handle_phids = array_filter(mpull($page, 'getBuildablePHID'));
+      if ($handle_phids) {
+        $handles = id(new PhabricatorHandleQuery())
+          ->setViewer($this->getViewer())
+          ->withPHIDs($handle_phids)
+          ->setParentQuery($this)
+          ->execute();
+      }
+
+      foreach ($page as $key => $buildable) {
+        $handle_phid = $buildable->getBuildablePHID();
+        $buildable->attachBuildableHandle(idx($handles, $handle_phid));
+      }
+    }
+
+    return $page;
+  }
+
+  private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+    $where = array();
+
+    if ($this->ids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'id IN (%Ld)',
+        $this->ids);
+    }
+
+    if ($this->phids) {
+      $where[] = qsprintf(
+        $conn_r,
+        'phid IN (%Ls)',
+        $this->phids);
+    }
+
+    if ($this->buildablePHIDs) {
+      $where[] = qsprintf(
+        $conn_r,
+        'buildablePHID IN (%Ls)',
+        $this->buildablePHIDs);
+    }
+
+    if ($this->containerPHIDs) {
+      $where[] = qsprintf(
+        $conn_r,
+        'containerPHID in (%Ls)',
+        $this->containerPHIDs);
+    }
+
+    $where[] = $this->buildPagingClause($conn_r);
+
+    return $this->formatWhereClause($where);
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorApplicationHarbormaster';
+  }
+
+}
Index: src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php
@@ -0,0 +1,49 @@
+<?php
+
+final class HarbormasterBuildableSearchEngine
+  extends PhabricatorApplicationSearchEngine {
+
+  public function buildSavedQueryFromRequest(AphrontRequest $request) {
+    $saved = new PhabricatorSavedQuery();
+
+    return $saved;
+  }
+
+  public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+    $query = id(new HarbormasterBuildableQuery());
+
+    return $query;
+  }
+
+  public function buildSearchForm(
+    AphrontFormView $form,
+    PhabricatorSavedQuery $saved_query) {
+
+  }
+
+  protected function getURI($path) {
+    return '/harbormaster/'.$path;
+  }
+
+  public function getBuiltinQueryNames() {
+    $names = array(
+      'all' => pht('All Buildables'),
+    );
+
+    return $names;
+  }
+
+  public function buildSavedQueryFromBuiltin($query_key) {
+
+    $query = $this->newSavedQuery();
+    $query->setQueryKey($query_key);
+
+    switch ($query_key) {
+      case 'all':
+        return $query;
+    }
+
+    return parent::buildSavedQueryFromBuiltin($query_key);
+  }
+
+}
Index: src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php
@@ -0,0 +1,18 @@
+<?php
+
+final class HarbormasterRemarkupRule
+  extends PhabricatorRemarkupRuleObject {
+
+  protected function getObjectNamePrefix() {
+    return 'B';
+  }
+
+  protected function loadObjects(array $ids) {
+    $viewer = $this->getEngine()->getConfig('viewer');
+    return id(new HarbormasterBuildableQuery())
+      ->setViewer($viewer)
+      ->withIDs($ids)
+      ->execute();
+  }
+
+}
Index: src/applications/harbormaster/storage/HarbormasterBuildable.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/HarbormasterBuildable.php
@@ -0,0 +1,85 @@
+<?php
+
+final class HarbormasterBuildable extends HarbormasterDAO
+  implements PhabricatorPolicyInterface {
+
+  protected $buildablePHID;
+  protected $containerPHID;
+  protected $buildStatus;
+  protected $buildableStatus;
+
+  private $buildableObject = self::ATTACHABLE;
+  private $containerObject = self::ATTACHABLE;
+  private $buildableHandle = self::ATTACHABLE;
+
+  public static function initializeNewBuildable(PhabricatorUser $actor) {
+    return id(new HarbormasterBuildable())
+      ->setBuildStatus('new')         // TODO: Define these.
+      ->setBuildableStatus('active'); // TODO: Define these, too.
+  }
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+    ) + parent::getConfiguration();
+  }
+
+  public function generatePHID() {
+    return PhabricatorPHID::generateNewPHID(
+      HarbormasterPHIDTypeBuildable::TYPECONST);
+  }
+
+  public function attachBuildableObject($buildable_object) {
+    $this->buildableObject = $buildable_object;
+    return $this;
+  }
+
+  public function getBuildableObject() {
+    return $this->assertAttached($this->buildableObject);
+  }
+
+  public function attachContainerObject($container_object) {
+    $this->containerObject = $container_object;
+    return $this;
+  }
+
+  public function getContainerObject() {
+    return $this->assertAttached($this->containerObject);
+  }
+
+  public function attachBuildableHandle($buildable_handle) {
+    $this->buildableHandle = $buildable_handle;
+    return $this;
+  }
+
+  public function getBuildableHandle() {
+    return $this->assertAttached($this->buildableHandle);
+  }
+
+
+/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
+
+
+  public function getCapabilities() {
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+    );
+  }
+
+  public function getPolicy($capability) {
+    return $this->getBuildableObject()->getPolicy($capability);
+  }
+
+  public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+    return $this->getBuildableObject()->hasAutomaticCapability(
+      $capability,
+      $viewer);
+  }
+
+  public function describeAutomaticCapability($capability) {
+    return pht(
+      'Users must be able to see the revision or repository to see a '.
+      'buildable.');
+  }
+
+}
Index: src/applications/harbormaster/storage/build/HarbormasterBuild.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -0,0 +1,80 @@
+<?php
+
+final class HarbormasterBuild extends HarbormasterDAO
+  implements PhabricatorPolicyInterface {
+
+  protected $buildablePHID;
+  protected $buildPlanPHID;
+  protected $buildStatus;
+
+  private $buildable = self::ATTACHABLE;
+  private $buildPlan = self::ATTACHABLE;
+
+  public static function initializeNewBuild(PhabricatorUser $actor) {
+    return id(new HarbormasterBuild())
+      ->setBuildStatus('building'); // TODO: Sort this.
+  }
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+    ) + parent::getConfiguration();
+  }
+
+  public function generatePHID() {
+    return PhabricatorPHID::generateNewPHID(
+      HarbormasterPHIDTypeBuildPlan::TYPECONST);
+  }
+
+  public function attachBuildable(HarbormasterBuildable $buildable) {
+    $this->buildable = $buildable;
+    return $this;
+  }
+
+  public function getBuildable() {
+    return $this->assertAttached($this->buildable);
+  }
+
+  public function getName() {
+    if ($this->getBuildPlan()) {
+      return $this->getBuildPlan()->getName();
+    }
+    return pht('Build');
+  }
+
+  public function attachBuildPlan(
+    HarbormasterBuildPlan $build_plan = null) {
+    $this->buildPlan = $build_plan;
+    return $this;
+  }
+
+  public function getBuildPlan() {
+    return $this->assertAttached($this->buildPlan);
+  }
+
+
+/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
+
+
+  public function getCapabilities() {
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+    );
+  }
+
+  public function getPolicy($capability) {
+    return $this->getBuildable()->getPolicy($capability);
+  }
+
+  public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+    return $this->getBuildable()->hasAutomaticCapability(
+      $capability,
+      $viewer);
+  }
+
+  public function describeAutomaticCapability($capability) {
+    return pht(
+      'Users must be able to see a buildable to view its build plans.');
+  }
+
+}
Index: src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildArtifact extends HarbormasterDAO
+  implements PhabricatorPolicyInterface {
+
+  protected $buildablePHID;
+  protected $artifactType;
+  protected $artifactIndex;
+  protected $artifactKey;
+  protected $artifactData = array();
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_SERIALIZATION => array(
+        'artifactData' => self::SERIALIZATION_JSON,
+      ),
+    ) + parent::getConfiguration();
+  }
+
+  public function attachBuildable(HarbormasterBuildable $buildable) {
+    $this->buildable = $buildable;
+    return $this;
+  }
+
+  public function getBuildable() {
+    return $this->assertAttached($this->buildable);
+  }
+
+  public function setArtifactKey($key) {
+    $this->artifactIndex = PhabricatorHash::digestForIndex($key);
+    $this->artifactKey = $key;
+    return $this;
+  }
+
+
+/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
+
+
+  public function getCapabilities() {
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+    );
+  }
+
+  public function getPolicy($capability) {
+    return $this->getBuildable()->getPolicy($capability);
+  }
+
+  public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+    return $this->getBuildable()->hasAutomaticCapability(
+      $capability,
+      $viewer);
+  }
+
+  public function describeAutomaticCapability($capability) {
+    return pht(
+      'Users must be able to see a buildable to see its artifacts.');
+  }
+
+}
Index: src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
@@ -0,0 +1,18 @@
+<?php
+
+final class HarbormasterBuildItem extends HarbormasterDAO {
+
+  protected $name;
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+    ) + parent::getConfiguration();
+  }
+
+  public function generatePHID() {
+    return PhabricatorPHID::generateNewPHID(
+      HarbormasterPHIDTypeBuildItem::TYPECONST);
+  }
+
+}
Index: src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
@@ -0,0 +1,7 @@
+<?php
+
+final class HarbormasterBuildLog extends HarbormasterDAO {
+
+  protected $buildItemPHID;
+
+}
Index: src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
@@ -0,0 +1,16 @@
+<?php
+
+final class HarbormasterBuildTarget extends HarbormasterDAO {
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+    ) + parent::getConfiguration();
+  }
+
+  public function generatePHID() {
+    return PhabricatorPHID::generateNewPHID(
+      HarbormasterPHIDTypeBuildTarget::TYPECONST);
+  }
+
+}
Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
@@ -0,0 +1,71 @@
+<?php
+
+final class HarbormasterBuildPlan extends HarbormasterDAO
+  implements
+    PhabricatorPolicyInterface,
+    PhabricatorSubscribableInterface {
+
+  protected $name;
+  protected $planStatus;
+
+  private $buildSteps = self::ATTACHABLE;
+
+  public static function initializeNewBuildPlan(PhabricatorUser $actor) {
+    return id(new HarbormasterBuildPlan())
+      ->setPlanStatus('active'); // TODO: Figure this out.
+  }
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+    ) + parent::getConfiguration();
+  }
+
+  public function generatePHID() {
+    return PhabricatorPHID::generateNewPHID(
+      HarbormasterPHIDTypeBuildPlan::TYPECONST);
+  }
+
+  public function attachBuildSteps(array $steps) {
+    assert_instances_of($steps, 'HarbormasterBuildStep');
+    $this->buildSteps = $steps;
+    return $this;
+  }
+
+  public function getBuildSteps() {
+    return $this->assertAttached($this->buildSteps);
+  }
+
+
+/* -(  PhabricatorSubscribableInterface  )----------------------------------- */
+
+
+  public function isAutomaticallySubscribed($phid) {
+    return false;
+  }
+
+
+/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
+
+
+  public function getCapabilities() {
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+    );
+  }
+
+  public function getPolicy($capability) {
+    switch ($capability) {
+      case PhabricatorPolicyCapability::CAN_VIEW:
+        return PhabricatorPolicies::getMostOpenPolicy();
+    }
+  }
+
+  public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+    return false;
+  }
+
+  public function describeAutomaticCapability($capability) {
+    return null;
+  }
+}
Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php
@@ -0,0 +1,74 @@
+<?php
+
+final class HarbormasterBuildPlanTransaction
+  extends PhabricatorApplicationTransaction {
+
+  const TYPE_NAME = 'harbormaster:name';
+
+  public function getApplicationName() {
+    return 'harbormaster';
+  }
+
+  public function getApplicationTransactionType() {
+    return HarbormasterPHIDTypeBuildPlan::TYPECONST;
+  }
+
+  public function getApplicationTransactionCommentObject() {
+    return new HarbormasterBuildPlanTransactionComment();
+  }
+
+  public function getIcon() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_NAME:
+        if ($old === null) {
+          return 'create';
+        }
+        break;
+    }
+
+    return parent::getIcon();
+  }
+
+  public function getColor() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_NAME:
+        if ($old === null) {
+          return 'green';
+        }
+        break;
+    }
+
+    return parent::getIcon();
+  }
+
+  public function getTitle() {
+    $old = $this->getOldValue();
+    $new = $this->getNewValue();
+    $author_handle = $this->renderHandleLink($this->getAuthorPHID());
+
+    switch ($this->getTransactionType()) {
+      case self::TYPE_NAME:
+        if ($old === null) {
+          return pht(
+            '%s created this build plan.',
+            $author_handle);
+        } else {
+          return pht(
+            '%s renamed this build plan from "%s" to "%s".',
+            $author_handle,
+            $old,
+            $new);
+        }
+        break;
+    }
+
+    return parent::getTitle();
+  }
+
+}
Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php
@@ -0,0 +1,10 @@
+<?php
+
+final class HarbormasterBuildPlanTransactionComment
+  extends PhabricatorApplicationTransactionComment {
+
+  public function getApplicationTransactionObject() {
+    return new HarbormasterBuildPlan();
+  }
+
+}
Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
===================================================================
--- /dev/null
+++ src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
@@ -0,0 +1,29 @@
+<?php
+
+final class HarbormasterBuildStep extends HarbormasterDAO {
+
+  protected $buildPlanPHID;
+
+  private $buildPlan = self::ATTACHABLE;
+
+  public function getConfiguration() {
+    return array(
+      self::CONFIG_AUX_PHID => true,
+    ) + parent::getConfiguration();
+  }
+
+  public function generatePHID() {
+    return PhabricatorPHID::generateNewPHID(
+      HarbormasterPHIDTypeBuildStep::TYPECONST);
+  }
+
+  public function attachBuildPlan(HarbormasterBuildPlan $plan) {
+    $this->buildPlan = $plan;
+    return $this;
+  }
+
+  public function getBuildPlan() {
+    return $this->assertAttached($this->buildPlan);
+  }
+
+}
Index: src/applications/policy/controller/PhabricatorPolicyExplainController.php
===================================================================
--- src/applications/policy/controller/PhabricatorPolicyExplainController.php
+++ src/applications/policy/controller/PhabricatorPolicyExplainController.php
@@ -43,7 +43,7 @@
       ->setViewer($viewer)
       ->withPHIDs(array($phid))
       ->executeOne();
-    $object_uri = $handle->getURI();
+    $object_uri = nonempty($handle->getURI(), '/');
 
     $explanation = PhabricatorPolicy::getPolicyExplanation(
       $viewer,
Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
===================================================================
--- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1684,6 +1684,10 @@
         'type' => 'php',
         'name' => $this->getPatchPath('20130926.dinline.php'),
       ),
+      '20131020.harbormaster.sql' => array(
+        'type' => 'sql',
+        'name' => $this->getPatchPath('20131020.harbormaster.sql'),
+      ),
     );
   }
 }