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:
- 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 andbuild-testmust pass while the two build lanes are skipped. A workflow/tooling change must select full checks. - On a temporary PR to
main, deliberately break an existing test. Confirm the test fails,build-testis 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. - 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.
- Repeat the failure/pass and review checks on a PR into a real
patch/vX.Y.Zbranch. 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. - 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.
- 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.