diff --git a/conf/default.conf.php b/conf/default.conf.php
index 465e402492..1ccf8c2277 100644
--- a/conf/default.conf.php
+++ b/conf/default.conf.php
@@ -1,1341 +1,1346 @@
 <?php
 
 return array(
 
   // The root URI which Phabricator is installed on.
   // Example: "http://phabricator.example.com/"
   'phabricator.base-uri'        => null,
 
   // If you have multiple environments, provide the production environment URI
   // here so that emails, etc., generated in development/sandbox environments
   // contain the right links.
   'phabricator.production-uri'  => null,
 
   // Setting this to 'true' will invoke a special setup mode which helps guide
   // you through setting up Phabricator.
   'phabricator.setup'           => false,
 
 // -- IMPORTANT! Security! -------------------------------------------------- //
 
   // IMPORTANT: By default, Phabricator serves files from the same domain the
   // application lives on. This is convenient but not secure: it creates a large
   // class of vulnerabilities which can not be generally mitigated.
   //
   // To avoid this, you should configure a second domain in the same way you
   // have the primary domain configured (e.g., point it at the same machine and
   // set up the same vhost rules) and provide it here. For instance, if your
   // primary install is on "http://www.phabricator-example.com/", you could
   // configure "http://www.phabricator-files.com/" and specify the entire
   // domain (with protocol) here. This will enforce that files are
   // served only from the alternate domain. Ideally, you should use a
   // completely separate domain name rather than just a different subdomain.
   //
   // It is STRONGLY RECOMMENDED that you configure this. Your install is NOT
   // SECURE unless you do so.
   'security.alternate-file-domain'  => null,
 
   // Default key for HMAC digests where the key is not important (i.e., the
   // hash itself is secret). You can change this if you want (to any other
   // string), but doing so will break existing sessions and CSRF tokens.
   'security.hmac-key' => '[D\t~Y7eNmnQGJ;rnH6aF;m2!vJ8@v8C=Cs:aQS\.Qw',
 
   // If the web server responds to both HTTP and HTTPS requests but you want
   // users to connect with only HTTPS, you can set this to true to make
   // Phabricator redirect HTTP requests to HTTPS.
   //
   // Normally, you should just configure your server not to accept HTTP traffic,
   // but this setting may be useful if you originally used HTTP and have now
   // switched to HTTPS but don't want to break old links, or if your webserver
   // sits behind a load balancer which terminates HTTPS connections and you
   // can not reasonably configure more granular behavior there.
   //
   // NOTE: Phabricator determines if a request is HTTPS or not by examining the
   // PHP $_SERVER['HTTPS'] variable. If you run Apache/mod_php this will
   // probably be set correctly for you automatically, but if you run Phabricator
   // as CGI/FCGI (e.g., through nginx or lighttpd), you need to configure your
   // web server so that it passes the value correctly based on the connection
   // type. Alternatively, you can add a PHP snippet to the top of this
   // configuration file to directly set $_SERVER['HTTPS'] to the correct value.
   'security.require-https' => false,
 
 
 // -- Internationalization -------------------------------------------------- //
 
   // This allows customizing texts used in Phabricator. The class must extend
   // PhabricatorTranslation.
   'translation.provider' => 'PhabricatorEnglishTranslation',
 
   // You can use 'translation.override' if you don't want to create a full
   // translation to give users an option for switching to it and you just want
   // to override some strings in the default translation.
   'translation.override' => array(),
 
 
 // -- Access Policies ------------------------------------------------------- //
 
   // Phabricator allows you to set the visibility of objects (like repositories
   // and source code) to "Public", which means anyone on the internet can see
   // them, even without being logged in. This is great for open source, but
   // some installs may never want to make anything public, so this policy is
   // disabled by default. You can enable it here, which will let you set the
   // policy for objects to "Public". With this option disabled, the most open
   // policy is "All Users", which means users must be logged in to view things.
   'policy.allow-public'         => false,
 
 
 // -- Logging --------------------------------------------------------------- //
 
   // To enable the Phabricator access log, specify a path here. The Phabricator
   // access log can provide more detailed information about Phabricator access
   // than normal HTTP access logs (for instance, it can show logged-in users,
   // controllers, and other application data). If not set, no log will be
   // written.
   //
   // Make sure the PHP process can write to the log!
   'log.access.path'             => null,
 
   // Format for the access log. If not set, the default format will be used:
   //
   //  "[%D]\t%h\t%u\t%M\t%C\t%m\t%U\t%c\t%T"
   //
   // Available variables are:
   //
   //  - %c The HTTP response code.
   //  - %C The controller which handled the request.
   //  - %D The request date.
   //  - %e Epoch timestamp.
   //  - %h The webserver's host name.
   //  - %p The PID of the server process.
   //  - %R The HTTP referrer.
   //  - %r The remote IP.
   //  - %T The request duration, in microseconds.
   //  - %U The request path.
   //  - %u The logged-in user, if one is logged in.
   //  - %M The HTTP method.
   //  - %m For conduit, the Conduit method which was invoked.
   //
   // If a variable isn't available (for example, %m appears in the file format
   // but the request is not a Conduit request), it will be rendered as "-".
   //
   // Note that the default format is subject to change in the future, so if you
   // rely on the log's format, specify it explicitly.
   'log.access.format'           => null,
 
 
 // -- DarkConsole ----------------------------------------------------------- //
 
   // DarkConsole is a administrative debugging/profiling tool built into
   // Phabricator. You can leave it disabled unless you're developing against
   // Phabricator.
 
   // Determines whether or not DarkConsole is available. DarkConsole exposes
   // some data like queries and stack traces, so you should be careful about
   // turning it on in production (although users can not normally see it, even
   // if the deployment configuration enables it).
   'darkconsole.enabled'         => false,
 
   // Always enable DarkConsole, even for logged out users. This potentially
   // exposes sensitive information to users, so make sure untrusted users can
   // not access an install running in this mode. You should definitely leave
   // this off in production. It is only really useful for using DarkConsole
   // utilities to debug or profile logged-out pages. You must set
   // 'darkconsole.enabled' to use this option.
   'darkconsole.always-on'       => false,
 
   // Map of additional configuration values to lock.
   'config.lock' => array(),
 
   // Map of additional configuration values to hide.
   'config.hide' => array(),
 
   // Map of additional configuration values to mask.
   'config.mask' => array(),
 
 // --  MySQL  --------------------------------------------------------------- //
 
   // Class providing database configuration. It must implement
   // DatabaseConfigurationProvider.
   'mysql.configuration-provider' => 'DefaultDatabaseConfigurationProvider',
 
   // The username to use when connecting to MySQL.
   'mysql.user' => 'root',
 
   // The password to use when connecting to MySQL.
   'mysql.pass' => '',
 
   // The MySQL server to connect to. If you want to connect to a different
   // port than the default (which is 3306), specify it in the hostname
   // (e.g., db.example.com:1234).
   'mysql.host' => 'localhost',
 
   // Phabricator supports PHP extensions MySQL and MySQLi. It is possible to
   // implement also other access mechanism (e.g. PDO_MySQL). The class must
   // extend AphrontMySQLDatabaseConnectionBase.
   'mysql.implementation' => 'AphrontMySQLDatabaseConnection',
 
 
 // -- Notifications --------------------------------------------------------- //
 
   // Set this to true to enable real-time notifications. You must also run a
   // notification server for this to work. Consult the documentation in
   // "Notifications User Guide: Setup and Configuration" for instructions.
   'notification.enabled'      => false,
 
   // Client port for the realtime server to listen on, and for realtime clients
   // to connect to. Use "localhost" if you are running the notification server
   // on the same host as the web server.
   'notification.client-uri'   => 'http://localhost:22280/',
 
   // URI and port for the notification root server.
   'notification.server-uri'   => 'http://localhost:22281/',
 
   // The server must be started as root so it can bind to privileged ports, but
   // if you specify a user here it will drop permissions after binding.
   'notification.user'         => null,
 
   // Location where the server should log to.
   'notification.log'          => '/var/log/aphlict.log',
 
   // PID file to use.
   'notification.pidfile'      => '/var/run/aphlict.pid',
 
   // Enable this option to get additional debug output in the browser.
   'notification.debug'        => false,
 
 
 // -- Email ----------------------------------------------------------------- //
 
   // Some Phabricator tools send email notifications, e.g. when Differential
   // revisions are updated or Maniphest tasks are changed. These options allow
   // you to configure how email is delivered.
 
   // You can test your mail setup by going to "MetaMTA" in the web interface,
   // clicking "Send New Message", and then composing a message.
 
   // Default address to send mail "From".
   'metamta.default-address'     => 'noreply@example.com',
 
   // Domain used to generate Message-IDs.
   'metamta.domain'              => 'example.com',
 
   // When a message is sent to multiple recipients (for example, several
   // reviewers on a code review), Phabricator can either deliver one email to
   // everyone (e.g., "To: alincoln, usgrant, htaft") or separate emails to each
   // user (e.g., "To: alincoln", "To: usgrant", "To: htaft"). The major
   // advantages and disadvantages of each approach are:
   //
   //   - One mail to everyone:
   //     - Recipients can see To/Cc at a glance.
   //     - If you use mailing lists, you won't get duplicate mail if you're
   //       a normal recipient and also Cc'd on a mailing list.
   //     - Getting threading to work properly is harder, and probably requires
   //       making mail less useful by turning off options.
   //     - Sometimes people will "Reply All" and everyone will get two mails,
   //       one from the user and one from Phabricator turning their mail into
   //       a comment.
   //     - Not supported with a private reply-to address.
   //     - Mails are sent in the server default translation.
   //   - One mail to each user:
   //     - Recipients need to look in the mail body to see To/Cc.
   //     - If you use mailing lists, recipients may sometimes get duplicate
   //       mail.
   //     - Getting threading to work properly is easier, and threading settings
   //       can be customzied by each user.
   //     - "Reply All" no longer spams all other users.
   //     - Required if private reply-to addresses are configured.
   //     - Mails are sent in the language of user preference.
   //
   // In the code, splitting one outbound email into one-per-recipient is
   // sometimes referred to as "multiplexing".
   'metamta.one-mail-per-recipient'  => true,
 
   // When sending a message that has no To recipient (i.e. all recipients
   // are CC'd, for example when multiplexing mail), set the To field to the
   // following value. If no value is set, messages with no To will have
   // their CCs upgraded to To.
   'metamta.placeholder-to-recipient' => null,
 
   // When a user takes an action which generates an email notification (like
   // commenting on a Differential revision), Phabricator can either send that
   // mail "From" the user's email address (like "alincoln@logcabin.com") or
   // "From" the 'metamta.default-address' address. The user experience is
   // generally better if Phabricator uses the user's real address as the "From"
   // since the messages are easier to organize when they appear in mail clients,
   // but this will only work if the server is authorized to send email on behalf
   // of the "From" domain. Practically, this means:
   //    - If you are doing an install for Example Corp and all the users will
   //      have corporate @corp.example.com addresses and any hosts Phabricator
   //      is running on are authorized to send email from corp.example.com,
   //      you can enable this to make the user experience a little better.
   //    - If you are doing an install for an open source project and your
   //      users will be registering via Facebook and using personal email
   //      addresses, you MUST NOT enable this or virtually all of your outgoing
   //      email will vanish into SFP blackholes.
   //    - If your install is anything else, you're much safer leaving this
   //      off since the risk in turning it on is that your outgoing mail will
   //      mostly never arrive.
   'metamta.can-send-as-user'    => false,
 
   // Adapter class to use to transmit mail to the MTA. The default uses
   // PHPMailerLite, which will invoke "sendmail". This is appropriate
   // if sendmail actually works on your host, but if you haven't configured mail
   // it may not be so great. A number of other mailers are available (e.g., SES,
   // SendGrid, SMTP, custom mailers), consult "Configuring Outbound Email" in
   // the documentation for details.
   'metamta.mail-adapter'        =>
     'PhabricatorMailImplementationPHPMailerLiteAdapter',
 
   // When email is sent, try to hand it off to the MTA immediately instead of
   // queueing it for delivery by the daemons. If you are running the Phabricator
   // daemons with "phd start", you should disable this to provide a (sometimes
   // substantial) performance boost. It's on by default to make setup and
   // configuration a little easier.
   'metamta.send-immediately'    => true,
 
   // When email is sent, what format should Phabricator use for user's
   // email addresses? Valid values are:
   //  - 'short' - 'gwashington <gwashington@example.com>'
   //  - 'real'  - 'George Washington <gwashington@example.com>'
   //  - 'full' - 'gwashington (George Washington) <gwashington@example.com>'
   // The default is 'full'.
   'metamta.user-address-format' => 'full',
 
   // If you're using PHPMailer to send email, provide the mailer and options
   // here. PHPMailer is much more enormous than PHPMailerLite, and provides more
   // mailers and greater enormity. You need it when you want to use SMTP
   // instead of sendmail as the mailer.
   'phpmailer.mailer'            =>  'smtp',
   'phpmailer.smtp-host'         =>  '',
   'phpmailer.smtp-port'         =>  25,
 
   // When using PHPMailer with SMTP, you can set this to one of "tls" or "ssl"
   // to use TLS or SSL. Leave it blank for vanilla SMTP. If you're sending
   // via Gmail, set it to "ssl".
   'phpmailer.smtp-protocol'     => '',
 
   // Set following if your smtp server requires authentication.
   'phpmailer.smtp-user'         =>  null,
   'phpmailer.smtp-password'     =>  null,
 
   // If you're using Amazon SES to send email, provide your AWS access key
   // and AWS secret key here. To set up Amazon SES with Phabricator, you need
   // to:
   //  - Make sure 'metamta.mail-adapter' is set to:
   //    "PhabricatorMailImplementationAmazonSESAdapter"
   //  - Make sure 'metamta.can-send-as-user' is false.
   //  - Make sure 'metamta.default-address' is configured to something sensible.
   //  - Make sure 'metamta.default-address' is a validated SES "From" address.
   'amazon-ses.access-key'       =>  null,
   'amazon-ses.secret-key'       =>  null,
 
   // If you're using Sendgrid to send email, provide your access credentials
   // here. This will use the REST API. You can also use Sendgrid as a normal
   // SMTP service.
   'sendgrid.api-user'           => null,
   'sendgrid.api-key'            => null,
 
   // You can configure a reply handler domain so that email sent from Maniphest
   // will have a special "Reply To" address like "T123+82+af19f@example.com"
   // that allows recipients to reply by email and interact with tasks. For
   // instructions on configurating reply handlers, see the article
   // "Configuring Inbound Email" in the Phabricator documentation. By default,
   // this is set to 'null' and Phabricator will use a generic 'noreply@' address
   // or the address of the acting user instead of a special reply handler
   // address (see 'metamta.default-address'). If you set a domain here,
   // Phabricator will begin generating private reply handler addresses. See
   // also 'metamta.maniphest.reply-handler' to further configure behavior.
   // This key should be set to the domain part after the @, like "example.com".
   'metamta.maniphest.reply-handler-domain' => null,
 
   // You can follow the instructions in "Configuring Inbound Email" in the
   // Phabricator documentation and set 'metamta.maniphest.reply-handler-domain'
   // to support updating Maniphest tasks by email. If you want more advanced
   // customization than this provides, you can override the reply handler
   // class with an implementation of your own. This will allow you to do things
   // like have a single public reply handler or change how private reply
   // handlers are generated and validated.
   //
   // This key should be set to a loadable subclass of
   // PhabricatorMailReplyHandler.
   'metamta.maniphest.reply-handler' => 'ManiphestReplyHandler',
 
   // If you don't want phabricator to take up an entire domain
   // (or subdomain for that matter), you can use this and set a common
   // prefix for mail sent by phabricator. It will make use of the fact that
   // a mail-address such as phabricator+D123+1hjk213h@example.com will be
   // delivered to the phabricator users mailbox.
   // Set this to the left part of the email address and it well get
   // prepended to all outgoing mail. If you want to use e.g.
   // 'phabricator@example.com' this should be set to 'phabricator'.
   'metamta.single-reply-handler-prefix' => null,
 
   // Prefix prepended to mail sent by Maniphest. You can change this to
   // distinguish between testing and development installs, for example.
   'metamta.maniphest.subject-prefix' => '[Maniphest]',
 
   // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, but
   // affects Pholio.
   'metamta.pholio.reply-handler-domain' => null,
 
   // Prefix prepended to mail sent by Pholio.
   'metamta.pholio.subject-prefix' => '[Pholio]',
 
   // See 'metamta.maniphest.reply-handler-domain'. This does the same thing, but
   // affects Macro.
   'metamta.macro.reply-handler-domain' => null,
 
   // Prefix prepended to mail sent by Macro.
   'metamta.macro.subject-prefix' => '[Macro]',
 
   // See 'metamta.maniphest.reply-handler-domain'. This does the same thing,
   // but allows email replies via Differential.
   'metamta.differential.reply-handler-domain' => null,
 
   // See 'metamta.maniphest.reply-handler'. This does the same thing, but
   // affects Differential.
   'metamta.differential.reply-handler' => 'DifferentialReplyHandler',
 
   // Prefix prepended to mail sent by Differential.
   'metamta.differential.subject-prefix' => '[Differential]',
 
   // Set this to true if you want patches to be attached to mail from
   // Differential. This won't work if you are using SendGrid as your mail
   // adapter.
   'metamta.differential.attach-patches' => false,
 
   // To include patches in email bodies, set this to a positive integer. Patches
   // will be inlined if they are at most that many lines. For instance, a value
   // of 100 means "inline patches if they are no longer than 100 lines". By
   // default, patches are not inlined.
   'metamta.differential.inline-patches' => 0,
 
   // If you enable either of the options above, you can choose what format
   // patches are sent in. Valid options are 'unified' (like diff -u) or 'git'.
   'metamta.differential.patch-format'   => 'unified',
 
   // Enables a different format for comments in differential emails.
   // Differential will create unified diffs around the comment, which
   // will give enough context for people who are only viewing the
   // reviews in email to understand what is going on. The context will
   // be created based on the range of the comment.
   'metamta.differential.unified-comment-context' => false,
 
   // Prefix prepended to mail sent by Diffusion.
   'metamta.diffusion.subject-prefix' => '[Diffusion]',
 
   // See 'metamta.maniphest.reply-handler-domain'. This does the same thing,
   // but allows email replies via Diffusion.
   'metamta.diffusion.reply-handler-domain' => null,
 
   // See 'metamta.maniphest.reply-handler'. This does the same thing, but
   // affects Diffusion.
   'metamta.diffusion.reply-handler' => 'PhabricatorAuditReplyHandler',
 
   // Set this to true if you want patches to be attached to commit notifications
   // from Diffusion. This won't work with SendGrid.
   'metamta.diffusion.attach-patches' => false,
 
   // To include patches in Diffusion email bodies, set this to a positive
   // integer. Patches will be inlined if they are at most that many lines.
   // By default, patches are not inlined.
   'metamta.diffusion.inline-patches' => 0,
 
   // If you've enabled attached patches or inline patches for commit emails, you
   // can establish a hard byte limit on their size. You should generally set
   // reasonable byte and time limits (defaults are 1MB and 60 seconds) to avoid
   // sending ridiculously enormous email for changes like "importing an external
   // library" or "accidentally committed this full-length movie as text".
   'metamta.diffusion.byte-limit'     => 1024 * 1024,
 
   // If you've enabled attached patches or inline patches for commit emails, you
   // can establish a hard time limit on generating them.
   'metamta.diffusion.time-limit'     => 60,
 
   // Prefix prepended to mail sent by Package.
   'metamta.package.subject-prefix' => '[Package]',
 
   // See 'metamta.maniphest.reply-handler'. This does similar thing for package
   // except that it only supports sending out mail and doesn't handle incoming
   // email.
   'metamta.package.reply-handler' => 'OwnersPackageReplyHandler',
 
   // By default, Phabricator generates unique reply-to addresses and sends a
   // separate email to each recipient when you enable reply handling. This is
   // more secure than using "From" to establish user identity, but can mean
   // users may receive multiple emails when they are on mailing lists. Instead,
   // you can use a single, non-unique reply to address and authenticate users
   // based on the "From" address by setting this to 'true'. This trades away
   // a little bit of security for convenience, but it's reasonable in many
   // installs. Object interactions are still protected using hashes in the
   // single public email address, so objects can not be replied to blindly.
   'metamta.public-replies' => false,
 
   // You can configure an email address like "bugs@phabricator.example.com"
   // which will automatically create Maniphest tasks when users send email
   // to it. This relies on the "From" address to authenticate users, so it is
   // is not completely secure. To set this up, enter a complete email
   // address like "bugs@phabricator.example.com" and then configure mail to
   // that address so it routed to Phabricator (if you've already configured
   // reply handlers, you're probably already done). See "Configuring Inbound
   // Email" in the documentation for more information.
   'metamta.maniphest.public-create-email' => null,
 
   // If you enable 'metamta.public-replies', Phabricator uses "From" to
   // authenticate users. You can additionally enable this setting to try to
   // authenticate with 'Reply-To'. Note that this is completely spoofable and
   // insecure (any user can set any 'Reply-To' address) but depending on the
   // nature of your install or other deliverability conditions this might be
   // okay. Generally, you can't do much more by spoofing Reply-To than be
   // annoying (you can write but not read content). But, you know, this is
   // still **COMPLETELY INSECURE**.
   'metamta.insecure-auth-with-reply-to' => false,
 
   // If you enable 'metamta.maniphest.public-create-email' and create an
   // email address like "bugs@phabricator.example.com", it will default to
   // rejecting mail which doesn't come from a known user. However, you might
   // want to let anyone send email to this address; to do so, set a default
   // author here (a Phabricator username). A typical use of this might be to
   // create a "System Agent" user called "bugs" and use that name here. If you
   // specify a valid username, mail will always be accepted and used to create
   // a task, even if the sender is not a system user. The original email
   // address will be stored in an 'From Email' field on the task.
   'metamta.maniphest.default-public-author' => null,
 
   // You can disable the Herald hints in email if users prefer smaller messages.
   // These are the links under the headers "MANAGE HERALD RULES" and
   // "WHY DID I GET THIS EMAIL?". If you set this to true, they will not appear
   // in any mail. Users can still navigate to the links via the web interface.
   'metamta.herald.show-hints' => true,
 
   // You can disable the hints under "REPLY HANDLER ACTIONS" if users prefer
   // smaller messages. The actions themselves will still work properly.
   'metamta.reply.show-hints' => true,
 
   // You can disable the "To:" and "Cc:" footers in mail if users prefer
   // smaller messages.
   'metamta.recipients.show-hints' => true,
 
   // If this option is enabled, Phabricator will add a "Precedence: bulk"
   // header to transactional mail (e.g., Differential, Maniphest and Herald
   // notifications). This may improve the behavior of some auto-responder
   // software and prevent it from replying. However, it may also cause
   // deliverability issues -- notably, you currently can not send this header
   // via Amazon SES, and enabling this option with SES will prevent delivery
   // of any affected mail.
   'metamta.precedence-bulk' => false,
 
   // Mail.app on OS X Lion won't respect threading headers unless the subject
   // is prefixed with "Re:". If you enable this option, Phabricator will add
   // "Re:" to the subject line of all mail which is expected to thread. If
   // you've set 'metamta.one-mail-per-recipient', users can override this
   // setting in their preferences.
   'metamta.re-prefix' => false,
 
   // If true, allow MetaMTA to change mail subjects to put text like
   // '[Accepted]' and '[Commented]' in them. This makes subjects more useful,
   // but might break threading on some clients. If you've set
   // 'metamta.one-mail-per-recipient', users can override this setting in their
   // preferences.
   'metamta.vary-subjects' => true,
 
 
 // -- Auth ------------------------------------------------------------------ //
 
   // Can users login with a username/password, or by following the link from
   // a password reset email? You can disable this and configure one or more
   // OAuth providers instead.
   'auth.password-auth-enabled'  => true,
 
   // Maximum number of simultaneous web sessions each user is permitted to have.
   // Setting this to "1" will prevent a user from logging in on more than one
   // browser at the same time.
   'auth.sessions.web'           => 5,
 
   // Maximum number of simultaneous Conduit sessions each user is permitted
   // to have.
   'auth.sessions.conduit'       => 5,
 
   // Set this true to enable the Settings -> SSH Public Keys panel, which will
   // allow users to associated SSH public keys with their accounts. This is only
   // really useful if you're setting up services over SSH and want to use
   // Phabricator for authentication; in most situations you can leave this
   // disabled.
   'auth.sshkeys.enabled'        => false,
 
   // If true, email addresses must be verified (by clicking a link in an
   // email) before a user can login. By default, verification is optional
   // unless 'auth.email-domains' is nonempty (see below).
   'auth.require-email-verification' => false,
 
   // You can restrict allowed email addresses to certain domains (like
   // "yourcompany.com") by setting a list of allowed domains here. Users will
   // only be allowed to register using email addresses at one of the domains,
   // and will only be able to add new email addresses for these domains. If
   // you configure this, it implies 'auth.require-email-verification'.
   //
   // To configure email domains, set a list of domains like this:
   //
   //   array(
   //     'yourcompany.com',
   //     'yourcompany.co.uk',
   //   )
   //
   // You should omit the "@" from domains. Note that the domain must match
   // exactly. If you allow "yourcompany.com", that permits "joe@yourcompany.com"
   // but rejects "joe@mail.yourcompany.com".
   'auth.email-domains' => array(),
 
   // You can provide an arbitrary block of HTML here, which will appear on the
   // login screen. Normally, you'd use this to provide login or registration
   // instructions to users.
   'auth.login-message' => null,
 
 
 // -- Accounts -------------------------------------------------------------- //
 
   // Is basic account information (email, real name, profile picture) editable?
   // If you set up Phabricator to automatically synchronize account information
   // from some other authoritative system, you can disable this to ensure
   // information remains consistent across both systems.
   'account.editable'            => true,
 
   // When users set or reset a password, it must have at least this many
   // characters.
   'account.minimum-password-length'  => 8,
 
 
 // -- Facebook OAuth -------------------------------------------------------- //
 
   // Can users use Facebook credentials to login to Phabricator?
   'facebook.auth-enabled'       => false,
 
   // Can users use Facebook credentials to create new Phabricator accounts?
   'facebook.registration-enabled' => true,
 
   // Are Facebook accounts permanently linked to Phabricator accounts, or can
   // the user unlink them?
   'facebook.auth-permanent'     => false,
 
   // The Facebook "Application ID" to use for Facebook API access.
   'facebook.application-id'     => null,
 
   // The Facebook "Application Secret" to use for Facebook API access.
   'facebook.application-secret' => null,
 
   // Should Phabricator reject requests made by users with
   // Secure Browsing disabled?
   'facebook.require-https-auth' => false,
 
 // -- GitHub OAuth ---------------------------------------------------------- //
 
   // Can users use GitHub credentials to login to Phabricator?
   'github.auth-enabled'         => false,
 
   // Can users use GitHub credentials to create new Phabricator accounts?
   'github.registration-enabled' => true,
 
   // Are GitHub accounts permanently linked to Phabricator accounts, or can
   // the user unlink them?
   'github.auth-permanent'       => false,
 
   // The GitHub "Client ID" to use for GitHub API access.
   'github.application-id'       => null,
 
   // The GitHub "Secret" to use for GitHub API access.
   'github.application-secret'   => null,
 
 
 // -- Google OAuth ---------------------------------------------------------- //
 
   // Can users use Google credentials to login to Phabricator?
   'google.auth-enabled'         => false,
 
   // Can users use Google credentials to create new Phabricator accounts?
   'google.registration-enabled' => true,
 
   // Are Google accounts permanently linked to Phabricator accounts, or can
   // the user unlink them?
   'google.auth-permanent'       => false,
 
   // The Google "Client ID" to use for Google API access.
   'google.application-id'       => null,
 
   // The Google "Client Secret" to use for Google API access.
   'google.application-secret'   => null,
 
 // -- LDAP Auth ----------------------------------------------------- //
   // Enable ldap auth
   'ldap.auth-enabled'         => false,
 
   // The LDAP server hostname
   'ldap.hostname' => null,
 
   // The LDAP server port
   'ldap.port' => 389,
 
   // The LDAP base domain name
   'ldap.base_dn' => null,
 
   // The attribute to be regarded as 'username'. Has to be unique
   'ldap.search_attribute' => null,
 
   // Perform a search to find a user
   // Many LDAP installations do not have the username in the dn, if this is
   // true for you set this to true and configure the username_attribute below
   'ldap.search-first'         => false,
 
   // The attribute to search for if you have to search for a user
   'ldap.username-attribute' => null,
 
   // The attribute(s) to be regarded as 'real name'.
   // If more then one attribute is supplied the values of the attributes in
   // the array will be joined
   'ldap.real_name_attributes' => array(),
 
   // A domain name to use when authenticating against Active Directory
   // (e.g. 'example.com')
   'ldap.activedirectory_domain' => null,
 
   // The LDAP version
   'ldap.version' => 3,
 
   // LDAP Referrals Option
   // Whether referrals should be followed by the client
   // Should be set to 0 if you use Windows 2003 AD
   'ldap.referrals' => true,
 
   // The anonymous user name to use before searching a user.
   // Many LDAP installations require login even before searching a user, set
   // this option to enable it.
   'ldap.anonymous-user-name'     => null,
 
   // The password of the LDAP anonymous user.
   'ldap.anonymous-user-password' => null,
 
   // Whether to use STARTTLS
   'ldap.start-tls' => false,
 
 
 // -- Disqus OAuth ---------------------------------------------------------- //
 
   // Can users use Disqus credentials to login to Phabricator?
   'disqus.auth-enabled'         => false,
 
   // Can users use Disqus credentials to create new Phabricator accounts?
   'disqus.registration-enabled' => true,
 
   // Are Disqus accounts permanently linked to Phabricator accounts, or can
   // the user unlink them?
   'disqus.auth-permanent'       => false,
 
   // The Disqus "Client ID" to use for Disqus API access.
   'disqus.application-id'       => null,
 
   // The Disqus "Client Secret" to use for Disqus API access.
   'disqus.application-secret'   => null,
 
 
 // -- Phabricator OAuth ----------------------------------------------------- //
 
   // Meta-town -- Phabricator is itself an OAuth Provider
   // TODO -- T887 -- make this support multiple Phabricator instances!
 
   // The URI of the Phabricator instance to use as an OAuth server.
   'phabricator.oauth-uri'            => null,
 
   // Can users use Phabricator credentials to login to Phabricator?
   'phabricator.auth-enabled'         => false,
 
   // Can users use Phabricator credentials to create new Phabricator accounts?
   'phabricator.registration-enabled' => true,
 
   // Are Phabricator accounts permanently linked to Phabricator accounts, or can
   // the user unlink them?
   'phabricator.auth-permanent'       => false,
 
   // The Phabricator "Client ID" to use for Phabricator API access.
   'phabricator.application-id'       => null,
 
   // The Phabricator "Client Secret" to use for Phabricator API access.
   'phabricator.application-secret'   => null,
 
 
 // -- Recaptcha ------------------------------------------------------------- //
 
   // Is Recaptcha enabled? If disabled, captchas will not appear. You should
   // enable Recaptcha if your install is public-facing, as it hinders
   // brute-force attacks.
   'recaptcha.enabled'           => false,
 
   // Your Recaptcha public key, obtained from Recaptcha.
   'recaptcha.public-key'        => null,
 
   // Your Recaptcha private key, obtained from Recaptcha.
   'recaptcha.private-key'       => null,
 
 
 // -- Misc ------------------------------------------------------------------ //
 
   // This is hashed with other inputs to generate CSRF tokens. If you want, you
   // can change it to some other string which is unique to your install. This
   // will make your install more secure in a vague, mostly theoretical way. But
   // it will take you like 3 seconds of mashing on your keyboard to set it up so
   // you might as well.
   'phabricator.csrf-key'        => '0b7ec0592e0a2829d8b71df2fa269b2c6172eca3',
 
   // This is hashed with other inputs to generate mail tokens. If you want, you
   // can change it to some other string which is unique to your install. In
   // particular, you will want to do this if you accidentally send a bunch of
   // mail somewhere you shouldn't have, to invalidate all old reply-to
   // addresses.
   'phabricator.mail-key'        => '5ce3e7e8787f6e40dfae861da315a5cdf1018f12',
 
   // Version string displayed in the footer. You can generate this value from
   // Git log or from the current date in the deploy with a script like this:
   //
   // git log -n1 --pretty=%h > version.txt
   //
   // You can then use this generated value like this:
   //
   // 'phabricator.version' =>
   //   file_get_contents(dirname(__FILE__).'/version.txt'),
   'phabricator.version'         => 'UNSTABLE',
 
   // PHP requires that you set a timezone in your php.ini before using date
   // functions, or it will emit a warning. If this isn't possible (for instance,
   // because you are using HPHP) you can set some valid constant for
   // date_default_timezone_set() here and Phabricator will set it on your
   // behalf, silencing the warning.
   'phabricator.timezone'        => null,
 
   // When unhandled exceptions occur, stack traces are hidden by default.
   // You can enable traces for development to make it easier to debug problems.
   'phabricator.show-stack-traces' => false,
 
   // Shows an error callout if a page generated PHP errors, warnings or notices.
   // This makes it harder to miss problems while developing Phabricator.
   'phabricator.show-error-callout' => false,
 
   // When users write comments which have URIs, they'll be automatically linked
   // if the protocol appears in this set. This whitelist is primarily to prevent
   // security issues like javascript:// URIs.
   'uri.allowed-protocols' => array(
     'http'  => true,
     'https' => true,
   ),
 
   // Tokenizers are UI controls which let the user select other users, email
   // addresses, project names, etc., by typing the first few letters and having
   // the control autocomplete from a list. They can load their data in two ways:
   // either in a big chunk up front, or as the user types. By default, the data
   // is loaded in a big chunk. This is simpler and performs better for small
   // datasets. However, if you have a very large number of users or projects,
   // (in the ballpark of more than a thousand), loading all that data may become
   // slow enough that it's worthwhile to query on demand instead. This makes
   // the typeahead slightly less responsive but overall performance will be much
   // better if you have a ton of stuff. You can figure out which setting is
   // best for your install by changing this setting and then playing with a
   // user tokenizer (like the user selectors in Maniphest or Differential) and
   // seeing which setting loads faster and feels better.
   'tokenizer.ondemand'          => false,
 
   // By default, Phabricator includes some silly nonsense in the UI, such as
   // a submit button called "Clowncopterize" in Differential and a call to
   // "Leap Into Action". If you'd prefer more traditional UI strings like
   // "Submit", you can set this flag to disable most of the jokes and easter
   // eggs.
   'phabricator.serious-business' => false,
 
 
 // -- Files ----------------------------------------------------------------- //
 
   // Lists which uploaded file types may be viewed in the browser. If a file
   // has a mime type which does not appear in this list, it will always be
   // downloaded instead of displayed. This is mainly a usability
   // consideration, since browsers tend to freak out when viewing enormous
   // binary files.
   //
   // The keys in this array are viewable mime types; the values are the mime
   // types they will be delivered as when they are viewed in the browser.
   //
   // IMPORTANT: Configure 'security.alternate-file-domain' above! Your install
   // is NOT safe if it is left unconfigured.
   'files.viewable-mime-types' => array(
     'image/jpeg'  => 'image/jpeg',
     'image/jpg'   => 'image/jpg',
     'image/png'   => 'image/png',
     'image/gif'   => 'image/gif',
     'text/plain'  => 'text/plain; charset=utf-8',
     'text/x-diff' => 'text/plain; charset=utf-8',
 
     // ".ico" favicon files, which have mime type diversity. See:
     // http://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type
     'image/x-ico'               => 'image/x-icon',
     'image/x-icon'              => 'image/x-icon',
     'image/vnd.microsoft.icon'  => 'image/x-icon',
   ),
 
   // List of mime types which can be used as the source for an <img /> tag.
   // This should be a subset of 'files.viewable-mime-types' and exclude files
   // like text.
   'files.image-mime-types' => array(
     'image/jpeg'                => true,
     'image/jpg'                 => true,
     'image/png'                 => true,
     'image/gif'                 => true,
     'image/x-ico'               => true,
     'image/x-icon'              => true,
     'image/vnd.microsoft.icon'  => true,
   ),
 
 // -- Storage --------------------------------------------------------------- //
 
   // Phabricator allows users to upload files, and can keep them in various
   // storage engines. This section allows you to configure which engines
   // Phabricator will use, and how it will use them.
 
   // The largest filesize Phabricator will store in the MySQL BLOB storage
   // engine, which just uses a database table to store files. While this isn't a
   // best practice, it's really easy to set up. Set this to 0 to disable use of
   // the MySQL blob engine.
   'storage.mysql-engine.max-size' => 1000000,
 
   // Phabricator provides a local disk storage engine, which just writes files
   // to some directory on local disk. The webserver must have read/write
   // permissions on this directory. This is straightforward and suitable for
   // most installs, but will not scale past one web frontend unless the path
   // is actually an NFS mount, since you'll end up with some of the files
   // written to each web frontend and no way for them to share. To use the
   // local disk storage engine, specify the path to a directory here. To
   // disable it, specify null.
   'storage.local-disk.path'       => null,
 
   // If you want to store files in Amazon S3, specify an AWS access and secret
   // key here and a bucket name below.
   'amazon-s3.access-key'          =>  null,
   'amazon-s3.secret-key'          =>  null,
 
   // To use a custom endpoint, specify it here. Normally, you do not need to
   // configure this.
   'amazon-s3.endpoint'            =>  null,
 
   // Set this to a valid Amazon S3 bucket to store files there. You must also
   // configure S3 access keys above.
   'storage.s3.bucket'             => null,
 
   // Phabricator uses a storage engine selector to choose which storage engine
   // to use when writing file data. If you add new storage engines or want to
   // provide very custom rules (e.g., write images to one storage engine and
   // other files to a different one), you can provide an alternate
   // implementation here. The default engine will use choose MySQL, Local Disk,
   // and S3, in that order, if they have valid configurations above and a file
   // fits within configured limits.
   'storage.engine-selector' => 'PhabricatorDefaultFileStorageEngineSelector',
 
   // Set the size of the largest file a user may upload. This is used to render
   // text like "Maximum file size: 10MB" on interfaces where users can upload
   // files, and files larger than this size will be rejected.
   //
   // Specify this limit in bytes, or using a "K", "M", or "G" suffix.
   //
   // NOTE: Setting this to a large size is NOT sufficient to allow users to
   // upload large files. You must also configure a number of other settings. To
   // configure file upload limits, consult the article "Configuring File Upload
   // Limits" in the documentation. Once you've configured some limit across all
   // levels of the server, you can set this limit to an appropriate value and
   // the UI will then reflect the actual configured limit.
   'storage.upload-size-limit'   => null,
 
   // Phabricator puts databases in a namespace, which defualts to "phabricator"
   // -- for instance, the Differential database is named
   // "phabricator_differential" by default. You can change this namespace if you
   // want. Normally, you should not do this unless you are developing
   // Phabricator and using namespaces to separate multiple sandbox datasets.
   'storage.default-namespace'    => 'phabricator',
 
 
 // -- Search ---------------------------------------------------------------- //
 
   // Phabricator supports Elastic Search; to use it, specify a host like
   // 'http://elastic.example.com:9200/' here.
   'search.elastic.host'     => null,
 
   // Phabricator uses a search engine selector to choose which search engine
   // to use when indexing and reconstructing documents, and when executing
   // queries. You can override the engine selector to provide a new selector
   // class which can select some custom engine you implement, if you want to
   // store your documents in some search engine which does not have default
   // support.
   'search.engine-selector'  => 'PhabricatorDefaultSearchEngineSelector',
 
 
 // -- Differential ---------------------------------------------------------- //
 
   'differential.revision-custom-detail-renderer'  => null,
 
   // Array for custom remarkup rules. The array should have a list of
   // class names of classes that extend PhutilRemarkupRule
   'differential.custom-remarkup-rules' => null,
 
   // Array for custom remarkup block rules. The array should have a list of
   // class names of classes that extend PhutilRemarkupEngineBlockRule
   'differential.custom-remarkup-block-rules' => null,
 
   // List of file regexps where whitespace is meaningful and should not
   // use 'ignore-all' by default
   'differential.whitespace-matters' => array(
     '/\.py$/',
     '/\.l?hs$/',
   ),
 
   'differential.field-selector' => 'DifferentialDefaultFieldSelector',
 
   // Differential can show "Host" and "Path" fields on revisions, with
   // information about the machine and working directory where the
   // change came from. These fields are disabled by default because they may
   // occasionally have sensitive information; you can set this to true to
   // enable them.
   'differential.show-host-field'  => false,
 
   // Differential has a required "Test Plan" field by default, which requires
   // authors to fill out information about how they verified the correctness of
   // their changes when sending code for review. If you'd prefer not to use
   // this field, you can disable it here. You can also make it optional
   // (instead of required) below.
   'differential.show-test-plan-field' => true,
 
   // Differential has a required "Test Plan" field by default. You can make it
   // optional by setting this to false. You can also completely remove it above,
   // if you prefer.
   'differential.require-test-plan-field' => true,
 
   // If you set this to true, users can "!accept" revisions via email (normally,
   // they can take other actions but can not "!accept"). This action is disabled
   // by default because email authentication can be configured to be very weak,
   // and, socially, email "!accept" is kind of sketchy and implies revisions may
   // not actually be receiving thorough review.
   'differential.enable-email-accept' => false,
 
   // If you set this to true, users won't need to login to view differential
   // revisions.  Anonymous users will have read-only access and won't be able to
   // interact with the revisions.
   'differential.anonymous-access' => false,
 
   // If you set this to true, revision author email address information will
   // be exposed in Conduit. This is useful for Arcanist.
   //
   // For example, consider the "arc patch DX" workflow which needs to ask
   // Differential for the revision DX. This data often should contain
   // the author's email address, eg "George Washington
   // <gwashinton@example.com>" when DX is a git or mercurial revision. If this
   // option is false, Differential defaults to the best it can, something like
   // "George Washington" or "gwashington".
   'differential.expose-emails-prudently' => false,
 
   // List of file regexps that should be treated as if they are generated by
   // an automatic process, and thus get hidden by default in differential.
   'differential.generated-paths' => array(
     // '/config\.h$/',
     // '#/autobuilt/#',
   ),
 
   // If you set this to true, users can accept their own revisions.  This action
   // is disabled by default because it's most likely not a behavior you want,
   // but it proves useful if you are working alone on a project and want to make
   // use of all of differential's features.
   'differential.allow-self-accept' => false,
 
   // If you set this to true, any user can close any revision so long as it has
   // been accepted. This can be useful depending on your development model. For
   // example, github-style pull requests where the reviewer is often the
   // actual committer can benefit from turning this option to true. If false,
   // only the submitter can close a revision.
   'differential.always-allow-close' => false,
 
   // Revisions newer than this number of days are marked as fresh in Action
   // Required and Revisions Waiting on You views. Only work days (not weekends
   // and holidays) are included. Set to 0 to disable this feature.
   'differential.days-fresh' => 1,
 
   // Similar to 'differential.days-fresh' but marks stale revisions. If the
   // revision is even older than it is marked as old.
   'differential.days-stale' => 3,
 
 // -- Repositories ---------------------------------------------------------- //
 
   // The default location in which to store local copies of repositories.
   // Anything stored in this directory will be assumed to be under the
   // control of phabricator, which means that Phabricator will try to do some
   // maintenance on working copies if there are problems (such as a change
   // to the remote origin url). This maintenance may include completely
   // removing (and recloning) anything in this directory.
   //
   // When set to null, this option is ignored (i.e. Phabricator will not fully
   // control any working copies).
   'repository.default-local-path' => null,
 
 // -- Maniphest ------------------------------------------------------------- //
 
   'maniphest.enabled' => true,
 
   // Array of custom fields for Maniphest tasks. For details on adding custom
   // fields to Maniphest, see "Maniphest User Guide: Adding Custom Fields".
   'maniphest.custom-fields' => array(),
 
   // Class which drives custom field construction. See "Maniphest User Guide:
   // Adding Custom Fields" in the documentation for more information.
   'maniphest.custom-task-extensions-class' => 'ManiphestDefaultTaskExtensions',
 
   // What should the default task priority be in create flows?
   // See the constants in @{class:ManiphestTaskPriority} for valid values.
   // Defaults to "needs triage".
   'maniphest.default-priority' => 90,
 
 // -- Phriction ------------------------------------------------------------- //
 
   'phriction.enabled' => true,
 
 // -- Phame ----------------------------------------------------------------- //
 
   // Should Phame users have Disqus comment widget, and if so what's the
   // website shortname to use? For example, secure.phabricator.org uses
   // "phabricator", which we registered with Disqus. If you aren't familiar
   // with Disqus, see:
   // Disqus quick start guide - http://docs.disqus.com/help/4/
   // Information on shortnames - http://docs.disqus.com/help/68/
   'disqus.shortname'            => null,
 
   // Directories to look for Phame skins inside of.
   'phame.skins' => array(
     'externals/skins/',
   ),
 
 // -- Remarkup -------------------------------------------------------------- //
 
   // If you enable this, linked YouTube videos will be embeded inline. This has
   // mild security implications (you'll leak referrers to YouTube) and is pretty
   // silly (but sort of awesome).
   'remarkup.enable-embedded-youtube' => false,
 
 
 // -- Cache ----------------------------------------------------------------- //
 
   // Set this to false to disable the use of gzdeflate()-based compression in
   // some caches. This may give you less performant (but more debuggable)
   // caching.
   'cache.enable-deflate' => true,
 
 // -- Garbage Collection ---------------------------------------------------- //
 
   // Phabricator generates various logs and caches in the database which can
   // be garbage collected after a while to make the total data size more
   // manageable. To run garbage collection, launch a
   // PhabricatorGarbageCollector daemon.
 
   // These 'ttl' keys configure how much old data the GC daemon keeps around.
   // Objects older than the ttl will be collected. Set any value to 0 to store
   // data indefinitely.
 
   'gcdaemon.ttl.herald-transcripts'         => 30 * (24 * 60 * 60),
   'gcdaemon.ttl.daemon-logs'                =>  7 * (24 * 60 * 60),
   'gcdaemon.ttl.differential-parse-cache'   => 14 * (24 * 60 * 60),
   'gcdaemon.ttl.markup-cache'               => 30 * (24 * 60 * 60),
   'gcdaemon.ttl.task-archive'               => 14 * (24 * 60 * 60),
   'gcdaemon.ttl.general-cache'              => 30 * (24 * 60 * 60),
 
 
 // -- Feed ------------------------------------------------------------------ //
 
   // If you set this to true, you can embed Phabricator activity feeds in other
   // pages using iframes. These feeds are completely public, and a login is not
   // required to view them! This is intended for things like open source
   // projects that want to expose an activity feed on the project homepage.
   //
   // NOTE: You must also set `policy.allow-public` to true for this setting
   // to work properly.
   'feed.public' => false,
 
   // If you set this to a list of http URIs, when a feed story is published a
   // task will be created for each uri that posts the story data to the uri.
   // Daemons automagically retry failures 100 times, waiting $fail_count * 60s
   // between each subsequent failure. Be sure to keep the daemon console
   // (/daemon/) open while developing and testing your end points.
   //
   // NOTE: URIs are not validated, the URI must return http status 200 within
   // 30 seconds, and no permission checks are performed.
   'feed.http-hooks' => array(),
 
 // -- Drydock --------------------------------------------------------------- //
 
   // If you want to use Drydock's builtin EC2 Blueprints, configure your AWS
   // EC2 credentials here.
   'amazon-ec2.access-key'   => null,
   'amazon-ec2.secret-key'   => null,
 
 
 // -- Customization --------------------------------------------------------- //
 
   // Paths to additional phutil libraries to load.
   'load-libraries' => array(),
 
   'aphront.default-application-configuration-class' =>
     'AphrontDefaultApplicationConfiguration',
 
   'controller.oauth-registration' =>
     'PhabricatorOAuthDefaultRegistrationController',
 
 
   // Directory that phd (the Phabricator daemon control script) should use to
   // track running daemons.
   'phd.pid-directory' => '/var/tmp/phd/pid',
 
   // Directory that the Phabricator daemons should use to store the log file
   'phd.log-directory' => '/var/tmp/phd/log',
 
   // Number of "TaskMaster" daemons that "phd start" should start. You can
   // raise this if you have a task backlog, or explicitly launch more with
   // "phd launch <N> taskmaster".
   'phd.start-taskmasters' => 4,
 
   // Launch daemons in "verbose" mode by default. This creates a lot of output,
   // but can help debug issues. Daemons launched in debug mode with "phd debug"
   // are always launched in verbose mode. See also 'phd.trace'.
   'phd.verbose' => false,
 
   // Launch daemons in "trace" mode by default. This creates an ENORMOUS amount
   // of output, but can help debug issues. Daemons launched in debug mode with
   // "phd debug" are always launched in trace mdoe. See also 'phd.verbose'.
   'phd.trace' => false,
 
   // Path to custom celerity resource map relative to 'phabricator/src'.
   // See also `scripts/celerity_mapper.php`.
   'celerity.resource-path' => '__celerity_resource_map__.php',
 
   // This value is an input to the hash function when building resource hashes.
   // It has no security value, but if you accidentally poison user caches (by
   // pushing a bad patch or having something go wrong with a CDN, e.g.) you can
   // change this to something else and rebuild the Celerity map to break user
   // caches. Unless you are doing Celerity development, it is exceptionally
   // unlikely that you need to modify this.
   'celerity.resource-hash' => 'd9455ea150622ee044f7931dabfa52aa',
 
   // In a development environment, it is desirable to force static resources
   // (CSS and JS) to be read from disk on every request, so that edits to them
   // appear when you reload the page even if you haven't updated the resource
   // maps. This setting ensures requests will be verified against the state on
   // disk. Generally, you should leave this off in production (caching behavior
   // and performance improve with it off) but turn it on in development. (These
   // settings are the defaults.)
   'celerity.force-disk-reads' => false,
 
   // Minify static resources by removing whitespace and comments. You should
   // enable this in production, but disable it in development.
   'celerity.minify' => false,
 
   // You can respond to various application events by installing listeners,
   // which will receive callbacks when interesting things occur. Specify a list
   // of classes which extend PhabricatorEventListener here.
   'events.listeners'  => array(),
 
 // -- Syntax Highlighting --------------------------------------------------- //
 
   // Phabricator can highlight PHP by default and use Pygments for other
   // languages if enabled. You can provide a custom highlighter engine by
   // extending class PhutilSyntaxHighlighterEngine.
   'syntax-highlighter.engine' => 'PhutilDefaultSyntaxHighlighterEngine',
 
   // If you want syntax highlighting for other languages than PHP then you can
   // install the python package 'Pygments', make sure the 'pygmentize' script is
   //  available in the $PATH of the webserver, and then enable this.
   'pygments.enabled'            => false,
 
   // In places that we display a dropdown to syntax-highlight code,
   // this is where that list is defined.
   // Syntax is 'lexer-name' => 'Display Name',
   'pygments.dropdown-choices' => array(
     'apacheconf' => 'Apache Configuration',
     'bash' => 'Bash Scripting',
     'brainfuck' => 'Brainf*ck',
     'c' => 'C',
     'cpp' => 'C++',
     'css' => 'CSS',
     'd' => 'D',
     'diff' => 'Diff',
     'django' => 'Django Templating',
     'erb' => 'Embedded Ruby/ERB',
     'erlang' => 'Erlang',
     'haskell' => 'Haskell',
     'html' => 'HTML',
     'java' => 'Java',
     'js' => 'Javascript',
     'mysql' => 'MySQL',
     'objc' => 'Objective-C',
     'perl' => 'Perl',
     'php' => 'PHP',
     'rest' => 'reStructuredText',
     'text' => 'Plain Text',
     'python' => 'Python',
     'rainbow' => 'Rainbow',
     'remarkup' => 'Remarkup',
     'ruby' => 'Ruby',
     'xml' => 'XML',
   ),
 
   // This is an override list of regular expressions which allows you to choose
   // what language files are highlighted as. If your projects have certain rules
   // about filenames or use unusual or ambiguous language extensions, you can
   // create a mapping here. This is an ordered dictionary of regular expressions
   // which will be tested against the filename. They should map to either an
   // explicit language as a string value, or a numeric index into the captured
   // groups as an integer.
   'syntax.filemap' => array(
     // Example: Treat all '*.xyz' files as PHP.
     // '@\\.xyz$@' => 'php',
 
     // Example: Treat 'httpd.conf' as 'apacheconf'.
     // '@/httpd\\.conf$@' => 'apacheconf',
 
     // Example: Treat all '*.x.bak' file as '.x'. NOTE: we map to capturing
     // group 1 by specifying the mapping as "1".
     // '@\\.([^.]+)\\.bak$@' => 1,
 
     '@\.arcconfig$@' => 'js',
     '@\.divinerconfig$@' => 'js',
   ),
 
   // Set the default monospaced font style for users who haven't set a custom
   // style.
   'style.monospace' => '10px "Menlo", "Consolas", "Monaco", monospace',
 
 
 // -- Debugging ------------------------------------------------------------- //
 
   // Enable this to change HTTP redirects into normal pages with a link to the
   // redirection target. For example, after you submit a form you'll get a page
   // saying "normally, you'd be redirected...". This is useful to examine
   // service or profiler information on write pathways, or debug redirects. It
   // also makes the UX horrible for normal use, so you should enable it only
   // when debugging.
   //
   // NOTE: This does not currently work for forms with Javascript "workflow",
   // since the redirect happens in Javascript.
   'debug.stop-on-redirect'    => false,
 
   // Set the rate for how often to do sampled profiling. On average, one
   // request for every number of requests specified here will be sampled.
   // Set this value to 0 to completely disable profiling. In a production
   // environment, this value should either be set to 0 (to disable) or to
   // a large number (to sample only a few requests).
   'debug.profile-rate' => 0,
 
 // -- Environment  ---------------------------------------------------------- //
 
   // Phabricator occasionally shells out to other binaries on the server.
   // An example of this is the "pygmentize" command, used to syntax-highlight
   // code written in languages other than PHP. By default, it is assumed that
   // these binaries are in the $PATH of the user running Phabricator (normally
   // 'apache', 'httpd', or 'nobody'). Here you can add extra directories to
   // the $PATH environment variable, for when these binaries are in non-standard
   // locations.
   'environment.append-paths' => array(),
+
+// -- Audit  ---------------------------------------------------------- //
+
+  // Controls whether or not task creator can Close Audits
+  'audit.can-author-close-audit' => false,
 );
diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php
index 9595fc6316..d64a48c8ca 100644
--- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php
+++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php
@@ -1,535 +1,539 @@
 <?php
 
 final class PhabricatorAuditCommentEditor extends PhabricatorEditor {
 
   private $commit;
 
   private $attachInlineComments;
   private $auditors = array();
   private $ccs = array();
 
   public function __construct(PhabricatorRepositoryCommit $commit) {
     $this->commit = $commit;
     return $this;
   }
 
   public function addAuditors(array $auditor_phids) {
     $this->auditors = array_merge($this->auditors, $auditor_phids);
     return $this;
   }
 
   public function addCCs(array $cc_phids) {
     $this->ccs = array_merge($this->ccs, $cc_phids);
     return $this;
   }
 
   public function setAttachInlineComments($attach_inline_comments) {
     $this->attachInlineComments = $attach_inline_comments;
     return $this;
   }
 
   public function addComment(PhabricatorAuditComment $comment) {
 
     $commit = $this->commit;
     $actor = $this->getActor();
 
     $other_comments = id(new PhabricatorAuditComment())->loadAllWhere(
       'targetPHID = %s',
       $commit->getPHID());
 
     $inline_comments = array();
     if ($this->attachInlineComments) {
       $inline_comments = id(new PhabricatorAuditInlineComment())->loadAllWhere(
         'authorPHID = %s AND commitPHID = %s
           AND auditCommentID IS NULL',
         $actor->getPHID(),
         $commit->getPHID());
     }
 
     $comment
       ->setActorPHID($actor->getPHID())
       ->setTargetPHID($commit->getPHID())
       ->save();
 
     $content_blocks = array($comment->getContent());
 
     if ($inline_comments) {
       foreach ($inline_comments as $inline) {
         $inline->setAuditCommentID($comment->getID());
         $inline->save();
         $content_blocks[] = $inline->getContent();
       }
     }
 
     $ccs = $this->ccs;
     $auditors = $this->auditors;
 
     $metadata = $comment->getMetadata();
     $metacc = array();
 
     // Find any "@mentions" in the content blocks.
     $mention_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions(
       $content_blocks);
     if ($mention_ccs) {
       $metacc = idx(
         $metadata,
         PhabricatorAuditComment::METADATA_ADDED_CCS,
         array());
       foreach ($mention_ccs as $cc_phid) {
         $metacc[] = $cc_phid;
       }
     }
 
     if ($metacc) {
       $ccs = array_merge($ccs, $metacc);
     }
 
     // When an actor submits an audit comment, we update all the audit requests
     // they have authority over to reflect the most recent status. The general
     // idea here is that if audit has triggered for, e.g., several packages, but
     // a user owns all of them, they can clear the audit requirement in one go
     // without auditing the commit for each trigger.
 
     $audit_phids = self::loadAuditPHIDsForUser($actor);
     $audit_phids = array_fill_keys($audit_phids, true);
 
     $requests = id(new PhabricatorRepositoryAuditRequest())
       ->loadAllWhere(
         'commitPHID = %s',
         $commit->getPHID());
 
     $action = $comment->getAction();
 
 
     // TODO: We should validate the action, currently we allow anyone to, e.g.,
     // close an audit if they muck with form parameters. I'll followup with this
     // and handle the no-effect cases (e.g., closing and already-closed audit).
 
 
     $actor_is_author = ($actor->getPHID() == $commit->getAuthorPHID());
 
     if ($action == PhabricatorAuditActionConstants::CLOSE) {
+      if (!PhabricatorEnv::getEnvConfig('audit.can-author-close-audit')) {
+          throw new Exception('Cannot Close Audit without enabling'.
+          'audit.can-author-close-audit');
+      }
       // "Close" means wipe out all the concerns.
       $concerned_status = PhabricatorAuditStatusConstants::CONCERNED;
       foreach ($requests as $request) {
         if ($request->getAuditStatus() == $concerned_status) {
           $request->setAuditStatus(PhabricatorAuditStatusConstants::CLOSED);
           $request->save();
         }
       }
     } else if ($action == PhabricatorAuditActionConstants::RESIGN) {
       // "Resign" has unusual rules for writing user rows, only affects the
       // user row (never package/project rows), and always affects the user
       // row (other actions don't, if they were able to affect a package/project
       // row).
       $actor_request = null;
       foreach ($requests as $request) {
         if ($request->getAuditorPHID() == $actor->getPHID()) {
           $actor_request = $request;
           break;
         }
       }
       if (!$actor_request) {
         $actor_request = id(new PhabricatorRepositoryAuditRequest())
           ->setCommitPHID($commit->getPHID())
           ->setAuditorPHID($actor->getPHID())
           ->setAuditReasons(array("Resigned"));
       }
 
       $actor_request
         ->setAuditStatus(PhabricatorAuditStatusConstants::RESIGNED)
         ->save();
 
       $requests[] = $actor_request;
     } else {
       $have_any_requests = false;
       foreach ($requests as $request) {
         if (empty($audit_phids[$request->getAuditorPHID()])) {
           continue;
         }
 
         $request_is_for_actor =
           ($request->getAuditorPHID() == $actor->getPHID());
 
         $have_any_requests = true;
         $new_status = null;
         switch ($action) {
           case PhabricatorAuditActionConstants::COMMENT:
           case PhabricatorAuditActionConstants::ADD_CCS:
           case PhabricatorAuditActionConstants::ADD_AUDITORS:
             // Commenting or adding cc's/auditors doesn't change status.
             break;
           case PhabricatorAuditActionConstants::ACCEPT:
             if (!$actor_is_author || $request_is_for_actor) {
               // When modifying your own commits, you act only on behalf of
               // yourself, not your packages/projects -- the idea being that
               // you can't accept your own commits.
               $new_status = PhabricatorAuditStatusConstants::ACCEPTED;
             }
             break;
           case PhabricatorAuditActionConstants::CONCERN:
             if (!$actor_is_author || $request_is_for_actor) {
               // See above.
               $new_status = PhabricatorAuditStatusConstants::CONCERNED;
             }
             break;
           default:
             throw new Exception("Unknown action '{$action}'!");
         }
         if ($new_status !== null) {
           $request->setAuditStatus($new_status);
           $request->save();
         }
       }
 
       // If the actor has no current authority over any audit trigger, make a
       // new one to represent their audit state.
       if (!$have_any_requests) {
         $new_status = null;
         switch ($action) {
           case PhabricatorAuditActionConstants::COMMENT:
           case PhabricatorAuditActionConstants::ADD_CCS:
           case PhabricatorAuditActionConstants::ADD_AUDITORS:
             $new_status = PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED;
             break;
           case PhabricatorAuditActionConstants::ACCEPT:
             $new_status = PhabricatorAuditStatusConstants::ACCEPTED;
             break;
           case PhabricatorAuditActionConstants::CONCERN:
             $new_status = PhabricatorAuditStatusConstants::CONCERNED;
             break;
           case PhabricatorAuditActionConstants::CLOSE:
             // Impossible to reach this block with 'close'.
           default:
             throw new Exception("Unknown or invalid action '{$action}'!");
         }
 
         $request = id(new PhabricatorRepositoryAuditRequest())
           ->setCommitPHID($commit->getPHID())
           ->setAuditorPHID($actor->getPHID())
           ->setAuditStatus($new_status)
           ->setAuditReasons(array("Voluntary Participant"))
           ->save();
         $requests[] = $request;
       }
     }
 
     $requests_by_auditor = mpull($requests, null, 'getAuditorPHID');
     $requests_phids = array_keys($requests_by_auditor);
 
     $ccs = array_diff($ccs, $requests_phids);
     $auditors = array_diff($auditors, $requests_phids);
 
     if ($action == PhabricatorAuditActionConstants::ADD_CCS) {
       if ($ccs) {
         $metadata[PhabricatorAuditComment::METADATA_ADDED_CCS] = $ccs;
         $comment->setMetaData($metadata);
       } else {
         $comment->setAction(PhabricatorAuditActionConstants::COMMENT);
       }
     }
 
     if ($action == PhabricatorAuditActionConstants::ADD_AUDITORS) {
       if ($auditors) {
         $metadata[PhabricatorAuditComment::METADATA_ADDED_AUDITORS]
           = $auditors;
         $comment->setMetaData($metadata);
       } else {
         $comment->setAction(PhabricatorAuditActionConstants::COMMENT);
       }
     }
 
     $comment->save();
 
     if ($auditors) {
       foreach ($auditors as $auditor_phid) {
         $audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED;
         $requests[] = id (new PhabricatorRepositoryAuditRequest())
           ->setCommitPHID($commit->getPHID())
           ->setAuditorPHID($auditor_phid)
           ->setAuditStatus($audit_requested)
           ->setAuditReasons(
             array('Added by ' . $actor->getUsername()))
           ->save();
       }
     }
 
     if ($ccs) {
       foreach ($ccs as $cc_phid) {
         $audit_cc = PhabricatorAuditStatusConstants::CC;
         $requests[] = id (new PhabricatorRepositoryAuditRequest())
           ->setCommitPHID($commit->getPHID())
           ->setAuditorPHID($cc_phid)
           ->setAuditStatus($audit_cc)
           ->setAuditReasons(
             array('Added by ' . $actor->getUsername()))
           ->save();
       }
     }
 
     $commit->updateAuditStatus($requests);
     $commit->save();
 
     $feed_dont_publish_phids = array();
     foreach ($requests as $request) {
       $status = $request->getAuditStatus();
       switch ($status) {
       case PhabricatorAuditStatusConstants::RESIGNED:
       case PhabricatorAuditStatusConstants::NONE:
       case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED:
       case PhabricatorAuditStatusConstants::CC:
         $feed_dont_publish_phids[$request->getAuditorPHID()] = 1;
         break;
       default:
         unset($feed_dont_publish_phids[$request->getAuditorPHID()]);
         break;
       }
     }
     $feed_dont_publish_phids = array_keys($feed_dont_publish_phids);
 
     $feed_phids = array_diff($requests_phids, $feed_dont_publish_phids);
     $this->publishFeedStory($comment, $feed_phids);
 
     id(new PhabricatorSearchIndexer())
       ->indexDocumentByPHID($commit->getPHID());
 
     $this->sendMail($comment, $other_comments, $inline_comments, $requests);
   }
 
 
   /**
    * Load the PHIDs for all objects the user has the authority to act as an
    * audit for. This includes themselves, and any packages they are an owner
    * of.
    */
   public static function loadAuditPHIDsForUser(PhabricatorUser $user) {
     $phids = array();
 
     // TODO: This method doesn't really use the right viewer, but in practice we
     // never issue this query of this type on behalf of another user and are
     // unlikely to do so in the future. This entire method should be refactored
     // into a Query class, however, and then we should use a proper viewer.
 
     // The user can audit on their own behalf.
     $phids[$user->getPHID()] = true;
 
     $owned_packages = id(new PhabricatorOwnersPackageQuery())
       ->setViewer($user)
       ->withOwnerPHIDs(array($user->getPHID()))
       ->execute();
     foreach ($owned_packages as $package) {
       $phids[$package->getPHID()] = true;
     }
 
     // The user can audit on behalf of all projects they are a member of.
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withMemberPHIDs(array($user->getPHID()))
       ->execute();
     foreach ($projects as $project) {
       $phids[$project->getPHID()] = true;
     }
 
     return array_keys($phids);
   }
 
   private function publishFeedStory(
     PhabricatorAuditComment $comment,
     array $more_phids) {
 
     $commit = $this->commit;
     $actor = $this->getActor();
 
     $related_phids = array_merge(
       array(
         $actor->getPHID(),
         $commit->getPHID(),
       ),
       $more_phids);
 
     id(new PhabricatorFeedStoryPublisher())
       ->setRelatedPHIDs($related_phids)
       ->setStoryAuthorPHID($actor->getPHID())
       ->setStoryTime(time())
       ->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_AUDIT)
       ->setStoryData(
         array(
           'commitPHID'    => $commit->getPHID(),
           'action'        => $comment->getAction(),
           'content'       => $comment->getContent(),
         ))
       ->publish();
   }
 
   private function sendMail(
     PhabricatorAuditComment $comment,
     array $other_comments,
     array $inline_comments,
     array $requests) {
 
     assert_instances_of($other_comments, 'PhabricatorAuditComment');
     assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
 
     $commit = $this->commit;
 
     $data = $commit->loadCommitData();
     $summary = $data->getSummary();
 
     $commit_phid = $commit->getPHID();
     $phids = array($commit_phid);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $handle = $handles[$commit_phid];
 
     $name = $handle->getName();
 
     $map = array(
       PhabricatorAuditActionConstants::CONCERN  => 'Raised Concern',
       PhabricatorAuditActionConstants::ACCEPT   => 'Accepted',
       PhabricatorAuditActionConstants::RESIGN   => 'Resigned',
       PhabricatorAuditActionConstants::CLOSE    => 'Closed',
       PhabricatorAuditActionConstants::ADD_CCS => 'Added CCs',
       PhabricatorAuditActionConstants::ADD_AUDITORS => 'Added Auditors',
     );
     $verb = idx($map, $comment->getAction(), 'Commented On');
 
     $reply_handler = self::newReplyHandlerForCommit($commit);
 
     $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
 
     $repository = id(new PhabricatorRepository())
       ->load($commit->getRepositoryID());
     $threading = self::getMailThreading($repository, $commit);
     list($thread_id, $thread_topic) = $threading;
 
     $body       = $this->renderMailBody(
       $comment,
       "{$name}: {$summary}",
       $handle,
       $reply_handler,
       $inline_comments);
 
     $email_to = array();
     $email_cc = array();
 
     $author_phid = $data->getCommitDetail('authorPHID');
     if ($author_phid) {
       $email_to[] = $author_phid;
     }
 
     foreach ($other_comments as $other_comment) {
       $email_cc[$other_comment->getActorPHID()] = true;
     }
 
     foreach ($requests as $request) {
       if ($request->getAuditStatus() == PhabricatorAuditStatusConstants::CC) {
         $email_cc[$request->getAuditorPHID()] = true;
       } else if ($request->getAuditStatus() ==
                  PhabricatorAuditStatusConstants::RESIGNED) {
         unset($email_cc[$request->getAuditorPHID()]);
       }
     }
     $email_cc = array_keys($email_cc);
 
     $phids = array_merge($email_to, $email_cc);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
 
     // NOTE: Always set $is_new to false, because the "first" mail in the
     // thread is the Herald notification of the commit.
     $is_new = false;
 
     $template = id(new PhabricatorMetaMTAMail())
       ->setSubject("{$name}: {$summary}")
       ->setSubjectPrefix($prefix)
       ->setVarySubjectPrefix("[{$verb}]")
       ->setFrom($comment->getActorPHID())
       ->setThreadID($thread_id, $is_new)
       ->addHeader('Thread-Topic', $thread_topic)
       ->setRelatedPHID($commit->getPHID())
       ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs())
       ->setIsBulk(true)
       ->setBody($body);
 
     $mails = $reply_handler->multiplexMail(
       $template,
       array_select_keys($handles, $email_to),
       array_select_keys($handles, $email_cc));
 
     foreach ($mails as $mail) {
       $mail->saveAndSend();
     }
   }
 
   public static function getMailThreading(
     PhabricatorRepository $repository,
     PhabricatorRepositoryCommit $commit) {
 
     return array(
       'diffusion-audit-'.$commit->getPHID(),
       'Commit r'.$repository->getCallsign().$commit->getCommitIdentifier(),
     );
   }
 
   public static function newReplyHandlerForCommit($commit) {
     $reply_handler = PhabricatorEnv::newObjectFromConfig(
       'metamta.diffusion.reply-handler');
     $reply_handler->setMailReceiver($commit);
     return $reply_handler;
   }
 
   private function renderMailBody(
     PhabricatorAuditComment $comment,
     $cname,
     PhabricatorObjectHandle $handle,
     PhabricatorMailReplyHandler $reply_handler,
     array $inline_comments) {
     assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
 
     $commit = $this->commit;
     $actor = $this->getActor();
     $name = $actor->getUsername();
 
     $verb = PhabricatorAuditActionConstants::getActionPastTenseVerb(
       $comment->getAction());
 
     $body = new PhabricatorMetaMTAMailBody();
     $body->addRawSection("{$name} {$verb} commit {$cname}.");
     $body->addRawSection($comment->getContent());
 
     if ($inline_comments) {
       $block = array();
 
       $path_map = id(new DiffusionPathQuery())
         ->withPathIDs(mpull($inline_comments, 'getPathID'))
         ->execute();
       $path_map = ipull($path_map, 'path', 'id');
 
       foreach ($inline_comments as $inline) {
         $path = idx($path_map, $inline->getPathID());
         if ($path === null) {
           continue;
         }
 
         $start = $inline->getLineNumber();
         $len   = $inline->getLineLength();
         if ($len) {
           $range = $start.'-'.($start + $len);
         } else {
           $range = $start;
         }
 
         $content = $inline->getContent();
         $block[] = "{$path}:{$range} {$content}";
       }
 
       $body->addTextSection(pht('INLINE COMMENTS'), implode("\n", $block));
     }
 
     $body->addTextSection(
       pht('COMMIT'),
       PhabricatorEnv::getProductionURI($handle->getURI()));
     $body->addReplySection($reply_handler->getReplyHandlerInstructions());
 
     return $body->render();
   }
 
 }
diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
index 4b3805dbb1..d002498baa 100644
--- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
+++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php
@@ -1,61 +1,71 @@
 <?php
 
 final class PhabricatorDiffusionConfigOptions
   extends PhabricatorApplicationConfigOptions {
 
   public function getName() {
     return pht('Diffusion');
   }
 
   public function getDescription() {
     return pht('Configure Diffusion repository browsing.');
   }
 
   public function getOptions() {
     return array(
       $this->newOption(
         'metamta.diffusion.subject-prefix',
         'string',
         '[Diffusion]')
         ->setDescription(pht('Subject prefix for Diffusion mail.')),
       $this->newOption(
         'metamta.diffusion.reply-handler-domain',
         'string',
         null)
         ->setDescription(
           pht(
             'See {{metamta.maniphest.reply-handler}}. This does the same '.
             'thing, but affects Diffusion.')),
       $this->newOption(
         'metamta.diffusion.reply-handler',
         'class',
         'PhabricatorAuditReplyHandler')
         ->setBaseClass('PhabricatorMailReplyHandler')
         ->setDescription(pht('Override mail reply handler class.')),
       $this->newOption(
         'metamta.diffusion.attach-patches',
         'bool',
         false)
         ->setBoolOptions(
           array(
             pht("Attach Patches"),
             pht("Do Not Attach Patches"),
           ))
         ->setDescription(pht(
           'Set this to true if you want patches to be attached to commit '.
           'notifications from Diffusion.')),
       $this->newOption('metamta.diffusion.inline-patches', 'int', 0)
         ->setSummary(pht('Include patches in Diffusion mail as body text.'))
         ->setDescription(
           pht(
             'To include patches in Diffusion email bodies, set this to a '.
             'positive integer. Patches will be inlined if they are at most '.
             'that many lines. By default, patches are not inlined.')),
       $this->newOption('metamta.diffusion.byte-limit', 'int', 1024 * 1024)
         ->setDescription(pht('Hard byte limit on including patches in email.')),
       $this->newOption('metamta.diffusion.time-limit', 'int', 60)
         ->setDescription(pht('Hard time limit on generating patches.')),
+      $this->newOption(
+        'audit.can-author-close-audit',
+        'bool',
+        false)
+        ->setBoolOptions(
+          array(
+            pht("Enable Closing Audits"),
+            pht("Disable Closing Audits"),
+          ))
+        ->setDescription(pht('Controls whether Author can Close Audits.')),
     );
   }
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index 854d9a04c4..1f81f013d7 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,917 +1,918 @@
 <?php
 
 final class DiffusionCommitController extends DiffusionController {
 
   const CHANGES_LIMIT = 100;
 
   private $auditAuthorityPHIDs;
   private $highlightedAudits;
 
   public function willProcessRequest(array $data) {
     // This controller doesn't use blob/path stuff, just pass the dictionary
     // in directly instead of using the AphrontRequest parsing mechanism.
     $drequest = DiffusionRequest::newFromDictionary($data);
     $this->diffusionRequest = $drequest;
   }
 
   public function processRequest() {
     $drequest = $this->getDiffusionRequest();
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($request->getStr('diff')) {
       return $this->buildRawDiffResponse($drequest);
     }
 
     $callsign = $drequest->getRepository()->getCallsign();
 
     $content = array();
     $repository = $drequest->getRepository();
     $commit = $drequest->loadCommit();
 
     if (!$commit) {
       $query = DiffusionExistsQuery::newFromDiffusionRequest($drequest);
       $exists = $query->loadExistentialData();
       if (!$exists) {
         return new Aphront404Response();
       }
       return $this->buildStandardPageResponse(
         id(new AphrontErrorView())
         ->setTitle('Error displaying commit.')
         ->appendChild('Failed to load the commit because the commit has not '.
                       'been parsed yet.'),
           array('title' => 'Commit Still Parsing')
         );
     }
 
     $commit_data = $drequest->loadCommitData();
     $commit->attachCommitData($commit_data);
 
     $top_anchor = id(new PhabricatorAnchorView())
       ->setAnchorName('top')
       ->setNavigationMarker(true);
 
     $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
     if ($is_foreign) {
       $subpath = $commit_data->getCommitDetail('svn-subpath');
 
       $error_panel = new AphrontErrorView();
       $error_panel->setTitle('Commit Not Tracked');
       $error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
       $error_panel->appendChild(
         "This Diffusion repository is configured to track only one ".
         "subdirectory of the entire Subversion repository, and this commit ".
         "didn't affect the tracked subdirectory ('".
         phutil_escape_html($subpath)."'), so no information is available.");
       $content[] = $error_panel;
       $content[] = $top_anchor;
     } else {
       $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
 
       require_celerity_resource('diffusion-commit-view-css');
       require_celerity_resource('phabricator-remarkup-css');
 
       $parent_query = DiffusionCommitParentsQuery::newFromDiffusionRequest(
         $drequest);
 
       $headsup_view = id(new PhabricatorHeaderView())
         ->setHeader('Commit Detail');
 
       $headsup_actions = $this->renderHeadsupActionList($commit, $repository);
 
       $commit_properties = $this->loadCommitProperties(
         $commit,
         $commit_data,
         $parent_query->loadParents()
       );
       $property_list = id(new PhabricatorPropertyListView())
         ->setHasKeyboardShortcuts(true);
       foreach ($commit_properties as $key => $value) {
         $property_list->addProperty($key, $value);
       }
 
       $property_list->addTextContent(
         '<div class="diffusion-commit-message phabricator-remarkup">'.
         $engine->markupText($commit_data->getCommitMessage()).
         '</div>'
       );
 
       $content[] = $top_anchor;
       $content[] = $headsup_view;
       $content[] = $headsup_actions;
       $content[] = $property_list;
     }
 
     $query = new PhabricatorAuditQuery();
     $query->withCommitPHIDs(array($commit->getPHID()));
     $audit_requests = $query->execute();
 
     $this->auditAuthorityPHIDs =
       PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
 
     $content[] = $this->buildAuditTable($commit, $audit_requests);
     $content[] = $this->buildComments($commit);
 
     $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
       $drequest);
     $changes = $change_query->loadChanges();
 
     $content[] = $this->buildMergesTable($commit);
 
     $owners_paths = array();
     if ($this->highlightedAudits) {
       $packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
         'phid IN (%Ls)',
         mpull($this->highlightedAudits, 'getAuditorPHID'));
       if ($packages) {
         $owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
           'repositoryPHID = %s AND packageID IN (%Ld)',
           $repository->getPHID(),
           mpull($packages, 'getID'));
       }
     }
 
     $change_table = new DiffusionCommitChangeTableView();
     $change_table->setDiffusionRequest($drequest);
     $change_table->setPathChanges($changes);
     $change_table->setOwnersPaths($owners_paths);
 
     $count = count($changes);
 
     $bad_commit = null;
     if ($count == 0) {
       $bad_commit = queryfx_one(
         id(new PhabricatorRepository())->establishConnection('r'),
         'SELECT * FROM %T WHERE fullCommitName = %s',
         PhabricatorRepository::TABLE_BADCOMMIT,
         'r'.$callsign.$commit->getCommitIdentifier());
     }
 
     $pane_id = null;
     if ($bad_commit) {
       $error_panel = new AphrontErrorView();
       $error_panel->setTitle('Bad Commit');
       $error_panel->appendChild(
         phutil_escape_html($bad_commit['description']));
 
       $content[] = $error_panel;
     } else if ($is_foreign) {
       // Don't render anything else.
     } else if (!count($changes)) {
       $no_changes = new AphrontErrorView();
       $no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
       $no_changes->setTitle('Not Yet Parsed');
       // TODO: This can also happen with weird SVN changes that don't do
       // anything (or only alter properties?), although the real no-changes case
       // is extremely rare and might be impossible to produce organically. We
       // should probably write some kind of "Nothing Happened!" change into the
       // DB once we parse these changes so we can distinguish between
       // "not parsed yet" and "no changes".
       $no_changes->appendChild(
         "This commit hasn't been fully parsed yet (or doesn't affect any ".
         "paths).");
       $content[] = $no_changes;
     } else {
       $change_panel = new AphrontPanelView();
       $change_panel->setHeader("Changes (".number_format($count).")");
       $change_panel->setID('toc');
 
       if ($count > self::CHANGES_LIMIT) {
         $show_all_button = phutil_render_tag(
           'a',
           array(
             'class'   => 'button green',
             'href'    => '?show_all=true',
           ),
           phutil_escape_html('Show All Changes'));
         $warning_view = id(new AphrontErrorView())
           ->setSeverity(AphrontErrorView::SEVERITY_WARNING)
           ->setTitle('Very Large Commit')
           ->appendChild(
             "<p>This commit is very large. Load each file individually.</p>");
 
         $change_panel->appendChild($warning_view);
         $change_panel->addButton($show_all_button);
       }
 
       $change_panel->appendChild($change_table);
       $change_panel->setNoBackground();
 
       $content[] = $change_panel;
 
       $changesets = DiffusionPathChange::convertToDifferentialChangesets(
         $changes);
 
       $vcs = $repository->getVersionControlSystem();
       switch ($vcs) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $vcs_supports_directory_changes = true;
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $vcs_supports_directory_changes = false;
           break;
         default:
           throw new Exception("Unknown VCS.");
       }
 
       $references = array();
       foreach ($changesets as $key => $changeset) {
         $file_type = $changeset->getFileType();
         if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
           if (!$vcs_supports_directory_changes) {
             unset($changesets[$key]);
             continue;
           }
         }
 
         $references[$key] = $drequest->generateURI(
           array(
             'action' => 'rendering-ref',
             'path'   => $changeset->getFilename(),
           ));
       }
 
       // TODO: Some parts of the views still rely on properties of the
       // DifferentialChangeset. Make the objects ephemeral to make sure we don't
       // accidentally save them, and then set their ID to the appropriate ID for
       // this application (the path IDs).
       $path_ids = array_flip(mpull($changes, 'getPath'));
       foreach ($changesets as $changeset) {
         $changeset->makeEphemeral();
         $changeset->setID($path_ids[$changeset->getFilename()]);
       }
 
       if ($count <= self::CHANGES_LIMIT) {
         $visible_changesets = $changesets;
       } else {
         $visible_changesets = array();
         $inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
           'commitPHID = %s AND (auditCommentID IS NOT NULL OR authorPHID = %s)',
           $commit->getPHID(),
           $user->getPHID());
         $path_ids = mpull($inlines, null, 'getPathID');
         foreach ($changesets as $key => $changeset) {
           if (array_key_exists($changeset->getID(), $path_ids)) {
             $visible_changesets[$key] = $changeset;
           }
         }
       }
 
       $change_list_title = DiffusionView::nameCommit(
         $repository,
         $commit->getCommitIdentifier()
       );
       $change_list = new DifferentialChangesetListView();
       $change_list->setTitle($change_list_title);
       $change_list->setChangesets($changesets);
       $change_list->setVisibleChangesets($visible_changesets);
       $change_list->setRenderingReferences($references);
       $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
       $change_list->setRepository($repository);
       $change_list->setUser($user);
       // pick the first branch for "Browse in Diffusion" View Option
       $branches     = $commit_data->getCommitDetail('seenOnBranches', array());
       $first_branch = reset($branches);
       $change_list->setBranch($first_branch);
 
       $change_list->setStandaloneURI(
         '/diffusion/'.$callsign.'/diff/');
       $change_list->setRawFileURIs(
         // TODO: Implement this, somewhat tricky if there's an octopus merge
         // or whatever?
         null,
         '/diffusion/'.$callsign.'/diff/?view=r');
 
       $change_list->setInlineCommentControllerURI(
         '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
 
       $change_references = array();
       foreach ($changesets as $key => $changeset) {
         $change_references[$changeset->getID()] = $references[$key];
       }
       $change_table->setRenderingReferences($change_references);
 
       // TODO: This is pretty awkward, unify the CSS between Diffusion and
       // Differential better.
       require_celerity_resource('differential-core-view-css');
       $pane_id = celerity_generate_unique_node_id();
       $add_comment_view = $this->renderAddCommentPanel($commit,
                                                        $audit_requests,
                                                        $pane_id);
       $main_pane = phutil_render_tag(
         'div',
         array(
           'id'    => $pane_id
         ),
         $change_list->render().
         id(new PhabricatorAnchorView())
         ->setAnchorName('comment')
         ->setNavigationMarker(true)
         ->render().
         $add_comment_view);
 
       $content[] = $main_pane;
     }
 
     $commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
     $short_name = DiffusionView::nameCommit(
       $repository,
       $commit->getCommitIdentifier()
     );
     $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
       ->setAnchorName('top')
       ->setTitle($short_name)
       ->setBaseURI(new PhutilURI('/'.$commit_id))
       ->build($changesets);
     foreach ($content as $child) {
       $nav->appendChild($child);
     }
 
     $crumbs = $this->buildCrumbs(array(
       'commit' => true,
     ));
     $nav->setCrumbs($crumbs);
 
     return $this->buildApplicationPage(
       $nav,
       array(
         'title' => $commit_id
       )
     );
   }
 
   private function loadCommitProperties(
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepositoryCommitData $data,
     array $parents) {
 
     assert_instances_of($parents, 'PhabricatorRepositoryCommit');
     $user = $this->getRequest()->getUser();
     $commit_phid = $commit->getPHID();
 
     $edges = id(new PhabricatorEdgeQuery())
       ->withSourcePHIDs(array($commit_phid))
       ->withEdgeTypes(array(
         PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
         PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT
       ))
       ->execute();
 
     $task_phids = array_keys(
       $edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK]
     );
     $proj_phids = array_keys(
       $edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT]
     );
 
     $phids = array_merge($task_phids, $proj_phids);
     if ($data->getCommitDetail('authorPHID')) {
       $phids[] = $data->getCommitDetail('authorPHID');
     }
     if ($data->getCommitDetail('reviewerPHID')) {
       $phids[] = $data->getCommitDetail('reviewerPHID');
     }
     if ($data->getCommitDetail('committerPHID')) {
       $phids[] = $data->getCommitDetail('committerPHID');
     }
     if ($data->getCommitDetail('differential.revisionPHID')) {
       $phids[] = $data->getCommitDetail('differential.revisionPHID');
     }
     if ($parents) {
       foreach ($parents as $parent) {
         $phids[] = $parent->getPHID();
       }
     }
 
     $handles = array();
     if ($phids) {
       $handles = $this->loadViewerHandles($phids);
     }
 
     $props = array();
 
     if ($commit->getAuditStatus()) {
       $status = PhabricatorAuditCommitStatusConstants::getStatusName(
         $commit->getAuditStatus());
       $props['Status'] = phutil_render_tag(
         'strong',
         array(),
         phutil_escape_html($status));
     }
 
     $props['Committed'] = phabricator_datetime($commit->getEpoch(), $user);
 
     $author_phid = $data->getCommitDetail('authorPHID');
     if ($data->getCommitDetail('authorPHID')) {
       $props['Author'] = $handles[$author_phid]->renderLink();
     } else {
       $props['Author'] = phutil_escape_html($data->getAuthorName());
     }
 
     $reviewer_phid = $data->getCommitDetail('reviewerPHID');
     if ($reviewer_phid) {
       $props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
     }
 
     $committer = $data->getCommitDetail('committer');
     if ($committer) {
       $committer_phid = $data->getCommitDetail('committerPHID');
       if ($data->getCommitDetail('committerPHID')) {
         $props['Committer'] = $handles[$committer_phid]->renderLink();
       } else {
         $props['Committer'] = phutil_escape_html($committer);
       }
     }
 
     $revision_phid = $data->getCommitDetail('differential.revisionPHID');
     if ($revision_phid) {
       $props['Differential Revision'] = $handles[$revision_phid]->renderLink();
     }
 
     if ($parents) {
       $parent_links = array();
       foreach ($parents as $parent) {
         $parent_links[] = $handles[$parent->getPHID()]->renderLink();
       }
       $props['Parents'] = implode(' &middot; ', $parent_links);
     }
 
     $request = $this->getDiffusionRequest();
 
     $props['Branches'] = '<span id="commit-branches">Unknown</span>';
     $props['Tags'] = '<span id="commit-tags">Unknown</span>';
 
     $callsign = $request->getRepository()->getCallsign();
     $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
     Javelin::initBehavior(
       'diffusion-commit-branches',
       array(
         $root.'/branches/' => 'commit-branches',
         $root.'/tags/' => 'commit-tags',
       ));
 
     $refs = $this->buildRefs($request);
     if ($refs) {
       $props['References'] = $refs;
     }
 
     if ($task_phids) {
       $task_list = array();
       foreach ($task_phids as $phid) {
         $task_list[] = $handles[$phid]->renderLink();
       }
       $task_list = implode('<br />', $task_list);
       $props['Tasks'] = $task_list;
     }
 
     if ($proj_phids) {
       $proj_list = array();
       foreach ($proj_phids as $phid) {
         $proj_list[] = $handles[$phid]->renderLink();
       }
       $proj_list = implode('<br />', $proj_list);
       $props['Projects'] = $proj_list;
     }
 
     return $props;
   }
 
   private function buildAuditTable(
     PhabricatorRepositoryCommit $commit,
     array $audits) {
     assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest');
     $user = $this->getRequest()->getUser();
 
     $view = new PhabricatorAuditListView();
     $view->setAudits($audits);
     $view->setCommits(array($commit));
     $view->setUser($user);
     $view->setShowDescriptions(false);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
     $view->setAuthorityPHIDs($this->auditAuthorityPHIDs);
     $this->highlightedAudits = $view->getHighlightedAudits();
 
     $panel = new AphrontPanelView();
     $panel->setHeader('Audits');
     $panel->setCaption('Audits you are responsible for are highlighted.');
     $panel->appendChild($view);
     $panel->setNoBackground();
 
     return $panel;
   }
 
   private function buildComments(PhabricatorRepositoryCommit $commit) {
     $user = $this->getRequest()->getUser();
     $comments = id(new PhabricatorAuditComment())->loadAllWhere(
       'targetPHID = %s ORDER BY dateCreated ASC',
       $commit->getPHID());
 
     $inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
       'commitPHID = %s AND auditCommentID IS NOT NULL',
       $commit->getPHID());
 
     $path_ids = mpull($inlines, 'getPathID');
 
     $path_map = array();
     if ($path_ids) {
       $path_map = id(new DiffusionPathQuery())
         ->withPathIDs($path_ids)
         ->execute();
       $path_map = ipull($path_map, 'path', 'id');
     }
 
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer($user);
 
     foreach ($comments as $comment) {
       $engine->addObject(
         $comment,
         PhabricatorAuditComment::MARKUP_FIELD_BODY);
     }
 
     foreach ($inlines as $inline) {
       $engine->addObject(
         $inline,
         PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
     }
 
     $engine->process();
 
     $view = new DiffusionCommentListView();
     $view->setMarkupEngine($engine);
     $view->setUser($user);
     $view->setComments($comments);
     $view->setInlineComments($inlines);
     $view->setPathMap($path_map);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     return $view;
   }
 
   private function renderAddCommentPanel(
     PhabricatorRepositoryCommit $commit,
     array $audit_requests,
     $pane_id = null) {
     assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
     $user = $this->getRequest()->getUser();
 
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 
     Javelin::initBehavior(
       'differential-keyboard-navigation',
       array(
         'haunt' => $pane_id,
       ));
 
     $draft = id(new PhabricatorDraft())->loadOneWhere(
       'authorPHID = %s AND draftKey = %s',
       $user->getPHID(),
       'diffusion-audit-'.$commit->getID());
     if ($draft) {
       $draft = $draft->getDraft();
     } else {
       $draft = null;
     }
 
     $actions = $this->getAuditActions($commit, $audit_requests);
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->setAction('/audit/addcomment/')
       ->addHiddenInput('commit', $commit->getPHID())
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setLabel('Action')
           ->setName('action')
           ->setID('audit-action')
           ->setOptions($actions))
       ->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setLabel('Add Auditors')
           ->setName('auditors')
           ->setControlID('add-auditors')
           ->setControlStyle('display: none')
           ->setID('add-auditors-tokenizer')
           ->setDisableBehavior(true))
       ->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setLabel('Add CCs')
           ->setName('ccs')
           ->setControlID('add-ccs')
           ->setControlStyle('display: none')
           ->setID('add-ccs-tokenizer')
           ->setDisableBehavior(true))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
           ->setLabel('Comments')
           ->setName('content')
           ->setValue($draft)
           ->setID('audit-content')
           ->setUser($user))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue($is_serious ? 'Submit' : 'Cook the Books'));
 
     $panel = new AphrontPanelView();
     $panel->setHeader($is_serious ? 'Audit Commit' : 'Creative Accounting');
     $panel->appendChild($form);
     $panel->addClass('aphront-panel-accent');
     $panel->addClass('aphront-panel-flush');
 
     require_celerity_resource('phabricator-transaction-view-css');
 
     Javelin::initBehavior(
       'differential-add-reviewers-and-ccs',
       array(
         'dynamic' => array(
           'add-auditors-tokenizer' => array(
             'actions' => array('add_auditors' => 1),
             'src' => '/typeahead/common/users/',
             'row' => 'add-auditors',
             'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
             'placeholder' => 'Type a user name...',
           ),
           'add-ccs-tokenizer' => array(
             'actions' => array('add_ccs' => 1),
             'src' => '/typeahead/common/mailable/',
             'row' => 'add-ccs',
             'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
             'placeholder' => 'Type a user or mailing list...',
           ),
         ),
         'select' => 'audit-action',
       ));
 
     Javelin::initBehavior('differential-feedback-preview', array(
       'uri'       => '/audit/preview/'.$commit->getID().'/',
       'preview'   => 'audit-preview',
       'content'   => 'audit-content',
       'action'    => 'audit-action',
       'previewTokenizers' => array(
         'auditors' => 'add-auditors-tokenizer',
         'ccs'      => 'add-ccs-tokenizer',
       ),
       'inline'     => 'inline-comment-preview',
       'inlineuri'  => '/diffusion/inline/preview/'.$commit->getPHID().'/',
     ));
 
     $preview_panel =
       '<div class="aphront-panel-preview aphront-panel-flush">
         <div id="audit-preview">
           <div class="aphront-panel-preview-loading-text">
             Loading preview...
           </div>
         </div>
         <div id="inline-comment-preview">
         </div>
       </div>';
 
     return
       phutil_render_tag(
         'div',
         array(
           'class' => 'differential-add-comment-panel',
         ),
         $panel->render().
         $preview_panel);
   }
 
   /**
    * Return a map of available audit actions for rendering into a <select />.
    * This shows the user valid actions, and does not show nonsense/invalid
    * actions (like closing an already-closed commit, or resigning from a commit
    * you have no association with).
    */
   private function getAuditActions(
     PhabricatorRepositoryCommit $commit,
     array $audit_requests) {
     assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
     $user = $this->getRequest()->getUser();
 
     $user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
 
     $user_request = null;
     foreach ($audit_requests as $audit_request) {
       if ($audit_request->getAuditorPHID() == $user->getPHID()) {
         $user_request = $audit_request;
         break;
       }
     }
 
     $actions = array();
     $actions[PhabricatorAuditActionConstants::COMMENT] = true;
     $actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
     $actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
 
     // We allow you to accept your own commits. A use case here is that you
     // notice an issue with your own commit and "Raise Concern" as an indicator
     // to other auditors that you're on top of the issue, then later resolve it
     // and "Accept". You can not accept on behalf of projects or packages,
     // however.
     $actions[PhabricatorAuditActionConstants::ACCEPT]  = true;
     $actions[PhabricatorAuditActionConstants::CONCERN] = true;
 
 
     // To resign, a user must have authority on some request and not be the
     // commit's author.
     if (!$user_is_author) {
       $may_resign = false;
 
       $authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
       foreach ($audit_requests as $request) {
         if (empty($authority_map[$request->getAuditorPHID()])) {
           continue;
         }
         $may_resign = true;
         break;
       }
 
       // If the user has already resigned, don't show "Resign...".
       $status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
       if ($user_request) {
         if ($user_request->getAuditStatus() == $status_resigned) {
           $may_resign = false;
         }
       }
 
       if ($may_resign) {
         $actions[PhabricatorAuditActionConstants::RESIGN] = true;
       }
     }
 
     $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
     $concern_raised = ($commit->getAuditStatus() == $status_concern);
-
-    if ($user_is_author && $concern_raised) {
+    $can_close_option = PhabricatorEnv::getEnvConfig(
+      'audit.can-author-close-audit');
+    if ($can_close_option && $user_is_author && $concern_raised) {
       $actions[PhabricatorAuditActionConstants::CLOSE] = true;
     }
 
     foreach ($actions as $constant => $ignored) {
       $actions[$constant] =
         PhabricatorAuditActionConstants::getActionName($constant);
     }
 
     return $actions;
   }
 
   private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
     $drequest = $this->getDiffusionRequest();
 
     $limit = 50;
 
     $merge_query = DiffusionMergedCommitsQuery::newFromDiffusionRequest(
       $drequest);
     $merge_query->setLimit($limit + 1);
     $merges = $merge_query->loadMergedCommits();
 
     if (!$merges) {
       return null;
     }
 
     $caption = null;
     if (count($merges) > $limit) {
       $merges = array_slice($merges, 0, $limit);
       $caption =
         "This commit merges more than {$limit} changes. Only the first ".
         "{$limit} are shown.";
     }
 
     $history_table = new DiffusionHistoryTableView();
     $history_table->setUser($this->getRequest()->getUser());
     $history_table->setDiffusionRequest($drequest);
     $history_table->setHistory($merges);
     $history_table->loadRevisions();
 
     $phids = $history_table->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $history_table->setHandles($handles);
 
     $panel = new AphrontPanelView();
     $panel->setHeader('Merged Changes');
     $panel->setCaption($caption);
     $panel->appendChild($history_table);
     $panel->setNoBackground();
 
     return $panel;
   }
 
   private function renderHeadsupActionList(
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepository $repository) {
 
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $actions = id(new PhabricatorActionListView())
       ->setUser($user)
       ->setObject($commit);
 
     // TODO -- integrate permissions into whether or not this action is shown
     $uri = '/diffusion/'.$repository->getCallSign().'/commit/'.
            $commit->getCommitIdentifier().'/edit/';
 
     $action = id(new PhabricatorActionView())
       ->setName('Edit Commit')
       ->setHref($uri)
       ->setIcon('edit');
     $actions->addAction($action);
 
     require_celerity_resource('phabricator-object-selector-css');
     require_celerity_resource('javelin-behavior-phabricator-object-selector');
 
     if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
       $action = id(new PhabricatorActionView())
         ->setName('Edit Maniphest Tasks')
         ->setIcon('attach')
         ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
         ->setWorkflow(true);
       $actions->addAction($action);
     }
 
     if ($user->getIsAdmin()) {
       $action = id(new PhabricatorActionView())
         ->setName('MetaMTA Transcripts')
         ->setIcon('file')
         ->setHref('/mail/?phid='.$commit->getPHID());
       $actions->addAction($action);
     }
 
     $action = id(new PhabricatorActionView())
       ->setName('Herald Transcripts')
       ->setIcon('file')
       ->setHref('/herald/transcript/?phid='.$commit->getPHID())
       ->setWorkflow(true);
     $actions->addAction($action);
 
     $action = id(new PhabricatorActionView())
       ->setName('Download Raw Diff')
       ->setHref($request->getRequestURI()->alter('diff', true))
       ->setIcon('download');
     $actions->addAction($action);
 
     return $actions;
   }
 
   private function buildRefs(DiffusionRequest $request) {
     // Not turning this into a proper Query class since it's pretty simple,
     // one-off, and Git-specific.
 
     $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
 
     $repository = $request->getRepository();
     if ($repository->getVersionControlSystem() != $type_git) {
       return null;
     }
 
     list($stdout) = $repository->execxLocalCommand(
       'log --format=%s -n 1 %s --',
       '%d',
       $request->getCommit());
 
     // %d, gives a weird output format
     // similar to (remote/one, remote/two, remote/three)
     $refs = trim($stdout, "() \n");
     if (!$refs) {
         return null;
     }
     $refs = explode(',', $refs);
     $refs = array_map('trim', $refs);
 
     $ref_links = array();
     foreach ($refs as $ref) {
       $ref_links[] = phutil_render_tag(
         'a',
         array(
           'href' => $request->generateURI(
             array(
               'action'  => 'browse',
               'branch'  => $ref,
             )),
         ),
         phutil_escape_html($ref));
     }
     $ref_links = implode(', ', $ref_links);
     return $ref_links;
   }
 
   private function buildRawDiffResponse(DiffusionRequest $drequest) {
     $raw_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
     $raw_diff  = $raw_query->loadRawDiff();
 
     $file = PhabricatorFile::buildFromFileDataOrHash(
       $raw_diff,
       array(
         'name' => $drequest->getCommit().'.diff',
       ));
 
     return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
   }
 
 }