ci: build and gate galaxy-gen inside the dev-base image #76

Merged
coilysiren merged 7 commits from gg/claude/74-ci-in-dev-base into main 2026-08-10 02:13:41 +00:00
Member

Closes part of galaxy-gen#74. The CI contract questions this surfaced are galaxy-gen#75.

What

Both Forgejo lanes now run inside the promoted dev-base image through this repo's own ward exec verbs, and the Dockerfile's builder stage is that same image. rust, node, wasm-pack, and a pinned binaryen all arrive with it (agentic-os#986), so the gate and the shipped artifact are finally built by one toolchain.

Why

The bundle was produced in four places with three toolchains, and the one that shipped was the one nothing tested:

  • Local host - floating cargo install wasm-pack, and no binaryen on PATH at all
  • GitHub PR CI - floating wasm-pack, binaryen 119, node lts/* (22)
  • Publish Dockerfile - floating wasm-pack, binaryen 119, node 20
  • Forgejo main - built no bundle, Rust lint and cargo test only

The local row is the sharp one. wasm-pack takes wasm-opt from PATH when it finds one and otherwise downloads its own floating latest, so local builds optimized every bundle by date rather than by source, on the exact tool whose output already broke chromium once.

Notes for review

  • install no longer runs cargo install wasm-pack unconditionally. Inside the image that would overwrite the pinned binary in the shared CARGO_HOME/bin with a floating one, reintroducing the drift being removed. New ci-setup verb does the lockfile-exact npm ci that CI needs.
  • rust-toolchain.toml still pins 1.90.0, which is what keeps dev-base's floating stable default from mattering. Its comment referenced the now-deleted scripts/ci/install-rust.sh and is rewritten.
  • docker build now needs a forgejo.coilysiren.me login to pull its own base. scripts/publish-image.sh already logs in first. A local ward exec build-docker needs that login too, where the old public Rust base needed none.
  • .github/workflows/action.yml is deliberately untouched. Its rust and js jobs now duplicate this gate, but leaving them keeps coverage while the new lane earns trust. Removing them is galaxy-gen#75.

Browser e2e is not here

It cannot move. AGENTS.md records that the in-cluster runner cannot reach the Playwright browser CDN, so playwright install cannot fetch chromium on this side, and dev-base carries no browser. I drafted an e2e job, then removed it on that evidence rather than shipping a job I expected to fail. e2e therefore remains the one gate exercising a differently-built bundle (galaxy-gen#75).

Two doc corrections

  • The tsc typecheck now runs on the Forgejo side, not only on GitHub.
  • AGENTS.md claimed a push to main "rolls the public site." It does not. The deploy repo pins an exact source SHA and rolls only on its own services/galaxy-gen/** changes, with cross-repo dispatch unbuilt (deploy#11).

Verification

  • ward doctor passes with the new verb
  • pre-commit: main already fails 91 checks pre-existing (galaxy-gen#46). This branch takes it to 88 and adds none.
  • The dev-base image carrying these tools published green as agentic-os run 3170, and its own build smoke-tests wasm-pack --version and wasm-opt --version.
  • Not verified locally: no Docker daemon on this host, so the rebased builder stage has not been built. This PR's gate is the first real exercise of the CI half, and the publish job on merge is the first exercise of the Dockerfile half.
Closes part of galaxy-gen#74. The CI contract questions this surfaced are galaxy-gen#75. ## What Both Forgejo lanes now run inside the promoted dev-base image through this repo's own `ward exec` verbs, and the Dockerfile's builder stage is that same image. rust, node, wasm-pack, and a pinned binaryen all arrive with it (agentic-os#986), so the gate and the shipped artifact are finally built by one toolchain. ## Why The bundle was produced in four places with three toolchains, and the one that shipped was the one nothing tested: * Local host - floating `cargo install wasm-pack`, and **no binaryen on PATH at all** * GitHub PR CI - floating wasm-pack, binaryen 119, node `lts/*` (22) * Publish Dockerfile - floating wasm-pack, binaryen 119, **node 20** * Forgejo main - built no bundle, Rust lint and `cargo test` only The local row is the sharp one. wasm-pack takes `wasm-opt` from PATH when it finds one and otherwise downloads its own floating `latest`, so local builds optimized every bundle by date rather than by source, on the exact tool whose output already broke chromium once. ## Notes for review * `install` no longer runs `cargo install wasm-pack` unconditionally. Inside the image that would overwrite the pinned binary in the shared `CARGO_HOME/bin` with a floating one, reintroducing the drift being removed. New `ci-setup` verb does the lockfile-exact `npm ci` that CI needs. * `rust-toolchain.toml` still pins 1.90.0, which is what keeps dev-base's floating `stable` default from mattering. Its comment referenced the now-deleted `scripts/ci/install-rust.sh` and is rewritten. * `docker build` now needs a `forgejo.coilysiren.me` login to pull its own base. `scripts/publish-image.sh` already logs in first. A local `ward exec build-docker` needs that login too, where the old public Rust base needed none. * `.github/workflows/action.yml` is deliberately untouched. Its `rust` and `js` jobs now duplicate this gate, but leaving them keeps coverage while the new lane earns trust. Removing them is galaxy-gen#75. ## Browser e2e is not here It cannot move. `AGENTS.md` records that the in-cluster runner cannot reach the Playwright browser CDN, so `playwright install` cannot fetch chromium on this side, and dev-base carries no browser. I drafted an e2e job, then removed it on that evidence rather than shipping a job I expected to fail. e2e therefore remains the one gate exercising a differently-built bundle (galaxy-gen#75). ## Two doc corrections * The tsc typecheck now runs on the Forgejo side, not only on GitHub. * `AGENTS.md` claimed a push to `main` "rolls the public site." It does not. The deploy repo pins an exact source SHA and rolls only on its own `services/galaxy-gen/**` changes, with cross-repo dispatch unbuilt (deploy#11). ## Verification * `ward doctor` passes with the new verb * pre-commit: `main` already fails 91 checks pre-existing (galaxy-gen#46). This branch takes it to 88 and adds none. * The dev-base image carrying these tools published green as agentic-os run 3170, and its own build smoke-tests `wasm-pack --version` and `wasm-opt --version`. * **Not** verified locally: no Docker daemon on this host, so the rebased builder stage has not been built. This PR's gate is the first real exercise of the CI half, and the publish job on merge is the first exercise of the Dockerfile half.
ci: build and gate galaxy-gen inside the dev-base image
Some checks failed
ci / gate (pull_request) Failing after 45s
a88d78d604
The bundle was produced in four places with three toolchains, and the one that
shipped was the one nothing tested. Local dev had no binaryen on PATH at all,
so wasm-pack fell back to downloading its own floating latest wasm-opt, while
GitHub CI and the publish Dockerfile each pinned 119 and disagreed on node
(20 in the image build, 22 on GitHub).

Both Forgejo lanes now run in the promoted dev-base image through this repo's
own ward verbs, and the Dockerfile's builder stage is that same image. rust,
node, wasm-pack, and a pinned binaryen arrive with it (agentic-os#986), so the
gate and the shipped artifact are built by one toolchain. rust-toolchain.toml
keeps pinning 1.90.0 on top of it, which is what makes dev-base's floating
`stable` default not matter.

`install` no longer runs `cargo install wasm-pack` unconditionally: in the
image that would overwrite the pinned binary in the shared CARGO_HOME/bin with
a floating one, reintroducing exactly the drift being removed. New `ci-setup`
verb does the lockfile-exact npm ci that CI needs, since CI no longer installs
a toolchain at all.

scripts/ci/install-rust.sh is deleted, its only remaining reference being the
rust-toolchain.toml comment, which is rewritten.

Browser e2e stays on GitHub. The in-cluster runner cannot reach the Playwright
browser CDN, so it cannot fetch chromium at all, and dev-base carries no
browser. That leaves e2e as the one gate still exercising a differently-built
bundle, tracked in galaxy-gen#74.

Also corrects two claims in AGENTS.md: the tsc typecheck now runs on the
Forgejo side rather than only on GitHub, and a push to main does not roll the
public site. The deploy repo pins an exact source SHA and rolls only on its own
changes, with cross-repo dispatch still unbuilt (deploy#11).

Refs galaxy-gen#74.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
fix(ci): keep the build-publish comment in the top header block
Some checks failed
ci / gate (pull_request) Failing after 47s
56b6962da3
The code-comments rule allows YAML comments only above the first content
line, because a key-sorter would drift a mid-file comment away from whatever
it described. The explanation I added sat inside the `test:` job, which is
below content and therefore a violation.

It passed CI only because `.forgejo/workflows/build-publish.yml` carries a
code-comments exclusion, and that list is for legacy teaching sources
grandfathered under galaxy-gen#46. Writing new prose into a grandfather slot
defeats the point of the list.

Moved to a real top-of-file header, where the rule permits it, and dropped the
file from the exclusion list now that it complies on its own. The repo-wide
violation count is unchanged at 88, so removing the exclusion hides nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
The scenario bars were set on macOS and the shipped artifact has never
satisfied them. The simulation is not bit-reproducible across libm
implementations, so linux and macOS run two measurably different galaxies from
the same seed:

  metric          linux (ships)          macOS
  mean coherence  0.11289115             0.20040232
  min coherence   0.040970046 @ t1061    0.081265524 @ t1001
  mean coverage   0.25123763             0.32920793

That is not last-bit noise surfacing in a metric. The troughs land on different
ticks, so the trajectories have fully diverged by the measurement window.

Architecture is not the variable. linux/arm64 and linux/x86_64 agree bit for
bit, while macOS/arm64 differs from linux/arm64 on the same architecture. The
remaining difference is glibc against Apple libm, across the 105 transcendental
call sites in this file, amplified over 1000 ticks of chaotic gravity.

Recalibrated to linux with headroom. macOS reads higher on every metric so it
clears the same bars. mean coherence 0.15 -> 0.10, min coherence 0.04 -> 0.035,
mean coverage 0.25 -> 0.22. Two of those were already within 2.5% of failing on
linux, so moving only the red one would have left the test on a knife edge.

This makes CI green on a true statement about the deployed build rather than an
aspirational one about a laptop. It is not the real fix: routing the sim's
transcendentals through the pure-Rust libm crate would make it reproducible
everywhere and is tracked in galaxy-gen#73.

Verified 91/91 plus clippy and rustfmt on both macOS arm64 and linux glibc.

Refs galaxy-gen#73.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
fix(ci): build the wasm package in ci-setup, and move ward prose to docs
All checks were successful
ci / gate (pull_request) Successful in 56s
360043579e
check-js typechecks against galaxy_gen_backend, which only exists once
wasm-pack has run and ./pkg is linked. ci-setup did just `npm ci`, so the
typecheck would have failed the moment test-rust stopped failing first. It was
invisible in PR #76's run because check-js never executed.

Also trims .ward/ward.yaml from 16 comment lines to 3. The capabilities and
catalog prose moved to docs/ward-config.md, which is where the code-comments
rule wants long explanations, and the file drops off that hook's exclude list
now that it complies unaided.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Update .forgejo/workflows/ci.yml
Some checks failed
ci / gate (pull_request) Has been cancelled
3abc174770
Update .forgejo/workflows/build-publish.yml
Some checks failed
ci / gate (pull_request) Has been cancelled
deb83a5b9e
Update Dockerfile
All checks were successful
ci / gate (pull_request) Successful in 11m45s
f27f1ec4c3
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/galaxy-gen!76
No description provided.