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.

For implemented transitions, labels, release tags, recovery cadence, measured delays and dated live-policy gaps, see Spec-to-Production Pipeline.

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 by default; may be enabled locally 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
  • size labels exclude generated lockfile churn (package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml), which is gated by scripts/npm-lock-integrity.mjs instead
  • 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 approval is not required for solo-dev, and the native ruleset does not require conversation resolution. The executor still withholds eligibility while any non-outdated review thread is unresolved. 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

GitHub Actions execution approval (action_required) is separate from PR review approval. The trusted auto-approver uses cloud_agent.auto_approve_workflow_runs from default-branch governance. Copilot PR events retain their immediate approval path; other same-repository PR events and completion of spec synthesis, token inventory, dependency graph, or model catalog automation trigger a held-run sweep. The 15-minute schedule is a fallback, not a delivery guarantee: GitHub can delay scheduled execution. The sweep revalidates open PR membership, repository, and current head before approving; fork runs remain held. Sweeps share a repository-wide concurrency group to avoid duplicate approval requests. A rejected request is rechecked: if the run still requires approval, permission errors fail the workflow rather than reporting a successful recovery.

To recover without waiting for cron, dispatch the installed auto-approval workflow on the trusted default branch, for example:

gh workflow run auto-approve-cloud-agent-workflows.yml --ref main

Downstream installations use the installed filename, normally basecoat-internal-auto-approve-cloud-agent-workflows.yml. This starts execution approval reconciliation only; it does not approve PR reviews or bypass checks.

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.

Zero routine PR review is not zero authorization. Issue approval authorizes its approved implementation scope; feature-origin delivery separately requires current delivery consent. Actions execution approval only permits a held workflow to run. None substitutes for source/spec authority, exact-head eligibility, protected integration checks, or applicable production controls. Missing evidence remains a hold even for an XS through XL change.

Under every profile, the executor also withholds eligibility while the PR has any unresolved review thread that is not outdated. Outdated threads on superseded diffs do not block, and eligibility fails closed if thread state cannot be read. GitHub Actions has no review-thread resolution trigger, so the next push, PR edit, required-check completion, or a manual workflow_dispatch of the executor with pr_number re-evaluates the PR.

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:

This root bootstrap command is for a BaseCoat source/release payload obtained through the supported setup flow, not a synced consumer overlay. scripts/bootstrap.ps1 is not installed by content sync. For an existing consumer, use the installed workflow tooling below and keep profile/settings changes explicit; content sync is not governance onboarding.

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:

Provision labels before enabling the gates

Content sync and workflow installation do not provision repository labels. The shell onboarding script creates memory labels only. With an account that can manage issues in the consumer repository, run this PowerShell example before activating the ruleset or issuing /approve. Replace OWNER/REPO; do not run it against the BaseCoat source repository.

Names and meanings follow Label Taxonomy. Size colors/descriptions match the source pr-size-labeler.yml; other colors below are presentation defaults, not authorization or priority evidence. Existing labels and their customized colors/descriptions are preserved.

$repo = 'OWNER/REPO'
if ($repo -eq 'OWNER/REPO') { throw 'Set the consumer repository first.' }
$labels = @(
    @('priority:critical', 'b60205', 'Service down, data loss, active security breach, CVE'),
    @('priority:high', 'd93f0b', 'Major feature broken, significant user impact, no workaround'),
    @('priority:medium', 'fbca04', 'Minor feature issue, workaround exists, moderate user impact'),
    @('priority:low', 'd4c5f9', 'Cosmetic issue, nice-to-have enhancement, minor improvement'),
    @('size:XS', '0e8a16', 'Small PR (<=20 changed lines)'),
    @('size:S', '1d76db', 'Small-medium PR (<=100 changed lines)'),
    @('size:M', 'fbca04', 'Medium PR (<=300 changed lines)'),
    @('size:L', 'd93f0b', 'Large PR (<=800 changed lines)'),
    @('size:XL', 'b60205', 'Extra large PR (801-2000 changed lines)'),
    @('size:XXL', '5319e7', 'XXL PR (>2000 changed lines; qualified approval required)'),
    @('approved', 'bfdadc', 'Issue approved for implementation by a qualified stakeholder'),
    @('copilot-agent', 'e99695', 'Coding-agent assignment workflow marker; verify assignee'),
    @('needs-triage', 'ededed', 'Issue needs type and priority triage'),
    @('needs-info', 'ededed', 'Additional details required before implementation'),
    @('delivery-hold', 'b60205', 'PR delivery paused by the hold owner'),
    @('bug', 'd73a4a', 'Unexpected behavior, error, regression or defect'),
    @('enhancement', 'a2eeef', 'New feature or capability improvement'),
    @('documentation', '0075ca', 'Missing or incorrect documentation'),
    @('question', 'd876e3', 'Question or clarification request'),
    @('chore', 'ededed', 'Maintenance, refactoring or housekeeping'),
    @('security', 'b60205', 'Security concern or hardening work')
)
$existing = @(gh api --paginate "repos/$repo/labels?per_page=100" --jq '.[].name')
if ($LASTEXITCODE -ne 0) { throw 'Cannot read consumer labels; check repository access.' }
foreach ($label in $labels) {
    if ($existing -contains $label[0]) { continue }
    gh label create $label[0] --repo $repo --color $label[1] --description $label[2]
    if ($LASTEXITCODE -ne 0) { throw "Failed to create label $($label[0]); resolve before continuing." }
}
gh label list --repo $repo --limit 1000
if ($LASTEXITCODE -ne 0) { throw 'Cannot verify consumer labels.' }

This creates label definitions only. It does not apply priority, approve issues, assign an agent, remove holds, enable auto-merge or authorize releases. If a creation fails, stop and reconcile access or concurrent changes; rerunning reads the current definitions and skips those already present.

Choose a producer or an explicit labeling owner

Installed gate / flow Required label evidence Producer or manual/agent path
basecoat-issue-approve.yml Primary type and canonical priority:* on the source issue, normalized to one of each by triage A qualified triager applies type/priority from issue evidence before /approve; the approval workflow applies approved/copilot-agent only after its checks
basecoat-pr-auto-merge-executor.yml Exactly one current size:XS through size:XXL on the PR Deterministic PR size labeler, or a human/agent calculating current additions + deletions minus generated package-lock.json churn
release-label-gate A planning label such as wave:1 or sprint:42 on the PR Planning owner chooses the actual wave/sprint; the size labeler can assign an ISO-week sprint to same-repository PRs without existing coverage
Delivery pause delivery-hold when intentionally paused Only the hold owner removes it; provisioning never changes a hold

Do not select a priority just to pass approval or label a large PR XS to escape the XXL boundary. Issue labels do not automatically satisfy PR label gates. On the manual size path, read the current PR head and complete file list, calculate the reviewable churn, remove every prior size:* label, then apply exactly one canonical label using the source thresholds: XS <=20, S <=100, M <=300, L <=800, XL <=2000, otherwise XXL. Recheck the head and labels after writing; if the head changed, recalculate before requesting eligibility. Adding a new size label without removing the old one leaves duplicate evidence and must be reconciled before requesting eligibility. Older executors may check only label presence; do not rely on them to detect duplicates. The current-size validation repair is tracked separately in #3738 / #3739, so inspect the installed version rather than assume that repair is available. For example, a triager can apply bug and priority:high after confirming a major broken feature; a planning owner then creates/applies the real sprint or wave label separately. Do not invent approved or copilot-agent as a shortcut.

The source producer is .github/workflows/pr-size-labeler.yml; consumer installation uses basecoat-pr-size-labeler.yml only if that template is present and supported in the selected release. Check the installed inventory. Older overlays without it need an explicit labeling owner until a supported update is installed; copying the source runtime or bypassing the gate is not an onboarding step. Existing PRs need a producer event or its manual workflow_dispatch with the PR number; external PRs need their planning owner to supply sprint/wave coverage.

Source issue triage runs via issue-triage.md / issue-triage.lock.yml. The consumer installer currently marks its basecoat-agent-issue-triage.yml mapping unsupported; do not enable compiled agent templates by hand or assume opt-in selection makes them runnable. Until a supported triage producer is configured, name a human or load the issue-triage agent to review evidence and apply type/priority. Example prompt: @issue-triage Triage issue 12: apply one primary type and one canonical priority, explain the evidence, and report missing spec details. Do not approve or assign it. Expected result: a triage explanation and labels, or needs-info with the missing details, not delivery consent.

Install and verify the selected workflows

pwsh .github/base-coat/scripts/configure-downstream-workflows.ps1 `
  -SourceDir .github/base-coat/workflows `
  -DestinationDir .github/workflows `
  -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 .github/base-coat/scripts/configure-downstream-workflows.ps1 `
  -SourceDir .github/base-coat/workflows `
  -DestinationDir .github/workflows `
  -Workflow issue-approve.yml, approved-spec-handoff.yml `
  -KeepUnknownBc

That workflow validates the supplied spec reference on the issue-side /approve path, applies approved and copilot-agent, attempts coding-agent assignment, and dispatches linked-PR reevaluation after labels are finalized. Verify the assignee: labels alone do not prove implementation started. approved-spec-handoff.yml handles /approve-spec and installs its agent-assignment-worker.yml dependency. /spec-2-prod belongs to ship-it-intent-dispatch.yml and requires an already approved source issue; it does not approve the issue itself.

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 requests GitHub-native auto-merge. Current source, reverified on 2026-10-09, uses an exact-head queue-enabled command without --delete-branch; #3574 closed on 2026-10-06. It also requires the configured event-generating admission credential so downstream checks can run. A missing credential fails closed; do not copy an interactive token or switch to an administrator bypass. Repository automatic branch deletion is a separate post-merge setting. Older consumers must inspect their installed workflow/version rather than assume this source correction is deployed there. The 2026-10-06 incompatible-command observation remains historical evidence in the dated pipeline audit, not a current operating recipe. The executor 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.

To pause delivery without changing eligibility logic, add the delivery-hold label to the PR. The executor reports the hold in the BaseCoat merge eligibility status and job summary, rechecks it immediately before auto-merge, disables any existing auto-merge request, and will not re-enable delivery until the label is removed.

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; the executor enforces unresolved, non-outdated review threads instead
  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 and every non-outdated review thread is resolved
  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. the issue-side qualified /approve path validates its supplied spec and attempts coding-agent assignment; verify the persisted assignee and actual execution evidence, not just approved / copilot-agent labels
  5. /spec-2-prod routes delivery for an already approved source; it does not approve the issue or serve as an issue-assignment directive
  6. the coding agent opens its draft implementation handoff; ready promotion requires completed source acceptance, current-head validation and delivery authority. Missing completion handoff is tracked in #3669, not proven by a draft PR or an unrelated successful workflow
  7. production deployment follows its actual authorized release path and configured environment policy; validated merge alone does not establish tag creation, publication or production verification
  8. 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.