Skip to content

ci(web): single-source toolchain versions, patch-level drift guard, Godot 4.7.1 - #8

Merged
rtkelly13 merged 5 commits into
mainfrom
claude/split-1-web-toolchain-3a6h5m
Jul 27, 2026
Merged

ci(web): single-source toolchain versions, patch-level drift guard, Godot 4.7.1#8
rtkelly13 merged 5 commits into
mainfrom
claude/split-1-web-toolchain-3a6h5m

Conversation

@rtkelly13

Copy link
Copy Markdown
Owner

Stack 1 of 4 — split out of #6, which had grown to cover four separate concerns. This is the original web-hosting work and stands alone on main.

Started as "has official Godot C# web export landed?" (it hasn't — see below) and turned into fixing three gaps the review surfaced in our own pipeline.

Gaps fixed

1. Production and preview could build different toolchains. The editor tag and template version were duplicated in four places — the composite action's defaults, workflow_dispatch defaults, and inline || '4.7-stable' fallbacks in the production job — while preview passed nothing and inherited the action defaults. Bumping one and not the others meant /preview validated a toolchain production never built. All versions now come from .github/web-toolchain.env.

2. The drift guard couldn't catch a patch-level mismatch. It compared only major.minor, so 4.7.0 vs 4.7.1 passed silently — the most likely kind of bump. It now normalises all three spellings (4.7.1 / 4.7.1.stable.mono / 4.7.1-stable), requires exact agreement including the editor tag, and confirms a checksum is pinned before downloading a 165 MB editor.

3. Checksum verification could be bypassed by the cache. It matched the first *.zip in the cache directory rather than the expected filename. Now matched exactly, and the cache key includes the fork repo.

Toolchain bump to 4.7.1-stable

Upstream 4.7.1 is a stability-only release (78 fixes, no known incompatibilities with 4.7). Superseded checksums are retained so a rollback needs no checksum work.

Upstream verdict

Official C#/.NET web export is still not supported, so the hack stays — but the strategy pivoted favourably: LibGodot Core (#110863) merged and shipped in Godot 4.6, with #121502 (LibGodot on web) and #118976 (.NET on top) still open at milestone 4.x. Direction is good; no date. Full status, exit criteria and migration plan in the new docs/WEB_EXPORT_ROADMAP.md.

Verification

  • Drift-guard logic executed against all drift permutations — the three cases the old guard let through are now caught; every legitimate combination still passes.
  • Both editor checksums computed from the published assets. The 4.7-stable hash reproduces the existing pin exactly, which validates the method and therefore the new 4.7.1 pin.
  • All YAML parses; every relative doc link and anchor resolves.
  • test check green.

Docs

docs/WEB_EXPORT_ROADMAP.md (new). docs/WEB_EXPORT.md corrected — DNS for maze.ryankelly.dev resolves now, and the fork-abandonment risk is downgraded with evidence (it matched upstream 4.7.1 within two days). AGENTS.md gains the web-only failure modes, which pass desktop CI and break only in the browser.

Ordering

Merge #7 (GitHub Actions pinning) before or after this — both touch test.yml/web-export.yml, so expect a small conflict either way. Then #6's stack: this → #10#11#6.


Generated by Claude Code

….7.1 bump

Closes three gaps in the experimental C#/WASM export pipeline and adds a plan
for getting it onto officially supported foundations.

Fixes:

- Production and preview could build different toolchains. The editor tag and
  template version were duplicated in four places (composite action defaults,
  workflow_dispatch defaults, and inline fallbacks in the production job) while
  the preview job passed nothing and inherited the action defaults. Updating one
  and not the others meant /preview validated a toolchain production never used.
  All versions now come from .github/web-toolchain.env; action inputs default to
  empty and act purely as deliberate overrides.

- The drift guard compared only major.minor, so a 4.7.0-vs-4.7.1 mismatch passed
  silently -- the most likely kind of bump. It now normalises all three spellings
  of a version (4.7.1 / 4.7.1.stable.mono / 4.7.1-stable), requires exact
  agreement including the editor tag, and handles Godot's convention that x.y
  releases carry no patch component. It also verifies a checksum is pinned for
  the target asset before downloading a 165 MB editor rather than after.

- Checksum verification matched the first zip found in the cache directory; it
  now requires the expected filename, so a restored cache cannot substitute a
  differently-named editor. Cache key includes the fork repo as well as the tag.

Toolchain bump to 4.7.1-stable (upstream 4.7.1 is a stability-only release with
no known incompatibilities with 4.7). Superseded checksums are retained so a
rollback needs no checksum work.

Docs:

- docs/WEB_EXPORT_ROADMAP.md (new): upstream status with evidence, definition of
  done, four-phase migration plan, risk register, quarterly monitoring routine
  and rollback procedure. Records that LibGodot Core (#110863) merged and shipped
  in Godot 4.6, that the approach pivoted away from the stalled #106125 to
  LibGodot, and that #121502 and #118976 remain open at milestone 4.x -- so
  official C# web export is still unscheduled.
- docs/WEB_EXPORT.md: corrected the DNS section (maze.ryankelly.dev resolves),
  softened the fork-abandonment risk (the fork matched upstream 4.7.1 within two
  days), and repointed the version matrix at the new single source of truth.
- AGENTS.md: added the web-only failure modes (invariant globalization, missing
  crypto BCL APIs, no GDExtension), which pass desktop CI and break only in the
  browser.

Not verified here: no .NET SDK in this environment, so the desktop build against
Godot.NET.Sdk 4.7.1 is unbuilt. The version-resolution and drift-guard logic was
executed against all drift permutations, and both editor checksums were computed
from the published assets (the 4.7-stable hash reproduces the existing pin).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwhKDz2xEVEtYECxT7q5F3
claude added 2 commits July 25, 2026 21:45
…sm data race

`[assembly: Parallelizable(ParallelScope.All)]` was combined with NUnit's default
SingleInstance lifecycle, so test cases in a fixture ran concurrently against one
shared instance. Every field assigned in `[SetUp]` was therefore a data race.

It surfaced as two NullReferenceExceptions in RandomValueTests on CI, on a commit
whose other runs of the same job were green — re-running the identical commit
passed, confirming a race rather than an environment difference. The mechanism:

  _mazePointFactory = new Mock<IMazePointFactory>();                 // (a) bare mock
  _mazePointFactory.Setup(x => x.MakePoint(...)).Returns(...);       // (b) configures it
  _randomPoint = new RandomPointGenerator(_random,
                     _mazePointFactory.Object);                      // (c) re-reads the field

Another test's (a) landing between this test's (b) and (c) makes (c) capture an
unconfigured mock. Moq then returns default(MazePoint) — null — and the test dies
dereferencing `point.X`, nowhere near the actual cause.

Measured with a trace harness (NUnit's per-test console capture reorders output and
hides this): with SingleInstance, four concurrent `[SetUp]` bodies share one fixture
instance; with InstancePerTestCase, four concurrent tests get four distinct
instances. Parallelism is unchanged, the unsafe sharing is gone.

Also drops MovementHelperTests' `[NonParallelizable]`, which was a local workaround
for this same root cause, and adds a guard test so removing the attribute fails
immediately by name instead of resurfacing as an occasional unexplained flake.
This branch and `main` had no Node or Playwright ignore rules, so nothing stopped
an `npm ci` in tests/visual/ from being committed. That is exactly how 170 files /
17.7 MiB of node_modules got into the GitHub-Actions branch (#7), which is also
based on `main`.

Later branches in this stack do carry `tests/visual/`-anchored rules, added with the
visual-regression harness. These are deliberately repo-wide rather than anchored:
the trap is a node_modules appearing somewhere the anchored pattern doesn't cover.

Baseline screenshots under *-snapshots/ stay tracked; only regenerated run output
is ignored.
@rtkelly13
rtkelly13 force-pushed the claude/split-1-web-toolchain-3a6h5m branch from 419235e to 13e49ae Compare July 26, 2026 08:21
rtkelly13 pushed a commit that referenced this pull request Jul 26, 2026
This branch committed 170 files / 17.7 MiB of tests/visual/node_modules, plus a
Playwright test-results/.last-run.json, because neither this branch nor `main` had
any Node or Playwright ignore rules.

The blobs are removed from this branch's history rather than deleted in a follow-up
commit, so they never reach `main` at all — a later deletion would leave 17.7 MiB
permanently in the repository.

Identical to the block added on the #8 stack, so whichever merges to main first, the
other merges without conflict. Baseline screenshots under *-snapshots/ stay tracked.
rtkelly13 added a commit that referenced this pull request Jul 26, 2026
* ci: upgrade GitHub Actions to current majors

GitHub now warns that actions/checkout@v4 and actions/setup-dotnet@v4 target
Node 20 and are being force-run on Node 24. This moves every action to its
current major, all of which declare the node24 runtime.

  actions/checkout          v4 -> v7   (5 uses)
  actions/setup-dotnet      v4 -> v6   (2 uses)
  actions/setup-node        v4 -> v7   (2 uses)
  actions/cache             v4 -> v6   (1 use)
  actions/upload-artifact   v4 -> v7   (1 use)

These are multi-major jumps, so the release notes were checked for breaking
changes rather than bumping blind. Four are relevant, none of them bite here:

- checkout v5+ refuses to check out fork PR code by default, needing
  `allow-unsafe-pr-checkout: true` to restore the old behaviour. The guard fires
  only on `pull_request_target` and `workflow_run`. This repo triggers on push,
  pull_request, workflow_dispatch and issue_comment, so nothing changes -- notably
  the /preview job's PR-head checkout is unaffected. (That job's own trust
  boundary is unchanged by this upgrade: it still runs owner-only.)
- setup-dotnet v5+ dropped support for older .NET versions. This repo pins 9.0.x
  and targets net8.0/net9.0, so it is unaffected.
- setup-node v5+ enables automatic caching when package.json has a
  `packageManager` field, narrowed to npm in v6. No package.json in this repo sets
  that field, so no caching behaviour changes and no lockfile is required.
- cache/upload-artifact v5+ require Actions Runner 2.327.1+. Only GitHub-hosted
  runners are used, which are well past that.

Verified: fetched action.yml at each new major and confirmed every input this
repo passes still exists (11 steps cross-checked programmatically), that
cache@v6 still exposes the `cache-hit` output the export action reads, and that
upload-artifact@v7 still accepts `if-no-files-found`. All three YAML files parse.

Coverage note: only test.yml runs on a pull request, so these checks exercise
checkout@v7 and setup-dotnet@v6 alone. The setup-node, cache and upload-artifact
bumps live in web-export.yml, which runs only on merge to main or an explicit
/preview -- they are validated by schema, not by execution. Worth a /preview
before merging if you want them exercised first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwhKDz2xEVEtYECxT7q5F3

* ci: pin actions to commit SHAs with version comments

Adopts the pinning convention already used across the other repos in this
account -- `uses: <action>@<40-char commit sha> # v<semver>` -- which this repo
was the only one not following (0 of 13 pinned, against 100% in blog,
github-actions, platform, rights-application, sentric-frontends, workspace and
data-platform, and the large majority in the-vault-system and shared-utilities).

A moving major tag can be repointed at new code by whoever controls the action,
so the tag alone is not a supply-chain control. This repo already checksum-pins
the third-party Godot editor binary for exactly that reason; the actions were the
remaining gap.

  actions/checkout        @3d3c42e5 # v7.0.1   (5 uses)
  actions/setup-dotnet    @a98b5685 # v6.0.0   (2 uses)
  actions/setup-node      @82076278 # v7.0.0   (2 uses)
  actions/cache           @55cc8345 # v6.1.0   (1 use)
  actions/upload-artifact @043fb46d # v7.0.1   (1 use)

Full semver in the comment, matching the dominant form in the other repos
(platform, github-actions, the-vault-system) rather than the bare-major form in
blog, since the exact patch is the thing a reader wants to check.

Verification -- the failure mode of this convention is a comment that drifts from
its SHA, so both directions were checked:

- Resolved every SHA from upstream tags, then confirmed the method by resolving
  four pins that already exist in the other repos (checkout v7.0.0, setup-node
  v7.0.0, cache v6.1.0, setup-dotnet v5.2.0) and matching them byte for byte.
- Re-derived each committed pin from its own comment: 5 distinct pins, 0
  mismatched.
- Two SHAs are byte-identical to pins already in platform (setup-node v7.0.0,
  cache v6.1.0), so those are corroborated by a second source.
- All three YAML files parse; no unpinned third-party actions remain.

Where this differs from the other repos: checkout and upload-artifact go to
v7.0.1 rather than the v7.0.0 pinned elsewhere, and setup-dotnet to v6.0.0 rather
than v5.2.0 -- these are the current latest, and a fresh pin may as well start
there. Say the word to align them downward instead.

No Dependabot config added: no repo in this account has one, so these pins are
maintained by hand and this change does not alter that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwhKDz2xEVEtYECxT7q5F3

* chore: gitignore node_modules and Playwright run output

This branch committed 170 files / 17.7 MiB of tests/visual/node_modules, plus a
Playwright test-results/.last-run.json, because neither this branch nor `main` had
any Node or Playwright ignore rules.

The blobs are removed from this branch's history rather than deleted in a follow-up
commit, so they never reach `main` at all — a later deletion would leave 17.7 MiB
permanently in the repository.

Identical to the block added on the #8 stack, so whichever merges to main first, the
other merges without conflict. Baseline screenshots under *-snapshots/ stay tracked.

---------

Co-authored-by: Claude <noreply@anthropic.com>
claude and others added 2 commits July 26, 2026 08:56
Each composite-action step now invokes a dedicated script under
.github/actions/export-web/scripts/, keeping action.yml to wiring
(env, ids, outputs). The drift-guard error message becomes a plain
here-string now that it lives outside a YAML block scalar.

All scripts parse clean; resolve-toolchain and check-version-drift
verified locally including the drift-failure path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rtkelly13
rtkelly13 merged commit f15c5d7 into main Jul 27, 2026
1 check passed
@rtkelly13
rtkelly13 deleted the claude/split-1-web-toolchain-3a6h5m branch July 27, 2026 20:57
rtkelly13 added a commit that referenced this pull request Jul 27, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rtkelly13 added a commit that referenced this pull request Jul 27, 2026
AGENTS.md keeps this branch's testing/session sections and re-adds the
Repository Conventions section from main (deduplicated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rtkelly13 added a commit that referenced this pull request Jul 27, 2026
…a6h5m

Both this branch and main had extracted the composite action's inline
PowerShell into script files; this branch's extraction (17 scripts,
mechanically lifted and diffed) supersedes main's 8, so its versions are
kept and main's now-unreferenced download-editor.ps1 is removed.
Re-adds main-only .gitignore entries and the AGENTS.md Repository
Conventions section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rtkelly13 added a commit that referenced this pull request Aug 1, 2026
…t rendering (#13)

The 4.7.1 web export (from #8) ships with corrupted texture uploads:
every texImage2D/bufferData call errors ('ArrayBufferView not big
enough' / 'srcOffset + length too large'), leaving all UI text as
garbled boxes on maze.ryankelly.dev. Bisected via
preview-maze.ryankelly.dev: the 4.7.0 build of 038e196 renders
cleanly with zero WebGL errors.

Rolls GODOT_FORK_TAG, GODOT_TEMPLATE_VERSION and Godot.NET.Sdk back
together per the drift guard. The 4.7-stable checksum entry was
retained in editor-checksums.txt by design.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants