GitHub Setup

This guide describes the checked-in workflows and separately dates inspected GitHub settings and acceptance evidence. Protected-source deployment and explicit host profiles are published; see the deployment evidence. Later local changes still need their own hosted checks before hosted success is claimed. A merged workflow does not establish that host configuration was applied.

CI merge gate

The required pull-request check is build-test, produced by GitHub Actions (app ID 15368) in CI. Keep that job name stable. CodeQL, code quality, and automated review supplement it; none runs the behavior tests in its place.

CI runs on every pull request, without workflow-level path filters, on pushes to patch/v*, and by manual dispatch. build-test aggregates the selection job and its selected lanes. Documentation-only PRs run the shared content check and the DocFX site build with rendered link, anchor, search, and edit-link checks; application, workflow, tooling, mixed, and unrecognized changes require both Windows solution checks and Linux full validation. Pushes and manual runs always require both platforms. See selection and lane coverage for the conservative path rules and local equivalents.

The shared validation action uses the repository's content, quick, or full command. Both OS lanes run locked restore, formatting verification, a Release solution build (including AppHost), and all tests. Linux additionally checks content, frontend packaging, smoke-package installation/syntax, the documentation site, and the shell suites, then builds and verifies the API container without pushing it. The aggregate requires explicit success for every selected lane and skipped for unselected lanes; failures, cancellations, missing results, and unexpected skips cannot pass it. Keep only the stable build-test name as this workflow's required check so intentionally skipped lanes do not block documentation-only PRs.

Deployment validation continues to use the full shared action; its required package job handles frontend publication. Deployment requires validation and packaging to succeed before entering the environment job. No deployment credentials are supplied to pull-request CI; the deployment validation job also has a read-only repository token.

Before executing release scripts or deployment validation, protected-source resolution accepts only main or patch/vX.Y.Z, requires GitHub to report the branch as protected, and resolves its immutable commit through the API. Tags, arbitrary SHAs, and unprotected branches are not deployment inputs. Validation and deployment check out that resolved SHA, not unchecked dispatch input. If the branch moves after source selection, the workflow fails explicitly; start a new run rather than silently deploying a different commit.

The validation action definition is loaded from github.workflow_sha before source checkout. That trusted action checks out the resolved deployment source into a separate directory and runs the shared checks there. This keeps executable pipeline definitions separate from the selected application's files. Third-party Actions in workflows and in the local composite validation action are pinned to reviewed commit SHAs with adjacent release comments. Separate Dependabot entries cover .github/workflows, .github/actions/validate, and .github/actions/docs, so their pins continue to receive reviewable update PRs. The DocFX tool manifest under docs/.config has its own NuGet update entry. See documentation publication for the manual protected-main Pages workflow and recorded hosted acceptance.

The shared PowerShell command checks native exit codes while retaining output. The API image check uses Bash with -e -o pipefail, so tee cannot hide failure. Failures upload the available solution, script-suite, frontend-package (when run), and smoke-package console logs, selected OS/tool versions, and the formatter's JSON report as ci-windows-validation, ci-linux-validation, ci-documentation-validation, staging-validation, or production-validation. API runtime failures use ci-linux-image after the shared action has finished. Each name includes the run-attempt suffix so reruns do not collide. Artifacts expire after seven days. These are diagnostic logs, not TRX reports; test failures and stack traces are in test.log. A cancellation, runner loss, or job timeout can prevent the upload, so also consult the Actions job log. Do not log credentials or sensitive response bodies.

Every runner job has an explicit timeout:

Runner job Timeout (minutes)
CI build-test; each CodeQL analysis 30
Deployment build-test 20
Deployment package 25
Deployment deploy, including browser setup, activation, smoke, and recovery 45
Production release validation/publication; patch preparation 10
Source resolution; main-dispatch guards; edge migration authorization 5
Edge validation and apply 15

Reusable workflow callers use the timeouts on their called jobs. Browser smoke has no separate job or workflow step timeout. These are upper bounds, not targets. Do not add continue-on-error, conditional skipping, or path filters to the required job.

Live protection

Authenticated administrator inspection and read-back on 2026-09-15 verified the following active repository rulesets. Branch rules target main and patch/v*; tag rules target v*. There are no legacy branch-protection rules: the GraphQL collection was empty, and the authenticated main protection endpoint returned "Branch not protected" (404). Rulesets still protect that branch.

Ruleset Requirements Bypass
Main and patch merge gate (16765458) build-test from GitHub Actions (15368), up-to-date branches, CodeQL, code quality, no deletion or force push None, including administrators and the release bot
Main linear history (23469610) Linear history on main None
Main and patch human review (23468686) One approval, stale-review dismissal, latest-push approval, resolved conversations, squash-only merges Organization administrators, PR-only, as explained below
Protected branch creation (23468683) Restrict creation of protected branches Release App, always mode, creation only
Release tag creation (23468685) Restrict creation of release tags Release App, always mode, creation only
Release tag immutability (16754313) No tag updates, deletion, or force pushes None, including administrators and the release bot

The review rule also requires an extra approval for unattributed Copilot PRs. Automated review is not human approval. No code-owner requirement is configured without real owners. CodeQL blocks high-or-higher security alerts and other errors; code quality blocks errors. The merge-gate ruleset also requests Copilot review. The separate Copilot ruleset 19404460 is disabled and supplies no protection.

Required status checks are not enforced on branch creation: the release bot must be able to create a patch branch at an existing released commit. Subsequent updates receive the full PR/check gate. Do not enable a merge queue without adding merge_group workflow triggers.

Patch branches must preserve their released base verbatim, including historical merge commits. The separate linear-history rule therefore targets only main. New PR merges remain squash-only on both branch families, enforced by repository merge-method settings and the review rule. Do not rewrite a released history or give the bot a general CI bypass just to create a patch branch.

Deliberate maintainer exception

ostomachion is the only organization owner and only collaborator with write access at verification time. Requiring a second eligible reviewer on that maintainer's own PRs would deadlock routine development. Organization administrators therefore retain a pull-request-only bypass of the review ruleset, not of the merge gate. This is a deliberate sole-maintainer exception, not a claim that the maintainer supplied an independent human approval.

Ordinary contributor and bot PRs require human review. New commits dismiss stale approvals and require approval of the latest reviewable push. The maintainer may merge their own PR without independent review only through the documented exception; failing CI, CodeQL, and code quality still block that merge. The exception does not permit direct pushes, deletion, or force pushes.

Remove this review bypass once a second trusted reviewer has write access and can routinely review maintainer-authored PRs. Recheck the exception whenever organization ownership changes. Do not grant repository access merely to make a verification exercise pass.

Release bot permissions

The installed opengamebuilder-release-bot App has ID 3815756, verified against the public App record, organization installation, and RELEASE_BOT_CLIENT_ID. Its approved installation permissions are repository contents write, pull requests write, workflows write, and metadata read.

The release scripts create a patch branch from a released tag, push unprotected chore/* branches, and open preparation, version-bump, and merge-back PRs. The creation-only rules permit this with the approved App permissions, without granting permission to bypass checks, review its own PRs, directly update protected branches, or overwrite release tags. Do not add the bot to the merge-gate, review, or tag-immutability bypass lists. PR validation never receives the short-lived release App token.

A bypass applies to its entire ruleset, which is why creation and immutability are separate. Administrator ability to edit rules is not a standing bypass. If emergency recovery needs a temporary rule change, record the reason, actor, exact ref, and restoration in a public issue without credentials. Environment approvals and release-token scope are audited below.

Workflow-file permission

Workflows write is needed even to create a branch containing an existing release's workflow-file history. The native Git preparation run 34997049849 and reference-only API probe both failed without it. Owner ostomachion approved the permission on 2026-09-15, and authenticated installation read-back and the successful preparation below confirmed it is active. No key rotation or token sharing was needed.

Both token-creation steps explicitly request permission-workflows: write, so insufficient installation permissions fail at token creation rather than halfway through a release. The action's default token scope is this repository; PR CI never receives these permissions. Do not restore a broad ruleset bypass to try to fix an App permission failure.

For future installation changes, update the App permissions and approve them in the installation settings. The installation currently selects all repositories, but the workflow does not set the action's owner or repositories inputs, so each short-lived token is scoped to this repository. Restrict the installation itself to selected repositories if organization-wide installation is no longer needed; that is a separate administrator setting, not a reason to broaden workflow tokens.

Deployment authority and recovery

Authenticated read-back on 2026-09-22 confirmed that both production and staging select only the main branch for deployment. GitHub matches an environment's deployment rule against the workflow run's GITHUB_REF, not the commit checked out inside a job. For both release kinds, dispatch CD Production with the branch picker on main: the ref input chooses main or a protected patch/vX.Y.Z source commit. Do not add patch/* to the environment merely because a patch commit is deployed. Both published CD workflows also fail immediately when the dispatch ref is not main, before resolving a source or entering a deployment environment. GitHub's environment rule reference explains this distinction.

Workflow run ref Source input Environment result without admin bypass Source check on merged main
main main Production allowed, then reviewer approval Protected main SHA
main patch/vX.Y.Z Production allowed, then reviewer approval Protected matching patch SHA
patch/vX.Y.Z or a tag Any Workflow guard fails; production also denies Not run
main Tag, arbitrary SHA, or unprotected branch Environment permits the dispatch ref Source resolver rejects the input

Staging runs on a push to main or a manual dispatch from main; the workflow guard and environment policy reject other dispatch refs in the normal path. The deliberately invalid production dispatch from main with a tag as the source input failed in the resolver; validation, deployment, and release jobs were skipped. A non-main dispatch failed at the first guard, with all downstream jobs skipped. These rejection checks did not deploy. Successful application and edge runs are recorded in hosting.

The production environment has one required reviewer, ostomachion, confirmed along with self-review and administrator bypass settings on 2026-09-22. Self-approval is allowed because there is no second eligible release reviewer; administrator bypass is also enabled. Decision (2026-09-19): retain bypass for emergency recovery while there is only one release operator. It is not the routine approval path and does not count as independent review. The only human collaborator with write access at read-back was ostomachion; justinhufford had read access only. Someone with repository write access can dispatch a manual workflow, but the deployment job waits for the configured reviewer (or an administrator's explicit bypass). GitHub's deployment review guide describes approval and bypass. Do not use bypass for a routine release; record any emergency bypass, its reason, and the affected run. Because bypass can force waiting jobs, main-only is not an absolute restriction against an administrator. Revisit self-review and bypass when a second trusted release operator exists.

Each environment holds only its own DEPLOY_HOST, DEPLOY_USER, and DEPLOY_SSH_KEY secrets, plus DEPLOY_KNOWN_HOSTS and EDGE_PROFILE environment variables. The hosts and accounts may be the same or different. Set EDGE_PROFILE explicitly: shared in both environments for a shared server, or staging and production respectively for separate servers. There is no default. The edge workflow selects the environment whose credentials to use; a shared edge or profile migration requires production approval. See host profiles and migration. DEPLOY_KNOWN_HOSTS contains the complete OpenSSH known_hosts entry for that environment's DEPLOY_HOST; the public host key is configuration, not a secret. Follow the deployment host-key guide to read the public key through an existing SSH connection that strictly checks a saved, trusted server key, construct the entry, set the selected environment's host-key variable, and read it back. Repeat for the other environment and its host. This carries forward the administrator's existing trust decision; it is not independent verification of an unverified first connection. A provider console or another authenticated independent channel is needed when the saved key is missing, changed without explanation, or untrusted. Do not populate the variable from an unverified ssh-keyscan result. During rotation, authenticate the replacement before changing the variable. The workflow requires an exact host match, enables strict host-key checking, and prints the pinned fingerprint to the job log without printing private credentials. Administrator confirmation of the original trust source remains unverified; see the owned host checks.

The RELEASE_BOT_PRIVATE_KEY is a repository secret because Prepare Patch needs the App before any deployment environment is entered; the client ID and smoke-test URLs are repository variables. Deployment callers use secrets: inherit. The workflow checks the three deployment secrets and the pinned host-key variable for presence in the environment job before publishing an image; it never logs secret values. Inheritance also makes the repository-scoped RELEASE_BOT_PRIVATE_KEY available to the trusted reusable workflow's secret context, although no deployment step references it. Keep the reusable workflow definition trusted and the bot key out of scripts/checkout. Revisit isolation if release credentials move to a separate approval boundary. The deployment job's permissions and retained credentials also cover its browser-smoke steps, as detailed below. The App installation has contents, pull requests, and workflows write plus metadata read; its creation-only branch/tag bypasses and the no-bypass tag-immutability rule are recorded above. The token-creation action requests these permissions explicitly and defaults to this repository, despite the broader installation. Normal PR CI has a read-only GITHUB_TOKEN, references no deployment environment and no release-bot secret, and therefore receives no deployment credentials. GitHub makes environment secrets available only after that environment's rules pass. On 2026-09-20, the signed-in organization Actions secrets settings page explicitly reported that OpenGameBuilder has no organization secrets. This was a read-only UI metadata check; no secret values were viewed. The audit CLI token received 403 for the organization secret API inventory at that inspection; organization-secret inventory was not refreshed in the 2026-09-22 documentation check.

Deployment job credential boundary

In _deploy.yml, source resolution, validation, and packaging use separate jobs with contents: read. Only deploy enters the selected environment and receives contents: read plus packages: write. These are job-wide token permissions; a later smoke step does not reduce them. See GitHub's permissions reference.

The deployment runner logs in to GHCR with its GITHUB_TOKEN, builds/pushes the API image, then sets up SSH. Docker login retains authentication in the runner's Docker credential configuration (~/.docker/config.json or its configured credential store). SSH setup writes ~/.ssh/deploy_key and ~/.ssh/config with mode 600, and the public pin to ~/.ssh/known_hosts. Checkout uses persist-credentials: false; this prevents persisted Git checkout credentials, but does not remove Docker or SSH credentials. Node/Playwright installation, activation, browser smoke, finalization, and recovery all run afterward on the same runner. The key and registry login remain available through those steps; there is no explicit logout or key-removal step. File modes protect against other users, not later code running as the same runner user. The smoke command receives only its URL and expected identities as explicit step environment variables, but it is not an isolated credential-free job.

Decision (2026-09-22): retain the combined deployment job. It keeps activation, smoke acceptance, finalization, and failure recovery in one transaction using the same SSH connection configuration and candidate identity. Dependencies are installed before activation; a subsequent failed activation or smoke check triggers rollback in that job. Cancellation, timeout, or runner loss can prevent recovery; inspect the host's pending transaction before retrying. This choice trusts the reviewed protected-source deployment scripts and locked smoke dependencies with the deployment runner's authority. It records an exposure boundary, not evidence of exploitation. Splitting jobs would need an explicit handoff and recovery coverage; it is not part of this documentation correction.

Settings inspection and operator ownership

Read back these settings without revealing secret values:

gh api repos/OpenGameBuilder/opengamebuilder/environments/production
gh api repos/OpenGameBuilder/opengamebuilder/environments/production/deployment-branch-policies
gh api repos/OpenGameBuilder/opengamebuilder/environments/staging/deployment-branch-policies
gh api repos/OpenGameBuilder/opengamebuilder/environments/production/secrets --jq '.secrets[].name'
gh api repos/OpenGameBuilder/opengamebuilder/environments/staging/secrets --jq '.secrets[].name'
gh api repos/OpenGameBuilder/opengamebuilder/actions/secrets --jq '.secrets[].name'
gh api orgs/OpenGameBuilder/installations --jq '.installations[] | select(.app_id == 3815756) | {repository_selection, permissions}'

At the recorded access inspection, ostomachion was the only human who could both initiate and approve a production release and handle recovery. If deployment fails before publishing, fix the cause and rerun from main with the intended protected source branch. If deployment succeeded but tagging, GitHub Release creation, or the follow-up PR failed, keep that source branch at the same commit and follow the release rerun guidance. The bot may create the tag, Release, and follow-up PR after deployment; it cannot approve its own PR, bypass CI, move a release tag, or recover the server. There is no agreed backup operator; see practical stewardship. Artifact rollback was rehearsed in staging; the hosting guide records the evidence. That evidence does not establish a second operator's access or recovery readiness.

Acceptance evidence

These are historical acceptance checks, not the current test count or a fresh inspection of every repository setting. Current local commands and evidence boundaries are in testing guidance.

Check Evidence What it establishes
Required CI rejects a behavior failure on main and patch/v* Deliberately failing main run and patch run build-test failed, diagnostic artifacts contained the assertion, and both PRs were blocked
Corrected validation baseline passes on both branch families Passing main run and patch run Tests and API image build passed; CodeQL and code-quality checks passed without suppressing alerts
Release App can prepare a patch without bypassing the PR gate Prepare Patch run, PR #84, and its CI run Branch/PR creation succeeded; review remained required and NU1903 blocked the old dependency baseline

The baseline was published through PR #82. Temporary patch PR #83 and PR #84 were closed without merging, and their disposable refs were removed. Temporary deletion exclusions were removed afterward; read-back found no merge-gate bypass actors. No release tags were changed by these acceptance checks. The recorded human-review settings do not establish independent approval of maintainer-authored PRs; the sole-maintainer exception above still applies.

Patch branches created from older tags must receive the current dependency and validation baseline through their preparation PR. Do not disable NuGet Audit, required checks, or review to make an old release pass. Deployment and rollback acceptance is recorded separately in hosting.

Administrator verification

Use an authenticated administrator session; do not put tokens in the repository. These read-only commands complement the settings UI:

gh auth status
gh api repos/OpenGameBuilder/opengamebuilder/rulesets --paginate
gh api repos/OpenGameBuilder/opengamebuilder/rulesets/16765458
gh api repos/OpenGameBuilder/opengamebuilder/rulesets/16754313
gh api repos/OpenGameBuilder/opengamebuilder/rulesets/23468683
gh api repos/OpenGameBuilder/opengamebuilder/rulesets/23468685
gh api repos/OpenGameBuilder/opengamebuilder/rulesets/23468686
gh api repos/OpenGameBuilder/opengamebuilder/rulesets/23469610
gh api repos/OpenGameBuilder/opengamebuilder/branches/main/protection
gh api repos/OpenGameBuilder/opengamebuilder/rules/branches/main
gh api graphql -f query='query { repository(owner: "OpenGameBuilder", name: "opengamebuilder") { branchProtectionRules(first: 100) { nodes { pattern } pageInfo { hasNextPage endCursor } } } }'

Inspect any additional/inherited rulesets and their bypass actors, plus every matching legacy pattern (paginate if necessary). For a real patch branch, also query branches/patch%2FvX.Y.Z/protection and rules/branches/patch%2FvX.Y.Z. An authenticated 404 for legacy protection can mean no legacy rule; a 401/403 does not. If one terminal is authenticated and another is not, compare GH_CONFIG_DIR and XDG_CONFIG_HOME configuration locations before logging in again. Do not display tokens or copy credentials into the repository.

Then perform a controlled acceptance check without deploying:

  1. After a CI/action/test change is merged, run CI on a representative PR. Confirm the reported check is exactly build-test, and require it from GitHub Actions in the ruleset. Confirm real hosted Windows and Linux lanes both pass and record their runner images and selected SDK versions. Also run a documentation-only PR: content checks and build-test must pass while the two build lanes are skipped. A workflow/tooling change must select full checks.
  2. On a temporary PR to main, deliberately break an existing test. Confirm the test fails, build-test is red, the failure artifact contains the assertion, and the merge box specifically identifies the failed required check as a blocker for a non-bypass contributor. Use a platform-specific failure so the other lane can still pass. Confirm a cancelled required lane cannot yield a passing gate, and inspect the job logs if cancellation prevents artifact upload. Do not merge the failing PR.
  3. Restore the assertion and push. Confirm the check passes. For a PR that does not use the sole-maintainer exception, have an eligible human approve, then push a small reviewable change and verify reapproval is required.
  4. Repeat the failure/pass and review checks on a PR into a real patch/vX.Y.Z branch. Verify both CodeQL analyses and code-quality results run, rather than leaving required checks pending forever. Patch branches created from older releases must first receive the current workflows, shared action, and test baseline in their preparation PR; do not assume old tags contain these files.
  5. Verify the release App can create the intended patch branch and open its preparation PR, while that PR still requires human approval and green tests. Confirm tag creation is the App's only tag bypass from authenticated ruleset details; do not create, move, or delete a real release tag merely to test rules. Rehearse actual release/tag operations only during an authorized release.
  6. Record PR URLs, head SHAs, check-run URLs, merge-blocking evidence, and the authenticated ruleset/bypass inventory here before claiming merge-gate acceptance. Close temporary PRs; do not merge them just to exercise the gate.

See GitHub's available rules for status-check, approval, creation, and bypass semantics.