Checklist: publishing¶
The product repository is public from the first push
(ADR 0039); every
workflow runs on GitHub-hosted runners. This list is worked through completely and in this
order, every item with evidence (command output or screenshot in the
ops repository): first check locally, then push and make public right away, then connect SonarQube
Cloud and CodeRabbit, then protect main. After that, each ticket comes as its own pull
request.
A. Before the push¶
- Red-team gate over the whole state with the verdict
JA(red-team-gate.md). - One root commit with the chosen identity. What is published is a single commit
without parents with the checked state; the planning history stays private (D-W-93). Rewriting the
history with
git filter-repo(PLAN M0-08, D-W-48) is only needed if a history is to be published after all. The owner chose the commit identity; it is entered inallow-authors.txtof the private list and allowed there only in the author and committer fields (and in aSigned-off-byline that repeats exactly this identity, D-W-37). Every identity inallow-authors.txtthat is not the chosen one is removed before step 3 runs; only then does a scan without a hit prove that no other identity is in the commit. - Scans in a fresh clone of only this commit, with the private list:
make denylist,make denylist-historyandmake gitleaks GITLEAKS_LOG_OPTS=--allare green. Every hit is removed from the state, then the commit is rebuilt and both scans run again. - Rotate secrets that were ever in the repository or in CI logs.
- License gate green,
NOTICEcomplete, ScanCode run over the whole tree without an unexpected license (licenses.md). - Create the environment
denylistin the still empty repository: secretsBOTHY_DENYLISTandBOTHY_DENYLIST_ALLOW_AUTHORS, reachable for pull requests from the repository and formain.denylist-main.ymlalready runs on the push (step 7); if the allow-list of authors is missing, its log can show that the author identity matches a rule of the private list, and this log is public afterwards.
B. Push and public right away¶
- Push the commit as
maininto the empty repository. There must be nomainyet: an existingmainaccepts a commit without parents only by force push. - Public right away: Repository, Settings, General, Danger Zone: visibility "Public". In
the same move, set the repository variable
BOTHY_VISIBILITYtopublic(scheduled runs of CodeQL, Scorecard and Pages, ADR 0039 rule 1). - Enable private vulnerability reporting (Settings, Advanced Security), in the same
move as step 8:
SECURITY.mdandCODE_OF_CONDUCT.mdname only this reporting channel, it must work from the switch on. Cross-check: start a new issue; the chooser linksSECURITY.mdandSUPPORT.mdfrom the repository [assumed, not checked offline]. If one of the two links is missing,.github/ISSUE_TEMPLATE/config.ymlgets, by pull request, an entry undercontact_linkswithname,aboutand the absolute URL of the file; the entry must passmake denylist, and the header comment of the file is adjusted. - Enable secret scanning and push protection (Settings, Advanced Security); free for public repositories (https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection, retrieved 2026-10-10) [verified for secret scanning; push protection at repository level free according to the 2023 changelog, not checked again].
- Deny scan of the push:
denylist-main.ymlran green for the push from step 7. If it is not green, find the cause and start it again viaworkflow_dispatch.
C. SonarQube Cloud and CodeRabbit¶
- SonarQube Cloud: organization of the account that owns the repository (name in the
ops repository), with the OSS plan, create the project, automatic analysis off (R-QA-10).
Repository variables
SONAR_HOST_URL(https://sonarcloud.io),SONAR_ORGANIZATION,SONAR_PROJECT_KEY, secretSONAR_TOKEN. Without these values, the jobsonarof every pull request from the repository fails. Then apply the shared gate and profiles to the cloud and check them (sonarqube-local.md, section 6) with the workflow.github/workflows/sonar-config.yml, which runsmake sonar-configandmake sonar-config-checkagainsthttps://sonarcloud.iowith the variablesSONAR_ORGANIZATIONandSONAR_PROJECT_KEYand the secretSONAR_TOKEN:gh workflow run sonar-config.yml --ref main, thengh run watch. The job runs only formain; the check step ends withquality_sync: check ok for https://sonarcloud.io. AnHTTP 401means the token is invalid, anHTTP 403that its account lacks the permission "Administer Quality Gates" or "Administer Quality Profiles" of the organization, which these calls require (https://sonarcloud.io/api/webservices/list, retrieved 2026-10-13) [verified]. Start it again after every change underquality/sonar/. - CodeRabbit: install it as a GitHub app for the repository;
.coderabbit.yamlapplies.
D. Branch protection¶
- Rulesets for
mainaccording to push-discipline.md (pull request, squash only, required checkci-ok, linear history, signed commits, no force push, no deletion); in addition "Require actions to be pinned to a full-length commit SHA" in the Actions settings and, in the general settings, the squash commit message "Pull request title and description" (push-discipline.md; configuring commit squashing, retrieved 2026-10-11). - Environments
release,release-appleandrelease-windowswith required reviewers and tag rule*/v*andv*;github-pagesfor the docs.
E. Afterwards: each ticket as its own pull request¶
Each ticket comes in order as its own pull request according to push-discipline.md onto main.
- Enable Renovate (ADR 0041) and check the Dependency Dashboard.
- Pages: set the source to "GitHub Actions"; start
docs.ymlmanually. - Trial run: first start
codeql.ymlonmainby hand, so that code scanning has an analysis of the base branch to compare the pull request results with [assumed, not checked offline]. Then the first pull request from the repository (full list): all jobs ran on GitHub-hosted runners,ci-okis green, SonarQube Cloud, CodeQL and CodeRabbit report, Scorecard starts; the jobsanalyze (actions),analyze (go),analyze (javascript-typescript)(TypeScript SDK) andanalyze (python)are green, and code scanning shows results of the category/language:go. In addition, a pull request from a fork (generic deny rules, no secret). - Check DCO: a pull request without sign-off must fail at
ci-ok. - Release signatures before the first release tag (build ticket M7-10): the release chain signs keyless and with attestations (ADR 0036). The key pair from ADR 0062 applies only to releases from a private repository and is not created.
- Portability test on the public path with the first product release: first make the
packages
bothy-serverandbothy-postgresin the container registry public (package settings, Danger Zone, "Change visibility"). A new package is private, and a package linked to the repository inherits "the access permissions (but not the visibility)" of the repository (https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility, retrieved 2026-10-11) [verified]. The step cannot be undone ("Once you make a package public, you cannot make it private again", same source) [verified]. Then startportability.ymlviaworkflow_dispatchwith the latest product tag. It checks out this tag (test code matching the release, 16-operations.md 11.1), fetchesinstall.shwithout login with the documentedcurlcommand (sourceurl, 16-operations.md 3.1), downloads the bundle from the address that the release set intoinstall.sh, and pulls the images that the bundle pins (18-release.md 2.1, rules 1 to 3), also without login; a still private package makes it fail. It verifies the signature of each image digest (key of the private phase from the repository or keyless, ADR 0062 item 5) beforeinstall.shruns. This run is the proof of the publiccurlpath, which the trial of M7-14 (make portability-trial, sourcerelease-download) cannot give. It must be green. - Replace the placeholder
OWNER(PLAN V1-03):@OWNERin.github/CODEOWNERS,OWNERinmkdocs.yml,docs/index.md,README.mdandCONTRIBUTING.md(grep -rn OWNERas a cross-check).make denylistmust be green afterwards; if the private list matches the new path, it gets a narrow exception for exactly this path in the ops repository.