Repository navigation
Host cache: read by content, written by main's cold nightly alone, sealed and bounded, with its workflow rules in the review prompt - #669
Conversation
… pruned to one entry; the licence step fetches only the fork's library/ Freshness. Cargo calls a path crate fresh when no source is newer than the build that read it, and actions/checkout dates every source at the checkout, so every path crate recompiled in every host run (152 compile events, 472 s median of a PR run's 817 s). `-Z checksum-freshness` would fix it inside cargo, and is refused here: it is still unstable (nightly-2026-09-21 lists it under -Z, stable 1.98.1 rejects -Z), and the only ways to it are a nightly toolchain or RUSTC_BOOTSTRAP=1 on stable. Either one moves the gate's verdict off what a user's stable compiles: RUSTC_BOOTSTRAP lifts the feature gate for every crate and flips the nightly probes in dependencies' build scripts, and cargo passes -Zchecksum-hash-algorithm to rustc, so rustc needs it too. The fork builds no cargo of its own (toolchain.rs lends the machine's). An mtime made up from history (a commit's time) would call a changed file fresh whenever its commit predates the entry's build. Instead the entry carries the git blob id of every source its build read (target/ci-sources), and the writer dates every file under every target 2001-09-09. A reader dates each source whose blob matches the same, and every other one now. Cargo's comparison is `source <= reference` is fresh, so a match is fresh and a change or an addition is newer than everything in the entry, whatever any runner's clock says. A package that lost a file keeps its Cargo.toml dated now, because cargo's package fingerprint (a build script that names no input) is the newest of the remaining files. Only a cold run seals: a warm run's targets hold units its steps never rebuilt, compiled from sources no manifest it could write describes. A reader deletes the manifest it read, so a warm tree is never saved, and targets restored without a manifest are refused. The driver builds in target/ci-driver: cargo compiles it before any of this runs, so its path crates recompile every time, and in the steps' target that rebuild would make every dependent stale. Cache discipline. nightly.yml's host is the writer on main and restores nothing, so the entry is one cold build's tree and never an accumulation (4.25 GB from 2.65 GB in one write). It then deletes every other host entry, and reds if its own is not on main. A pull request that found no entry seals and saves its own, which only its later runs read. The key carries the runner's OS. Licence step. The fork's std names toyos and toyos-abi by path from rust/, so the library stays in rust/; a runner fetches the pinned commit's library/ alone (blobless, sparse) instead of the whole tree, and a developer's uninitialised rust/ is refused rather than left sparse for a later build.
Cargo keeps one check of a unit, and the toyos-abi shape lints the unit the workspace shape also lints, under other lints: each re-checks it every run, and the workspace shape then re-checks every crate depending on it. Measured on this host: after the shared-target toyos-abi shape the workspace shape checked 13 crates again; after one in target/clippy-abi, none. A cache read by content otherwise serves every one of those checks. The guest cache's discipline is filed: its writer restores before it saves, and its readers judge by mtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…s one leaf crate to recompile A measurement, reverted by the next commit: the run before this one sealed its tree into this pull request's cache scope, and this run reads it. Nothing depends on toyos-dhcp, so it is the only crate whose bytes differ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…t run has one leaf crate to recompile" This reverts commit 455780e: its run measured what it was for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
An entry is read by content, so a step recompiles only a crate whose bytes changed, and incremental state is most of an entry's bytes: on this host one host run's root target held 4.36 of 6.66 GB under incremental/, and the workspace's test build took 2.83 GB with it and 1.01 GB without. This pull request's first warm run restored its 3,250,736,567 B entry in 109 s, more than every compile in it together (23.0 s). The driver keeps it: its path crates recompile every run, and incremental state is what makes that 13.2 s on a runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Round 1, head Stale-build cases (covered means a warm run rebuilds or refuses)
#667: the keys carry First run after landing: a prune failure is contained. Prune runs after the save. It deletes nothing unless the listing is complete and holds this run's key on main, and it never deletes that key. No nightly job needs Growth: +675 −39, net +636. Production is +479 −34 (net +445); tests are +196 −5 (net +191). Content-dating earns its growth: jobs went from 944 s to 403–504 s. The deletions are named below: the PR save, BLOCKER
NOTE
REMOVE
SEND BACK |
…ole, the image in the manifest Answers the review at #669 (issuecomment-5930046178). - The pull request's own cache save goes, with the comments that described it and the loosened writer test: nightly.yml's `host` is the host cache's one writer again, and `each_cache_has_one_writer` is main's test plus the assertion that a host save is guarded by the manifest. - `--ci prune`, `doomed`, `gh` and nightly's `actions: write` go. Eviction is by last access, and with one writer the newest host and guest entries are the most recently read. - A tracked file's identity is the SHA-256 of its bytes, hashed in-process over `sysroot::tracked_files`; the `git hash-object` child, its writer thread and its count check go. The gitlink is skipped as a directory. - The licence step's sparse fetch (git init, remote add, an HTTPS `fetch --filter=blob:none`, sparse-checkout, checkout --detach) goes back to main's `git submodule update --init --depth 1 rust`, the use the admitted git row already declares. Measuring gitoxide's partial-clone and sparse-checkout support for a verdict was not worth the licence step's 15-20 s. - A package with any tracked file changed, added or removed has every file dated now. A file rustc probed for and did not find (a new `src/x/mod.rs` beside `src/x.rs`, E0761 cold) is in no dep-info, so dating the new file alone left the crate fresh; dating its package's files rebuilds it. This subsumes the lost-file rule. What a build reads outside its own package undeclared stays trusted, as cargo's own incremental build trusts it, and is filed with its exit. - The runner image goes in the manifest, not the key: no workflow expression sees `ImageOS` or `ImageVersion` (the `env` context holds only what a workflow sets; rclone's and apache/httpd's workflows say so at their sites). The key carries `runner.os` and `runner.arch`; a reader whose `RUNNER_OS RUNNER_ARCH ImageOS ImageVersion` differs from the entry's deletes the restored targets and runs cold. - A warm reader whose clock does not read after 2001-09-09 is refused. - New tests: a written-back source is refused by the seal; a restored `target/debug` without a manifest is refused; a driver built elsewhere is refused; another image's entry is deleted; a clock at the entry's date is refused; E0761 rebuilds warm as it fails cold. - REMOVEs: ci.yml's "runs the same on a dev host", identity.rs's "one definition" sentence, and cicache's "whatever any runner's clock says". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Round 2, head Round 1's BLOCKERs
Can a stale build still pass? Only through the filed hole.
sha2: #667 (
One conflict will not show in a textual merge. #667 adds Land #667 first. This branch is going back anyway, so its merge of main carries the reconciliation: the key, and that step made to pass The proposed rule ("a cache key never uses Growth: +646 −29, net +617.
Production is down from round 1's net +445, by the named deletions and the licence revert. Content-dating earns the rest: the job went from 944 s to 481–504 s in round 0's warm runs. This round's reader has run only cold. BLOCKER
NOTE
REMOVE
SEND BACK |
…d code run at build time runs again on every read The runner-variable list in `read` was reached by no test: dropping `ImageVersion` from it stayed green. `runner` now builds the runner from a lookup, `read` hands it the environment, and `an_entry_built_on_another_runner_is_deleted` builds both runners with it, from environments that differ in one variable, for each of the four. It also refuses an environment missing one, so an unset variable is never defaulted. The warm read's trust in cargo for what a build reads outside its package is removed rather than recorded. Every package with a build script or a proc macro has every file dated now on every warm read, so cargo reruns the script and rebuilds the macro and what expands it, whatever either reads. A package is one when its manifest has `build.rs` beside it and no `build = false`, a `build` key naming a script, `proc-macro` or `proc_macro`, or a `proc-macro` crate type. In the host job that is userland/calc alone, whose font already sits in the root package that nearly every landing changes. - `code_run_at_build_time_runs_again_on_every_read` is the deleted issue's exit: a build script and a proc macro read another package's file and never say so, and a warm read after a change to that file prints what a cold build prints. - `every_spelling_of_code_run_at_build_time_is_found` holds each spelling. - The oracle test gains `gone`, a package that lost a file nothing read. `count`'s build script now reruns on every read, which would otherwise hide a removed file's package dating from every test. What a warm run still trusts cargo for is a file only a flag names, a linker script in another package: filed as issues/build/a-warm-host-run-never-relinks-for-a-file-only-a-flag-names.md. Removed: SEALED's doc line, which restated the workflows' key, and the guest cache issue's restatement of the host cache's design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
#667 moves `host` to ubuntu-24.04 in both workflows and keys its cache `host-linux-`. Resolved: - ci.yml: ubuntu-24.04 and the dropped macOS comment are main's. The key stays `host-sealed-${{ runner.os }}-${{ runner.arch }}-`, which is `host-sealed-Linux-X64-` on ubuntu-24.04, so it keeps Linux entries apart from macOS ones as `host-linux-` did. - nightly.yml `host`: ubuntu-24.04 is main's. Main's `host-linux-` restore and its main-only save step give way to this branch's writer, which restores nothing, runs on main alone and saves under the sealed key. - nightly.yml `portability-macos`: main's `cargo run -- --ci host` after `--build-only` is kept as main has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
… job carries the cache
`src/CLAUDE.md` says a workflow step runs `cargo run -- --ci <job>` and
nothing else. Both host steps passed `--target-dir target/ci-driver`, and
nightly's save was guarded by `hashFiles('target/ci-sources')`.
- The driver's own target is now the `CARGO_TARGET_DIR` of ci.yml's and
nightly.yml's `host` jobs, so each step is the bare command. Cargo hands
that variable to the program it runs, so `host` takes it out of its
environment before any step and no step builds in the driver's target.
- `cicache::carried` asks where the driver runs from: a job that builds it in
`target/ci-driver` carries the cache, and any other runs the host suite as
a developer's tree does. That reconciles #667's `cargo run -- --ci host` in
nightly's `portability-macos`, which follows `--build-only`: its driver is
in `target/debug`, and `read` would have refused the targets `--build-only`
left, which no manifest describes.
- No runtime refusal of a driver built elsewhere is left. Instead
`each_cache_has_one_writer` holds every job that restores or saves the host
cache to that `CARGO_TARGET_DIR` at job level, so a workflow that drops it
reds in the pull request that drops it.
- Nightly's save has no guard. Its job restores nothing, so its run is cold,
and a cold run's last step is the seal: the job is green only with a sealed
tree, and a save follows only a green job. `each_cache_has_one_writer` reds
on a host writer that restores anything, in place of the guard it checked.
- The seal names the runner it records, so a cold run's log shows what a warm
reader will compare against.
- `MANIFEST` is private: the guard was its one reader outside the module.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Round 3, head Round 2's findings, checked against
Growth: +806 −35, net +771.
The growth pays only if a Linux entry serves pull requests, and BLOCKER 2 leaves that unshown. BLOCKER
NOTE
REMOVE
SEND BACK |
…te or full debuginfo, and the writer bounds what actions/cache stores
- `ci::carry` is the one function `host()` calls for a job that carries the
cache. `a_step_of_a_job_that_carries_the_cache_builds_in_its_own_target`
runs it in a driver of its own, spawned with the job's `CARGO_TARGET_DIR`
and without `CARGO_INCREMENTAL` or `CARGO_PROFILE_DEV_DEBUG`, and requires
`cargo metadata`'s `target_directory` to be its fixture's `target`, and
`cargo build -v`'s rustc line to carry no `-C incremental` and
`-C debuginfo=line-tables-only`.
- The steps build with line tables alone
(`CARGO_PROFILE_DEV_DEBUG=line-tables-only`). A backtrace in a step's log
reads them: a panic while panicking prints one, as the `doorbell-kick-relaxed`
control does in run 36867673999. Nothing reads the rest: no step sets
`RUST_BACKTRACE`, and no host test reads a binary's DWARF. A Linux link
copies the DWARF of every object it takes into the binary, which macOS
leaves in the objects: the Linux run sealed 7684 MiB where macOS sealed
3575 MiB. `toyos-build`'s lib test, cross-linked on the dev host for
x86_64-unknown-linux-gnu by rust-lld with no C runtime, so that what it keeps
is almost all DWARF, is 124,452,112 B with full debuginfo, 35,983,096 B with
line tables alone, and 848,744 B with none.
- `cicache::bound` runs after the seal. It makes the archive actions/cache
v4.3.0 makes of `cicache::PATHS` (its log on a runner: `gtar --posix -cf
cache.tzst --exclude cache.tzst -P -C <workspace> --files-from
manifest.txt --use-compress-program zstdmt`) with the runner's own `tar` and
`zstdmt`, counts it as it streams, and refuses it above 2,000,000,000 B, so
the save, which follows only a green run, never runs.
- The limit: eviction is by last access past the repository's 10 GB, and on
a night whose guest jobs restore after the host entry is saved the
repository holds two host entries beside a guest one, then one beside two
guest ones. With the last guest entry, G = 3,281,375,938 B, and H at the
limit, 2H + G = 7,281,375,938 B and H + 2G = 8,562,751,876 B. G may grow
to 4,000,000,000 B before H + 2G reaches 10 GB.
- `each_cache_has_one_writer` holds every host-cache step's `path:` list to
`cicache::PATHS`, which the bound measures, and refuses a step taken by an
alias, or anchored for one, in a job that names the host cache: its reader
sees neither.
- `runs_at_build` reads `[project]` as cargo reads `[package]`.
- `issues/build/a-warm-host-run-keeps-what-a-registry-proc-macro-expanded-from-a-file-it-never-named.md`
files the residual a warm read leaves for a registry proc macro.
- The host-tools row for `tar` and `zstd` names `src/cicache.rs` beside
`src/release.rs`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
… zstdmt go The bound ran the runner's `tar` and `zstdmt` to count what actions/cache would store. The host-tools declaration refuses both, and the Rust tool its row names is a zstd crate, a new dependency only to measure. The seal already summed the lengths of the files it dates under every target, for its log line; it now refuses a tree above `LIMIT` with that sum, before it writes the manifest. The step is red, so the save, which runs only after a green step, stores nothing, and the tree carries no manifest a reader would take. The seal's line prints the sum in bytes beside the limit, where it printed MiB. The limit is 8,000,000,000 B of targets, uncompressed. Run 36878222090 sealed 5369 MiB of targets, at least 5,629,804,544 B, and the archive the bound made of the cache's paths, as actions/cache makes one, was 1,403,326,566 B: a ratio of 4.01. At that ratio the limit stores at most 1,994,138,951 B. With the last guest entry's 3,281,375,938 B, a night then holds 2H + G = 7,269,653,840 B and H + 2G = 8,556,890,827 B, both under the 10 GB eviction limit, and the ratio may fall to 2.38 before 2H + G reaches it. The 7,684 MiB tree is db4654f's, sealed with full debuginfo in run 36867673999, which ran no bound. `an_entry_above_its_bound_is_refused` seals a fixture whose targets hold the limit plus one byte, the limit in a sparse file under `target/` and the byte under `kernel/target/`, and requires the refusal and no manifest. It then seals the same tree at the limit exactly. The host-tools row is main's again: no ToyOS code runs `tar` or `zstd` for the cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Re-review, head The last review's BLOCKERs (
The bound
Growth.
Since BLOCKER
NOTE
REMOVE
SEND BACK |
src/ci.rs conflicted twice, both sides additive, and both are kept
whole: the `use` of `cicache::{self, Start}` beside #674's
`userlandhost::{Host, Os, Program}`, and `carry()` beside #674's
`apps_for`, `verb` and `attempted`. The merged file's changed lines
against main are exactly the branch's, and against the branch exactly
main's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…save follows only a green run The bound sums `cicache::PATHS`, the list the save archives, with `~` taken from HOME: the three `~/.cargo` paths the save stores are counted, and a target the save does not store is not. That gives `PATHS` the production reader it lost when `stored()` went. `cicache::close` runs after the last step of every job that carries the cache, warm or cold. A cold run is bounded before it seals, so a refused tree gets no manifest; a warm run is bounded too, so a pull request whose tree holds more than `LIMIT` reds on its own run, not on the nightly after it lands. Only the nightly saves, as before. `seal` no longer counts, and is private to the module. `each_cache_has_one_writer` reds on any line naming `always()`, `cancelled()`, `failure()` or `continue-on-error` in a job that saves the host cache: either would let the save store a tree the seal refused, above `LIMIT` and with no manifest, which every pull request's read would then refuse. `carry()`'s comment loses "in an entry": `target/ci-driver`, saved under `target`, holds the driver's own incremental state. LIMIT's tie to the 10 GB, one ratio measured once on run 36878222090, is filed as issues/build/the-host-caches-limit-reaches-the-10-gb-only-through-one-measured-ratio.md with its evidence and an exit a gate can fail. The guest-cache issue records the ceiling H + 2G <= 10 GB leaves the guest entry: 4,002,930,524 B at LIMIT, 4,298,336,717 B at that run's H. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Re-review, head Round 7's BLOCKERs
The questions
Growth.
Since BLOCKER
NOTE
REMOVE
SEND BACK |
…the host entry seals it `each_cache_has_one_writer` read the workflows as text, and three rounds found spellings that escaped it: a quoted key, a comment after `jobs:`, an unknown job key, a runner variable in `env:`, an anchor and an alias, a deleted run step. - `src/workflow.rs` reads every workflow with yaml-rust2's event parser. An alias is resolved as GitHub resolves it, and every node an anchor, an alias or a tag made is marked. A key given twice, a key that is not a plain scalar, and anything but one document are refused. It is compiled for tests alone, beside its two tests, and its accessors are what another gate over `.github/workflows/` calls. - yaml-rust2 0.13, as a dev-dependency with its encoding feature off: the pure-Rust YAML 1.2 parser with 61,444,840 downloads on crates.io, whose events carry the anchors, aliases and tags a loader resolves away. - The gate works on that structure. Every cache step's key and restore keys must begin, up to their first expression, with a known cache's prefix, and the combined action is refused. The jobs that name the host cache must be exactly ci.yml's `host`, the reader, and nightly.yml's `host`, the writer. Each is held to a closed allow-list: its workflow's keys, its own keys, its `if:`, `env:` with `CARGO_TARGET_DIR` alone, and its steps in order as checkout, restore and the run, or checkout, the run and save. No step has a condition of its own, no anchor, alias or tag is in the job, and the host cache's paths, key and restore key are exact. - Only the writer seals. `cargo run -- --ci seal` is `host` from a cold tree, which it refuses if anything was restored, then sealed and bounded by `LIMIT`. nightly.yml's `host` runs it, and ci.yml's `host` runs `--ci host`, whose read never makes a `Cold` and so never seals. A pull request's run, warm or cold, is never refused by `LIMIT`. - The LIMIT issue's residual says what is true now. The guest-cache issue records that its jobs are not held to the allow-list, and its exit names that allow-list. A new issue records the two workflow gates that still read text, with the two patches that pass them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…s a workflow's extension in any case - An alias copies a node its anchor marked, so the line that marked the copy again was dead: with it deleted, `a_workflow_is_its_structure_and_not_its_spelling` was green (EXIT 0), as it is without the deletion. - `.yml` and `.yaml` are matched in any case, so the files read are a superset of those GitHub reads. - `parse` and `Value` are private: a gate reads through `all` and `Node`'s accessors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Re-review, head
Round 8's BLOCKERs
The questions
Growth.
Since
I accept the growth on rulings 1 and 2. BLOCKER
NOTE
REMOVE
SEND BACK |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
… the cold start, host and the seal The workflow gate goes, all of it: src/workflow.rs, the yaml-rust2 dev-dependency and its lock entries, and src/ci.rs's `each_cache_has_one_writer` (main's text reading of it too), with `the_driver_alone`, `host_step`, `caches`, `action`, `keys` and their constants. Round 9's review found two more spellings that GitHub's reader takes and the gate's did not: a `uses:` path GitHub splits on `/` and `\` and drops empty segments of, and U+2028, U+0085 and U+2029, which YamlDotNet breaks a line at and yaml-rust2 does not. A gate that re-reads GitHub's YAML lags GitHub's reader by construction. Its rules are now sentences in `.claude/agents/reviewer.md`'s new **Caches** bullet, read off the diff: one writer per cache, in nightly.yml; the host cache's writer nightly's `host` and its reader ci.yml's `host`, on one `runs-on` and both caching `PATHS`; those jobs run checkout, their cache step and the driver alone; the save's `github.ref == 'refs/heads/main'` guard is their only step-level `if:`, and ci.yml's `host` skips only a draft; no `continue-on-error`, `shell:`, `defaults:` or cargo `runner` reaches them; nightly's `on:` is one daily schedule and `workflow_dispatch`. The last three come from the review's NOTEs: a root `.cargo/config.toml` runner, a free `runs-on`, and a free `on:` were each invisible to the gate. Code stays where reading cannot see: the seal, its bound, the manifest and its refusals in src/cicache.rs. The Tests bullet takes the owner's two principles: a mistake a type makes unrepresentable needs no test, and a rule a reviewer can check by reading lives in the prompt, a gate being code only for what reading cannot see. `--ci seal` is `ci::seal`: the cold start, then `host`, then the seal, as calls, so no bool decides whether the one writer seals. `host` reads the cache in every job that carries it, so in the seal job it reads the tree the cold start just proved empty and says the run is cold. `cicache::cold` refuses a driver built outside `target/ci-driver`, the check `host` made for the seal before. nightly.yml's save carries the main-only guard again, the only step-level `if:` the rule allows, and the job carries none: a dispatch on a branch runs the cold build and the seal, bounded, and saves nothing. `cicache::SEALED` was read by the gate alone and goes; `PATHS` is private to the bound that reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…ctions or go, and each cache issue names its owner `issues/build/two-workflow-gates-read-the-workflows-as-text.md` is replaced by `issues/build/the-workflow-gates-that-read-workflows-as-text-become-reviewer-instructions-or-go.md`. It names the third gate, src/hostws.rs's `nothing_that_runs_names_a_target_directory_a_member_does_not_have`, gives each of the three a patch that reaches it, its owner (the track `issues/build/the-tooling-is-a-review-prompt-and-three-workflows.md`), and an exit a test can fail: each is deleted with its rule in the review prompt, or reds on its patch. Measured on 038da72. Each patch was applied with `git apply --check` then `git apply`, built with `cargo test --lib --no-run` (EXIT=0), the three tests run with `--exact`, and reverted with `git apply -R`; the tree matched its state before after every one: patch hosted required member-target publish.yml `runs-on:` label on the next line 0 0 0 publish.yml `pull_request: {branches: [dev]}` 0 0 0 ci.yml `host`'s `if: false` 0 0 0 publish.yml `run: "ls userland/sshd/targe\x74"` 0 0 0 publish.yml `run: ls userland/sshd/target` 0 0 101 The last is the positive control: "publish.yml: names userland/sshd/target". Each run selected one test ("1 passed; 400 filtered out", or "1 failed"). The guest cache's issue loses its paragraph on the deleted allow-list and the exit clause that named it. The two warm-read issues and the guest cache's name their owners, as the limit's does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
The deleted gate held each job that names the host cache to `CARGO_TARGET_DIR: target/ci-driver`, and the review sentence did not. Without it a reader is not carried: it runs its restored tree judged by cargo's mtimes alone, with no read by content and no image check, and nothing at run time says so. The writer without it is refused by `cicache::cold`, so only the reader needed the sentence, but it holds both, as the gate did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Patches the body of this pull request rests on. The workflow-gate issue's evidence, measured on 038da72: each applied with === P1a-runs-on-next-line
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -23,7 +23,8 @@ permissions:
jobs:
publish:
- runs-on: ubuntu-latest
+ runs-on:
+ - self-hosted
timeout-minutes: 30
steps:
# No submodules: `cargo publish` walks the repository's vcs state, and an
=== P1b-pull-request-flow
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -8,6 +8,7 @@ on:
push:
branches: [main]
workflow_dispatch: {}
+ pull_request: {branches: [dev]}
# Never two publishers, and never cancel one: a cancelled `cargo publish` may
# have already taken the version.
=== P2-host-if-false
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,5 +16,5 @@ concurrency:
jobs:
host:
- if: github.event_name == 'merge_group' || github.event.pull_request.draft == false
+ if: false
runs-on: ubuntu-24.04
timeout-minutes: 45
=== P3-dead-target-escaped
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -31,5 +31,7 @@ jobs:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ - run: "ls userland/sshd/targe\x74"
+
- id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
=== P3plain-dead-target-plain
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -31,5 +31,7 @@ jobs:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ - run: ls userland/sshd/target
+
- id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
The negative controls on === mSt-writer-takes-a-restored-tree src/cicache.rs
- let restored = restored(root)?;
- if !restored.is_empty() {
- return Err(format!("{}: an entry is one cold build, and its writer restores nothing", listed(&restored)));
- }
=== mN-no-bound src/cicache.rs
- if bytes > LIMIT {
- return Err(format!(
- "{bytes} B in {files} files under the cache's paths, above the {LIMIT} B an entry may \
- hold: saved, it could evict the guest entry or the next host one"
- ));
- }
=== mX-one-above-passes src/cicache.rs
- if bytes > LIMIT {
+ if bytes > LIMIT + 1 {
=== mO-at-bound-refused src/cicache.rs
- if bytes > LIMIT {
+ if bytes >= LIMIT {
=== mM-largest-not-sum src/cicache.rs
- bytes += meta.len();
+ bytes = bytes.max(meta.len());
=== mW-bound-after-manifest src/cicache.rs
- if bytes > LIMIT {
- return Err(format!(
- "{bytes} B in {files} files under the cache's paths, above the {LIMIT} B an entry may \
- hold: saved, it could evict the guest entry or the next host one"
- ));
- }
+ if bytes > LIMIT {
+ return Err(format!(
+ "{bytes} B in {files} files under the cache's paths, above the {LIMIT} B an entry may \
+ hold: saved, it could evict the guest entry or the next host one"
+ ));
+ }
=== mT-every-target-not-PATHS src/cicache.rs
- for path in PATHS {
- let dir = match path.strip_prefix("~/") {
- Some(under) => home.join(under),
- None => root.join(path),
- };
- // A path the save finds nothing at stores nothing.
- if !dir.try_exists().map_err(|e| format!("{}: {e}", dir.display()))? {
- continue;
- }
+ let _ = home;
+ for dir in targets(root)? {
=== mR-home-from-root src/cicache.rs
- Some(under) => home.join(under),
+ Some(under) => root.join(under),
=== mU-missing-path-read src/cicache.rs
- // A path the save finds nothing at stores nothing.
- if !dir.try_exists().map_err(|e| format!("{}: {e}", dir.display()))? {
- continue;
- }
=== mWk-walker-enters-nothing src/cicache.rs
- if meta.is_dir() {
- each_under(&entry.path(), visit)?;
- }
=== m1-source-existence-only src/cicache.rs
- now.get(*path) != Some(*hash) || modified(&root.join(path)).ok() != Some(built())
+ now.get(*path) != Some(*hash) || modified(&root.join(path)).is_err()
=== m11-seal-dates-no-file src/cicache.rs
- each_under(&target, &mut |path, _| date(path, built()))?;
+ each_under(&target, &mut |_, _| Ok(()))?;
=== m2-root-target-dropped src/cicache.rs
- if path != root.join(DRIVER) {
- found.push(path);
- }
+ let _ = path;
=== m4-no-clock-check src/cicache.rs
- if now <= built() {
+ if false && now <= built() {
=== m5-no-runner-check src/cicache.rs
- if built_on != runner {
+ if false && built_on != runner {
=== m6-file-dated-alone src/cicache.rs
- let mut dirty: BTreeSet<Option<String>> = current
- .iter()
- .filter(|(path, hash)| entry.get(*path) != Some(*hash))
- .map(|(path, _)| path)
- .chain(entry.keys().filter(|path| !current.contains_key(*path)))
+ let mut dirty: BTreeSet<Option<String>> = entry
+ .keys()
+ .filter(|path| !current.contains_key(*path))
- let fresh = entry.get(path) == Some(hash) && !dirty.contains(&package(path, ¤t));
+ let fresh = entry.get(path) == Some(hash) && !dirty.contains(&Some(path.clone()));
=== m7-every-hash-empty src/cicache.rs
- sources.insert(path, format!("{:x}", Sha256::digest(bytes)));
+ sources.insert(path, format!("{:x}", Sha256::digest(&bytes[..0])));
=== m9-only-matched-dated src/cicache.rs
- date(&root.join(path), if fresh { built() } else { now })?;
+ if fresh {
+ date(&root.join(path), built())?;
+ }
=== m10-every-file-dated-old src/cicache.rs
- date(&root.join(path), if fresh { built() } else { now })?;
+ date(&root.join(path), built())?;
=== m12-manifest-kept-on-read src/cicache.rs
- fs::remove_file(&manifest).map_err(|e| format!("remove {MANIFEST}: {e}"))?;
=== m19-removed-file-dirties-nothing src/cicache.rs
- .chain(entry.keys().filter(|path| !current.contains_key(*path)))
=== m14a-no-RUNNER_OS src/cicache.rs
- let values = ["RUNNER_OS", "RUNNER_ARCH", "ImageOS", "ImageVersion"]
+ let values = ["RUNNER_ARCH", "ImageOS", "ImageVersion"]
=== m14b-no-RUNNER_ARCH src/cicache.rs
- let values = ["RUNNER_OS", "RUNNER_ARCH", "ImageOS", "ImageVersion"]
+ let values = ["RUNNER_OS", "ImageOS", "ImageVersion"]
=== m14c-no-ImageOS src/cicache.rs
- let values = ["RUNNER_OS", "RUNNER_ARCH", "ImageOS", "ImageVersion"]
+ let values = ["RUNNER_OS", "RUNNER_ARCH", "ImageVersion"]
=== m14d-no-ImageVersion src/cicache.rs
- let values = ["RUNNER_OS", "RUNNER_ARCH", "ImageOS", "ImageVersion"]
+ let values = ["RUNNER_OS", "RUNNER_ARCH", "ImageOS"]
=== m15-unset-read-as-empty src/cicache.rs
- .map(|name| var(name).ok_or_else(|| format!("{name} is unset: a hosted runner sets it")));
+ .map(|name| Ok::<_, String>(var(name).unwrap_or_default()));
=== m16-no-build-time-dating src/cicache.rs
- if runs_at_build(root, manifest, ¤t)? {
+ if false && runs_at_build(root, manifest, ¤t)? {
=== m17-build-scripts-only src/cicache.rs
- Ok(script || proc_macro)
+ let _ = proc_macro;
+ Ok(script)
=== m18-proc-macros-only src/cicache.rs
- Ok(script || proc_macro)
+ let _ = script;
+ Ok(proc_macro)
=== mJ-package-table-alone src/cicache.rs
- let package = doc.get("package").or_else(|| doc.get("project"));
+ let package = doc.get("package");
=== mA1-any-driver-carries src/cicache.rs
- Ok(driver) => exe.starts_with(driver),
+ Ok(_) => true,
=== mA2-no-driver-dir-carries src/cicache.rs
- Err(e) if e.kind() == ErrorKind::NotFound => false,
+ Err(e) if e.kind() == ErrorKind::NotFound => true,
=== mB-no-driver-carries src/cicache.rs
- Ok(driver) => exe.starts_with(driver),
+ Ok(_) => false,
=== mF-carry-keeps-target-dir src/ci.rs
- std::env::remove_var("CARGO_TARGET_DIR");
=== mI-carry-keeps-incremental src/ci.rs
- std::env::set_var("CARGO_INCREMENTAL", "0");
=== mG-carry-keeps-debuginfo src/ci.rs
- std::env::set_var("CARGO_PROFILE_DEV_DEBUG", "line-tables-only"); |
|
Re-review, head CI: Round 9's BLOCKERs
The questions
Growth.
Since
The production growth stands on round 9's acceptance. BLOCKER
NOTE
REMOVE
SEND BACK |
Three files conflicted or needed adapting. .claude/agents/reviewer.md: main's text is taken whole, and this branch's Caches bullet is kept, between "What no gate reads" and "Growth". The two sentences this branch added to the Tests bullet are dropped, because main now states both in its own words: "Rank every finding" names no mutation a type refuses or a reader of the diff catches, and "Growth" sends back a new gate, check, lock or test that guards what a reader can check. The round 10 BLOCKER was against the second of those sentences, which this branch no longer adds. src/ci.rs: both imports are kept, this branch's `cicache` and main's `sysroot::git_out`. src/cicache.rs: main deleted `src/sync.rs`, and with it `sync::git`. The seal reads HEAD through `sysroot::git_out`, as main's `ci::publish` now does, and trims it; a git that cannot answer panics instead of returning the refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
…ne's job Round 10's review names three things the deleted workflow gate pinned and the Caches bullet did not, each one a line a reviewer reads off a diff: - nightly's `host` has no job-level `if:`. An `if: false` there stops the one writer, and a skipped job is a green check. - `CARGO_TARGET_DIR` is the one variable an `env:` gives either job. `cicache::runner` reads `ImageOS` and `ImageVersion` from the environment, so one set in both jobs outlives an image move and the image check passes. - the writer runs `--ci seal` and the reader `--ci host`. A writer on `--ci host` saves no manifest, and every pull request then refuses the targets it restored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
|
Round 11's mutation patch, measured on 883adf1: applied with === mH-head-untrimmed
diff --git a/src/cicache.rs b/src/cicache.rs
index 85192dd10..f5edac0ba 100644
--- a/src/cicache.rs
+++ b/src/cicache.rs
@@ -247,7 +247,7 @@ fn seal_at(root: &Path, home: &Path, cold: &Cold) -> Result<String, String> {
date(&target, built())?;
}
let head = crate::sysroot::git_out(root, &["rev-parse", "HEAD"]);
- let mut text = format!("{}\n{}\n", head.trim(), cold.runner);
+ let mut text = format!("{}\n{}\n", head, cold.runner);
for (path, hash) in &cold.sources {
text.push_str(&format!("{hash} {path}\n"));
}Red: |
|
Re-review, head Round 10's BLOCKER
The merge, read against both parents
Evidence at 883adf1
Round 10's NOTEs and REMOVEs are applied: the three clauses are in Caches (:82-87), the repeated Tests sentence went with the merge, "Gone:" is one item, and the "one entry a night" clause is gone. Growth.
BLOCKERNone. NOTE
REMOVE
LAND AFTER NAMED CHANGES |
…the keys and the order Round 11's review left two NOTEs, both on `.claude/agents/reviewer.md`. The cut of `ci::tests::each_cache_has_one_writer` stood on nothing the prompt said: Growth cut a test "only when it tests nothing, or as **Guest tests** says". The orchestrator's decision is that the test stays cut and the sentence names the case, so it gains one clause: a test is also cut when this prompt takes its rule. Caches then has to hold all the test held. It already said one writer per cache, a nightly.yml job; the test also refused the combined `actions/cache`, whose post step saves, and that is now a clause of the same sentence, since a diff that adds it shows no `save`. Caches gains the two things the deleted workflow gate pinned and the bullet did not say, each a cold pull request run behind green checks when broken: - the reader's `restore-keys` is the writer's `key` up to its run id: a head renamed on one side leaves every pull request on the old name's last entry, or on none; - the reader restores before its `--ci host` and the writer saves after its `--ci seal`: a save above the seal stores no target, and the nightly stays green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
#678 (de5f63c) restructured the review prompt, deleted src/sync.rs and src/worktree.rs, made a Sysroot owned by what compiles against it, and swept the sysroot stores at each placement. #669 (74a2e70) landed the host cache: src/cicache.rs, `--ci seal`, and the review prompt's Caches. Resolved by hand: - .claude/agents/reviewer.md: main's text whole, with this branch's six Workflows rules after Caches. Caches opened "Each cache has one writer, a nightly.yml job"; toolchain.yml's `build` saves the toolchain's layers from three workflows, so that sentence is now the host cache's alone. - .github/workflows/ci.yml: main's header comment said "no guest" and goes; the host job is main's, the toolchain and guest jobs this branch's. - src/ci.rs: main's `seal` job beside this branch's `toolchain`, `bootstrap` and `release`; five workflow files. - src/release.rs: this branch's module; `sync::git` is gone, so its four git reads are `sysroot::git_out`, as main's `publish` reads them. - src/build.rs, tests/common/compile.rs: main's owned Sysroot, with this branch's `hosted_rustc` argument to `toolchain::ensure`. - src/llvm.rs: main's unconditional `keystore::remove`, this branch's `keep`. - issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md: main's git rows less src/worktree.rs and src/sync.rs, with this branch's submodule callers and "CI's containers". What the merge makes false, and so changes with it: - CLAUDE.md said "Guests run nightly."; it says where they run now. - nightly.yml's guest cache is gone with the jobs that carried it, so issues/build/the-guest-cache-is-read-by-mtime-and-its-writer-restores- before-it-saves.md is deleted, src/cicache.rs's LIMIT no longer names a guest entry, and the host-cache limit issue sums two host entries beside the toolchain's layers (918,708,556 B a set, run 36934214557's saves) where it summed them beside guest entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
…ain consumes Main moved under this round three times: #669, #650 and #653. The merge before this one, 54c375a, took main at 74a2e70 (#669), not at de5f63c as its title says: the shared `origin/main` ref had been fetched forward between this round's fetch and its merge. This one names its commit. The one conflict is the `rust` gitlink. Main pins 3f6050fc829 (#650); this branch pinned d1b9f2eae3c, its three std commits over the pin before, on a fork branch of its own. Neither contained the other. The gitlink is now 6c7f996a4fe, the merge of d1b9f2eae3c onto 3f6050fc829: main's pin and this branch's three commits, and nothing else. The two sides share no file. It is on the fork's `main` through the merge 012fdce3c79, which also brings that branch the two of this branch's commits it lacked. `kernel/src/loader/mod.rs`, `tests/toyos.rs` and `toyos-abi/src/syscall.rs` merged without a conflict. Before this commit, on the merged tree: `cargo run -- --build-only` exit 0, with the sysroot built from the fork at 6c7f996a4fe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
The host job's cache, as it lands:
src/cicache.rs);hostis its one writer:cargo run -- --ci sealbuilds from a cold tree and seals it, and the save, on main alone, follows only a green run;.claude/agents/reviewer.md, read off the diff; no gate reads the workflows for it;toyos-abi's clippy shape lints in a target dir of its own.Before and after (
hoston macos-latest)toyos-build+ main's #668 changedCompiling/CheckinglinesFinished)cargo run … --ci hostRuns: 1, 2, 3, 4.
toyos-dhcpand nothing else, beyond the driver's 19 path crates, which every run rebuilds incrementally (13.2 s). 455780e is that one-line change; e57e0be reverts it.toyos-builddiffers from the entry. It compiled the driver, the lib test and the checks, plus 2 clippy checks oftoyos-build.Decisions
Freshness by content, not by mtime. Every path crate recompiled in every run. Checkout dates every source at checkout time, and cargo's path freshness is
source mtime <= build reference.-Z checksum-freshnessis refused. It is still unstable: nightly-2026-09-21 lists it under-Z, and stable 1.98.1 rejects-Z. Turning it on takes a nightly toolchain orRUSTC_BOOTSTRAP=1on stable. Rustc needs it too, because cargo passes it-Zchecksum-hash-algorithm. Either way the gate would stop compiling what a user's stable compiles:RUSTC_BOOTSTRAPlifts the feature gate for every crate and flips dependencies' nightly probes.toolchain.rslends it the machine's. Its rustc is not stable either.What
src/cicache.rsdoes instead:target/ci-sources: the commit, the runner, and the SHA-256 of every tracked file.sha2, already a dependency, run oversysroot::tracked_files. The gitlinkrust/is skipped as a directory.identity::of: thetoyos-abishape'sundocumented_unsafe_blocksreads// SAFETY:comments, and line numbers reach panic locations.2001-09-09.src/x/mod.rsbesidesrc/x.rsis E0761 to a cold build, yet it is in no dep-info, so dating that file alone left the crate fresh. Dating its package rebuilds the crate.toyos-build, that is the 59.2 s lib test plus 54.8 s of checks measured in run 4. 79 of main's last 80 landings touched a root-package file, and 60 of them touchedsrc/ortests/. An entry is the nightly's and a pull request is based on a later main, sotoyos-buildis almost always rebuilt anyway.rerun-if-*lines, and nothing at all for a proc macro. Dated whole, the package's build script reruns and its proc macro is rebuilt, and so is every crate that expands it, whatever either reads.[package], or[project], which cargo reads as the same table, hasbuild.rsbeside it and nobuild = false, or abuildkey naming a script; or when its[lib]saysproc-macroorproc_macro, or has aproc-macrocrate type. All 99 tracked manifests parse; the three such packages areuserland/calc,userland/doomanduserland/snake.calcalone of them. Its font sits in the root package, so it already reran on nearly every warm read.The runner image is in the manifest, not the key. No workflow expression sees
ImageOSorImageVersion, because theenvcontext holds only what a workflow sets. rclone'sbuild_publish_docker_image.ymlsays so ("ImageOS is only available to processes"), as does apache/httpd'slinux.yml("the env context, which does not see the runner's own $ImageOS");${{ env.ImageOS }}would key on the empty string.runner.osandrunner.arch, so a runner of another OS or arch never restores an entry. On ubuntu-24.04 the key's prefix ishost-sealed-Linux-X64-.runner()names the runner byRUNNER_OS RUNNER_ARCH ImageOS ImageVersionand refuses an unset one. The manifest records that name, and the seal prints it. A reader on another runner deletes every restored target and runs cold, because the image's linker and C compiler made the entry's units and cargo's fingerprint names neither. An image move therefore costs a cold run on every pull request until the next nightly writes on the new image.Only the writer seals. nightly.yml's
hostrunscargo run -- --ci seal; ci.yml'shostrunscargo run -- --ci host.ci::sealcallscicache::cold, thenhost, thencicache::seal, which takes theColdonlycicache::coldreturns. No flag decides whether a run seals:hostholds no seal.cicache::coldrefuses a driver built outsidetarget/ci-driver, then any target or manifest but the driver's own, and dates every source as built. So an entry is one cold build.hostreads the cache in every job that carries it. In the seal job that read follows the cold start, finds nothing, and says the run is cold.target/included.A job carries the cache when its workflow builds the driver in
target/ci-driver. Cargo builds the driver before any of this code runs, so its path crates are compiled again every run. In the steps' target, that rebuild would make every dependent stale.CARGO_TARGET_DIRin ci.yml's and nightly.yml'shost, so each of their steps is onecargo run -- --ci <job>and nothing else. Cargo hands that variable to the program it runs.ci::carry, whichhostcalls for a job that carries the cache, takes it out of the environment before the first step, so every step builds in its own target.cicache::carriedasks where the driver runs from. A job whose driver runs from anywhere else runs the host suite as a developer's tree does. That is nightly'sportability-macos, which runscargo run -- --ci hostafter--build-only: its driver is intarget/debug, nothing restores its tree, andreadwould refuse the targets--build-onlyleft, which no manifest describes.CARGO_TARGET_DIRwould run its restored tree judged by cargo's mtimes alone, with no image check, and nothing at run time would say so. The Caches bullet holds both jobs to it.What an entry holds.
ci::carrysets what the steps of a job that carries the cache build with; the driver keeps its own incremental state and debuginfo.CARGO_INCREMENTAL=0).toyos-buildlib test in 59.2 s and its checks in 54.8 s. The before median was 50.6 s and 47.6 s with incremental state present. On a runner, then, that state did not buy a changed crate's compile back.CARGO_PROFILE_DEV_DEBUG=line-tables-only). db4654f's run sealed 7684 MiB in 11823 files, against 3575 MiB in 15182 files on macOS (59cc178's run 36855422648).toyos-build's lib test, cross-linked forx86_64-unknown-linux-gnuby rust-lld with no C runtime, so that what it keeps is almost all DWARF, is 124,452,112 B with full debuginfo, 35,983,096 B with line tables alone, and 848,744 B with none.doorbell-kick-relaxedcontrol does in run 36867673999. Nothing reads the rest: no step setsRUST_BACKTRACE, and no host test reads a binary's DWARF.The seal bounds what the save would store.
cicache::sealsums the lengths of the files underPATHS, the list the save archives, with~taken fromHOME, and refuses aboveLIMIT, 8,000,000,000 B, before it dates a target or writes the manifest.LIMITis first refused by the next nightly's seal. Until an entry is sealed again, a pull request restores the last sealed one, or runs cold once the runner image has moved:issues/build/the-host-caches-limit-reaches-the-10-gb-only-through-one-measured-ratio.md, whose exit is a gate.path:lists toPATHS: actions/cache computes an entry's version from the list, so a restore whose list differs finds nothing, and the bound countsPATHSalone.hostsaves, the repository holds two host entries, yesterday's and tonight's, beside yesterday's guest entry; then tonight's host entry beside two guest entries. Both must fit: 2H + G ≤ 10 GB and H + 2G ≤ 10 GB. G is 3,281,375,938 B, the last guest entry.~/.cargopaths as well as the targets, so under it that run's ratio is at least 4.01. No gate reads a stored size: the limit's issue above.One writer.
nightly.yml'shostrestores nothing and runsseal, so its entry is one cold build, never an accumulation.seal's last step is the seal, so the job is green only with a sealed tree within the bound.github.ref == 'refs/heads/main', is its onlyif:and names no status function, so GitHub prefixessuccess() &&and the save follows only a green run. On a branch, aworkflow_dispatchbuilds cold and seals, bounded, and saves nothing.hoston macos-latest at the same 54 steps: cold on 09-29 in 593 s, and warm on 09-30 in 695 s (run 36550208853, run 36696295750). Restoring took 1 s against 76 s,cargo run541 s against 502 s, and saving 38 s against 104 s.hostlogs, on macos-26-arm64: on 09-29 it found no entry and saved 2,779,017,614 B; on 09-30 it restored that and saved 4,456,914,229 B; on 10-01 it restored that and saved 5,272,701,372 B. On 09-28 it had restored 6,911,542,445 B and saved 7,910,605,002 B, and on 09-29 that entry was gone.coldrefuses any other, so what the seal keeps is what that night's build produced.The workflow rules are review sentences, not a gate. A gate that re-reads GitHub's YAML lags GitHub's reader: actions/runner splits a
uses:path on/and\and drops empty segments, and YamlDotNet, which it reads with, breaks a line at U+0085, U+2028 and U+2029, where yaml-rust2 does not..claude/agents/reviewer.md: each cache has one writer, a nightly.yml job, and no workflow uses the combinedactions/cache, which saves too; the host cache's is nightly'shostand its one reader ci.yml'shost, on oneruns-on, both cachingPATHSwithDRIVERas theirCARGO_TARGET_DIR, the one variable anenv:gives either; the reader'srestore-keysis the writer'skeyup to its run id; those jobs run checkout, their cache step andcargo run -- --ci <job>alone,sealin the writer andhostin the reader, the reader restoring before that step and the writer saving after it; the save's guard is their only step-levelif:, ci.yml'shostskips only a draft, and nightly'shosthas noif:of its own, since a skipped job is a green check; nocontinue-on-error,shell:,defaults:or cargorunnerreaches them; nightly.yml'son:is one dailyscheduleandworkflow_dispatch.env:clause:runner()readsImageOSandImageVersionfrom the environment, so one set in both jobs would outlive an image move and pass the image check.<job>clause: a writer on--ci hostsaves no manifest, and every reader then refuses the targets it restored.--ci sealstores no target, the nightly stays green, and every pull request reads "none restored"..cargo/config.tomlsets arunner: the three arebootloader/'s,kernel/'s anduserland/'s, andgit grep runnerfinds nothing in them.ci::tests::each_cache_has_one_writer. Its rule is the prompt's: Growth's sentence on when a test is cut gains the case, "when this prompt takes its rule", and Caches' first sentence holds what the test asserted, oneactions/cache/saveper cache, each in nightly.yml, and no combinedactions/cache.issues/build/the-workflow-gates-that-read-workflows-as-text-become-reviewer-instructions-or-go.md, owned byissues/build/the-tooling-is-a-review-prompt-and-three-workflows.md.toyos-abi's clippy shape gets its own target dir. It lints the unit the workspace shape lints, under other lints. So each shape checked it again every run, and the workspace shape then checked its 12 dependents again. Measured here: 13 crates re-checked with the shared target, 0 with its own. In CI, run 2 did 1Checking, against 155 before.Stale-build cases (covered means a warm run rebuilds or refuses)
src/x/mod.rsbesidesrc/x.rs): covered when it is in the package that read it, or in the package of a#[path]file that read it.wayland-scanneralone reads a file it does not name, and no tracked source names it. Filed asissues/build/a-warm-host-run-keeps-what-a-registry-proc-macro-expanded-from-a-file-it-never-named.md, with a test as its exit.-Clink-arg=-T…from a.cargo/config.tomlorRUSTFLAGS), outside the package that links with it: not covered. Cargo compares the flag, never the file, in its own incremental build as well. No flag the host job passes names a file. Filed asissues/build/a-warm-host-run-never-relinks-for-a-file-only-a-flag-names.md, with a test as its exit.rustc -vV, profile, features, RUSTFLAGS,env!values,rerun-if-env-changedvalues, the unit graph, the package path): covered by cargo itself.Gates
cargo run -- --ci hoston 79bd4ec, the head with main's Worktrees and the primary's sync are git commands in the role prompts, a sysroot placement sweeps its store, and the prompts take the owner's decisions #678 (de5f63c) merged, on a developer's tree (macOS arm64), which carries no cache: "Host: 59 step(s), all green", EXIT=0. Itscargo test --lib: 373 passed, 0 failed, 8 ignored. Its clippy step, warnings denied: "clean". Its app steps: 11 apps each, built foraarch64-apple-darwinand checked forx86_64-unknown-linux-gnuandx86_64-pc-windows-msvc.cargo run -- --build-onlyon 79bd4ec: "Build finished.", EXIT=0.79bd4ec is 883adf1 with
.claude/agents/reviewer.mdchanged and nothing else (git diff --stat 883adf16a 79bd4ec49: 1 file, +15 −13), so what is measured below on 883adf1'ssrc/is measured on the head's.No guest test is reached: the diff against main changes
--ci hostand--ci sealinsrc/ci.rs,src/cicache.rs, which only they call, one clippy shape's arguments, a doc comment insrc/identity.rs, two workflows, a prompt and issues.The text gates' issue, measured on 038da72: each patch applied with
git apply --checkthengit apply, built (cargo test --lib --no-run, EXIT=0 each), the three tests run with--exact, each selecting one test, and reverted; the tree matched its prior state after each. Patches: the PR comment.workflows_run_against_main_on_hosted_runnersthe_required_check_is_a_job_on_every_pull_requestnothing_that_runs_names_a_target_directory_a_member_does_not_haveruns-on:label on the next line,- self-hostedpull_request: {branches: [dev]}hostgetsif: falserun: "ls userland/sshd/targe\x74"run: ls userland/sshd/target(positive control)CI on f7e0bd4, run 36906786717,
hoston ubuntu-24.04 green and cold: "Cache not found for input keys: host-sealed-Linux-X64-36906786717, host-sealed-Linux-X64-" (log line 146), "[ci] the cache entry, read by content: none restored: the run is cold" (309), "[ci] Host: 60 step(s), all green" (8496), and no seal step: a pull request's run does not seal. At 883adf1hostholds no seal at all.CI on 7923051, run 36897889906,
hoston ubuntu-24.04 green and cold, "Host: 61 step(s), all green": this PR's measure of a Linux cold tree against the bound, withseal_atthe same code as at 883adf1 but for the call that reads HEAD: "15976 files, 6720362549 B of the 8000000000 B an entry may hold; sealed: 2230 sources, built on Linux X64 ubuntu24 20260927.320.1, every target dated as built".CI on f9d535d, run 36890754662,
hoston ubuntu-24.04 green and cold. The job took 833 s.CI on b4dfda5, run 36881892289,
hoston ubuntu-24.04 green and cold, "Host: 58 step(s), all green". Thedoorbell-kick-relaxedcontrol's backtrace names file and line in the workspace's own frames, such astoyos-sched/loom/src/../../src/mailbox.rs:178:9.CI on e0ced58, run 36878222090,
hoston ubuntu-24.04 green and cold, is the run the ratio rests on. Its seal: "2229 sources, built on Linux X64 ubuntu24 20260927.320.1; 11827 files, 5369 MiB, dated as built". Its own archive of the cache's paths: "1403326566 B".Growth,
git diff --shortstat origin/main...HEAD: 13 files, +1040 −57. Production Rust +448 −7 (src/cicache.rs390,src/ci.rs+50 −2); test Rust +416 −29; workflows +21 −18;reviewer.md+16 −3; issues +139.High-risk checks
Oracle: cargo and the program it builds.
an_entry_serves_exactly_the_sources_it_was_built_frombuilds a 5-crate workspace cold and seals it. It then reads the entry from a checkout where one leaf changed and its file was dated 1970, one package lost a file that only its build script counts, and one lost a file nothing reads. The binary must print what a cold build prints, and cargo must report the untouched crate fresh and the other four rebuilt.a_file_cargo_was_never_told_about_rebuilds_its_packageholds a warm build to rustc's cold verdict: E0761.code_run_at_build_time_runs_again_on_every_read: a build script and a proc macro, in packages that do not change, read another package'sword.txtand never say so. Afterword.txtchanges, the warm build must printtwo two, as a cold build does.a_step_of_a_job_that_carries_the_cache_builds_in_its_own_target:ci::carryruns in a driver of its own, spawned as the job spawns one, withCARGO_TARGET_DIR=target/ci-driverand withoutCARGO_INCREMENTALorCARGO_PROFILE_DEV_DEBUG. Thencargo metadatamust name the one-crate fixture's owntarget, andcargo build -v's rustc line must carry no-C incrementaland-C debuginfo=line-tables-only.Negative controls, measured on f7e0bd4. One script applied each patch with
git apply --checkthengit apply, built it (cargo test --lib --no-run, EXIT=0 every time), ran the named test with--exact, and reverted it withgit apply -R;git status --porcelainwas empty after each. Since f7e0bd4,src/cicache.rsmadePATHSprivate, moved the seal's driver check intocold, and reads HEAD through main'ssysroot::git_out, andci::carrydid not change: each of the 69 lines these patches remove is present verbatim at 883adf1 (every one found bygrep -xFin the file its patch names: 69 lines, 0 missing, EXIT=0). Patches: the PR comment.The merge's one changed line, measured on 883adf1. The seal reads HEAD through
sysroot::git_outand trims it. mH leaves it untrimmed:git apply --check0,cargo test --lib --no-runEXIT=0,cargo test --lib -- cicache::EXIT=101 with 5 of 10 red, each ontarget/ci-sources holds "macOS ARM64 macos15 20260928.1", the runner read where a source line belongs;git apply -R0 andgit status --porcelainempty. Patch: the PR comment. A git that cannot answerrev-parse HEADpanics the seal, as it does main'sci::publish.cicache::coldtakes a tree with restored targetstargets_restored_without_a_manifest_are_refused[Some("…/kernel/target restored without target/ci-sources, …"), None]: the reader refused, the writer did nota_seal_refuses_what_its_save_would_store_above_the_boundone byte above the bound: "2 files, 8000000001 B of the 8000000000 B an entry may hold; sealed: …"at the bound: "8000000000 B in 1 files under the cache's paths, above the 8000000000 B an entry may hold: …"one byte above the bound: "2 files, 8000000000 B of the 8000000000 B an entry may hold; sealed: …"a refused tree carries a manifestPATHSat the bound: "8000000001 B in 2 files under the cache's paths, …":tests/target, which no save stores, was counted~read from the root, notHOMEone byte above the bound: "1 files, 8000000000 B of the 8000000000 B …": the home's byte was not countedread <tmp>/home/.cargo/registry/index: No such file or directory (os error 2)each_underenters no directoryone byte above the bound: "1 files, 1 B of the 8000000000 B …"a_seal_dates_every_target_and_refuses_a_written_sourcetarget/debug/deps/xattv_sec: 1790878652, not1000000000unwrap_err()onOk("2 files, 2 B of the 8000000000 B an entry may hold; sealed: 1 sources, …")target/debug/deps/xattv_sec: 1790878663, not1000000000restored()finds nothing in the root'starget/targets_restored_without_a_manifest_are_refused[None, None]: neither the reader nor the writer refusedtarget/debuga_reader_whose_clock_is_not_after_the_entry_is_refuseda clock at the entry's date: "built from ff968c0…: 1 of 1 sources dated as built; …"an_entry_built_on_another_runner_is_deletedImageOS: built from 7bcbf90…: 1 of 1 sources dated as built; …a_file_cargo_was_never_told_about_rebuilds_its_packageprobedreported"fresh":true,build-finished success:truean_entry_serves_exactly_the_sources_it_was_built_from"gone": trueleft: "one 1",right: "two 1"left: "one 1",right: "two 1"left: "one 2",right: "two 1"a warm tree keeps no manifest"gone": trueRUNNER_OS,RUNNER_ARCH,ImageOS,ImageVersiondropped from the runner, one eachan_entry_built_on_another_runner_is_deletedImageVersion: built from be909b5…: 1 of 1 sources dated as built; …a runner without a variable: "macOS ARM64 20260928.1"code_run_at_build_time_runs_again_on_every_readleft: "one one",right: "two two"every_spelling_of_code_run_at_build_time_is_foundcode_run_at_build_time_runs_again_on_every_readleft: "two one"every_spelling_of_code_run_at_build_time_is_found[package] "[lib]\nproc-macro = true\n", build.rs: falsecode_run_at_build_time_runs_again_on_every_readleft: "one two"every_spelling_of_code_run_at_build_time_is_found[package] "", build.rs: trueruns_at_buildreads[package]alone[project] "build = \"gen.rs\"\n", build.rs: false,left: Ok(false),right: Ok(true)target/ci-driverexists, any driver carries the cacheonly_a_driver_built_in_its_own_target_carries_the_cachetarget/debug/toyos-buildtarget/ci-drivercarries ita tree with no target/ci-drivertarget/ci-driver/debug/toyos-buildcarryleavesCARGO_TARGET_DIRa_step_of_a_job_that_carries_the_cache_builds_in_its_own_targetleft: Some(".../ci-carried-0/target/ci-driver"),right: Some(".../ci-carried-0/target")carryleaves incremental state on-C incremental=<fixture>/target/debug/incrementalcarryleaves debuginfo whole-C debuginfo=2RUNNER_ARCH,ImageOSandImageVersionthe way m14a dropsRUNNER_OS.gone:count's build script reruns on every read, socountalone cannot show a removed file's package dating.The seal job's route stands on reading, with no flag in it:
Job::Seal => seal(root), andci::sealcallscicache::cold,hostandcicache::seal.Unsure
runs_at_buildruns only on a warm read.--ci sealhas not run on a runner. Aworkflow_dispatchof nightly.yml on this branch would run it and save nothing.guestandtcg, which name the guest cache, are held to the one-writer sentence alone: theirdepsstep is the shellissues/build/the-build-runs-host-tools-outside-rust-and-qemu.mddeclares, and their serial-log upload runs underif: failure()withcontinue-on-error. The guest cache keeps today's discipline and no bound:issues/build/the-guest-cache-is-read-by-mtime-and-its-writer-restores-before-it-saves.md. At run 36878222090's ratio the host limit leaves it room up to 4,002,930,524 B.🤖 Generated with Claude Code
https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm