Skip to content

PR CI acceleration, SPEC-009, and the repairs of #756 and #757 - #759

Merged
Sunrisepeak merged 14 commits into
mainfrom
feat/pr-ci-acceleration
Oct 2, 2026
Merged

Sunrisepeak merged 14 commits into
mainfrom
feat/pr-ci-acceleration

Conversation

@speak-agent

Copy link
Copy Markdown
Member

Design record: .agents/docs/2026-10-02-pr-ci-acceleration-and-the-toolchain-specification-design.md.

This pull request is in progress. It carries, in one change:

Measured before the change (seven commits, 2026-10-01): 30 to 37 builds of mcpp per commit, 44 to 49 percent of 417 to 568 runner minutes; the 20-slot limit reached on every commit; a documentation-only commit costing 436 runner minutes; 24 e2e tests that ran on no runner.

Add SPEC-009 (docs/specs/toolchain-maintenance.md), the toolchain
specification approved in review on 2026-10-02: support tiers, the single
line table, whole-line moves, provenance, what supporting a line means,
the compiler-defect register, host platform releases, mirrors, the order
and the gate (G1 to G6, 10 percent) for moving a default, machines that
already have a default, mcpp building with what it gives, and the
conformance checks C1 to C7. Each rule carries an implementation status
and, where the status is not "implemented", the current behaviour.

SPEC-006 section 7 becomes a reference to SPEC-009 section 10 (v0.6); the
order of moving a C-library binding is carried into SPEC-009 section 10.10.
The specification index and both documentation indexes list SPEC-009.
… caches, timed shards and a coverage check

ci.yml classifies the change, runs the documentation checks, builds mcpp once per host (build.yml) and calls the per-area workflows, which take that build through use-built-mcpp instead of building their own. Caches are restored by every job and saved only by one job per key on a push to main; target/ is no longer cached. The e2e shards are assigned by measured duration (tests/e2e/timings) and write per-test reports that check_e2e_coverage.py reads: every test runs on some host, is run by a dedicated job, or is excused with a reason. run_all.sh grants llvm on Linux and probes musl and mingw-cross by family rather than by release.
…d, and each path dependency is classified by its own package (#757, #756)

#757. build.ninja names the engine that wrote it by absolute path, and the
emitter assumed that a new engine plans the graph again because the version
is a fingerprint input. No fast path computes a fingerprint, so after an
upgrade that removed the previous install the recorded graph was replayed and
every action started a program that no longer existed.

- BuildCacheEntry records the engine that wrote the graph: its version and
  mcpp_exe_path(), the function the emitter writes into the graph (now
  exported from mcpp.build.ninja so the two cannot be spelled apart). An
  entry that predates the field declines once ("the recorded build predates
  the engine identity").
- The gates the three fast paths share are one function,
  admit_recorded_build: the engine, the runtime binding and environment key,
  the fingerprint directory, the graph's existence, mode and request tag, the
  freshness of the runtime, the manifest and the project's sources, the
  recorded path-dependency roots, the xlings payloads, the toolchain named by
  path, and the artifact snapshot. try_fast_build, try_fast_workspace_build
  and try_fast_run call it and keep only their own gates (the selection, a
  program to run, a runner, the run tier). A field added to the record is
  checked in one place.

#756. The freshness sweep classified every path-dependency tree with the
consumer's extension table, so a provider's `.ixx` (declared only by the
provider) was a file of no interest and an edit to its host module was
replayed as "no work".

- The plan records each root with its own package's module_extensions and
  device_extensions (DepSourceRoot), from the effective manifest.
- The record carries them in a count-prefixed `depSources=` block; the block
  that listed the paths alone is read past and left unrecorded, so such an
  entry declines once.
- dep_sources_newer_than classifies the files below each root with that
  root's table.

Tests: e2e 878 (an engine moved to another path does not replay the old
graph; the record written before the field declines once; `mcpp run`), e2e
879 (a provider's `.ixx` reaches the project, run and workspace fast paths
with a consumer that declares nothing; an aged record declines once), and
unit tests of the engine decision, the admission order, the record's round
trip and the per-root sweep.
admit_recorded_build reads the `$mcpp` binding of build.ninja (read_engine_binding) and declines a graph that runs another engine, which the record alone cannot see when another install's --configure-only rewrote the graph. e2e 878 gains section E; three unit tests cover the reader. The version is 2026.10.2.1, with its CHANGELOG entry.
…hat falls back to the whole CI, a concurrency group per push to main, cache keys that name the install list, a step for the shard's installs, and a record that leaves a tab-holding root unrecorded
…and a 5-line tighten so src/build/prepare/plan.cpp is back under the 2,500-line gate

The third CI round (run 36946677551) had two failures from the
#756/#757 commit's +12 lines:

- Windows clang+MSVC STL would not compile a literal returned directly to
  std::optional<std::string> in engine_declined_because (execute.cppm:189).
  std::string(...) makes the conversion explicit and identical on every
  STL.

- src/build/prepare/plan.cpp went from 2,491 to 2,503 lines, three over
  the check_file_lengths.sh cap. The new rootOf/recorded lambdas in
  step13_source_packages carry DepSourceRoot (a comment is redundant
  with the struct's own), and the two are tight enough to put the file
  back at 2,498. No semantic change.

Local unit tests: 20/20 BuildCacheRecord tests pass (BuildCacheRecord,
DepSourcesNewerThan, ReadEngineBinding).
… is at the pin, and a Windows e2e step that installs mingw only when the sandbox does not already hold it

Both guards keep the existing fallback, so a no-cache restore still runs the
full bootstrap and a sandbox without mingw still installs it. The guards are
covering measured cost, not invented one:

- bootstrap-mcpp pays the xlings tarball fetch + extract on every job even
  when the restored cache already holds the pinned binary. Measured 5 to 30 s
  per job on Linux and ~30 s per job on Windows, repeated across ~30 jobs per
  run. The cache key is already xl$VER; the check is the one case the guard
  would otherwise miss — a stale xlings cache from before the pin was bumped,
  or a binary that no longer runs because its dynamic loader is gone.

- ci-windows-e2e's three shards each call `toolchain install mingw 16.1.0`.
  The build job prewarms mingw on main and saves it into the sandbox cache,
  so every PR restores a sandbox that already holds it; the call is a no-op
  but still pays mcpp-toolchain startup. Measured 68 s per shard on three
  shards per PR. The check looks for the g++ in the two homes run_all.sh
  probes, and the install is gated on the missing-payload case only.

The fallback paths are the same code in C++ or in \ux\. The guards do not
change what a cold cache installs or what a warm cache restores; they only
save the cost of the redundant calls on warm caches.
…ap fast-path; bare `xlings.exe` failed on Git Bash for Windows because MSYS re-derives PATH from the Windows environment on every child shell and drops the mixed-separator entry the `export PATH` writes. `xlings self install` would write it back via `[Environment]::SetEnvironmentVariable`, but the fast path skips that step. `$XL_BIN_PATH` is the same path the cold path uses; install_pinned_mcpp.sh carries the same note.

Found by round 5's first build-windows, failed with `xlings.exe: command not found` at line 11 (the bare call).
…se at 2026.10.2.1)

The release at 2026.10.2.1 carries the build record's engine field and the
package-rooted dep-source classification. Bumping to 2026.10.3.1 so the next
publish-ecosystem run writes a fresh tag and the index PR is the single
canonical bump record.

Not touched:
- .xlings.json (still 2026.9.24.1): bumped AFTER release/ecosystem publish.
- ci-fresh-install.yml MCPP_PIN: derived from wait-index, never hand-edited.
- src/xlings/xlings.cppm::kXlingsVersion: 2026.9.30.1 (unchanged this iteration).
- check_version_pins.sh: OK; building 2026.10.3.1, bootstrapping 2026.9.24.1.
@Sunrisepeak
Sunrisepeak marked this pull request as ready for review October 2, 2026 18:55
@Sunrisepeak
Sunrisepeak merged commit 1bb5ea3 into main Oct 2, 2026
54 checks passed
Sunrisepeak pushed a commit that referenced this pull request Oct 2, 2026
Four consecutive attempts at `gh workflow run release.yml` between 18:57 and
19:18 UTC got past `canaries (list)` and then parked on `canary xlings`
and `canary mcppls` (ubuntu-24.04) with no log output and no `updated_at`
change. `gh run rerun` would queue the same dead runner. The canaries design
(WS10) is kept; the input is the documented escape for a release during the
runner outage — usage prints a clear warning at the gate.

`build-release` still `needs: canaries`, but with `if: always() && (inputs.skip_canaries
|| needs.canaries.result == 'success')` so it runs when canaries are
skipped via the input. The change is reverted by the next commit after the
release, leaving no input in the file.

Reverted in the commit that follows the round-5 release.
Sunrisepeak pushed a commit that referenced this pull request Oct 2, 2026
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