Repository navigation
The package repository's keys: toyos-root signs root 1 offline, and the publisher signs under it alone (stage 1 of apps leaving the tree) - #892
Conversation
|
Owner's rulings on the design's three open points, recorded by the orchestrator:
Also from the orchestrator: under the owner's testing ruling (#890), |
|
Design review, round 1, at BLOCKER
NOTE
DESIGN NEEDS CHANGES:
|
|
Owner's rulings on round 1's open points, recorded by the orchestrator:
The orchestrator's technical rulings on the review's other blockers (#892 (comment)), each as the review names it:
|
|
Round 2 of the design: where each round 1 finding closes. The review is 6130406171, and the rulings are 6129963881 and 6130600934. The head is still BLOCKERs
NOTEs
Also changedAt the owner's direction, the body decides by published specifications and vendor documentation and cites only those: the TUF specification, PEP 458, GitHub's and Cargo's documentation, GPL-2.0, RFC 8032 and |
|
Design review, round 2, at Round 1's BLOCKERs
BLOCKER
NOTE
The two flagged departures, weighed for the owner's ruling
|
|
Rulings on round 2 (#892 (comment)).
|
|
Round 2's findings, answered at BLOCKER
NOTE
Departures and
|
|
Design review, round 3, at Round 2's BLOCKERs
Round 2's NOTEs, the two accepted departures, and the BLOCKER
NOTE
SEND BACK |
|
Rulings on round 3 (#892 (comment)). (1) Stage 2 gives |
|
Round 3's findings, answered at BLOCKER
NOTE
Nothing is built, no gate was run, and no evidence file was added this round. |
|
Design review, round 4, at Round 3's findings
BLOCKER
NOTENone. The design is ready to build once that BLOCKER is answered. No other finding is open. SEND BACK |
|
Ruling on round 4 (#892 (comment)): (a). A key release's asset never changes once it is up. Stage 2 makes Design accepted at |
…he publisher signs under it with the repository's signing key alone Stage 1 of the design accepted on #892: apps leave the tree as packages that `pkg` installs from a repository CI signs. This stage builds the keys. - `toyos-root` (`build/root`), a new crate depending only on toyos-update with `sign` and on getrandom, so it compiles for any triple without a C toolchain. `new` mints the root key as a 64-hex-digit seed file; `sign` writes root 1, which gives the root key the root role alone and the signing key the timestamp and targets roles, each at threshold 1, expiring a year from the machine's clock, read back as self-signed before it is written. Its module header carries the ceremony's steps. Signing root N+1 (`--previous`), renewal and rotation are migration stage 9's. - `cargo run -- --repo-signing-key-new` mints the repository's signing key at `~/.config/toyos/repo-signing-key` (0600), distinct from the image key, prints its fingerprint on stderr and its public line on stdout, so the line can be redirected onto the stick the ceremony carries. - `build/publish.rs` loses root minting, root renewal and `one_key`. It receives roots under `roots/` beside its manifest, copies those the repository lacks and never rewrites one it holds, holds root 1 to `repo::self_signed`, and refuses by name a newest root that does not grant its key the targets and timestamp roles. It signs with the repository's signing key only. Every name is flat (`<name>-<sequence>-<target>.tar.gz`, no `archives/`), which a GitHub release's asset namespace keeps. After a publish leaving targets M it prunes every targets below M-1 and every archive neither M nor M-1 names, and never a root. - `toyos_update::repo` gains `self_signed`, `fingerprint`, `public_line` and its parser, and `render::first_root`, the one definition of root 1 that toyos-root signs and the publisher's tests use. The fingerprint is held to OpenSSH's (`ssh-keygen -l` over the fixture's key). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
e2ebc2c to
423f6fd
Compare
|
Build review, stage 1 (keys), at Lines ( Evidence read: BLOCKER
NOTE
SEND BACK |
|
Rulings on the stage 1 review's NOTEs (#892 (comment)).
The BLOCKERs are taken as written, |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
…es have one home each, and the repository key is its own type The build review of #892's stage 1 and the owner's rulings on its NOTEs. - `toyos-root new` mints the root key 0600, as the accepted ceremony says; root 1 is written 0644. The crate is built for a Unix triple, and the ceremony names the one measured path: `--target aarch64-apple-darwin` on this Mac, for an offline Mac of the same macOS and architecture. Any other offline triple is stated as unmeasured. - A seed's key-file text has one writer and one reader, `toyos_update::sig::{seed_text, seed_of_text}` behind `sign`: 64 lowercase hex digits and a newline, and nothing else reads. The checkout's throwaway key, the repository's signing key and the root key all go through it, and the two `seed_of_hex` copies and `hex_of` go. - The `SHA256:` fingerprint has one home, `toyos_update::repo::fingerprint`: toyos-ssh calls it and loses its own, with its fixture, and the assert holding the two equal goes. - `signing::RepoKey` is the repository's signing key: it signs documents and has no way to sign an image header or name a floor. `Key` loses `sign_document`. - The publisher's grant check goes: the read-back refuses a root that does not grant the key before anything is written. - A file in `roots/` the walk from root 1 does not reach, a root past a gap among them, is refused by name. - One claim per test: the tests the review named are split, and toyos-root's repetition of `repo`'s grant check is cut; toyos-root's test holds the written root to `render::first_root` byte for byte instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
|
Build review, stage 1 (keys), round 2, at Lines ( Gates at this head: Round 1
BLOCKER
NOTE
SEND BACK |
…eeps no Unix mode, and the ceremony names APFS The 0600 the header promised the root key rested on a filesystem the ceremony never named. On exFAT and FAT32, the formats USB sticks come in, open(O_CREAT, 0600) succeeds and every file reads back the mount's one mode: measured 0700 on both, attached as disk images and erased by diskutil. `new` printed its fingerprint as though it had minted 0600. - `new` reads the mode back from the file it created, before the seed is written, and where it is not 0600 removes the empty file and refuses by name. `sign`'s root 1 to stick C takes no read-back: it is public. - The header names A and B as APFS, erased with `diskutil eraseDisk APFS <name> GPT <disk>`, and says why. - Step 2.1 names `sudo date -u <MMDDhhmmCCYY>` for a wrong clock; steps 2.2 and 2.4 run `<C>/toyos-root`, which is on no PATH offline; step 2.3 reads the B copy's mode back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
|
Build review, stage 1 (keys), round 3, at Lines ( Gates at this head: Round 2
BLOCKER
NOTE
SEND BACK |
…st holds, and the ceremony enables ownership on A and B `new`'s refusal of a key file that reads back other than 0600 moves into `read_back_0600`, which `new` calls with the open file; a host test creates a 0644 file in a tempdir and asserts the refusal and the removal, so the refusal no longer rests on a disk-image script alone. The ceremony names the sticks ROOTA and ROOTB, takes `<disk>` only from an `(external, physical)` entry, enables ownership on both with `sudo diskutil enableOwnership`, since an APFS stick mounts `noowners`, and reads owner and mode back on both copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
|
Build review, stage 1 (keys), round 4, at Lines ( Gates at this head: Round 3
On the brief's question: is the red ownership step acceptable evidence for landing stage 1?Yes, once the BLOCKER below is fixed, and only for the four checks it names.
BLOCKER
NOTE
SEND BACK |
…ts scope, and step 2.3 reads the mode alone diskutil(8) enableOwnership writes the setting to the running OS's volume database, not to the target disk, so 0600 on A and B binds only on the offline machine; elsewhere the sticks' guard is custody. The %Su check in step 2.3 could not fail (noowners reports the current user, and with ownership enabled the on-disk owner is that same user), so it is cut back to %Sp; the mount check is the discriminating one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
|
Build review, stage 1 (keys), round 5, at Lines ( Gates at this head: Round 4
BLOCKERNone. NOTENone. LAND |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
Stage 1 of the accepted design is built: the package repository's keys. The design below was accepted at
e2ebc2c68with round 4's change (a) (6136355825), which this body now carries in its stage 2 text. The stage 1 review (6136858838) and the owner's rulings on its NOTEs (6136865450) are answered atad6f61c49, the second review (6137091933) at72caf9c0d, the third (6137214587) atc1e06aa01, and the fourth (6137327123) at41bbac4f5. The branch is six commits on main'sb6a556546, which is stillorigin/main: the stage 1 build423f6fda7, the merge of mainbefc4449f, and the review roundsad6f61c49,72caf9c0d,c1e06aa01and41bbac4f5. Nothing is signed for real. The root-key ceremony is the owner's, and its steps are below.What stage 1 changed, per decision
toyos-root(build/root, a new workspace member). It depends only ontoyos-updatewithsignand on getrandom. It is built for a Unix triple, because it mints the root key0600. It has two commands and no more:toyos-root new <key file>mints the root key0600into a file that must not exist yet. The file holds the seed astoyos_update::sig::seed_textwrites it. The command prints the key's fingerprint and public line.read_back_0600, whichnewcalls with the open file. Where the mode is not0600, it removes the empty file and refuses, naming the path and the mode it read. The host testa_key_file_that_reads_back_other_than_0600_is_refused_and_removedholds the refusal and the removal on a0644file in a tempdir, on every host. exFAT and FAT32, the formats USB sticks come in, keep no Unix mode:open(O_CREAT, 0600)succeeds on them and every file reads back the mount's one mode, measured as0700on both. Before this round,newprinted a fingerprint there as though it had minted0600.signtakes no read-back. Root 1 is public, and stick C may be any format.toyos-root sign --key <key file> --signing <public line file> --out <root.1.txt>writes root 1, mode0644.repo::self_signed.ROOTAandROOTB, so<A>is/Volumes/ROOTAand<B>is/Volumes/ROOTB, and<disk>is taken only from an entrydiskutil listmarks(external, physical). After the erase,sudo diskutil enableOwnershipis run on each (the orchestrator's ruling on round 3's first NOTE), because an APFS stick mountsnoowners, and a volume that ignores ownership treats every user as the owner0600names;mountmust then list neither asnoowners. The step cites Apple'sdiskutil(8),enableOwnership, which defines it and gives its scope: the setting is written to "the on-root-disk Volume Database at /var/db/volinfo.database", persists "as seen by the current OS", and "is not kept on the target disk", and plug-in USB disks do not consider ownership by default. So0600on A and B binds only on the offline machine, and the header says the sticks' guard anywhere else is custody (step 2.5). Step 2.3 reads the mode back on both copies withstat -f %Sp; it reads no owner, because the owner read-back cannot fail here: on anoownersvolumestatreports the current user, and with ownership enabled the on-disk owner is that same user. Themountline is the one check that discriminates. Step 1.2 names the one measured path (ruling 4):cargo build --release -p toyos-root --target aarch64-apple-darwinon this Mac, for an offline Mac of the same macOS and architecture. Every other offline triple is stated as unmeasured.--previous, renewal and rotation stay stage 9's.cargo run -- --repo-signing-key-newmints the repository's signing key at~/.config/toyos/repo-signing-key(0600), and refuses to replace one.signing::RepoKey(ruling 3). That type signs documents. It has no method that signs an image header and no floor scope.Key, the image key, losessign_documentand is otherwise unchanged.build/publish.rs) losesone_key, root minting and root renewal, along withROOT_LIFE,RENEW,THROWAWAY_DIRandrepository(). What it does now:roots/root.<N>.txtbeside its manifest, walking from root 1. Any other file inroots/is refused by name (ruling 2). That includes a root past a gap, such asroot.3.txtwith noroot.2.txt.repo::self_signed, because the client trusts the root it pins unchecked.repo::refresh, before anything is written.<name>-<sequence>-<target>.tar.gzat the repository's top level, andarchives/is gone.prunedis a pure function over a file list, so stage 2 can apply it to the release's asset listing.--publish <packages.toml>signs with the repository's signing key, read0600from its path, into~/.config/toyos/pkg-repository.--packagesbrings back the per-checkout repository.--publishitself goes in stage 2, as the design says.toyos_update::sig::{seed_text, seed_of_text}, behindsign. The text is 64 lowercase hex digits and one newline. The reader accepts only text thatseed_textwould write back byte for byte.seed_of_hexcopies andhex_ofare deleted. The old readers differed: one stripped a single\nand the other calledtrim(). Both accepted upper case and a+sign throughfrom_str_radix.SHA256:fingerprint:toyos_update::repo::fingerprint.toyos-sshnow depends ontoyos-updateand calls it. Its ownhostkey::fingerprintand thehost_ed25519.fingerprintfixture are deleted.toyos-sshdepends ontoyos-update, and not both on a new crate:toyos-updateis pure andno_std, and already builds on every host. A crate for one function would be a third place to look.toyos_update::repogains five items:self_signed;fingerprint;public_lineand its parserparse_public_line(ssh-ed25519 <base64>, the words a root'skeyline already carried, whichRoot::ofnow parses through the same helper);render::first_root, the one definition of root 1, used bytoyos-root signand by the publisher's tests.Choices this build made that the design left open:
--signingtakes a file holding the public line rather than the line itself. On the offline machine the line arrives as a file on the carrying stick, so nothing has to be typed or pasted there.roots/beside its manifest, which is wherepackageskeeps them.REPO_SIGNING_KEY. Stage 2'spackages-publishreads it under that name.One claim per test. Each test the review named is split into one test per claim:
toyos-root's two tests became seven;signing's repository-key test became four;publish's two refusal tests became eight;repo's two new tests became five;repo_oracle's new test became three, and its round-trip leg was cut becauserepo's unit test holds it.The duplicate is cut.
toyos-root's test no longer drives a repository throughrefresh, becauserepo's tests holdfirst_root's grants. Instead it holds the root thatsignwrites torender::first_rootof the minted key, byte for byte, which m12 shows can fail.Lines (
git diff --shortstat origin/main...HEAD): 18 files, +996 −230, net +766. This round is +8 −6, all inbuild/root/src/main.rs's header: thediskutil(8)citation and its scope, and step 2.3 cut back to%Sp. Production code, manifests and lock are net +362. Tests are net +377, counted as the lines from each file's#[cfg(test)]on, plusrepo_oracle.rsand the deleted fixture.Gates
At
41bbac4f5, this round. Evidence is atToyOSOrg/evidencepr-89257cb84cd0, with every exit inexits.txt. The round changes only the header's prose, so no test or mutation is rerun.cargo run -- --ci hostci-host.txtdiskutil(8)read on this host, the ownership step's oracleenableOwnershipat lines 220-257diskutil-8.txtAt
c1e06aa01, the round before. Evidence is atToyOSOrg/evidencepr-892e92ddd9ee, with every exit inexits.txt.cargo run -- --ci hosttests::a_key_file_that_reads_back_other_than_0600_is_refused_and_removed)ci-host.txtcargo test -p toyos-root0,git statusemptymutate.txt, m14, m15, runnermedium-oracle.txt, oracleThe ceremony oracle (
medium-oracle.sh) erases blank 64 MiB disk images with the header's own commands. C is ExFATSTICKC, and the binary is copied onto it: everytoyos-rootbelow runs from C, as steps 2.2 and 2.4 do.newfrom C onto APFSROOTAexits 0, andstatreads-rw-------. Step 2.3's copy onto APFSROOTBpassescmpand reads the same.signfrom C exits 0, writes root 1 onto C, and prints the root key fingerprintnewprinted and the signing key fingerprintssh-keygen -lprints.newfrom C onto ExFAT and onto FAT32 exits 1 withreads back 0700, not 0600, and leaves no file.date -u -j 101312002026printsTue Oct 13 12:00:00 UTC 2026.sudo: a password is required:enableOwnershipneeds root, which an agent does not hold, so an agent can never make them green. The step is macOS's behaviour, not ToyOS's; its oracle is Apple'sdiskutil(8)(row above), and its measurement is the owner'smountcheck in the ceremony, which comes beforenewand stops the ceremony before any key exists if the step did not take.The round-2 disk-image oracle stays as the evidence of what ExFAT and FAT32 read back; the suite never runs it. The gate on the refusal is now the host test, which m14 and m15 each turn red.
At
72caf9c0d, round 2. Evidence is atToyOSOrg/evidencepr-892659e45d56, with every exit inexits.txt.cargo run -- --ci hostci-host.txtgit statusemptym14-no-readback.txt, patch, runner, oracle--build-onlyandconsole_image_bootswere not rerun this round: the round touchesbuild/rootalone, which no image and no guest links.The medium oracle (
medium-oracle.sh). Three blank 64 MiB disk images were each attached unmounted and erased with the header's own command,diskutil eraseDisk <fs> <name> GPT <disk>. Then the step 1.2 build oftoyos-root newwas run onto each one. The script exits with the number of expectations that failed:newexits 0, andstatreads-rw-------. Step 2.3's copy,cpand thencmp, reads-rw-------too.newexits 1 withreads back 0700, not 0600: its filesystem keeps no Unix mode, so the root key is not written there, and no file is left.date -u -j 101312002026printsTue Oct 13 12:00:00 UTC 2026.sudo date -uwas not run, because it would set this shared machine's clock.m14 drops the read-back. The oracle goes red with exit 4. On ExFAT and on FAT32,
newexits 0, prints a fingerprint, and leaves a-rwx------key.At
ad6f61c49, the round before. Evidence is atToyOSOrg/evidencepr-8924cfd338a3, with every exit inexits.txt.cargo run -- --ci hostci-host.txtcargo run -- --build-onlybuild-only.txtcargo test --test toyos-build -- console_image_bootsguest-console-image-boots.txtmutations.txt, patches and runner--publishceremony-openssh-oracle.txt, scriptGuest tests reached.
toyos_update::repo.rg 'repo::'outsidelib/updateandbuild/finds none.updateand the supervisor linktoyos-updatewithoutsign, so none of them compilesseed_textorseed_of_text.update_writes_the_idle_slot_through_the_block_service, but Integration tests outside the kernel are cut, and unit and module tests hold what they checked #893 cut that test and moved what it held toupdate-copies's host tests. Those tests ran green in--ci host, andconsole_image_bootswas run as the one guest boot.Negative control. Each check was mutated by its own patch:
git apply --check;toyos-build(publish::,signing::),toyos-update,toyos-rootandtoyos-sshwere run;Every arm went red (exit 101) in the test that holds it. The unmutated tree was green, and
git statusprinted nothing afterwards:roots()ignored what the walk does not reach.a_root_past_a_gap_in_roots_is_refused_and_nothing_is_writtenwent red.a_root_one_that_is_not_self_signed_…went red.pruning_keeps_…went red.archives/.self_signedvouched for nothing.first_rootgranted the signing key the root role.the_fixtures_key_has_the_fingerprint_ssh_keygen_printedwent red.0644.the_root_key_is_minted_0600went red.seed_of_texttook any near form.a_text_not_in_the_seed_texts_form_is_refusedwent red.sign_writes_the_ceremonys_root_one_expiring_a_year_onwent red.toyos-rootreplaced an existing file.new_over_a_key_is_refused_and_leaves_itandsign_over_an_existing_root_is_refusedwent red.The grant-check mutation of round 1 is gone with the check. The publish read-back now refuses a root that does not grant the key, and
a_root_that_does_not_grant_the_key_is_refused_and_nothing_is_writtenasserts that refusal's message. m7 does not exercise that refusal. m7 takes the root role off the root key, and the test goes red onroot 1 is not self-signed(mutations.txt:947-948). No mutation in this record removes the grant refusal on its own. The test fails without it, because the publish would then returnOk.m14, adapted to
read_back_0600, and m15 are round 3's mutations, and they are listed under Gates.Independent oracle: OpenSSH (
ssh-keygen), run on this host. The ceremony was run end to end with throwaway keys, under a scratch$HOMEand on scratch "sticks":cargo build --release -p toyos-root --target aarch64-apple-darwinexited 0, andfilereports a Mach-O 64-bit arm64 executable.toyos-root newwrote it-rw-------under umask0022, and a secondnewover it exited 1.ssh-keygen -lover each public line printed the fingerprint thattoyos-rootand--repo-signing-key-newhad printed.ssh-keygen -Y verify -n toyos-rootaccepted it under the root key's line (exit 0). It refused the same signature under the signing key's line, and a body with one role's threshold bent (255 each).--publishunder that root 1 exited 0 twice. With aroots/root.3.txtadded past the gap, it exited 1, naming the file.repo_oracle.rs) holdrepo::fingerprintto the stringssh-keygen -lprinted over the OpenSSH-signed fixture's key, and hold that fixture's root toself_signed.The ceremony, for the owner
What you bring:
diskutil listand take each stick's whole disk only from an entry it marks(external, physical): any other entry is the machine's own disk, anderaseDiskwould erase it. Erase A withdiskutil eraseDisk APFS ROOTA GPT <disk>and B withdiskutil eraseDisk APFS ROOTB GPT <disk>. exFAT and FAT32, the formats sticks come in, keep no Unix mode, andtoyos-root newrefuses them. Then runsudo diskutil enableOwnership /Volumes/ROOTAandsudo diskutil enableOwnership /Volumes/ROOTB, and check thatmountlists neither asnoowners. The setting lives in this offline Mac's volume database, not on the sticks (diskutil(8)), so on any other Mac A and B are guarded by your custody alone. Below,<A>is/Volumes/ROOTAand<B>is/Volumes/ROOTB;1. Online, before, from a ToyOS checkout at main once this lands:
cargo run -q -- --repo-signing-key-new > <C>/signing.pub. Write theSHA256:…fingerprint it prints on paper.cargo build --release -p toyos-root --target aarch64-apple-darwin, thencp target/aarch64-apple-darwin/release/toyos-root <C>/.2. Offline:
date -u. It must show today's date in UTC, because root 1 expires a year from that clock. If it does not, set the clock by hand withsudo date -u <MMDDhhmmCCYY>, for examplesudo date -u 101312002026for 2026-10-13 12:00 UTC, and rundate -uagain.<C>/toyos-root new <A>/root-key. It mints the root key0600. Write its fingerprint on the paper.cp <A>/root-key <B>/root-key && cmp <A>/root-key <B>/root-key. It prints nothing when the copy is whole. Thenstat -f %Sp <A>/root-key <B>/root-keymust print-rw-------on both lines.<C>/toyos-root sign --key <A>/root-key --signing <C>/signing.pub --out <C>/root.1.txt.root keyandsigning keylines must match the paper.expiresmust be a year from today.3. What you bring back, and where it goes:
<C>/root.1.txt, and nothing else.roots/root.1.txtinToyOSOrg/packagesandimages/pkg/root.1.txtin ToyOS.repo::self_signedand through the publisher, whose read-back refuses a root that does not grant the signing key.~/.config/toyos/repo-signing-key(one line of 64 hex digits) into theREPO_SIGNING_KEYsecret ofToyOSOrg/packages'spublishenvironment, then delete the file.0600on this machine.Unsure
toyos-rootbuilt here is only measured on this Mac's macOS. It is not measured on an older macOS release. The offline Mac is assumed to run this Mac's macOS, as ruling 4 states.diskutilcommand the ceremony uses, and the mount's file system is whatnewreads back through. A real stick was not measured.images/pkg/root.1.txtlands later. The ToyOS pull request that commits that file lands with the test. This stage writes no test that reads a file which does not exist yet.issues/a-package-is-a-directory-under-apps-and-the-installer-is-a-program.mdto this design. Stage 1's work list does not name it, so this stage does not touch it.The design, as accepted
This round follows the rulings recorded on this pull request:
pkggets network; repository name only;filesstays;consent_promptandapp_viewgo with the test sweep.ToyOSOrg/packages; the orchestrator's nine technical rulings on round 1's review (6130406171).release_asstays as it is;pkg install <file>goes and the staging file ispkg's;toyos-rootis accepted, and stage 1 builds onlynewand the signing of root 1; the review's NOTEs are taken as written.releasegets one concurrency group withcancel-in-progress: false; apackagespush or pull request builds against the ToyOS commit therepositoryrelease's notes name, one path with no fallback; "main's tip is always checked" holds only when the tip's own run reachesrelease.The design in one paragraph
Each app's source lives in its own repository.
ToyOSOrg/packagesholds one directory per package: the source as a gitlink pinned by commit, a recipe, and theCargo.lockit builds with. After a merge to ToyOSmainwhose run still finds it main's tip, ToyOS puts up that commit's toolchain release and dispatches the packages CI with the commit. The CI builds every recipe for both ToyOS targets with that commit's toolchain, SDK and forks, and holds each recipe to the licence gate and the host gate. Where the commit is still main's tip when the builds end, it signs and publishes the repository as the assets of one GitHub release. One root key, which the owner keeps offline, certifies one signing key, which CI holds and which signs the targets and the timestamp. Every image pins the root committed in the ToyOS tree.pkg install gbaerefuses an item built for another SDK before it fetches anything. It fetches through a second program that alone holdsnetstack, verifies the whole archive before it inflates any of it, and commits the package as a versioned directory under/apps/gbae/behind one atomic pointer. The supervisor launches a package only when a signed targets names its binary's digest.ToyOSOrg/packages
actions/checkoutwithsubmodules: truefetches every source at its pinned commit, so ToyOS's build system fetches no source itself. A source bump is a pull request topackages. ToyOS's own apps move toToyOSOrg/<name>; gbae stays atJapabu/gbae. The owner creates the repositories, and each one's first commit names the ToyOS commit its history came from.Cargo.tomlversion.--lockedholds the build to (measurement 2 below).cargo run -- --package-lock <packages>/<name>, run from a ToyOS checkout, writes it:Cargo.lock, so every crate the patch table does not name keeps its author's pin (4c: only the patched crate'ssourceline moved).forks.tomldeclares each fork that no ToyOS crate uses. Once the apps leave, these are winit, softbuffer and raw-window-handle (used today by calc, doom and snake) and sysinfo (used by toyfetch).[patch], because cargo warns on every run about a patch the crate graph does not use (2c, 2d).[patch]and inforks.tomlis refused by name.roots/holds the public roots. The publish job uploads any root the release lacks before it uploads anything else.mainmoves only by pull request, as ToyOS's does.packagesis MIT OR Apache-2.0, as ToyOS is.The builder: three measurements, and what they decide
The two logs are
cargo-patch-locked-profile.txtandcargo-patch-same-version.txt. Their scripts sit beside them. The setup:CARGO_HOME;file://URLs. Cargo resolves these as git sources, the same way it resolves a GitHub fork branch.A git-branch patch given with
--configfloats.patch.crates-io.itoa = { git, branch = "toyos" }, the build compiled the branch's commit A (1a).rev = A, the build compiled A while the branch was at B (1d).So the builder never passes a branch. Each fork's commit is the
revthat ToyOS'sCargo.lockrecords for it (for example winit's#f58e1f3b…,Cargo.lock:5474), or the one inforks.toml.--lockedagainst the source's own lock fails under a patch, and a patch can go unused while the build exits 0.cargo build --lockedexits 101 (4b).--locked, cargo moves only the patched crate'ssourceline (4c).--lockedexits 0 and the binary runs the fork (4d).cargo install --lockedexits 0 after compiling crates.io's itoa 1.0.11 (2c).cargo builddoes the same (2d) and records[[patch.unused]], and--lockedthen accepts that lock (2e).cargo installwithout--lockedignores the source's lock: it compiled itoa 1.0.18 and ryu 1.0.23 where the lock pins 1.0.11 and 1.0.10 (2b).So the builder runs
cargo build --lockedin the source's checkout, with the recipe's lock, and nevercargo install.cargo installcan take no lock but the source's own, and does not refuse an unused patch. The builder also refuses a lock in which a crate the patch table names resolves from crates.io.cargo installbuilds the source's ownreleaseprofile.release(3a).[profile.release] debug-assertions = truetook effect (3b).--profile toyos, with every key of ToyOS's[profile.toyos](Cargo.toml:320) given by--config, builttoyoswith debug assertions and overflow checks on (3c; 3e undercargo build).--configoverrode the source's own[profile.toyos](3d).So the builder passes every key of ToyOS's profile. A source's own profile keys apply only where ToyOS's profile states none.
The builder's command, for one recipe and one target:
cargo build --locked --profile toyos --target <triple>, run in the checkout of<name>/sourcewith<name>/Cargo.lockin place of the source's own lock. It runs throughcargo_build(build/build.rs:399) underGuestEnvwhole (:370), as an in-tree guest build does today, so the environment is declared once:RUSTUP_TOOLCHAINnames the commit's sysroot, which carries the linker;ccisCSysroot::cc_env(build/clang.rs:150):CC_<triple>,AR_<triple>andCFLAGS_<triple>name the toolchain's clang andllvm-aragainst libc's C sysroot, so a recipe's C, doom's included, is compiled for ToyOS and never by the host'scc;RUSTFLAGSandRUSTCare removed, and the image key and floor scope are set, as for every guest crate.Its arguments add
--configcarrying[profile.toyos], every fork byrev, and every SDK crate bypathinto the ToyOS checkout.The
pathpatch to the checkout is round 1's measurement (cargo-install-config-patch.txt).The packages CI, against ToyOS main
Why CI builds the packages, not each author. The ABI is completely unstable:
toolchain-releases.txt).gbae-v0.2.0-against-main.txt).A package built at every merge is built for the SDK that main has.
The trigger.
publish.ymlalready runs on every push tomainand builds that commit's toolchain.releasejob moves there fromnightly.yml.release_asstays as it is: a run whose commit is still main's tip puts up its key's release,toolchain-linux-x86_64-<sysroot key>, and moves thesdk-alias; a run main has moved past returns beforelay_outand puts up nothing (build/release.rs:344-346,sdk_at_tipat:373). Today's release is 460,242,927 bytes (github-release-asset-names.txt).workflow_dispatchtopackages'sbuild.yml, naming the commit, through the build system's own GitHub client (build/release.rs). A run main has moved past dispatches nothing, so the merge it ran for is never built against the recipes on its own; the run of the merge that passed it checks both together. The last merge of any burst is never passed, so main's tip is checked whenever the tip's own run reachesrelease. A redtoolchainor crates.iopublishat the tip stops that run beforerelease, which leaves the tip unchecked and the repository at the older SDK until the next merge (the roast).publish.ymlpush runs (2026-10-08 to 2026-10-13), a later push run had already been created by the time all of the run's jobs had finished in 15 (publish-runs-overtaken.txt, script beside it, exit 0). Thereleasejob decides after its own checkout, cache restore and driver build too, so 15 is a floor, and a cold toolchain build raises it.publish.ymland the gate, all stage 2's work:build/release.rs'sPUBLISHER(:36) andpublisher()(:124), notbuild/ci.rs.PUBLISHERnamespublish.yml, andpublisher()acceptspushandworkflow_dispatchwhere it acceptsscheduleandworkflow_dispatchtoday; its tests (:680) move with it.id-token: writenarrows to the crates.iopublishjob alone, soreleaseholdscontents: writeand no OIDC token, andtoolchainholds neither.releasegets one concurrency group of its own,release, withcancel-in-progress: false, asnightly.yml's workflow-level group gives it today. Without it, two pushes minutes apart can each passsdk_at_tip(:344) and then act at once: twoput_ups of one tag collide (:459-483), or the older run moves thesdk-alias back. With it, onereleaseruns at a time, and GitHub replaces a pending older run with the newer one, whichsdk_at_tipwould refuse anyway. A reader checks this in the YAML, so no test is owed.releaseneeds: [publish, toolchain], so it reads crates.io only after this commit's crates are up; otherwisesdk_at_tiprefuses with "crates.io holds no …". That is the fixissues/a-release-that-decides-before-its-tips-crates-are-up-reds-the-nightly.mdnames, and stage 2 takes that issue up with its test.put_uprefuses aReplaceon a key tag,toolchain-linux-x86_64-<sysroot key>, by name, and only thesdk-alias's asset may be replaced. Theputtest (:743-757) names which tags may be replaced. A key whose toolchain is rebuilt to other bytes, after its cache entry was evicted while the key was still main's, then redsreleaseloudly instead of swapping the asset under apackagesrun that is reading it (the roast).nightly.ymllosesrelease, andreviewer.md's "nightly.yml'sreleaseis the only job grantedcontents: write" namespublish.yml's; stage 2 is briefed to edit that line.ToyOSOrg/packagesalone. It carries the Actions write permission, which GitHub's documentation for that endpoint names as the one it needs.packageswas considered and refused: GitHub's documentation says it disables a public repository's scheduled workflows after 60 days with no activity, and the recipes can sit unchanged that long.build.ymlruns on that dispatch, on a push topackages'smain, and on a pull request to it. It has three jobs.planpicks the ToyOS commit and writes the list of recipes as the matrix (cargo run -- --ci packages-plan). The job holds no secret.repositoryrelease's notes name: the commit of the last publish. That publish installed this commit's toolchain release, and a key release's asset never changes once it is up: stage 2 makesput_uprefuse aReplaceon a key tag by name, leavingReplaceto thesdk-alias (build/release.rs:449,:476-477). So the asset the commit names exists by construction, with the bytes it was installed with, and the verdict never waits on another repository's run. This is the one path;plannever falls back to main's tip or to another commit.repositoryrelease, andplanrefuses a push or a pull request by name. Stage 2's first publish is a dispatch, so this lasts only until stage 2's exit.packages.buildruns once per recipe. It checks out ToyOS andpackageswith its gitlinks, then runscargo run -- --ci package-build <name>. That command:release::lay_out's check), so the toolchain is the commit's own;build/licence.rs) over the package's crates and its files, and packs the package's corresponding source beside it wherever a licence requires one, as doom's GPL-2.0 does in §3;build/userlandhost.rs) over the source as any other OS builds it: with the source's own lock and no ToyOS patch, built and tested on the runner's host, and checked for the other two hosts, as--ci hostjudges an in-tree app today;The job holds no secret. A recipe's build scripts and proc macros run here, and since each recipe builds in a job of its own, one recipe's code cannot touch another's archive.
publishruns after everybuildjob, whatever each concluded, and only for a dispatch or a push onmain. It runs in thepublishenvironment. It checks out ToyOS, andpackageswithout its gitlinks, downloads the artifacts, and runscargo run -- --ci packages-publish, which runs no code from any recipe. The command:repositoryrelease's notes, which "An image behind the repository" below points at;build/publish.rsdoes today, and only then uploads it;timestamp.ymlruns daily. It signs a timestamp that names the same targets. From 30 days before the root expires it also goes red, after signing, naming the ceremony.One writer at a time.
publishandtimestamp.ymlshare one concurrency group: they run one at a time, and neither is ever cancelled.Hosting
The mirror is
https://github.com/ToyOSOrg/packages/releases/download/repository/.Flat names. A release's assets share one flat namespace, and GitHub's REST documentation says an upload renames a file whose name has special characters. Names made of
a-z 0-9 . _ -survive the upload (github-release-asset-names.txt).urlbecomes<name>-<sequence>-<target>.tar.gz, whichis_relativeaccepts (lib/update/src/repo.rs:930).publish.rs'sarchives/directory goes.Immutable names, as in the TUF specification's consistent snapshots. Roots and targets carry their version, and an archive carries its sequence.
The timestamp.
timestamp.txtis the only file that is replaced. GitHub replaces no asset in place, so the publisher deletes it and uploads the new one last. A client that reads in between is refused withAbsent, by name. A job that dies between the delete and the upload leaves no timestamp at all, and every client is refusedAbsentuntil the nexttimestamp.ymlrun, at most a day, or a dispatch of it (the roast).Pruning. After a publish whose timestamp names targets M, the publisher deletes:
It never deletes a root. A client that is mid-install holds targets M−1 at the oldest.
Size. GitHub's documentation caps an asset at 2 GiB, which is above
ARCHIVE_CAP(1 GiB,lib/update/src/repo.rs:53).Keys
packages'spublishenvironment, which is restricted tomainby GitHub's deployment branch policy. It is kept nowhere else.build/signing.rs), which this design does not change.pkgwalks to it at its next refresh with no new image: from then on an install or update refuses a targets the old key signed, and floors signed by the old key stop counting, which isrepo.rs's floor rule./state/pkg/, and/stateis served on the samedatavolume as/apps(lib/manifest/src/lib.rs:109). Until per-row views land, every row that can rewrite/appscan also deleteroot.<N+1>.txt; the walk then stops at root N, which still grants the stolen key, and a targets that key signed, naming an attacker's binary, launches. This is filed withissues/every-program-sees-only-the-files-it-was-given.mdstage 2 as its exit, at which onlypkgwrites/state/pkg/(below, "What the check does not close", and the roast).build/publish.rslosesone_key(:194), root minting and root renewal.images/pkg/root.1.txt, and no build fetches it.cargo runimage included, because the image key and the repository's root are independent of each other.The root-key ceremony
The tool is
toyos-root, a new crate that depends only ontoyos-updatewith itssignfeature, and on getrandom for minting. Stage 1 builds two commands and no more:new, which mints the root key, andsign, which signs root 1. Signing root N+1 from root N (--previous), renewal and rotation are a later stage's (migration stage 9).ccrow ofissues/the-build-runs-host-tools-outside-rust-and-qemu.md). Its link still needs the target triple's linker. As built, step 1.2 is measured foraarch64-apple-darwinalone, and it is built for Unix triples.build/signing.rs).The owner needs three things:
cargo run -- --repo-signing-key-newmints the signing key at~/.config/toyos/repo-signing-key(mode 0600) and prints its public key and fingerprint. Write the fingerprint on paper.cargo build --release -p toyos-root --target aarch64-apple-darwin, for an offline Mac of this Mac's macOS and architecture: the one measured path (ruling 4 on stage 1).toyos-rootbinary and the signing key's public line onto the carrying stick.toyos-root new <stick A>/root-keymints the root key (mode 0600) and prints its fingerprint. Write it on the paper.Copy
<stick A>/root-keyto stick B.toyos-root sign --key <stick A>/root-key --signing <the public line> --out <carrying stick>/root.1.txtwrites root 1 and prints it. Root 1 carries:Read the printout: both fingerprints must match the paper.
Unplug sticks A and B. Keep them apart and offline, each in a place of its own.
roots/root.1.txttopackages, and one that adds it to ToyOS asimages/pkg/root.1.txt. Each repository's checks read it back through the client: it must be self-signed under its root key, and grant the targets and timestamp roles to one other key.publishenvironment's secret on GitHub's settings page, then delete the local file.toyos-root sign --previous root.N.txtsigns root N+1 with the same root key, which meets both its predecessor's threshold and its own.What a package is
The format is a ustar
.tar.gzthat holds one directory,<name>/:<name>/<name>, a static PIE (issues/a-package-cannot-ship-its-own-libraries.md);std::env::current_exe()on every host.The item in
targets.<M>.txtgains two signed fields:sdk: the SDK identity the package was built against;program: the SHA-256 of<name>/<name>.A targets still holds one item per name and triple.
The name and version come from the item.
pkgholds the archive's top directory to the item's name, and takes the version from the item. Today it takes both from the archive (system/pkg/src/lib.rs:34-35,:100-112).version_ofgoes.The SDK identity is one function of the build system,
sdk::identity(root), built from the sysroot's witness rather than a second walk.sysroot::witness(build/sysroot.rs:341) already writes one line per.rs,.tomland.hfile ofSYSROOT_SOURCES(:71:toyos-abi,toyos, std's backend,toyos-elf,toyos-osreleaseand libc), each throughidentity::of, plus a line perSYSROOT_MANIFESTSentry (:83) over its bytes.toyos-window,filepicker-api,toyos-fontandtoyos-keymap.identity::of; a manifest is read as bytes, as the sysroot's key reads it./system/etc/os-release, as a new field oftoyos-osrelease. The builder writes it into each item.An image behind the repository.
pkgcompares the item'ssdkwith os-release's before it fetches anything. It refuses by name, naming both identities, and says to update the system first (the orchestrator's ruling 3).pkg listshows such an item as needing another SDK.build/release.rs's oneASSET(:33) is the toolchain. The only source is a ToyOS checkout at the commit the repository was published from, which therepositoryrelease's notes name: the person builds it, signed with the image key the machine's loader trusts, and installs it withupdate(issues/the-machine-updates-itself-without-ubuntu.md). The refusal names that release page.build/release.rs). Exit: every release at main's tip carries an image per target thatupdateinstalls, andpkg's refusal names it.pkgTwo programs.
/system/bin/pkgholds nonetstack. It writes/appsand/state/pkg, and it asks the person./system/bin/pkg-fetchis the fetcher. Its row receivesnetstack, and onlypkg's rowstartsit.pkgpasses as its stdout. The launcher keeps a caller's pipes (system/supervisor/src/main.rs:17-19)./system/etc/ssl/cert.pem, and follows GitHub's one redirect.pkg'sMirror(repo.rs) startspkg-fetchfor a file and reads at most cap + 1 bytes from its pipe.pkg's, not the fetcher's, as the owner accepted (6136067341, 3).pkgwrites the bytes it hashes, so once per-row views land nothing the network-facing process can reach is read again after the check.issues/every-program-sees-only-the-files-it-was-given.mdstage 2). Stage 3 files it as one issue, owned bypkgand the supervisor's launch check, with that stage as its exit, at which/state/pkg/is written bypkgalone andpkg-fetch's view holds no file. Until then, three things that issue names stay open:pkg-fetchsees every file the kernel serves;/state/pkg/staging/<file>betweenpkg's hash and its inflate (install step 4);/appscan delete the supervisor's newest root (Keys, and the launch check).Install:
pkg install <name>.repo::refresh). The image's root is/system/etc/pkg/root.1.txt, and the machine's copies are under/state/pkg/.sdkbefore fetching anything.pkg-fetchthroughrepo::Archive::takeinto/state/pkg/staging/<file>.Archive::finishchecks the length and the SHA-256.Okis the only way to make the token the inflater takes, so the inflater cannot be called on a stream that did not pass. The token proves that a stream passed; it does not prove that the staging file still holds those bytes when the inflater reads it.pkg-fetchincluded, can rewrite the staging file between the hash and the inflate. The launch check catches a swapped program, but not the data files inflated beside it. This is the second item of the gap above./apps/<name>/<sequence>.staged/, bounded as today by what the archive claims. Writemanifest.toml(name, version, sequence, sdk, archive digest, program) and a copy of the signed targets the package was installed from./apps/<name>/<sequence>/. Then write/apps/<name>/current.staged, naming the sequence, and rename it over/apps/<name>/current.The file server gains no operation for this:
Mounted::rename_all,lib/bcachefs/src/fs.rs:933).rename_one,:943).currentnames either the old version or the new one, whole either way, and the volume's sync makes either durable.write_store,system/supervisor/src/main.rs:1706).Streaming to a file retires the 256 MiB in-memory ceiling (
system/pkg/src/main.rs:24); the bound becomes the 1 GiBARCHIVE_CAP.Update:
pkg update [<name>].pkgkeeps the versioncurrentnames and the one before it, which a running instance may still read, and removes the rest./home/<user>/Apps/<name>is left alone.Remove deletes
/apps/<name>whole. The app's folder of the home stays.pkg listnames what is installed and what the held targets offers for this machine, and marks any item built for another SDK.pkg install <file>goes, andSHA256SUMSandpkg::sumsgo with it.The supervisor's launch check
At each launch of
/apps/<name>, the supervisor:current;/apps/<name>/<sequence>/<name>, as it already does for a grant (binary_digest,system/supervisor/src/main.rs:1699);/state/pkg/; that is the machine's newest root only while no row butpkgcan delete a copy (below);programdigest;sdkthat os-release names.The check ignores expiry, which is a matter of an install's freshness: an installed app still launches offline. A refusal names its reason, and for an SDK mismatch it names
pkg update <name>.lib/manifest/src/grants.rs:10-14), as ruling 2 says.image_from); the kernel also pages it in on demand.issues/a-swapped-binary-lives-where-any-process-can-rewrite-it.mdrecords the same window for a swapped service, and its exit, a spawn from what the supervisor verified, closes it here too./appscan put back an older signed version (issues/whether-pkg-alone-writes-apps.md)./appscan also delete/state/pkg/root.<N+1>.txt, on the samedatavolume (lib/manifest/src/lib.rs:109). The walk then stops at root N, which still grants the revoked key, and a targets that key signed passes all three conditions. Revocation holds at launch only once the supervisor's newest root sits where no other row can write: the gappkg's stage files, withissues/every-program-sees-only-the-files-it-was-given.mdstage 2 as its exit. No high-water mark is built now (the owner's ruling).What an installed app holds
This is unchanged, as
toyos_manifest::Program::viewgives it today:/apps/<name>, read-only, every version in it included;/home/<user>/Apps/<name>, read-write, as itsHOME;[apps]row's connectors: compositor, soundserver and filepicker.It gets no device, no
syscap, no launcher and nonetstack. From the track's stage 6 on, it holds the signed item's request ∩ the person's grant ∩ the image's ceiling.A branch whose SDK is not main's
An image pins the public root, so a branch that changes an SDK crate can install nothing: no item carries the branch's identity.
cargo run -- --packages <packages checkout> <name>…closes that gap:target/pkg-repository, the throwaway repositorybuild/publish.rswrites today.That image then pins the throwaway root, and uses the directory as its mirror, in place of the public ones.
pkgreads a directory mirror itself and starts nopkg-fetch. Each image has one repository, chosen by the build, as the image key is.repo.rs'sMirrorhas been a local directory since it was written.The apps, weighed
packagesCargo.tomlon crates.io's winit 0.30, softbuffer 0.4 and cpal 0.18, the versions the forks carry. Today it names fork branches…-sdk-0.2and a winit 0.31 beta. The repository is the owner's, and only he can make this changecc's defaults on each host, so it builds on all three (issues/apps-that-build-or-run-on-toyos-alone.md); its data read from beside itselfissues/five-apps-read-the-system-font-by-a-path-only-this-tree-has.md); calc's engine tests move with itSysInfo::decodebelowfilepicker-apimust also be published on crates.io: it is not inPUBLISHED(build/sdkversion.rs:23-30)system/filesTests
The owner's testing ruling, as the brief gave it: userspace is tested as units or modules, with at most a very cheap smoke test, and no test installs software and then drives it through steps. No guest test is added. The build rounds write these host tests:
toyos-root, stage 1: a root 1 it signs reads back through the client, self-signed under its root key and granting the targets and timestamp roles to the signing key. Stage 9 adds: a machine holding root N walks to the N+1 it signs with--previous.The publisher:
The builder:
[patch]and inforks.toml;sdk::identity: a comment-only edit keeps it, and a token edit tosdk/filepickerchanges it.pkg:repo.rs;pkg-fetchagainst a loopback TLS server on the host, which is hermetic and reaches no internet;The file server: after a rename over
currentand a kill before the sync finishes, the remounted volume'scurrentnames the old sequence or the new one, and that directory is whole.The supervisor's decision:
No test is written for a deleted root 2: until per-row views land the walk then stops at root 1 and launches, which is the recorded hole above, not a claim this design makes.
What goes with the apps. The searches are in
tests-that-name-the-apps.txt.toyfetch_reports(QEMU)tests/toyos.rs:471,[programs.toyfetch]intests/images/testcases.tomltoyos_osrelease's 8 host tests; a host test oftoyos_abi::syscall::SysInfo::decode(sdk/abi/src/syscall.rs:66), which has none today and lands in the same diff as the cut; and toyfetch's own unit test in its repositorypkg_install_gbae(owed metal row)issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md:125pkg's host tests above; the line is deleted, as isissues/pkg-install-gbaes-launch-client-exits-over-a-child-that-ends-with-it.mddoom_frames(owed metal row):207the_shipped_bank_*×3,doom1_wads_music_selects_the_instruments_it_selectsbuild/soundfont.rs--regen-soundfont, into doom's repositoryparser_identitiesapps/calc/testsconsent_prompt(QEMU),app_view(metal)tests/toyos.rsThere is no T14 audio row that plays through doom: the audio rows play the test runner's own tone clients.
Migration order
Before stage 1, the owner creates
ToyOSOrg/packagesand mints the dispatch token.The keys.
toyos-root'snewandsignof root 1, and nothing else of it;--repo-signing-key-new; the publisher's new key model, flat names and pruning.toyos-rootand the publisher's host tests above are green. Then the owner runs the ceremony, and root 1 is committed in both repositories.The packages CI publishes gbae.
sdk::identity, os-release's new field, and the items'sdkandprogramfields;--package-lockand--ci packages-plan,package-build,packages-publishandpackages-timestamp;--publishand itspackages.tomlgo, since the publisher now reads what the recipes built;put_uprefuses aReplaceon a key tag by name, leavingReplaceto thesdk-alias, and theputtest names which tags may be replaced;releasemoves topublish.yml,needs: [publish, toolchain], runs in its own concurrency group withcancel-in-progress: false, and dispatches only after it has put up its release;nightly.ymlloses it;packages-plantakes a dispatch's commit, and for a push or a pull request the commit therepositoryrelease's notes name;PUBLISHERnamespublish.ymlandpublisher()acceptspushandworkflow_dispatch, with its tests;publish.yml'sid-token: writenarrows from the workflow to the crates.iopublishjob;cargo_buildunderGuestEnvwhole;issues/a-release-that-decides-before-its-tips-crates-are-up-reds-the-nightly.md, whose exit's test it writes.packageswork: the two workflows and gbae's directory, which waits for the owner'sCargo.tomlchange..claude/agents/implementer.md:82, which puts a C program's recipe "in this repository", to nameToyOSOrg/packages, andreviewer.md's write-token line.pkg install gbae.pkg-fetchand the pipe mirror;update,listandremove;SHA256SUMSgo.consent_promptandapp_view:pkg-fetch's view, the staging file and the supervisor's newest root, withissues/every-program-sees-only-the-files-it-was-given.mdstage 2 as its exit;build/release.rs, with the exit given under "An image behind the repository".pkg's, the file server's and the supervisor's host tests are green, and the owner installs gbae by hand in QEMU withpkg install gbaeand runs it.cargo run -- --packages.doom. It gets its own repository and its directory in
packages. The tree loses:apps/doom,assets/DOOM1.WADandassets/soundfont.sf2;build/soundfont.rsand--regen-soundfont;DOOM_LEAVES(build/licence.rs:166) and doom's sections ofNOTICE;[programs.doom]and itsstartsentries;doom_framesline;Exit: the image's file list names no doom byte, the licence gate is green without
DOOM_LEAVES, and the repository publishes doom with its corresponding source.snake and calc, once the font call exists.
[patch]entries move toforks.toml. Thepackagespull request is merged right after the ToyOS one, and the run in between is red, by name.toyfetch.
forks.toml.SysInfo::decodehost test lands in the diff that cutstoyfetch_reports.editor and paint, once they are portable, with
filepicker-apiadded toPUBLISHED.Root renewal and rotation, owed before root 1 expires and at once if the signing key leaks.
toyos-root sign --previous, so root N+1 is signed from root N; a new signing key as a new public line; a new root key signing beside the old; the ceremony's step 4 in the module header.timestamp.yml's red, 30 days before root 1 expires, which names the ceremony. By then the host test that a machine holding root N walks to the N+1toyos-rootsigns is green.Each stage deletes the app's
[programs.<name>]row, itsstartsentries, and the guest-suite and track lines it meets. The stage that emptiesapps/also deletes:apps/CLAUDE.md;"apps"inPROGRAM_ROOTS(build/build.rs:199);apps/in the layout track's tree;package-buildholds the same gate.What
pkgneeds before it is production-readyOpen issues:
issues/a-package-is-a-directory-under-apps-and-the-installer-is-a-program.md, stages 2, 3, 4, 6 and 7. The build rounds rewrite this issue to this design: it is the issue this design takes up.issues/whether-pkg-alone-writes-apps.md, the owner's question.issues/every-program-sees-only-the-files-it-was-given.mdstage 2, andissues/any-declared-row-can-write-the-grants-store.md.issues/a-swapped-binary-lives-where-any-process-can-rewrite-it.md. Its exit also closes the launch check's window.issues/an-installed-gbae-browses-to-no-rom.md, andissues/a-question-waits-unseen-while-a-file-is-picked.md.issues/a-package-cannot-ship-its-own-libraries.md, andissues/toyos-builds-itself.md's "pkginstalls a toolchain".issues/remove-dir-all-empties-a-directory-and-leaves-it.md.issues/the-internet-clients-work-unchanged.mdstage 3, thehttps_tls13row on the T14.New with this design: everything in the migration above, and the two issues stage 3 files: the gap until per-row views land (
pkg-fetch's view, the staging file, the supervisor's newest root), and no published image at the repository's SDK.Servo
Servo is the motivating next package, and this design does not design for it. Beyond what doom needs, Servo needs:
netstackuntil its request can ask for it;issues/a-rust-std-binary-cannot-link-the-cxx-runtime.md);issues/the-build-runs-host-tools-outside-rust-and-qemu.mdfor each host tool its build runs, before its recipe lands. Round 1's review names mozjs-sys's configure and bindgen's libclang; this round did not measure them.The repository's one-SDK-at-a-time rule also rebuilds Servo for both targets at every SDK change.
The roast
packages, which makes three repositories for one breaking SDK change.release. A redtoolchainor crates.iopublishat the tip puts up no release and dispatches nothing, so the tip is unchecked and the repository stays at the older SDK until the next merge's run reachesrelease.packagespull request is judged against the last publish's SDK, not main's tip. A source bump that builds there can still break against a later merge's SDK, and that break shows only at the next dispatch, after the bump has merged. A push lands no package until a dispatch, unless no merge has landed since the last publish.main,packages'smain, or GitHub itself. The TUF specification would keep the targets key offline, and the delegation that PEP 458 uses to do so is folded away here.--previousis built./appscan delete the supervisor's copy of root N+1, and the stolen key's targets launches again. The only exit is the isolation track's stage 2; no high-water mark is built meanwhile.releasejob goes red. If it leaks, anyone can dispatch or cancelpackages's runs; the tip check means a dispatch naming an old commit publishes nothing.Absent.timestamp.txtand uploading the new one, a job that dies leaves every client refusedAbsentuntil the nexttimestamp.ymlrun, up to a day, or a dispatch of it.release. A key release's asset never changes once it is up, so where a key's cache entry is evicted while the key is still main's andtoolchain.ymlrebuilds it to other bytes,releaserefuses theReplaceby name and that run dispatches nothing until the key moves or the bytes match.issues/two-checkouts-of-one-tree-build-different-guest-bytes.md).packagespull request./appsstays writable to every declared row, which can put back an older signed version, and the launch check's window between digest and spawn stays open until the swapped-binary issue's exit.pkg-fetchis not yet isolated. It holds the network and still sees the whole tree until the per-row views land.Archive::finish's token proves a stream passed, not that the staging file still holds it; until per-row views land any row can rewrite it between the hash and the inflate, and the launch check covers the program, never its data files.pkg install doom.nightly.ymlforpublish.yml, its gatePUBLISHERandpublisher()move with it,releasetakes a concurrency group thatnightly.yml's gave it before, andimplementer.md's recipe line changes.Unsure
build.rsusescc's defaults.Published specifications and vendor documentation cited
permissionsandjobs.<job_id>.permissions: a job-level grant replaces the workflow's for that job;schedule: disabled after 60 days of inactivity.[patch]andrev,cargo install --locked, and custom profiles set from configuration.PROTOCOL.sshsig, whichlib/update/src/sig.rsimplements.🤖 Generated with Claude Code
https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy