Skip to content

Solo-Developer Governance Profile

Use the solo-dev profile for a repository with one accountable maintainer and no dependable second reviewer. It still requires pull requests, required status checks, protected main, advisory automated feedback, and an auditable merge path. It simplifies operations; it does not weaken team profiles or authorize administrator bypass.

Choose the right profile

Signal solo-dev team-dev regulated-team
Active maintainers One accountable maintainer Two or more regular contributors Any team with mandated separation of duties
Routine independent review Not required through size:XL; checks remain required Required for medium and higher risk Required for every risk tier
XXL PR intent One qualified human approval Independent PR approval Two independent PR approvals
Merge queue Deferred Deferred Required
Production deployment Autonomous after merge and required checks Protected GitHub environment approval Protected GitHub environment approval
Best fit True single-owner service with reliable CI Shared ownership and normal team delivery Regulated, security-sensitive, or audited delivery

Choose team-dev as soon as another person regularly owns, reviews, or operates the repository. Choose regulated-team when policy, customer commitments, or compliance controls require separation of duties. Repository size is not the deciding factor; ownership and risk are.

Non-negotiable solo-dev controls

minimal branch policy means the minimum protected posture, not an unprotected branch:

  • every change to main uses a pull request
  • direct pushes, force pushes, and branch deletion are blocked
  • required status checks run against the current head and must succeed
  • administrators are subject to the rules and the bypass list is empty
  • the auto-merge workflow uses GitHub auto-merge, never --admin
  • XS through XL pull requests require zero independent PR approvals
  • size:XXL pull requests require one qualified human approval
  • governance policy, human-boundary, and canonical/distributed/downstream executor changes are always classified as critical
  • the executor's BaseCoat merge eligibility status is required as a fail-closed policy and check aggregator

The six canonical validation checks come from .github/governance/policy-packs.json:

  1. lint-and-validate
  2. test
  3. validate-commit-messages
  4. validate-unix
  5. validate-windows
  6. release-label-gate

The ruleset must also require BaseCoat merge eligibility. The trusted executor publishes that commit status directly against the pull request head SHA. Pin the required context to GitHub Actions integration ID 15368 for provenance. Copilot review feedback is advisory for solo-dev; it never becomes a merge blocker or requires conversation resolution. Pending validation, a missing qualified size:XXL approval, or policy violations keep the status red and prevent both manual and automatic merge.

If a consumer uses different check names, update its local policy pack and ruleset together. Do not delete checks from only one surface.

Self-merge policy

solo-dev PRs through size:XL use zero independent PR approvals. The accountable maintainer may author and merge after required checks and the BaseCoat merge eligibility status pass. A size:XXL PR requires one qualified approval on its current head. This autonomous path applies only to solo-dev; team and regulated profiles retain their independent-review requirements.

Explicit policy-exception, security-incident, and incident boundaries remain human-controlled. They are exceptions to the normal feature-delivery size policy, not routine review gates.

For linked-issue evidence, adding, editing, or deleting the qualified /approve comment and adding or removing the approved label automatically sets linked PR eligibility to pending and dispatches reevaluation. Editing the PR title or body also reevaluates closing links.

Production release paths are explicit policy, not inferred from the broad high tier. The shipped policy pack includes every BaseCoat workflow that can target the production environment in production_release_paths, plus the trusted deployment job and exact environment scalar in production_environment.workflow_bindings, plus a SHA-256 digest covering the entire workflow. For changes to those workflows, the executor reads the PR-head file and verifies both the full digest and environment binding before checking the live protected GitHub production environment. This prevents moving production effects into an unprotected sibling job. PR approval is not treated as equivalent; in solo-dev, a validated merge can continue through the environment without an additional deployment-review wait.

Before enabling solo-dev self-merge in a consumer repository, inventory every local workflow that can deploy to production, including workflows with dynamic environment expressions, and add every path to production_release_paths. Add its deployment job ID and exact environment scalar and normalized UTF-8 SHA-256 digest to production_environment.workflow_bindings. To change a production workflow, first merge the policy PR that preauthorizes the exact prospective digest; then submit the byte-matching workflow PR. This two-PR sequence is mandatory because the executor reads trusted policy from main. Do not activate the solo-dev ruleset until the consumer check names and production workflow inventory are complete.

Risk tier is the highest path tier matched by the changed files. See Governance Policy Packs and Risk-Tier Autonomy Policy.

Configure the profile

1. Commit the onboarding contract

Create .github/basecoat-onboarding-profile.json:

{
  "contract_version": "1.0.0",
  "profile": "solo-dev",
  "branch_policy": "minimal",
  "workflow_pack": "solo",
  "template_pack": "solo",
  "telemetry_mode": "local",
  "secrets_mode": "local",
  "hook_pack": "none",
  "preserve_local_customizations": true,
  "allow_profile_downgrade": false
}

Validate the selection through the existing bootstrap entrypoint:

pwsh scripts/bootstrap.ps1 `
  -OnboardingContractPath .github\basecoat-onboarding-profile.json `
  -Silent

2. Install the existing merge executor

Use the downstream workflow installer rather than writing another auto-merge implementation:

pwsh scripts/configure-downstream-workflows.ps1 `
  -Workflow pr-auto-merge-executor.yml `
  -KeepUnknownBc

This installs:

  • .github/workflows/basecoat-pr-auto-merge-executor.yml
  • .github/governance/policy-packs.json
  • .github/governance/human-approval-boundaries.json

To use the approved-spec directive, also install the issue approval workflow:

pwsh scripts/configure-downstream-workflows.ps1 `
  -Workflow issue-approve.yml `
  -KeepUnknownBc

That workflow validates the supplied spec reference, applies the repository-standard approved label for a qualified /approve or /spec-2-prod directive, assigns the coding agent, and dispatches merge-eligibility reevaluation after the label is finalized.

The exact -Workflow selector installs only the executor and its governance contracts. Targeted mode preserves every non-selected workflow; -KeepUnknownBc adds an explicit safeguard for older installer versions. On reinstall, targeted mode also preserves existing consumer governance files so local check names and production paths are not overwritten. Review upstream governance changes and merge them into the consumer policy deliberately.

The executor evaluates an existing PR and enables GitHub-native auto-merge with gh pr merge --auto --squash --delete-branch. It does not discover BaseCoat updates or create upgrade PRs. Keep consumer update detection and PR creation in the consumer updater lifecycle; do not duplicate that implementation here.

3. Set repository options

In GitHub:

  1. Settings > General > Pull Requests
  2. enable Allow auto-merge
  3. enable Allow squash merging
  4. enable Automatically delete head branches
  5. Settings > Actions > General > Workflow permissions
  6. keep Read repository contents and packages permissions
  7. do not grant repository-wide write access; the executor declares only its required actions: write, checks: read, deployments: read, contents: write, pull-requests: write, issues: write, and statuses: write permissions
  8. if the repository installs a workflow that opens pull requests with the default token (for example issue-to-spec-synthesis.yml), review the separate Allow GitHub Actions to create and approve pull requests platform policy; see PR-creation permission below before enabling it at any scope
  9. Settings > Rules > Rulesets
  10. create an active branch ruleset targeting the default branch
  11. require zero approving reviews
  12. do not require extra approval for unattributed changes
  13. do not require conversation resolution; Copilot feedback is advisory
  14. require all six validation checks plus BaseCoat merge eligibility
  15. require branches to be up to date
  16. block deletion and non-fast-forward pushes
  17. leave the bypass list empty
  18. Settings > Secrets and variables > Actions > Variables
  19. set BASECOAT_POLICY_PACK to solo-dev
  20. Settings > Environments > production
  21. do not configure required reviewers for solo-dev
  22. allow only protected branches or selected deployment branches including main
  23. keep production secrets and variables environment-scoped
  24. preserve protected or selected deployment branches including main

Set the variable explicitly even though workflows can fall back to the policy file's default_profile. Explicit selection prevents onboarding and workflow defaults from being confused during migrations.

PR-creation permission for automation workflows

Workflows that open pull requests using the default GITHUB_TOKEN (for example issue-to-spec-synthesis.yml, which turns a triaged issue into a draft spec PR) are blocked by a separate, hierarchical GitHub setting: Allow GitHub Actions to create and approve pull requests. It is distinct from the read/write workflow-permissions radio button above — a workflow's own permissions: block already grants it pull-requests: write, but GitHub still refuses the actual create/approve call unless this toggle is on.

The setting is enforced top-down: Enterprise → Organization → Repository. If an Enterprise or Organization has set it to Disabled (not Not enforced), no lower level can override it — gh api calls to enable it at the repo or org return 409 with a message that write permissions for workflows are disabled by the enterprise, even with admin:org scope.

This policy capability and related Actions settings do not ship with BaseCoat or with repository templates. BaseCoat ships workflow files, policy-pack files, and bootstrap guidance; the Enterprise, organization, and repository owners must separately decide whether Actions may create or approve pull requests in the consumer environment.

First avoid PR-creating automation when a read-only report or issue comment is sufficient. When PR creation is required, use this policy decision order:

  1. Treat Enterprise-level global enablement as the highest-blast-radius option: it can affect every organization and repository unless each org explicitly restricts it. Do not enable the Enterprise checkbox solely to unblock one BaseCoat or downstream repository unless org restriction, audit ownership, and rollback are already part of the plan.
  2. Prefer an organization-level override or restriction when the Enterprise owner can delegate safely but the repository-level control is unavailable.
  3. Prefer repository-level opt-in only when GitHub exposes that control under the current Enterprise and organization policy, and only for repositories that install a PR-creating workflow.
  4. If the platform policy cannot be narrowed safely, use a scoped GH_AW_GITHUB_TOKEN only as a temporary fallback. A GitHub App or brokered token with repository-scoped installation and auditability is the preferred durable direction.

The blast radius matters more in solo-dev: routine XS through XL pull requests have required_approving_review_count: 0. A broadly enabled toggle allows every workflow in scope to create PRs and submit approving PR reviews, not just the intended workflow. In a permissive ruleset, an unrelated or compromised workflow could create and self-approve a PR that then has no independent-review gate. Keep enablement scoped to the repositories that need it, and confirm the workflow itself has compensating controls such as draft-only output and an explicit human /approve directive before implementation.

If an Enterprise policy is Disabled with no delegation, no repo or org action can fix it locally. Escalate to an Enterprise owner for policy review instead of treating it as a repository-admin prerequisite. Falling back to a manually-created PAT (see docs/operations/github-secrets.md) trades an enforced platform policy for a standing secret with rotation burden; treat it strictly as a temporary exception while a GitHub App or brokered token design is implemented.

scripts/bootstrap.ps1 performs a best-effort, warn-only check for this setting when a PR-creating workflow is present, so onboarding surfaces the requirement without blocking on it.

Programmatic GitHub setup

The caller needs repository administration permission. Replace OWNER/REPOSITORY before running these commands.

$repository = "OWNER/REPOSITORY"

gh api --method PATCH "repos/$repository" `
  -F allow_auto_merge=true `
  -F allow_squash_merge=true `
  -F delete_branch_on_merge=true

gh variable set BASECOAT_POLICY_PACK `
  --repo $repository `
  --body "solo-dev"

# Only if the repository installs a PR-creating automation workflow
# (for example issue-to-spec-synthesis.yml), and only after reviewing
# Enterprise, organization, and repository policy scope. A 409 here means the
# setting is blocked above the repository — escalate to that owner instead of
# retrying at the repository scope.
gh api --method PUT "repos/$repository/actions/permissions/workflow" `
  -F can_approve_pull_request_reviews=true

Create solo-dev-main-ruleset.json:

{
  "name": "BaseCoat solo-dev main",
  "target": "branch",
  "enforcement": "active",
  "bypass_actors": [],
  "conditions": {
    "ref_name": {
      "include": ["~DEFAULT_BRANCH"],
      "exclude": []
    }
  },
  "rules": [
    {
      "type": "deletion"
    },
    {
      "type": "non_fast_forward"
    },
    {
      "type": "pull_request",
      "parameters": {
        "allowed_merge_methods": ["squash"],
        "dismiss_stale_reviews_on_push": true,
        "require_code_owner_review": false,
        "require_extra_approval_for_unattributed_changes": false,
        "require_last_push_approval": false,
        "required_approving_review_count": 0,
        "required_review_thread_resolution": false
      }
    },
    {
      "type": "required_status_checks",
      "parameters": {
        "do_not_enforce_on_create": false,
        "strict_required_status_checks_policy": true,
        "required_status_checks": [
          {"context": "lint-and-validate", "integration_id": 15368},
          {"context": "test", "integration_id": 15368},
          {"context": "validate-commit-messages", "integration_id": 15368},
          {"context": "validate-unix", "integration_id": 15368},
          {"context": "validate-windows", "integration_id": 15368},
          {"context": "release-label-gate", "integration_id": 15368},
          {"context": "BaseCoat merge eligibility", "integration_id": 15368}
        ]
      }
    }
  ]
}

Apply it:

gh api --method POST "repos/$repository/rulesets" `
  --input solo-dev-main-ruleset.json

For an existing ruleset, use PUT repos/$repository/rulesets/$rulesetId with the same payload rather than creating a duplicate.

Verify before enabling routine auto-merge

Run each required workflow at least once so GitHub knows its check context, then inspect the live configuration:

gh variable get BASECOAT_POLICY_PACK --repo $repository
gh api "repos/$repository" `
  --jq '{allow_auto_merge,allow_squash_merge,delete_branch_on_merge}'
gh api "repos/$repository/rulesets" `
  --jq '.[] | select(.name == "BaseCoat solo-dev main") | {id,enforcement,bypass_actors}'
gh workflow list --repo $repository

Open a non-draft test PR and confirm:

  1. an XS through XL PR reports zero required approvals and becomes eligible after required checks pass; Copilot review feedback remains advisory
  2. auto-merge remains pending while any required check is missing or failing
  3. an XXL test PR remains blocked until a qualified reviewer approves its current head
  4. a linked issue with a supplied spec reference and qualified /approve or /spec-2-prod directive assigns the coding agent
  5. the coding agent opens a ready implementation PR with a closing reference to the approved issue
  6. production deployment continues after a validated merge without a separate environment-review wait
  7. no run or command uses an administrator bypass

Rollback

Use normal Git history for content rollback:

  1. disable the auto-merge executor workflow
  2. disable repository auto-merge if automated landing must stop immediately
  3. revert the merged PR in a new protected PR
  4. restore the previous ruleset payload if protection configuration changed
  5. rerun required checks and record the rollback PR or incident

Do not force-push main, delete audit history, or use gh pr merge --admin. A failed critical or production change follows the risk-tier rollback and incident process rather than a solo-dev exception.

Transition to a stronger profile

Move to team-dev

Transition when a second maintainer becomes active, ownership is shared, or routine independent review is expected:

  1. change the onboarding contract to profile: "team-dev" and migration_from: "solo-dev"
  2. set BASECOAT_POLICY_PACK=team-dev
  3. update the ruleset to require the policy-pack approval baseline
  4. rerun bootstrap and downstream workflow installation
  5. verify required checks and reviewer routing before merging new work

Move to regulated-team

Transition when compliance, security, or customer controls require stricter evidence or separation of duties:

  1. migrate from the current profile to regulated-team
  2. require the regulated approval counts and prd-spec-gate
  3. enable the required merge queue and organization-managed secrets/telemetry
  4. remove any local exceptions and verify the empty bypass posture

When migration_from records the currently applied profile, bootstrap compares profile strength and blocks a weaker target unless explicitly approved with allow_profile_downgrade: true. Omitting migration_from prevents bootstrap from identifying the transition, so migration contracts must always include it. See the Onboarding Profile Contract for migration semantics.