diff --git a/resources/sql/autopatches/20151108.phame.blog.joinpolicy.sql b/resources/sql/autopatches/20151108.phame.blog.joinpolicy.sql
new file mode 100644
index 000000000..54aea48bb
--- /dev/null
+++ b/resources/sql/autopatches/20151108.phame.blog.joinpolicy.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_phame.phame_blog
+  DROP joinPolicy;
diff --git a/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php b/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php
index 425bf1735..1f569d3a3 100644
--- a/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php
+++ b/src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php
@@ -1,103 +1,104 @@
 <?php
 
 final class PhameCreatePostConduitAPIMethod extends PhameConduitAPIMethod {
 
   public function getAPIMethodName() {
     return 'phame.createpost';
   }
 
   public function getMethodDescription() {
     return pht('Create a phame post.');
   }
 
   public function getMethodStatus() {
     return self::METHOD_STATUS_UNSTABLE;
   }
 
   protected function defineParamTypes() {
     return array(
       'blogPHID'      => 'required phid',
       'title'         => 'required string',
       'body'          => 'required string',
       'phameTitle'    => 'optional string',
       'bloggerPHID'   => 'optional phid',
       'isDraft'       => 'optional bool',
     );
   }
 
   protected function defineReturnType() {
     return 'list<dict>';
   }
 
   protected function defineErrorTypes() {
     return array(
       'ERR-INVALID-PARAMETER' =>
         pht('Missing or malformed parameter.'),
       'ERR-INVALID-BLOG'      =>
         pht('Invalid blog PHID or user can not post to blog.'),
     );
   }
 
   protected function execute(ConduitAPIRequest $request) {
     $user = $request->getUser();
     $blog_phid = $request->getValue('blogPHID');
     $title = $request->getValue('title');
     $body = $request->getValue('body');
     $exception_description = array();
     if (!$blog_phid) {
       $exception_description[] = pht('No blog phid.');
     }
     if (!strlen($title)) {
       $exception_description[] = pht('No post title.');
     }
     if (!strlen($body)) {
       $exception_description[] = pht('No post body.');
     }
     if ($exception_description) {
       throw id(new ConduitException('ERR-INVALID-PARAMETER'))
         ->setErrorDescription(implode("\n", $exception_description));
     }
 
     $blogger_phid = $request->getValue('bloggerPHID');
     if ($blogger_phid) {
       $blogger = id(new PhabricatorPeopleQuery())
         ->setViewer($user)
         ->withPHIDs(array($blogger_phid))
         ->executeOne();
     } else {
       $blogger = $user;
     }
 
     $blog = id(new PhameBlogQuery())
       ->setViewer($blogger)
       ->withPHIDs(array($blog_phid))
       ->requireCapabilities(
         array(
-          PhabricatorPolicyCapability::CAN_JOIN,
+          PhabricatorPolicyCapability::CAN_VIEW,
+          PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
 
     if (!$blog) {
       throw new ConduitException('ERR-INVALID-BLOG');
     }
 
     $post = PhamePost::initializePost($blogger, $blog);
     $is_draft = $request->getValue('isDraft', false);
     if (!$is_draft) {
       $post->setDatePublished(time());
       $post->setVisibility(PhameConstants::VISIBILITY_PUBLISHED);
     }
     $post->setTitle($title);
     $phame_title = $request->getValue(
       'phameTitle',
       id(new PhutilUTF8StringTruncator())
       ->setMaximumBytes(64)
       ->truncateString($title));
     $post->setPhameTitle(PhabricatorSlug::normalize($phame_title));
     $post->setBody($body);
     $post->save();
 
     return $post->toDictionary();
   }
 
 }
diff --git a/src/applications/phame/controller/blog/PhameBlogEditController.php b/src/applications/phame/controller/blog/PhameBlogEditController.php
index b59e5e01a..0c2bb324d 100644
--- a/src/applications/phame/controller/blog/PhameBlogEditController.php
+++ b/src/applications/phame/controller/blog/PhameBlogEditController.php
@@ -1,223 +1,210 @@
 <?php
 
 final class PhameBlogEditController
   extends PhameBlogController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     if ($id) {
       $blog = id(new PhameBlogQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$blog) {
         return new Aphront404Response();
       }
 
       $submit_button = pht('Save Changes');
       $page_title = pht('Edit Blog');
       $cancel_uri = $this->getApplicationURI('blog/view/'.$blog->getID().'/');
 
       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $blog->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $v_projects = array_reverse($v_projects);
       $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID(
         $blog->getPHID());
 
     } else {
       $this->requireApplicationCapability(
         PhameBlogCreateCapability::CAPABILITY);
 
       $blog = PhameBlog::initializeNewBlog($viewer);
 
       $submit_button = pht('Create Blog');
       $page_title = pht('Create Blog');
       $cancel_uri = $this->getApplicationURI();
       $v_projects = array();
       $v_cc = array();
     }
     $name = $blog->getName();
     $description = $blog->getDescription();
     $custom_domain = $blog->getDomain();
     $skin = $blog->getSkin();
     $can_view = $blog->getViewPolicy();
     $can_edit = $blog->getEditPolicy();
-    $can_join = $blog->getJoinPolicy();
 
     $e_name               = true;
     $e_custom_domain      = null;
     $e_view_policy        = null;
     $validation_exception = null;
     if ($request->isFormPost()) {
       $name = $request->getStr('name');
       $description = $request->getStr('description');
       $custom_domain = nonempty($request->getStr('custom_domain'), null);
       $skin = $request->getStr('skin');
       $can_view = $request->getStr('can_view');
       $can_edit = $request->getStr('can_edit');
-      $can_join = $request->getStr('can_join');
       $v_projects = $request->getArr('projects');
       $v_cc = $request->getArr('cc');
 
       $xactions = array(
         id(new PhameBlogTransaction())
           ->setTransactionType(PhameBlogTransaction::TYPE_NAME)
           ->setNewValue($name),
         id(new PhameBlogTransaction())
           ->setTransactionType(PhameBlogTransaction::TYPE_DESCRIPTION)
           ->setNewValue($description),
         id(new PhameBlogTransaction())
           ->setTransactionType(PhameBlogTransaction::TYPE_DOMAIN)
           ->setNewValue($custom_domain),
         id(new PhameBlogTransaction())
           ->setTransactionType(PhameBlogTransaction::TYPE_SKIN)
           ->setNewValue($skin),
         id(new PhameBlogTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
           ->setNewValue($can_view),
         id(new PhameBlogTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
           ->setNewValue($can_edit),
-        id(new PhameBlogTransaction())
-          ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY)
-          ->setNewValue($can_join),
         id(new PhameBlogTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
           ->setNewValue(array('=' => $v_cc)),
       );
 
       $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
       $xactions[] = id(new PhameBlogTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $proj_edge_type)
         ->setNewValue(array('=' => array_fuse($v_projects)));
 
       $editor = id(new PhameBlogEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
         $editor->applyTransactions($blog, $xactions);
         return id(new AphrontRedirectResponse())
           ->setURI($this->getApplicationURI('blog/view/'.$blog->getID().'/'));
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
 
         $e_name = $validation_exception->getShortMessage(
           PhameBlogTransaction::TYPE_NAME);
         $e_custom_domain = $validation_exception->getShortMessage(
           PhameBlogTransaction::TYPE_DOMAIN);
         $e_view_policy = $validation_exception->getShortMessage(
           PhabricatorTransactions::TYPE_VIEW_POLICY);
       }
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($viewer)
       ->setObject($blog)
       ->execute();
 
     $skins = PhameSkinSpecification::loadAllSkinSpecifications();
     $skins = mpull($skins, 'getName');
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Name'))
         ->setName('name')
         ->setValue($name)
         ->setID('blog-name')
         ->setError($e_name))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setUser($viewer)
           ->setLabel(pht('Description'))
           ->setName('description')
           ->setValue($description)
           ->setID('blog-description')
           ->setUser($viewer)
           ->setDisableMacros(true))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Subscribers'))
           ->setName('cc')
           ->setValue($v_cc)
           ->setUser($viewer)
           ->setDatasource(new PhabricatorMetaMTAMailableDatasource()))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicyObject($blog)
           ->setPolicies($policies)
           ->setError($e_view_policy)
           ->setValue($can_view)
           ->setName('can_view'))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($viewer)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicyObject($blog)
           ->setPolicies($policies)
           ->setValue($can_edit)
           ->setName('can_edit'))
-      ->appendChild(
-        id(new AphrontFormPolicyControl())
-          ->setUser($viewer)
-          ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)
-          ->setPolicyObject($blog)
-          ->setPolicies($policies)
-          ->setValue($can_join)
-          ->setName('can_join'))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Projects'))
           ->setName('projects')
           ->setValue($v_projects)
           ->setDatasource(new PhabricatorProjectDatasource()))
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Custom Domain'))
         ->setName('custom_domain')
         ->setValue($custom_domain)
         ->setCaption(
           pht('Must include at least one dot (.), e.g. %s', 'blog.example.com'))
         ->setError($e_custom_domain))
       ->appendChild(
         id(new AphrontFormSelectControl())
         ->setLabel(pht('Skin'))
         ->setName('skin')
         ->setValue($skin)
         ->setOptions($skins))
       ->appendChild(
         id(new AphrontFormSubmitControl())
         ->addCancelButton($cancel_uri)
         ->setValue($submit_button));
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText($page_title)
       ->setValidationException($validation_exception)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Blogs'), $this->getApplicationURI('blog/'));
     $crumbs->addTextCrumb($page_title, $this->getApplicationURI('blog/new'));
 
     return $this->newPage()
       ->setTitle($page_title)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $form_box,
       ));
   }
 }
diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php
index 03dfc8c1c..249bd5506 100644
--- a/src/applications/phame/controller/blog/PhameBlogViewController.php
+++ b/src/applications/phame/controller/blog/PhameBlogViewController.php
@@ -1,178 +1,169 @@
 <?php
 
 final class PhameBlogViewController extends PhameBlogController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $blog = id(new PhameBlogQuery())
       ->setViewer($viewer)
       ->withIDs(array($id))
       ->executeOne();
     if (!$blog) {
       return new Aphront404Response();
     }
 
     $pager = id(new AphrontCursorPagerView())
       ->readFromRequest($request);
 
     $posts = id(new PhamePostQuery())
       ->setViewer($viewer)
       ->withBlogPHIDs(array($blog->getPHID()))
       ->executeWithCursorPager($pager);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($blog->getName())
       ->setUser($viewer)
       ->setPolicyObject($blog);
 
     $actions = $this->renderActions($blog, $viewer);
     $properties = $this->renderProperties($blog, $viewer, $actions);
     $post_list = $this->renderPostList(
       $posts,
       $viewer,
       pht('This blog has no visible posts.'));
 
     $post_list = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Latest Posts'))
       ->appendChild($post_list);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Blogs'), $this->getApplicationURI('blog/'));
     $crumbs->addTextCrumb($blog->getName(), $this->getApplicationURI());
 
     $object_box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
 
     return $this->newPage()
       ->setTitle($blog->getName())
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $object_box,
           $post_list,
       ));
   }
 
   private function renderProperties(
     PhameBlog $blog,
     PhabricatorUser $viewer,
     PhabricatorActionListView $actions) {
 
     require_celerity_resource('aphront-tooltip-css');
     Javelin::initBehavior('phabricator-tooltips');
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($blog)
       ->setActionList($actions);
 
     $properties->addProperty(
       pht('Skin'),
       $blog->getSkin());
 
     $properties->addProperty(
       pht('Domain'),
       $blog->getDomain());
 
     $feed_uri = PhabricatorEnv::getProductionURI(
       $this->getApplicationURI('blog/feed/'.$blog->getID().'/'));
     $properties->addProperty(
       pht('Atom URI'),
       javelin_tag('a',
         array(
           'href' => $feed_uri,
           'sigil' => 'has-tooltip',
           'meta' => array(
             'tip' => pht('Atom URI does not support custom domains.'),
             'size' => 320,
           ),
         ),
         $feed_uri));
 
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
       $viewer,
       $blog);
 
     $properties->addProperty(
       pht('Editable By'),
       $descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
 
-    $properties->addProperty(
-      pht('Joinable By'),
-      $descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
-
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($viewer)
       ->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)
       ->process();
 
     $properties->invokeWillRenderEvent();
 
     if (strlen($blog->getDescription())) {
       $description = PhabricatorMarkupEngine::renderOneObject(
         id(new PhabricatorMarkupOneOff())->setContent($blog->getDescription()),
         'default',
         $viewer);
       $properties->addSectionHeader(
         pht('Description'),
         PHUIPropertyListView::ICON_SUMMARY);
       $properties->addTextContent($description);
     }
 
     return $properties;
   }
 
   private function renderActions(PhameBlog $blog, PhabricatorUser $viewer) {
     $actions = id(new PhabricatorActionListView())
       ->setObject($blog)
       ->setObjectURI($this->getRequest()->getRequestURI())
       ->setUser($viewer);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $blog,
       PhabricatorPolicyCapability::CAN_EDIT);
 
-    $can_join = PhabricatorPolicyFilter::hasCapability(
-      $viewer,
-      $blog,
-      PhabricatorPolicyCapability::CAN_JOIN);
-
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-plus')
         ->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID()))
         ->setName(pht('Write Post'))
-        ->setDisabled(!$can_join)
-        ->setWorkflow(!$can_join));
+        ->setDisabled(!$can_edit)
+        ->setWorkflow(!$can_edit));
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setUser($viewer)
         ->setIcon('fa-globe')
         ->setHref($blog->getLiveURI())
         ->setName(pht('View Live')));
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/'))
         ->setName(pht('Edit Blog'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-times')
         ->setHref($this->getApplicationURI('blog/delete/'.$blog->getID().'/'))
         ->setName(pht('Delete Blog'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
     return $actions;
   }
 
 }
diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php
index 99e6df390..55ce9e176 100644
--- a/src/applications/phame/controller/post/PhamePostEditController.php
+++ b/src/applications/phame/controller/post/PhamePostEditController.php
@@ -1,232 +1,232 @@
 <?php
 
 final class PhamePostEditController extends PhamePostController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     if ($id) {
       $post = id(new PhamePostQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$post) {
         return new Aphront404Response();
       }
 
       $cancel_uri = $this->getApplicationURI('/post/view/'.$id.'/');
       $submit_button = pht('Save Changes');
       $page_title = pht('Edit Post');
 
       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $post->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $v_projects = array_reverse($v_projects);
       $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID(
           $post->getPHID());
     } else {
       $blog = id(new PhameBlogQuery())
         ->setViewer($viewer)
         ->withIDs(array($request->getInt('blog')))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
-            PhabricatorPolicyCapability::CAN_JOIN,
+            PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$blog) {
         return new Aphront404Response();
       }
       $v_projects = array();
       $v_cc = array();
 
       $post = PhamePost::initializePost($viewer, $blog);
       $cancel_uri = $this->getApplicationURI('/blog/view/'.$blog->getID().'/');
 
       $submit_button = pht('Create Post');
       $page_title = pht('Create Post');
     }
 
     $title = $post->getTitle();
     $phame_title = $post->getPhameTitle();
     $body = $post->getBody();
     $comments_widget = $post->getCommentsWidget();
     $visibility = $post->getVisibility();
 
     $e_title       = true;
     $e_phame_title = true;
     $validation_exception = null;
     if ($request->isFormPost()) {
       $title = $request->getStr('title');
       $phame_title = $request->getStr('phame_title');
       $phame_title = PhabricatorSlug::normalize($phame_title);
       $body = $request->getStr('body');
       $comments_widget = $request->getStr('comments_widget');
       $v_projects = $request->getArr('projects');
       $v_cc = $request->getArr('cc');
       $visibility = $request->getInt('visibility');
 
       $xactions = array(
         id(new PhamePostTransaction())
           ->setTransactionType(PhamePostTransaction::TYPE_TITLE)
           ->setNewValue($title),
         id(new PhamePostTransaction())
           ->setTransactionType(PhamePostTransaction::TYPE_PHAME_TITLE)
           ->setNewValue($phame_title),
         id(new PhamePostTransaction())
           ->setTransactionType(PhamePostTransaction::TYPE_BODY)
           ->setNewValue($body),
         id(new PhamePostTransaction())
           ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY)
           ->setNewValue($visibility),
         id(new PhamePostTransaction())
           ->setTransactionType(PhamePostTransaction::TYPE_COMMENTS_WIDGET)
           ->setNewValue($comments_widget),
         id(new PhamePostTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
           ->setNewValue(array('=' => $v_cc)),
 
       );
 
       $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
       $xactions[] = id(new PhamePostTransaction())
         ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
         ->setMetadataValue('edge:type', $proj_edge_type)
         ->setNewValue(array('=' => array_fuse($v_projects)));
 
       $editor = id(new PhamePostEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
         $editor->applyTransactions($post, $xactions);
 
         $uri = $this->getApplicationURI('/post/view/'.$post->getID().'/');
         return id(new AphrontRedirectResponse())->setURI($uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
         $e_title = $validation_exception->getShortMessage(
           PhamePostTransaction::TYPE_TITLE);
         $e_phame_title = $validation_exception->getShortMessage(
           PhamePostTransaction::TYPE_PHAME_TITLE);
       }
     }
 
     $handle = id(new PhabricatorHandleQuery())
       ->setViewer($viewer)
       ->withPHIDs(array($post->getBlogPHID()))
       ->executeOne();
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->addHiddenInput('blog', $request->getInt('blog'))
       ->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel(pht('Blog'))
           ->setValue($handle->renderLink()))
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Title'))
         ->setName('title')
         ->setValue($title)
         ->setID('post-title')
         ->setError($e_title))
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setLabel(pht('Phame Title'))
         ->setName('phame_title')
         ->setValue(rtrim($phame_title, '/'))
         ->setID('post-phame-title')
         ->setCaption(pht('Up to 64 alphanumeric characters '.
                      'with underscores for spaces. '.
                      'Formatting is enforced.'))
         ->setError($e_phame_title))
       ->appendChild(
         id(new AphrontFormSelectControl())
         ->setLabel(pht('Visibility'))
         ->setName('visibility')
         ->setvalue($visibility)
         ->setOptions(PhameConstants::getPhamePostStatusMap()))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
         ->setLabel(pht('Body'))
         ->setName('body')
         ->setValue($body)
         ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
         ->setID('post-body')
         ->setUser($viewer)
         ->setDisableMacros(true))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Subscribers'))
           ->setName('cc')
           ->setValue($v_cc)
           ->setUser($viewer)
           ->setDatasource(new PhabricatorMetaMTAMailableDatasource()))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Projects'))
           ->setName('projects')
           ->setValue($v_projects)
           ->setDatasource(new PhabricatorProjectDatasource()))
       ->appendChild(
         id(new AphrontFormSelectControl())
         ->setLabel(pht('Comments Widget'))
         ->setName('comments_widget')
         ->setvalue($comments_widget)
         ->setOptions($post->getCommentsWidgetOptionsForSelect()))
       ->appendChild(
         id(new AphrontFormSubmitControl())
         ->addCancelButton($cancel_uri)
         ->setValue($submit_button));
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('%s (Post Preview)', $title));
 
     $container = id(new PHUIBoxView())
       ->setID('post-preview');
 
     $document = id(new PHUIDocumentViewPro())
       ->setHeader($header)
       ->appendChild($container);
 
     $preview_panel = id(new PHUIObjectBoxView())
       ->appendChild($document);
 
     Javelin::initBehavior(
       'phame-post-preview',
       array(
         'preview'     => 'post-preview',
         'body'        => 'post-body',
         'title'       => 'post-title',
         'phame_title' => 'post-phame-title',
         'uri'         => '/phame/post/preview/',
       ));
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText($page_title)
       ->setValidationException($validation_exception)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       $page_title,
       $this->getApplicationURI('/post/view/'.$id.'/'));
 
     return $this->newPage()
       ->setTitle($page_title)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $form_box,
           $preview_panel,
       ));
   }
 
 }
diff --git a/src/applications/phame/controller/post/PhamePostNewController.php b/src/applications/phame/controller/post/PhamePostNewController.php
index 709840174..8dd2e0d3c 100644
--- a/src/applications/phame/controller/post/PhamePostNewController.php
+++ b/src/applications/phame/controller/post/PhamePostNewController.php
@@ -1,120 +1,120 @@
 <?php
 
 final class PhamePostNewController extends PhamePostController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
 
     $post = null;
     $view_uri = null;
     if ($id) {
       $post = id(new PhamePostQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$post) {
         return new Aphront404Response();
       }
 
       $view_uri = '/post/view/'.$post->getID().'/';
       $view_uri = $this->getApplicationURI($view_uri);
 
       if ($request->isFormPost()) {
         $blog = id(new PhameBlogQuery())
           ->setViewer($viewer)
           ->withIDs(array($request->getInt('blog')))
           ->requireCapabilities(
             array(
-              PhabricatorPolicyCapability::CAN_JOIN,
+              PhabricatorPolicyCapability::CAN_EDIT,
             ))
           ->executeOne();
 
         if ($blog) {
           $post->setBlogPHID($blog->getPHID());
           $post->save();
 
           return id(new AphrontRedirectResponse())->setURI($view_uri);
         }
       }
 
       $title = pht('Move Post');
     } else {
       $title = pht('Create Post');
       $view_uri = $this->getApplicationURI('/post/new');
     }
 
     $blogs = id(new PhameBlogQuery())
       ->setViewer($viewer)
       ->requireCapabilities(
         array(
-          PhabricatorPolicyCapability::CAN_JOIN,
+          PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->execute();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($title, $view_uri);
 
     $notification = null;
     $form_box = null;
     if (!$blogs) {
       $notification = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
         ->appendChild(
-          pht('You do not have permission to join any blogs. Create a blog '.
+          pht('You do not have permission to post to any blogs. Create a blog '.
               'first, then you can post to it.'));
 
     } else {
       $options = mpull($blogs, 'getName', 'getID');
       asort($options);
 
       $selected_value = null;
       if ($post && $post->getBlog()) {
         $selected_value = $post->getBlog()->getID();
       }
 
       $form = id(new AphrontFormView())
         ->setUser($viewer)
         ->appendChild(
           id(new AphrontFormSelectControl())
             ->setLabel(pht('Blog'))
             ->setName('blog')
             ->setOptions($options)
             ->setValue($selected_value));
 
       if ($post) {
         $form
           ->appendChild(
             id(new AphrontFormSubmitControl())
               ->setValue(pht('Move Post'))
               ->addCancelButton($view_uri));
       } else {
         $form
           ->setAction($this->getApplicationURI('post/edit/'))
           ->setMethod('GET')
           ->appendChild(
             id(new AphrontFormSubmitControl())
               ->setValue(pht('Continue')));
       }
 
       $form_box = id(new PHUIObjectBoxView())
         ->setHeaderText($title)
         ->setForm($form);
     }
 
     return $this->newPage()
       ->setTitle($title)
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $notification,
           $form_box,
       ));
 
     }
 
 }
diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php
index 3afdf5401..ca48af103 100644
--- a/src/applications/phame/controller/post/PhamePostViewController.php
+++ b/src/applications/phame/controller/post/PhamePostViewController.php
@@ -1,193 +1,195 @@
 <?php
 
 final class PhamePostViewController extends PhamePostController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $post = id(new PhamePostQuery())
       ->setViewer($viewer)
       ->withIDs(array($request->getURIData('id')))
       ->executeOne();
 
     if (!$post) {
       return new Aphront404Response();
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(
       $post->getTitle(),
       $this->getApplicationURI('post/view/'.$post->getID().'/'));
     $crumbs->setBorder(true);
 
     $actions = $this->renderActions($post, $viewer);
     $properties = $this->renderProperties($post, $viewer);
 
     $action_button = id(new PHUIButtonView())
       ->setTag('a')
       ->setText(pht('Actions'))
       ->setHref('#')
       ->setIconFont('fa-bars')
       ->addClass('phui-mobile-menu')
       ->setDropdownMenu($actions);
 
     $header = id(new PHUIHeaderView())
       ->setHeader($post->getTitle())
       ->setUser($viewer)
       ->setPolicyObject($post)
       ->addActionLink($action_button);
 
     $document = id(new PHUIDocumentViewPro())
       ->setHeader($header)
       ->setPropertyList($properties);
 
     if ($post->isDraft()) {
       $document->appendChild(
         id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
           ->setTitle(pht('Draft Post'))
           ->appendChild(
             pht(
               'Only you can see this draft until you publish it. '.
               'Use "Preview / Publish" to publish this post.')));
     }
 
     if (!$post->getBlog()) {
       $document->appendChild(
         id(new PHUIInfoView())
           ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
           ->setTitle(pht('Not On A Blog'))
           ->appendChild(
             pht(
               'This post is not associated with a blog (the blog may have '.
               'been deleted). Use "Move Post" to move it to a new blog.')));
     }
 
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($viewer)
       ->addObject($post, PhamePost::MARKUP_FIELD_BODY)
       ->process();
 
     $document->appendChild(
       phutil_tag(
          'div',
         array(
           'class' => 'phabricator-remarkup',
         ),
         $engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY)));
 
     return $this->newPage()
       ->setTitle($post->getTitle())
       ->addClass('pro-white-background')
       ->setCrumbs($crumbs)
       ->appendChild(
         array(
           $document,
       ));
   }
 
   private function renderActions(
     PhamePost $post,
     PhabricatorUser $viewer) {
 
       $actions = id(new PhabricatorActionListView())
         ->setObject($post)
         ->setObjectURI($this->getRequest()->getRequestURI())
         ->setUser($viewer);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $post,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $id = $post->getID();
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-pencil')
         ->setHref($this->getApplicationURI('post/edit/'.$id.'/'))
         ->setName(pht('Edit Post'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-arrows')
         ->setHref($this->getApplicationURI('post/move/'.$id.'/'))
         ->setName(pht('Move Post'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(!$can_edit));
 
     if ($post->isDraft()) {
       $actions->addAction(
         id(new PhabricatorActionView())
           ->setIcon('fa-eye')
           ->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
+          ->setDisabled(!$can_edit)
           ->setName(pht('Preview / Publish')));
     } else {
       $actions->addAction(
         id(new PhabricatorActionView())
           ->setIcon('fa-eye-slash')
           ->setHref($this->getApplicationURI('post/unpublish/'.$id.'/'))
           ->setName(pht('Unpublish'))
+          ->setDisabled(!$can_edit)
           ->setWorkflow(true));
     }
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setIcon('fa-times')
         ->setHref($this->getApplicationURI('post/delete/'.$id.'/'))
         ->setName(pht('Delete Post'))
         ->setDisabled(!$can_edit)
         ->setWorkflow(true));
 
     $blog = $post->getBlog();
     $can_view_live = $blog && !$post->isDraft();
 
     if ($can_view_live) {
       $live_uri = $blog->getLiveURI($post);
     } else {
       $live_uri = 'post/notlive/'.$post->getID().'/';
       $live_uri = $this->getApplicationURI($live_uri);
     }
 
     $actions->addAction(
       id(new PhabricatorActionView())
         ->setUser($viewer)
         ->setIcon('fa-globe')
         ->setHref($live_uri)
         ->setName(pht('View Live'))
         ->setDisabled(!$can_view_live)
         ->setWorkflow(!$can_view_live));
 
     return $actions;
   }
 
   private function renderProperties(
     PhamePost $post,
     PhabricatorUser $viewer) {
 
     $properties = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setObject($post);
 
     $properties->addProperty(
       pht('Blog'),
       $viewer->renderHandle($post->getBlogPHID()));
 
     $properties->addProperty(
       pht('Blogger'),
       $viewer->renderHandle($post->getBloggerPHID()));
 
     $properties->addProperty(
       pht('Published'),
       $post->isDraft()
         ? pht('Draft')
         : phabricator_datetime($post->getDatePublished(), $viewer));
 
     $properties->invokeWillRenderEvent();
 
     return $properties;
   }
 
 }
diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php
index 7c793aff6..712c4ddd5 100644
--- a/src/applications/phame/editor/PhameBlogEditor.php
+++ b/src/applications/phame/editor/PhameBlogEditor.php
@@ -1,229 +1,228 @@
 <?php
 
 final class PhameBlogEditor
   extends PhabricatorApplicationTransactionEditor {
 
   public function getEditorApplicationClass() {
     return 'PhabricatorPhameApplication';
   }
 
   public function getEditorObjectsDescription() {
     return pht('Phame Blogs');
   }
 
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
     $types[] = PhameBlogTransaction::TYPE_NAME;
     $types[] = PhameBlogTransaction::TYPE_DESCRIPTION;
     $types[] = PhameBlogTransaction::TYPE_DOMAIN;
     $types[] = PhameBlogTransaction::TYPE_SKIN;
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
     $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
-    $types[] = PhabricatorTransactions::TYPE_JOIN_POLICY;
 
     return $types;
   }
 
   protected function getCustomTransactionOldValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhameBlogTransaction::TYPE_NAME:
         return $object->getName();
       case PhameBlogTransaction::TYPE_DESCRIPTION:
         return $object->getDescription();
       case PhameBlogTransaction::TYPE_DOMAIN:
         return $object->getDomain();
       case PhameBlogTransaction::TYPE_SKIN:
         return $object->getSkin();
     }
   }
 
   protected function getCustomTransactionNewValue(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhameBlogTransaction::TYPE_NAME:
       case PhameBlogTransaction::TYPE_DESCRIPTION:
       case PhameBlogTransaction::TYPE_DOMAIN:
       case PhameBlogTransaction::TYPE_SKIN:
         return $xaction->getNewValue();
     }
   }
 
   protected function applyCustomInternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhameBlogTransaction::TYPE_NAME:
         return $object->setName($xaction->getNewValue());
       case PhameBlogTransaction::TYPE_DESCRIPTION:
         return $object->setDescription($xaction->getNewValue());
       case PhameBlogTransaction::TYPE_DOMAIN:
         return $object->setDomain($xaction->getNewValue());
       case PhameBlogTransaction::TYPE_SKIN:
         return $object->setSkin($xaction->getNewValue());
     }
 
     return parent::applyCustomInternalTransaction($object, $xaction);
   }
 
   protected function applyCustomExternalTransaction(
     PhabricatorLiskDAO $object,
     PhabricatorApplicationTransaction $xaction) {
 
     switch ($xaction->getTransactionType()) {
       case PhameBlogTransaction::TYPE_NAME:
       case PhameBlogTransaction::TYPE_DESCRIPTION:
       case PhameBlogTransaction::TYPE_DOMAIN:
       case PhameBlogTransaction::TYPE_SKIN:
         return;
     }
 
     return parent::applyCustomExternalTransaction($object, $xaction);
   }
 
   protected function validateTransaction(
     PhabricatorLiskDAO $object,
     $type,
     array $xactions) {
 
     $errors = parent::validateTransaction($object, $type, $xactions);
 
 
     switch ($type) {
       case PhameBlogTransaction::TYPE_NAME:
         $missing = $this->validateIsEmptyTextField(
           $object->getName(),
           $xactions);
 
         if ($missing) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             pht('Required'),
             pht('Name is required.'),
             nonempty(last($xactions), null));
 
           $error->setIsMissingFieldError(true);
           $errors[] = $error;
         }
         break;
       case PhameBlogTransaction::TYPE_DOMAIN:
         if (!$xactions) {
           continue;
         }
         $custom_domain = last($xactions)->getNewValue();
         if (empty($custom_domain)) {
           continue;
         }
         list($error_label, $error_text) =
           $object->validateCustomDomain($custom_domain);
         if ($error_label) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             $error_label,
             $error_text,
             nonempty(last($xactions), null));
           $errors[] = $error;
         }
         if ($object->getViewPolicy() != PhabricatorPolicies::POLICY_PUBLIC) {
           $error_text = pht(
             'For custom domains to work, the blog must have a view policy of '.
             'public.');
           $error = new PhabricatorApplicationTransactionValidationError(
             PhabricatorTransactions::TYPE_VIEW_POLICY,
             pht('Invalid Policy'),
             $error_text,
             nonempty(last($xactions), null));
           $errors[] = $error;
         }
         $duplicate_blog = id(new PhameBlogQuery())
           ->setViewer(PhabricatorUser::getOmnipotentUser())
           ->withDomain($custom_domain)
           ->executeOne();
         if ($duplicate_blog && $duplicate_blog->getID() != $object->getID()) {
           $error = new PhabricatorApplicationTransactionValidationError(
             $type,
             pht('Not Unique'),
             pht('Domain must be unique; another blog already has this domain.'),
             nonempty(last($xactions), null));
           $errors[] = $error;
         }
 
         break;
     }
     return $errors;
   }
 
   protected function shouldSendMail(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
     return true;
   }
 
    protected function getMailTo(PhabricatorLiskDAO $object) {
     $phids = array();
     $phids[] = $this->requireActor()->getPHID();
     $phids[] = $object->getCreatorPHID();
 
     return $phids;
   }
 
   protected function buildMailTemplate(PhabricatorLiskDAO $object) {
     $phid = $object->getPHID();
     $name = $object->getName();
 
     return id(new PhabricatorMetaMTAMail())
       ->setSubject($name)
       ->addHeader('Thread-Topic', $phid);
   }
 
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     return id(new PhameBlogReplyHandler())
       ->setMailReceiver($object);
   }
 
   protected function buildMailBody(
     PhabricatorLiskDAO $object,
     array $xactions) {
 
     $body = parent::buildMailBody($object, $xactions);
 
     $body->addLinkSection(
       pht('BLOG DETAIL'),
       PhabricatorEnv::getProductionURI($object->getViewURI()));
 
     return $body;
   }
 
   public function getMailTagsMap() {
     return array(
       PhameBlogTransaction::MAILTAG_DETAILS =>
         pht("A blog's details change."),
       PhameBlogTransaction::MAILTAG_SUBSCRIBERS =>
         pht("A blog's subscribers change."),
       PhameBlogTransaction::MAILTAG_OTHER =>
         pht('Other blog activity not listed above occurs.'),
     );
   }
 
   protected function getMailSubjectPrefix() {
     return '[Phame]';
   }
 
 
   protected function supportsSearch() {
     return false;
   }
 
 }
diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php
index fa9828e5f..3082592a9 100644
--- a/src/applications/phame/storage/PhameBlog.php
+++ b/src/applications/phame/storage/PhameBlog.php
@@ -1,364 +1,345 @@
 <?php
 
 final class PhameBlog extends PhameDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorMarkupInterface,
     PhabricatorSubscribableInterface,
     PhabricatorFlaggableInterface,
     PhabricatorProjectInterface,
     PhabricatorApplicationTransactionInterface {
 
   const MARKUP_FIELD_DESCRIPTION = 'markup:description';
 
   const SKIN_DEFAULT = 'oblivious';
 
   protected $name;
   protected $description;
   protected $domain;
   protected $configData;
   protected $creatorPHID;
   protected $viewPolicy;
   protected $editPolicy;
-  protected $joinPolicy;
   protected $mailKey;
 
   private static $requestBlog;
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID   => true,
       self::CONFIG_SERIALIZATION => array(
         'configData' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'name' => 'text64',
         'description' => 'text',
         'domain' => 'text128?',
         'mailKey' => 'bytes20',
 
         // T6203/NULLABILITY
         // These policies should always be non-null.
-        'joinPolicy' => 'policy?',
         'editPolicy' => 'policy?',
         'viewPolicy' => 'policy?',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
         'domain' => array(
           'columns' => array('domain'),
           'unique' => true,
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function save() {
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
     return parent::save();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPhameBlogPHIDType::TYPECONST);
   }
 
   public static function initializeNewBlog(PhabricatorUser $actor) {
     $blog = id(new PhameBlog())
       ->setCreatorPHID($actor->getPHID())
       ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy())
-      ->setEditPolicy(PhabricatorPolicies::POLICY_USER)
-      ->setJoinPolicy(PhabricatorPolicies::POLICY_USER);
+      ->setEditPolicy(PhabricatorPolicies::POLICY_USER);
     return $blog;
   }
 
   public function getSkinRenderer(AphrontRequest $request) {
     $spec = PhameSkinSpecification::loadOneSkinSpecification(
       $this->getSkin());
 
     if (!$spec) {
       $spec = PhameSkinSpecification::loadOneSkinSpecification(
         self::SKIN_DEFAULT);
     }
 
     if (!$spec) {
       throw new Exception(
         pht(
           'This blog has an invalid skin, and the default skin failed to '.
           'load.'));
     }
 
     $skin = newv($spec->getSkinClass(), array());
     $skin->setRequest($request);
     $skin->setSpecification($spec);
 
     return $skin;
   }
 
   /**
    * Makes sure a given custom blog uri is properly configured in DNS
    * to point at this Phabricator instance. If there is an error in
    * the configuration, return a string describing the error and how
    * to fix it. If there is no error, return an empty string.
    *
    * @return string
    */
   public function validateCustomDomain($custom_domain) {
     $example_domain = 'blog.example.com';
     $label = pht('Invalid');
 
     // note this "uri" should be pretty busted given the desired input
     // so just use it to test if there's a protocol specified
     $uri = new PhutilURI($custom_domain);
     if ($uri->getProtocol()) {
       return array(
         $label,
         pht(
           'The custom domain should not include a protocol. Just provide '.
           'the bare domain name (for example, "%s").',
           $example_domain),
       );
     }
 
     if ($uri->getPort()) {
       return array(
         $label,
         pht(
           'The custom domain should not include a port number. Just provide '.
           'the bare domain name (for example, "%s").',
           $example_domain),
       );
     }
 
     if (strpos($custom_domain, '/') !== false) {
       return array(
         $label,
         pht(
           'The custom domain should not specify a path (hosting a Phame '.
           'blog at a path is currently not supported). Instead, just provide '.
           'the bare domain name (for example, "%s").',
           $example_domain),
         );
     }
 
     if (strpos($custom_domain, '.') === false) {
       return array(
         $label,
         pht(
           'The custom domain should contain at least one dot (.) because '.
           'some browsers fail to set cookies on domains without a dot. '.
           'Instead, use a normal looking domain name like "%s".',
           $example_domain),
         );
     }
 
     if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) {
       $href = PhabricatorEnv::getProductionURI(
         '/config/edit/policy.allow-public/');
       return array(
         pht('Fix Configuration'),
         pht(
           'For custom domains to work, this Phabricator instance must be '.
           'configured to allow the public access policy. Configure this '.
           'setting %s, or ask an administrator to configure this setting. '.
           'The domain can be specified later once this setting has been '.
           'changed.',
           phutil_tag(
             'a',
             array('href' => $href),
             pht('here'))),
       );
     }
 
     return null;
   }
 
   public function getSkin() {
     $config = coalesce($this->getConfigData(), array());
     return idx($config, 'skin', self::SKIN_DEFAULT);
   }
 
   public function setSkin($skin) {
     $config = coalesce($this->getConfigData(), array());
     $config['skin'] = $skin;
     return $this->setConfigData($config);
   }
 
   public static function getSkinOptionsForSelect() {
     $classes = id(new PhutilSymbolLoader())
       ->setAncestorClass('PhameBlogSkin')
       ->setType('class')
       ->setConcreteOnly(true)
       ->selectSymbolsWithoutLoading();
 
     return ipull($classes, 'name', 'name');
   }
 
   public static function setRequestBlog(PhameBlog $blog) {
     self::$requestBlog = $blog;
   }
 
   public static function getRequestBlog() {
     return self::$requestBlog;
   }
 
   public function getLiveURI(PhamePost $post = null) {
     if ($this->getDomain()) {
       $base = new PhutilURI('http://'.$this->getDomain().'/');
     } else {
       $base = '/phame/live/'.$this->getID().'/';
       $base = PhabricatorEnv::getURI($base);
     }
 
     if ($post) {
       $base .= '/post/'.$post->getPhameTitle();
     }
 
     return $base;
   }
 
   public function getViewURI() {
     $uri = '/phame/blog/view/'.$this->getID().'/';
     return PhabricatorEnv::getProductionURI($uri);
   }
 
 
 /* -(  PhabricatorPolicyInterface Implementation  )-------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
-      PhabricatorPolicyCapability::CAN_JOIN,
     );
   }
 
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
-      case PhabricatorPolicyCapability::CAN_JOIN:
-        return $this->getJoinPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
     $can_edit = PhabricatorPolicyCapability::CAN_EDIT;
-    $can_join = PhabricatorPolicyCapability::CAN_JOIN;
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         // Users who can edit or post to a blog can always view it.
         if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_edit)) {
           return true;
         }
-        if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_join)) {
-          return true;
-        }
-        break;
-      case PhabricatorPolicyCapability::CAN_JOIN:
-        // Users who can edit a blog can always post to it.
-        if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_edit)) {
-          return true;
-        }
         break;
     }
 
     return false;
   }
 
 
   public function describeAutomaticCapability($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return pht(
-          'Users who can edit or post on a blog can always view it.');
-      case PhabricatorPolicyCapability::CAN_JOIN:
-        return pht(
-          'Users who can edit a blog can always post on it.');
+          'Users who can edit a blog can always view it.');
     }
 
     return null;
   }
 
 
 /* -(  PhabricatorMarkupInterface Implementation  )-------------------------- */
 
 
   public function getMarkupFieldKey($field) {
     $hash = PhabricatorHash::digest($this->getMarkupText($field));
     return $this->getPHID().':'.$field.':'.$hash;
   }
 
 
   public function newMarkupEngine($field) {
     return PhabricatorMarkupEngine::newPhameMarkupEngine();
   }
 
 
   public function getMarkupText($field) {
     return $this->getDescription();
   }
 
 
   public function didMarkupText(
     $field,
     $output,
     PhutilMarkupEngine $engine) {
     return $output;
   }
 
   public function shouldUseMarkupCache($field) {
     return (bool)$this->getPHID();
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhameBlogEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhameBlogTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
     return $timeline;
   }
 
 
 /* -(  PhabricatorSubscribableInterface Implementation  )-------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($this->creatorPHID == $phid);
   }
 
   public function shouldShowSubscribersProperty() {
     return true;
   }
 
   public function shouldAllowSubscription($phid) {
     return true;
   }
 
 
 }
diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php
index ef39d2054..76681f2f5 100644
--- a/src/applications/phame/storage/PhamePost.php
+++ b/src/applications/phame/storage/PhamePost.php
@@ -1,307 +1,310 @@
 <?php
 
 final class PhamePost extends PhameDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorMarkupInterface,
     PhabricatorFlaggableInterface,
     PhabricatorProjectInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorSubscribableInterface,
     PhabricatorTokenReceiverInterface {
 
   const MARKUP_FIELD_BODY    = 'markup:body';
   const MARKUP_FIELD_SUMMARY = 'markup:summary';
 
   protected $bloggerPHID;
   protected $title;
   protected $phameTitle;
   protected $body;
   protected $visibility;
   protected $configData;
   protected $datePublished;
   protected $blogPHID;
   protected $mailKey;
 
   private $blog;
 
   public static function initializePost(
     PhabricatorUser $blogger,
     PhameBlog $blog) {
 
     $post = id(new PhamePost())
       ->setBloggerPHID($blogger->getPHID())
       ->setBlogPHID($blog->getPHID())
       ->setBlog($blog)
       ->setDatePublished(0)
       ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED);
     return $post;
   }
 
   public function setBlog(PhameBlog $blog) {
     $this->blog = $blog;
     return $this;
   }
 
   public function getBlog() {
     return $this->blog;
   }
 
   public function getViewURI() {
     // go for the pretty uri if we can
     $domain = ($this->blog ? $this->blog->getDomain() : '');
     if ($domain) {
       $phame_title = PhabricatorSlug::normalize($this->getPhameTitle());
       return 'http://'.$domain.'/post/'.$phame_title;
     }
     $uri = '/phame/post/view/'.$this->getID().'/';
     return PhabricatorEnv::getProductionURI($uri);
   }
 
   public function getEditURI() {
     return '/phame/post/edit/'.$this->getID().'/';
   }
 
   public function isDraft() {
     return $this->getVisibility() == PhameConstants::VISIBILITY_DRAFT;
   }
 
   public function getHumanName() {
     if ($this->isDraft()) {
       $name = 'draft';
     } else {
       $name = 'post';
     }
 
     return $name;
   }
 
   public function setCommentsWidget($widget) {
     $config_data = $this->getConfigData();
     $config_data['comments_widget'] = $widget;
     return $this;
   }
 
   public function getCommentsWidget() {
     $config_data = $this->getConfigData();
     if (empty($config_data)) {
       return 'none';
     }
     return idx($config_data, 'comments_widget', 'none');
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID   => true,
       self::CONFIG_SERIALIZATION => array(
         'configData' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'title' => 'text255',
         'phameTitle' => 'sort64',
         'visibility' => 'uint32',
         'mailKey' => 'bytes20',
 
         // T6203/NULLABILITY
         // These seem like they should always be non-null?
         'blogPHID' => 'phid?',
         'body' => 'text?',
         'configData' => 'text?',
 
         // T6203/NULLABILITY
         // This one probably should be nullable?
         'datePublished' => 'epoch',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
         'phameTitle' => array(
           'columns' => array('bloggerPHID', 'phameTitle'),
           'unique' => true,
         ),
         'bloggerPosts' => array(
           'columns' => array(
             'bloggerPHID',
             'visibility',
             'datePublished',
             'id',
           ),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function save() {
     if (!$this->getMailKey()) {
       $this->setMailKey(Filesystem::readRandomCharacters(20));
     }
     return parent::save();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPhamePostPHIDType::TYPECONST);
   }
 
   public function toDictionary() {
     return array(
       'id'            => $this->getID(),
       'phid'          => $this->getPHID(),
       'blogPHID'      => $this->getBlogPHID(),
       'bloggerPHID'   => $this->getBloggerPHID(),
       'viewURI'       => $this->getViewURI(),
       'title'         => $this->getTitle(),
       'phameTitle'    => $this->getPhameTitle(),
       'body'          => $this->getBody(),
       'summary'       => PhabricatorMarkupEngine::summarize($this->getBody()),
       'datePublished' => $this->getDatePublished(),
       'published'     => !$this->isDraft(),
     );
   }
 
   public function getCommentsWidgetOptionsForSelect() {
     $current = $this->getCommentsWidget();
     $options = array();
 
     if ($current == 'facebook' ||
         PhabricatorFacebookAuthProvider::getFacebookApplicationID()) {
       $options['facebook'] = pht('Facebook');
     }
     if ($current == 'disqus' ||
         PhabricatorEnv::getEnvConfig('disqus.shortname')) {
       $options['disqus'] = pht('Disqus');
     }
     $options['none'] = pht('None');
 
     return $options;
   }
 
 
 /* -(  PhabricatorPolicyInterface Implementation  )-------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     // Draft posts are visible only to the author. Published posts are visible
     // to whoever the blog is visible to.
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         if (!$this->isDraft() && $this->getBlog()) {
           return $this->getBlog()->getViewPolicy();
-        } else {
-          return PhabricatorPolicies::POLICY_NOONE;
+        } else if ($this->getBlog()) {
+          return $this->getBlog()->getEditPolicy();
         }
         break;
       case PhabricatorPolicyCapability::CAN_EDIT:
-        return PhabricatorPolicies::POLICY_NOONE;
+        if ($this->getBlog()) {
+          return $this->getBlog()->getEditPolicy();
+        } else {
+          return PhabricatorPolicies::POLICY_NOONE;
+        }
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
-    // A blog post's author can always view it, and is the only user allowed
-    // to edit it.
+    // A blog post's author can always view it.
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
       case PhabricatorPolicyCapability::CAN_EDIT:
         return ($user->getPHID() == $this->getBloggerPHID());
     }
   }
 
   public function describeAutomaticCapability($capability) {
     return pht('The author of a blog post can always view and edit it.');
   }
 
 
 /* -(  PhabricatorMarkupInterface Implementation  )-------------------------- */
 
 
   public function getMarkupFieldKey($field) {
     $hash = PhabricatorHash::digest($this->getMarkupText($field));
     return $this->getPHID().':'.$field.':'.$hash;
   }
 
   public function newMarkupEngine($field) {
     return PhabricatorMarkupEngine::newPhameMarkupEngine();
   }
 
   public function getMarkupText($field) {
     switch ($field) {
       case self::MARKUP_FIELD_BODY:
         return $this->getBody();
       case self::MARKUP_FIELD_SUMMARY:
         return PhabricatorMarkupEngine::summarize($this->getBody());
     }
   }
 
   public function didMarkupText(
     $field,
     $output,
     PhutilMarkupEngine $engine) {
     return $output;
   }
 
   public function shouldUseMarkupCache($field) {
     return (bool)$this->getPHID();
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhamePostEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhamePostTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
 
   public function getUsersToNotifyOfTokenGiven() {
     return array(
       $this->getBloggerPHID(),
     );
   }
 
 
 /* -(  PhabricatorSubscribableInterface Implementation  )-------------------- */
 
 
   public function isAutomaticallySubscribed($phid) {
     return ($this->bloggerPHID == $phid);
   }
 
   public function shouldShowSubscribersProperty() {
     return true;
   }
 
   public function shouldAllowSubscription($phid) {
     return true;
   }
 
 }