docs: SSOT for the Gitea CI pipeline and automated alpha releases
docs/ci-and-releases.md documents what happens on a push to main, why Gitea rather than GitHub (billing-blocked, private repo, and Forgejo ships no hosted runners), both runners and their prerequisites, the release/manifest layout, and how to verify a release with the Lane=Live install test. Its landmine table is the part worth keeping: every row cost a red pipeline — Node for JS actions, setup-dotnet unmirrored on data.forgejo.org, the zombie-task timeout caused by run-release-gate.ps1 redirecting child output, an en-SE runner locale breaking 40 tests on decimal commas, DAT tests missing their InstalledDat lane tag, parallel-load timing flakes, and the Avalonia compositor threading failure that must NOT be 'fixed' by de-async-ing the test. Also records the culture finding: config, parsing and the wire are all invariant-safe, so US and European installs behave identically; only diagnostic strings follow the current culture. Cross-linked from docs/README.md and release-gate.md, which keeps ownership of the local bounded gate.
This commit is contained in:
parent
657ac6baca
commit
6ef82934dc
3 changed files with 127 additions and 0 deletions
|
|
@ -82,6 +82,8 @@ document in the same change; do not leave both claims standing.
|
||||||
- [`superpowers/specs/`](superpowers/specs/) and
|
- [`superpowers/specs/`](superpowers/specs/) and
|
||||||
[`superpowers/plans/`](superpowers/plans/) are per-slice design and execution
|
[`superpowers/plans/`](superpowers/plans/) are per-slice design and execution
|
||||||
records. Completed plans remain historical.
|
records. Completed plans remain historical.
|
||||||
|
- [`ci-and-releases.md`](ci-and-releases.md) is the SSOT for the Gitea CI
|
||||||
|
pipeline, the self-hosted runners, and how alpha releases are published.
|
||||||
- [`audit/`](audit/) contains completion and conformance audits.
|
- [`audit/`](audit/) contains completion and conformance audits.
|
||||||
- [`reference/ace-commands.md`](reference/ace-commands.md) preserves the local
|
- [`reference/ace-commands.md`](reference/ace-commands.md) preserves the local
|
||||||
ACE server's complete in-game command catalog and points to the authoritative
|
ACE server's complete in-game command catalog and points to the authoritative
|
||||||
|
|
|
||||||
117
docs/ci-and-releases.md
Normal file
117
docs/ci-and-releases.md
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
# Continuous integration and alpha releases (Gitea)
|
||||||
|
|
||||||
|
Single source of truth for how acdream builds, gates, and ships alpha builds.
|
||||||
|
Landed 2026-08-19. Companion to [`release-gate.md`](release-gate.md), which
|
||||||
|
owns the *local* bounded gate.
|
||||||
|
|
||||||
|
## What happens on a push to main
|
||||||
|
|
||||||
|
```
|
||||||
|
git push origin main
|
||||||
|
│
|
||||||
|
├─ windows-gate (RARE-win) build + full lane-filtered suite
|
||||||
|
├─ linux-portable (eriktestLinux) portable closure, Linux lanes
|
||||||
|
│
|
||||||
|
└─ release (needs BOTH green) publish a Gitea Release
|
||||||
|
+ repoint the launcher manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
Workflow: [`.gitea/workflows/ci.yml`](../.gitea/workflows/ci.yml). A red gate
|
||||||
|
cannot publish: `release` uses `needs:`, not a `workflow_run` trigger, whose
|
||||||
|
Forgejo support is unreliable.
|
||||||
|
|
||||||
|
## Why Gitea and not GitHub
|
||||||
|
|
||||||
|
GitHub Actions is **billing-blocked** on this account ("recent account payments
|
||||||
|
have failed"), and the repo is private, so hosted runners consume paid minutes.
|
||||||
|
Forgejo ships **no hosted runners at all**, so Actions there requires
|
||||||
|
self-hosted ones — which are free on both platforms. The same two machines can
|
||||||
|
serve GitHub later by registering a second agent; only the workflow's
|
||||||
|
`runs-on` labels change.
|
||||||
|
|
||||||
|
## The runners
|
||||||
|
|
||||||
|
| | Windows | Linux |
|
||||||
|
|---|---|---|
|
||||||
|
| Host | `RARE` (10.6.0.3) | `eriktestLinux` (10.0.0.202) |
|
||||||
|
| Agent | `act_runner` 0.2.13 | `forgejo-runner` 13.0.0 |
|
||||||
|
| Persistence | Scheduled task `ForgejoRunner`, at logon of `acbot` | systemd `forgejo-runner`, `Restart=always` |
|
||||||
|
| Labels | `windows`, `windows-latest`, `windows-x64` | `ubuntu-latest`, `ubuntu`, `linux`, `ubuntu-slim` |
|
||||||
|
| Execution | host mode (`:host`) — no Docker on either box | host mode |
|
||||||
|
|
||||||
|
Both **poll outbound** over HTTPS. Gitea never connects to them, so no inbound
|
||||||
|
ports, no port forwarding, and no static IP; they work behind NAT. The runner
|
||||||
|
does not have to live next to the Gitea container (which runs on `bluesnake`,
|
||||||
|
a host we have no shell on).
|
||||||
|
|
||||||
|
`forgejo-runner` publishes **no Windows binary in any release**, which is why
|
||||||
|
Windows uses Gitea's `act_runner`. Forgejo speaks the same Actions protocol.
|
||||||
|
|
||||||
|
### Prerequisites on a runner
|
||||||
|
|
||||||
|
- **.NET SDK in the `global.json` band** — currently `10.0.3xx`. `10.0.400` is a
|
||||||
|
different feature band and `rollForward: latestPatch` rejects it.
|
||||||
|
- **Node.js** — `actions/checkout` and `actions/upload-artifact` are JavaScript
|
||||||
|
actions. Docker images normally supply Node; in host mode the machine must.
|
||||||
|
- **Git**, and outbound HTTPS to `git.snakedesert.se`.
|
||||||
|
- **PowerShell 7** on Windows (`pwsh`); `tools/*.ps1` require it.
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Payloads are **release attachments**, deliberately outside git history: a build
|
||||||
|
is ~120 MB and would otherwise land in a branch every push. Only the ~500-byte
|
||||||
|
`manifest.json` is committed, to the payload-free `dist` branch.
|
||||||
|
|
||||||
|
```
|
||||||
|
Release 0.1.0-build.<yyyyMMddHHmm>
|
||||||
|
client-win-x64.zip AcDream.App.exe + acdream-headless.exe
|
||||||
|
launcher-win-x64.zip acdream-launcher.exe + acdream-bake.exe
|
||||||
|
manifest.json
|
||||||
|
|
||||||
|
dist branch (manifest only, force-replaced each publish)
|
||||||
|
bin/manifest.json -> points at the release attachment URLs
|
||||||
|
```
|
||||||
|
|
||||||
|
The launcher polls the manifest at a fixed raw URL
|
||||||
|
(`ReleaseManifestClient.ProductionManifestUri`). **Forgejo has no
|
||||||
|
`/releases/latest/download/` route** (verified: 404), which is why the manifest
|
||||||
|
cannot simply live in the release itself.
|
||||||
|
|
||||||
|
`tools/publish-bin.ps1 -BaseUrl <release asset base>` builds the payloads; the
|
||||||
|
pipeline passes the tag's asset base. With no `-BaseUrl` it falls back to the
|
||||||
|
dist-branch layout for a manual local publish.
|
||||||
|
|
||||||
|
### Verifying a release
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
dotnet test tests/AcDream.Launcher.Core.Tests --filter Lane=Live
|
||||||
|
```
|
||||||
|
|
||||||
|
`LiveGiteaReleaseInstallTests` installs the advertised client from the real feed
|
||||||
|
through the production updater — real SHA-256/size verification, extraction, and
|
||||||
|
atomic activation — then asserts both hosts resolve out of the activated
|
||||||
|
directory and `current.json` names the installed version.
|
||||||
|
|
||||||
|
## Landmines
|
||||||
|
|
||||||
|
Each of these cost a red pipeline; none was a config typo.
|
||||||
|
|
||||||
|
| Symptom | Cause |
|
||||||
|
|---|---|
|
||||||
|
| `Cannot find: node in PATH` | JS actions need Node on the host in `:host` mode |
|
||||||
|
| `actions/setup-dotnet` never resolves | `data.forgejo.org` does not mirror it (404). `checkout` and `upload-artifact` **are** mirrored. Self-hosted runners carry the SDK anyway |
|
||||||
|
| Job "failed" while dotnet processes still run | `run-release-gate.ps1` redirects children to log files, so the step goes silent; Forgejo fails a non-reporting task as a zombie. CI runs `dotnet test` directly so output streams |
|
||||||
|
| ~40 tests fail on formatted numbers | Runner's `HKCU` locale was `en-SE` (comma decimal): expected `"update:0.25"`, got `"update:0,25"`. `Set-Culture` does **not** reach a scheduled task without a loaded profile — set the registry directly |
|
||||||
|
| `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1` as the locale fix | Too blunt — it breaks tests that legitimately construct a culture. Fix the machine locale instead |
|
||||||
|
| `FileNotFoundException: client_cell_1.dat` | DAT-dependent tests missing `[Trait("Lane", "InstalledDat")]`. Build machines have no DATs |
|
||||||
|
| Timing-sensitive test fails only under load | `FakeAceTransportTests.PausedSelector_…` took 37 s under full-assembly parallelism vs ~350 ms isolated. Cap `xUnit.MaxParallelThreads` on small runners rather than edit transport code |
|
||||||
|
| Avalonia "calling thread cannot access this object" in cleanup | Serialize that assembly (`xunit.runner.json`). The stack shows a compositor being **constructed** during teardown; `TestAppBuilder` documents the hazard. Do **not** "fix" it by de-async-ing the test — that causes the failure |
|
||||||
|
|
||||||
|
## Culture note
|
||||||
|
|
||||||
|
The `en-SE` discovery is worth remembering beyond CI: config files, numeric
|
||||||
|
parsing, and the wire are all culture-safe (`System.Text.Json` is invariant by
|
||||||
|
spec, every `float/double.TryParse` passes `CultureInfo.InvariantCulture`, and
|
||||||
|
the protocol is binary). Only **diagnostic strings** format with the current
|
||||||
|
culture, so a European player sees `local=(8,00; 191,00)` in an F3 dump. The
|
||||||
|
client installs and runs correctly in both the US and Europe.
|
||||||
|
|
@ -72,6 +72,14 @@ The JSON summary records the exact test filter. Environment-dependent,
|
||||||
diagnostic, manual, and known-failure results must be published as their own
|
diagnostic, manual, and known-failure results must be published as their own
|
||||||
lane and must never be added to the hermetic pass headline.
|
lane and must never be added to the hermetic pass headline.
|
||||||
|
|
||||||
|
## Continuous integration
|
||||||
|
|
||||||
|
This document owns the LOCAL gate. Pushes to `main` are gated on self-hosted
|
||||||
|
runners and publish alpha releases — see
|
||||||
|
[`ci-and-releases.md`](ci-and-releases.md). Note that CI deliberately does NOT
|
||||||
|
invoke `run-release-gate.ps1`: that script redirects child output to log files,
|
||||||
|
and Forgejo fails a task that stops reporting as a zombie.
|
||||||
|
|
||||||
## Non-hermetic test lanes
|
## Non-hermetic test lanes
|
||||||
|
|
||||||
Installed-DAT tests require an explicit opt-in and a retail DAT directory:
|
Installed-DAT tests require an explicit opt-in and a retail DAT directory:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue