CI: compile the C# mods (build-mods verb) so a mod typo can't ship to the server unseen #60

Closed
opened 2026-07-05 03:45:54 +00:00 by coilysiren · 2 comments
Owner

Problem

Nothing compiles the C# mods on the way to deploy. .forgejo/workflows/build-publish.yml runs pytest + ruff + mypy + docker build, and the Dockerfile builds only the frontend + Python. The mods (mods/{jobs,replay,telemetry,stores}) are built by the build-mod-* ward verbs and shipped to the server out of band, so a C# typo in a mod is caught by no automated gate and reaches the live Eco game server unseen.

Surfaced by the eco-app#8 engineer run: there is no dotnet in the container either, so the engineer had to hand-install the .NET 10 SDK (ICU was missing, so DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1) just to get a real green mod build before committing. That detour should not be per-run.

Ask

Gate mod compilation in CI so a mod that does not compile fails the build.

  1. Add a build-mods umbrella ward verb that builds every mod (jobs, replay, telemetry, stores) - today the build-mod-* verbs are per-mod and CI would have to enumerate them.
  2. Add a CI job that provisions dotnet and runs ward exec build-mods. Provisioning options, cheapest first:
    • Self-install in the job now (mirrors how this same workflow already self-installs the docker CLI as a static binary): fetch the .NET 10 SDK, set DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 for the missing-ICU case (the recipe the #8 engineer proved). Independent of the image, lands immediately.
    • Consume dev-base's dotnet once coilyco-flight-deck/agentic-os ships it (sibling issue) - then drop the self-install. This is the direction CI-in-dev-base parity (agentic-os#328) points.

Prefer the self-install now so the gap closes this cycle, with a comment pointing at the dev-base path as the eventual replacement.

Acceptance

  • A compile error in any mods/* fails CI on a PR/push, before deploy.
  • ward exec build-mods builds all four mods green locally and in CI.
  • The dotnet provisioning is documented inline (version + the invariant-globalization flag + why).

Origin

Found by the eco-app#8 engineer, 2026-07-05, who installed .NET 10 by hand to avoid shipping a mod blind. Sibling: dev-base should ship the .NET SDK (agentic-os issue). Umbrella: agentic-os#328 CI-in-dev-base parity.

## Problem Nothing compiles the C# mods on the way to deploy. `.forgejo/workflows/build-publish.yml` runs pytest + ruff + mypy + docker build, and the Dockerfile builds only the frontend + Python. The mods (`mods/{jobs,replay,telemetry,stores}`) are built by the `build-mod-*` ward verbs and **shipped to the server out of band**, so a C# typo in a mod is caught by **no automated gate** and reaches the live Eco game server unseen. Surfaced by the eco-app#8 engineer run: there is **no dotnet in the container** either, so the engineer had to hand-install the .NET 10 SDK (ICU was missing, so `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1`) just to get a real green mod build before committing. That detour should not be per-run. ## Ask Gate mod compilation in CI so a mod that does not compile fails the build. 1. Add a **`build-mods`** umbrella ward verb that builds every mod (`jobs`, `replay`, `telemetry`, `stores`) - today the `build-mod-*` verbs are per-mod and CI would have to enumerate them. 2. Add a **CI job** that provisions dotnet and runs `ward exec build-mods`. Provisioning options, cheapest first: - **Self-install in the job** now (mirrors how this same workflow already self-installs the docker CLI as a static binary): fetch the **.NET 10 SDK**, set `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1` for the missing-ICU case (the recipe the #8 engineer proved). Independent of the image, lands immediately. - **Consume dev-base's dotnet** once [coilyco-flight-deck/agentic-os](https://forgejo.coilysiren.me/coilyco-flight-deck/agentic-os) ships it (sibling issue) - then drop the self-install. This is the direction CI-in-dev-base parity ([agentic-os#328](https://forgejo.coilysiren.me/coilyco-flight-deck/agentic-os/issues/328)) points. Prefer the self-install now so the gap closes this cycle, with a comment pointing at the dev-base path as the eventual replacement. ## Acceptance * A compile error in any `mods/*` fails CI on a PR/push, before deploy. * `ward exec build-mods` builds all four mods green locally and in CI. * The dotnet provisioning is documented inline (version + the invariant-globalization flag + why). ## Origin Found by the eco-app#8 engineer, 2026-07-05, who installed .NET 10 by hand to avoid shipping a mod blind. Sibling: dev-base should ship the .NET SDK (agentic-os issue). Umbrella: [agentic-os#328](https://forgejo.coilysiren.me/coilyco-flight-deck/agentic-os/issues/328) CI-in-dev-base parity.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-60 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T03:46:06Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-60` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T03:46:06Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - build-mods umbrella verb + a CI mods job (self-installed .NET 10 SDK) now compile all four C# mods before publish; landed on main.

Smooth run, mostly because the #8 engineer had already blazed the trail: the invariant-globalization recipe was handed to me, and installing the .NET 10 SDK via dot.net/v1/dotnet-install.sh just worked. All four mods (jobs, replay, telemetry, stores) built green first try once dotnet was on PATH, so I could actually verify the umbrella target locally rather than trusting it blind.

One judgment call worth flagging: the ask says CI runs ward exec build-mods, but I wired the job to make build-mods directly. Ward is not in the CI runner (node:20-bookworm), and every existing job already bypasses the gate the same way - uv run pytest, not ward exec test. So this matches convention; ward exec build-mods remains the local equivalent, same target. If you would rather CI literally shell through ward, that is a follow-up (install the binary in the job).

Confidence is high on the verb and the mapping (verified locally, ward doctor clean, pre-commit green). The one thing I could not exercise is the CI job on the real Forgejo runner - the missing-ICU assumption and the $GITHUB_PATH handoff are reasoned, not observed. First push to main will be the real test. If the runner already ships ICU the invariant flag is just harmless belt-and-suspenders. Rough edge / follow-up: swap the self-install for dev-base's dotnet once agentic-os#328 lands - there is a TODO in the workflow pointing at it.

WARD-OUTCOME: done - build-mods umbrella verb + a CI `mods` job (self-installed .NET 10 SDK) now compile all four C# mods before publish; landed on main. Smooth run, mostly because the #8 engineer had already blazed the trail: the invariant-globalization recipe was handed to me, and installing the .NET 10 SDK via `dot.net/v1/dotnet-install.sh` just worked. All four mods (jobs, replay, telemetry, stores) built green first try once dotnet was on PATH, so I could actually verify the umbrella target locally rather than trusting it blind. One judgment call worth flagging: the ask says CI runs `ward exec build-mods`, but I wired the job to `make build-mods` directly. Ward is not in the CI runner (node:20-bookworm), and every existing job already bypasses the gate the same way - `uv run pytest`, not `ward exec test`. So this matches convention; `ward exec build-mods` remains the local equivalent, same target. If you would rather CI literally shell through ward, that is a follow-up (install the binary in the job). Confidence is high on the verb and the mapping (verified locally, ward doctor clean, pre-commit green). The one thing I could not exercise is the CI job on the real Forgejo runner - the missing-ICU assumption and the `$GITHUB_PATH` handoff are reasoned, not observed. First push to main will be the real test. If the runner already ships ICU the invariant flag is just harmless belt-and-suspenders. Rough edge / follow-up: swap the self-install for dev-base's dotnet once agentic-os#328 lands - there is a TODO in the workflow pointing at it.
Sign in to join this conversation.
No description provided.