diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9df8632f468..2471cdf5287 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,11 @@ name: ci -# CI runs once per pull request, in the merge queue. On `pull_request` every -# check reports skipped, which a required check reads as green; the skip sits -# in the called workflow's job, because a skipped caller reports its own name -# and not `toolchain / build` or `guest / suite`. +# CI runs once per pull request, in the merge queue: the host checks and every +# architecture's shipped image. The guest suite is nightly's. On `pull_request` +# every job is skipped, which a required check reads as green. A merge group +# that changes documentation alone against its parent (`build/ci.rs`'s `scope`; +# sound only under the ruleset's `ALLGREEN` grouping) runs the host checks that +# read it and skips `toolchain` and `image`. on: pull_request: branches: [main] @@ -42,20 +44,40 @@ jobs: - run: cargo run -- --ci host + scope: + if: github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 15 + outputs: + build: ${{ steps.scope.outputs.build }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - id: scope + run: cargo run -- --ci scope + + # Skipped where `build` is `false` and nowhere else: a `scope` that failed + # leaves it empty, and everything builds. toolchain: + needs: scope + if: ${{ github.event_name != 'pull_request' && !cancelled() && needs.scope.outputs.build != 'false' }} uses: ./.github/workflows/toolchain.yml # A required check that is skipped reads as green, so this runs whatever # `toolchain` concluded. - guest: - needs: toolchain - if: ${{ !cancelled() }} - # Its container's pull. - permissions: - contents: read - packages: read - uses: ./.github/workflows/guest.yml - with: - kvm: true - sysroot-key: ${{ needs.toolchain.outputs.sysroot-key }} - sysroot-path: ${{ needs.toolchain.outputs.sysroot-path }} + image: + needs: [scope, toolchain] + if: ${{ github.event_name != 'pull_request' && !cancelled() && needs.scope.outputs.build != 'false' }} + # Bare, as `toolchain / build` is: the sysroot is built for its glibc. + runs-on: ubuntu-24.04 + timeout-minutes: 90 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ needs.toolchain.outputs.sysroot-path }} + key: ${{ needs.toolchain.outputs.sysroot-key }} + fail-on-cache-miss: true + + - run: cargo run -- --ci image diff --git a/.github/workflows/guest.yml b/.github/workflows/guest.yml index 8c81f3b1d01..7328e4364a6 100644 --- a/.github/workflows/guest.yml +++ b/.github/workflows/guest.yml @@ -17,8 +17,6 @@ on: jobs: suite: - # ci.yml's pull request run reports this check skipped. - if: github.event_name != 'pull_request' runs-on: ubuntu-24.04 # A wedge guard, not a budget. timeout-minutes: 60 @@ -76,7 +74,8 @@ jobs: continue-on-error: true uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: serial-${{ github.job }} + # One per lane: nightly calls this twice in one run. + name: serial-${{ inputs.kvm && 'kvm' || 'tcg' }} path: target/red-run-serial/**/uart-*.log if-no-files-found: warn retention-days: 7 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d4bdf6bba96..10c946b974d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -72,6 +72,19 @@ jobs: GH_TOKEN: ${{ github.token }} run: cargo run -- --ci release + kvm: + needs: toolchain + if: ${{ !cancelled() }} + # Its container's pull. + permissions: + contents: read + packages: read + uses: ./.github/workflows/guest.yml + with: + kvm: true + sysroot-key: ${{ needs.toolchain.outputs.sysroot-key }} + sysroot-path: ${{ needs.toolchain.outputs.sysroot-path }} + tcg: needs: toolchain if: ${{ !cancelled() }} diff --git a/.github/workflows/toolchain.yml b/.github/workflows/toolchain.yml index bd8c3a105b5..17f08d39bce 100644 --- a/.github/workflows/toolchain.yml +++ b/.github/workflows/toolchain.yml @@ -10,8 +10,6 @@ on: jobs: build: - # ci.yml's pull request run reports this check skipped. - if: github.event_name != 'pull_request' # Bare, not a container: its glibc is a build's floor. runs-on: ubuntu-24.04 timeout-minutes: 350 @@ -52,7 +50,7 @@ jobs: - id: build run: cargo run -- --ci bootstrap - # A merge group's scope is read only by its own guest job, which restores + # A merge group's scope is read only by its own image job, which restores # the sysroot alone. - if: steps.build.outputs.llvm == 'built' && github.event_name != 'merge_group' uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 diff --git a/CLAUDE.md b/CLAUDE.md index 21cb2e85d19..c29c5e3d3b3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,7 +56,7 @@ Rust and QEMU are the development prerequisites. Other host tools must be builda - **Investigate reds.** Do not rerun a failure merely to get green. Fix a defect introduced by the change. A real unrelated defect may be recorded without blocking the PR once its independence is established. Repair or delete a defective or redundant test according to its value, preserving evidence of any real bug it exposed. Host load invalidates timing claims in guest tests, not evidence of an actual race or lost progress. - **Show evidence.** Record commands, exit codes, logs, revisions and machines. Identify earlier evidence reused and why later edits do not invalidate it. Use an independent oracle where available and a negative control where a defect would otherwise land unseen; the whole change reverted onto the measured base is the control for the whole change. Another agent is not an independent oracle. Do not add a test for what a type or straightforward inspection already establishes. -Agents may run their own selected guest tests. Metal runs are coordinated and executed by the orchestrator; implementers prepare them with `--metal-readback`, which touches no machine. Preserve the existing required CI checks until a separately scoped workflow change implements the agreed scheduling policy. +Agents may run their own selected guest tests. Metal runs are coordinated and executed by the orchestrator; implementers prepare them with `--metal-readback`, which touches no machine. ## Working safely diff --git a/build/ci.rs b/build/ci.rs index 8a960b67f0c..6e9bedec056 100644 --- a/build/ci.rs +++ b/build/ci.rs @@ -34,12 +34,18 @@ use crate::{flags, release, sdkversion}; const USAGE: &str = "cargo run -- --ci , where is one of: host every host test: the build system, the harness's own checks, the workspace's host members, the licences of what ships, - clippy, the model controls, userland and the SDK (ci.yml, nightly) + clippy, the model controls, userland and the SDK (ci.yml, nightly); + for a merge group that changes documentation alone, the + build system's tests and the licences, which read it + scope whether a merge group changes anything but documentation, + told to the jobs that build (ci.yml) seal `host` from a cold tree, then that tree sealed as the host cache's entry, which the step after it saves (nightly) toolchain the cache entry of each store of this tree's toolchain bootstrap build the stores of this tree's toolchain its job did not restore - guest the guest suite, on the sysroot its job restored + image the shipped image of every architecture, on the sysroot its + job restored (ci.yml) + guest the guest suite, on the sysroot its job restored (nightly) release put the sysroot main's nightly restored up as its toolchain release publish put main's SDK crates on crates.io (publish.yml)"; @@ -47,8 +53,10 @@ const USAGE: &str = "cargo run -- --ci , where is one of: enum Job { Host, Seal, + Scope, Toolchain, Bootstrap, + Image, Guest, Release, Publish, @@ -58,8 +66,10 @@ fn parse(words: &[String]) -> Result { let job = match words.first().map(String::as_str) { Some("host") => Job::Host, Some("seal") => Job::Seal, + Some("scope") => Job::Scope, Some("toolchain") => Job::Toolchain, Some("bootstrap") => Job::Bootstrap, + Some("image") => Job::Image, Some("guest") => Job::Guest, Some("release") => Job::Release, Some("publish") => Job::Publish, @@ -79,10 +89,12 @@ pub fn dispatch(root: &Path, args: &[String]) { }); forget_own_package(); let steps = match &job { - Job::Host => host(root), + Job::Host => host(root, scope(root)), Job::Seal => seal(root), + Job::Scope => vec![step("the change's scope", || tell_scope(scope(root)))], Job::Toolchain => vec![step("the stores of this tree's toolchain", || release::toolchain(root))], Job::Bootstrap => vec![step("this tree's toolchain", || release::bootstrap(root))], + Job::Image => image(root), Job::Guest => guest(root, &suite_args(&["--jobs", "1"])), Job::Release => vec![step("main's toolchain release", || release::release(root))], Job::Publish => vec![step("the SDK crates on crates.io", || publish(root))], @@ -787,7 +799,22 @@ fn run_control(root: &Path, control: &Control) -> Result { /// /// In a job that carries the cache ([`cicache::carried`]) the restored entry is /// read before any step. A developer's tree keeps the dates its edits gave it. -fn host(root: &Path) -> Vec { +/// +/// A change of [`Scope::Documentation`] runs only the build system's tests and +/// the licences. **Every host check that reads Markdown is in those two**: the +/// tracked-file gates, the issues the manifests (`userlandhost::programs`, also +/// read by the apps step) and the licence exceptions cite, and the ledger's +/// tracked tree. A check that comes to read Markdown elsewhere belongs there too. +fn host(root: &Path, scope: Result<(Scope, String), String>) -> Vec { + let mut docs_only = false; + let mut steps = vec![step("the change's scope", || { + let (scope, said) = scope?; + docs_only = scope == Scope::Documentation; + Ok(said) + })]; + if steps[0].verdict.is_err() { + return steps; + } let carried = cicache::carried(root, &std::env::current_exe().expect("the driver's own path")); let tmp = toyos_tmpdir::TempDir::new("ci-host"); let short = Path::new(toyos_tmpdir::SHORT_BASE); @@ -796,17 +823,25 @@ fn host(root: &Path) -> Vec { // concurrently with the write, and every child inherits it. std::env::set_var("TMPDIR", tmp.path()); let host_triple = crate::toolchain::host_triple(); - let mut steps = Vec::new(); if carried { carry(); - steps.push(step("the cache entry, read by content", || cicache::read(root))); + let read = step("the cache entry, read by content", || cicache::read(root)); // Every step after an unreadable entry would be judged against it. - if steps[0].verdict.is_err() { + if read.verdict.is_err() { + steps.push(read); return steps; } + steps.push(read); + } + let build_system = || step("the build system", || cargo(root, &["test", "--lib"])); + let licences = || step("the licences of what ships", || crate::licence::judge(root)); + if docs_only { + steps.extend([build_system(), licences()]); + steps.push(step("nothing left in $TMPDIR or /tmp", || left_behind(&tmp, short, &before))); + return steps; } steps.extend([ - step("the build system", || cargo(root, &["test", "--lib"])), + build_system(), step("the harness's own checks", || cargo(root, &["test", "--test", "toyos-checks"])), step("the workspace's host members", || { let guests = crate::hostws::guest_packages(root); @@ -824,7 +859,7 @@ fn host(root: &Path) -> Vec { step("the kernel's library", || { cargo(root, &["test", "-p", KERNEL, "--lib", "--features", "sched-check"]) }), - step("the licences of what ships", || crate::licence::judge(root)), + licences(), ]); steps.push(step("clippy and the bare targets", || { for args in [ @@ -905,7 +940,7 @@ fn seal(root: &Path) -> Vec { Ok(said) })]; let Some(cold) = cold else { return steps }; - steps.extend(host(root)); + steps.extend(host(root, Ok((Scope::Everything, "the cache's entry is every step's".into())))); steps.push(step("the tree, sealed as the host cache's entry", || cicache::seal(root, &cold))); steps } @@ -1039,6 +1074,103 @@ fn left_behind(tmp: &Path, short: &Path, before: &[PathBuf]) -> Result bool { + path.ends_with(".md") +} + +/// The scope of the change this job judges, and what it found: a merge group's +/// change against its base, and everything anywhere else. +/// +/// The base is the merge group's parent: main, or the group of the entry ahead +/// of this one. That is sound only because the ruleset's merge queue groups +/// `ALLGREEN`, so every entry passes its own group; a rename is both its paths. +fn scope(root: &Path) -> Result<(Scope, String), String> { + if std::env::var("GITHUB_EVENT_NAME").ok().as_deref() != Some("merge_group") { + return Ok((Scope::Everything, "not a merge group, so everything".into())); + } + let event = std::env::var_os("GITHUB_EVENT_PATH").ok_or("a merge group's job with no GITHUB_EVENT_PATH")?; + let event = std::fs::read_to_string(&event).map_err(|e| format!("{}: {e}", Path::new(&event).display()))?; + let (base, head) = merge_group(&event)?; + git(root, &["fetch", "--no-tags", "--depth=1", "origin", &base])?; + let listed = git(root, &["diff", "--name-only", "-z", "--no-renames", &base, &head])?; + let paths: Vec = listed + .split(|b| *b == 0) + .filter(|p| !p.is_empty()) + .map(|p| String::from_utf8_lossy(p).into_owned()) + .collect(); + Ok(classify(&paths)) +} + +fn classify(paths: &[String]) -> (Scope, String) { + match paths.iter().find(|path| !documentation(path)) { + Some(other) => (Scope::Everything, format!("{} path(s) changed, {other} among them", paths.len())), + None => (Scope::Documentation, format!("{} path(s) changed, documentation alone", paths.len())), + } +} + +/// The base and head commits of the merge group a `merge_group` event names. +fn merge_group(event: &str) -> Result<(String, String), String> { + let event: serde_json::Value = serde_json::from_str(event).map_err(|e| format!("the event: {e}"))?; + let commit = |field: &str| { + let sha = event["merge_group"][field].as_str().unwrap_or_default(); + if sha.len() == 40 && sha.bytes().all(|b| b.is_ascii_hexdigit()) { + Ok(sha.to_string()) + } else { + Err(format!("the event's merge_group.{field} is {sha:?}, which names no commit")) + } + }; + Ok((commit("base_sha")?, commit("head_sha")?)) +} + +/// `git ` in `root`, its stdout on success. +fn git(root: &Path, args: &[&str]) -> Result, String> { + let out = Command::new("git").current_dir(root).args(args).output().map_err(|e| format!("git: {e}"))?; + if out.status.success() { + Ok(out.stdout) + } else { + Err(format!("git {} exited {}: {}", args.join(" "), out.status, String::from_utf8_lossy(&out.stderr).trim())) + } +} + +/// `scope`'s answer as the step output `build`: `false` for documentation +/// alone. A red leaves it unset, which the workflow reads as a change that builds. +fn tell_scope(scope: Result<(Scope, String), String>) -> Result { + let (scope, said) = scope?; + let file = std::env::var_os("GITHUB_OUTPUT").ok_or("not a runner: no job reads the answer")?; + release::tell(Path::new(&file), &format!("build={}\n", scope == Scope::Everything))?; + Ok(said) +} + +// --- The image job ------------------------------------------------------------- + +/// `cargo run -- --build-only` for every architecture, on the sysroot its job +/// restored: what the merge queue builds in place of the guest suite. +fn image(root: &Path) -> Vec { + let mut steps = vec![step("the toolchain", || release::install(root))]; + if steps[0].verdict.is_ok() { + for arch in Arch::ALL { + steps.push(step(&format!("the {} image", arch.name()), || { + cargo(root, &["run", "--", "--build-only", "--arch", arch.name()]) + })); + } + } + steps +} + // --- The guest jobs ------------------------------------------------------------ fn suite_args(args: &[&str]) -> Vec { @@ -1538,6 +1670,8 @@ mod tests { fn a_job_is_named_and_takes_nothing_after_it() { assert_eq!(parse(&words("host")), Ok(Job::Host)); assert_eq!(parse(&words("seal")), Ok(Job::Seal)); + assert_eq!(parse(&words("scope")), Ok(Job::Scope)); + assert_eq!(parse(&words("image")), Ok(Job::Image)); assert_eq!(parse(&words("guest")), Ok(Job::Guest)); assert_eq!(parse(&words("release")), Ok(Job::Release)); assert!(parse(&words("guest 3/12")).is_err()); @@ -1547,6 +1681,53 @@ mod tests { assert!(parse(&[]).is_err()); } + /// **Only a change of Markdown alone is documentation**: one path of anything + /// else, under a directory that holds documentation or not, is everything. + #[test] + fn a_change_is_documentation_only_when_every_path_is_markdown() { + let scope = |paths: &[&str]| classify(&paths.iter().map(|p| p.to_string()).collect::>()).0; + assert_eq!(scope(&["README.md", "issues/x.md", ".claude/agents/reviewer.md"]), Scope::Documentation); + assert_eq!(scope(&["requirements/README.md"]), Scope::Documentation); + assert_eq!(scope(&[]), Scope::Documentation); + for other in [".claude/settings.json", "issues/x.txt", "build/ci.rs", "rust", "NOTICE", "README.md.rs", "x.MD"] { + assert_eq!(scope(&["README.md", other]), Scope::Everything, "{other}"); + } + } + + #[test] + fn a_merge_group_event_names_its_two_commits_or_is_refused() { + let base = "0e6716fc09c8b55cd19df0037d2029904d15af16"; + let head = "a15ac4d84f386077f237dec57673ff18be239b40"; + let event = |base: &str| format!(r#"{{"merge_group":{{"base_sha":"{base}","head_sha":"{head}"}}}}"#); + assert_eq!(merge_group(&event(base)), Ok((base.to_string(), head.to_string()))); + assert!(merge_group(&event("main")).is_err()); + assert!(merge_group(&event("")).is_err()); + assert!(merge_group(r#"{"pull_request":{}}"#).is_err()); + assert!(merge_group("not json").is_err()); + } + + /// **No Rust line that `include_str!`s or `include_bytes!`s names a Markdown + /// file**: a crate that embedded one, as `#![doc = include_str!(...)]` does, + /// would compile and doc-test it, which a change of documentation alone + /// skips. Line-local: a split invocation or a build script's read passes. + #[test] + fn no_source_embeds_documentation() { + let root = repo_root(); + let embeds = ["include_str!", "include_bytes!"]; + let sources = crate::sysroot::tracked_files(&root, &["*.rs"]).unwrap_or_else(|e| panic!("{e}")); + assert!(sources.iter().any(|f| f == "build/ci.rs"), "the walk did not read the tree"); + let mut found = Vec::new(); + for file in &sources { + let text = std::fs::read_to_string(root.join(file)).unwrap_or_else(|e| panic!("{file}: {e}")); + for (n, line) in text.lines().enumerate() { + if embeds.iter().any(|e| line.contains(e)) && line.contains(".md") { + found.push(format!("{file}:{}", n + 1)); + } + } + } + assert!(found.is_empty(), "documentation a build reads is not documentation alone: {found:?}"); + } + /// Teeth for the controls' judge, on libtest's text as a control's run /// prints it: a green negative control, a verdict line absent or saying the /// opposite, a filter that selected nothing, and a self-catching case that diff --git a/build/main.rs b/build/main.rs index 3f392961eeb..2dd2b8fb2aa 100644 --- a/build/main.rs +++ b/build/main.rs @@ -52,13 +52,14 @@ fn executable_on_path(name: &str) -> bool { }) } -fn check_prerequisites(root: &Path, arch: Arch) { +/// What every build needs, and the QEMU that boots `arch` where the run `boots`. +fn check_prerequisites(root: &Path, arch: Arch, boots: bool) { let mut missing: Vec = REQUIRED .iter() .filter(|t| !t.any.iter().any(|n| executable_on_path(n))) .map(|t| format!("{} ({})", t.any.join(" or "), t.why)) .collect(); - if !executable_on_path(arch.qemu()) { + if boots && !executable_on_path(arch.qemu()) { missing.push(format!("{} (every {} boot — install QEMU)", arch.qemu(), arch.name())); } if !missing.is_empty() { @@ -72,7 +73,7 @@ fn check_prerequisites(root: &Path, arch: Arch) { // The one prerequisite whose *version* decides verdicts rather than whether // anything runs at all, so a scan of `PATH` cannot ask it. // `toyos_build::ci` carries why this is a note here and a red in CI. - if let Some(note) = toyos_build::ci::qemu_version_note(root, arch) { + if let Some(note) = boots.then(|| toyos_build::ci::qemu_version_note(root, arch)).flatten() { eprintln!("{note}"); } } @@ -183,7 +184,7 @@ fn main() { } let arch = toyos_build::build::arch_for(&args); - check_prerequisites(&root, arch); + check_prerequisites(&root, arch, !asked(&flags::BUILD_ONLY) && update_image.is_none()); env::set_current_dir(&root).expect("Failed to cd to project root"); let debug = asked(&flags::DEBUG); diff --git a/build/release.rs b/build/release.rs index 92ce50593db..3bb04aceead 100644 --- a/build/release.rs +++ b/build/release.rs @@ -187,7 +187,7 @@ fn step_outputs() -> Result { } /// Append `text` to the step outputs at `file`. -fn tell(file: &Path, text: &str) -> Result<(), String> { +pub(crate) fn tell(file: &Path, text: &str) -> Result<(), String> { fs::OpenOptions::new() .append(true) .open(file) diff --git a/issues/ci-runs-the-whole-guest-suite.md b/issues/ci-runs-the-whole-guest-suite.md deleted file mode 100644 index a9903b33e58..00000000000 --- a/issues/ci-runs-the-whole-guest-suite.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -status: owner -kind: question -opened: 2026-10-10 ---- - -# CI runs the whole guest suite - -The owner's ruling: guest tests run on demand, and CI's small tier holds only the tests that must -break when something important breaks. `guest.yml` runs `cargo run -- --ci guest` (`build/ci.rs`, -`guest`), which is every registration in `tests/toyos.rs`, in the merge queue (`ci.yml`) and -nightly (`nightly.yml`); no tier is declared, so no review can keep a -new guest test out of it. - -The question: which tests belong in the tier. Once the owner names them, the orchestrator -dispatches the reshape. - -**Exit condition**: `--ci guest` runs a declared list in `tests/toyos.rs` and nothing else, the -rest of the suite runs on demand, and root `CLAUDE.md`'s "The guest suite is its `guest / -suite` check" and `reviewer.md`'s **Guest tests** say so in the same diff. The same diff deletes -`ON_DEMAND` in `tests/toyos.rs`, the stopgap that keeps a test out of an unfiltered run until then: -once `--ci guest` runs a declared list, everything outside it is on demand. diff --git a/issues/contention-has-no-owning-instrument.md b/issues/contention-has-no-owning-instrument.md index 051eea93f0e..959a19c89f9 100644 --- a/issues/contention-has-no-owning-instrument.md +++ b/issues/contention-has-no-owning-instrument.md @@ -10,7 +10,7 @@ Every defect class has exactly one owning instrument, and a class with no owning instrument is recorded as unowned here. Four instruments carry the whole estate — host suites, KVM guest shards, the TCG shard, and metal — and none of them owns contention: parallel suites contending for a lock, a lock storm, a -scheduler collapse under load. KVM CI (`ci.yml`'s `guest` shards) is one +scheduler collapse under load. KVM CI (`nightly.yml`'s `kvm` suite) is one guest per machine by construction, so there is never a second guest to contend with, and the KVM row says so itself, listing "contention" under its own "blind to". The local suite is developer feedback and never a gate: a diff --git a/issues/no-required-check-builds-the-shipped-image-for-aarch64.md b/issues/no-required-check-builds-the-shipped-image-for-aarch64.md deleted file mode 100644 index 328ee4b5ac1..00000000000 --- a/issues/no-required-check-builds-the-shipped-image-for-aarch64.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -status: open -kind: tooling -opened: 2026-10-09 ---- - -# No required check builds the shipped image for AArch64 - -`cargo run -- --build-only --arch aarch64` builds every program of -`images/system.toml` for `aarch64-unknown-toyos` and puts it on the AArch64 ROOT, and -nothing a pull request or the merge queue runs issues it. `nightly.yml`'s two -`--build-only` steps name no architecture and build x86-64. `build/clippy.rs` -lints the kernel and the loader for AArch64 and no userland program. The -`virt_` guest tests build `tests/virt*case/system.toml` or -`tests/images/testcases.toml`, which name servers and test programs and no windowed -one. - -The build has no per-architecture list of programs left out: a program that -stops building for AArch64 fails that build by name. So the failure is loud, and -found only by whoever next runs that build by hand. - -Owner: the build system (`build/ci.rs` and `.github/workflows/`). - -**Exit condition.** A required check runs `cargo run -- --build-only --arch -aarch64`, or its equivalent, on the shipped `images/system.toml`, and is red when one -of its programs does not build for AArch64. diff --git a/issues/the-guest-suite-compiles-every-guest-crate-cold-on-every-run.md b/issues/the-guest-suite-compiles-every-guest-crate-cold-on-every-run.md index 1d4fa5b98cc..c4aefe14b67 100644 --- a/issues/the-guest-suite-compiles-every-guest-crate-cold-on-every-run.md +++ b/issues/the-guest-suite-compiles-every-guest-crate-cold-on-every-run.md @@ -73,5 +73,5 @@ than `cargo run` builds. Owner: the guest job (`build/ci.rs`'s `guest`, `.github/workflows/guest.yml`). -**Exit**: `guest / suite` on a pull request that moves no guest source prints -a `building` figure under 60 s. +**Exit**: a `guest.yml` run on a tree whose guest sources the restored entry +already holds prints a `building` figure under 60 s. diff --git a/issues/the-loaders-lines-carry-no-time-on-an-x86-cpu-that-states-no-tsc-rate.md b/issues/the-loaders-lines-carry-no-time-on-an-x86-cpu-that-states-no-tsc-rate.md index 2088f101dbb..8162e2ca67d 100644 --- a/issues/the-loaders-lines-carry-no-time-on-an-x86-cpu-that-states-no-tsc-rate.md +++ b/issues/the-loaders-lines-carry-no-time-on-an-x86-cpu-that-states-no-tsc-rate.md @@ -23,7 +23,7 @@ largest standard leaf, as `00000010` on Zen 2 (`0870F10_K17_Matisse_CPUID`), Zen 3+ (`0A40F41_K19_Rembrandt_01_CPUID`), Zen 4 (`0A60F12_K19_Raphael_01_CPUID`) and Zen 5 (`0B00F21_K20_Turin_01_CPUID`, `0B20F40_K20_StrixPoint_06_CPUID`, `0B40F40_K20_GraniteRidge_02_CPUID`): neither leaf exists there. QEMU's -`qemu64` states neither either, and under the `guest / suite` check's +`qemu64` states neither either, and under nightly's `kvm / suite`'s `-cpu host` whether a line is timed is the runner's CPU's to decide. A second rate source is the fix, and the choice of one is open: the diff --git a/issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md b/issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md index 9cece471257..6b42855c740 100644 --- a/issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md +++ b/issues/the-nightlys-macos-job-pins-rustc-1-98-1-for-a-hang-its-test-no-longer-shows.md @@ -137,7 +137,7 @@ PID after 120 s) and with that report's proposed fix the fork's compilers exit 0, so the row there tells them apart no more than it tells 1.99.0 from 1.98.1. -`guest.yml`, and so `guest / suite` and the nightly's `tcg / suite`, and +`guest.yml`, and so the nightly's `kvm / suite` and `tcg / suite`, and `nightly.yml`'s `portability-linux` install `stable` and log its version: the `tcg / suite` of run 37778826093 logged `rustc 1.99.0 (b940084d7 2026-09-28)`, on x86-64, where this test is compiled right for diff --git a/tests/common/iommu.rs b/tests/common/iommu.rs index 0d3208a996b..762d26894cd 100644 --- a/tests/common/iommu.rs +++ b/tests/common/iommu.rs @@ -342,7 +342,7 @@ pub(crate) fn amdvi_left_on(unit: &str) -> [String; 3] { /// **A unit an AMD machine's firmware left on is switched off and programmed, /// and the boot stick's DMA reaches memory through its controller's own /// domain.** QEMU's `amd-iommu` is the only AMD-Vi unit -/// the merge queue runs: the T14 is an Intel machine, and the hand-over's +/// the nightly guest suite runs: the T14 is an Intel machine, and the hand-over's /// register sequence and the DMA it frees are neither of them a host test's. /// Two boots: the shipped kernel finds a unit nothing left on and switches /// nothing off; the test kernel's `iommu-firmware-left` leaves the unit blocking diff --git a/tests/toyos.rs b/tests/toyos.rs index 463979d150d..06495d2360b 100644 --- a/tests/toyos.rs +++ b/tests/toyos.rs @@ -404,7 +404,7 @@ const MACHINE_TESTS: &[&str] = &[ // An AMD-Vi unit left blocking every device, switched off and programmed // before the boot stick is driven behind it: the register sequence and the // DMA it lets through are no host test's, and the AMD bench is staged by - // hand rather than by the merge queue. + // hand rather than by CI. "amdvi_firmware_left", // A translating AMD-Vi unit's refusal, recorded and read: a kernel-owned // record halts the machine, so no metal row reaches it, and only QEMU's