Skip to content

An image release: main's disk image, booted before a separate job publishes it - #556

Open
Japabu wants to merge 13 commits into
mainfrom
wt/toyos-release
Open

Japabu wants to merge 13 commits into
mainfrom
wt/toyos-release

Conversation

@Japabu

@Japabu Japabu commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

A disk image anyone can download from GitHub and start under QEMU or write to a USB stick. The nightly builds it once and boots it. On main, on a night the guest suite passed, it publishes the bytes that booted, from a job whose token is the only one that writes.

This branch lands after the kernel boots on the edk2 firmware Homebrew's QEMU ships (issues/kernel/the-kernel-dies-at-boot-on-the-edk2-firmware-qemu-ships.md, status: expected-red). Until then release_command_boots is disabled in src/redlist.rs against that issue.

What changed, per decision

The image. build::Boot::release is the shipped system.toml less every program whose package the licence gate holds pending the owner (licence::pending_owner). assets::collect already leaves out what only a missing program opens. The image goes to target/bootable-release.img, and its licence notice to target/bootable-release-licences.txt. There is no command-line flag for it. The release job and the test build it in-process, so one path builds the image. The notice is computed before the build takes its lock, and written under buildlock::licence.

Two jobs; the write token never meets a build.

  • release (needs: build) runs in the pinned *kvm Debian container with permissions: contents: read. It restores the guest cache, so it compiles and runs third-party build code. Its logic is cargo run -- --ci release, in the guest jobs' frame (the instrument, the toolchain, then imagerelease::release, in a private $TMPDIR that must be empty at the end). release:
    • builds the release image once;
    • boots a copy under the notes' Linux line (imagerelease::boots, Host::LinuxKvm) to a painting desktop;
    • writes the four assets into target/image-release: toyos.img.gz, SHA256SUMS, the notes and licences.txt;
    • answers their imagerelease::digest (the SHA-256 of one sha256sum line per asset) as the step output digest.
    • The directory is uploaded as the image-release artifact.
  • release-publish (needs: [build, guest, tcg, release]) is the one job with contents: write. So a night whose build, any guest shard or tcg went red publishes nothing. It restores no cache, and its checkout keeps no credential (persist-credentials: false). It downloads the artifact and runs cargo build -p toyos-build with no token in its environment. Only the last step gets GH_TOKEN, together with IMAGE_RELEASE_DIGEST, the release job's output. That step is cargo run -- --ci release-publish (imagerelease::publish):
    • It refuses a directory that holds anything but the four assets, or whose digest is not the one handed over.
    • Off main, it stops there, so a branch dispatch measures the handover.
    • On main, it creates the release as a draft carrying the assets. Once gh release create has exited 0, it publishes (--draft=false --latest) and deletes image releases past the newest 7.
    • A tag GitHub already holds as a published release with the image is left alone. A tag it holds in any other state is refused by name.
    • It refuses to run unless GITHUB_SHA is set and is the checkout's HEAD.
  • ci::a_token_that_writes_meets_no_cache_and_only_the_step_that_needs_it holds every workflow to this. A job's permissions are read from its own permissions: key, written on one line (write-all) or nested, and otherwise from the workflow's. A job whose permissions write:
    • may not restore a cache, directly or through a step anchor;
    • may not check out without persist-credentials: false, directly or through a step anchor;
    • may not set GH_TOKEN in job-level env.
  • The jobs whose tokens write are build (contents: write), release-publish, nightly-red (issues: write) and publish.yml's publish (id-token: write). Each checks out with persist-credentials: false. nightly-red has its own checkout step for this, not the shared *checkout.
  • actions/download-artifact v4.3.0 joins CI_ACTIONS, pinned by commit.
  • timeout-minutes: 60 on release is a wedge guard. It is sized from the tcg job, the same container building and booting one image: 13m07s, 9m41s and 10m17s on three nightly runs (gh run view). Both jobs join nightly-red's needs.

imagerelease::boots, shared by the job and release_command_boots, requires, within a liveness ceiling:

  • every program [boot] start names says it started;
  • compositor: ready, netd: ready and logd's line;
  • a compositor frame report.

Any PANIC or panicked at line fails it. It fingerprints both firmware files (fingerprint::whole_device) before the boot and again after QEMU is reaped, and fails if either changed. A missing firmware file is refused by name.

Hosts. The notes print two lines: Host::MacosAppleSilicon (Homebrew's QEMU, TCG) and Host::LinuxKvm (Debian's qemu-system-x86 and ovmf, KVM). The reader is assumed to have QEMU installed.

  • The Linux line is the release job's own boot.
  • The macOS line is release_command_boots (tests/common/release.rs). It is in no tier and in no test table. The suite's --release-command flag runs it and nothing else, and refuses any other word beside it. A disabled row makes that run exit 1, so a run that booted nothing is never green.
  • The nightly's portability-macos job runs on an Apple Silicon runner with Homebrew's QEMU. After its --build-only it runs env -u GITHUB_ACTIONS -u CI cargo test --test toyos-build -- --release-command. Nothing else runs the flag.

std's library/, one way. licence::std_library answers:

  • in a linked worktree, its fork checkout;
  • where this checkout holds it, rust/library;
  • everywhere else (a CI runner, an installed toolchain), fetched_library.

main's git submodule update --init branch is deleted. fetched_library works as follows:

  • It fetches the pinned commit with --depth 1 --filter=blob:none, sparse. The sparse set is /library/ plus one pattern for each name in LICENCE_FILES, the list licence_files reads a package's texts by, matched in any case. So a top-level licence file of the fork cannot be left out of the fetch while a dev host's notice has it.
  • The fetch goes into target/licence/fork.partial under buildlock::licence, and is then renamed to .licence-fork/. A fetch that is cut short is therefore never at the path a later call reads.
  • A fetch is reused only at the pinned commit and the same sparse set.
  • .licence-fork/ is a direct child of the checkout, hidden and ignored as .build-locks/ is. std's manifest names toyos and toyos-abi at ../../../ from its crates. Under target/ they do not resolve: with the fork at target/licence/fork/<commit>/, cargo metadata failed with failed to read `…/target/licence/fork/toyos/Cargo.toml` .

buildlock::licence is one exclusive lock per worktree over three writes: the fork's fetch, the walk's scratch target/licence/Cargo.lock (copy through cargo metadata), and the release notice. It is taken with no other lock held.

the_release_notice_carries_every_package_and_file_it_ships takes std from std_library, as the build does. On a dev host it reads the checkout's fork and fetches nothing. On CI's host runner, which has no rust/ source, it is fetched_library. a_fetch_cut_short_is_fetched_again_and_a_new_pin_replaces_the_fork covers the fetch offline against a file:// remote. That remote carries two top-level files per LICENCE_FILES name, one upper-case and one lower-case, and the test asserts that each is checked out.

Licence notices (licence::notices), written from the licence gate's own walk:

  • Every package the walk reaches is listed with its licence, its authors, where its source is, and its licence texts. The source is:
    • the crates.io .crate download for a registry package;
    • the git URL for a git one;
    • the fork's URL at its pinned commit for std's;
    • the path in the ToyOS source for ToyOS's own.
    • This satisfies MPL-2.0 §3.2(a) for the loader's uefi, uefi-raw, uefi-services and ucs2.
  • A package's texts are the files in its own directory whose names start license, licence, unlicense, copying, copyright or notice, plus its license_file.
    • A path package with none takes those of the nearest enclosing directory, up to the one holding .git.
    • A package that publishes no licence file gets the standard text of its licence from the fork's LICENSES/, one for each part of an AND. For an OR, it gets the Apache-2.0 branch where there is one: that text is whole as it stands, while MIT's asks for a copyright line. Otherwise it gets the first branch the fork holds.
    • A package that every edge into names another target by its triple is left out (fortanix-sgx-abi, std's for SGX only). The notice's opening sentence says so.
  • Every NOTICE section over a file that ships is included, less what is pending the owner.
  • Each text is given once, cited by number.

The notice goes on the release's ROOT at share/licences.txt and beside the release as licences.txt. Both are the one file the build wrote.

The notes. Before the dd line they name:

  • the floor variable (ToyOSImageFloor-I and 16 hex digits, under vendor GUID 33be3d4a-30e6-49f5-8050-f169d93a20fb, from toyos_update::floor);
  • that the variable stays after the stick is gone, that no OS can see or remove it, and that only dmpstore -d in a UEFI shell does;
  • that booting another ToyOS stick replaces it;
  • BootNext;
  • the unmount step.

The disk paragraph names the one disk a boot may write though it was not given it. With two sticks of one image plugged in, the loader can write its log to the one it did not boot from. The notes cite issues/boot-media/the-loader-writes-the-first-disk-carrying-its-log-guid-not-the-one-it-booted-from.md, and a test requires that issue to be open while the sentence stands. The withheld list comes from pending_owner.

gh's answers. Two pure functions read them:

  • held_of takes only gh's own release not found as nothing published. A bad token was measured to answer non-200 OK status code: 401 Unauthorized …, exit 1, and that is an error here.
  • releases refuses a listing that fills its 1000 limit, and an entry without a tag and a time.

The disk test. storage::foreign_disk_untouched boots twice on Profile::UsbDisk. Each boot has the NVMe disk, whose TOYOS-DATA partition holds another system's volume, and one USB disk beside the boot stick:

  • first, a disk whose GPT names an EFI system, a Microsoft basic data and a Linux filesystem partition over a filled disk;
  • then a filled stick with no table.

Each boot asserts, in order:

  • the kernel refused the NVMe volume;
  • the boot completed;
  • nothing was formatted;
  • after run shutdown and QEMU's exit (await_exit), the kernel's line for reading that USB disk's table is in the log;
  • both disks are unchanged, byte for byte.

Gates (d30dfff)

  • cargo run -- --ci host: EXIT=0, Host: 49 step(s), all green.
  • cargo test --lib: EXIT=0, 393 passed, 1 ignored.
  • cargo run -- --clippy: EXIT=0, 10 invocations clean.
  • cargo test --test toyos-build --no-run: EXIT=0.

Host red arms at this head. Each is a checked patch (git apply --check), run, then reversed, with git status empty after. The test binary compiled in each run; the two workflow patches change only YAML the test reads at run time:

  • release's permissions: block replaced by permissions: write-all (m-a.patch): a_token_that_writes_meets_no_cache_and_only_the_step_that_needs_it EXIT=101, nightly.yml: release restores a cache with a token that writes.
  • with: persist-credentials: false deleted from release-publish's checkout (m-b.patch): the same test EXIT=101, nightly.yml: release-publish keeps a token that writes in its checkout.
  • the sparse set put back as the hand list /library/, /COPYRIGHT, /LICENSE-*, /LICENSES/ (m-sparse.patch): a_fetch_cut_short_is_fetched_again_and_a_new_pin_replaces_the_fork EXIT=101, LICENCE-A was not checked out.

The notice test with no fetched fork and no network for git: .licence-fork moved out of the checkout, then GIT_ALLOW_PROTOCOL=file CARGO_NET_OFFLINE=true cargo test --lib -- licence::tests::the_release_notice EXIT=0. No .licence-fork was made.

Host red arms at 20179a7:

  • fetched_library put back as it was, fetching into its final path: a_fetch_cut_short_is_fetched_again_and_a_new_pin_replaces_the_fork EXIT=101, git rev-parse HEAD exited exit status: 128: fatal: ambiguous argument 'HEAD'. That is the unborn HEAD a failed fetch left, refused on the next call.
  • /COPYRIGHT dropped from the sparse set: the_release_notice_carries_every_package_and_file_it_ships EXIT=101, std cites no COPYRIGHT.
  • an OR given its first branch held: an_or_is_given_apache_2_0s_standard_text_where_it_is_a_branch EXIT=101, left: ["MIT.txt"] right: ["Apache-2.0.txt"].
  • the digest over the image alone: the_digest_covers_every_asset_and_refuses_anything_else EXIT=101, SHA256SUMS is outside the digest.
  • contents: write back on the release job: a_token_that_writes_meets_no_cache_and_only_the_step_that_needs_it EXIT=101, nightly.yml: release restores a cache with a token that writes.
  • the notes saying never written again: the_notes_cite_the_open_defect_behind_the_disk_a_boot_may_write EXIT=101.

At 002bc8d:

  • no standard texts: EXIT=101, shipped package(s) carry no licence text;
  • the notes without the firmware bullets: EXIT=101;
  • any gh failure read as absent: EXIT=101;
  • the listing's limit unchecked: EXIT=101.

Run by the orchestrator

At 20179a7 (556r3):

  • foreign_disk_untouched: EXIT=0.
  • with byte-oracle-nvme-designated.patch: EXIT=1, the kernel wrote to …/foreign-disk.img, a disk it was not given.
  • with byte-oracle-usb-designated.patch: EXIT=1, the kernel wrote to …/bare-stick.img, a disk it was not given.

Owed:

  • release_command_boots on an Apple Silicon Mac after the edk2 fix, with the row removed (redlist-enable-release-command-boots.patch): cargo test --test toyos-build -- --release-command. Then with readonly-removed.patch on top.
  • The nightly dispatched on this branch. It is the first run of the Debian OVMF paths, the KVM line, the artifact handover, the digest check, release-publish's needs, the build and nightly-red checkouts without a persisted credential, and portability-macos's --release-command.
  • One draft release created, viewed by its tag and deleted, which is the first run of gh's draft path.
  • The release image on the T14 through the metal loop.

High-risk: the two checks

A boot writes no disk it was not given:

  • Negative control: the two byte-oracle patches. Each makes one of the disks the kernel's to format and deletes the log checks, so only the bytes can go red. Both were EXIT=1 above.
  • Independent oracle: each disk's backing file on the host, fingerprinted per MiB with SHA-256 after QEMU has exited. QEMU's block layer decides what is in those files, not the guest.

The published command line writes no firmware file:

  • Negative control: readonly-removed.patch, owed.
  • Oracle: both firmware files' bytes on the host.

The token that writes releases meets no code a build ran:

  • Negative control: m-a.patch and m-b.patch, EXIT=101 above.
  • Oracle: the GITHUB_TOKEN Permissions block GitHub prints in each job's set-up log, which the branch dispatch owes.

Unsure

  • release-publish does not need portability-macos. So a night whose macOS line did not boot still publishes notes that print that line. Until the edk2 fix lands, that line does not boot and portability-macos is red every night.
  • build's bootstrap and toolchain steps have not yet run with no persisted credential. ToyOSOrg/ToyOS and ToyOSOrg/rust are public (gh repo view … --json visibility), so their anonymous git ls-remote and submodule fetch are expected to work. The dispatch owes that measurement.

Filed

  • issues/build/the-toolchain-install-unpacks-an-asset-no-digest-vouches-for.md: release::install unpacks and links the toolchain asset with no digest check. The nightly's build job and release-publish hold a token that can replace it. The owner is the release module. The exit condition is a digest that no job holding a write token can rewrite.
  • issues/boot-media/the-loader-writes-the-first-disk-carrying-its-log-guid-not-the-one-it-booted-from.md.
  • issues/kernel/the-kernel-dies-at-boot-on-the-edk2-firmware-qemu-ships.md, expected-red until the edk2 fix lands.
  • issues/build/a-toolchain-release-takes-the-latest-badge-from-the-image-release.md.

🤖 Generated with Claude Code

Japabu and others added 3 commits September 27, 2026 22:44
`cargo run -- --release-boot --build-only` builds `target/bootable-release.img`:
the shipped config less every program whose package the licence gate holds
pending the owner (doom), and so less the assets only doom opens (DOOM1.WAD and
the SoundFont). `cargo run -- --ci release`, a new nightly job that needs every
other lane green and runs on main alone, installs the toolchain, builds that
image, and publishes it as `image-x86_64-<12 hex of the commit>`: the image
gzipped, its SHA256SUMS, the notes as README.md, and ToyOS's licences, NOTICE
and the texts NOTICE names for what the image ships. It keeps the newest seven
image releases and deletes the rest with their tags.

The notes print one QEMU command line per host, declared once in
`imagerelease::Host::command` with QEMU's own edk2 firmware; the harness boots
exactly those lines: `release_command_boots` to the desktop, and
`release_writes_no_other_disk` beside an NVMe disk laid out as another
operating system's and a stick with no table, comparing both byte for byte.

Measured on the dev host before this commit: the macOS line over a copy of
`target/bootable.img` early-panics on Homebrew QEMU 11.1.1's edk2 firmware
(memory allocation of 4096 bytes failed, right after pmm), and reaches
`compositor: ready` with the repository's `ovmf/` firmware in its place.
Filed as issues/kernel/the-kernel-dies-at-boot-on-the-edk2-firmware-qemu-ships.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rch names it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu marked this pull request as ready for review September 27, 2026 21:05
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #556 at 748890e (first round)

Readiness is not met, and under reviewer.md that alone is NOT READY FOR REVIEW. The two guest tests this branch adds have never run at any head. The body's "both GREEN" and "negative control RED" are predictions, and the author's scratchpad holds no run of either. probe.log and probe-ovmf.log are hand boots of target/bootable.img, not of the release image. On the dev host both tests are red with no src/redlist.rs entry, and the issue carries status: open, not expected-red. PR CI host is still pending at 748890e. The stick path has no hardware reading. The brief asked for a full judgement, so the full review follows.

Net: +1053/−21.

  • Production: about 465 lines (imagerelease.rs 346, build.rs 38, licence.rs 29, main.rs 22, sourcegate.rs 14, ci.rs 13, flags.rs and lib.rs 3).
  • Tests: 492 lines (183 unit, 309 guest harness).
  • Issues: 73 lines. Workflow and manifest: 23 lines.
  • Deletable, as named below: about 250 lines.

BLOCKER

  • tests/common/release.rs:141-224 — The added tests are unmeasured, and red on the dev host with no redlist entry. Required runs, each with its command, exit code and log, at the head that lands:
    • (a) cargo test --test toyos-build -- release_command_boots release_writes_no_other_disk green on the dev host, using the Homebrew firmware the notes name;
    • (b) the nightly dispatched on the branch (gh workflow run nightly.yml --ref wt/toyos-release), with the conclusion of the guest shard that holds both tests;
    • (c) the negative control and mutation named below, red;
    • (d) the release image (not target/bootable.img) booted on the T14 through the metal loop.
  • src/imagerelease.rs:112-115 — The notes print a macOS command line whose only measurement is an early panic (probe.log: EARLY PANIC … memory allocation of 4096 bytes failed). Publishing it ships an instruction known to fail. This branch lands after the kernel fix, and after (a) above is green at the merged head. Until then, no notes carry that line.
  • .github/workflows/nightly.yml:257-268 — The published bytes are never booted. Each guest shard built and booted its own image in the Debian container. The release job then builds a new image on bare ubuntu-24.04 and uploads that one. "Known to boot" is true of the commit, not of the artifact. The trigger (Q3): gate on a smaller set, the release's own two tests plus the build, run on the exact bytes. needs: [build] is enough: it provides the toolchain, and host already gates every commit on main through the merge queue. Run the job in the pinned *kvm Debian container. Build the release image once, run the release tests against that file, and publish those same bytes: either hand the file to a bare gh job through an artifact with its SHA-256 re-checked, or install gh in the container. Name neither audio (being removed) nor the other lanes. A red nightly that has nothing to do with the image then blocks nothing, and every published image has booted.
  • tests/common/release.rs:155-224 — release_writes_no_other_disk has three problems:
    • It is a sibling of storage::foreign_disk_untouched: the same claim, the same whole_device oracle, and a weaker stimulus. With no TOYOS-DATA partition, the kernel never opens a page cache at all.
    • Its host oracle has no red arm. The guest is killed (Drop → kill) after "three more frame reports". PageCache::sync reaches the device only at sync, and storage.rs:82-84 records exactly why the existing test shuts down instead. The negative control is red only on the guest's own log line (:184).
    • The three-frame wait is a flat wait on "late writes are in".
      The fix: delete it, along with another_os_disk and pattern_file. Add the non-DATA NVMe disk and the table-less USB stick as stimuli of foreign_disk_untouched, which shuts down. If a release-image arm is kept, it has to end the guest through the kernel's sync path, and its byte comparison has to go red alone, with the page cache: device check deleted, on the designated-disk patch.
  • src/imagerelease.rs:226-228 — Licensing (Q2) is a BLOCKER for a public binary release. Filing an issue does not make distribution compliant.
    • The loader on the image links uefi 0.26, uefi-raw 0.5, uefi-services 0.23 and ucs2 0.3.2, all under license = "MPL-2.0" in their registry manifests. MPL-2.0 §3.2(a) requires that recipients of the executable be told how to obtain the Source Code Form.
    • BSD-2/3-Clause requires the notice to be reproduced with binary redistributions. MIT requires it in all copies. Apache-2.0 §4(a) requires a copy of the licence, and §4(d) the upstream NOTICE contents.
    • Rust's std, built from rust/, is on the image too.
    • Generate the notices from the licence gate's own walk: each shipped package's licence files and copyright, its NOTICE, and, for MPL, where its source is. Carry them on the image's ROOT, because a stick written with dd has no release page. Carry them beside the image as well. Add a test that goes red when a shipped package carries none. The filed issue goes with that fix.
  • src/imagerelease.rs:223 — Firmware variables (Q1). Writing them is acceptable. The floor is one 8-byte variable under ToyOS's own vendor, at most one per scope (toyos-update/src/floor.rs:20-23). BootNext is one-shot, visible to the OS, and set only where a boot entry names the stick. The disclosure is not enough. The floor is non-volatile and boot-services-only (bootloader/src/floor.rs:28-29): it outlives the stick, and the user's OS can neither see nor delete it. The notes must say this before the dd line:
    • its name, ToyOSImageFloor-I…, and its vendor GUID, 33be3d4a-30e6-49f5-8050-f169d93a20fb;
    • that it persists, and that it is removed only from a UEFI shell (dmpstore -d);
    • that BootNext makes the next restart boot the stick once.
  • src/imagerelease.rs:143 — This mutation passes every test: change &format!("if=pflash,format=raw,unit=1,file={vars},readonly=on") to &format!("if=pflash,format=raw,unit=1,file={vars}"). A user following the notes would then let edk2 write the shared vars template of their QEMU installation (on Linux as root, /usr/share/OVMF). release_command_boots must take a whole_device fingerprint of both firmware files before and after the boot, and go red on this patch.
  • src/imagerelease.rs:116, .github/workflows/nightly.yml:261 — The Debian OVMF paths and the Linux KVM line are guesses, although one branch dispatch of the nightly measures both at no risk (the release job is main-only). timeout-minutes: 120 is unmeasured. Size it from a measured cold build, or call it a wedge guard sized from one.

NOTE

  • src/main.rs:152-172, src/flags.rs:77-78 — The 9-flag refusal list protects nothing a publisher reaches: publish passes a fixed argv (imagerelease.rs:311), and the tests build Boot::release in-process (release.rs:34-36). That is two build paths for one image. Build in-process in publish too, and delete the flag, the list and the nested cargo run. Or keep the flag as the only path, with an allowlist: a denylist lets a future flag that changes the image pass silently.
  • src/imagerelease.rs:47-54, src/licence.rs:971-1008, src/imagerelease.rs:399-441 — LICENCE_ASSETS is a hand mirror of NOTICE, plus a test that the two agree. The generator in the licensing BLOCKER reads NOTICE's Licence text: lines itself, which deletes the list, the texts parsing and the test.
  • src/imagerelease.rs:287 — published reads every gh failure (auth, network) as "not published". Tell a missing release apart from an error, and go red on the error.
  • src/imagerelease.rs:333-337 — --limit 1000 and filter_map(split_once) drop input silently. Go red when the list reaches the limit or a line does not parse.
  • src/imagerelease.rs:321 — An upload that fails after the release is created can leave a public release without its image. Create with --draft, verify with published, then run gh release edit --draft=false --latest.
  • bootloader/src/loaderlog.rs:95-111 — Outside this fence; file it. Every copy of a published image has the same partition unique GUIDs. The loader takes the first filesystem anywhere on the machine that carries the log GUID. With two sticks of one release plugged in, loader.log and the attempt records land on the stick it did not boot from. That is a disk with no TOYOS-DATA partition, so it contradicts the notes' claim.
  • src/imagerelease.rs:215-218 — The dd line was measured only against a file. No unmount step is given (macOS: diskutil unmountDisk), and it has not been run against a device.
  • src/imagerelease.rs:78-88 — Host::this() makes both tests permanently red on an Intel Mac, on aarch64 Linux, and on Linux without KVM. They are Fast tier and have no redlist entry.
  • src/imagerelease.rs:230 — The withheld list is written by hand rather than taken from licence::pending_owner().
  • gh in CI is acceptable: it is declared in HOST_SPAWNS, and nothing that builds or boots reaches it. No test runs gzip or sha256sum (flate2 and sha2 are used in-process). sha256sum is stock on macOS, at /sbin/sha256sum.

REMOVE

  • src/imagerelease.rs:224 — "release_writes_no_other_disk boots this image … and compares every byte of it": a test name in public notes, and an overclaim (the guest is killed before any sync).
  • src/imagerelease.rs:230 — "whether they may ship is the owner's to rule (src/licence.rs), and nothing is published while it is not": internal governance and a source path in public notes; it rots when the owner rules.
  • .github/workflows/nightly.yml:254-256 — "of the commit every lane above passed": false once the trigger changes.
  • Cargo.toml:163-164 — "Already resolved here through bcachefs": the history of a lock resolution.
  • PR body "Run by the orchestrator" — predictions written as outcomes.
  • PR body "Blocked on macOS" and "Unsure" — process state, not main's record.
  • PR body "all 6 of main's nightly runs … were failure" and the asset byte counts — counts that rot.

SEND BACK

Japabu and others added 4 commits September 27, 2026 23:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… notice

The release job needs only `build`, runs in the pinned Debian KVM
container, builds `Boot::release`'s image once in-process, boots a copy
of it under the Linux line the notes print (`imagerelease::boots`), and
on main uploads those same bytes: created as a draft, checked to carry
the image, then published. Off main it builds and boots and publishes
nothing, so a branch dispatch measures the Debian OVMF paths and the KVM
line. `--release-boot` and its refusal list are gone; `--ci release`
shares the guest jobs' frame (instrument, toolchain, private $TMPDIR).

`boots` fingerprints both firmware files before and after the guest, so
a line that lets edk2 write the shared variable-store template is red.

`licence::notices` is written from the licence gate's own walk: every
package with its own licence files, or the standard texts of its licence
from the fork's LICENSES/ where the package publishes none, with its
source (the crates.io download, the git URL, or the fork at its pinned
commit for std); and every NOTICE section over a shipped file with its
texts, less what is pending the owner. It is on the release's ROOT at
share/licences.txt and beside the release. A package with no text reds
`the_release_notice_carries_every_package_and_file_it_ships`. Where the
toolchain was installed, std's library is fetched sparse beside rust/,
since a source tree at rust/ makes the toolchain the checkout's to build.
This replaces LICENCE_ASSETS and its hand-mirror test.

The notes name the floor variable (its name's head and the vendor GUID,
now `toyos_update::floor::VENDOR`, which the loader parses), that only
`dmpstore -d` removes it, and that BootNext boots the stick once, before
the dd line; the dd line gains its unmount step; the withheld list comes
from `pending_owner`. `gh release view` answering anything but `release
not found` is an error, and a listing that fills its limit or carries an
entry without a tag and a time is refused.

`release_writes_no_other_disk` is deleted. `foreign_disk_untouched`
gains a USB disk whose table names only other systems' partitions and a
USB stick with no table, asserts the kernel read both, and waits for
QEMU to exit after `run shutdown` rather than draining for a fixed time.

`release_command_boots` is disabled while the kernel dies on Homebrew's
edk2; the loader writing a second stick of one release is filed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`held_of` reads `gh release view` and takes only its own `release not
found` as nothing published; `releases` reads `gh release list` and
refuses a listing that filled its limit or an entry without a tag and a
time. foreign_disk_untouched hands its disks to the boot without clones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu Japabu changed the title An image release: main's disk image once its nightly is green An image release: main's disk image, booted before it is published Sep 27, 2026
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #556 at 002bc8d (round 2)

Readiness is still not met. PR CI host is IN_PROGRESS at 002bc8d. The author's local cargo run -- --ci host gave EXIT=0 (cihost3.log). foreign_disk_untouched has no run at this head: orch-runs/summary.txt has no 556r2 line. release_command_boots lands disabled. There is no T14 reading. The full review follows, as the brief asks.

Net: +1494/−94. Production is about +928: imagerelease.rs 527 of its 657 lines, licence.rs about 340 of 419, build.rs about 81 of 142, plus ci.rs, sourcegate.rs and the workflow. Tests are about +380 and issues +77. I accept the growth. The notice generator and the build-boot-publish flow are what round 1's licensing and published-bytes BLOCKERs required. The cuts are named below: B1's second fetcher, N2 and N3. I found no larger cut that keeps a legal or safety property. Std's licence cannot be carried as one known text: the walk reads std's graph out of library/, and that graph brings in compiler_builtins, hashbrown and rustc-demangle, each with its own licence file (notice.txt: 346, 916, 1898).

Round-1 BLOCKERs

BLOCKER

  • src/licence.rs:1240-1253 — Two fetchers of the same library/ at the same commit. Owner::Us uses git submodule update --init --depth 1 rust, and Owner::Installed uses fetched_library.
    • The only red test of the notice, the_release_notice_carries_every_package_and_file_it_ships, runs in host CI on the submodule path.
    • The release job and the Linux guest shard take the sparse path, which has never run: no log in release-r2/ shows it, and the worktree has no target/licence/fork. A wrong sparse pattern, for example a missing /COPYRIGHT, reaches a published notice without turning anything red.
    • Patch: std_library = Owner::Elsewhere → fork_checkout(root).join("library"); rust/library/Cargo.toml present → that; else fetched_library(root). Delete the submodule-init branch.
    • Measurement: PR CI host green, which then runs the notice test through fetched_library.
  • .github/workflows/nightly.yml:256-273 — A contents: write token sits in the environment of a job that restores the guest cache and compiles and runs the userland's third-party build scripts and proc-macros.
    • The guest cache is written by tcg, which is itself a job running third-party code.
    • The repository default is read (gh api repos/ToyOSOrg/ToyOS/actions/permissions/workflow → "read"), so this is the first job that hands a write token to that code.
    • release::install checks no digest of the asset it downloads (src/release.rs:109-160). Any such crate can therefore replace the toolchain asset every CI job installs, or rewrite a published image.
    • Fix: the boot job keeps read permissions and uploads its four assets as an artifact. A publish job (needs: release, contents: write, no cache restore, building nothing but toyos-build) re-checks SHA256SUMS and runs the gh half.

NOTE

  • src/licence.rs:1478 — Expr::Or takes the first branch the fork holds.
    • So MIT OR Apache-2.0 packages linked here (dasp_sample 0.11.0, defmt 0.3.100 and 1.0.1, delegate 0.13.5) are given MIT's template line Copyright (c) <year> <copyright holders>. Prefer a branch whose standard text is complete as it stands. Apache-2.0 is: §4(a) asks for the licence text, and §4(d) applies only where the package has a NOTICE.
    • The standard text is enough for MIT-only realfft 3.5.0 (through soundd's rubato). Its .crate holds no copyright line at all (grep -rni copyright over the registry source gives nothing), so the text plus the authors line is everything the package gives to reproduce.
  • src/licence.rs:1533 — Skipping fortanix-sgx-abi is correct and stays. std's only edge into it is [target.x86_64-fortanix-unknown-sgx.dependencies] (rust/library/std/Cargo.toml:68), a target no ToyOS image is built for. The header at :1523, "Every package in the dependency graphs", is therefore not literally true.
  • src/imagerelease.rs:473-476 — The re-read after publishing repeats the exit status of gh release edit. Delete it.
  • src/imagerelease.rs:484-486 — The GITHUB_ACTIONS guard is redundant: an unset GITHUB_SHA and the HEAD check (:487-491) already refuse off a runner. Delete it.
  • src/licence.rs:1268 — In the reuse check, git rev-parse HEAD fails on a fetch interrupted before checkout, and that failure is propagated. An unborn HEAD then refuses every later call instead of fetching again. The fetch also runs under buildlock::shared and the build slot (src/build.rs:1925), so it holds the build lock across the network.
  • src/imagerelease.rs:345 — "Every other disk is read for its partition table and never written" is false with two sticks of one release plugged in (issues/boot-media/the-loader-writes-the-first-disk-carrying-its-log-guid-not-the-one-it-booted-from.md). The public notes make the claim with no exception.
  • tests/toyos.rs:988 — On the Linux nightly, release_command_boots builds the release a second time and boots the line the release job boots in the same run. Only its macOS reading is unique. The round-1 NOTE also stands: Host::this() is red, with no redlist entry, on an Intel Mac, on aarch64 Linux and on Linux without KVM.
  • src/imagerelease.rs:340-343 — The dd and unmount lines have still not been run against a device.

REMOVE

  • PR body "Run by the orchestrator" — predictions ("Expected GREEN", "Expected RED") written into main's record.
  • PR body "Unsure" — process state.
  • PR body "The walk reaches 59 such packages. Most are only other platforms' … Some are linked here: …" — a count that rots.
  • PR body "Its cargo test --lib step: 390 passed, 1 ignored." — a count that rots.

SEND BACK

Japabu and others added 3 commits September 28, 2026 00:43
…ed one way

The release job builds and boots with a token that only reads, and hands the
four assets on as an artifact and their digest as a job output. A new
`release-publish` job holds `contents: write`, restores no cache, builds only
toyos-build with no token in reach, rechecks the digest (`imagerelease::digest`)
and publishes on main. `ci::a_token_that_writes_meets_no_cache_and_only_the_step_that_needs_it`
holds every workflow to that. The post-publish re-read and the GITHUB_ACTIONS
guard are gone; `gh` is no longer installed in the container.

std's `library/` comes from a linked worktree's fork checkout, from
`rust/library` where the checkout holds it, and from `fetched_library`
everywhere else; the submodule-init path is deleted. The fetch sits at
`.licence-fork/`, a direct child of the checkout, because std's manifest names
`toyos` and `toyos-abi` three levels above its crates: under `target/` it never
resolved. It is fetched into `target/licence/fork.partial` under its own lock
and renamed into place, reused only at the pinned commit and sparse set, and
the release build writes the notice before any build lock. The release notice
test fetches the way the release job does.

An OR takes Apache-2.0's standard text where it is a branch. The notice's
opening sentence says what the walk leaves out. The notes name the loader
defect as the one disk a boot may write though it was not given it.

`foreign_disk_untouched` boots twice on `Profile::UsbDisk`, one foreign USB
disk beside the NVMe disk each time: the USB driver serves two disks and the
boot stick is one, so `UsbDiskCrowd`'s third was never read.

`release_command_boots` is in a new Apple Silicon tier and boots that line;
`Host::this` is gone, and the release job boots the Linux line.

Filed: the toolchain install unpacks an asset no digest vouches for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A copy of the Rust project's COPYRIGHT carried by another package satisfied the
old check, so a fetch that dropped the fork's own left it green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… boots it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu Japabu changed the title An image release: main's disk image, booted before it is published An image release: main's disk image, booted before a separate job publishes it Sep 27, 2026
src/build.rs: main deleted the build slot `shipped_parts` took; the release
notice, written before any lock, stays where the slot was, and the slot goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #556 at 20179a7 (round 3)

Readiness is not met, so under reviewer.md this is NOT READY FOR REVIEW. release_command_boots lands disabled, the release and release-publish jobs have never run, and there is no T14 reading. PR CI host is SUCCESS at 20179a7 (run 36357539666). The full review follows, as the brief asks.

Net: +1896/−109.

  • Production is about +1197: imagerelease.rs 566, licence.rs 395, build.rs 83, nightly.yml 55, ci.rs 40, sourcegate.rs 22, and 36 elsewhere.
  • Tests are about +597, and issues +102.
  • I accept the growth except for the cuts named below. The one large cut that keeps every legal and safety property is under the first NOTE: print only the Linux line.
  • A committed fixture of std's licence graph would not be smaller. It is a second copy of bytes every dev host already holds (rust/library or the fork checkout), it goes stale at every pin, and it needs a refresh command of its own. It is rejected.

Round-2 BLOCKERs

  • B1, two fetchers of library/: CLOSED.
    • The submodule branch is gone (src/licence.rs:1239).
    • PR CI host at 20179a7 ran licence::tests::the_release_notice_carries_every_package_and_file_it_ships ... ok on a runner with no rust/ source, so through fetched_library.
    • m2 (/COPYRIGHT dropped from the sparse set) gave EXIT=101 on that test.
  • B2, the write token in the boot job: CLOSED in code.
    • release is contents: read. release-publish restores no cache and keeps no credential.
    • m5 (contents: write back on release) gave EXIT=101 on a_token_that_writes_meets_no_cache_and_only_the_step_that_needs_it.
    • GitHub's GITHUB_TOKEN Permissions block is still owed with R1-1(b).
    • The gate has holes; see the new BLOCKER.

Round-1 BLOCKERs

BLOCKER

  • src/licence.rs:2432 — the notice test calls fetched_library(root), so every fresh worktree's cargo test --lib fetches std from GitHub once per pin.
    • The checkout already holds those bytes, in rust/library or the fork checkout.
    • This is a new fetch in a host unit test. The tree's own line at Cargo.toml:202 is that no test fetches anything. Offline runs and GitHub outages turn the host suite red.
    • Fix: call std_library(root). On a CI runner that is still fetched_library, so B1 stays closed.
    • The sparse set is already held offline: a_fetch_cut_short_is_fetched_again_and_a_new_pin_replaces_the_fork asserts COPYRIGHT is checked out from a file:// remote.
    • Measurement: PR CI host green, and m2 run against that offline test with EXIT=101.
  • src/ci.rs:1108 — the writing-token gate misses two spellings, on a security boundary. Each patch below must turn a_token_that_writes_meets_no_cache_and_only_the_step_that_needs_it red.
    • (a) Patch .github/workflows/nightly.yml:263-264 from permissions:\n contents: read to permissions: write-all.
      • "\n permissions:\n" does not match, so the gate reads the workflow head instead, which does not write.
      • release then restores the guest cache with a write token, and the gate stays green.
    • (b) Delete nightly.yml:294-295 (with: / persist-credentials: false).
      • The token then sits in .git/config while cargo build -p toyos-build runs crates.io build scripts, and the gate stays green.
      • release-publish's own comment and the PR body both rest on this line.
      • build and nightly-red also persist a writing token. Each takes persist-credentials: false, or is named in the gate with the issue that owns it. build is already in issues/build/the-toolchain-install-unpacks-an-asset-no-digest-vouches-for.md.

NOTE

REMOVE

  • tests/common/storage.rs:26 — "(Profile::UsbDiskCrowd's third is never served)": another profile's behaviour, which rots when the driver serves three.
  • src/tiers.rs:17-18 — "so no other host runs them": misleading. CI's macos-latest is an Apple Silicon Mac and does not run them.
  • PR body — "The previous form's third disk rode Profile::UsbDiskCrowd … never read it.": branch history main never had.
  • PR body — "gh is no longer installed in the container (it left CI_PACKAGES)", "The post-publish re-read and the GITHUB_ACTIONS guard are deleted", "Host::this is deleted": each names a state main never had.
  • PR body — "The round-2 assertion stayed green under this patch (EXIT=0) … It now asks that std's own block cite the text.": branch history.
  • PR body — "Agents here run no QEMU." is process state. "At 002bc8d the green arm and the NVMe arm were EXIT=1 … the runs at this head are owed." is stale.

SEND BACK

Japabu and others added 2 commits September 28, 2026 08:17
licence.rs: main's ls_files went to sysroot::tracked_files; the branch's
std_library and fetched_library replace main's submodule fetch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…lity-macos; the writing-token gate reads write-all and persisted credentials

- release-publish needs build, guest and tcg besides release.
- Tier::AppleSilicon is gone. release_command_boots is in no tier: the
  suite's --release-command runs it and nothing else, and the nightly's
  portability-macos runs that on its Apple Silicon runner after the build,
  with the QEMU Homebrew already installed there.
- The writing-token gate reads a job's permissions inline or nested, from
  any line of the job, and requires persist-credentials: false on every
  checkout of a job whose token writes. build, nightly-red and publish.yml's
  publish take it.
- The notice test takes std from std_library, so a host test fetches
  nothing where the checkout holds the fork.
- The fork's sparse set is derived from LICENCE_FILES, in any case.
- The licence lock (was the fork-fetch lock) also covers the walk's
  scratch Cargo.lock and the release notice's write.
- The re-read of the draft after `gh release create` is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant